renderer refactoring; controls for lookat

main
Clyne 5 months ago
parent fdc2d91285
commit 456b9f4cef
Signed by: clyne
GPG Key ID: 3267C8EBF3F9AFC7

@ -30,15 +30,17 @@ static int threads = 4;
static int SamplesPerPixel = 20; static int SamplesPerPixel = 20;
static int SamplesPerPixelTmp = 20; static int SamplesPerPixelTmp = 20;
static float Daylight = 0.5f; static float Daylight = 0.5f;
static std::unique_ptr<Renderer> renderer; static Renderer renderer;
static std::chrono::time_point<std::chrono::high_resolution_clock> renderStart; static std::chrono::time_point<std::chrono::high_resolution_clock> renderStart;
static std::chrono::duration<double> renderTime; static std::chrono::duration<double> renderTime;
static color ray_color(const ray& r, int depth = 50); static color ray_color(const ray& r, int depth = 50);
static void initiateRender(SDL_Surface *canvas); static void initiateRender(SDL_Surface *canvas);
static void showObjectControls(int index, std::unique_ptr<Object>& o); static void showObjectControls(int index, std::unique_ptr<Object>& o);
static void showCameraControls(SDL_Surface *canvas);
static void addRandomObject(); static void addRandomObject();
static void preview(SDL_Surface *canvas); static void preview(SDL_Surface *canvas);
static void exportScreenshot(SDL_Surface *canvas);
int main() int main()
{ {
@ -47,6 +49,8 @@ int main()
auto window = SDL_CreateWindow("raytrace", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, Width, Height, SDL_WINDOW_RESIZABLE); auto window = SDL_CreateWindow("raytrace", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, Width, Height, SDL_WINDOW_RESIZABLE);
auto canvas = SDL_CreateRGBSurfaceWithFormat(0, Width, Height, 32, SDL_PIXELFORMAT_RGBA8888); auto canvas = SDL_CreateRGBSurfaceWithFormat(0, Width, Height, 32, SDL_PIXELFORMAT_RGBA8888);
auto painter = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC /*| SDL_RENDERER_ACCELERATED*/); auto painter = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC /*| SDL_RENDERER_ACCELERATED*/);
auto tex = SDL_CreateTextureFromSurface(painter, canvas);
bool run = true;
ImGui::CreateContext(); ImGui::CreateContext();
ImGui_ImplSDL2_InitForSDLRenderer(window, painter); ImGui_ImplSDL2_InitForSDLRenderer(window, painter);
@ -57,17 +61,12 @@ int main()
for (auto i : std::views::iota(0, 10)) for (auto i : std::views::iota(0, 10))
addRandomObject(); addRandomObject();
std::cout << "Spawning threads..." << std::endl;
initiateRender(canvas); initiateRender(canvas);
auto tex = SDL_CreateTextureFromSurface(painter, canvas);
std::cout << "Entering render..." << std::endl;
bool run = true;
for (SDL_Event event; run;) { for (SDL_Event event; run;) {
while (SDL_PollEvent(&event)) { while (SDL_PollEvent(&event)) {
ImGui_ImplSDL2_ProcessEvent(&event); ImGui_ImplSDL2_ProcessEvent(&event);
if (event.type == SDL_QUIT) { if (event.type == SDL_QUIT) {
renderer->stop(); renderer.stop();
run = false; run = false;
} }
} }
@ -80,51 +79,36 @@ int main()
if (ImGui::SliderFloat("fov", &Camera.fieldOfView, 10, 160)) if (ImGui::SliderFloat("fov", &Camera.fieldOfView, 10, 160))
preview(canvas); preview(canvas);
ImGui::SameLine(); ImGui::SetNextItemWidth(80); ImGui::SameLine(); ImGui::SetNextItemWidth(80);
ImGui::InputInt("T", &threads); if (ImGui::InputInt("T", &threads))
ImGui::SetNextItemWidth(100); threads = std::max(threads, 1);
if (ImGui::InputDouble("X", &Camera.camera.x(), 0.1, 0.05, "%.2lf")) showCameraControls(canvas);
preview(canvas);
ImGui::SameLine(); ImGui::SetNextItemWidth(100);
if (ImGui::InputDouble("Y", &Camera.camera.y(), 0.1, 0.05, "%.2lf"))
preview(canvas);
ImGui::SameLine(); ImGui::SetNextItemWidth(100);
if (ImGui::InputDouble("Z", &Camera.camera.z(), 0.1, 0.05, "%.2lf"))
preview(canvas);
if (ImGui::SliderInt("samples", &SamplesPerPixel, 1, 200)) { if (ImGui::SliderInt("samples", &SamplesPerPixel, 1, 200)) {
SamplesPerPixelTmp = SamplesPerPixel; SamplesPerPixelTmp = SamplesPerPixel;
} }
ImGui::SliderFloat("shade", &Daylight, 0.25f, 1.f); ImGui::SliderFloat("shade", &Daylight, 0.25f, 1.f);
if (ImGui::Button("recalculate")) { if (ImGui::Button("recalculate"))
initiateRender(canvas); initiateRender(canvas);
}
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("export")) { if (ImGui::Button("export"))
std::string filename ("screenshot_"); exportScreenshot(canvas);
filename += std::to_string(int(randomN() * 1000000));
filename += ".png";
IMG_SavePNG(canvas, filename.c_str());
std::cout << "saved " << filename << std::endl;
}
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("exit")) { if (ImGui::Button("exit")) {
renderer->stop(); renderer.stop();
run = false; run = false;
} }
if (*renderer) { if (renderer) {
SDL_DestroyTexture(tex); SDL_DestroyTexture(tex);
tex = SDL_CreateTextureFromSurface(painter, canvas); tex = SDL_CreateTextureFromSurface(painter, canvas);
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("stop")) { if (ImGui::Button("stop"))
renderer->stop(); renderer.stop();
} ImGui::Text("wait... %u%%", renderer.progress());
ImGui::Text("wait... %u%%", renderer->progress());
} else if (renderTime == std::chrono::duration<double>::zero()) { } else if (renderTime == std::chrono::duration<double>::zero()) {
SDL_DestroyTexture(tex); SDL_DestroyTexture(tex);
tex = SDL_CreateTextureFromSurface(painter, canvas); tex = SDL_CreateTextureFromSurface(painter, canvas);
renderTime = std::chrono::high_resolution_clock::now() - renderStart; renderTime = std::chrono::high_resolution_clock::now() - renderStart;
SamplesPerPixel = SamplesPerPixelTmp; SamplesPerPixel = SamplesPerPixelTmp;
} else { } else {
@ -132,21 +116,20 @@ int main()
} }
ImGui::End(); ImGui::End();
ImGui::Begin("balls", nullptr, ImGuiWindowFlags_NoResize); { ImGui::Begin("balls", nullptr, ImGuiWindowFlags_NoResize);
std::ranges::for_each( std::ranges::for_each(
std::views::zip(std::views::iota(0), std::views::zip(std::views::iota(0), std::views::drop(world.objects, 1)),
std::views::drop(world.objects, 1)), [](auto io) { std::apply(showObjectControls, io); });
[](auto io) { std::apply(showObjectControls, io); });
if (ImGui::Button("add")) { if (ImGui::Button("add")) {
addRandomObject(); addRandomObject();
initiateRender(canvas); initiateRender(canvas);
} }
if (ImGui::Button("del")) { if (ImGui::Button("del")) {
world.objects.pop_back(); world.objects.pop_back();
initiateRender(canvas); initiateRender(canvas);
} }
} ImGui::End(); ImGui::End();
ImGui::Render(); ImGui::Render();
SDL_RenderClear(painter); SDL_RenderClear(painter);
@ -185,21 +168,23 @@ color ray_color(const ray& r, int depth)
void initiateRender(SDL_Surface *canvas) void initiateRender(SDL_Surface *canvas)
{ {
renderStart = std::chrono::high_resolution_clock::now(); if (renderer)
renderer.stop();
renderTime = std::chrono::duration<double>::zero(); renderTime = std::chrono::duration<double>::zero();
auto func = [format = canvas->format](auto x, auto y, auto pbuf) { auto func = [format = canvas->format](auto x, auto y) {
auto col = std::ranges::fold_left(std::views::iota(0, SamplesPerPixel), color(), auto col = std::ranges::fold_left(std::views::iota(0, SamplesPerPixel), color(),
[y, x](color c, int i) { return c + ray_color(Camera.getRay(x, y, true)); }); [y, x](color c, int i) { return c + ray_color(Camera.getRay(x, y, true)); });
col = col / SamplesPerPixel * 255; col = col / SamplesPerPixel * 255;
pbuf[y * Width + x] = SDL_MapRGBA(format, col.x(), col.y(), col.z(), 255); return SDL_MapRGB(format, col.x(), col.y(), col.z());
}; };
Camera.recalculate(); Camera.recalculate();
threads = std::clamp(threads, 1, Renderer::MaxThreads); renderer.setBuffer((uint32_t *)canvas->pixels, Width, Height);
renderer.reset(new Renderer(threads, func, Width, Height, renderStart = std::chrono::high_resolution_clock::now();
(uint32_t *)canvas->pixels)); renderer.start(func, threads);
} }
void showObjectControls(int index, std::unique_ptr<Object>& o) void showObjectControls(int index, std::unique_ptr<Object>& o)
@ -209,9 +194,9 @@ void showObjectControls(int index, std::unique_ptr<Object>& o)
ImGui::SetNextItemWidth(200); ImGui::SetNextItemWidth(200);
ImGui::Combo((std::string("mat") + idx).c_str(), ImGui::Combo((std::string("mat") + idx).c_str(),
reinterpret_cast<int *>(&o->M), "Lambertian\0Metal\0Dielectric\0"); reinterpret_cast<int *>(&o->M), "Lambertian\0Metal\0Dielectric\0");
//ImGui::SameLine(); ImGui::SetNextItemWidth(100); ImGui::SameLine(); ImGui::SetNextItemWidth(100);
//ImGui::InputDouble((std::string("radius") + idx).c_str(), ImGui::InputDouble((std::string("radius") + idx).c_str(),
// &o->radius, 0.1, 0.05, "%.2lf"); &dynamic_cast<Sphere *>(o.get())->radius, 0.1, 0.05, "%.2lf");
ImGui::SetNextItemWidth(100); ImGui::SetNextItemWidth(100);
ImGui::InputDouble((std::string("x") + idx).c_str(), ImGui::InputDouble((std::string("x") + idx).c_str(),
&o->center.x(), 0.05, 0.05, "%.2lf"); &o->center.x(), 0.05, 0.05, "%.2lf");
@ -223,6 +208,28 @@ void showObjectControls(int index, std::unique_ptr<Object>& o)
&o->center.z(), 0.1, 0.05, "%.2lf"); &o->center.z(), 0.1, 0.05, "%.2lf");
} }
void showCameraControls(SDL_Surface *canvas)
{
ImGui::SetNextItemWidth(100);
if (ImGui::InputDouble("X", &Camera.camera.x(), 0.1, 0.05, "%.2lf"))
preview(canvas);
ImGui::SameLine(); ImGui::SetNextItemWidth(100);
if (ImGui::InputDouble("Y", &Camera.camera.y(), 0.1, 0.05, "%.2lf"))
preview(canvas);
ImGui::SameLine(); ImGui::SetNextItemWidth(100);
if (ImGui::InputDouble("Z", &Camera.camera.z(), 0.1, 0.05, "%.2lf"))
preview(canvas);
ImGui::SetNextItemWidth(100);
if (ImGui::InputDouble("I", &Camera.lookat.x(), 0.1, 0.05, "%.2lf"))
preview(canvas);
ImGui::SameLine(); ImGui::SetNextItemWidth(100);
if (ImGui::InputDouble("J", &Camera.lookat.y(), 0.1, 0.05, "%.2lf"))
preview(canvas);
ImGui::SameLine(); ImGui::SetNextItemWidth(100);
if (ImGui::InputDouble("K", &Camera.lookat.z(), 0.1, 0.05, "%.2lf"))
preview(canvas);
}
void addRandomObject() void addRandomObject()
{ {
const point3 pos = vec3::random() * vec3(6, 0.8, 3) - vec3(3, 0, 3.8); const point3 pos = vec3::random() * vec3(6, 0.8, 3) - vec3(3, 0, 3.8);
@ -238,3 +245,12 @@ void preview(SDL_Surface *canvas)
initiateRender(canvas); initiateRender(canvas);
} }
void exportScreenshot(SDL_Surface *canvas)
{
std::string filename ("screenshot_");
filename += std::to_string(int(randomN() * 1000000));
filename += ".png";
IMG_SavePNG(canvas, filename.c_str());
std::cout << "saved " << filename << std::endl;
}

