diff options
author | Keuin <[email protected]> | 2022-04-20 00:22:02 +0800 |
---|---|---|
committer | Keuin <[email protected]> | 2022-04-20 00:22:02 +0800 |
commit | 3fa4d41e9cac75df7514c2ff8dd27842aaafc4a7 (patch) | |
tree | 90e3222e900fee3a1417a230010dc0fb963aba9f | |
parent | ee3b665441b2f83041416098049cc16b6b63d942 (diff) |
Code refactor: make basic_viewport and aa_viewport no longer a child class. Move data into classes.
Code refactor: make threading.h more specific, reducing redundant data copies in memory.
Code refactor: make camera parameters more clear and well-defined in viewport.h, ready to extend camera flexibility.
-rw-r--r-- | aa.h | 85 | ||||
-rw-r--r-- | main_simple_scanner.cpp | 49 | ||||
-rw-r--r-- | threading.h | 41 | ||||
-rw-r--r-- | viewport.h | 74 |
4 files changed, 143 insertions, 106 deletions
@@ -14,64 +14,71 @@ #include <random> // Antialiasing viewport -template<typename T> -class aa_viewport : public viewport<T> { +// U: color depth, V: pos +template<typename U, typename V> +class aa_viewport { + vec3<V> cxyz; + vec3<V> screen_center; + uint32_t image_width; // how many pixels every row has + uint32_t image_height; // how many pixels every column has + V screen_hw; // determined if screen_height is known + V screen_hh; // determined if screen_width is known + hitlist &world; unsigned samples; - std::vector<basic_viewport<T>> *subviews; int threads; public: - aa_viewport(double width, double height, vec3d viewport_center, unsigned samples, int threads = -1) - : samples(samples), threads{(threads > 0) ? threads : (int)std::thread::hardware_concurrency()} { - assert(samples >= 1); - subviews = new std::vector<basic_viewport<T>>{samples, {width, height, viewport_center}}; - } - ~aa_viewport() { - delete subviews; + aa_viewport(const vec3<V> &cxyz, + const vec3<V> &screen_center, + uint16_t image_width, + uint16_t image_height, + V screen_hw, + V screen_hh, + hitlist &world, + unsigned samples, + int threads = -1) : + cxyz(cxyz), + screen_center(screen_center), + image_width(image_width), + image_height(image_height), + screen_hw(screen_hw), + screen_hh(screen_hh), + world(world), + samples(samples), + threads((threads > 0) ? threads : (int) std::thread::hardware_concurrency()) { + assert(samples >= 1); } - virtual bitmap<T> render(const hitlist &world, vec3d viewpoint, uint16_t image_width, uint16_t image_height) { + bitmap<U> render() { static constexpr auto seed = 123456789012345678ULL; - const unsigned thread_count = std::min((unsigned)threads, samples); + const unsigned thread_count = std::min((unsigned) threads, samples); std::cerr << "Preparing tasks..." << std::endl; - std::vector<bitmap<T>> images{samples, {0, 0}}; + std::vector<bitmap<U>> images{samples, {0, 0}}; std::mt19937_64 seedgen{seed}; // generates seeds for workers - const struct s_render_shared { - std::vector<basic_viewport<T>> &subs; - vec3d viewpoint; - uint16_t image_width; - uint16_t image_height; - const hitlist &world; - std::vector<bitmap<T>> &images; - } s_{.subs=*subviews, .viewpoint = viewpoint, - .image_width=image_width, .image_height=image_height, - .world=world, .images=images - }; - struct s_render_task { - uint32_t task_id; - uint64_t seed; + uint64_t bias_seed; uint64_t diffuse_seed; - const s_render_shared &shared; }; - thread_pool<s_render_task> pool{thread_count}; + thread_pool<s_render_task, typeof(*this), typeof(images)> pool{thread_count, *this, images}; timer tim{true}; std::cerr << "Seeding tasks..." << std::endl; tim.start_measure(); for (typeof(samples) i = 0; i < samples; ++i) { - pool.submit_task([](s_render_task &task) { - bias_ctx bc{task.seed}; - auto image = task.shared.subs[task.task_id].render( - task.shared.world, task.shared.viewpoint, - task.shared.image_width, task.shared.image_height, - bc, task.diffuse_seed); - task.shared.images[task.task_id] = image; + pool.submit_task([](size_t tid, s_render_task &task, const aa_viewport<U, V> &ctx, typeof(images) &images) { + basic_viewport<U, V> vp{ + ctx.cxyz, ctx.screen_center, + ctx.image_width, ctx.image_height, + ctx.screen_hw, ctx.screen_hh, + ctx.world + }; + bias_ctx bc{task.bias_seed}; + images[tid] = vp.render(task.diffuse_seed, bc); }, s_render_task{ - .task_id = i, .seed=seedgen(), .diffuse_seed=seedgen(), .shared=s_ + .bias_seed=seedgen(), .diffuse_seed=seedgen() }); } tim.stop_measure(); @@ -84,12 +91,10 @@ public: tim.stop_measure(); std::cerr << "Finish rendering sub-pixels. Speed: " << 1.0 * tim.duration().count() / samples << "sec/image, " << 1.0 * samples * image_width * image_height / tim.duration().count() << " pixel/sec" << std::endl; - return bitmap<T>::average(images); + return bitmap<U>::average(images); } }; -using aa_viewport8b = aa_viewport<uint8_t>; - #endif //RT_AA_H diff --git a/main_simple_scanner.cpp b/main_simple_scanner.cpp index 6e79293..e3aedc6 100644 --- a/main_simple_scanner.cpp +++ b/main_simple_scanner.cpp @@ -23,8 +23,10 @@ //#define SCENE_REFLECT #define SCENE_DIALECT -// T: intermediate color depth -template<typename T> +static constexpr uint64_t default_diffuse_seed = 123456789012345678ULL; + +// T: color depth, V: pos +template<typename T, typename V> void generate_image(uint16_t image_width, uint16_t image_height, double viewport_width, double focal_length, double sphere_z, double sphere_r, unsigned samples, const std::string &caption = "", unsigned caption_scale = 1) { @@ -35,13 +37,30 @@ void generate_image(uint16_t image_width, uint16_t image_height, double viewport } std::cerr << "Initializing context..." << std::endl; double r = 1.0 * image_width / image_height; - viewport<T> *vp; - if (samples == 1) { - vp = new basic_viewport<T>{viewport_width, viewport_width / r, vec3d{0, 0, -focal_length}}; - } else { - vp = new aa_viewport<T>{viewport_width, viewport_width / r, vec3d{0, 0, -focal_length}, samples}; - } hitlist world; + + //////////////// + // noaa rendering + bias_ctx no_bias{}; + basic_viewport<T, V> vp_noaa{ + vec3<V>::zero(), // camera position as the coordinate origin + vec3d{0, 0, -focal_length}, + image_width, image_height, + viewport_width / 2.0, ((double) image_height / image_width) * viewport_width / 2.0, + world + }; + //////////////// + + //////////////// + // aa rendering + aa_viewport<T, V> vp_aa{ + vec3<V>::zero(), // camera position as the coordinate origin + vec3d{0, 0, -focal_length}, + image_width, image_height, + viewport_width / 2.0, ((double) image_height / image_width) * viewport_width / 2.0, + world, samples + }; + //////////////// #ifdef SCENE_DIFFUSE material_diffuse_lambertian materi{0.5}; world.add_object(std::make_shared<sphere>( @@ -76,8 +95,7 @@ void generate_image(uint16_t image_width, uint16_t image_height, double viewport timer tm; std::cerr << "Rendering..." << std::endl; tm.start_measure(); - auto image = vp->render(world, vec3d::zero(), - image_width, image_height); // camera position as the coordinate origin + auto image = ((samples == 1) ? vp_noaa.render(default_diffuse_seed, no_bias) : vp_aa.render()); tm.stop_measure(); std::cerr << "Applying gamma2..." << std::endl; tm.start_measure(); @@ -94,7 +112,6 @@ void generate_image(uint16_t image_width, uint16_t image_height, double viewport } else { std::cerr << "NOPRINT is defined. PPM Image won't be printed." << std::endl; } - delete vp; } int main(int argc, char **argv) { @@ -115,9 +132,9 @@ int main(int argc, char **argv) { cap = std::string{argv[8]}; } const auto image_width = std::stoul(iw); - generate_image<uint16_t>(image_width, std::stoul(ih), - std::stod(vw), std::stod(fl), - std::stod(sz), std::stod(sr), - std::stoul(sp), cap, - std::max((int) (1.0 * image_width * 0.010 / 8), 1)); + generate_image<uint16_t, double>(image_width, std::stoul(ih), + std::stod(vw), std::stod(fl), + std::stod(sz), std::stod(sr), + std::stoul(sp), cap, + std::max((int) (1.0 * image_width * 0.010 / 8), 1)); }
\ No newline at end of file diff --git a/threading.h b/threading.h index 73a1741..6d79494 100644 --- a/threading.h +++ b/threading.h @@ -18,31 +18,36 @@ // Tasks should be added into the queue before starting. // Once the task queue is empty, threads quit. +template<typename T_Args, typename T_ImmuCtx, typename T_MutCtx> +using task_func_t = void (*)(size_t, T_Args &, const T_ImmuCtx &, T_MutCtx &); + // internal usage -template<typename T> +template<typename T, typename U, typename V> struct s_task { - void (*f)(T &); - + task_func_t<T, U, V> f; T arg; }; -template<typename T> +template<typename T, typename U, typename V> class thread_pool { unsigned thread_count; std::vector<std::thread> workers; std::atomic<size_t> counter{0}; // index to the first available task in queue - std::vector<s_task<T>> tasks; + std::vector<s_task<T, U, V>> tasks; + const U &shared_ctx; // reference to immutable shared context + V &mut_shared_ctx; // mutable shared context void worker_main(); public: - explicit thread_pool(unsigned thread_count) : thread_count{thread_count} { + explicit thread_pool(unsigned thread_count, const U &shared_ctx, V &mut_shared_ctx) : + thread_count{thread_count}, shared_ctx{shared_ctx}, mut_shared_ctx{mut_shared_ctx} { std::cerr << "Using " << (counter.is_lock_free() ? "lock-free" : "locking") << " dispatcher." << std::endl; } // Thread unsafe! - void submit_task(void (*f)(T &), T &&t); + void submit_task(task_func_t<T, U, V> f, T &&t); void start(); @@ -50,36 +55,36 @@ public: void wait(); }; -template<typename T> -void thread_pool<T>::start() { +template<typename T, typename U, typename V> +void thread_pool<T, U, V>::start() { if (workers.empty()) { for (typeof(thread_count) i = 0; i < thread_count; ++i) { - workers.emplace_back(std::thread{&thread_pool<T>::worker_main, this}); + workers.emplace_back(std::thread{&thread_pool<T, U, V>::worker_main, this}); } } else { // TODO } } -template<typename T> -void thread_pool<T>::worker_main() { +template<typename T, typename U, typename V> +void thread_pool<T, U, V>::worker_main() { const auto max_cnt = tasks.size(); while (true) { const auto i = counter.fetch_add(1, std::memory_order_relaxed); // we only need atomicity if (i >= max_cnt) break; // all tasks are done auto &task = tasks[i]; - task.f(task.arg); + task.f(i, task.arg, shared_ctx, mut_shared_ctx); } } // Do not submit after starting. -template<typename T> -void thread_pool<T>::submit_task(void (*f)(T &), T &&t) { - tasks.push_back(s_task<T>{.f=f, .arg=std::move(t)}); +template<typename T, typename U, typename V> +void thread_pool<T, U, V>::submit_task(task_func_t<T, U, V> f, T &&t) { + tasks.push_back(s_task<T, U, V>{.f=f, .arg=std::move(t)}); } -template<typename T> -void thread_pool<T>::wait() { +template<typename T, typename U, typename V> +void thread_pool<T, U, V>::wait() { for (auto &th: workers) { th.join(); } @@ -18,6 +18,7 @@ #include <iostream> #include <cstdint> #include <random> +#include <cmath> // bias context, used for placing sub-pixels class bias_ctx { @@ -41,32 +42,40 @@ public: } }; -template<typename T> -class viewport { -public: - virtual bitmap<T> render(const hitlist &world, vec3d viewpoint, uint16_t image_width, uint16_t image_height) = 0; - - virtual ~viewport() = default; -}; - +// TODO rename to camera // Single sampled viewport which supports bias sampling -template<typename T> -class basic_viewport : public viewport<T> { - const double half_width, half_height; // viewport size - const vec3d center; // coordinate of the viewport center point - +// U: color depth, V: pos +template<typename U, typename V> +class basic_viewport { + vec3<V> cxyz; // coordinate of the focus point + vec3<V> screen_center; +// double pitch; // TODO implement +// double yaw; // TODO implement + uint32_t image_width; // how many pixels every row has + uint32_t image_height; // how many pixels every column has + V screen_hw; // determined if screen_height is known + V screen_hh; // determined if screen_width is known +// double fov_h; // horizontal FOV, determined if screen_width or screen_height is known +// double focus_length; // distance between the focus point and the image screen + hitlist &world; public: - basic_viewport() = delete; - basic_viewport(double width, double height, vec3d viewport_center) : - half_width(width / 2.0), half_height(height / 2.0), center(viewport_center) {} + basic_viewport(const vec3<V> &cxyz, const vec3<V> &screen_center, + uint32_t image_width, uint32_t image_height, + double fov_h, hitlist &world) : + cxyz{cxyz}, screen_center{screen_center}, image_width{image_width}, image_height{image_height}, + screen_hw{(cxyz - screen_center).norm() * tan((double) fov_h / 2.0)}, + screen_hh{screen_hw * ((double) image_height / image_width)}, + world{world} {} - virtual bitmap<T> - render(const hitlist &world, vec3d viewpoint, uint16_t image_width, uint16_t image_height) override { - bias_ctx bc{}; - static constexpr uint64_t default_diffuse_seed = 123456789012345678ULL; - return render(world, viewpoint, image_width, image_height, bc, default_diffuse_seed); - } + basic_viewport(const vec3<V> &cxyz, const vec3<V> &screen_center, + uint32_t image_width, uint32_t image_height, + double screen_hw, double screen_hh, + hitlist &world) : + cxyz{cxyz}, screen_center{screen_center}, image_width{image_width}, image_height{image_height}, + screen_hw{screen_hw}, + screen_hh{screen_hh}, + world{world} {} /** * Generate the image seen on given viewpoint. @@ -74,13 +83,14 @@ public: * @param by bias on y axis (0.0 <= by < 1.0) * @return */ - virtual bitmap<T> render(const hitlist &world, vec3d viewpoint, - uint16_t image_width, uint16_t image_height, - bias_ctx &bias, uint64_t diffuse_seed) const { - bitmap<T> image{image_width, image_height}; + bitmap<U> render(uint64_t diffuse_seed, bias_ctx &bias + /* by putting thread-specific parameters in call argument list, make users convenient*/) const { + // The implementation keep all mutable state in local stack, + // keeping the class immutable and thread-safe. + bitmap<U> image{image_width, image_height}; random_uv_gen_3d ruvg{diffuse_seed}; - double bx, by; - const auto r = center - viewpoint; + V bx, by; + const auto r = screen_center - cxyz; const int img_hw = image_width / 2, img_hh = image_height / 2; // iterate over every pixel on the image for (int j = -img_hh + 1; j <= img_hh; ++j) { // axis y, transformation is needed @@ -91,13 +101,13 @@ public: assert(bx < 1.0); assert(by < 1.0); const vec3d off{ - .x=(1.0 * i + bx) / img_hw * half_width, - .y=(1.0 * j + by) / img_hh * half_height, + .x=(1.0 * i + bx) / img_hw * screen_hw, + .y=(1.0 * j + by) / img_hh * screen_hh, .z=0.0 }; // offset on screen plane const auto dir = r + off; // direction vector from camera to current pixel on screen - ray3d ray{viewpoint, dir}; // from camera to pixel (on the viewport) - const auto pixel = world.color<T>(ray, ruvg); + ray3d ray{cxyz, dir}; // from camera to pixel (on the viewport) + const auto pixel = world.color<U>(ray, ruvg); const auto x_ = i + img_hw, y_ = -j + img_hh; image.set(x_, y_, pixel); |