diff --git a/main.cpp b/main.cpp index 6fecf20..6d1e1d2 100644 --- a/main.cpp +++ b/main.cpp @@ -30,15 +30,17 @@ static int threads = 4; static int SamplesPerPixel = 20; static int SamplesPerPixelTmp = 20; static float Daylight = 0.5f; -static std::unique_ptr renderer; +static Renderer renderer; static std::chrono::time_point renderStart; static std::chrono::duration renderTime; static color ray_color(const ray& r, int depth = 50); static void initiateRender(SDL_Surface *canvas); static void showObjectControls(int index, std::unique_ptr& o); +static void showCameraControls(SDL_Surface *canvas); static void addRandomObject(); static void preview(SDL_Surface *canvas); +static void exportScreenshot(SDL_Surface *canvas); 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 canvas = SDL_CreateRGBSurfaceWithFormat(0, Width, Height, 32, SDL_PIXELFORMAT_RGBA8888); auto painter = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC /*| SDL_RENDERER_ACCELERATED*/); + auto tex = SDL_CreateTextureFromSurface(painter, canvas); + bool run = true; ImGui::CreateContext(); ImGui_ImplSDL2_InitForSDLRenderer(window, painter); @@ -57,17 +61,12 @@ int main() for (auto i : std::views::iota(0, 10)) addRandomObject(); - std::cout << "Spawning threads..." << std::endl; initiateRender(canvas); - auto tex = SDL_CreateTextureFromSurface(painter, canvas); - - std::cout << "Entering render..." << std::endl; - bool run = true; for (SDL_Event event; run;) { while (SDL_PollEvent(&event)) { ImGui_ImplSDL2_ProcessEvent(&event); if (event.type == SDL_QUIT) { - renderer->stop(); + renderer.stop(); run = false; } } @@ -80,51 +79,36 @@ int main() if (ImGui::SliderFloat("fov", &Camera.fieldOfView, 10, 160)) preview(canvas); ImGui::SameLine(); ImGui::SetNextItemWidth(80); - ImGui::InputInt("T", &threads); - 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); + if (ImGui::InputInt("T", &threads)) + threads = std::max(threads, 1); + showCameraControls(canvas); if (ImGui::SliderInt("samples", &SamplesPerPixel, 1, 200)) { SamplesPerPixelTmp = SamplesPerPixel; } ImGui::SliderFloat("shade", &Daylight, 0.25f, 1.f); - if (ImGui::Button("recalculate")) { + if (ImGui::Button("recalculate")) initiateRender(canvas); - } ImGui::SameLine(); - if (ImGui::Button("export")) { - 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; - } + if (ImGui::Button("export")) + exportScreenshot(canvas); ImGui::SameLine(); if (ImGui::Button("exit")) { - renderer->stop(); + renderer.stop(); run = false; } - if (*renderer) { + if (renderer) { SDL_DestroyTexture(tex); tex = SDL_CreateTextureFromSurface(painter, canvas); ImGui::SameLine(); - if (ImGui::Button("stop")) { - renderer->stop(); - } - ImGui::Text("wait... %u%%", renderer->progress()); + if (ImGui::Button("stop")) + renderer.stop(); + ImGui::Text("wait... %u%%", renderer.progress()); } else if (renderTime == std::chrono::duration::zero()) { SDL_DestroyTexture(tex); tex = SDL_CreateTextureFromSurface(painter, canvas); - renderTime = std::chrono::high_resolution_clock::now() - renderStart; SamplesPerPixel = SamplesPerPixelTmp; } else { @@ -132,21 +116,20 @@ int main() } ImGui::End(); - ImGui::Begin("balls", nullptr, ImGuiWindowFlags_NoResize); { - std::ranges::for_each( - std::views::zip(std::views::iota(0), - std::views::drop(world.objects, 1)), - [](auto io) { std::apply(showObjectControls, io); }); + ImGui::Begin("balls", nullptr, ImGuiWindowFlags_NoResize); + std::ranges::for_each( + std::views::zip(std::views::iota(0), std::views::drop(world.objects, 1)), + [](auto io) { std::apply(showObjectControls, io); }); - if (ImGui::Button("add")) { - addRandomObject(); - initiateRender(canvas); - } - if (ImGui::Button("del")) { - world.objects.pop_back(); - initiateRender(canvas); - } - } ImGui::End(); + if (ImGui::Button("add")) { + addRandomObject(); + initiateRender(canvas); + } + if (ImGui::Button("del")) { + world.objects.pop_back(); + initiateRender(canvas); + } + ImGui::End(); ImGui::Render(); SDL_RenderClear(painter); @@ -185,21 +168,23 @@ color ray_color(const ray& r, int depth) void initiateRender(SDL_Surface *canvas) { - renderStart = std::chrono::high_resolution_clock::now(); + if (renderer) + renderer.stop(); + renderTime = std::chrono::duration::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(), [y, x](color c, int i) { return c + ray_color(Camera.getRay(x, y, true)); }); 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(); - threads = std::clamp(threads, 1, Renderer::MaxThreads); - renderer.reset(new Renderer(threads, func, Width, Height, - (uint32_t *)canvas->pixels)); + renderer.setBuffer((uint32_t *)canvas->pixels, Width, Height); + renderStart = std::chrono::high_resolution_clock::now(); + renderer.start(func, threads); } void showObjectControls(int index, std::unique_ptr& o) @@ -209,9 +194,9 @@ void showObjectControls(int index, std::unique_ptr& o) ImGui::SetNextItemWidth(200); ImGui::Combo((std::string("mat") + idx).c_str(), reinterpret_cast(&o->M), "Lambertian\0Metal\0Dielectric\0"); - //ImGui::SameLine(); ImGui::SetNextItemWidth(100); - //ImGui::InputDouble((std::string("radius") + idx).c_str(), - // &o->radius, 0.1, 0.05, "%.2lf"); + ImGui::SameLine(); ImGui::SetNextItemWidth(100); + ImGui::InputDouble((std::string("radius") + idx).c_str(), + &dynamic_cast(o.get())->radius, 0.1, 0.05, "%.2lf"); ImGui::SetNextItemWidth(100); ImGui::InputDouble((std::string("x") + idx).c_str(), &o->center.x(), 0.05, 0.05, "%.2lf"); @@ -223,6 +208,28 @@ void showObjectControls(int index, std::unique_ptr& o) &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() { 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); } +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; +} + diff --git a/renderer.h b/renderer.h index 864c0b8..a5b2ec5 100644 --- a/renderer.h +++ b/renderer.h @@ -3,48 +3,28 @@ #include #include -#include #include -#include #include class Renderer { public: - static constexpr int MaxThreads = 64; - -private: - int N; - std::counting_semaphore Workers; - std::atomic_uint processed; - unsigned total; - std::atomic_bool Stop; - std::unique_ptr primary; - -public: - Renderer(int n, auto func, unsigned width, unsigned height, auto pbuf): - N(n), Workers(N), processed(0), total(N * 8) - { + template + void start(Fn func, int tn) { + actives.store(tn); + processed.store(0); Stop.store(false); - auto threads = std::views::transform( - std::views::chunk( - 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); }); }); + if (primary.joinable()) + primary.join(); - primary.reset(new std::thread([=, this] { - for (auto th : threads) { - Workers.acquire(); - th.detach(); - } - for (int i : std::views::iota(0, N)) - Workers.acquire(); - Stop.store(true); - })); + primary = std::thread(&Renderer::dispatchWorkers, this, func); + } + + void setBuffer(std::uint32_t *pixelbuf, unsigned w, unsigned h) { + pixelBuffer = pixelbuf; + width = w; + height = h; } ~Renderer() { @@ -61,20 +41,51 @@ public: void stop() { Stop.store(true); - if (primary->joinable()) - primary->join(); + if (primary.joinable()) + primary.join(); } 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 + 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) { - for (auto xyb : chunk) { + for (auto [x, y] : chunk) { if (Stop.load()) break; - std::apply(func, xyb); + pixelBuffer[y * width + x] = func(x, y); } - processed++; - Workers.release(); + ++processed; + ++actives; } }; diff --git a/view.h b/view.h index 0f97358..08c5641 100644 --- a/view.h +++ b/view.h @@ -8,11 +8,11 @@ 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 float fieldOfView = 90.f; point3 camera {0, 0.5, 0.5}; + point3 lookat {0, 0, -1}; // Point camera is looking at float focalLength; float viewportHeight;