@ -3,48 +3,28 @@
#include <algorithm> #include <algorithm>
#include <atomic> #include <atomic>
#include <memory>
#include <ranges> #include <ranges>
#include <semaphore>
#include <thread> #include <thread>
class Renderer class Renderer
{ {
public: public:
static constexpr int MaxThreads = 64; template<typename Fn>
void start(Fn func, int tn) {
private: actives.store(tn);
int N; processed.store(0);
std::counting_semaphore<MaxThreads> Workers;
std::atomic_uint processed;
unsigned total;
std::atomic_bool Stop;
std::unique_ptr<std::thread> primary;
public:
Renderer(int n, auto func, unsigned width, unsigned height, auto pbuf):
N(n), Workers(N), processed(0), total(N * 8)
{
Stop.store(false); Stop.store(false);
auto threads = std::views::transform( if (primary.joinable())
std::views::chunk( primary.join();
std::views::cartesian_product(
std::views::iota(0u, width),
std::views::iota(0u, height),
std::views::single(pbuf)),
width * height / total),
[=, this](auto chunk) { return std::thread([=, this] { worker(func, chunk); }); });
primary.reset(new std::thread([=, this] { primary = std::thread(&Renderer::dispatchWorkers<Fn>, this, func);
for (auto th : threads) { }
Workers.acquire();
th.detach(); void setBuffer(std::uint32_t *pixelbuf, unsigned w, unsigned h) {
} pixelBuffer = pixelbuf;
for (int i : std::views::iota(0, N)) width = w;
Workers.acquire(); height = h;
Stop.store(true);
}));
} }
~Renderer() { ~Renderer() {
@ -61,20 +41,51 @@ public:
void stop() { void stop() {
Stop.store(true); Stop.store(true);
if (primary->joinable()) if (primary.joinable())
primary->join(); primary.join();
} }
private: private:
std::uint32_t *pixelBuffer = nullptr;
unsigned width = 0, height = 0, total = 0;
std::thread primary;
std::atomic_uint actives;
std::atomic_uint processed;
std::atomic_bool Stop;
template<typename Fn>
void dispatchWorkers(Fn func) {
const auto N = actives.load();
total = N * 16;
auto threads = std::views::transform(
std::views::chunk(
std::views::cartesian_product(
std::views::iota(0u, width),
std::views::iota(0u, height)),
width * height / total),
[=, this](auto chunk) { return std::thread([=, this] { worker(func, chunk); }); });
for (auto th : threads) {
while (actives.load() == 0)
std::this_thread::yield();
--actives;
th.detach();
}
while (actives.load() < N)
std::this_thread::yield();
Stop.store(true);
}
void worker(auto func, auto chunk) { void worker(auto func, auto chunk) {
for (auto xyb : chunk) { for (auto [x, y] : chunk) {
if (Stop.load()) if (Stop.load())
break; break;
std::apply(func, xyb); pixelBuffer[y * width + x] = func(x, y);
} }
processed++; ++processed;
Workers.release(); ++actives;
} }
}; };

@ -8,11 +8,11 @@
struct View struct View
{ {
static constexpr auto lookat = point3(0, 0, -1); // Point camera is looking at
static constexpr auto vup = vec3(0, 1, 0); // Camera-relative "up" direction static constexpr auto vup = vec3(0, 1, 0); // Camera-relative "up" direction
float fieldOfView = 90.f; float fieldOfView = 90.f;
point3 camera {0, 0.5, 0.5}; point3 camera {0, 0.5, 0.5};
point3 lookat {0, 0, -1}; // Point camera is looking at
float focalLength; float focalLength;
float viewportHeight; float viewportHeight;

Loading…
Cancel
Save