Changeset View
Changeset View
Standalone View
Standalone View
intern/cycles/render/session.cpp
| Show All 12 Lines | |||||
| * See the License for the specific language governing permissions and | * See the License for the specific language governing permissions and | ||||
| * limitations under the License. | * limitations under the License. | ||||
| */ | */ | ||||
| #include <limits.h> | #include <limits.h> | ||||
| #include <string.h> | #include <string.h> | ||||
| #include "device/device.h" | #include "device/device.h" | ||||
| #include "integrator/pass_accessor_cpu.h" | |||||
| #include "integrator/path_trace.h" | |||||
| #include "render/bake.h" | #include "render/bake.h" | ||||
| #include "render/buffers.h" | #include "render/buffers.h" | ||||
| #include "render/camera.h" | #include "render/camera.h" | ||||
| #include "render/gpu_display.h" | |||||
| #include "render/graph.h" | #include "render/graph.h" | ||||
| #include "render/integrator.h" | #include "render/integrator.h" | ||||
| #include "render/light.h" | #include "render/light.h" | ||||
| #include "render/mesh.h" | #include "render/mesh.h" | ||||
| #include "render/object.h" | #include "render/object.h" | ||||
| #include "render/scene.h" | #include "render/scene.h" | ||||
| #include "render/session.h" | #include "render/session.h" | ||||
| #include "util/util_foreach.h" | #include "util/util_foreach.h" | ||||
| #include "util/util_function.h" | #include "util/util_function.h" | ||||
| #include "util/util_logging.h" | #include "util/util_logging.h" | ||||
| #include "util/util_math.h" | #include "util/util_math.h" | ||||
| #include "util/util_opengl.h" | #include "util/util_opengl.h" | ||||
| #include "util/util_task.h" | #include "util/util_task.h" | ||||
| #include "util/util_time.h" | #include "util/util_time.h" | ||||
| CCL_NAMESPACE_BEGIN | CCL_NAMESPACE_BEGIN | ||||
| /* Note about preserve_tile_device option for tile manager: | Session::Session(const SessionParams ¶ms_, const SceneParams &scene_params) | ||||
| * progressive refine and viewport rendering does requires tiles to | : params(params_), render_scheduler_(params.headless, params.background, params.pixel_size) | ||||
| * always be allocated for the same device | |||||
| */ | |||||
| Session::Session(const SessionParams ¶ms_) | |||||
| : params(params_), | |||||
| tile_manager(params.progressive, | |||||
| params.samples, | |||||
| params.tile_size, | |||||
| params.start_resolution, | |||||
| params.background == false || params.progressive_refine, | |||||
| params.background, | |||||
| params.tile_order, | |||||
| max(params.device.multi_devices.size(), 1), | |||||
| params.pixel_size), | |||||
| stats(), | |||||
| profiler() | |||||
| { | { | ||||
| device_use_gl_ = ((params.device.type != DEVICE_CPU) && !params.background); | |||||
| TaskScheduler::init(params.threads); | TaskScheduler::init(params.threads); | ||||
| session_thread_ = NULL; | session_thread_ = nullptr; | ||||
| scene = NULL; | |||||
| reset_time_ = 0.0; | |||||
| last_update_time_ = 0.0; | |||||
| delayed_reset_.do_reset = false; | delayed_reset_.do_reset = false; | ||||
| delayed_reset_.samples = 0; | delayed_reset_.samples = 0; | ||||
| display_outdated_ = false; | |||||
| gpu_draw_ready_ = false; | |||||
| gpu_need_display_buffer_update_ = false; | |||||
| pause_ = false; | pause_ = false; | ||||
| cancel_ = false; | cancel_ = false; | ||||
| new_work_added_ = false; | new_work_added_ = false; | ||||
| buffers = NULL; | device = Device::create(params.device, stats, profiler); | ||||
| display = NULL; | |||||
| /* Validate denoising parameters. */ | scene = new Scene(scene_params, device); | ||||
| set_denoising(params.denoising); | |||||
| /* Create CPU/GPU devices. */ | /* Configure path tracer. */ | ||||
| device = Device::create(params.device, stats, profiler, params.background); | path_trace_ = make_unique<PathTrace>(device, scene->film, &scene->dscene, render_scheduler_); | ||||
| path_trace_->set_progress(&progress); | |||||
| if (!device->error_message().empty()) { | path_trace_->buffer_update_cb = [&]() { | ||||
| progress.set_error(device->error_message()); | if (!update_render_tile_cb) { | ||||
| return; | return; | ||||
| } | } | ||||
| update_render_tile_cb(); | |||||
| /* Create buffers for interactive rendering. */ | }; | ||||
| if (!(params.background && !params.write_render_cb)) { | path_trace_->buffer_write_cb = [&]() { | ||||
| buffers = new RenderBuffers(device); | if (!write_render_tile_cb) { | ||||
| display = new DisplayBuffer(device, params.display_buffer_linear); | return; | ||||
| } | } | ||||
| write_render_tile_cb(); | |||||
| }; | |||||
| path_trace_->buffer_read_cb = [&]() -> bool { | |||||
| if (!read_render_tile_cb) { | |||||
| return false; | |||||
| } | |||||
| read_render_tile_cb(); | |||||
| return true; | |||||
| }; | |||||
| path_trace_->progress_update_cb = [&]() { update_status_time(); }; | |||||
| } | } | ||||
| Session::~Session() | Session::~Session() | ||||
| { | { | ||||
| cancel(); | cancel(); | ||||
| /* TODO(sergey): Bring the passes in viewport back. | |||||
| * It is unclear why there is such an exception needed though. */ | |||||
| #if 0 | |||||
| if (buffers && params.write_render_cb) { | if (buffers && params.write_render_cb) { | ||||
| /* Copy to display buffer and write out image if requested */ | /* Copy to display buffer and write out image if requested */ | ||||
| delete display; | delete display; | ||||
| display = new DisplayBuffer(device, false); | display = new DisplayBuffer(device, false); | ||||
| display->reset(buffers->params); | display->reset(buffers->params); | ||||
| copy_to_display_buffer(params.samples); | copy_to_display_buffer(params.samples); | ||||
| int w = display->draw_width; | int w = display->draw_width; | ||||
| int h = display->draw_height; | int h = display->draw_height; | ||||
| uchar4 *pixels = display->rgba_byte.copy_from_device(0, w, h); | uchar4 *pixels = display->rgba_byte.copy_from_device(0, w, h); | ||||
| params.write_render_cb((uchar *)pixels, w, h, 4); | params.write_render_cb((uchar *)pixels, w, h, 4); | ||||
| } | } | ||||
| #endif | |||||
| /* Make sure path tracer is destroyed before the deviec. This is needed because destruction might | |||||
| * need to access device for device memory free. */ | |||||
| /* TODO(sergey): Convert device to be unique_ptr, and rely on C++ to destruct objects in the | |||||
| * pre-defined order. */ | |||||
| path_trace_.reset(); | |||||
| #if 0 | |||||
| /* clean up */ | /* clean up */ | ||||
| tile_manager.device_free(); | tile_manager_.device_free(); | ||||
| #endif | |||||
| delete buffers; | |||||
| delete display; | |||||
| delete scene; | delete scene; | ||||
| delete device; | delete device; | ||||
| TaskScheduler::exit(); | TaskScheduler::exit(); | ||||
| } | } | ||||
| void Session::start() | void Session::start() | ||||
| { | { | ||||
| if (!session_thread_) { | if (!session_thread_) { | ||||
| session_thread_ = new thread(function_bind(&Session::run, this)); | session_thread_ = new thread(function_bind(&Session::run, this)); | ||||
| } | } | ||||
| } | } | ||||
| void Session::cancel() | void Session::cancel(bool quick) | ||||
| { | { | ||||
| if (quick && path_trace_) { | |||||
| path_trace_->cancel(); | |||||
| } | |||||
| if (session_thread_) { | if (session_thread_) { | ||||
| /* wait for session thread to end */ | /* wait for session thread to end */ | ||||
| progress.set_cancel("Exiting"); | progress.set_cancel("Exiting"); | ||||
| gpu_need_display_buffer_update_ = false; | |||||
| gpu_need_display_buffer_update_cond_.notify_all(); | |||||
| { | { | ||||
| thread_scoped_lock pause_lock(pause_mutex_); | thread_scoped_lock pause_lock(pause_mutex_); | ||||
| pause_ = false; | pause_ = false; | ||||
| cancel_ = true; | cancel_ = true; | ||||
| } | } | ||||
| pause_cond_.notify_all(); | pause_cond_.notify_all(); | ||||
| wait(); | wait(); | ||||
| } | } | ||||
| } | } | ||||
| bool Session::ready_to_reset() | bool Session::ready_to_reset() | ||||
| { | { | ||||
| double dt = time_dt() - reset_time_; | return path_trace_->ready_to_reset(); | ||||
| if (!display_outdated_) | |||||
| return (dt > params.reset_timeout); | |||||
| else | |||||
| return (dt > params.cancel_timeout); | |||||
| } | |||||
| /* GPU Session */ | |||||
| void Session::reset_gpu(BufferParams &buffer_params, int samples) | |||||
| { | |||||
| thread_scoped_lock pause_lock(pause_mutex_); | |||||
| /* block for buffer access and reset immediately. we can't do this | |||||
| * in the thread, because we need to allocate an OpenGL buffer, and | |||||
| * that only works in the main thread */ | |||||
| thread_scoped_lock display_lock(display_mutex_); | |||||
| thread_scoped_lock buffers_lock(buffers_mutex_); | |||||
| display_outdated_ = true; | |||||
| reset_time_ = time_dt(); | |||||
| reset_(buffer_params, samples); | |||||
| gpu_need_display_buffer_update_ = false; | |||||
| gpu_need_display_buffer_update_cond_.notify_all(); | |||||
| new_work_added_ = true; | |||||
| pause_cond_.notify_all(); | |||||
| } | } | ||||
| bool Session::draw_gpu(BufferParams &buffer_params, DeviceDrawParams &draw_params) | void Session::run_main_render_loop() | ||||
| { | { | ||||
| /* block for buffer access */ | while (true) { | ||||
| thread_scoped_lock display_lock(display_mutex_); | RenderWork render_work = run_update_for_next_iteration(); | ||||
| /* first check we already rendered something */ | |||||
| if (gpu_draw_ready_) { | |||||
| /* then verify the buffers have the expected size, so we don't | |||||
| * draw previous results in a resized window */ | |||||
| if (buffer_params.width == display->params.width && | |||||
| buffer_params.height == display->params.height) { | |||||
| /* for CUDA we need to do tone-mapping still, since we can | |||||
| * only access GL buffers from the main thread. */ | |||||
| if (gpu_need_display_buffer_update_) { | |||||
| thread_scoped_lock buffers_lock(buffers_mutex_); | |||||
| copy_to_display_buffer(tile_manager.state.sample); | |||||
| gpu_need_display_buffer_update_ = false; | |||||
| gpu_need_display_buffer_update_cond_.notify_all(); | |||||
| } | |||||
| display->draw(device, draw_params); | if (!render_work) { | ||||
| if (VLOG_IS_ON(2)) { | |||||
| if (display_outdated_ && (time_dt() - reset_time_) > params.text_timeout) | double total_time, render_time; | ||||
| return false; | progress.get_time(total_time, render_time); | ||||
| VLOG(2) << "Rendering in main loop is done in " << render_time << " seconds."; | |||||
| return true; | VLOG(2) << path_trace_->full_report(); | ||||
| } | |||||
| } | } | ||||
| return false; | |||||
| } | |||||
| void Session::run_gpu() | |||||
| { | |||||
| bool tiles_written = false; | |||||
| reset_time_ = time_dt(); | |||||
| last_update_time_ = time_dt(); | |||||
| last_display_time_ = last_update_time_; | |||||
| progress.set_render_start_time(); | |||||
| while (!progress.get_cancel()) { | |||||
| const bool no_tiles = !run_update_for_next_iteration(); | |||||
| if (no_tiles) { | |||||
| if (params.background) { | if (params.background) { | ||||
| /* if no work left and in background mode, we can stop immediately */ | /* if no work left and in background mode, we can stop immediately. */ | ||||
| progress.set_status("Finished"); | progress.set_status("Finished"); | ||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| if (run_wait_for_work(no_tiles)) { | const bool did_cancel = progress.get_cancel(); | ||||
| continue; | if (did_cancel) { | ||||
| } | render_scheduler_.render_work_reschedule_on_cancel(render_work); | ||||
| if (!render_work) { | |||||
| if (progress.get_cancel()) { | |||||
| break; | |||||
| } | |||||
| if (!no_tiles) { | |||||
| if (!device->error_message().empty()) | |||||
| progress.set_error(device->error_message()); | |||||
| if (progress.get_cancel()) | |||||
| break; | |||||
| /* buffers mutex is locked entirely while rendering each | |||||
| * sample, and released/reacquired on each iteration to allow | |||||
| * reset and draw in between */ | |||||
| thread_scoped_lock buffers_lock(buffers_mutex_); | |||||
| /* update status and timing */ | |||||
| update_status_time(); | |||||
| /* render */ | |||||
| bool delayed_denoise = false; | |||||
| const bool need_denoise = render_need_denoise(delayed_denoise); | |||||
| render(need_denoise); | |||||
| device->task_wait(); | |||||
| if (!device->error_message().empty()) | |||||
| progress.set_cancel(device->error_message()); | |||||
| /* update status and timing */ | |||||
| update_status_time(); | |||||
| gpu_need_display_buffer_update_ = !delayed_denoise; | |||||
| gpu_draw_ready_ = true; | |||||
| progress.set_update(); | |||||
| /* wait for until display buffer is updated */ | |||||
| if (!params.background) { | |||||
| while (gpu_need_display_buffer_update_) { | |||||
| if (progress.get_cancel()) | |||||
| break; | |||||
| gpu_need_display_buffer_update_cond_.wait(buffers_lock); | |||||
| } | |||||
| } | |||||
| if (!device->error_message().empty()) | |||||
| progress.set_error(device->error_message()); | |||||
| tiles_written = update_progressive_refine(progress.get_cancel()); | |||||
| if (progress.get_cancel()) | |||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| else if (run_wait_for_work(render_work)) { | |||||
| if (!tiles_written) | |||||
| update_progressive_refine(true); | |||||
| } | |||||
| /* CPU Session */ | |||||
| void Session::reset_cpu(BufferParams &buffer_params, int samples) | |||||
| { | |||||
| thread_scoped_lock reset_lock(delayed_reset_.mutex); | |||||
| thread_scoped_lock pause_lock(pause_mutex_); | |||||
| display_outdated_ = true; | |||||
| reset_time_ = time_dt(); | |||||
| delayed_reset_.params = buffer_params; | |||||
| delayed_reset_.samples = samples; | |||||
| delayed_reset_.do_reset = true; | |||||
| device->task_cancel(); | |||||
| pause_cond_.notify_all(); | |||||
| } | |||||
| bool Session::draw_cpu(BufferParams &buffer_params, DeviceDrawParams &draw_params) | |||||
| { | |||||
| thread_scoped_lock display_lock(display_mutex_); | |||||
| /* first check we already rendered something */ | |||||
| if (display->draw_ready()) { | |||||
| /* then verify the buffers have the expected size, so we don't | |||||
| * draw previous results in a resized window */ | |||||
| if (buffer_params.width == display->params.width && | |||||
| buffer_params.height == display->params.height) { | |||||
| display->draw(device, draw_params); | |||||
| if (display_outdated_ && (time_dt() - reset_time_) > params.text_timeout) | |||||
| return false; | |||||
| return true; | |||||
| } | |||||
| } | |||||
| return false; | |||||
| } | |||||
| bool Session::steal_tile(RenderTile &rtile, Device *tile_device, thread_scoped_lock &tile_lock) | |||||
| { | |||||
| /* Devices that can get their tiles stolen don't steal tiles themselves. | |||||
| * Additionally, if there are no stealable tiles in flight, give up here. */ | |||||
| if (tile_device->info.type == DEVICE_CPU || stealable_tiles_ == 0) { | |||||
| return false; | |||||
| } | |||||
| /* Wait until no other thread is trying to steal a tile. */ | |||||
| while (tile_stealing_state_ != NOT_STEALING && stealable_tiles_ > 0) { | |||||
| /* Someone else is currently trying to get a tile. | |||||
| * Wait on the condition variable and try later. */ | |||||
| tile_steal_cond_.wait(tile_lock); | |||||
| } | |||||
| /* If another thread stole the last stealable tile in the meantime, give up. */ | |||||
| if (stealable_tiles_ == 0) { | |||||
| return false; | |||||
| } | |||||
| /* There are stealable tiles in flight, so signal that one should be released. */ | |||||
| tile_stealing_state_ = WAITING_FOR_TILE; | |||||
| /* Wait until a device notices the signal and releases its tile. */ | |||||
| while (tile_stealing_state_ != GOT_TILE && stealable_tiles_ > 0) { | |||||
| tile_steal_cond_.wait(tile_lock); | |||||
| } | |||||
| /* If the last stealable tile finished on its own, give up. */ | |||||
| if (tile_stealing_state_ != GOT_TILE) { | |||||
| tile_stealing_state_ = NOT_STEALING; | |||||
| return false; | |||||
| } | |||||
| /* Successfully stole a tile, now move it to the new device. */ | |||||
| rtile = stolen_tile_; | |||||
| rtile.buffers->buffer.move_device(tile_device); | |||||
| rtile.buffer = rtile.buffers->buffer.device_pointer; | |||||
| rtile.stealing_state = RenderTile::NO_STEALING; | |||||
| rtile.num_samples -= (rtile.sample - rtile.start_sample); | |||||
| rtile.start_sample = rtile.sample; | |||||
| tile_stealing_state_ = NOT_STEALING; | |||||
| /* Poke any threads which might be waiting for NOT_STEALING above. */ | |||||
| tile_steal_cond_.notify_one(); | |||||
| return true; | |||||
| } | |||||
| bool Session::get_tile_stolen() | |||||
| { | |||||
| /* If tile_stealing_state is WAITING_FOR_TILE, atomically set it to RELEASING_TILE | |||||
| * and return true. */ | |||||
| TileStealingState expected = WAITING_FOR_TILE; | |||||
| return tile_stealing_state_.compare_exchange_weak(expected, RELEASING_TILE); | |||||
| } | |||||
| bool Session::acquire_tile(RenderTile &rtile, Device *tile_device, uint tile_types) | |||||
| { | |||||
| if (progress.get_cancel()) { | |||||
| if (params.progressive_refine == false) { | |||||
| /* for progressive refine current sample should be finished for all tiles */ | |||||
| return false; | |||||
| } | |||||
| } | |||||
| thread_scoped_lock tile_lock(tile_mutex_); | |||||
| /* get next tile from manager */ | |||||
| Tile *tile; | |||||
| int device_num = device->device_number(tile_device); | |||||
| while (!tile_manager.next_tile(tile, device_num, tile_types)) { | |||||
| /* Can only steal tiles on devices that support rendering | |||||
| * This is because denoising tiles cannot be stolen (see below) | |||||
| */ | |||||
| if ((tile_types & (RenderTile::PATH_TRACE | RenderTile::BAKE)) && | |||||
| steal_tile(rtile, tile_device, tile_lock)) { | |||||
| return true; | |||||
| } | |||||
| /* Wait for denoising tiles to become available */ | |||||
| if ((tile_types & RenderTile::DENOISE) && !progress.get_cancel() && tile_manager.has_tiles()) { | |||||
| denoising_cond_.wait(tile_lock); | |||||
| continue; | continue; | ||||
| } | } | ||||
| return false; | |||||
| } | |||||
| /* fill render tile */ | |||||
| rtile.x = tile_manager.state.buffer.full_x + tile->x; | |||||
| rtile.y = tile_manager.state.buffer.full_y + tile->y; | |||||
| rtile.w = tile->w; | |||||
| rtile.h = tile->h; | |||||
| rtile.start_sample = tile_manager.state.sample; | |||||
| rtile.num_samples = tile_manager.state.num_samples; | |||||
| rtile.resolution = tile_manager.state.resolution_divider; | |||||
| rtile.tile_index = tile->index; | |||||
| rtile.stealing_state = RenderTile::NO_STEALING; | |||||
| if (tile->state == Tile::DENOISE) { | |||||
| rtile.task = RenderTile::DENOISE; | |||||
| } | |||||
| else { | |||||
| if (tile_device->info.type == DEVICE_CPU) { | |||||
| stealable_tiles_++; | |||||
| rtile.stealing_state = RenderTile::CAN_BE_STOLEN; | |||||
| } | |||||
| if (read_bake_tile_cb) { | |||||
| rtile.task = RenderTile::BAKE; | |||||
| } | |||||
| else { | |||||
| rtile.task = RenderTile::PATH_TRACE; | |||||
| } | |||||
| } | |||||
| tile_lock.unlock(); | |||||
| /* in case of a permanent buffer, return it, otherwise we will allocate | |||||
| * a new temporary buffer */ | |||||
| if (buffers) { | |||||
| tile_manager.state.buffer.get_offset_stride(rtile.offset, rtile.stride); | |||||
| rtile.buffer = buffers->buffer.device_pointer; | |||||
| rtile.buffers = buffers; | |||||
| device->map_tile(tile_device, rtile); | |||||
| /* Reset copy state, since buffer contents change after the tile was acquired */ | |||||
| buffers->map_neighbor_copied = false; | |||||
| /* This hack ensures that the copy in 'MultiDevice::map_neighbor_tiles' accounts | |||||
| * for the buffer resolution divider. */ | |||||
| buffers->buffer.data_width = (buffers->params.width * buffers->params.get_passes_size()) / | |||||
| tile_manager.state.resolution_divider; | |||||
| buffers->buffer.data_height = buffers->params.height / tile_manager.state.resolution_divider; | |||||
| return true; | |||||
| } | |||||
| if (tile->buffers == NULL) { | |||||
| /* fill buffer parameters */ | |||||
| BufferParams buffer_params = tile_manager.params; | |||||
| buffer_params.full_x = rtile.x; | |||||
| buffer_params.full_y = rtile.y; | |||||
| buffer_params.width = rtile.w; | |||||
| buffer_params.height = rtile.h; | |||||
| /* allocate buffers */ | |||||
| tile->buffers = new RenderBuffers(tile_device); | |||||
| tile->buffers->reset(buffer_params); | |||||
| } | |||||
| else if (tile->buffers->buffer.device != tile_device) { | |||||
| /* Move buffer to current tile device again in case it was stolen before. | |||||
| * Not needed for denoising since that already handles mapping of tiles and | |||||
| * neighbors to its own device. */ | |||||
| if (rtile.task != RenderTile::DENOISE) { | |||||
| tile->buffers->buffer.move_device(tile_device); | |||||
| } | |||||
| } | |||||
| tile->buffers->map_neighbor_copied = false; | |||||
| tile->buffers->params.get_offset_stride(rtile.offset, rtile.stride); | |||||
| rtile.buffer = tile->buffers->buffer.device_pointer; | |||||
| rtile.buffers = tile->buffers; | |||||
| rtile.sample = tile_manager.state.sample; | |||||
| if (read_bake_tile_cb) { | |||||
| /* This will read any passes needed as input for baking. */ | |||||
| if (tile_manager.state.sample == tile_manager.range_start_sample) { | |||||
| { | |||||
| thread_scoped_lock tile_lock(tile_mutex_); | |||||
| read_bake_tile_cb(rtile); | |||||
| } | |||||
| rtile.buffers->buffer.copy_to_device(); | |||||
| } | |||||
| } | |||||
| else { | |||||
| /* This will tag tile as IN PROGRESS in blender-side render pipeline, | |||||
| * which is needed to highlight currently rendering tile before first | |||||
| * sample was processed for it. */ | |||||
| update_tile_sample(rtile); | |||||
| } | |||||
| return true; | |||||
| } | |||||
| void Session::update_tile_sample(RenderTile &rtile) | |||||
| { | |||||
| thread_scoped_lock tile_lock(tile_mutex_); | |||||
| if (update_render_tile_cb) { | |||||
| if (params.progressive_refine == false) { | |||||
| /* todo: optimize this by making it thread safe and removing lock */ | |||||
| update_render_tile_cb(rtile, true); | |||||
| } | |||||
| } | |||||
| update_status_time(); | |||||
| } | |||||
| void Session::release_tile(RenderTile &rtile, const bool need_denoise) | |||||
| { | { | ||||
| thread_scoped_lock tile_lock(tile_mutex_); | |||||
| if (rtile.stealing_state != RenderTile::NO_STEALING) { | |||||
| stealable_tiles_--; | |||||
| if (rtile.stealing_state == RenderTile::WAS_STOLEN) { | |||||
| /* If the tile is being stolen, don't release it here - the new device will pick up where | |||||
| * the old one left off. */ | |||||
| assert(tile_stealing_state_ == RELEASING_TILE); | |||||
| assert(rtile.sample < rtile.start_sample + rtile.num_samples); | |||||
| tile_stealing_state_ = GOT_TILE; | |||||
| stolen_tile_ = rtile; | |||||
| tile_steal_cond_.notify_all(); | |||||
| return; | |||||
| } | |||||
| else if (stealable_tiles_ == 0) { | |||||
| /* If this was the last stealable tile, wake up any threads still waiting for one. */ | |||||
| tile_steal_cond_.notify_all(); | |||||
| } | |||||
| } | |||||
| progress.add_finished_tile(rtile.task == RenderTile::DENOISE); | |||||
| bool delete_tile; | |||||
| if (tile_manager.finish_tile(rtile.tile_index, need_denoise, delete_tile)) { | |||||
| /* Finished tile pixels write. */ | |||||
| if (write_render_tile_cb && params.progressive_refine == false) { | |||||
| write_render_tile_cb(rtile); | |||||
| } | |||||
| if (delete_tile) { | |||||
| delete rtile.buffers; | |||||
| tile_manager.state.tiles[rtile.tile_index].buffers = NULL; | |||||
| } | |||||
| } | |||||
| else { | |||||
| /* In progress tile pixels update. */ | |||||
| if (update_render_tile_cb && params.progressive_refine == false) { | |||||
| update_render_tile_cb(rtile, false); | |||||
| } | |||||
| } | |||||
| update_status_time(); | |||||
| /* Notify denoising thread that a tile was finished. */ | |||||
| denoising_cond_.notify_all(); | |||||
| } | |||||
| void Session::map_neighbor_tiles(RenderTileNeighbors &neighbors, Device *tile_device) | |||||
| { | |||||
| thread_scoped_lock tile_lock(tile_mutex_); | |||||
| const int4 image_region = make_int4( | |||||
| tile_manager.state.buffer.full_x, | |||||
| tile_manager.state.buffer.full_y, | |||||
| tile_manager.state.buffer.full_x + tile_manager.state.buffer.width, | |||||
| tile_manager.state.buffer.full_y + tile_manager.state.buffer.height); | |||||
| RenderTile ¢er_tile = neighbors.tiles[RenderTileNeighbors::CENTER]; | |||||
| if (!tile_manager.schedule_denoising) { | |||||
| /* Fix up tile slices with overlap. */ | |||||
| if (tile_manager.slice_overlap != 0) { | |||||
| int y = max(center_tile.y - tile_manager.slice_overlap, image_region.y); | |||||
| center_tile.h = min(center_tile.y + center_tile.h + tile_manager.slice_overlap, | |||||
| image_region.w) - | |||||
| y; | |||||
| center_tile.y = y; | |||||
| } | |||||
| /* Tiles are not being denoised individually, which means the entire image is processed. */ | |||||
| neighbors.set_bounds_from_center(); | |||||
| } | |||||
| else { | |||||
| int center_idx = center_tile.tile_index; | |||||
| assert(tile_manager.state.tiles[center_idx].state == Tile::DENOISE); | |||||
| for (int dy = -1, i = 0; dy <= 1; dy++) { | |||||
| for (int dx = -1; dx <= 1; dx++, i++) { | |||||
| RenderTile &rtile = neighbors.tiles[i]; | |||||
| int nindex = tile_manager.get_neighbor_index(center_idx, i); | |||||
| if (nindex >= 0) { | |||||
| Tile *tile = &tile_manager.state.tiles[nindex]; | |||||
| rtile.x = image_region.x + tile->x; | |||||
| rtile.y = image_region.y + tile->y; | |||||
| rtile.w = tile->w; | |||||
| rtile.h = tile->h; | |||||
| if (buffers) { | |||||
| tile_manager.state.buffer.get_offset_stride(rtile.offset, rtile.stride); | |||||
| rtile.buffer = buffers->buffer.device_pointer; | |||||
| rtile.buffers = buffers; | |||||
| } | |||||
| else { | |||||
| assert(tile->buffers); | |||||
| tile->buffers->params.get_offset_stride(rtile.offset, rtile.stride); | |||||
| rtile.buffer = tile->buffers->buffer.device_pointer; | |||||
| rtile.buffers = tile->buffers; | |||||
| } | |||||
| } | |||||
| else { | |||||
| int px = center_tile.x + dx * params.tile_size.x; | |||||
| int py = center_tile.y + dy * params.tile_size.y; | |||||
| rtile.x = clamp(px, image_region.x, image_region.z); | |||||
| rtile.y = clamp(py, image_region.y, image_region.w); | |||||
| rtile.w = rtile.h = 0; | |||||
| rtile.buffer = (device_ptr)NULL; | |||||
| rtile.buffers = NULL; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| assert(center_tile.buffers); | |||||
| device->map_neighbor_tiles(tile_device, neighbors); | |||||
| /* The denoised result is written back to the original tile. */ | |||||
| neighbors.target = center_tile; | |||||
| } | |||||
| void Session::unmap_neighbor_tiles(RenderTileNeighbors &neighbors, Device *tile_device) | |||||
| { | |||||
| thread_scoped_lock tile_lock(tile_mutex_); | |||||
| device->unmap_neighbor_tiles(tile_device, neighbors); | |||||
| } | |||||
| void Session::run_cpu() | |||||
| { | |||||
| bool tiles_written = false; | |||||
| last_update_time_ = time_dt(); | |||||
| last_display_time_ = last_update_time_; | |||||
| while (!progress.get_cancel()) { | |||||
| const bool no_tiles = !run_update_for_next_iteration(); | |||||
| bool need_copy_to_display_buffer = false; | |||||
| if (no_tiles) { | |||||
| if (params.background) { | |||||
| /* if no work left and in background mode, we can stop immediately */ | |||||
| progress.set_status("Finished"); | |||||
| break; | |||||
| } | |||||
| } | |||||
| if (run_wait_for_work(no_tiles)) { | |||||
| continue; | |||||
| } | |||||
| if (progress.get_cancel()) { | |||||
| break; | |||||
| } | |||||
| if (!no_tiles) { | |||||
| if (!device->error_message().empty()) | |||||
| progress.set_error(device->error_message()); | |||||
| if (progress.get_cancel()) | |||||
| break; | |||||
| /* buffers mutex is locked entirely while rendering each | /* buffers mutex is locked entirely while rendering each | ||||
| * sample, and released/reacquired on each iteration to allow | * sample, and released/reacquired on each iteration to allow | ||||
| * reset and draw in between */ | * reset and draw in between */ | ||||
| thread_scoped_lock buffers_lock(buffers_mutex_); | thread_scoped_lock buffers_lock(buffers_mutex_); | ||||
| /* update status and timing */ | /* update status and timing */ | ||||
| update_status_time(); | update_status_time(); | ||||
| /* render */ | /* render */ | ||||
| bool delayed_denoise = false; | path_trace_->render(render_work); | ||||
| const bool need_denoise = render_need_denoise(delayed_denoise); | |||||
| render(need_denoise); | |||||
| /* update status and timing */ | /* update status and timing */ | ||||
| update_status_time(); | update_status_time(); | ||||
| if (!params.background) | if (device->have_error()) { | ||||
| need_copy_to_display_buffer = !delayed_denoise; | const string &error_message = device->error_message(); | ||||
| progress.set_error(error_message); | |||||
| if (!device->error_message().empty()) | progress.set_cancel(error_message); | ||||
| progress.set_error(device->error_message()); | break; | ||||
| } | |||||
| device->task_wait(); | |||||
| { | |||||
| thread_scoped_lock reset_lock(delayed_reset_.mutex); | |||||
| thread_scoped_lock buffers_lock(buffers_mutex_); | |||||
| thread_scoped_lock display_lock(display_mutex_); | |||||
| if (delayed_reset_.do_reset) { | |||||
| /* reset rendering if request from main thread */ | |||||
| delayed_reset_.do_reset = false; | |||||
| reset_(delayed_reset_.params, delayed_reset_.samples); | |||||
| } | } | ||||
| else if (need_copy_to_display_buffer) { | |||||
| /* Only copy to display_buffer if we do not reset, we don't | |||||
| * want to show the result of an incomplete sample */ | |||||
| copy_to_display_buffer(tile_manager.state.sample); | |||||
| } | } | ||||
| if (!device->error_message().empty()) | progress.set_update(); | ||||
| progress.set_error(device->error_message()); | |||||
| tiles_written = update_progressive_refine(progress.get_cancel()); | if (did_cancel) { | ||||
| break; | |||||
| } | } | ||||
| progress.set_update(); | |||||
| } | } | ||||
| if (!tiles_written) | |||||
| update_progressive_refine(true); | |||||
| } | } | ||||
| void Session::run() | void Session::run() | ||||
| { | { | ||||
| if (params.use_profiling && (params.device.type == DEVICE_CPU)) { | if (params.use_profiling && (params.device.type == DEVICE_CPU)) { | ||||
| profiler.start(); | profiler.start(); | ||||
| } | } | ||||
| /* session thread loop */ | /* session thread loop */ | ||||
| progress.set_status("Waiting for render to start"); | progress.set_status("Waiting for render to start"); | ||||
| /* run */ | /* run */ | ||||
| if (!progress.get_cancel()) { | if (!progress.get_cancel()) { | ||||
| /* reset number of rendered samples */ | /* reset number of rendered samples */ | ||||
| progress.reset_sample(); | progress.reset_sample(); | ||||
| if (device_use_gl_) | run_main_render_loop(); | ||||
| run_gpu(); | |||||
| else | |||||
| run_cpu(); | |||||
| } | } | ||||
| profiler.stop(); | profiler.stop(); | ||||
| /* progress update */ | /* progress update */ | ||||
| if (progress.get_cancel()) | if (progress.get_cancel()) | ||||
| progress.set_status(progress.get_cancel_message()); | progress.set_status(progress.get_cancel_message()); | ||||
| else | else | ||||
| progress.set_update(); | progress.set_update(); | ||||
| } | } | ||||
| bool Session::run_update_for_next_iteration() | RenderWork Session::run_update_for_next_iteration() | ||||
| { | { | ||||
| RenderWork render_work; | |||||
| thread_scoped_lock scene_lock(scene->mutex); | thread_scoped_lock scene_lock(scene->mutex); | ||||
| thread_scoped_lock reset_lock(delayed_reset_.mutex); | thread_scoped_lock reset_lock(delayed_reset_.mutex); | ||||
| bool have_tiles = true; | |||||
| bool switched_to_new_tile = false; | |||||
| if (delayed_reset_.do_reset) { | if (delayed_reset_.do_reset) { | ||||
| thread_scoped_lock buffers_lock(buffers_mutex_); | thread_scoped_lock buffers_lock(buffers_mutex_); | ||||
| reset_(delayed_reset_.params, delayed_reset_.samples); | do_delayed_reset(); | ||||
| delayed_reset_.do_reset = false; | |||||
| /* After reset make sure the tile manager is at the first big tile. */ | |||||
| have_tiles = tile_manager_.next(); | |||||
| switched_to_new_tile = true; | |||||
| } | } | ||||
| const bool have_tiles = tile_manager.next(); | /* Update number of samples in the integrator. | ||||
| * Ideally this would need to happen once in `Session::set_samples()`, but the issue there is | |||||
| * the initial configuration when Session is created where the `set_samples()` is not used. */ | |||||
| scene->integrator->set_aa_samples(params.samples); | |||||
| /* Update denoiser settings. */ | |||||
| { | |||||
| const DenoiseParams denoise_params = scene->integrator->get_denoise_params(); | |||||
| path_trace_->set_denoiser_params(denoise_params); | |||||
| } | |||||
| /* Update adaptive sampling. */ | |||||
| { | |||||
| const AdaptiveSampling adaptive_sampling = scene->integrator->get_adaptive_sampling(); | |||||
| path_trace_->set_adaptive_sampling(adaptive_sampling); | |||||
| } | |||||
| render_scheduler_.set_num_samples(params.samples); | |||||
| render_scheduler_.set_time_limit(params.time_limit); | |||||
| while (have_tiles) { | |||||
| render_work = render_scheduler_.get_render_work(); | |||||
| if (render_work) { | |||||
| break; | |||||
| } | |||||
| have_tiles = tile_manager_.next(); | |||||
| if (have_tiles) { | if (have_tiles) { | ||||
| render_scheduler_.reset_for_next_tile(); | |||||
| switched_to_new_tile = true; | |||||
| } | |||||
| } | |||||
| if (render_work) { | |||||
| scoped_timer update_timer; | scoped_timer update_timer; | ||||
| if (update_scene()) { | |||||
| if (switched_to_new_tile) { | |||||
| BufferParams tile_params = buffer_params_; | |||||
| const Tile &tile = tile_manager_.get_current_tile(); | |||||
| tile_params.width = tile.width; | |||||
| tile_params.height = tile.height; | |||||
| tile_params.full_x = tile.x + buffer_params_.full_x; | |||||
| tile_params.full_y = tile.y + buffer_params_.full_y; | |||||
| tile_params.full_width = buffer_params_.full_width; | |||||
| tile_params.full_height = buffer_params_.full_height; | |||||
| tile_params.update_offset_stride(); | |||||
| path_trace_->reset(tile_params); | |||||
| } | |||||
| const int resolution = render_work.resolution_divider; | |||||
| const int width = max(1, buffer_params_.full_width / resolution); | |||||
| const int height = max(1, buffer_params_.full_height / resolution); | |||||
| if (update_scene(width, height)) { | |||||
| profiler.reset(scene->shaders.size(), scene->objects.size()); | profiler.reset(scene->shaders.size(), scene->objects.size()); | ||||
| } | } | ||||
| progress.add_skip_time(update_timer, params.background); | progress.add_skip_time(update_timer, params.background); | ||||
| } | } | ||||
| return have_tiles; | return render_work; | ||||
| } | } | ||||
| bool Session::run_wait_for_work(bool no_tiles) | bool Session::run_wait_for_work(const RenderWork &render_work) | ||||
| { | { | ||||
| /* In an offline rendering there is no pause, and no tiles will mean the job is fully done. */ | /* In an offline rendering there is no pause, and no tiles will mean the job is fully done. */ | ||||
| if (params.background) { | if (params.background) { | ||||
| return false; | return false; | ||||
| } | } | ||||
| thread_scoped_lock pause_lock(pause_mutex_); | thread_scoped_lock pause_lock(pause_mutex_); | ||||
| if (!pause_ && !no_tiles) { | if (!pause_ && render_work) { | ||||
| /* Rendering is not paused and there is work to be done. No need to wait for anything. */ | /* Rendering is not paused and there is work to be done. No need to wait for anything. */ | ||||
| return false; | return false; | ||||
| } | } | ||||
| update_status_time(pause_, no_tiles); | const bool no_work = !render_work; | ||||
| update_status_time(pause_, no_work); | |||||
| /* Only leave the loop when rendering is not paused. But even if the current render is un-paused | /* Only leave the loop when rendering is not paused. But even if the current render is un-paused | ||||
| * but there is nothing to render keep waiting until new work is added. */ | * but there is nothing to render keep waiting until new work is added. */ | ||||
| while (!cancel_) { | while (!cancel_) { | ||||
| scoped_timer pause_timer; | scoped_timer pause_timer; | ||||
| if (!pause_ && (!no_tiles || new_work_added_ || delayed_reset_.do_reset)) { | if (!pause_ && (render_work || new_work_added_ || delayed_reset_.do_reset)) { | ||||
| break; | break; | ||||
| } | } | ||||
| /* Wait for either pause state changed, or extra samples added to render. */ | /* Wait for either pause state changed, or extra samples added to render. */ | ||||
| pause_cond_.wait(pause_lock); | pause_cond_.wait(pause_lock); | ||||
| if (pause_) { | if (pause_) { | ||||
| progress.add_skip_time(pause_timer, params.background); | progress.add_skip_time(pause_timer, params.background); | ||||
| } | } | ||||
| update_status_time(pause_, no_tiles); | update_status_time(pause_, no_work); | ||||
| progress.set_update(); | progress.set_update(); | ||||
| } | } | ||||
| new_work_added_ = false; | new_work_added_ = false; | ||||
| return no_tiles; | return no_work; | ||||
| } | } | ||||
| bool Session::draw(BufferParams &buffer_params, DeviceDrawParams &draw_params) | void Session::draw() | ||||
| { | { | ||||
| if (device_use_gl_) | path_trace_->draw(); | ||||
| return draw_gpu(buffer_params, draw_params); | |||||
| else | |||||
| return draw_cpu(buffer_params, draw_params); | |||||
| } | } | ||||
| void Session::reset_(BufferParams &buffer_params, int samples) | void Session::do_delayed_reset() | ||||
| { | { | ||||
| if (buffers && buffer_params.modified(tile_manager.params)) { | if (!delayed_reset_.do_reset) { | ||||
| gpu_draw_ready_ = false; | return; | ||||
| buffers->reset(buffer_params); | |||||
| if (display) { | |||||
| display->reset(buffer_params); | |||||
| } | |||||
| } | } | ||||
| delayed_reset_.do_reset = false; | |||||
| scene->film->update_passes(scene); | |||||
| buffer_params_ = delayed_reset_.params; | |||||
| buffer_params_.update_passes(scene->passes); | |||||
| render_scheduler_.reset(buffer_params_, delayed_reset_.samples); | |||||
| /* TODO(sergey): Use real big tile size. */ | |||||
| tile_manager_.reset(buffer_params_, make_int2(buffer_params_.width, buffer_params_.height)); | |||||
| tile_manager.reset(buffer_params, samples); | |||||
| stealable_tiles_ = 0; | |||||
| tile_stealing_state_ = NOT_STEALING; | |||||
| progress.reset_sample(); | progress.reset_sample(); | ||||
| bool show_progress = params.background || tile_manager.get_num_effective_samples() != INT_MAX; | /* TODO(sergey): Progress report needs to be worked on. */ | ||||
| progress.set_total_pixel_samples(show_progress ? tile_manager.state.total_pixel_samples : 0); | #if 0 | ||||
| bool show_progress = params.background || tile_manager_.get_num_effective_samples() != INT_MAX; | |||||
| progress.set_total_pixel_samples(show_progress ? tile_manager_.state.total_pixel_samples : 0); | |||||
| #endif | |||||
| if (!params.background) | if (!params.background) { | ||||
| progress.set_start_time(); | progress.set_start_time(); | ||||
| } | |||||
| progress.set_render_start_time(); | progress.set_render_start_time(); | ||||
| } | } | ||||
| void Session::reset(BufferParams &buffer_params, int samples) | void Session::reset(BufferParams &buffer_params, int samples) | ||||
| { | { | ||||
| if (device_use_gl_) | { | ||||
| reset_gpu(buffer_params, samples); | thread_scoped_lock reset_lock(delayed_reset_.mutex); | ||||
| else | thread_scoped_lock pause_lock(pause_mutex_); | ||||
| reset_cpu(buffer_params, samples); | |||||
| delayed_reset_.params = buffer_params; | |||||
| delayed_reset_.samples = samples; | |||||
| delayed_reset_.do_reset = true; | |||||
| path_trace_->cancel(); | |||||
| } | |||||
| pause_cond_.notify_all(); | |||||
| } | } | ||||
| void Session::set_samples(int samples) | void Session::set_samples(int samples) | ||||
| { | { | ||||
| if (samples == params.samples) { | if (samples == params.samples) { | ||||
| return; | return; | ||||
| } | } | ||||
| params.samples = samples; | params.samples = samples; | ||||
| tile_manager.set_samples(samples); | |||||
| { | |||||
| thread_scoped_lock pause_lock(pause_mutex_); | |||||
| new_work_added_ = true; | |||||
| } | |||||
| pause_cond_.notify_all(); | |||||
| } | |||||
| void Session::set_time_limit(double time_limit) | |||||
| { | |||||
| if (time_limit == params.time_limit) { | |||||
| return; | |||||
| } | |||||
| params.time_limit = time_limit; | |||||
| { | { | ||||
| thread_scoped_lock pause_lock(pause_mutex_); | thread_scoped_lock pause_lock(pause_mutex_); | ||||
| new_work_added_ = true; | new_work_added_ = true; | ||||
| } | } | ||||
| pause_cond_.notify_all(); | pause_cond_.notify_all(); | ||||
| } | } | ||||
| Show All 16 Lines | if (notify) { | ||||
| pause_cond_.notify_all(); | pause_cond_.notify_all(); | ||||
| } | } | ||||
| } | } | ||||
| else if (pause_) { | else if (pause_) { | ||||
| update_status_time(pause_); | update_status_time(pause_); | ||||
| } | } | ||||
| } | } | ||||
| void Session::set_denoising(const DenoiseParams &denoising) | void Session::set_gpu_display(unique_ptr<GPUDisplay> gpu_display) | ||||
| { | { | ||||
| bool need_denoise = denoising.need_denoising_task(); | path_trace_->set_gpu_display(move(gpu_display)); | ||||
| /* Lock buffers so no denoising operation is triggered while the settings are changed here. */ | |||||
| thread_scoped_lock buffers_lock(buffers_mutex_); | |||||
| params.denoising = denoising; | |||||
| if (!(params.device.denoisers & denoising.type)) { | |||||
| if (need_denoise) { | |||||
| progress.set_error("Denoiser type not supported by compute device"); | |||||
| } | |||||
| params.denoising.use = false; | |||||
| need_denoise = false; | |||||
| } | |||||
| // TODO(pmours): Query the required overlap value for denoising from the device? | |||||
| tile_manager.slice_overlap = need_denoise && !params.background ? 64 : 0; | |||||
| /* Schedule per tile denoising for final renders if we are either denoising or | |||||
| * need prefiltered passes for the native denoiser. */ | |||||
| tile_manager.schedule_denoising = need_denoise && !buffers; | |||||
| } | |||||
| void Session::set_denoising_start_sample(int sample) | |||||
| { | |||||
| if (sample != params.denoising.start_sample) { | |||||
| params.denoising.start_sample = sample; | |||||
| pause_cond_.notify_all(); | |||||
| } | |||||
| } | } | ||||
| void Session::wait() | void Session::wait() | ||||
| { | { | ||||
| if (session_thread_) { | if (session_thread_) { | ||||
| session_thread_->join(); | session_thread_->join(); | ||||
| delete session_thread_; | delete session_thread_; | ||||
| } | } | ||||
| session_thread_ = NULL; | session_thread_ = nullptr; | ||||
| } | } | ||||
| bool Session::update_scene() | bool Session::update_scene(int width, int height) | ||||
| { | { | ||||
| /* update camera if dimensions changed for progressive render. the camera | /* Update camera if dimensions changed for progressive render. the camera | ||||
| * knows nothing about progressive or cropped rendering, it just gets the | * knows nothing about progressive or cropped rendering, it just gets the | ||||
| * image dimensions passed in */ | * image dimensions passed in. */ | ||||
| Camera *cam = scene->camera; | Camera *cam = scene->camera; | ||||
| int width = tile_manager.state.buffer.full_width; | cam->set_screen_size(width, height); | ||||
| int height = tile_manager.state.buffer.full_height; | |||||
| int resolution = tile_manager.state.resolution_divider; | |||||
| cam->set_screen_size_and_resolution(width, height, resolution); | |||||
| /* number of samples is needed by multi jittered | /* First detect which kernel features are used and allocate working memory. | ||||
| * sampling pattern and by baking */ | * This helps estimate how may device memory is available for the scene and | ||||
| Integrator *integrator = scene->integrator; | * how much we need to allocate on the host instead. */ | ||||
| BakeManager *bake_manager = scene->bake_manager; | scene->update_kernel_features(); | ||||
| if (integrator->get_sampling_pattern() != SAMPLING_PATTERN_SOBOL || bake_manager->get_baking()) { | path_trace_->load_kernels(); | ||||
| integrator->set_aa_samples(tile_manager.num_samples); | path_trace_->alloc_work_memory(); | ||||
| } | |||||
| bool kernel_switch_needed = false; | if (scene->update(progress)) { | ||||
| if (scene->update(progress, kernel_switch_needed)) { | |||||
| if (kernel_switch_needed) { | |||||
| reset(tile_manager.params, params.samples); | |||||
| } | |||||
| return true; | return true; | ||||
| } | } | ||||
| return false; | return false; | ||||
| } | } | ||||
| void Session::update_status_time(bool show_pause, bool show_done) | void Session::update_status_time(bool show_pause, bool show_done) | ||||
| { | { | ||||
| int progressive_sample = tile_manager.state.sample; | #if 0 | ||||
| int num_samples = tile_manager.get_num_effective_samples(); | int progressive_sample = tile_manager_.state.sample; | ||||
| int num_samples = tile_manager_.get_num_effective_samples(); | |||||
| int tile = progress.get_rendered_tiles(); | int tile = progress.get_rendered_tiles(); | ||||
| int num_tiles = tile_manager.state.num_tiles; | int num_tiles = tile_manager_.state.num_tiles; | ||||
| /* update status */ | /* update status */ | ||||
| string status, substatus; | string status, substatus; | ||||
| if (!params.progressive) { | if (!params.progressive) { | ||||
| const bool is_cpu = params.device.type == DEVICE_CPU; | const bool is_cpu = params.device.type == DEVICE_CPU; | ||||
| const bool rendering_finished = (tile == num_tiles); | const bool rendering_finished = (tile == num_tiles); | ||||
| const bool is_last_tile = (tile + 1) == num_tiles; | const bool is_last_tile = (tile + 1) == num_tiles; | ||||
| substatus = string_printf("Rendered %d/%d Tiles", tile, num_tiles); | substatus = string_printf("Rendered %d/%d Tiles", tile, num_tiles); | ||||
| if (!rendering_finished && (device->show_samples() || (is_cpu && is_last_tile))) { | if (!rendering_finished && (device->show_samples() || (is_cpu && is_last_tile))) { | ||||
| /* Some devices automatically support showing the sample number: | /* Some devices automatically support showing the sample number: | ||||
| * - CUDADevice | * - CUDADevice | ||||
| * - OpenCLDevice when using the megakernel (the split kernel renders multiple | |||||
| * samples at the same time, so the current sample isn't really defined) | |||||
| * - CPUDevice when using one thread | * - CPUDevice when using one thread | ||||
| * For these devices, the current sample is always shown. | * For these devices, the current sample is always shown. | ||||
| * | * | ||||
| * The other option is when the last tile is currently being rendered by the CPU. | * The other option is when the last tile is currently being rendered by the CPU. | ||||
| */ | */ | ||||
| substatus += string_printf(", Sample %d/%d", progress.get_current_sample(), num_samples); | substatus += string_printf(", Sample %d/%d", progress.get_current_sample(), num_samples); | ||||
| } | } | ||||
| if (params.denoising.use && params.denoising.type != DENOISER_OPENIMAGEDENOISE) { | if (params.denoising.use && params.denoising.type != DENOISER_OPENIMAGEDENOISE) { | ||||
| substatus += string_printf(", Denoised %d tiles", progress.get_denoised_tiles()); | substatus += string_printf(", Denoised %d tiles", progress.get_denoised_tiles()); | ||||
| } | } | ||||
| else if (params.denoising.store_passes && params.denoising.type == DENOISER_NLM) { | |||||
| substatus += string_printf(", Prefiltered %d tiles", progress.get_denoised_tiles()); | |||||
| } | } | ||||
| } | else if (tile_manager_.num_samples == Integrator::MAX_SAMPLES) | ||||
| else if (tile_manager.num_samples == Integrator::MAX_SAMPLES) | |||||
| substatus = string_printf("Path Tracing Sample %d", progressive_sample + 1); | substatus = string_printf("Path Tracing Sample %d", progressive_sample + 1); | ||||
| else | else | ||||
| substatus = string_printf("Path Tracing Sample %d/%d", progressive_sample + 1, num_samples); | substatus = string_printf("Path Tracing Sample %d/%d", progressive_sample + 1, num_samples); | ||||
| if (show_pause) { | if (show_pause) { | ||||
| status = "Rendering Paused"; | status = "Rendering Paused"; | ||||
| } | } | ||||
| else if (show_done) { | else if (show_done) { | ||||
| status = "Rendering Done"; | status = "Rendering Done"; | ||||
| progress.set_end_time(); /* Save end time so that further calls to get_time are accurate. */ | progress.set_end_time(); /* Save end time so that further calls to get_time are accurate. */ | ||||
| } | } | ||||
| else { | else { | ||||
| status = substatus; | status = substatus; | ||||
| substatus.clear(); | substatus.clear(); | ||||
| } | } | ||||
| #else | |||||
| string status, substatus; | |||||
| progress.set_status(status, substatus); | /* TODO(sergey): Take number of big tiles into account. */ | ||||
| } | /* TODO(sergey): Take sample range into account. */ | ||||
| bool Session::render_need_denoise(bool &delayed) | substatus += string_printf("Sample %d/%d", progress.get_current_sample(), params.samples); | ||||
| { | |||||
| delayed = false; | |||||
| /* Not supported yet for baking. */ | if (show_pause) { | ||||
| if (read_bake_tile_cb) { | status = "Rendering Paused"; | ||||
| return false; | |||||
| } | } | ||||
| else if (show_done) { | |||||
| /* Denoising enabled? */ | status = "Rendering Done"; | ||||
| if (!params.denoising.need_denoising_task()) { | progress.set_end_time(); /* Save end time so that further calls to get_time are accurate. */ | ||||
| return false; | |||||
| } | } | ||||
| else { | |||||
| if (params.background) { | status = substatus; | ||||
| /* Background render, only denoise when rendering the last sample. */ | substatus.clear(); | ||||
| return tile_manager.done(); | |||||
| } | } | ||||
| /* Viewport render. */ | #endif | ||||
| /* It can happen that denoising was already enabled, but the scene still needs an update. */ | progress.set_status(status, substatus); | ||||
| if (scene->film->is_modified() || !scene->film->get_denoising_data_offset()) { | |||||
| return false; | |||||
| } | } | ||||
| /* Immediately denoise when we reach the start sample or last sample. */ | void Session::device_free() | ||||
| const int num_samples_finished = tile_manager.state.sample + 1; | { | ||||
| if (num_samples_finished == params.denoising.start_sample || | scene->device_free(); | ||||
| num_samples_finished == params.samples) { | |||||
| return true; | |||||
| } | |||||
| /* Do not denoise until the sample at which denoising should start is reached. */ | #if 0 | ||||
| if (num_samples_finished < params.denoising.start_sample) { | tile_manager_.device_free(); | ||||
| return false; | #endif | ||||
| } | |||||
| /* Avoid excessive denoising in viewport after reaching a certain amount of samples. */ | /* used from background render only, so no need to | ||||
| delayed = (tile_manager.state.sample >= 20 && | * re-create render/display buffers here | ||||
| (time_dt() - last_display_time_) < params.progressive_update_timeout); | */ | ||||
| return !delayed; | |||||
| } | } | ||||
| void Session::render(bool need_denoise) | void Session::collect_statistics(RenderStats *render_stats) | ||||
| { | { | ||||
| if (buffers && tile_manager.state.sample == tile_manager.range_start_sample) { | scene->collect_statistics(render_stats); | ||||
| /* Clear buffers. */ | if (params.use_profiling && (params.device.type == DEVICE_CPU)) { | ||||
| buffers->zero(); | render_stats->collect_profiling(scene, profiler); | ||||
| } | } | ||||
| if (tile_manager.state.buffer.width == 0 || tile_manager.state.buffer.height == 0) { | |||||
| return; /* Avoid empty launches. */ | |||||
| } | } | ||||
| /* Add path trace task. */ | /* -------------------------------------------------------------------- | ||||
| DeviceTask task(DeviceTask::RENDER); | * Tile and tile pixels aceess. | ||||
| */ | |||||
| task.acquire_tile = function_bind(&Session::acquire_tile, this, _2, _1, _3); | |||||
| task.release_tile = function_bind(&Session::release_tile, this, _1, need_denoise); | |||||
| task.map_neighbor_tiles = function_bind(&Session::map_neighbor_tiles, this, _1, _2); | |||||
| task.unmap_neighbor_tiles = function_bind(&Session::unmap_neighbor_tiles, this, _1, _2); | |||||
| task.get_cancel = function_bind(&Progress::get_cancel, &this->progress); | |||||
| task.update_tile_sample = function_bind(&Session::update_tile_sample, this, _1); | |||||
| task.update_progress_sample = function_bind(&Progress::add_samples, &this->progress, _1, _2); | |||||
| task.get_tile_stolen = function_bind(&Session::get_tile_stolen, this); | |||||
| task.need_finish_queue = params.progressive_refine; | |||||
| task.integrator_branched = scene->integrator->get_method() == Integrator::BRANCHED_PATH; | |||||
| task.adaptive_sampling.use = (scene->integrator->get_sampling_pattern() == | |||||
| SAMPLING_PATTERN_PMJ) && | |||||
| scene->dscene.data.film.pass_adaptive_aux_buffer; | |||||
| task.adaptive_sampling.min_samples = scene->dscene.data.integrator.adaptive_min_samples; | |||||
| task.adaptive_sampling.adaptive_step = scene->dscene.data.integrator.adaptive_step; | |||||
| /* Acquire render tiles by default. */ | |||||
| task.tile_types = RenderTile::PATH_TRACE; | |||||
| if (need_denoise) { | |||||
| task.denoising = params.denoising; | |||||
| task.pass_stride = scene->film->get_pass_stride(); | |||||
| task.target_pass_stride = task.pass_stride; | |||||
| task.pass_denoising_data = scene->film->get_denoising_data_offset(); | |||||
| task.pass_denoising_clean = scene->film->get_denoising_clean_offset(); | |||||
| task.denoising_from_render = true; | |||||
| if (tile_manager.schedule_denoising) { | |||||
| /* Acquire denoising tiles during rendering. */ | |||||
| task.tile_types |= RenderTile::DENOISE; | |||||
| } | |||||
| else { | |||||
| assert(buffers); | |||||
| /* Schedule rendering and wait for it to finish. */ | |||||
| device->task_add(task); | |||||
| device->task_wait(); | |||||
| /* Then run denoising on the whole image at once. */ | int2 Session::get_render_tile_size() const | ||||
| task.type = DeviceTask::DENOISE_BUFFER; | { | ||||
| task.x = tile_manager.state.buffer.full_x; | const Tile &tile = tile_manager_.get_current_tile(); | ||||
| task.y = tile_manager.state.buffer.full_y; | |||||
| task.w = tile_manager.state.buffer.width; | |||||
| task.h = tile_manager.state.buffer.height; | |||||
| task.buffer = buffers->buffer.device_pointer; | |||||
| task.sample = tile_manager.state.sample; | |||||
| task.num_samples = tile_manager.state.num_samples; | |||||
| tile_manager.state.buffer.get_offset_stride(task.offset, task.stride); | |||||
| task.buffers = buffers; | |||||
| } | |||||
| } | |||||
| device->task_add(task); | return make_int2(tile.width, tile.height); | ||||
| } | } | ||||
| void Session::copy_to_display_buffer(int sample) | int2 Session::get_render_tile_offset() const | ||||
| { | { | ||||
| /* add film conversion task */ | const Tile &tile = tile_manager_.get_current_tile(); | ||||
| DeviceTask task(DeviceTask::FILM_CONVERT); | |||||
| task.x = tile_manager.state.buffer.full_x; | |||||
| task.y = tile_manager.state.buffer.full_y; | |||||
| task.w = tile_manager.state.buffer.width; | |||||
| task.h = tile_manager.state.buffer.height; | |||||
| task.rgba_byte = display->rgba_byte.device_pointer; | |||||
| task.rgba_half = display->rgba_half.device_pointer; | |||||
| task.buffer = buffers->buffer.device_pointer; | |||||
| task.sample = sample; | |||||
| tile_manager.state.buffer.get_offset_stride(task.offset, task.stride); | |||||
| if (task.w > 0 && task.h > 0) { | |||||
| device->task_add(task); | |||||
| device->task_wait(); | |||||
| /* set display to new size */ | |||||
| display->draw_set(task.w, task.h); | |||||
| last_display_time_ = time_dt(); | return make_int2(tile.x, tile.y); | ||||
| } | } | ||||
| display_outdated_ = false; | bool Session::copy_render_tile_from_device() | ||||
| { | |||||
| return path_trace_->copy_render_tile_from_device(); | |||||
| } | } | ||||
| bool Session::update_progressive_refine(bool cancel) | bool Session::get_render_tile_pixels(const string &pass_name, int num_components, float *pixels) | ||||
| { | { | ||||
| int sample = tile_manager.state.sample + 1; | const Pass *pass = Pass::find(scene->passes, pass_name); | ||||
| bool write = sample == tile_manager.num_samples || cancel; | if (pass == nullptr) { | ||||
| double current_time = time_dt(); | |||||
| if (current_time - last_update_time_ < params.progressive_update_timeout) { | |||||
| /* If last sample was processed, we need to write buffers anyway. */ | |||||
| if (!write && sample != 1) | |||||
| return false; | return false; | ||||
| } | } | ||||
| if (params.progressive_refine) { | const bool has_denoised_result = path_trace_->has_denoised_result(); | ||||
| foreach (Tile &tile, tile_manager.state.tiles) { | if (pass->mode == PassMode::DENOISED && !has_denoised_result) { | ||||
| if (!tile.buffers) { | pass = Pass::find(scene->passes, pass->type); | ||||
| continue; | if (pass == nullptr) { | ||||
| } | /* Happens when denoised result pass is requested but is never written by the kernel. */ | ||||
| return false; | |||||
| RenderTile rtile; | |||||
| rtile.x = tile_manager.state.buffer.full_x + tile.x; | |||||
| rtile.y = tile_manager.state.buffer.full_y + tile.y; | |||||
| rtile.w = tile.w; | |||||
| rtile.h = tile.h; | |||||
| rtile.sample = sample; | |||||
| rtile.buffers = tile.buffers; | |||||
| if (write) { | |||||
| if (write_render_tile_cb) | |||||
| write_render_tile_cb(rtile); | |||||
| } | |||||
| else { | |||||
| if (update_render_tile_cb) | |||||
| update_render_tile_cb(rtile, true); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| last_update_time_ = current_time; | pass = scene->film->get_actual_display_pass(scene, pass); | ||||
| return write; | const float exposure = scene->film->get_exposure(); | ||||
| } | const int num_samples = render_scheduler_.get_num_rendered_samples(); | ||||
| void Session::device_free() | |||||
| { | |||||
| scene->device_free(); | |||||
| tile_manager.device_free(); | const PassAccessor::PassAccessInfo pass_access_info( | ||||
| *pass, *scene->film, *scene->background, scene->passes); | |||||
| const PassAccessorCPU pass_accessor(pass_access_info, exposure, num_samples); | |||||
| const PassAccessor::Destination destination(pixels, num_components); | |||||
| /* used from background render only, so no need to | return path_trace_->get_render_tile_pixels(pass_accessor, destination); | ||||
| * re-create render/display buffers here | |||||
| */ | |||||
| } | } | ||||
| void Session::collect_statistics(RenderStats *render_stats) | bool Session::set_render_tile_pixels(const string &pass_name, | ||||
| int num_components, | |||||
| const float *pixels) | |||||
| { | { | ||||
| scene->collect_statistics(render_stats); | /* TODO(sergey): Do we write to alias? */ | ||||
| if (params.use_profiling && (params.device.type == DEVICE_CPU)) { | const Pass *pass = Pass::find(scene->passes, pass_name); | ||||
| render_stats->collect_profiling(scene, profiler); | if (!pass) { | ||||
| return false; | |||||
| } | } | ||||
| const float exposure = scene->film->get_exposure(); | |||||
| const int num_samples = render_scheduler_.get_num_rendered_samples(); | |||||
| const PassAccessor::PassAccessInfo pass_access_info( | |||||
| *pass, *scene->film, *scene->background, scene->passes); | |||||
| PassAccessorCPU pass_accessor(pass_access_info, exposure, num_samples); | |||||
| PassAccessor::Source source(pixels, num_components); | |||||
| return path_trace_->set_render_tile_pixels(pass_accessor, source); | |||||
| } | } | ||||
| CCL_NAMESPACE_END | CCL_NAMESPACE_END | ||||