forked from bartvdbraak/blender
929 lines
22 KiB
C++
929 lines
22 KiB
C++
/*
|
|
* Copyright 2011-2013 Blender Foundation
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <limits.h>
|
|
|
|
#include "buffers.h"
|
|
#include "camera.h"
|
|
#include "device.h"
|
|
#include "integrator.h"
|
|
#include "scene.h"
|
|
#include "session.h"
|
|
#include "bake.h"
|
|
|
|
#include "util_foreach.h"
|
|
#include "util_function.h"
|
|
#include "util_math.h"
|
|
#include "util_opengl.h"
|
|
#include "util_task.h"
|
|
#include "util_time.h"
|
|
|
|
CCL_NAMESPACE_BEGIN
|
|
|
|
/* Note about preserve_tile_device option for tile manager:
|
|
* progressive refine and viewport rendering does requires tiles to
|
|
* always be allocated for the same device
|
|
*/
|
|
Session::Session(const SessionParams& params_)
|
|
: 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)),
|
|
stats()
|
|
{
|
|
device_use_gl = ((params.device.type != DEVICE_CPU) && !params.background);
|
|
|
|
TaskScheduler::init(params.threads);
|
|
|
|
device = Device::create(params.device, stats, params.background);
|
|
|
|
if(params.background && params.output_path.empty()) {
|
|
buffers = NULL;
|
|
display = NULL;
|
|
}
|
|
else {
|
|
buffers = new RenderBuffers(device);
|
|
display = new DisplayBuffer(device, params.display_buffer_linear);
|
|
}
|
|
|
|
session_thread = NULL;
|
|
scene = NULL;
|
|
|
|
start_time = 0.0;
|
|
reset_time = 0.0;
|
|
preview_time = 0.0;
|
|
paused_time = 0.0;
|
|
last_update_time = 0.0;
|
|
|
|
delayed_reset.do_reset = false;
|
|
delayed_reset.samples = 0;
|
|
|
|
display_outdated = false;
|
|
gpu_draw_ready = false;
|
|
gpu_need_tonemap = false;
|
|
pause = false;
|
|
kernels_loaded = false;
|
|
}
|
|
|
|
Session::~Session()
|
|
{
|
|
if(session_thread) {
|
|
/* wait for session thread to end */
|
|
progress.set_cancel("Exiting");
|
|
|
|
gpu_need_tonemap = false;
|
|
gpu_need_tonemap_cond.notify_all();
|
|
|
|
{
|
|
thread_scoped_lock pause_lock(pause_mutex);
|
|
pause = false;
|
|
}
|
|
pause_cond.notify_all();
|
|
|
|
wait();
|
|
}
|
|
|
|
if(!params.output_path.empty()) {
|
|
/* tonemap and write out image if requested */
|
|
delete display;
|
|
|
|
display = new DisplayBuffer(device, false);
|
|
display->reset(device, buffers->params);
|
|
tonemap(params.samples);
|
|
|
|
progress.set_status("Writing Image", params.output_path);
|
|
display->write(device, params.output_path);
|
|
}
|
|
|
|
/* clean up */
|
|
foreach(RenderBuffers *buffers, tile_buffers)
|
|
delete buffers;
|
|
|
|
delete buffers;
|
|
delete display;
|
|
delete scene;
|
|
delete device;
|
|
|
|
TaskScheduler::exit();
|
|
}
|
|
|
|
void Session::start()
|
|
{
|
|
session_thread = new thread(function_bind(&Session::run, this));
|
|
}
|
|
|
|
bool Session::ready_to_reset()
|
|
{
|
|
double dt = time_dt() - reset_time;
|
|
|
|
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_tonemap = false;
|
|
gpu_need_tonemap_cond.notify_all();
|
|
|
|
pause_cond.notify_all();
|
|
}
|
|
|
|
bool Session::draw_gpu(BufferParams& buffer_params, DeviceDrawParams& draw_params)
|
|
{
|
|
/* block for buffer access */
|
|
thread_scoped_lock display_lock(display_mutex);
|
|
|
|
/* 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.modified(display->params)) {
|
|
/* for CUDA we need to do tonemapping still, since we can
|
|
* only access GL buffers from the main thread */
|
|
if(gpu_need_tonemap) {
|
|
thread_scoped_lock buffers_lock(buffers_mutex);
|
|
tonemap(tile_manager.state.sample);
|
|
gpu_need_tonemap = false;
|
|
gpu_need_tonemap_cond.notify_all();
|
|
}
|
|
|
|
display->draw(device, draw_params);
|
|
|
|
if(display_outdated && (time_dt() - reset_time) > params.text_timeout)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Session::run_gpu()
|
|
{
|
|
bool tiles_written = false;
|
|
|
|
start_time = time_dt();
|
|
reset_time = time_dt();
|
|
paused_time = 0.0;
|
|
last_update_time = time_dt();
|
|
|
|
if(!params.background)
|
|
progress.set_start_time(start_time + paused_time);
|
|
|
|
while(!progress.get_cancel()) {
|
|
/* advance to next tile */
|
|
bool no_tiles = !tile_manager.next();
|
|
|
|
if(params.background) {
|
|
/* if no work left and in background mode, we can stop immediately */
|
|
if(no_tiles) {
|
|
progress.set_status("Finished");
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
/* if in interactive mode, and we are either paused or done for now,
|
|
* wait for pause condition notify to wake up again */
|
|
thread_scoped_lock pause_lock(pause_mutex);
|
|
|
|
if(!pause && !tile_manager.done()) {
|
|
/* reset could have happened after no_tiles was set, before this lock.
|
|
* in this case we shall not wait for pause condition
|
|
*/
|
|
}
|
|
else if(pause || no_tiles) {
|
|
update_status_time(pause, no_tiles);
|
|
|
|
while(1) {
|
|
double pause_start = time_dt();
|
|
pause_cond.wait(pause_lock);
|
|
paused_time += time_dt() - pause_start;
|
|
|
|
if(!params.background)
|
|
progress.set_start_time(start_time + paused_time);
|
|
|
|
update_status_time(pause, no_tiles);
|
|
progress.set_update();
|
|
|
|
if(!pause)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(progress.get_cancel())
|
|
break;
|
|
}
|
|
|
|
if(!no_tiles) {
|
|
/* update scene */
|
|
update_scene();
|
|
|
|
if(!device->error_message().empty())
|
|
progress.set_cancel(device->error_message());
|
|
|
|
if(progress.get_cancel())
|
|
break;
|
|
}
|
|
|
|
if(!no_tiles) {
|
|
/* 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();
|
|
|
|
/* path trace */
|
|
path_trace();
|
|
|
|
device->task_wait();
|
|
|
|
if(!device->error_message().empty())
|
|
progress.set_cancel(device->error_message());
|
|
|
|
/* update status and timing */
|
|
update_status_time();
|
|
|
|
gpu_need_tonemap = true;
|
|
gpu_draw_ready = true;
|
|
progress.set_update();
|
|
|
|
/* wait for tonemap */
|
|
if(!params.background) {
|
|
while(gpu_need_tonemap) {
|
|
if(progress.get_cancel())
|
|
break;
|
|
|
|
gpu_need_tonemap_cond.wait(buffers_lock);
|
|
}
|
|
}
|
|
|
|
if(!device->error_message().empty())
|
|
progress.set_cancel(device->error_message());
|
|
|
|
tiles_written = update_progressive_refine(progress.get_cancel());
|
|
|
|
if(progress.get_cancel())
|
|
break;
|
|
}
|
|
}
|
|
|
|
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.modified(display->params)) {
|
|
display->draw(device, draw_params);
|
|
|
|
if(display_outdated && (time_dt() - reset_time) > params.text_timeout)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Session::acquire_tile(Device *tile_device, RenderTile& rtile)
|
|
{
|
|
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);
|
|
|
|
if(!tile_manager.next_tile(tile, device_num))
|
|
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;
|
|
|
|
tile_lock.unlock();
|
|
|
|
/* in case of a permanent buffer, return it, otherwise we will allocate
|
|
* a new temporary buffer */
|
|
if(!(params.background && params.output_path.empty())) {
|
|
tile_manager.state.buffer.get_offset_stride(rtile.offset, rtile.stride);
|
|
|
|
rtile.buffer = buffers->buffer.device_pointer;
|
|
rtile.rng_state = buffers->rng_state.device_pointer;
|
|
rtile.buffers = buffers;
|
|
|
|
device->map_tile(tile_device, rtile);
|
|
|
|
return true;
|
|
}
|
|
|
|
/* 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;
|
|
|
|
buffer_params.get_offset_stride(rtile.offset, rtile.stride);
|
|
|
|
RenderBuffers *tilebuffers;
|
|
|
|
/* allocate buffers */
|
|
if(params.progressive_refine) {
|
|
tile_lock.lock();
|
|
|
|
if(tile_buffers.size() == 0)
|
|
tile_buffers.resize(tile_manager.state.num_tiles, NULL);
|
|
|
|
tilebuffers = tile_buffers[tile.index];
|
|
if(tilebuffers == NULL) {
|
|
tilebuffers = new RenderBuffers(tile_device);
|
|
tile_buffers[tile.index] = tilebuffers;
|
|
|
|
tilebuffers->reset(tile_device, buffer_params);
|
|
}
|
|
|
|
tile_lock.unlock();
|
|
}
|
|
else {
|
|
tilebuffers = new RenderBuffers(tile_device);
|
|
|
|
tilebuffers->reset(tile_device, buffer_params);
|
|
}
|
|
|
|
rtile.buffer = tilebuffers->buffer.device_pointer;
|
|
rtile.rng_state = tilebuffers->rng_state.device_pointer;
|
|
rtile.buffers = tilebuffers;
|
|
|
|
/* 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);
|
|
}
|
|
}
|
|
|
|
update_status_time();
|
|
}
|
|
|
|
void Session::release_tile(RenderTile& rtile)
|
|
{
|
|
thread_scoped_lock tile_lock(tile_mutex);
|
|
|
|
if(write_render_tile_cb) {
|
|
if(params.progressive_refine == false) {
|
|
/* todo: optimize this by making it thread safe and removing lock */
|
|
write_render_tile_cb(rtile);
|
|
|
|
delete rtile.buffers;
|
|
}
|
|
}
|
|
|
|
update_status_time();
|
|
}
|
|
|
|
void Session::run_cpu()
|
|
{
|
|
bool tiles_written = false;
|
|
|
|
last_update_time = time_dt();
|
|
|
|
{
|
|
/* reset once to start */
|
|
thread_scoped_lock reset_lock(delayed_reset.mutex);
|
|
thread_scoped_lock buffers_lock(buffers_mutex);
|
|
thread_scoped_lock display_lock(display_mutex);
|
|
|
|
reset_(delayed_reset.params, delayed_reset.samples);
|
|
delayed_reset.do_reset = false;
|
|
}
|
|
|
|
while(!progress.get_cancel()) {
|
|
/* advance to next tile */
|
|
bool no_tiles = !tile_manager.next();
|
|
bool need_tonemap = false;
|
|
|
|
if(params.background) {
|
|
/* if no work left and in background mode, we can stop immediately */
|
|
if(no_tiles) {
|
|
progress.set_status("Finished");
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
/* if in interactive mode, and we are either paused or done for now,
|
|
* wait for pause condition notify to wake up again */
|
|
thread_scoped_lock pause_lock(pause_mutex);
|
|
|
|
if(!pause && delayed_reset.do_reset) {
|
|
/* reset once to start */
|
|
thread_scoped_lock reset_lock(delayed_reset.mutex);
|
|
thread_scoped_lock buffers_lock(buffers_mutex);
|
|
thread_scoped_lock display_lock(display_mutex);
|
|
|
|
reset_(delayed_reset.params, delayed_reset.samples);
|
|
delayed_reset.do_reset = false;
|
|
}
|
|
else if(pause || no_tiles) {
|
|
update_status_time(pause, no_tiles);
|
|
|
|
while(1) {
|
|
double pause_start = time_dt();
|
|
pause_cond.wait(pause_lock);
|
|
paused_time += time_dt() - pause_start;
|
|
|
|
if(!params.background)
|
|
progress.set_start_time(start_time + paused_time);
|
|
|
|
update_status_time(pause, no_tiles);
|
|
progress.set_update();
|
|
|
|
if(!pause)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(progress.get_cancel())
|
|
break;
|
|
}
|
|
|
|
if(!no_tiles) {
|
|
/* 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 scene */
|
|
update_scene();
|
|
|
|
if(!device->error_message().empty())
|
|
progress.set_cancel(device->error_message());
|
|
|
|
if(progress.get_cancel())
|
|
break;
|
|
|
|
/* update status and timing */
|
|
update_status_time();
|
|
|
|
/* path trace */
|
|
path_trace();
|
|
|
|
/* update status and timing */
|
|
update_status_time();
|
|
|
|
if(!params.background)
|
|
need_tonemap = true;
|
|
|
|
if(!device->error_message().empty())
|
|
progress.set_cancel(device->error_message());
|
|
}
|
|
|
|
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_tonemap) {
|
|
/* tonemap only if we do not reset, we don't we don't
|
|
* want to show the result of an incomplete sample */
|
|
tonemap(tile_manager.state.sample);
|
|
}
|
|
|
|
if(!device->error_message().empty())
|
|
progress.set_cancel(device->error_message());
|
|
|
|
tiles_written = update_progressive_refine(progress.get_cancel());
|
|
}
|
|
|
|
progress.set_update();
|
|
}
|
|
|
|
if(!tiles_written)
|
|
update_progressive_refine(true);
|
|
}
|
|
|
|
void Session::load_kernels()
|
|
{
|
|
thread_scoped_lock scene_lock(scene->mutex);
|
|
|
|
if(!kernels_loaded) {
|
|
progress.set_status("Loading render kernels (may take a few minutes the first time)");
|
|
|
|
if(!device->load_kernels(params.experimental)) {
|
|
string message = device->error_message();
|
|
if(message.empty())
|
|
message = "Failed loading render kernel, see console for errors";
|
|
|
|
progress.set_cancel(message);
|
|
progress.set_status("Error", message);
|
|
progress.set_update();
|
|
return;
|
|
}
|
|
|
|
kernels_loaded = true;
|
|
}
|
|
}
|
|
|
|
void Session::run()
|
|
{
|
|
/* load kernels */
|
|
load_kernels();
|
|
|
|
/* session thread loop */
|
|
progress.set_status("Waiting for render to start");
|
|
|
|
/* run */
|
|
if(!progress.get_cancel()) {
|
|
/* reset number of rendered samples */
|
|
progress.reset_sample();
|
|
|
|
if(device_use_gl)
|
|
run_gpu();
|
|
else
|
|
run_cpu();
|
|
}
|
|
|
|
/* progress update */
|
|
if(progress.get_cancel())
|
|
progress.set_status("Cancel", progress.get_cancel_message());
|
|
else
|
|
progress.set_update();
|
|
}
|
|
|
|
bool Session::draw(BufferParams& buffer_params, DeviceDrawParams &draw_params)
|
|
{
|
|
if(device_use_gl)
|
|
return draw_gpu(buffer_params, draw_params);
|
|
else
|
|
return draw_cpu(buffer_params, draw_params);
|
|
}
|
|
|
|
void Session::reset_(BufferParams& buffer_params, int samples)
|
|
{
|
|
if(buffers) {
|
|
if(buffer_params.modified(buffers->params)) {
|
|
gpu_draw_ready = false;
|
|
buffers->reset(device, buffer_params);
|
|
display->reset(device, buffer_params);
|
|
}
|
|
}
|
|
|
|
tile_manager.reset(buffer_params, samples);
|
|
|
|
start_time = time_dt();
|
|
preview_time = 0.0;
|
|
paused_time = 0.0;
|
|
|
|
if(!params.background)
|
|
progress.set_start_time(start_time + paused_time);
|
|
}
|
|
|
|
void Session::reset(BufferParams& buffer_params, int samples)
|
|
{
|
|
if(device_use_gl)
|
|
reset_gpu(buffer_params, samples);
|
|
else
|
|
reset_cpu(buffer_params, samples);
|
|
|
|
if(params.progressive_refine) {
|
|
thread_scoped_lock buffers_lock(buffers_mutex);
|
|
|
|
foreach(RenderBuffers *buffers, tile_buffers)
|
|
delete buffers;
|
|
|
|
tile_buffers.clear();
|
|
}
|
|
}
|
|
|
|
void Session::set_samples(int samples)
|
|
{
|
|
if(samples != params.samples) {
|
|
params.samples = samples;
|
|
tile_manager.set_samples(samples);
|
|
|
|
{
|
|
thread_scoped_lock pause_lock(pause_mutex);
|
|
}
|
|
pause_cond.notify_all();
|
|
}
|
|
}
|
|
|
|
void Session::set_pause(bool pause_)
|
|
{
|
|
bool notify = false;
|
|
|
|
{
|
|
thread_scoped_lock pause_lock(pause_mutex);
|
|
|
|
if(pause != pause_) {
|
|
pause = pause_;
|
|
notify = true;
|
|
}
|
|
}
|
|
|
|
if(notify)
|
|
pause_cond.notify_all();
|
|
}
|
|
|
|
void Session::wait()
|
|
{
|
|
session_thread->join();
|
|
delete session_thread;
|
|
|
|
session_thread = NULL;
|
|
}
|
|
|
|
void Session::update_scene()
|
|
{
|
|
thread_scoped_lock scene_lock(scene->mutex);
|
|
|
|
/* update camera if dimensions changed for progressive render. the camera
|
|
* knows nothing about progressive or cropped rendering, it just gets the
|
|
* image dimensions passed in */
|
|
Camera *cam = scene->camera;
|
|
int width = tile_manager.state.buffer.full_width;
|
|
int height = tile_manager.state.buffer.full_height;
|
|
int resolution = tile_manager.state.resolution_divider;
|
|
|
|
if(width != cam->width || height != cam->height) {
|
|
cam->width = width;
|
|
cam->height = height;
|
|
cam->resolution = resolution;
|
|
cam->tag_update();
|
|
}
|
|
|
|
/* number of samples is needed by multi jittered
|
|
* sampling pattern and by baking */
|
|
Integrator *integrator = scene->integrator;
|
|
BakeManager *bake_manager = scene->bake_manager;
|
|
|
|
if(integrator->sampling_pattern == SAMPLING_PATTERN_CMJ ||
|
|
bake_manager->get_baking())
|
|
{
|
|
int aa_samples = tile_manager.num_samples;
|
|
|
|
if(aa_samples != integrator->aa_samples) {
|
|
integrator->aa_samples = aa_samples;
|
|
integrator->tag_update(scene);
|
|
}
|
|
}
|
|
|
|
/* update scene */
|
|
if(scene->need_update()) {
|
|
progress.set_status("Updating Scene");
|
|
scene->device_update(device, progress);
|
|
}
|
|
}
|
|
|
|
void Session::update_status_time(bool show_pause, bool show_done)
|
|
{
|
|
int sample = tile_manager.state.sample;
|
|
int resolution = tile_manager.state.resolution_divider;
|
|
int num_tiles = tile_manager.state.num_tiles;
|
|
int tile = tile_manager.state.num_rendered_tiles;
|
|
|
|
/* update status */
|
|
string status, substatus;
|
|
|
|
if(!params.progressive) {
|
|
bool is_gpu = params.device.type == DEVICE_CUDA || params.device.type == DEVICE_OPENCL;
|
|
bool is_multidevice = params.device.multi_devices.size() > 1;
|
|
bool is_cpu = params.device.type == DEVICE_CPU;
|
|
|
|
substatus = string_printf("Path Tracing Tile %d/%d", tile, num_tiles);
|
|
|
|
if((is_gpu && !is_multidevice) || (is_cpu && num_tiles == 1)) {
|
|
/* when rendering on GPU multithreading happens within single tile, as in
|
|
* tiles are handling sequentially and in this case we could display
|
|
* currently rendering sample number
|
|
* this helps a lot from feedback point of view.
|
|
* also display the info on CPU, when using 1 tile only
|
|
*/
|
|
|
|
int sample = progress.get_sample(), num_samples = tile_manager.num_samples;
|
|
|
|
if(tile > 1) {
|
|
/* sample counter is global for all tiles, subtract samples
|
|
* from already finished tiles to get sample counter for
|
|
* current tile only
|
|
*/
|
|
sample -= (tile - 1) * num_samples;
|
|
}
|
|
|
|
substatus += string_printf(", Sample %d/%d", sample, num_samples);
|
|
}
|
|
}
|
|
else if(tile_manager.num_samples == USHRT_MAX)
|
|
substatus = string_printf("Path Tracing Sample %d", sample+1);
|
|
else
|
|
substatus = string_printf("Path Tracing Sample %d/%d", sample+1, tile_manager.num_samples);
|
|
|
|
if(show_pause) {
|
|
status = "Paused";
|
|
}
|
|
else if(show_done) {
|
|
status = "Done";
|
|
}
|
|
else {
|
|
status = substatus;
|
|
substatus.clear();
|
|
}
|
|
|
|
progress.set_status(status, substatus);
|
|
|
|
/* update timing */
|
|
if(preview_time == 0.0 && resolution == 1)
|
|
preview_time = time_dt();
|
|
|
|
double tile_time = (tile == 0 || sample == 0)? 0.0: (time_dt() - preview_time - paused_time) / sample;
|
|
|
|
/* negative can happen when we pause a bit before rendering, can discard that */
|
|
if(preview_time < 0.0) preview_time = 0.0;
|
|
|
|
progress.set_tile(tile, tile_time);
|
|
}
|
|
|
|
void Session::update_progress_sample()
|
|
{
|
|
progress.increment_sample();
|
|
}
|
|
|
|
void Session::path_trace()
|
|
{
|
|
/* add path trace task */
|
|
DeviceTask task(DeviceTask::PATH_TRACE);
|
|
|
|
task.acquire_tile = function_bind(&Session::acquire_tile, this, _1, _2);
|
|
task.release_tile = function_bind(&Session::release_tile, this, _1);
|
|
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(&Session::update_progress_sample, this);
|
|
task.need_finish_queue = params.progressive_refine;
|
|
task.integrator_branched = scene->integrator->method == Integrator::BRANCHED_PATH;
|
|
|
|
device->task_add(task);
|
|
}
|
|
|
|
void Session::tonemap(int sample)
|
|
{
|
|
/* add tonemap task */
|
|
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);
|
|
}
|
|
|
|
display_outdated = false;
|
|
}
|
|
|
|
bool Session::update_progressive_refine(bool cancel)
|
|
{
|
|
int sample = tile_manager.state.sample + 1;
|
|
bool write = sample == tile_manager.num_samples || cancel;
|
|
|
|
double current_time = time_dt();
|
|
|
|
if (current_time - last_update_time < 1.0) {
|
|
/* if last sample was processed, we need to write buffers anyway */
|
|
if (!write)
|
|
return false;
|
|
}
|
|
|
|
if(params.progressive_refine) {
|
|
foreach(RenderBuffers *buffers, tile_buffers) {
|
|
RenderTile rtile;
|
|
rtile.buffers = buffers;
|
|
rtile.sample = sample;
|
|
|
|
if(write)
|
|
write_render_tile_cb(rtile);
|
|
else
|
|
update_render_tile_cb(rtile);
|
|
}
|
|
}
|
|
|
|
last_update_time = current_time;
|
|
|
|
return write;
|
|
}
|
|
|
|
void Session::device_free()
|
|
{
|
|
scene->device_free();
|
|
|
|
foreach(RenderBuffers *buffers, tile_buffers)
|
|
delete buffers;
|
|
|
|
tile_buffers.clear();
|
|
|
|
/* used from background render only, so no need to
|
|
* re-create render/display buffers here
|
|
*/
|
|
}
|
|
|
|
CCL_NAMESPACE_END
|