Cycles render engine, initial commit. This is the engine itself, blender modifications and build instructions will follow later.
Cycles uses code from some great open source projects, many thanks them: * BVH building and traversal code from NVidia's "Understanding the Efficiency of Ray Traversal on GPUs": http://code.google.com/p/understanding-the-efficiency-of-ray-traversal-on-gpus/ * Open Shading Language for a large part of the shading system: http://code.google.com/p/openshadinglanguage/ * Blender for procedural textures and a few other nodes. * Approximate Catmull Clark subdivision from NVidia Mesh tools: http://code.google.com/p/nvidia-mesh-tools/ * Sobol direction vectors from: http://web.maths.unsw.edu.au/~fkuo/sobol/ * Film response functions from: http://www.cs.columbia.edu/CAVE/software/softlib/dorf.php
This commit is contained in:
435
intern/cycles/render/session.cpp
Normal file
435
intern/cycles/render/session.cpp
Normal file
@@ -0,0 +1,435 @@
|
||||
/*
|
||||
* Copyright 2011, Blender Foundation.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "buffers.h"
|
||||
#include "camera.h"
|
||||
#include "device.h"
|
||||
#include "scene.h"
|
||||
#include "session.h"
|
||||
|
||||
#include "util_foreach.h"
|
||||
#include "util_function.h"
|
||||
#include "util_time.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
Session::Session(const SessionParams& params_)
|
||||
: params(params_),
|
||||
tile_manager(params.progressive, params.passes, params.tile_size, params.min_size)
|
||||
{
|
||||
device_use_gl = (params.device_type == DEVICE_CUDA && !params.background);
|
||||
|
||||
device = Device::create(params.device_type, params.background);
|
||||
buffers = new RenderBuffers(device);
|
||||
display = new DisplayBuffer(device);
|
||||
|
||||
session_thread = NULL;
|
||||
scene = NULL;
|
||||
|
||||
start_time = 0.0;
|
||||
reset_time = 0.0;
|
||||
preview_time = 0.0;
|
||||
pass = 0;
|
||||
|
||||
delayed_reset.do_reset = false;
|
||||
delayed_reset.w = 0;
|
||||
delayed_reset.h = 0;
|
||||
|
||||
display_outdated = false;
|
||||
gpu_draw_ready = false;
|
||||
gpu_need_tonemap = false;
|
||||
}
|
||||
|
||||
Session::~Session()
|
||||
{
|
||||
if(session_thread) {
|
||||
progress.set_cancel("Exiting");
|
||||
gpu_need_tonemap = false;
|
||||
gpu_need_tonemap_cond.notify_all();
|
||||
wait();
|
||||
}
|
||||
|
||||
if(params.output_path != "") {
|
||||
progress.set_status("Writing Image", params.output_path);
|
||||
display->write(device, params.output_path);
|
||||
}
|
||||
|
||||
delete buffers;
|
||||
delete display;
|
||||
delete scene;
|
||||
delete device;
|
||||
}
|
||||
|
||||
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(int w, int h)
|
||||
{
|
||||
/* block for buffer acces 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_(w, h);
|
||||
|
||||
gpu_need_tonemap = false;
|
||||
gpu_need_tonemap_cond.notify_all();
|
||||
}
|
||||
|
||||
bool Session::draw_gpu(int w, int h)
|
||||
{
|
||||
/* 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(w == display->width && h == display->height) {
|
||||
/* 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();
|
||||
gpu_need_tonemap = false;
|
||||
gpu_need_tonemap_cond.notify_all();
|
||||
}
|
||||
|
||||
display->draw(device);
|
||||
|
||||
if(display_outdated && (time_dt() - reset_time) > params.text_timeout)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Session::run_gpu()
|
||||
{
|
||||
start_time = time_dt();
|
||||
reset_time = time_dt();
|
||||
|
||||
while(!progress.get_cancel() && tile_manager.next()) {
|
||||
/* buffers mutex is locked entirely while rendering each
|
||||
pass, 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(progress.get_cancel())
|
||||
break;
|
||||
|
||||
/* update status and timing */
|
||||
update_status_time();
|
||||
|
||||
/* path trace */
|
||||
foreach(Tile& tile, tile_manager.state.tiles) {
|
||||
path_trace(tile);
|
||||
|
||||
device->task_wait();
|
||||
|
||||
if(progress.get_cancel())
|
||||
break;
|
||||
}
|
||||
|
||||
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(progress.get_cancel())
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* CPU Session */
|
||||
|
||||
void Session::reset_cpu(int w, int h)
|
||||
{
|
||||
thread_scoped_lock reset_lock(delayed_reset.mutex);
|
||||
|
||||
display_outdated = true;
|
||||
reset_time = time_dt();
|
||||
|
||||
delayed_reset.w = w;
|
||||
delayed_reset.h = h;
|
||||
delayed_reset.do_reset = true;
|
||||
device->task_cancel();
|
||||
}
|
||||
|
||||
bool Session::draw_cpu(int w, int h)
|
||||
{
|
||||
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(w == display->width && h == display->height) {
|
||||
display->draw(device);
|
||||
|
||||
if(display_outdated && (time_dt() - reset_time) > params.text_timeout)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Session::run_cpu()
|
||||
{
|
||||
{
|
||||
/* 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.w, delayed_reset.h);
|
||||
delayed_reset.do_reset = false;
|
||||
}
|
||||
|
||||
while(!progress.get_cancel() && tile_manager.next()) {
|
||||
{
|
||||
thread_scoped_lock buffers_lock(buffers->mutex);
|
||||
|
||||
/* update scene */
|
||||
update_scene();
|
||||
if(progress.get_cancel())
|
||||
break;
|
||||
|
||||
/* update status and timing */
|
||||
update_status_time();
|
||||
|
||||
/* path trace all tiles */
|
||||
foreach(Tile& tile, tile_manager.state.tiles)
|
||||
path_trace(tile);
|
||||
}
|
||||
|
||||
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.w, delayed_reset.h);
|
||||
}
|
||||
else {
|
||||
/* tonemap only if we do not reset, we don't we don't
|
||||
want to show the result of an incomplete pass*/
|
||||
tonemap();
|
||||
}
|
||||
}
|
||||
|
||||
progress.set_update();
|
||||
}
|
||||
}
|
||||
|
||||
void Session::run()
|
||||
{
|
||||
/* session thread loop */
|
||||
progress.set_status("Waiting for render to start");
|
||||
|
||||
/* first scene update */
|
||||
if(!progress.get_cancel()) {
|
||||
thread_scoped_lock scene_lock(scene->mutex);
|
||||
scene->device_update(device, progress);
|
||||
}
|
||||
|
||||
/* run */
|
||||
if(!progress.get_cancel()) {
|
||||
if(device_use_gl)
|
||||
run_gpu();
|
||||
else
|
||||
run_cpu();
|
||||
}
|
||||
|
||||
/* progress update */
|
||||
if(progress.get_cancel())
|
||||
progress.set_status(progress.get_cancel_message());
|
||||
else
|
||||
progress.set_update();
|
||||
}
|
||||
|
||||
bool Session::draw(int w, int h)
|
||||
{
|
||||
if(device_use_gl)
|
||||
return draw_gpu(w, h);
|
||||
else
|
||||
return draw_cpu(w, h);
|
||||
}
|
||||
|
||||
void Session::reset_(int w, int h)
|
||||
{
|
||||
if(w != buffers->width || h != buffers->height) {
|
||||
gpu_draw_ready = false;
|
||||
buffers->reset(device, w, h);
|
||||
display->reset(device, w, h);
|
||||
}
|
||||
|
||||
tile_manager.reset(w, h);
|
||||
|
||||
start_time = time_dt();
|
||||
preview_time = 0.0;
|
||||
pass = 0;
|
||||
}
|
||||
|
||||
void Session::reset(int w, int h)
|
||||
{
|
||||
if(device_use_gl)
|
||||
reset_gpu(w, h);
|
||||
else
|
||||
reset_cpu(w, h);
|
||||
}
|
||||
|
||||
void Session::wait()
|
||||
{
|
||||
session_thread->join();
|
||||
delete session_thread;
|
||||
|
||||
session_thread = NULL;
|
||||
}
|
||||
|
||||
void Session::update_scene()
|
||||
{
|
||||
thread_scoped_lock scene_lock(scene->mutex);
|
||||
|
||||
progress.set_status("Updating Scene");
|
||||
|
||||
/* update camera if dimensions changed for progressive render */
|
||||
Camera *cam = scene->camera;
|
||||
int w = tile_manager.state.width;
|
||||
int h = tile_manager.state.height;
|
||||
|
||||
if(cam->width != w || cam->height != h) {
|
||||
cam->width = w;
|
||||
cam->height = h;
|
||||
cam->tag_update();
|
||||
}
|
||||
|
||||
/* update scene */
|
||||
if(scene->need_update())
|
||||
scene->device_update(device, progress);
|
||||
}
|
||||
|
||||
void Session::update_status_time()
|
||||
{
|
||||
int pass = tile_manager.state.pass;
|
||||
int resolution = tile_manager.state.resolution;
|
||||
|
||||
/* update status */
|
||||
string substatus;
|
||||
if(!params.progressive)
|
||||
substatus = "Path Tracing";
|
||||
else if(params.passes == INT_MAX)
|
||||
substatus = string_printf("Path Tracing Pass %d", pass+1);
|
||||
else
|
||||
substatus = string_printf("Path Tracing Pass %d/%d", pass+1, params.passes);
|
||||
progress.set_status("Rendering", substatus);
|
||||
|
||||
/* update timing */
|
||||
if(preview_time == 0.0 && resolution == 1)
|
||||
preview_time = time_dt();
|
||||
|
||||
double total_time = (time_dt() - start_time);
|
||||
double pass_time = (pass == 0)? 0.0: (time_dt() - preview_time)/(pass);
|
||||
|
||||
progress.set_pass(pass + 1, total_time, pass_time);
|
||||
}
|
||||
|
||||
void Session::path_trace(Tile& tile)
|
||||
{
|
||||
/* add path trace task */
|
||||
DeviceTask task(DeviceTask::PATH_TRACE);
|
||||
|
||||
task.x = tile.x;
|
||||
task.y = tile.y;
|
||||
task.w = tile.w;
|
||||
task.h = tile.h;
|
||||
task.buffer = buffers->buffer.device_pointer;
|
||||
task.rng_state = buffers->rng_state.device_pointer;
|
||||
task.pass = tile_manager.state.pass;
|
||||
task.resolution = tile_manager.state.resolution;
|
||||
|
||||
device->task_add(task);
|
||||
}
|
||||
|
||||
void Session::tonemap()
|
||||
{
|
||||
/* add tonemap task */
|
||||
DeviceTask task(DeviceTask::TONEMAP);
|
||||
|
||||
task.x = 0;
|
||||
task.y = 0;
|
||||
task.w = tile_manager.state.width;
|
||||
task.h = tile_manager.state.height;
|
||||
task.rgba = display->rgba.device_pointer;
|
||||
task.buffer = buffers->buffer.device_pointer;
|
||||
task.pass = tile_manager.state.pass;
|
||||
task.resolution = tile_manager.state.resolution;
|
||||
|
||||
device->task_add(task);
|
||||
device->task_wait();
|
||||
|
||||
/* set display to new size */
|
||||
display->draw_set(tile_manager.state.width, tile_manager.state.height);
|
||||
|
||||
display_outdated = false;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
||||
Reference in New Issue
Block a user