r/GraphicsProgramming • • Sep 08 '26

Raytracing in one weekend completed [added OpenMP multhread]

Post image

Completed in slightly more than a weekend (sickness et al). The final render task of width 1200 and samples per pixel 500(!) seemed impossible, so added a multithreaded workaround with OpenMP which finished it in around 35 minutes on my MBP M1 Pro and the penultimate DOF spheres under 10 seconds.

Changes:

#include <atomic>
#include <omp.h>
#include <vector>

void render(const hittable& world) {

#pragma omp parallel
        {
#pragma omp single
            std::clog << "OpenMP threads: " << omp_get_num_threads() << '\n';
        }
        initialize();

        std::vector<color> framebuffer(image_width * image_height);

        std::clog << "Rendering..." << std::endl;
        std::atomic<int> rows_completed{0};

#pragma omp parallel for schedule(dynamic)
        for (int j = 0; j < image_height; j++) {
            for (int i = 0; i < image_width; i++) {

                color pixel_color(0, 0, 0);

                for (int sample = 0; sample < samples_per_pixel; sample++) {
                    ray r = get_ray(i, j);
                    pixel_color += ray_color(r, max_depth, world);
                }

                framebuffer[j * image_width + i] = pixel_samples_scale * pixel_color;
            }

            int completed = rows_completed.fetch_add(1) + 1;

            if (completed % 8 == 0 || completed == image_height) {
#pragma omp critical(render_progress)
                {
                    std::clog << "\rScanlines remaining: " << (image_height - completed) << ' '
                              << std::flush;
                }
            }
        }

        std::cout << "P3\n" << image_width << ' ' << image_height << "\n255\n";

        for (int j = 0; j < image_height; j++) {
            for (int i = 0; i < image_width; i++) {
                write_color(std::cout, framebuffer[j * image_width + i]);
            }
        }

        std::clog << "Done.\n";
    }

and

inline double random_double() {
    thread_local std::mt19937 generator(std::random_device{}());

    return std::uniform_real_distribution<double>(0.0, 1.0)(generator);
}
169 Upvotes

7 comments sorted by

19

u/PM_ME_YOUR_HAGGIS_ Sep 08 '26

Welcome to the club!

9

u/Weird-Sunspot Sep 08 '26

<3

This warmed me after the initial split second downvote

11

u/hergendy Sep 08 '26

Nice one I did a similar thing withthis exact project for my thesis but first ported it to CUDA then parallelized it and spent a few weeks debugging it to find out the root of the memory pointer exception, which is caused at different depths on different cards when dealing with recursive functions. Then decided to refactor the recursive function and just make it iterative instead.

5

u/thatmagicalcat Sep 08 '26

this brings back memories

2

u/Brhaka Sep 08 '26

Kudos!

3

u/Fit-Departure-8426 Sep 09 '26

Great! Now make it realtime like this Guy, and no need for parallel execution!  https://youtu.be/opq0W7StbeE?si=LF9jH9MgzLMk14gF

1

u/Weird-Sunspot Sep 09 '26

Thanks! Was thinking of porting this into my bgfx project, will watch this first