diff --git a/src/base/integrator.cpp b/src/base/integrator.cpp index 985b17e7..70f714b3 100644 --- a/src/base/integrator.cpp +++ b/src/base/integrator.cpp @@ -73,7 +73,6 @@ void ProgressiveIntegrator::Instance::_render_one_camera( auto pixel_count = resolution.x * resolution.y; sampler()->reset(command_buffer, resolution, pixel_count, spp); - command_buffer << pipeline().printer().reset(); command_buffer << compute::synchronize(); LUISA_INFO( diff --git a/src/base/pipeline.cpp b/src/base/pipeline.cpp index 9ef54b54..b699ac3b 100644 --- a/src/base/pipeline.cpp +++ b/src/base/pipeline.cpp @@ -12,8 +12,7 @@ namespace luisa::render { inline Pipeline::Pipeline(Device &device) noexcept : _device{device}, _bindless_array{device.create_bindless_array(bindless_array_capacity)}, - _general_buffer_arena{luisa::make_unique(device, 16_M)}, - _printer{luisa::make_unique(device)} {} + _general_buffer_arena{luisa::make_unique(device, 16_M)} {} Pipeline::~Pipeline() noexcept = default; @@ -50,7 +49,7 @@ luisa::unique_ptr Pipeline::create(Device &device, Stream &stream, con pipeline->_differentiation = luisa::make_unique(*pipeline, stream); } - stream << pipeline->printer().reset(); + stream << synchronize(); auto initial_time = std::numeric_limits::max(); for (auto c : scene.cameras()) { if (c->shutter_span().x < initial_time) { diff --git a/src/base/pipeline.h b/src/base/pipeline.h index f77ff5cf..d37e38e5 100644 --- a/src/base/pipeline.h +++ b/src/base/pipeline.h @@ -46,7 +46,6 @@ using compute::Image; using compute::Mesh; using compute::PixelStorage; using compute::Polymorphic; -using compute::Printer; using compute::Ray; using compute::Resource; using compute::Triangle; @@ -96,7 +95,6 @@ class Pipeline { Buffer _transform_matrix_buffer; luisa::unordered_map _named_ids; // other things - luisa::unique_ptr _printer; bool _any_dynamic_transforms{false}; public: @@ -220,8 +218,6 @@ class Pipeline { void render(Stream &stream) noexcept; luisa::vector render_diff(Stream &stream, luisa::vector> &grads) noexcept; luisa::vector render_with_return(Stream &stream) noexcept; - [[nodiscard]] auto &printer() noexcept { return *_printer; } - [[nodiscard]] auto &printer() const noexcept { return *_printer; } [[nodiscard]] uint named_id(luisa::string_view name) const noexcept; template [[nodiscard]] auto buffer(I &&i) const noexcept { return _bindless_array->buffer(std::forward(i)); } diff --git a/src/films/display.cpp b/src/films/display.cpp index 8f437bef..f879ef62 100644 --- a/src/films/display.cpp +++ b/src/films/display.cpp @@ -2,10 +2,12 @@ // Created by Mike Smith on 2023/6/14. // +#include + #include #include -#include +#include #include #include @@ -96,15 +98,20 @@ class DisplayInstance final : public Film::Instance { private: luisa::unique_ptr _base; - luisa::unique_ptr _window; + luisa::unique_ptr _window; Image _framebuffer; - Swapchain _swapchain; - Shader2D<> _blit; + Shader2D _blit; + Shader2D> _clear; Clock _clock; - mutable double _last_frame_time; + Stream *_stream{}; + ImTextureID _background{}; + mutable double _last_frame_time{}; + mutable int _tone_mapping{}; + mutable float _exposure{}; + mutable int _background_fit{}; private: - [[nodiscard]] auto _tone_mapping_uncharted2(Expr color) noexcept { + [[nodiscard]] static auto _tone_mapping_uncharted2(Expr color) noexcept { static constexpr auto a = 0.15f; static constexpr auto b = 0.50f; static constexpr auto c = 0.10f; @@ -117,7 +124,7 @@ class DisplayInstance final : public Film::Instance { }; return op(1.6f * color) / op(white); } - [[nodiscard]] auto _tone_mapping_aces(Expr color) noexcept { + [[nodiscard]] static auto _tone_mapping_aces(Expr color) noexcept { constexpr auto a = 2.51f; constexpr auto b = 0.03f; constexpr auto c = 2.43f; @@ -125,17 +132,19 @@ class DisplayInstance final : public Film::Instance { constexpr auto e = 0.14f; return (color * (a * color + b)) / (color * (c * color + d) + e); } - [[nodiscard]] auto _linear_to_srgb(Expr color) noexcept { + [[nodiscard]] static auto _linear_to_srgb(Expr color) noexcept { return ite(color <= .0031308f, color * 12.92f, 1.055f * pow(color, 1.f / 2.4f) - .055f); } public: - DisplayInstance(const Pipeline &pipeline, const Film *film, + DisplayInstance(const Pipeline &pipeline, const Display *film, luisa::unique_ptr base) noexcept : Film::Instance{pipeline, film}, - _base{std::move(base)} {} + _base{std::move(base)}, + _tone_mapping{luisa::to_underlying(film->tone_mapping())}, + _exposure{film->exposure()} {} [[nodiscard]] Film::Accumulation read(Expr pixel) const noexcept override { return _base->read(pixel); @@ -146,25 +155,37 @@ class DisplayInstance final : public Film::Instance { auto &&device = pipeline().device(); auto size = node()->resolution(); if (!_window) { - _window = luisa::make_unique("Display", size); auto d = node(); - _swapchain = device.create_swapchain( - _window->native_handle(), *command_buffer.stream(), - size, d->hdr(), d->vsync(), d->back_buffers()); - _framebuffer = device.create_image( - _swapchain.backend_storage(), size); - _blit = device.compile<2>([&] { + _stream = command_buffer.stream(); + auto window_size = size; + while (std::max(window_size.x, window_size.y) > 2048u) { + window_size /= 2u; + } + _window = luisa::make_unique( + device, *command_buffer.stream(), "Display", + ImGuiWindow::Config{ + .size = window_size, + .vsync = d->vsync(), + .hdr = d->hdr(), + .back_buffers = d->back_buffers()}); + _framebuffer = device.create_image(_window->swapchain().backend_storage(), size); + _background = reinterpret_cast(_window->register_texture(_framebuffer, TextureSampler::linear_point_zero())); + _blit = device.compile<2>([base = _base.get(), &framebuffer = _framebuffer](Int tonemapping, Bool ldr, Float scale) noexcept { auto p = dispatch_id().xy(); - auto color = _base->read(p).average * std::exp2(d->exposure()); - switch (d->tone_mapping()) { - case Display::ToneMapping::NONE: break; - case Display::ToneMapping::UNCHARTED2: color = _tone_mapping_uncharted2(color); break; - case Display::ToneMapping::ACES: color = _tone_mapping_aces(color); break; - } - if (_framebuffer.storage() == PixelStorage::BYTE4) {// LDR + auto color = base->read(p).average * scale; + $switch (tonemapping) { + $case (static_cast(Display::ToneMapping::NONE)) {}; + $case (static_cast(Display::ToneMapping::UNCHARTED2)) { color = _tone_mapping_uncharted2(color); }; + $case (static_cast(Display::ToneMapping::ACES)) { color = _tone_mapping_aces(color); }; + $default { unreachable(); }; + }; + $if (ldr) {// LDR color = _linear_to_srgb(color); - } - _framebuffer->write(p, make_float4(color, 1.f)); + }; + framebuffer->write(p, make_float4(color, 1.f)); + }); + _clear = device.compile<2>([](ImageFloat image) noexcept { + image.write(dispatch_id().xy(), make_float4(0.f)); }); } _last_frame_time = _clock.toc(); @@ -179,27 +200,75 @@ class DisplayInstance final : public Film::Instance { } void release() noexcept override { - while (_window && !_window->should_close()) { - _window->poll_events(); - } + _window = nullptr; _framebuffer = {}; - _swapchain = {}; - _window.reset(); _base->release(); } +private: + [[nodiscard]] float2 _compute_background_size(const ImGuiViewport *viewport, int fit) const noexcept { + auto frame_size = make_float2(_framebuffer.size()); + auto viewport_size = make_float2(viewport->Size.x, viewport->Size.y); + switch (fit) { + case 0: {// aspect fill + auto aspect = frame_size.x / frame_size.y; + auto viewport_aspect = viewport_size.x / viewport_size.y; + auto ratio = aspect < viewport_aspect ? viewport_size.x / frame_size.x : viewport_size.y / frame_size.y; + return frame_size * ratio; + } + case 1: {// aspect fit + auto aspect = frame_size.x / frame_size.y; + auto viewport_aspect = viewport_size.x / viewport_size.y; + auto ratio = aspect > viewport_aspect ? viewport_size.x / frame_size.x : viewport_size.y / frame_size.y; + return frame_size * ratio; + } + case 2: {// stretch + return viewport_size; + } + default: break; + } + return viewport_size; + } + + void _display() const noexcept { + auto scale = luisa::exp2(_exposure); + auto is_ldr = _window->framebuffer().storage() != PixelStorage::FLOAT4; + auto size = _framebuffer.size(); + *_stream << _blit(_tone_mapping, is_ldr, scale).dispatch(size); + auto viewport = ImGui::GetMainViewport(); + auto bg_size = _compute_background_size(viewport, _background_fit); + auto p_min = make_float2(viewport->Pos.x, viewport->Pos.y) + + .5f * (make_float2(viewport->Size.x, viewport->Size.y) - bg_size); + ImGui::GetBackgroundDrawList()->AddImage(_background, + ImVec2{p_min.x, p_min.y}, + ImVec2{p_min.x + bg_size.x, p_min.y + bg_size.y}); + ImGui::Begin("Console", nullptr, ImGuiWindowFlags_AlwaysAutoResize); + { + ImGui::Text("Display FPS: %.2f", ImGui::GetIO().Framerate); + ImGui::SliderFloat("Exposure", &_exposure, -10.f, 10.f); + constexpr const char *const tone_mapping_names[] = {"None", "Uncharted2", "ACES"}; + ImGui::Combo("Tone Mapping", &_tone_mapping, tone_mapping_names, std::size(tone_mapping_names)); + constexpr const char *const fit_names[] = {"Fill", "Fit", "Stretch"}; + ImGui::Combo("Background Fit", &_background_fit, fit_names, std::size(fit_names)); + } + ImGui::End(); + } + bool show(CommandBuffer &command_buffer) const noexcept override { + LUISA_ASSERT(command_buffer.stream() == _stream, "Command buffer stream mismatch."); auto interval = 1. / node()->target_fps(); if (auto current_time = _clock.toc(); current_time - _last_frame_time >= interval) { _last_frame_time = current_time; - _window->poll_events(); if (_window->should_close()) { command_buffer << synchronize(); exit(0);// FIXME: exit gracefully } - command_buffer << _blit().dispatch(node()->resolution()) - << _swapchain.present(_framebuffer); + command_buffer << commit(); + _window->prepare_frame(); + *_stream << _clear(_window->framebuffer()).dispatch(_window->framebuffer().size()); + _display(); + _window->render_frame(); return true; } return false; diff --git a/src/integrators/mega_replay_diff.cpp b/src/integrators/mega_replay_diff.cpp index c94445f2..21dc8c7b 100644 --- a/src/integrators/mega_replay_diff.cpp +++ b/src/integrators/mega_replay_diff.cpp @@ -216,7 +216,7 @@ void MegakernelReplayDiffInstance::_render_one_camera_backward( #ifdef LUISA_RENDER_PATH_REPLAY_DEBUG $if(all(pixel_id == pixel_checked)) { - pipeline().printer().info("Li_1spp forward: Li = ({}, {}, {})", Li[0u], Li[1u], Li[2u]); + pipeline().device_log("Li_1spp forward: Li = ({}, {}, {})", Li[0u], Li[1u], Li[2u]); }; #endif @@ -236,7 +236,7 @@ void MegakernelReplayDiffInstance::_render_one_camera_backward( #ifdef LUISA_RENDER_PATH_REPLAY_DEBUG $if(all(pixel_id == pixel_checked)) { - pipeline().printer().info("miss and break: Li = ({}, {}, {})", Li[0u], Li[1u], Li[2u]); + pipeline().device_log("miss and break: Li = ({}, {}, {})", Li[0u], Li[1u], Li[2u]); }; #endif } @@ -251,7 +251,7 @@ void MegakernelReplayDiffInstance::_render_one_camera_backward( #ifdef LUISA_RENDER_PATH_REPLAY_DEBUG $if(all(pixel_id == pixel_checked)) { - pipeline().printer().info("hit light: Li = ({}, {}, {})", Li[0u], Li[1u], Li[2u]); + pipeline().device_log("hit light: Li = ({}, {}, {})", Li[0u], Li[1u], Li[2u]); }; #endif }; @@ -314,7 +314,7 @@ void MegakernelReplayDiffInstance::_render_one_camera_backward( #ifdef LUISA_RENDER_PATH_REPLAY_DEBUG $if(all(pixel_id == pixel_checked)) { - pipeline().printer().info("direct lighted: Li = ({}, {}, {})", Li[0u], Li[1u], Li[2u]); + pipeline().device_log("direct lighted: Li = ({}, {}, {})", Li[0u], Li[1u], Li[2u]); }; #endif }; @@ -349,7 +349,7 @@ void MegakernelReplayDiffInstance::_render_one_camera_backward( #ifdef LUISA_RENDER_PATH_REPLAY_DEBUG $if(all(pixel_id == pixel_checked)) { - pipeline().printer().info("done: Li = ({}, {}, {})", Li[0u], Li[1u], Li[2u]); + pipeline().device_log("done: Li = ({}, {}, {})", Li[0u], Li[1u], Li[2u]); }; #endif }; @@ -407,7 +407,7 @@ void MegakernelReplayDiffInstance::_render_one_camera_backward( #ifdef LUISA_RENDER_PATH_REPLAY_DEBUG $if(all(pixel_id == pixel_checked)) { - pipeline().printer().info("Li_1spp backward start: Li = ({}, {}, {})", Li[0u], Li[1u], Li[2u]); + pipeline().device_log("Li_1spp backward start: Li = ({}, {}, {})", Li[0u], Li[1u], Li[2u]); }; #endif @@ -426,7 +426,7 @@ void MegakernelReplayDiffInstance::_render_one_camera_backward( #ifdef LUISA_RENDER_PATH_REPLAY_DEBUG $if(all(pixel_id == pixel_checked)) { - pipeline().printer().info("miss and break: Li = ({}, {}, {})", Li[0u], Li[1u], Li[2u]); + pipeline().device_log("miss and break: Li = ({}, {}, {})", Li[0u], Li[1u], Li[2u]); }; #endif } @@ -445,7 +445,7 @@ void MegakernelReplayDiffInstance::_render_one_camera_backward( #ifdef LUISA_RENDER_PATH_REPLAY_DEBUG $if(all(pixel_id == pixel_checked)) { - pipeline().printer().info("after -hit: Li = ({}, {}, {})", Li[0u], Li[1u], Li[2u]); + pipeline().device_log("after -hit: Li = ({}, {}, {})", Li[0u], Li[1u], Li[2u]); }; #endif }; @@ -510,9 +510,9 @@ void MegakernelReplayDiffInstance::_render_one_camera_backward( #ifdef LUISA_RENDER_PATH_REPLAY_DEBUG $if(all(pixel_id == pixel_checked)) { auto Li_variation = weight * eval.f * light_sample.eval.L; - pipeline().printer().info("direct lighting Li_variation = ({}, {}, {})", + pipeline().device_log("direct lighting Li_variation = ({}, {}, {})", Li_variation[0u], Li_variation[1u], Li_variation[2u]); - pipeline().printer().info("after -direct: Li = ({}, {}, {})", Li[0u], Li[1u], Li[2u]); + pipeline().device_log("after -direct: Li = ({}, {}, {})", Li[0u], Li[1u], Li[2u]); }; #endif closure->backward(wo, wi, d_loss * weight * light_sample.eval.L); @@ -559,7 +559,7 @@ void MegakernelReplayDiffInstance::_render_one_camera_backward( #ifdef LUISA_RENDER_PATH_REPLAY_DEBUG $if(all(pixel_id == pixel_checked)) { - pipeline().printer().info("should be 0: Li = ({}, {}, {})", Li[0u], Li[1u], Li[2u]); + pipeline().device_log("should be 0: Li = ({}, {}, {})", Li[0u], Li[1u], Li[2u]); }; #endif }; diff --git a/src/integrators/mega_vpt.cpp b/src/integrators/mega_vpt.cpp index 5739a0eb..c5deb74d 100644 --- a/src/integrators/mega_vpt.cpp +++ b/src/integrators/mega_vpt.cpp @@ -71,7 +71,7 @@ class MegakernelVolumePathTracingInstance final : public ProgressiveIntegrator:: wo_local = shading.world_to_local(wo); wi_local = shading.world_to_local(wi); }; - pipeline().printer().verbose_with_location( + device_log( "wo_local: ({}, {}, {}), wi_local: ({}, {}, {})", wo_local.x, wo_local.y, wo_local.z, wi_local.x, wi_local.y, wi_local.z); @@ -97,7 +97,7 @@ class MegakernelVolumePathTracingInstance final : public ProgressiveIntegrator:: SampledSpectrum Li{swl.dimension(), 0.f}; SampledSpectrum r_u{swl.dimension(), 1.f}, r_l{swl.dimension(), 1.f}; auto rr_depth = node()->rr_depth(); - auto medium_tracker = MediumTracker(pipeline().printer()); + MediumTracker medium_tracker; // functions auto le_zero = [](auto b) noexcept { return b <= 0.f; }; @@ -114,7 +114,7 @@ class MegakernelVolumePathTracingInstance final : public ProgressiveIntegrator:: auto it = pipeline().geometry()->intersect(ray); $if(!it->valid()) { $break; }; - pipeline().printer().verbose_with_location("depth={}", depth_track + 1u); + device_log("depth={}", depth_track + 1u); $if(it->shape().has_medium()) { auto surface_tag = it->shape().surface_tag(); @@ -129,39 +129,37 @@ class MegakernelVolumePathTracingInstance final : public ProgressiveIntegrator:: // deal with medium tracker auto surface_event = event(swl, it, time, -ray->direction(), ray->direction()); pipeline().surfaces().dispatch(surface_tag, [&](auto surface) { - pipeline().printer().verbose_with_location("surface event={}", surface_event); + device_log("surface event={}", surface_event); // update medium tracker $switch(surface_event) { $case(Surface::event_enter) { medium_tracker.enter(medium_priority, medium_info); - pipeline().printer().verbose_with_location("enter: priority={}, medium_tag={}", medium_priority, medium_tag); + device_log("enter: priority={}, medium_tag={}", medium_priority, medium_tag); }; $case(Surface::event_exit) { $if(medium_tracker.exist(medium_priority, medium_info)) { medium_tracker.exit(medium_priority, medium_info); - pipeline().printer().verbose_with_location("exit exist: priority={}, medium_tag={}", medium_priority, medium_tag); + device_log("exit exist: priority={}, medium_tag={}", medium_priority, medium_tag); } $else { medium_tracker.enter(medium_priority, medium_info); - pipeline().printer().verbose_with_location("exit nonexistent: priority={}, medium_tag={}", medium_priority, medium_tag); + device_log("exit nonexistent: priority={}, medium_tag={}", medium_priority, medium_tag); }; }; }; }); }; - pipeline().printer().verbose_with_location("medium tracker size={}", medium_tracker.size()); + device_log("medium tracker size={}", medium_tracker.size()); auto dir = ray->direction(); auto origin = ray->origin(); - pipeline().printer().verbose_with_location("ray->origin()=({}, {}, {})", origin.x, origin.y, origin.z); - pipeline().printer().verbose_with_location("ray->direction()=({}, {}, {})", dir.x, dir.y, dir.z); - pipeline().printer().verbose_with_location("it->p()=({}, {}, {})", it->p().x, it->p().y, it->p().z); - pipeline().printer().verbose_with_location("it->shape().has_medium()={}", it->shape().has_medium()); - pipeline().printer().verbose(""); + device_log("ray->origin()=({}, {}, {})", origin.x, origin.y, origin.z); + device_log("ray->direction()=({}, {}, {})", dir.x, dir.y, dir.z); + device_log("it->p()=({}, {}, {})", it->p().x, it->p().y, it->p().z); + device_log("it->shape().has_medium()={}", it->shape().has_medium()); ray = it->spawn_ray(ray->direction()); depth_track += 1u; }; - pipeline().printer().verbose_with_location("Final medium tracker size={}", medium_tracker.size()); - pipeline().printer().verbose(""); + device_log("Final medium tracker size={}", medium_tracker.size()); ray = camera_ray; auto pdf_bsdf = def(1e16f); @@ -178,10 +176,10 @@ class MegakernelVolumePathTracingInstance final : public ProgressiveIntegrator:: auto it = pipeline().geometry()->intersect(ray); auto has_medium = it->shape().has_medium(); - pipeline().printer().verbose_with_location("depth={}", depth + 1u); - pipeline().printer().verbose_with_location("before: medium tracker size={}, priority={}, tag={}", + device_log("depth={}", depth + 1u); + device_log("before: medium tracker size={}, priority={}, tag={}", medium_tracker.size(), medium_tracker.current().priority, medium_tracker.current().medium_tag); - pipeline().printer().verbose_with_location("it->p(): ({}, {}, {})", it->p().x, it->p().y, it->p().z); + device_log("it->p(): ({}, {}, {})", it->p().x, it->p().y, it->p().z); // sample the participating medium $if(!medium_tracker.vacuum()) { @@ -240,13 +238,13 @@ class MegakernelVolumePathTracingInstance final : public ProgressiveIntegrator:: UInt medium_event = Medium::sample_event(pAbsorb, pScatter, pNull, um); // don't use switch-case here, because of local variable definition $if(medium_event == Medium::event_absorb) { - pipeline().printer().verbose_with_location("Absorb"); + device_log("Absorb"); // Handle absorption along ray path terminated = true; ans = false; } $elif(medium_event == Medium::event_scatter) { - pipeline().printer().verbose_with_location("Scatter"); + device_log("Scatter"); // Handle scattering along ray path // Stop path sampling if maximum depth has been reached depth += 1u; @@ -352,7 +350,7 @@ class MegakernelVolumePathTracingInstance final : public ProgressiveIntegrator:: scattered = true; auto p = closure_p->ray()->origin(); ray = make_ray(p, ps.wi); - pipeline().printer().verbose_with_location( + device_log( "Medium scattering event at depth={}, p=({}, {}, {})", depth, p.x, p.y, p.z); }; @@ -361,7 +359,7 @@ class MegakernelVolumePathTracingInstance final : public ProgressiveIntegrator:: }; } $elif(medium_event == Medium::event_null) { - pipeline().printer().verbose_with_location("Null"); + device_log("Null"); // Handle null scattering along ray path SampledSpectrum sigma_n = max(sigma_maj - closure_p->sigma_a() - closure_p->sigma_s(), 0.f); Float pdf = T_maj[0u] * sigma_n[0u]; @@ -386,7 +384,7 @@ class MegakernelVolumePathTracingInstance final : public ProgressiveIntegrator:: $continue; }; - pipeline().printer().verbose_with_location( + device_log( "T_maj=({}, {}, {})", T_maj[0u], T_maj[1u], T_maj[2u]); beta *= T_maj / T_maj[0u]; @@ -458,7 +456,7 @@ class MegakernelVolumePathTracingInstance final : public ProgressiveIntegrator:: pipeline().media().dispatch(medium_tag, [&](auto medium) { medium_priority = medium->priority(); auto closure = medium->closure(ray, swl, time); - pipeline().printer().verbose_with_location("eta={}", closure->eta()); + device_log("eta={}", closure->eta()); }); }; auto medium_info = make_medium_info(medium_priority, medium_tag); @@ -475,15 +473,8 @@ class MegakernelVolumePathTracingInstance final : public ProgressiveIntegrator:: }); call.execute([&](auto closure) noexcept { // apply opacity map - auto alpha_skip = def(false); - if (auto o = closure->opacity()) { - auto opacity = saturate(*o); - alpha_skip = u_lobe >= opacity; - u_lobe = ite(alpha_skip, (u_lobe - opacity) / (1.f - opacity), u_lobe / opacity); - } - UInt surface_event; - $if(alpha_skip | (medium_tag != medium_tracker.current().medium_tag)) { + $if(medium_tag != medium_tracker.current().medium_tag) { surface_event = surface_event_skip; ray = it->spawn_ray(ray->direction()); pdf_bsdf = 1e16f; @@ -547,12 +538,11 @@ class MegakernelVolumePathTracingInstance final : public ProgressiveIntegrator:: }; depth += 1u; - pipeline().printer().verbose_with_location( + device_log( "scattered={}, beta=({}, {}, {}), pdf_bsdf={}, Li: ({}, {}, {})", scattered, beta[0u], beta[1u], beta[2u], pdf_bsdf, Li[0u], Li[1u], Li[2u]); - pipeline().printer().verbose_with_location("after: medium tracker size={}, priority={}, tag={}", + device_log("after: medium tracker size={}, priority={}, tag={}", medium_tracker.size(), medium_tracker.current().priority, medium_tracker.current().medium_tag); - pipeline().printer().verbose(""); }; return spectrum->srgb(swl, Li); } diff --git a/src/integrators/mega_vpt_naive.cpp b/src/integrators/mega_vpt_naive.cpp index bb5e36a2..f1e6feeb 100644 --- a/src/integrators/mega_vpt_naive.cpp +++ b/src/integrators/mega_vpt_naive.cpp @@ -2,6 +2,7 @@ // Created by ChenXin on 2023/3/30. // +#include #include #include #include @@ -152,25 +153,15 @@ class MegakernelVolumePathTracingNaiveInstance final : public ProgressiveIntegra surface->closure(call, *it, swl, wo, 1.f, time); }); call.execute([&](auto closure) noexcept { - // alpha skip - if (auto o = closure->opacity()) { - auto opacity = saturate(*o); - auto completely_opaque = opacity == 1.f; - transmittance.f = ite(completely_opaque, 0.f, transmittance.f); - transmittance.pdf = ite(completely_opaque, 1e16f, transmittance.pdf + 1.f / (1.f - opacity)); - } - // surface transmit - else { - auto surface_evaluation = closure->evaluate(wo, wi); - transmittance.f *= surface_evaluation.f; - transmittance.pdf += surface_evaluation.pdf; - } + auto surface_evaluation = closure->evaluate(wo, wi); + transmittance.f *= surface_evaluation.f; + transmittance.pdf += surface_evaluation.pdf; }); }; ray = it->spawn_ray_to(light_p); - pipeline().printer().verbose_with_location( + device_log( "transmittance: f=({}, {}, {}), pdf={}", transmittance.f[0u], transmittance.f[1u], transmittance.f[2u], transmittance.pdf); }; @@ -189,7 +180,7 @@ class MegakernelVolumePathTracingNaiveInstance final : public ProgressiveIntegra SampledSpectrum beta{swl.dimension(), camera_weight}; SampledSpectrum Li{swl.dimension(), 0.f}; auto rr_depth = node()->rr_depth(); - auto medium_tracker = MediumTracker(pipeline().printer()); + MediumTracker medium_tracker; // Initialize RNG for sampling the majorant transmittance PCG32 rng(U64(as(sampler()->generate_2d()))); @@ -208,7 +199,7 @@ class MegakernelVolumePathTracingNaiveInstance final : public ProgressiveIntegra auto it = pipeline().geometry()->intersect(ray); $if(!it->valid()) { $break; }; - pipeline().printer().verbose_with_location("depth={}", depth_track); + device_log("depth={}", depth_track); $if(it->shape().has_medium()) { auto surface_tag = it->shape().surface_tag(); @@ -223,40 +214,39 @@ class MegakernelVolumePathTracingNaiveInstance final : public ProgressiveIntegra // deal with medium tracker auto surface_event = _event(swl, it, time, -ray->direction(), ray->direction()); pipeline().surfaces().dispatch(surface_tag, [&](auto surface) { - pipeline().printer().verbose_with_location("surface event={}", surface_event); + device_log("surface event={}", surface_event); // update medium tracker $switch(surface_event) { $case(Surface::event_enter) { medium_tracker.enter(medium_priority, medium_info); - pipeline().printer().verbose_with_location("enter: priority={}, medium_tag={}", medium_priority, medium_tag); + device_log("enter: priority={}, medium_tag={}", medium_priority, medium_tag); }; $case(Surface::event_exit) { $if(medium_tracker.exist(medium_priority, medium_info)) { medium_tracker.exit(medium_priority, medium_info); - pipeline().printer().verbose_with_location("exit exist: priority={}, medium_tag={}", medium_priority, medium_tag); + device_log("exit exist: priority={}, medium_tag={}", medium_priority, medium_tag); } $else { medium_tracker.enter(medium_priority, medium_info); - pipeline().printer().verbose_with_location("exit nonexistent: priority={}, medium_tag={}", medium_priority, medium_tag); + device_log("exit nonexistent: priority={}, medium_tag={}", medium_priority, medium_tag); }; }; }; }); }; - pipeline().printer().verbose_with_location("medium tracker size={}", medium_tracker.size()); + device_log("medium tracker size={}", medium_tracker.size()); auto dir = ray->direction(); auto origin = ray->origin(); - pipeline().printer().verbose_with_location("ray->origin()=({}, {}, {})", origin.x, origin.y, origin.z); - pipeline().printer().verbose_with_location("ray->direction()=({}, {}, {})", dir.x, dir.y, dir.z); - pipeline().printer().verbose_with_location("it->p()=({}, {}, {})", it->p().x, it->p().y, it->p().z); - pipeline().printer().verbose_with_location("it->shape().has_medium()={}", it->shape().has_medium()); + device_log("ray->origin()=({}, {}, {})", origin.x, origin.y, origin.z); + device_log("ray->direction()=({}, {}, {})", dir.x, dir.y, dir.z); + device_log("it->p()=({}, {}, {})", it->p().x, it->p().y, it->p().z); + device_log("it->shape().has_medium()={}", it->shape().has_medium()); pipeline().printer().verbose(""); ray = it->spawn_ray(ray->direction()); depth_track += 1u; }; #endif - pipeline().printer().verbose_with_location("Final medium tracker size={}", medium_tracker.size()); - pipeline().printer().verbose(""); + device_log("Final medium tracker size={}", medium_tracker.size()); ray = camera_ray; auto pdf_bsdf = def(1e16f); @@ -272,14 +262,14 @@ class MegakernelVolumePathTracingNaiveInstance final : public ProgressiveIntegra auto has_medium = it->shape().has_medium(); auto t_max = ite(it->valid(), length(it->p() - ray->origin()), Interaction::default_t_max); - pipeline().printer().verbose_with_location("depth={}", depth); - pipeline().printer().verbose_with_location("before: medium tracker size={}, priority={}, tag={}", + device_log("depth={}", depth); + device_log("before: medium tracker size={}, priority={}, tag={}", medium_tracker.size(), medium_tracker.current().priority, medium_tracker.current().medium_tag); - pipeline().printer().verbose_with_location( + device_log( "ray=({}, {}, {}) + t * ({}, {}, {})", ray->origin().x, ray->origin().y, ray->origin().z, ray->direction().x, ray->direction().y, ray->direction().z); - pipeline().printer().verbose_with_location("it->p()=({}, {}, {})", it->p().x, it->p().y, it->p().z); + device_log("it->p()=({}, {}, {})", it->p().x, it->p().y, it->p().z); auto medium_sample = Medium::Sample::zero(swl.dimension()); // sample the participating medium @@ -336,7 +326,7 @@ class MegakernelVolumePathTracingNaiveInstance final : public ProgressiveIntegra $if(it->shape().has_light()) { auto eval = light_sampler()->evaluate_hit(*it, ray->origin(), swl, time); Li += beta * eval.L * balance_heuristic(pdf_bsdf, eval.pdf); - pipeline().printer().verbose_with_location( + device_log( "hit light: " "pdf_bsdf={}," "eval.pdf={}, " @@ -379,7 +369,7 @@ class MegakernelVolumePathTracingNaiveInstance final : public ProgressiveIntegra auto closure = medium->closure(ray, swl, time); medium_priority = medium->priority(); eta_next = closure->eta(); - pipeline().printer().verbose_with_location("eta_next={}", eta_next); + device_log("eta_next={}", eta_next); }); }; auto medium_info = make_medium_info(medium_priority, medium_tag); @@ -394,16 +384,8 @@ class MegakernelVolumePathTracingNaiveInstance final : public ProgressiveIntegra surface->closure(call, *it, swl, wo, eta, time); }); call.execute([&](auto closure) noexcept { - // apply opacity map - auto alpha_skip = def(false); - if (auto o = closure->opacity()) { - auto opacity = saturate(*o); - alpha_skip = u_lobe >= opacity; - u_lobe = ite(alpha_skip, (u_lobe - opacity) / (1.f - opacity), u_lobe / opacity); - } - UInt surface_event; - $if(alpha_skip | !medium_tracker.true_hit(medium_info.medium_tag)) { + $if(!medium_tracker.true_hit(medium_info.medium_tag)) { surface_event = surface_event_skip; ray = it->spawn_ray(ray->direction()); pdf_bsdf = 1e16f; @@ -428,7 +410,7 @@ class MegakernelVolumePathTracingNaiveInstance final : public ProgressiveIntegra auto w = 1.f / (light_sample.eval.pdf + eval.pdf); Li += w * beta * eval.f * light_sample.eval.L; #endif - pipeline().printer().verbose_with_location( + device_log( "direct lighting: " "eval.f=({}, {}, {}), " "eval.pdf={}, " @@ -475,13 +457,13 @@ class MegakernelVolumePathTracingNaiveInstance final : public ProgressiveIntegra }; }; - pipeline().printer().verbose_with_location( + device_log( "surface event={}, priority={}, tag={}", surface_event, medium_priority, medium_tag); }); }; - pipeline().printer().verbose_with_location( + device_log( "medium event={}, beta=({}, {}, {}), pdf_bsdf={}, Li=({}, {}, {})", medium_sample.medium_event, beta[0u], beta[1u], beta[2u], pdf_bsdf, Li[0u], Li[1u], Li[2u]); @@ -495,10 +477,9 @@ class MegakernelVolumePathTracingNaiveInstance final : public ProgressiveIntegra beta *= ite(q < rr_threshold, 1.0f / q, 1.f); }; - pipeline().printer().verbose_with_location("beta=({}, {}, {})", beta[0u], beta[1u], beta[2u]); - pipeline().printer().verbose_with_location("after: medium tracker size={}, priority={}, tag={}", + device_log("beta=({}, {}, {})", beta[0u], beta[1u], beta[2u]); + device_log("after: medium tracker size={}, priority={}, tag={}", medium_tracker.size(), medium_tracker.current().priority, medium_tracker.current().medium_tag); - pipeline().printer().verbose(""); }; return spectrum->srgb(swl, Li); } diff --git a/src/integrators/megapm.cpp b/src/integrators/megapm.cpp index 4ed63e67..cc878e4e 100644 --- a/src/integrators/megapm.cpp +++ b/src/integrators/megapm.cpp @@ -40,7 +40,7 @@ class MegakernelPhotonMapping final : public ProgressiveIntegrator { _initial_radius{std::max(desc->property_float_or_default("initial_radius", -200.f), -10000.f)},//<0 for world_size/-radius (-grid count) _photon_per_iter{std::max(desc->property_uint_or_default("photon_per_iter", 200000u), 10u)}, _separate_direct{true}, //when false, use photon mapping for all flux and gathering at first intersection. Just for debug - _shared_radius{true} {};//whether or not use the shared radius trick in SPPM paper. True is better in performance. + _shared_radius{desc->property_bool_or_default("shared_radius", true)} {};//whether or not use the shared radius trick in SPPM paper. True is better in performance. [[nodiscard]] auto max_depth() const noexcept { return _max_depth; } [[nodiscard]] auto photon_per_iter() const noexcept { return _photon_per_iter; } [[nodiscard]] auto rr_depth() const noexcept { return _rr_depth; } @@ -394,7 +394,6 @@ class MegakernelPhotonMappingInstance final : public ProgressiveIntegrator::Inst //TODO: use sampler right uint add_x = (photon_per_iter + resolution.y - 1) / resolution.y; sampler()->reset(command_buffer, make_uint2(resolution.x + add_x, resolution.y), pixel_count + add_x * resolution.y, spp); - command_buffer << pipeline().printer().reset(); command_buffer << compute::synchronize(); LUISA_INFO( "Rendering to '{}' of resolution {}x{} at {}spp.", @@ -534,13 +533,11 @@ class MegakernelPhotonMappingInstance final : public ProgressiveIntegrator::Inst command_buffer << [&progress, p] { progress.update(p); }; } } - command_buffer << pipeline().printer().retrieve(); } LUISA_INFO("total spp:{}", runtime_spp); //tot_photon is photon_per_iter not photon_per_iter*spp because of unnormalized samples - command_buffer << indirect_draw(node()->photon_per_iter(), 1).dispatch(resolution); + command_buffer << indirect_draw(node()->photon_per_iter(), runtime_spp).dispatch(resolution); command_buffer << synchronize(); - command_buffer << pipeline().printer().retrieve(); progress.done(); @@ -645,89 +642,75 @@ class MegakernelPhotonMappingInstance final : public ProgressiveIntegrator::Inst surface->closure(call, *it, swl, wo, 1.f, time); }); call.execute([&](auto closure) noexcept { - // apply opacity map - auto alpha_skip = def(false); - if (auto o = closure->opacity()) { - auto opacity = saturate(*o); - alpha_skip = u_lobe >= opacity; - u_lobe = ite(alpha_skip, (u_lobe - opacity) / (1.f - opacity), u_lobe / opacity); + if (auto dispersive = closure->is_dispersive()) { + $if(*dispersive) { swl.terminate_secondary(); }; } - - $if(alpha_skip) { - ray = it->spawn_ray(ray->direction()); - pdf_bsdf = 1e16f; + // direct lighting + if (node()->separate_direct()) { + $if(light_sample.eval.pdf > 0.0f & !occluded) { + auto wi = light_sample.shadow_ray->direction(); + auto eval = closure->evaluate(wo, wi); + auto w = balance_heuristic(light_sample.eval.pdf, eval.pdf) / + light_sample.eval.pdf; + Li += w * beta * eval.f * light_sample.eval.L; + }; } - $else { - if (auto dispersive = closure->is_dispersive()) { - $if(*dispersive) { swl.terminate_secondary(); }; - } - // direct lighting - if (node()->separate_direct()) { - $if(light_sample.eval.pdf > 0.0f & !occluded) { - auto wi = light_sample.shadow_ray->direction(); - auto eval = closure->evaluate(wo, wi); - auto w = balance_heuristic(light_sample.eval.pdf, eval.pdf) / - light_sample.eval.pdf; - Li += w * beta * eval.f * light_sample.eval.L; - }; - } - //TODO: get this done - auto roughness = closure->roughness(); - Bool stop_check; - if (node()->separate_direct()) { - stop_check = (roughness.x * roughness.y > 0.16f) | stop_direct; - } else { - stop_check = true;//always stop at first intersection - } - $if(stop_check) { - stop_direct = true; - auto grid = photons.point_to_grid(it->p()); - $for(x, grid.x - 1, grid.x + 2) { - $for(y, grid.y - 1, grid.y + 2) { - $for(z, grid.z - 1, grid.z + 2) { - Int3 check_grid{x, y, z}; - auto photon_index = photons.grid_head(photons.grid_to_index(check_grid)); - $while(photon_index != ~0u) { - auto position = photons.position(photon_index); - auto dis = distance(position, it->p()); - //pipeline().printer().info("check_grid:{},{},{};test_grid:{},{},{}; limit:{}", x, y, z, test_grid[0], test_grid[1], test_grid[2], indirect.radius(pixel_id)); - $if(dis <= indirect.radius(pixel_id)) { - auto photon_wi = photons.wi(photon_index); - auto photon_beta = photons.beta(photon_index); - auto test_grid = photons.point_to_grid(position); - auto eval_photon = closure->evaluate(wo, photon_wi); - auto wi_local = it->shading().world_to_local(photon_wi); - Float3 Phi; - if (!spectrum->node()->is_fixed()) { - auto photon_swl = photons.swl(photon_index); - Phi = spectrum->wavelength_mul(swl, beta * (eval_photon.f / abs_cos_theta(wi_local)), photon_swl, photon_beta); - } else { - Phi = spectrum->srgb(swl, beta * photon_beta * eval_photon.f / abs_cos_theta(wi_local)); - } - //testbeta += Phi; - indirect.add_phi(pixel_id, Phi); - indirect.add_cur_n(pixel_id, 1u); - //pipeline().printer().info("render:{}", indirect.cur_n(pixel_id)); - }; - - photon_index = photons.nxt(photon_index); + //TODO: get this done + auto roughness = closure->roughness(); + Bool stop_check; + if (node()->separate_direct()) { + stop_check = (roughness.x * roughness.y > 0.16f) | stop_direct; + } else { + stop_check = true;//always stop at first intersection + } + $if(stop_check) { + stop_direct = true; + auto grid = photons.point_to_grid(it->p()); + $for(x, grid.x - 1, grid.x + 2) { + $for(y, grid.y - 1, grid.y + 2) { + $for(z, grid.z - 1, grid.z + 2) { + Int3 check_grid{x, y, z}; + auto photon_index = photons.grid_head(photons.grid_to_index(check_grid)); + $while(photon_index != ~0u) { + auto position = photons.position(photon_index); + auto dis = distance(position, it->p()); + //pipeline().printer().info("check_grid:{},{},{};test_grid:{},{},{}; limit:{}", x, y, z, test_grid[0], test_grid[1], test_grid[2], indirect.radius(pixel_id)); + $if(dis <= indirect.radius(pixel_id)) { + auto photon_wi = photons.wi(photon_index); + auto photon_beta = photons.beta(photon_index); + auto test_grid = photons.point_to_grid(position); + auto eval_photon = closure->evaluate(wo, photon_wi); + auto wi_local = it->shading().world_to_local(photon_wi); + Float3 Phi; + if (!spectrum->node()->is_fixed()) { + auto photon_swl = photons.swl(photon_index); + Phi = spectrum->wavelength_mul(swl, beta * (eval_photon.f / abs_cos_theta(wi_local)), photon_swl, photon_beta); + } else { + Phi = spectrum->srgb(swl, beta * photon_beta * eval_photon.f / abs_cos_theta(wi_local)); + } + //testbeta += Phi; + indirect.add_phi(pixel_id, Phi); + indirect.add_cur_n(pixel_id, 1u); + //pipeline().printer().info("render:{}", indirect.cur_n(pixel_id)); }; + + photon_index = photons.nxt(photon_index); }; }; }; }; - // sample material - auto surface_sample = closure->sample(wo, u_lobe, u_bsdf); - ray = it->spawn_ray(surface_sample.wi); - pdf_bsdf = surface_sample.eval.pdf; - auto w = ite(surface_sample.eval.pdf > 0.f, 1.f / surface_sample.eval.pdf, 0.f); - beta *= w * surface_sample.eval.f; - // apply eta scale - auto eta = closure->eta().value_or(1.f); - $switch(surface_sample.event) { - $case(Surface::event_enter) { eta_scale = sqr(eta); }; - $case(Surface::event_exit) { eta_scale = sqr(1.f / eta); }; - }; + }; + // sample material + auto surface_sample = closure->sample(wo, u_lobe, u_bsdf); + ray = it->spawn_ray(surface_sample.wi); + pdf_bsdf = surface_sample.eval.pdf; + auto w = ite(surface_sample.eval.pdf > 0.f, 1.f / surface_sample.eval.pdf, 0.f); + beta *= w * surface_sample.eval.f; + // apply eta scale + auto eta = closure->eta().value_or(1.f); + $switch(surface_sample.event) { + $case(Surface::event_enter) { eta_scale = sqr(eta); }; + $case(Surface::event_exit) { eta_scale = sqr(1.f / eta); }; }; }); beta = zero_if_any_nan(beta); @@ -819,38 +802,24 @@ class MegakernelPhotonMappingInstance final : public ProgressiveIntegrator::Inst surface->closure(call, *it, swl, wi, 1.f, time); }); call.execute([&](auto closure) noexcept { - // apply opacity map - auto alpha_skip = def(false); - if (auto o = closure->opacity()) { - auto opacity = saturate(*o); - alpha_skip = u_lobe >= opacity; - u_lobe = ite(alpha_skip, (u_lobe - opacity) / (1.f - opacity), u_lobe / opacity); + if (auto dispersive = closure->is_dispersive()) { + $if(*dispersive) { swl.terminate_secondary(); }; } - $if(alpha_skip) { - ray = it->spawn_ray(ray->direction()); - pdf_bsdf = 1e16f; - } - $else { - if (auto dispersive = closure->is_dispersive()) { - $if(*dispersive) { swl.terminate_secondary(); }; - } - - // sample material - auto surface_sample = closure->sample(wi, u_lobe, u_bsdf, TransportMode::IMPORTANCE); - ray = it->spawn_ray(surface_sample.wi); - pdf_bsdf = surface_sample.eval.pdf; - auto w = ite(surface_sample.eval.pdf > 0.f, 1.f / surface_sample.eval.pdf, 0.f); - auto bnew = beta * w * surface_sample.eval.f; - // apply eta scale - auto eta = closure->eta().value_or(1.f); - $switch(surface_sample.event) { - $case(Surface::event_enter) { eta_scale = sqr(eta); }; - $case(Surface::event_exit) { eta_scale = sqr(1.f / eta); }; - }; - eta_scale *= ite(beta.max() < bnew.max(), 1.f, bnew.max() / beta.max()); - beta = bnew; + // sample material + auto surface_sample = closure->sample(wi, u_lobe, u_bsdf, TransportMode::IMPORTANCE); + ray = it->spawn_ray(surface_sample.wi); + pdf_bsdf = surface_sample.eval.pdf; + auto w = ite(surface_sample.eval.pdf > 0.f, 1.f / surface_sample.eval.pdf, 0.f); + auto bnew = beta * w * surface_sample.eval.f; + // apply eta scale + auto eta = closure->eta().value_or(1.f); + $switch(surface_sample.event) { + $case(Surface::event_enter) { eta_scale = sqr(eta); }; + $case(Surface::event_exit) { eta_scale = sqr(1.f / eta); }; }; + eta_scale *= ite(beta.max() < bnew.max(), 1.f, bnew.max() / beta.max()); + beta = bnew; }); beta = zero_if_any_nan(beta); $if(beta.all([](auto b) noexcept { return b <= 0.f; })) { $break; }; diff --git a/src/integrators/megappm_diff.cpp b/src/integrators/megappm_diff.cpp index a780b6be..f1be0e39 100644 --- a/src/integrators/megappm_diff.cpp +++ b/src/integrators/megappm_diff.cpp @@ -464,8 +464,7 @@ class MegakernelPhotonMappingDiffInstance : public DifferentiableIntegrator::Ins uint add_x = (photon_per_iter + resolution.y - 1) / resolution.y; sampler()->reset(command_buffer, make_uint2(resolution.x + add_x, resolution.y), pixel_count + add_x * resolution.y, spp); - - command_buffer << pipeline().printer().reset(); + command_buffer << compute::synchronize(); LUISA_INFO( "Rendering to '{}' of resolution {}x{} at {}spp.", @@ -497,9 +496,9 @@ class MegakernelPhotonMappingDiffInstance : public DifferentiableIntegrator::Ins viewpoints->write_grid_len(viewpoints->split(-radius)); else viewpoints->write_grid_len(node()->initial_radius()); - //camera->pipeline().printer().info("grid:{}", viewpoints->grid_len()); + //camera->pipeline().device_log("grid:{}", viewpoints->grid_len()); indirect->write_radius(index, viewpoints->grid_len()); - //camera->pipeline().printer().info("rad:{}", indirect->radius(index)); + //camera->pipeline().device_log("rad:{}", indirect->radius(index)); indirect->write_cur_n(index, 0u); indirect->write_cur_w(index, 0.f); @@ -554,7 +553,7 @@ class MegakernelPhotonMappingDiffInstance : public DifferentiableIntegrator::Ins auto pixel_id_1d = pixel_id.y * resolution.x + pixel_id.x; // $if(pixel_id_1d == 0) { // auto cur_n_tot = indirect->cur_n(0u); - // pipeline().printer().info("photon cur n is {}", cur_n_tot); + // pipeline().device_log("photon cur n is {}", cur_n_tot); // }; auto L = get_indirect(camera->pipeline().spectrum(), pixel_id_1d, tot_photon); camera->film()->accumulate(pixel_id, L, 0.5f * spp); @@ -605,7 +604,6 @@ class MegakernelPhotonMappingDiffInstance : public DifferentiableIntegrator::Ins command_buffer << synchronize(); command_buffer << indirect_draw(node()->photon_per_iter(), runtime_spp).dispatch(resolution); command_buffer << synchronize(); - command_buffer << pipeline().printer().retrieve(); //LUISA_INFO("Finishi indirect_draw"); progress.done(); auto render_time = clock.toc(); @@ -825,10 +823,10 @@ class MegakernelPhotonMappingDiffInstance : public DifferentiableIntegrator::Ins } indirect->add_phi(pixel_id, Phi*weight); indirect->add_cur_n(pixel_id, 1u); - //pipeline().printer().info("working here!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! pixel_id and phi are {}, {} ", pixel_id, Phi); + //pipeline().device_log("working here!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! pixel_id and phi are {}, {} ", pixel_id, Phi); }; viewpoint_index = viewpoints->nxt(viewpoint_index); - //pipeline().printer().info("working here!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! pixel_id is ", pixel_id); + //pipeline().device_log("working here!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! pixel_id is ", pixel_id); }; }; }; @@ -896,8 +894,7 @@ class MegakernelPhotonMappingDiffInstance : public DifferentiableIntegrator::Ins uint add_x = (photon_per_iter + resolution.y - 1) / resolution.y; sampler()->reset(command_buffer, make_uint2(resolution.x + add_x, resolution.y), pixel_count + add_x * resolution.y, spp); - - command_buffer << pipeline().printer().reset(); + command_buffer << compute::synchronize(); LUISA_INFO( "Rendering to '{}' of resolution {}x{} at {}spp.", @@ -924,9 +921,9 @@ class MegakernelPhotonMappingDiffInstance : public DifferentiableIntegrator::Ins viewpoints->write_grid_len(viewpoints->split(-radius)); else viewpoints->write_grid_len(node()->initial_radius()); - //camera->pipeline().printer().info("grid:{}", viewpoints->grid_len()); + //camera->pipeline().device_log("grid:{}", viewpoints->grid_len()); indirect->write_radius(index, viewpoints->grid_len()); - //camera->pipeline().printer().info("rad:{}", indirect->radius(index)); + //camera->pipeline().device_log("rad:{}", indirect->radius(index)); indirect->write_cur_n(index, 0u); indirect->write_cur_w(index, 0.f); indirect->write_n_photon(index, 0u); diff --git a/src/integrators/megawave.cpp b/src/integrators/megawave.cpp index 6d5bc151..e8a1096a 100644 --- a/src/integrators/megawave.cpp +++ b/src/integrators/megawave.cpp @@ -2,7 +2,6 @@ // Created by Mike Smith on 2022/1/10. // -#include "compute/src/backends/common/hlsl/hlsl_codegen.h" #include "dsl/builtin.h" #include "dsl/struct.h" #include "runtime/rtx/ray.h" @@ -264,6 +263,14 @@ class MegakernelWaveFrontInstance final : public ProgressiveIntegrator::Instance }; luisa::unique_ptr MegakernelWaveFront::build(Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept { + if (auto backend = luisa::string{pipeline.device().backend_name()}; backend == "cuda" || backend == "cpu") { + for (auto &c : backend) { c = static_cast(std::toupper(c)); } + LUISA_ERROR_WITH_LOCATION("The {} backend does not support the 'meagwave' integrator: " + "thread-block synchronization is not supported inside " + "ray-tracing kernels on this backend. " + "You can use 'megapath' or 'wavepath' instead.", + backend); + } return luisa::make_unique(pipeline, command_buffer, this); } @@ -646,55 +653,41 @@ void MegakernelWaveFrontInstance::_render_one_camera( surface->closure(call, *it, swl, wo, 1.f, time); }); call.execute([&](const Surface::Closure *closure) noexcept { - // apply opacity map - auto alpha_skip = def(false); - if (auto o = closure->opacity()) { - auto opacity = saturate(*o); - alpha_skip = u_lobe >= opacity; - u_lobe = ite(alpha_skip, (u_lobe - opacity) / (1.f - opacity), u_lobe / opacity); - } - - $if(alpha_skip) { - ray = it->spawn_ray(ray->direction()); - path_state[path_id].pdf_bsdf = 1e16f; - } - $else { - if (auto dispersive = closure->is_dispersive()) { - $if(*dispersive) { - swl.terminate_secondary(); - if (!spectrum->node()->is_fixed()) { - path_state[path_id].wl_sample = -u_wl; - } - }; - } - // direct lighting - auto light_wi_and_pdf = path_state[path_id].wi_and_pdf; - auto pdf_light = light_wi_and_pdf.w; - $if(light_wi_and_pdf.w > 0.f) { - auto eval = closure->evaluate(wo, light_wi_and_pdf.xyz()); - auto mis_weight = balance_heuristic(pdf_light, eval.pdf); - // update Li - SampledSpectrum Ld{dim}; - for (auto i = 0u; i < dim; ++i) { - Ld[i] = path_state_dim[path_id * dim + i].emission; + if (auto dispersive = closure->is_dispersive()) { + $if(*dispersive) { + swl.terminate_secondary(); + if (!spectrum->node()->is_fixed()) { + path_state[path_id].wl_sample = -u_wl; } - auto Li = mis_weight / pdf_light * beta * eval.f * Ld; - auto pixel_id = path_state[path_id].pixel_index; - auto pixel_coord = make_uint2(pixel_id % resolution.x, pixel_id / resolution.x); - camera->film()->accumulate(pixel_coord, spectrum->srgb(swl, Li), 0.f); - }; - // sample material - auto surface_sample = closure->sample(wo, u_lobe, u_bsdf); - path_state[path_id].pdf_bsdf = surface_sample.eval.pdf; - ray = it->spawn_ray(surface_sample.wi); - auto w = ite(surface_sample.eval.pdf > 0.0f, 1.f / surface_sample.eval.pdf, 0.f); - beta *= w * surface_sample.eval.f; - // eta scale - auto eta = closure->eta().value_or(1.f); - $switch(surface_sample.event) { - $case(Surface::event_enter) { eta_scale = sqr(eta); }; - $case(Surface::event_exit) { eta_scale = 1.f / sqr(eta); }; }; + } + // direct lighting + auto light_wi_and_pdf = path_state[path_id].wi_and_pdf; + auto pdf_light = light_wi_and_pdf.w; + $if(light_wi_and_pdf.w > 0.f) { + auto eval = closure->evaluate(wo, light_wi_and_pdf.xyz()); + auto mis_weight = balance_heuristic(pdf_light, eval.pdf); + // update Li + SampledSpectrum Ld{dim}; + for (auto i = 0u; i < dim; ++i) { + Ld[i] = path_state_dim[path_id * dim + i].emission; + } + auto Li = mis_weight / pdf_light * beta * eval.f * Ld; + auto pixel_id = path_state[path_id].pixel_index; + auto pixel_coord = make_uint2(pixel_id % resolution.x, pixel_id / resolution.x); + camera->film()->accumulate(pixel_coord, spectrum->srgb(swl, Li), 0.f); + }; + // sample material + auto surface_sample = closure->sample(wo, u_lobe, u_bsdf); + path_state[path_id].pdf_bsdf = surface_sample.eval.pdf; + ray = it->spawn_ray(surface_sample.wi); + auto w = ite(surface_sample.eval.pdf > 0.0f, 1.f / surface_sample.eval.pdf, 0.f); + beta *= w * surface_sample.eval.f; + // eta scale + auto eta = closure->eta().value_or(1.f); + $switch(surface_sample.event) { + $case(Surface::event_enter) { eta_scale = sqr(eta); }; + $case(Surface::event_exit) { eta_scale = 1.f / sqr(eta); }; }; }); @@ -776,12 +769,14 @@ void MegakernelWaveFrontInstance::_render_one_camera( }; sync_block(); }; +#ifndef NDEBUG $if(count == count_limit) { - pipeline().printer().info("block_id{},thread_id {}, loop not break! local:{}, global:{}", block_x(), thread_x(), rem_local[0], rem_global[0]); + device_log("block_id{},thread_id {}, loop not break! local:{}, global:{}", block_x(), thread_x(), rem_local[0], rem_global[0]); $if(thread_x() < (uint)KERNEL_COUNT) { - pipeline().printer().info("work rem: id {}, size {}", thread_x(), work_counter[thread_x()]); + device_log("work rem: id {}, size {}", thread_x(), work_counter[thread_x()]); }; }; +#endif }); auto clear_global_shader = compile_async<1>(device, [&]() noexcept { auto dispatch_id = dispatch_x(); @@ -818,7 +813,6 @@ void MegakernelWaveFrontInstance::_render_one_camera( << commit(); LUISA_ASSERT(launch_size % render_shader.get().block_size().x == 0u, ""); command_buffer << render_shader.get()(sample_count, host_sample_count, shutter_spp, time, s.point.weight).dispatch(launch_size); - command_buffer << pipeline().printer().retrieve(); command_buffer << synchronize(); shutter_spp += s.spp; } diff --git a/src/integrators/wave_path_v2.cpp b/src/integrators/wave_path_v2.cpp index ade74637..01a633ac 100644 --- a/src/integrators/wave_path_v2.cpp +++ b/src/integrators/wave_path_v2.cpp @@ -493,7 +493,7 @@ void WavefrontPathTracingv2Instance::_render_one_camera( path_id = path_indices.read(dispatch_id); } //$if(path_id < offset) { - // pipeline().printer().info("path_id {}, offset {}", path_id,offset); + // pipeline().device_log("path_id {}, offset {}", path_id,offset); //}; sampler()->start(pixel_coord, sample_id); auto u_filter = sampler()->generate_pixel_2d(); @@ -961,7 +961,7 @@ void WavefrontPathTracingv2Instance::_render_one_camera( queue.write(dispatch_id, new_id); if (gathering) path_states.write_kernel_index(path_id, (uint)INVALID);//in the end, the generation could left some states unchanged - //pipeline().printer().info("move {} to {}",path_id, new_id); + //pipeline().device_log("move {} to {}",path_id, new_id); }; } }); @@ -989,7 +989,7 @@ void WavefrontPathTracingv2Instance::_render_one_camera( queue.write(dispatch_id, new_id); else path_states.write_kernel_index(path_id, (uint)INVALID);//in the end, the generation could left some states unchanged - //pipeline().printer().info("move {} to {}",path_id, new_id); + //pipeline().device_log("move {} to {}",path_id, new_id); }; } }); @@ -1000,7 +1000,7 @@ void WavefrontPathTracingv2Instance::_render_one_camera( $if((dispatch_id < size) & (path_id < move_offset)) { auto queue_id = queue_size.atomic(0u).fetch_add(1u); queue.write(queue_id, path_id); - //pipeline().printer().info("{} is a slot", path_id); + //pipeline().device_log("{} is a slot", path_id); }; }); diff --git a/src/media/homogeneous.cpp b/src/media/homogeneous.cpp index c9e9326d..dfce07bd 100644 --- a/src/media/homogeneous.cpp +++ b/src/media/homogeneous.cpp @@ -55,7 +55,7 @@ class HomogeneousMedium : public Medium { auto channel = sample_discrete(pdf_channels, rng.uniform_float()); auto u = rng.uniform_float(); auto t = -log(max(1.f - u, std::numeric_limits::min())) / sigma_t()[channel]; - instance()->pipeline().printer().verbose_with_location("HomogeneousMediumClosure::sample: u={}, t={}", u, t); + device_log("HomogeneousMediumClosure::sample: u={}, t={}", u, t); // hit surface $if(t > t_max) { @@ -95,7 +95,7 @@ class HomogeneousMedium : public Medium { sample_ans.eval.f = Tr * sigma_s(); sample_ans.eval.pdf = pdf.sum(); - instance()->pipeline().printer().verbose_with_location( + device_log( "HomogeneousMediumClosure::sample::scatter\n" "t={}, Tr=({}, {}, {}), \n" "pdf_channels=({}, {}, {}), \n" diff --git a/src/textures/placeholder.cpp b/src/textures/placeholder.cpp index 81d2fff9..62ea82b9 100644 --- a/src/textures/placeholder.cpp +++ b/src/textures/placeholder.cpp @@ -6,9 +6,7 @@ #include "dsl/stmt.h" #include "runtime/stream.h" #include -#include #include -#include #include #include diff --git a/src/util/medium_tracker.cpp b/src/util/medium_tracker.cpp index ca544d5f..d54106df 100644 --- a/src/util/medium_tracker.cpp +++ b/src/util/medium_tracker.cpp @@ -9,7 +9,11 @@ namespace luisa::render { using namespace luisa::compute; -MediumTracker::MediumTracker(Printer &printer) noexcept : _size{0u}, _printer{printer} { +Bool equal(Expr a, Expr b) noexcept { + return a.medium_tag == b.medium_tag; +} + +MediumTracker::MediumTracker() noexcept : _size{0u} { for (auto i = 0u; i < capacity; i++) { _priority_list[i] = Medium::VACUUM_PRIORITY; _medium_list[i] = make_medium_info(Medium::VACUUM_PRIORITY, Medium::INVALID_TAG); @@ -22,8 +26,8 @@ Bool MediumTracker::true_hit(Expr priority) const noexcept { void MediumTracker::enter(Expr priority, Expr value) noexcept { $if(_size == capacity) { - printer().error_with_location( - "Medium stack overflow when trying to enter priority={}, medium_tag={}", + device_log( + "[error] Medium stack overflow when trying to enter priority={}, medium_tag={}", priority, value.medium_tag); } $else { @@ -46,7 +50,7 @@ void MediumTracker::exit(Expr priority, Expr value) noexcept { auto remove_num = def(0u); for (auto i = 0u; i < capacity - 1u; i++) { auto p = _priority_list[i]; - auto should_remove = (p == priority) & _medium_list[i]->equal(value) & (remove_num == 0u); + auto should_remove = (p == priority) & equal(_medium_list[i], value) & (remove_num == 0u); remove_num += ite(should_remove, 1u, 0u); _priority_list[i] = _priority_list[i + remove_num]; _medium_list[i] = _medium_list[i + remove_num]; @@ -57,8 +61,8 @@ void MediumTracker::exit(Expr priority, Expr value) noexcept { _medium_list[_size] = make_medium_info(Medium::VACUUM_PRIORITY, Medium::INVALID_TAG); } $else { - printer().error_with_location( - "Medium stack trying to exit nonexistent priority={}, medium_tag={}", + device_log( + "[error] Medium stack trying to exit nonexistent priority={}, medium_tag={}", priority, value.medium_tag); }; } @@ -67,7 +71,7 @@ Bool MediumTracker::exist(Expr priority, Expr value) noexcept auto exist = def(false); for (auto i = 0u; i < capacity - 1u; i++) { auto p = _priority_list[i]; - exist |= (p == priority) & _medium_list[i]->equal(value); + exist |= (p == priority) & equal(_medium_list[i], value); } return exist; } diff --git a/src/util/medium_tracker.h b/src/util/medium_tracker.h index fd401591..4e5a5630 100644 --- a/src/util/medium_tracker.h +++ b/src/util/medium_tracker.h @@ -15,7 +15,6 @@ using compute::ArrayVar; using compute::Bool; using compute::Expr; using compute::Float4; -using compute::Printer; using compute::UInt; using compute::Var; @@ -35,16 +34,11 @@ class MediumTracker { ArrayVar _priority_list; ArrayVar _medium_list; UInt _size; - Printer &_printer; public: - explicit MediumTracker(Printer &printer) noexcept; + explicit MediumTracker() noexcept; MediumTracker(const MediumTracker &) = default; -protected: - [[nodiscard]] auto &printer() noexcept { return _printer; } - [[nodiscard]] const auto &printer() const noexcept { return _printer; } - public: [[nodiscard]] Var current() const noexcept; [[nodiscard]] Bool vacuum() const noexcept; diff --git a/src/util/thread_pool.cpp b/src/util/thread_pool.cpp index a4006c22..1b90e7d0 100644 --- a/src/util/thread_pool.cpp +++ b/src/util/thread_pool.cpp @@ -2,13 +2,183 @@ // Created by Mike Smith on 2023/5/18. // +#include +#include +#include +#include +#include + +#if (!defined(__clang_major__) || __clang_major__ >= 14) && defined(__cpp_lib_barrier) +#define LUISA_COMPUTE_USE_STD_BARRIER +#endif + +#ifdef LUISA_COMPUTE_USE_STD_BARRIER +#include +#endif + +#include +#include +#include #include namespace luisa::render { +namespace detail { + +[[nodiscard]] static auto &is_worker_thread() noexcept { + static thread_local auto is_worker = false; + return is_worker; +} + +[[nodiscard]] static auto &worker_thread_index() noexcept { + static thread_local auto id = 0u; + return id; +} + +static inline void check_not_in_worker_thread(std::string_view f) noexcept { + if (is_worker_thread()) [[unlikely]] { + std::ostringstream oss; + oss << std::this_thread::get_id(); + LUISA_ERROR_WITH_LOCATION( + "Invoking ThreadPool::{}() " + "from worker thread {}.", + f, oss.str()); + } +} + +}// namespace detail + +#ifdef LUISA_COMPUTE_USE_STD_BARRIER +struct Barrier : std::barrier<> { + using std::barrier<>::barrier; +}; +#else +// reference: https://github.com/yohhoy/yamc/blob/master/include/yamc_barrier.hpp +class Barrier { +private: + uint _n; + uint _counter; + uint _phase; + std::condition_variable _cv; + std::mutex _callback_mutex; + +public: + explicit Barrier(uint n) noexcept + : _n{n}, _counter{n}, _phase{0u} {} + void arrive_and_wait() noexcept { + std::unique_lock lock{_callback_mutex}; + auto arrive_phase = _phase; + if (--_counter == 0u) { + _counter = _n; + _phase++; + _cv.notify_all(); + } + while (_phase <= arrive_phase) { + _cv.wait(lock); + } + } +}; +#endif + +struct ThreadPool::Impl { + luisa::vector threads; + luisa::queue> tasks; + std::mutex mutex; + Barrier synchronize_barrier; + Barrier dispatch_barrier; + std::condition_variable cv; + bool should_stop{false}; + explicit Impl(size_t num_threads) noexcept + : synchronize_barrier(num_threads + 1u), + dispatch_barrier(num_threads) { + threads.reserve(num_threads); + for (auto i = 0u; i < num_threads; i++) { + threads.emplace_back([this, i] { + detail::is_worker_thread() = true; + detail::worker_thread_index() = i; + for (;;) { + std::unique_lock lock{mutex}; + cv.wait(lock, [this] { return !tasks.empty() || should_stop; }); + if (should_stop && tasks.empty()) [[unlikely]] { break; } + auto task = std::move(tasks.front()); + tasks.pop(); + lock.unlock(); + task(); + } + }); + } + LUISA_INFO("Created thread pool with {} thread{}.", + num_threads, num_threads == 1u ? "" : "s"); + } +}; + +ThreadPool::ThreadPool(size_t num_threads) noexcept + : _impl{luisa::make_unique([&]() { + if (num_threads == 0u) { + num_threads = std::max( + std::thread::hardware_concurrency(), 1u); + } + return num_threads; + }())} { +} + +void ThreadPool::barrier() noexcept { + detail::check_not_in_worker_thread("barrier"); + _dispatch_all([this] { _impl->dispatch_barrier.arrive_and_wait(); }); +} + +void ThreadPool::synchronize() noexcept { + detail::check_not_in_worker_thread("synchronize"); + while (task_count() != 0u) { + _dispatch_all([this] { _impl->synchronize_barrier.arrive_and_wait(); }); + _impl->synchronize_barrier.arrive_and_wait(); + } +} + +void ThreadPool::_dispatch(luisa::SharedFunction &&task) noexcept { + { + std::lock_guard lock{_impl->mutex}; + _impl->tasks.emplace(std::move(task)); + } + _impl->cv.notify_one(); +} + +void ThreadPool::_dispatch_all(luisa::SharedFunction &&task, size_t max_threads) noexcept { + { + std::lock_guard lock{_impl->mutex}; + for (auto i = 0u; i < std::min(_impl->threads.size(), max_threads) - 1u; i++) { + _impl->tasks.emplace(task); + } + _impl->tasks.emplace(std::move(task)); + } + _impl->cv.notify_all(); +} + +ThreadPool::~ThreadPool() noexcept { + { + std::lock_guard lock{_impl->mutex}; + _impl->should_stop = true; + } + _impl->cv.notify_all(); + for (auto &&t : _impl->threads) { t.join(); } +} + +uint ThreadPool::size() const noexcept { + return static_cast(_impl->threads.size()); +} +bool ThreadPool::is_worker_thread() noexcept { + return detail::is_worker_thread(); +} +uint ThreadPool::worker_thread_index() noexcept { + LUISA_ASSERT(detail::is_worker_thread(), + "ThreadPool::worker_thread_index() " + "called in non-worker thread."); + return detail::worker_thread_index(); +} + ThreadPool &global_thread_pool() noexcept { static ThreadPool pool{std::thread::hardware_concurrency()}; return pool; } -}// namespace luisa::render +}// namespace luisa diff --git a/src/util/thread_pool.h b/src/util/thread_pool.h index 448530a7..93ed9faa 100644 --- a/src/util/thread_pool.h +++ b/src/util/thread_pool.h @@ -2,10 +2,157 @@ // Created by Mike Smith on 2023/5/18. // -#include +#pragma once + +#include + +#include +#include +#include namespace luisa::render { +/// Thread pool class +class ThreadPool { + +public: + struct Impl; + +private: + luisa::unique_ptr _impl; + std::atomic_uint _task_count; + +private: + void _dispatch(luisa::SharedFunction &&task) noexcept; + void _dispatch_all(luisa::SharedFunction &&task, size_t max_threads = std::numeric_limits::max()) noexcept; + +public: + /// Create a thread pool with num_threads threads + explicit ThreadPool(size_t num_threads = 0u) noexcept; + ~ThreadPool() noexcept; + ThreadPool(ThreadPool &&) noexcept = delete; + ThreadPool(const ThreadPool &) noexcept = delete; + ThreadPool &operator=(ThreadPool &&) noexcept = delete; + ThreadPool &operator=(const ThreadPool &) noexcept = delete; + /// Return global static ThreadPool instance + [[nodiscard]] static bool is_worker_thread() noexcept; + [[nodiscard]] static uint worker_thread_index() noexcept; + +public: + /// Barrier all threads + void barrier() noexcept; + /// Synchronize all threads + void synchronize() noexcept; + /// Return size of threads + [[nodiscard]] uint size() const noexcept; + /// Return count of tasks + [[nodiscard]] uint task_count() const noexcept { return _task_count.load(); } + + /// Run a function async and return future of return value + template + requires std::is_invocable_v + auto async(F &&f) noexcept { + using R = std::invoke_result_t; + auto promise = luisa::make_unique>( + std::allocator_arg, luisa::allocator{}); + auto future = promise->get_future().share(); + _task_count.fetch_add(1u); + _dispatch([promise = std::move(promise), future, f = std::forward(f), this]() mutable noexcept { + if constexpr (std::same_as) { + f(); + promise->set_value(); + } else { + promise->set_value(f()); + } + _task_count.fetch_sub(1u); + }); + return future; + } + + /// Run a function parallel + template + requires std::is_invocable_v + void parallel(uint n, F &&f) noexcept { + if (n == 0u) return; + _task_count.fetch_add(1u); + auto counter = luisa::make_unique(0u); + _dispatch_all( + [counter = std::move(counter), n, f = std::forward(f), this]() mutable noexcept { + auto i = 0u; + while ((i = counter->fetch_add(1u)) < n) { f(i); } + if (i == n) { _task_count.fetch_sub(1u); } + }, + n); + } + + /// Run a function 2D parallel + template + requires std::is_invocable_v + void parallel(uint nx, uint ny, F &&f) noexcept { + parallel(nx * ny, [nx, f = std::forward(f)](auto i) mutable noexcept { + f(i % nx, i / nx); + }); + } + + /// Run a function 3D parallel + template + requires std::is_invocable_v + void parallel(uint nx, uint ny, uint nz, F &&f) noexcept { + parallel(nx * ny * nz, [nx, ny, f = std::forward(f)](auto i) mutable noexcept { + f(i % nx, i / nx % ny, i / nx / ny); + }); + } + + template + requires std::is_invocable_v + auto async_parallel(uint n, F &&f) noexcept { + auto promise = luisa::make_unique>( + std::allocator_arg, luisa::allocator{}); + auto future = promise->get_future().share(); + if (n == 0u) { + promise->set_value(); + return future; + } + _task_count.fetch_add(1u); + auto counter = luisa::make_unique>(0u, 0u); + _dispatch_all( + [counter = std::move(counter), promise = std::move(promise), n, f = std::forward(f), this]() mutable noexcept { + auto i = 0u; + auto dispatched_count = 0u; + while ((i = counter->first.fetch_add(1u)) < n) { + f(i); + ++dispatched_count; + } + if (i == n) { + _task_count.fetch_sub(1u); + } + if (counter->second.fetch_add(dispatched_count) + dispatched_count == n) { + promise->set_value(); + } + }, + n); + return future; + } + + /// Run a function 2D parallel + template + requires std::is_invocable_v + auto async_parallel(uint nx, uint ny, F &&f) noexcept { + return async_parallel(nx * ny, [nx, f = std::forward(f)](auto i) mutable noexcept { + f(i % nx, i / nx); + }); + } + + /// Run a function 3D parallel + template + requires std::is_invocable_v + auto async_parallel(uint nx, uint ny, uint nz, F &&f) noexcept { + return async_parallel(nx * ny * nz, [nx, ny, f = std::forward(f)](auto i) mutable noexcept { + f(i % nx, i / nx % ny, i / nx / ny); + }); + } +}; + [[nodiscard]] ThreadPool &global_thread_pool() noexcept; -}// namespace luisa::render \ No newline at end of file +}// namespace luisa