]> code.bitgloo.com Git - clyne/raytracer.git/commitdiff
renderer refactoring; controls for lookat
authorClyne Sullivan <clyne@bitgloo.com>
Sat, 18 May 2024 12:52:06 +0000 (08:52 -0400)
committerClyne Sullivan <clyne@bitgloo.com>
Sat, 18 May 2024 12:52:06 +0000 (08:52 -0400)
main.cpp
renderer.h
view.h

index 6fecf204fdf6a7c2e3b8ed27cc93855e0c263db1..6d1e1d267c7ccbe5a9457368026a969ff3b8534e 100644 (file)
--- 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> renderer;
+static Renderer renderer;
 static std::chrono::time_point<std::chrono::high_resolution_clock> renderStart;
 static std::chrono::duration<double> 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<Object>& 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<double>::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<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(),
             [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<Object>& o)
@@ -209,9 +194,9 @@ void showObjectControls(int index, std::unique_ptr<Object>& o)
     ImGui::SetNextItemWidth(200);
     ImGui::Combo((std::string("mat") + idx).c_str(),
         reinterpret_cast<int *>(&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<Sphere *>(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<Object>& 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;
+}
+
index 864c0b8e3dc2959ef4e4085a4110c11ee9c312d1..a5b2ec553a199792efb448984407c436fc345a0c 100644 (file)
@@ -3,48 +3,28 @@
 
 #include <algorithm>
 #include <atomic>
-#include <memory>
 #include <ranges>
-#include <semaphore>
 #include <thread>
 
 class Renderer
 {
 public:
-    static constexpr int MaxThreads = 64;
-
-private:
-    int N;
-    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)
-    {
+    template<typename Fn>
+    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<Fn>, 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<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) {
-        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 0f973580cb422790091d6fd142da293849991fe3..08c5641415839d0076a3f7ca9d630ec03aaffffb 100644 (file)
--- 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;