Changeset View
Changeset View
Standalone View
Standalone View
intern/cycles/integrator/path_trace.cpp
| Show All 17 Lines | |||||
| #include "device/cpu/device.h" | #include "device/cpu/device.h" | ||||
| #include "device/device.h" | #include "device/device.h" | ||||
| #include "integrator/pass_accessor.h" | #include "integrator/pass_accessor.h" | ||||
| #include "integrator/render_scheduler.h" | #include "integrator/render_scheduler.h" | ||||
| #include "render/gpu_display.h" | #include "render/gpu_display.h" | ||||
| #include "render/pass.h" | #include "render/pass.h" | ||||
| #include "render/scene.h" | #include "render/scene.h" | ||||
| #include "render/tile.h" | |||||
| #include "util/util_algorithm.h" | #include "util/util_algorithm.h" | ||||
| #include "util/util_logging.h" | #include "util/util_logging.h" | ||||
| #include "util/util_progress.h" | #include "util/util_progress.h" | ||||
| #include "util/util_tbb.h" | #include "util/util_tbb.h" | ||||
| #include "util/util_time.h" | #include "util/util_time.h" | ||||
| CCL_NAMESPACE_BEGIN | CCL_NAMESPACE_BEGIN | ||||
| namespace { | PathTrace::PathTrace(Device *device, | ||||
| Film *film, | |||||
| DeviceScene *device_scene, | |||||
| RenderScheduler &render_scheduler, | |||||
| TileManager &tile_manager) | |||||
| : device_(device), | |||||
| device_scene_(device_scene), | |||||
| render_scheduler_(render_scheduler), | |||||
| tile_manager_(tile_manager) | |||||
| { | |||||
| DCHECK_NE(device_, nullptr); | |||||
| class TempCPURenderBuffers { | |||||
| public: | |||||
| /* `device_template` is used to access stats and profiler. */ | |||||
| explicit TempCPURenderBuffers(Device *device_template) | |||||
| { | { | ||||
| vector<DeviceInfo> cpu_devices; | vector<DeviceInfo> cpu_devices; | ||||
| device_cpu_info(cpu_devices); | device_cpu_info(cpu_devices); | ||||
| device.reset( | cpu_device_.reset(device_cpu_create(cpu_devices[0], device->stats, device->profiler)); | ||||
| device_cpu_create(cpu_devices[0], device_template->stats, device_template->profiler)); | |||||
| buffers = make_unique<RenderBuffers>(device.get()); | |||||
| } | } | ||||
| unique_ptr<Device> device; | |||||
| unique_ptr<RenderBuffers> buffers; | |||||
| }; | |||||
| } // namespace | |||||
| PathTrace::PathTrace(Device *device, | |||||
| Film *film, | |||||
| DeviceScene *device_scene, | |||||
| RenderScheduler &render_scheduler) | |||||
| : device_(device), device_scene_(device_scene), render_scheduler_(render_scheduler) | |||||
| { | |||||
| DCHECK_NE(device_, nullptr); | |||||
| /* Create path tracing work in advance, so that it can be reused by incremental sampling as much | /* Create path tracing work in advance, so that it can be reused by incremental sampling as much | ||||
| * as possible. */ | * as possible. */ | ||||
| device_->foreach_device([&](Device *path_trace_device) { | device_->foreach_device([&](Device *path_trace_device) { | ||||
| path_trace_works_.emplace_back(PathTraceWork::create( | path_trace_works_.emplace_back(PathTraceWork::create( | ||||
| path_trace_device, film, device_scene, &render_cancel_.is_requested)); | path_trace_device, film, device_scene, &render_cancel_.is_requested)); | ||||
| }); | }); | ||||
| work_balance_infos_.resize(path_trace_works_.size()); | work_balance_infos_.resize(path_trace_works_.size()); | ||||
| ▲ Show 20 Lines • Show All 49 Lines • ▼ Show 20 Lines | void PathTrace::reset(const BufferParams &big_tile_params) | ||||
| if (gpu_display_) { | if (gpu_display_) { | ||||
| gpu_display_->reset(big_tile_params); | gpu_display_->reset(big_tile_params); | ||||
| } | } | ||||
| render_state_.has_denoised_result_ = false; | render_state_.has_denoised_result_ = false; | ||||
| did_draw_after_reset_ = false; | did_draw_after_reset_ = false; | ||||
| full_frame_buffers_ = nullptr; | |||||
| } | } | ||||
| void PathTrace::set_progress(Progress *progress) | void PathTrace::set_progress(Progress *progress) | ||||
| { | { | ||||
| progress_ = progress; | progress_ = progress; | ||||
| } | } | ||||
| void PathTrace::render(const RenderWork &render_work) | void PathTrace::render(const RenderWork &render_work) | ||||
| ▲ Show 20 Lines • Show All 44 Lines • ▼ Show 20 Lines | if (render_cancel_.is_requested) { | ||||
| return; | return; | ||||
| } | } | ||||
| cryptomatte_postprocess(render_work); | cryptomatte_postprocess(render_work); | ||||
| if (render_cancel_.is_requested) { | if (render_cancel_.is_requested) { | ||||
| return; | return; | ||||
| } | } | ||||
| denoise(render_work); | denoise_tile(render_work); | ||||
| if (render_cancel_.is_requested) { | if (render_cancel_.is_requested) { | ||||
| return; | return; | ||||
| } | } | ||||
| update_display(render_work); | update_display(render_work); | ||||
| progress_update_if_needed(); | progress_update_if_needed(); | ||||
| if (render_work.write_final_result) { | write_tile_result(render_work); | ||||
| buffer_write(); | |||||
| } | handle_full_buffer(render_work); | ||||
| } | } | ||||
| void PathTrace::render_init_kernel_execution() | void PathTrace::render_init_kernel_execution() | ||||
| { | { | ||||
| for (auto &&path_trace_work : path_trace_works_) { | for (auto &&path_trace_work : path_trace_works_) { | ||||
| path_trace_work->init_execution(); | path_trace_work->init_execution(); | ||||
| } | } | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 101 Lines • ▼ Show 20 Lines | void PathTrace::init_render_buffers(const RenderWork &render_work) | ||||
| update_work_buffer_params_if_needed(render_work); | update_work_buffer_params_if_needed(render_work); | ||||
| /* Handle initialization scheduled by the render scheduler. */ | /* Handle initialization scheduled by the render scheduler. */ | ||||
| if (render_work.init_render_buffers) { | if (render_work.init_render_buffers) { | ||||
| tbb::parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) { | tbb::parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) { | ||||
| path_trace_work->zero_render_buffers(); | path_trace_work->zero_render_buffers(); | ||||
| }); | }); | ||||
| buffer_read(); | tile_buffer_read(); | ||||
| } | } | ||||
| } | } | ||||
| void PathTrace::path_trace(RenderWork &render_work) | void PathTrace::path_trace(RenderWork &render_work) | ||||
| { | { | ||||
| if (!render_work.path_trace.num_samples) { | if (!render_work.path_trace.num_samples) { | ||||
| return; | return; | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 108 Lines • ▼ Show 20 Lines | void PathTrace::cryptomatte_postprocess(const RenderWork &render_work) | ||||
| } | } | ||||
| VLOG(3) << "Perform cryptomatte work."; | VLOG(3) << "Perform cryptomatte work."; | ||||
| tbb::parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) { | tbb::parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) { | ||||
| path_trace_work->cryptomatte_postproces(); | path_trace_work->cryptomatte_postproces(); | ||||
| }); | }); | ||||
| } | } | ||||
| void PathTrace::denoise(const RenderWork &render_work) | void PathTrace::denoise_tile(const RenderWork &render_work) | ||||
| { | { | ||||
| if (!render_work.denoise) { | if (!render_work.tile.denoise) { | ||||
| return; | return; | ||||
| } | } | ||||
| if (!denoiser_) { | if (!denoiser_) { | ||||
| /* Denoiser was not configured, so nothing to do here. */ | /* Denoiser was not configured, so nothing to do here. */ | ||||
| return; | return; | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 60 Lines • ▼ Show 20 Lines | void PathTrace::update_display(const RenderWork &render_work) | ||||
| if (!render_work.update_display) { | if (!render_work.update_display) { | ||||
| return; | return; | ||||
| } | } | ||||
| if (!gpu_display_) { | if (!gpu_display_) { | ||||
| /* TODO(sergey): Ideally the offline buffers update will be done using same API than the | /* TODO(sergey): Ideally the offline buffers update will be done using same API than the | ||||
| * viewport GPU display. Seems to be a matter of moving pixels update API to a more abstract | * viewport GPU display. Seems to be a matter of moving pixels update API to a more abstract | ||||
| * class and using it here instead of `GPUDisplay`. */ | * class and using it here instead of `GPUDisplay`. */ | ||||
| if (buffer_update_cb) { | if (tile_buffer_update_cb) { | ||||
| VLOG(3) << "Invoke buffer update callback."; | VLOG(3) << "Invoke buffer update callback."; | ||||
| const double start_time = time_dt(); | const double start_time = time_dt(); | ||||
| buffer_update_cb(); | tile_buffer_update_cb(); | ||||
| render_scheduler_.report_display_update_time(render_work, time_dt() - start_time); | render_scheduler_.report_display_update_time(render_work, time_dt() - start_time); | ||||
| } | } | ||||
| else { | else { | ||||
| VLOG(3) << "Ignore display update."; | VLOG(3) << "Ignore display update."; | ||||
| } | } | ||||
| return; | return; | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 66 Lines • ▼ Show 20 Lines | void PathTrace::rebalance(const RenderWork &render_work) | ||||
| } | } | ||||
| if (!did_rebalance) { | if (!did_rebalance) { | ||||
| VLOG(kLogLevel) << "Balance in path trace works did not change."; | VLOG(kLogLevel) << "Balance in path trace works did not change."; | ||||
| render_scheduler_.report_rebalance_time(render_work, time_dt() - start_time, false); | render_scheduler_.report_rebalance_time(render_work, time_dt() - start_time, false); | ||||
| return; | return; | ||||
| } | } | ||||
| TempCPURenderBuffers big_tile_cpu_buffers(device_); | RenderBuffers big_tile_cpu_buffers(cpu_device_.get()); | ||||
| big_tile_cpu_buffers.buffers->reset(render_state_.effective_big_tile_params); | big_tile_cpu_buffers.reset(render_state_.effective_big_tile_params); | ||||
| copy_to_render_buffers(big_tile_cpu_buffers.buffers.get()); | copy_to_render_buffers(&big_tile_cpu_buffers); | ||||
| render_state_.need_reset_params = true; | render_state_.need_reset_params = true; | ||||
| update_work_buffer_params_if_needed(render_work); | update_work_buffer_params_if_needed(render_work); | ||||
| copy_from_render_buffers(big_tile_cpu_buffers.buffers.get()); | copy_from_render_buffers(&big_tile_cpu_buffers); | ||||
| render_scheduler_.report_rebalance_time(render_work, time_dt() - start_time, true); | render_scheduler_.report_rebalance_time(render_work, time_dt() - start_time, true); | ||||
| } | } | ||||
| void PathTrace::write_tile_result(const RenderWork &render_work) | |||||
| { | |||||
| if (!render_work.tile.write) { | |||||
| return; | |||||
| } | |||||
| VLOG(3) << "Write tile result."; | |||||
| const bool has_multiple_tiles = tile_manager_.has_multiple_tiles(); | |||||
| /* Write render tile result, but only if not using tiled rendering. | |||||
| * | |||||
| * Tiles are written to a partial file during rendering, and written to the software at the end | |||||
| * of rendering (wither when all tiles are finished, or when rendering was requested to be | |||||
| * cancelled). | |||||
| * | |||||
| * Important thing is: tile should be written to the software via callback only once. */ | |||||
| if (!has_multiple_tiles) { | |||||
| VLOG(3) << "Write tile result via buffer write callback."; | |||||
| tile_buffer_write(); | |||||
| } | |||||
| /* Write tile to disk, so that the render work's render buffer can be re-used for the next tile. | |||||
| */ | |||||
| if (has_multiple_tiles) { | |||||
| VLOG(3) << "Write tile result into partial file."; | |||||
| write_partial_tile(); | |||||
| } | |||||
| } | |||||
| void PathTrace::handle_full_buffer(const RenderWork &render_work) | |||||
| { | |||||
| if (!render_work.full.write) { | |||||
| return; | |||||
| } | |||||
| VLOG(3) << "Handle full-frame render buffer work."; | |||||
| if (!tile_manager_.has_partial_tiles()) { | |||||
| VLOG(3) << "No tiles in partial result."; | |||||
| return; | |||||
| } | |||||
| /* Free render buffers used by the path trace work to reduce memory peak. */ | |||||
| BufferParams empty_params; | |||||
| for (auto &&path_trace_work : path_trace_works_) { | |||||
| path_trace_work->get_render_buffers()->reset(empty_params); | |||||
| } | |||||
| render_state_.need_reset_params = true; | |||||
| /* TODO(sergey): Somehow free up session memory before readsing full frame. */ | |||||
| read_partial_full_buffer(); | |||||
| if (render_work.full.denoise) { | |||||
| /* Partial file is either scaled up to the final number of samples (when tile is cancelled) or | |||||
| * contains samples count pass. In the former case use final number of samples for the | |||||
| * denoising, and for the latter one the denoiser will sue sample count pass. */ | |||||
| const int num_samples = render_scheduler_.get_num_samples(); | |||||
| denoiser_->denoise_buffer( | |||||
| full_frame_buffers_->params, full_frame_buffers_.get(), num_samples, false); | |||||
| /* TODO(sergey): Report full-frame denoising time. It is different from the tile-based | |||||
| * denoising since it wouldn't be fair to use it for average values. */ | |||||
| } | |||||
| /* Write the full result pretending that there is a single tile. | |||||
| * Requires some state change, but allows to use same communication API with the software. */ | |||||
| tile_buffer_write(); | |||||
| /* Full frame is no longer needed, free it to save up memory. */ | |||||
| full_frame_buffers_ = nullptr; | |||||
| /* TODO(sergey): Only remove file if it is in the temporary directory. */ | |||||
| tile_manager_.remove_partial_file(); | |||||
| } | |||||
| void PathTrace::cancel() | void PathTrace::cancel() | ||||
| { | { | ||||
| thread_scoped_lock lock(render_cancel_.mutex); | thread_scoped_lock lock(render_cancel_.mutex); | ||||
| render_cancel_.is_requested = true; | render_cancel_.is_requested = true; | ||||
| while (render_cancel_.is_rendering) { | while (render_cancel_.is_rendering) { | ||||
| render_cancel_.condition.wait(lock); | render_cancel_.condition.wait(lock); | ||||
| Show All 17 Lines | if (progress_ != nullptr) { | ||||
| if (progress_->get_cancel()) { | if (progress_->get_cancel()) { | ||||
| return true; | return true; | ||||
| } | } | ||||
| } | } | ||||
| return false; | return false; | ||||
| } | } | ||||
| void PathTrace::buffer_write() | void PathTrace::tile_buffer_write() | ||||
| { | { | ||||
| if (!buffer_write_cb) { | if (!tile_buffer_write_cb) { | ||||
| return; | return; | ||||
| } | } | ||||
| buffer_write_cb(); | tile_buffer_write_cb(); | ||||
| } | } | ||||
| void PathTrace::buffer_read() | void PathTrace::tile_buffer_read() | ||||
| { | { | ||||
| if (!buffer_read_cb) { | if (!tile_buffer_read_cb) { | ||||
| return; | return; | ||||
| } | } | ||||
| if (buffer_read_cb()) { | if (tile_buffer_read_cb()) { | ||||
| tbb::parallel_for_each(path_trace_works_, [](unique_ptr<PathTraceWork> &path_trace_work) { | tbb::parallel_for_each(path_trace_works_, [](unique_ptr<PathTraceWork> &path_trace_work) { | ||||
| path_trace_work->copy_render_buffers_to_device(); | path_trace_work->copy_render_buffers_to_device(); | ||||
| }); | }); | ||||
| } | } | ||||
| } | } | ||||
| void PathTrace::write_partial_tile() | |||||
| { | |||||
| /* If the tile has less number than the requested number of samples scale the pixels up, so that | |||||
| * all tiles in the partial file has same number of samples. This avoids darkening of tile which | |||||
| * was in progress when user cancelled rendering. | |||||
| * | |||||
| * NOTE: If there is sample count pass do not scale. This allows to avoid extra processing, but | |||||
| * is also solving issues with scaling sample count pass which is not stored as float. */ | |||||
| /* TODO(sergey): To support resumable render we need to store per-tile sample, to make it | |||||
| * possible to stop rendering in the middle of tile and resume rendering from there. */ | |||||
| const int pass_sample_count = big_tile_params_.get_pass_offset(PASS_SAMPLE_COUNT); | |||||
| const int num_rendered_samples = render_scheduler_.get_num_rendered_samples(); | |||||
| const int num_samples = render_scheduler_.get_num_samples(); | |||||
| const bool need_scale = (pass_sample_count == PASS_UNUSED && | |||||
| num_rendered_samples != num_samples); | |||||
| if (num_rendered_samples == 0) { | |||||
| /* The tile has zero samples, no need to write it. */ | |||||
| return; | |||||
| } | |||||
| /* Get access to the CPU-side render buffers of the current big tile. */ | |||||
| RenderBuffers *buffers; | |||||
| RenderBuffers big_tile_cpu_buffers(cpu_device_.get()); | |||||
| if (path_trace_works_.size() == 1 && !need_scale) { | |||||
| path_trace_works_[0]->copy_render_buffers_from_device(); | |||||
| buffers = path_trace_works_[0]->get_render_buffers(); | |||||
| } | |||||
| else { | |||||
| big_tile_cpu_buffers.reset(render_state_.effective_big_tile_params); | |||||
| copy_to_render_buffers(&big_tile_cpu_buffers); | |||||
| if (need_scale) { | |||||
| const float scale = static_cast<float>(num_samples) / num_rendered_samples; | |||||
| render_buffers_scale_uniform(&big_tile_cpu_buffers, scale); | |||||
| } | |||||
| buffers = &big_tile_cpu_buffers; | |||||
| } | |||||
| if (!tile_manager_.write_tile(*buffers)) { | |||||
| LOG(ERROR) << "Error writing tile to partial file."; | |||||
| } | |||||
| } | |||||
| void PathTrace::read_partial_full_buffer() | |||||
| { | |||||
| VLOG(3) << "Reading full frame render buffer from partial file."; | |||||
| /* Make sure the partial file is finished to be written. | |||||
| * This will include writing all possible missing tiles, ensuring validness of the partial | |||||
| * file. */ | |||||
| tile_manager_.finish_write_tiles(); | |||||
| full_frame_buffers_ = make_unique<RenderBuffers>(cpu_device_.get()); | |||||
| if (!tile_manager_.read_full_buffer(full_frame_buffers_.get())) { | |||||
| LOG(ERROR) << "Error reading partial tiles file."; | |||||
| } | |||||
| } | |||||
| void PathTrace::progress_update_if_needed() | void PathTrace::progress_update_if_needed() | ||||
| { | { | ||||
| if (progress_ != nullptr) { | if (progress_ != nullptr) { | ||||
| progress_->add_samples(0, get_num_samples_in_buffer()); | progress_->add_samples(0, get_num_samples_in_buffer()); | ||||
| } | } | ||||
| if (progress_update_cb) { | if (progress_update_cb) { | ||||
| progress_update_cb(); | progress_update_cb(); | ||||
| Show All 15 Lines | void PathTrace::copy_from_render_buffers(RenderBuffers *render_buffers) | ||||
| tbb::parallel_for_each(path_trace_works_, | tbb::parallel_for_each(path_trace_works_, | ||||
| [&render_buffers](unique_ptr<PathTraceWork> &path_trace_work) { | [&render_buffers](unique_ptr<PathTraceWork> &path_trace_work) { | ||||
| path_trace_work->copy_from_render_buffers(render_buffers); | path_trace_work->copy_from_render_buffers(render_buffers); | ||||
| }); | }); | ||||
| } | } | ||||
| bool PathTrace::copy_render_tile_from_device() | bool PathTrace::copy_render_tile_from_device() | ||||
| { | { | ||||
| if (full_frame_buffers_) { | |||||
| /* Full frame buffer is always on the host side. */ | |||||
| return true; | |||||
| } | |||||
| bool success = true; | bool success = true; | ||||
| tbb::parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) { | tbb::parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) { | ||||
| if (!success) { | if (!success) { | ||||
| return; | return; | ||||
| } | } | ||||
| if (!path_trace_work->copy_render_buffers_from_device()) { | if (!path_trace_work->copy_render_buffers_from_device()) { | ||||
| success = false; | success = false; | ||||
| } | } | ||||
| }); | }); | ||||
| return success; | return success; | ||||
| } | } | ||||
| int PathTrace::get_num_render_tile_samples() const | |||||
| { | |||||
| if (full_frame_buffers_) { | |||||
| /* When full frame resutl is read from fisk it has all tiles scaled up to the final number of | |||||
| * samples. */ | |||||
| return render_scheduler_.get_num_samples(); | |||||
| } | |||||
| return render_scheduler_.get_num_rendered_samples(); | |||||
| } | |||||
| bool PathTrace::get_render_tile_pixels(const PassAccessor &pass_accessor, | bool PathTrace::get_render_tile_pixels(const PassAccessor &pass_accessor, | ||||
| const PassAccessor::Destination &destination) | const PassAccessor::Destination &destination) | ||||
| { | { | ||||
| if (full_frame_buffers_) { | |||||
| return pass_accessor.get_render_tile_pixels(full_frame_buffers_.get(), destination); | |||||
| } | |||||
| bool success = true; | bool success = true; | ||||
| tbb::parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) { | tbb::parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) { | ||||
| if (!success) { | if (!success) { | ||||
| return; | return; | ||||
| } | } | ||||
| if (!path_trace_work->get_render_tile_pixels(pass_accessor, destination)) { | if (!path_trace_work->get_render_tile_pixels(pass_accessor, destination)) { | ||||
| success = false; | success = false; | ||||
| Show All 15 Lines | tbb::parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) { | ||||
| if (!path_trace_work->set_render_tile_pixels(pass_accessor, source)) { | if (!path_trace_work->set_render_tile_pixels(pass_accessor, source)) { | ||||
| success = false; | success = false; | ||||
| } | } | ||||
| }); | }); | ||||
| return success; | return success; | ||||
| } | } | ||||
| int2 PathTrace::get_render_tile_size() const | |||||
| { | |||||
| if (full_frame_buffers_) { | |||||
| return make_int2(full_frame_buffers_->params.width, full_frame_buffers_->params.height); | |||||
| } | |||||
| const Tile &tile = tile_manager_.get_current_tile(); | |||||
| return make_int2(tile.width, tile.height); | |||||
| } | |||||
| int2 PathTrace::get_render_tile_offset() const | |||||
| { | |||||
| if (full_frame_buffers_) { | |||||
| return make_int2(full_frame_buffers_->params.full_x, full_frame_buffers_->params.full_y); | |||||
| } | |||||
| const Tile &tile = tile_manager_.get_current_tile(); | |||||
| return make_int2(tile.x, tile.y); | |||||
| } | |||||
| bool PathTrace::has_denoised_result() const | bool PathTrace::has_denoised_result() const | ||||
| { | { | ||||
| return render_state_.has_denoised_result_; | return render_state_.has_denoised_result_; | ||||
| } | } | ||||
| /* -------------------------------------------------------------------- | /* -------------------------------------------------------------------- | ||||
| * Report generation. | * Report generation. | ||||
| */ | */ | ||||
| ▲ Show 20 Lines • Show All 127 Lines • Show Last 20 Lines | |||||