Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLukas Stockner <lukas.stockner@freenet.de>2017-05-07 15:40:58 +0300
committerLukas Stockner <lukas.stockner@freenet.de>2017-05-07 15:40:58 +0300
commit43b374e8c5430488a302298b1026faa1c3a231e9 (patch)
tree42e619a9fa08d02cef515b6315ce34dd7fd062b2 /intern/cycles/render
parentbca697834728fd12c84941aa2a428abfe2090b27 (diff)
Cycles: Implement denoising option for reducing noise in the rendered image
This commit contains the first part of the new Cycles denoising option, which filters the resulting image using information gathered during rendering to get rid of noise while preserving visual features as well as possible. To use the option, enable it in the render layer options. The default settings fit a wide range of scenes, but the user can tweak individual settings to control the tradeoff between a noise-free image, image details, and calculation time. Note that the denoiser may still change in the future and that some features are not implemented yet. The most important missing feature is animation denoising, which uses information from multiple frames at once to produce a flicker-free and smoother result. These features will be added in the future. Finally, thanks to all the people who supported this project: - Google (through the GSoC) and Theory Studios for sponsoring the development - The authors of the papers I used for implementing the denoiser (more details on them will be included in the technical docs) - The other Cycles devs for feedback on the code, especially Sergey for mentoring the GSoC project and Brecht for the code review! - And of course the users who helped with testing, reported bugs and things that could and/or should work better!
Diffstat (limited to 'intern/cycles/render')
-rw-r--r--intern/cycles/render/buffers.cpp63
-rw-r--r--intern/cycles/render/buffers.h15
-rw-r--r--intern/cycles/render/film.cpp22
-rw-r--r--intern/cycles/render/film.h7
-rw-r--r--intern/cycles/render/mesh.cpp2
-rw-r--r--intern/cycles/render/session.cpp215
-rw-r--r--intern/cycles/render/session.h26
-rw-r--r--intern/cycles/render/tile.cpp199
-rw-r--r--intern/cycles/render/tile.h33
9 files changed, 464 insertions, 118 deletions
diff --git a/intern/cycles/render/buffers.cpp b/intern/cycles/render/buffers.cpp
index fe2c2e78926..cf402c3f214 100644
--- a/intern/cycles/render/buffers.cpp
+++ b/intern/cycles/render/buffers.cpp
@@ -42,6 +42,9 @@ BufferParams::BufferParams()
full_width = 0;
full_height = 0;
+ denoising_data_pass = false;
+ denoising_clean_pass = false;
+
Pass::add(PASS_COMBINED, passes);
}
@@ -68,10 +71,25 @@ int BufferParams::get_passes_size()
for(size_t i = 0; i < passes.size(); i++)
size += passes[i].components;
-
+
+ if(denoising_data_pass) {
+ size += DENOISING_PASS_SIZE_BASE;
+ if(denoising_clean_pass) size += DENOISING_PASS_SIZE_CLEAN;
+ }
+
return align_up(size, 4);
}
+int BufferParams::get_denoising_offset()
+{
+ int offset = 0;
+
+ for(size_t i = 0; i < passes.size(); i++)
+ offset += passes[i].components;
+
+ return offset;
+}
+
/* Render Buffer Task */
RenderTile::RenderTile()
@@ -138,12 +156,51 @@ void RenderBuffers::reset(Device *device, BufferParams& params_)
device->mem_alloc("rng_state", rng_state, MEM_READ_WRITE);
}
-bool RenderBuffers::copy_from_device()
+bool RenderBuffers::copy_from_device(Device *from_device)
{
if(!buffer.device_pointer)
return false;
- device->mem_copy_from(buffer, 0, params.width, params.height, params.get_passes_size()*sizeof(float));
+ if(!from_device) {
+ from_device = device;
+ }
+
+ from_device->mem_copy_from(buffer, 0, params.width, params.height, params.get_passes_size()*sizeof(float));
+
+ return true;
+}
+
+bool RenderBuffers::get_denoising_pass_rect(int offset, float exposure, int sample, int components, float *pixels)
+{
+ float scale = 1.0f/sample;
+
+ if(offset == DENOISING_PASS_COLOR) {
+ scale *= exposure;
+ }
+ else if(offset == DENOISING_PASS_COLOR_VAR) {
+ scale *= exposure*exposure;
+ }
+
+ offset += params.get_denoising_offset();
+ float *in = (float*)buffer.data_pointer + offset;
+ int pass_stride = params.get_passes_size();
+ int size = params.width*params.height;
+
+ if(components == 1) {
+ for(int i = 0; i < size; i++, in += pass_stride, pixels++) {
+ pixels[0] = in[0]*scale;
+ }
+ }
+ else if(components == 3) {
+ for(int i = 0; i < size; i++, in += pass_stride, pixels += 3) {
+ pixels[0] = in[0]*scale;
+ pixels[1] = in[1]*scale;
+ pixels[2] = in[2]*scale;
+ }
+ }
+ else {
+ return false;
+ }
return true;
}
diff --git a/intern/cycles/render/buffers.h b/intern/cycles/render/buffers.h
index 5c78971678a..e56556c8abe 100644
--- a/intern/cycles/render/buffers.h
+++ b/intern/cycles/render/buffers.h
@@ -51,6 +51,9 @@ public:
/* passes */
array<Pass> passes;
+ bool denoising_data_pass;
+ /* If only some light path types should be denoised, an additional pass is needed. */
+ bool denoising_clean_pass;
/* functions */
BufferParams();
@@ -59,6 +62,7 @@ public:
bool modified(const BufferParams& params);
void add_pass(PassType type);
int get_passes_size();
+ int get_denoising_offset();
};
/* Render Buffers */
@@ -73,18 +77,19 @@ public:
/* random number generator state */
device_vector<uint> rng_state;
+ Device *device;
+
explicit RenderBuffers(Device *device);
~RenderBuffers();
void reset(Device *device, BufferParams& params);
- bool copy_from_device();
+ bool copy_from_device(Device *from_device = NULL);
bool get_pass_rect(PassType type, float exposure, int sample, int components, float *pixels);
+ bool get_denoising_pass_rect(int offset, float exposure, int sample, int components, float *pixels);
protected:
void device_free();
-
- Device *device;
};
/* Display Buffer
@@ -131,6 +136,9 @@ protected:
class RenderTile {
public:
+ typedef enum { PATH_TRACE, DENOISE } Task;
+
+ Task task;
int x, y, w, h;
int start_sample;
int num_samples;
@@ -138,6 +146,7 @@ public:
int resolution;
int offset;
int stride;
+ int tile_index;
device_ptr buffer;
device_ptr rng_state;
diff --git a/intern/cycles/render/film.cpp b/intern/cycles/render/film.cpp
index 7809f4345f1..c8213d258d5 100644
--- a/intern/cycles/render/film.cpp
+++ b/intern/cycles/render/film.cpp
@@ -279,6 +279,10 @@ NODE_DEFINE(Film)
SOCKET_BOOLEAN(use_sample_clamp, "Use Sample Clamp", false);
+ SOCKET_BOOLEAN(denoising_data_pass, "Generate Denoising Data Pass", false);
+ SOCKET_BOOLEAN(denoising_clean_pass, "Generate Denoising Clean Pass", false);
+ SOCKET_INT(denoising_flags, "Denoising Flags", 0);
+
return type;
}
@@ -437,6 +441,20 @@ void Film::device_update(Device *device, DeviceScene *dscene, Scene *scene)
kfilm->pass_stride += pass.components;
}
+ kfilm->pass_denoising_data = 0;
+ kfilm->pass_denoising_clean = 0;
+ kfilm->denoising_flags = 0;
+ if(denoising_data_pass) {
+ kfilm->pass_denoising_data = kfilm->pass_stride;
+ kfilm->pass_stride += DENOISING_PASS_SIZE_BASE;
+ kfilm->denoising_flags = denoising_flags;
+ if(denoising_clean_pass) {
+ kfilm->pass_denoising_clean = kfilm->pass_stride;
+ kfilm->pass_stride += DENOISING_PASS_SIZE_CLEAN;
+ kfilm->use_light_pass = 1;
+ }
+ }
+
kfilm->pass_stride = align_up(kfilm->pass_stride, 4);
kfilm->pass_alpha_threshold = pass_alpha_threshold;
@@ -451,6 +469,10 @@ void Film::device_update(Device *device, DeviceScene *dscene, Scene *scene)
kfilm->mist_inv_depth = (mist_depth > 0.0f)? 1.0f/mist_depth: 0.0f;
kfilm->mist_falloff = mist_falloff;
+ pass_stride = kfilm->pass_stride;
+ denoising_data_offset = kfilm->pass_denoising_data;
+ denoising_clean_offset = kfilm->pass_denoising_clean;
+
need_update = false;
}
diff --git a/intern/cycles/render/film.h b/intern/cycles/render/film.h
index 83c941d5c57..29b1e7e9157 100644
--- a/intern/cycles/render/film.h
+++ b/intern/cycles/render/film.h
@@ -57,8 +57,15 @@ public:
float exposure;
array<Pass> passes;
+ bool denoising_data_pass;
+ bool denoising_clean_pass;
+ int denoising_flags;
float pass_alpha_threshold;
+ int pass_stride;
+ int denoising_data_offset;
+ int denoising_clean_offset;
+
FilterType filter_type;
float filter_width;
size_t filter_table_offset;
diff --git a/intern/cycles/render/mesh.cpp b/intern/cycles/render/mesh.cpp
index 33d1936659b..03825f780e0 100644
--- a/intern/cycles/render/mesh.cpp
+++ b/intern/cycles/render/mesh.cpp
@@ -903,7 +903,7 @@ void Mesh::pack_normals(Scene *scene, uint *tri_shader, float4 *vnormal)
float3 vNi = vN[i];
if(do_transform)
- vNi = normalize(transform_direction(&ntfm, vNi));
+ vNi = safe_normalize(transform_direction(&ntfm, vNi));
vnormal[i] = make_float4(vNi.x, vNi.y, vNi.z, 0.0f);
}
diff --git a/intern/cycles/render/session.cpp b/intern/cycles/render/session.cpp
index c9b5547b407..3eaf34c847f 100644
--- a/intern/cycles/render/session.cpp
+++ b/intern/cycles/render/session.cpp
@@ -114,8 +114,9 @@ Session::~Session()
}
/* clean up */
- foreach(RenderBuffers *buffers, tile_buffers)
- delete buffers;
+ foreach(RenderTile &rtile, render_tiles)
+ delete rtile.buffers;
+ tile_manager.free_device();
delete buffers;
delete display;
@@ -268,8 +269,8 @@ void Session::run_gpu()
/* update status and timing */
update_status_time();
- /* path trace */
- path_trace();
+ /* render */
+ render();
device->task_wait();
@@ -358,20 +359,22 @@ bool Session::acquire_tile(Device *tile_device, RenderTile& rtile)
thread_scoped_lock tile_lock(tile_mutex);
/* get next tile from manager */
- Tile tile;
+ 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.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.task = (tile->state == Tile::DENOISE)? RenderTile::DENOISE: RenderTile::PATH_TRACE;
tile_lock.unlock();
@@ -383,54 +386,70 @@ bool Session::acquire_tile(Device *tile_device, RenderTile& rtile)
rtile.buffer = buffers->buffer.device_pointer;
rtile.rng_state = buffers->rng_state.device_pointer;
rtile.buffers = buffers;
+ tile->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;
+ bool store_rtile = false;
+ 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 */
+ if(params.progressive_refine) {
+ tile_lock.lock();
+
+ if(render_tiles.size() == 0) {
+ RenderTile nulltile;
+ nulltile.buffers = NULL;
+ render_tiles.resize(tile_manager.state.num_tiles, nulltile);
+ }
- /* allocate buffers */
- if(params.progressive_refine) {
- tile_lock.lock();
+ /* In certain circumstances number of tiles in the tile manager could
+ * be changed. This is not supported by the progressive refine feature.
+ */
+ assert(render_tiles.size() == tile_manager.state.num_tiles);
- if(tile_buffers.size() == 0)
- tile_buffers.resize(tile_manager.state.num_tiles, NULL);
+ RenderTile &stored_rtile = render_tiles[tile->index];
+ if(stored_rtile.buffers == NULL) {
+ tile->buffers = new RenderBuffers(tile_device);
+ tile->buffers->reset(tile_device, buffer_params);
+ store_rtile = true;
+ }
+ else {
+ assert(rtile.x == stored_rtile.x &&
+ rtile.y == stored_rtile.y &&
+ rtile.w == stored_rtile.w &&
+ rtile.h == stored_rtile.h);
+ tile_lock.unlock();
+ tile->buffers = stored_rtile.buffers;
+ }
+ }
+ else {
+ tile->buffers = new RenderBuffers(tile_device);
- /* In certain circumstances number of tiles in the tile manager could
- * be changed. This is not supported by the progressive refine feature.
- */
- assert(tile_buffers.size() == tile_manager.state.num_tiles);
+ tile->buffers->reset(tile_device, buffer_params);
+ }
+ }
- tilebuffers = tile_buffers[tile.index];
- if(tilebuffers == NULL) {
- tilebuffers = new RenderBuffers(tile_device);
- tile_buffers[tile.index] = tilebuffers;
+ tile->buffers->params.get_offset_stride(rtile.offset, rtile.stride);
- tilebuffers->reset(tile_device, buffer_params);
- }
+ rtile.buffer = tile->buffers->buffer.device_pointer;
+ rtile.rng_state = tile->buffers->rng_state.device_pointer;
+ rtile.buffers = tile->buffers;
+ rtile.sample = 0;
+ if(store_rtile) {
+ render_tiles[tile->index] = rtile;
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
@@ -449,7 +468,7 @@ void Session::update_tile_sample(RenderTile& rtile)
if(params.progressive_refine == false) {
/* todo: optimize this by making it thread safe and removing lock */
- update_render_tile_cb(rtile);
+ update_render_tile_cb(rtile, true);
}
}
@@ -462,18 +481,75 @@ void Session::release_tile(RenderTile& rtile)
progress.add_finished_tile();
- 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);
+ bool delete_tile;
- delete rtile.buffers;
+ if(tile_manager.finish_tile(rtile.tile_index, delete_tile)) {
+ 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 {
+ if(update_render_tile_cb && params.progressive_refine == false) {
+ update_render_tile_cb(rtile, false);
}
}
update_status_time();
}
+void Session::map_neighbor_tiles(RenderTile *tiles, Device *tile_device)
+{
+ thread_scoped_lock tile_lock(tile_mutex);
+
+ int center_idx = tiles[4].tile_index;
+ assert(tile_manager.state.tiles[center_idx].state == Tile::DENOISE);
+ BufferParams buffer_params = tile_manager.params;
+ int4 image_region = make_int4(buffer_params.full_x, buffer_params.full_y,
+ buffer_params.full_x + buffer_params.width, buffer_params.full_y + buffer_params.height);
+
+ for(int dy = -1, i = 0; dy <= 1; dy++) {
+ for(int dx = -1; dx <= 1; dx++, i++) {
+ int px = tiles[4].x + dx*params.tile_size.x;
+ int py = tiles[4].y + dy*params.tile_size.y;
+ if(px >= image_region.x && py >= image_region.y &&
+ px < image_region.z && py < image_region.w) {
+ int tile_index = center_idx + dy*tile_manager.state.tile_stride + dx;
+ Tile *tile = &tile_manager.state.tiles[tile_index];
+ assert(tile->buffers);
+
+ tiles[i].buffer = tile->buffers->buffer.device_pointer;
+ tiles[i].x = tile_manager.state.buffer.full_x + tile->x;
+ tiles[i].y = tile_manager.state.buffer.full_y + tile->y;
+ tiles[i].w = tile->w;
+ tiles[i].h = tile->h;
+ tiles[i].buffers = tile->buffers;
+
+ tile->buffers->params.get_offset_stride(tiles[i].offset, tiles[i].stride);
+ }
+ else {
+ tiles[i].buffer = (device_ptr)NULL;
+ tiles[i].buffers = NULL;
+ tiles[i].x = clamp(px, image_region.x, image_region.z);
+ tiles[i].y = clamp(py, image_region.y, image_region.w);
+ tiles[i].w = tiles[i].h = 0;
+ }
+ }
+ }
+
+ assert(tiles[4].buffers);
+ device->map_neighbor_tiles(tile_device, tiles);
+}
+
+void Session::unmap_neighbor_tiles(RenderTile *tiles, Device *tile_device)
+{
+ thread_scoped_lock tile_lock(tile_mutex);
+ device->unmap_neighbor_tiles(tile_device, tiles);
+}
+
void Session::run_cpu()
{
bool tiles_written = false;
@@ -558,8 +634,8 @@ void Session::run_cpu()
/* update status and timing */
update_status_time();
- /* path trace */
- path_trace();
+ /* render */
+ render();
/* update status and timing */
update_status_time();
@@ -744,10 +820,10 @@ void Session::reset(BufferParams& buffer_params, int samples)
if(params.progressive_refine) {
thread_scoped_lock buffers_lock(buffers_mutex);
- foreach(RenderBuffers *buffers, tile_buffers)
- delete buffers;
+ foreach(RenderTile &rtile, render_tiles)
+ delete rtile.buffers;
- tile_buffers.clear();
+ render_tiles.clear();
}
}
@@ -882,13 +958,15 @@ void Session::update_status_time(bool show_pause, bool show_done)
progress.set_status(status, substatus);
}
-void Session::path_trace()
+void Session::render()
{
/* add path trace task */
- DeviceTask task(DeviceTask::PATH_TRACE);
+ DeviceTask task(DeviceTask::RENDER);
task.acquire_tile = function_bind(&Session::acquire_tile, this, _1, _2);
task.release_tile = function_bind(&Session::release_tile, this, _1);
+ 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);
@@ -897,6 +975,18 @@ void Session::path_trace()
task.requested_tile_size = params.tile_size;
task.passes_size = tile_manager.params.get_passes_size();
+ if(params.use_denoising) {
+ task.denoising_radius = params.denoising_radius;
+ task.denoising_strength = params.denoising_strength;
+ task.denoising_feature_strength = params.denoising_feature_strength;
+ task.denoising_relative_pca = params.denoising_relative_pca;
+
+ assert(!scene->film->need_update);
+ task.pass_stride = scene->film->pass_stride;
+ task.pass_denoising_data = scene->film->denoising_data_offset;
+ task.pass_denoising_clean = scene->film->denoising_clean_offset;
+ }
+
device->task_add(task);
}
@@ -940,9 +1030,7 @@ bool Session::update_progressive_refine(bool cancel)
}
if(params.progressive_refine) {
- foreach(RenderBuffers *buffers, tile_buffers) {
- RenderTile rtile;
- rtile.buffers = buffers;
+ foreach(RenderTile &rtile, render_tiles) {
rtile.sample = sample;
if(write) {
@@ -951,7 +1039,7 @@ bool Session::update_progressive_refine(bool cancel)
}
else {
if(update_render_tile_cb)
- update_render_tile_cb(rtile);
+ update_render_tile_cb(rtile, true);
}
}
}
@@ -965,10 +1053,11 @@ void Session::device_free()
{
scene->device_free();
- foreach(RenderBuffers *buffers, tile_buffers)
- delete buffers;
+ foreach(RenderTile &tile, render_tiles)
+ delete tile.buffers;
+ tile_manager.free_device();
- tile_buffers.clear();
+ render_tiles.clear();
/* used from background render only, so no need to
* re-create render/display buffers here
diff --git a/intern/cycles/render/session.h b/intern/cycles/render/session.h
index a7e5f78a64d..a7ca90abbce 100644
--- a/intern/cycles/render/session.h
+++ b/intern/cycles/render/session.h
@@ -57,6 +57,12 @@ public:
bool display_buffer_linear;
+ bool use_denoising;
+ int denoising_radius;
+ float denoising_strength;
+ float denoising_feature_strength;
+ bool denoising_relative_pca;
+
double cancel_timeout;
double reset_timeout;
double text_timeout;
@@ -77,6 +83,12 @@ public:
start_resolution = INT_MAX;
threads = 0;
+ use_denoising = false;
+ denoising_radius = 8;
+ denoising_strength = 0.0f;
+ denoising_feature_strength = 0.0f;
+ denoising_relative_pca = false;
+
display_buffer_linear = false;
cancel_timeout = 0.1;
@@ -99,6 +111,11 @@ public:
&& tile_size == params.tile_size
&& start_resolution == params.start_resolution
&& threads == params.threads
+ && use_denoising == params.use_denoising
+ && denoising_radius == params.denoising_radius
+ && denoising_strength == params.denoising_strength
+ && denoising_feature_strength == params.denoising_feature_strength
+ && denoising_relative_pca == params.denoising_relative_pca
&& display_buffer_linear == params.display_buffer_linear
&& cancel_timeout == params.cancel_timeout
&& reset_timeout == params.reset_timeout
@@ -126,7 +143,7 @@ public:
Stats stats;
function<void(RenderTile&)> write_render_tile_cb;
- function<void(RenderTile&)> update_render_tile_cb;
+ function<void(RenderTile&, bool)> update_render_tile_cb;
explicit Session(const SessionParams& params);
~Session();
@@ -162,7 +179,7 @@ protected:
void update_status_time(bool show_pause = false, bool show_done = false);
void tonemap(int sample);
- void path_trace();
+ void render();
void reset_(BufferParams& params, int samples);
void run_cpu();
@@ -177,6 +194,9 @@ protected:
void update_tile_sample(RenderTile& tile);
void release_tile(RenderTile& tile);
+ void map_neighbor_tiles(RenderTile *tiles, Device *tile_device);
+ void unmap_neighbor_tiles(RenderTile *tiles, Device *tile_device);
+
bool device_use_gl;
thread *session_thread;
@@ -202,7 +222,7 @@ protected:
double last_update_time;
bool update_progressive_refine(bool cancel);
- vector<RenderBuffers *> tile_buffers;
+ vector<RenderTile> render_tiles;
DeviceRequestedFeatures get_requested_device_features();
diff --git a/intern/cycles/render/tile.cpp b/intern/cycles/render/tile.cpp
index 944e746ca2d..176a1f4f0f3 100644
--- a/intern/cycles/render/tile.cpp
+++ b/intern/cycles/render/tile.cpp
@@ -25,37 +25,39 @@ namespace {
class TileComparator {
public:
- TileComparator(TileOrder order, int2 center)
- : order_(order),
- center_(center)
+ TileComparator(TileOrder order_, int2 center_, Tile *tiles_)
+ : order(order_),
+ center(center_),
+ tiles(tiles_)
{}
- bool operator()(Tile &a, Tile &b)
+ bool operator()(int a, int b)
{
- switch(order_) {
+ switch(order) {
case TILE_CENTER:
{
- float2 dist_a = make_float2(center_.x - (a.x + a.w/2),
- center_.y - (a.y + a.h/2));
- float2 dist_b = make_float2(center_.x - (b.x + b.w/2),
- center_.y - (b.y + b.h/2));
+ float2 dist_a = make_float2(center.x - (tiles[a].x + tiles[a].w/2),
+ center.y - (tiles[a].y + tiles[a].h/2));
+ float2 dist_b = make_float2(center.x - (tiles[b].x + tiles[b].w/2),
+ center.y - (tiles[b].y + tiles[b].h/2));
return dot(dist_a, dist_a) < dot(dist_b, dist_b);
}
case TILE_LEFT_TO_RIGHT:
- return (a.x == b.x)? (a.y < b.y): (a.x < b.x);
+ return (tiles[a].x == tiles[b].x)? (tiles[a].y < tiles[b].y): (tiles[a].x < tiles[b].x);
case TILE_RIGHT_TO_LEFT:
- return (a.x == b.x)? (a.y < b.y): (a.x > b.x);
+ return (tiles[a].x == tiles[b].x)? (tiles[a].y < tiles[b].y): (tiles[a].x > tiles[b].x);
case TILE_TOP_TO_BOTTOM:
- return (a.y == b.y)? (a.x < b.x): (a.y > b.y);
+ return (tiles[a].y == tiles[b].y)? (tiles[a].x < tiles[b].x): (tiles[a].y > tiles[b].y);
case TILE_BOTTOM_TO_TOP:
default:
- return (a.y == b.y)? (a.x < b.x): (a.y < b.y);
+ return (tiles[a].y == tiles[b].y)? (tiles[a].x < tiles[b].x): (tiles[a].y < tiles[b].y);
}
}
protected:
- TileOrder order_;
- int2 center_;
+ TileOrder order;
+ int2 center;
+ Tile *tiles;
};
inline int2 hilbert_index_to_pos(int n, int d)
@@ -96,6 +98,7 @@ TileManager::TileManager(bool progressive_, int num_samples_, int2 tile_size_, i
num_devices = num_devices_;
preserve_tile_device = preserve_tile_device_;
background = background_;
+ schedule_denoising = false;
range_start_sample = 0;
range_num_samples = -1;
@@ -108,6 +111,16 @@ TileManager::~TileManager()
{
}
+void TileManager::free_device()
+{
+ if(schedule_denoising) {
+ for(int i = 0; i < state.tiles.size(); i++) {
+ delete state.tiles[i].buffers;
+ state.tiles[i].buffers = NULL;
+ }
+ }
+}
+
static int get_divider(int w, int h, int start_resolution)
{
int divider = 1;
@@ -133,6 +146,8 @@ void TileManager::reset(BufferParams& params_, int num_samples_)
state.num_tiles = 0;
state.num_samples = 0;
state.resolution_divider = get_divider(params.width, params.height, start_resolution);
+ state.render_tiles.clear();
+ state.denoising_tiles.clear();
state.tiles.clear();
}
@@ -157,6 +172,9 @@ void TileManager::set_samples(int num_samples_)
}
state.total_pixel_samples = pixel_samples + (uint64_t)get_num_effective_samples() * params.width*params.height;
+ if(schedule_denoising) {
+ state.total_pixel_samples += params.width*params.height;
+ }
}
}
@@ -169,32 +187,36 @@ int TileManager::gen_tiles(bool sliced)
int image_h = max(1, params.height/resolution);
int2 center = make_int2(image_w/2, image_h/2);
- state.tiles.clear();
-
int num_logical_devices = preserve_tile_device? num_devices: 1;
int num = min(image_h, num_logical_devices);
int slice_num = sliced? num: 1;
- int tile_index = 0;
+ int tile_w = (tile_size.x >= image_w) ? 1 : divide_up(image_w, tile_size.x);
state.tiles.clear();
- state.tiles.resize(num);
- vector<list<Tile> >::iterator tile_list = state.tiles.begin();
+ state.render_tiles.clear();
+ state.denoising_tiles.clear();
+ state.render_tiles.resize(num);
+ state.denoising_tiles.resize(num);
+ state.tile_stride = tile_w;
+ vector<list<int> >::iterator tile_list;
+ tile_list = state.render_tiles.begin();
if(tile_order == TILE_HILBERT_SPIRAL) {
assert(!sliced);
+ int tile_h = (tile_size.y >= image_h) ? 1 : divide_up(image_h, tile_size.y);
+ state.tiles.resize(tile_w*tile_h);
+
/* Size of blocks in tiles, must be a power of 2 */
const int hilbert_size = (max(tile_size.x, tile_size.y) <= 12)? 8: 4;
- int tile_w = (tile_size.x >= image_w)? 1: (image_w + tile_size.x - 1)/tile_size.x;
- int tile_h = (tile_size.y >= image_h)? 1: (image_h + tile_size.y - 1)/tile_size.y;
- int tiles_per_device = (tile_w * tile_h + num - 1) / num;
+ int tiles_per_device = divide_up(tile_w * tile_h, num);
int cur_device = 0, cur_tiles = 0;
int2 block_size = tile_size * make_int2(hilbert_size, hilbert_size);
/* Number of blocks to fill the image */
- int blocks_x = (block_size.x >= image_w)? 1: (image_w + block_size.x - 1)/block_size.x;
- int blocks_y = (block_size.y >= image_h)? 1: (image_h + block_size.y - 1)/block_size.y;
+ int blocks_x = (block_size.x >= image_w)? 1: divide_up(image_w, block_size.x);
+ int blocks_y = (block_size.y >= image_h)? 1: divide_up(image_h, block_size.y);
int n = max(blocks_x, blocks_y) | 0x1; /* Side length of the spiral (must be odd) */
/* Offset of spiral (to keep it centered) */
int2 offset = make_int2((image_w - n*block_size.x)/2, (image_h - n*block_size.y)/2);
@@ -225,9 +247,11 @@ int TileManager::gen_tiles(bool sliced)
if(pos.x >= 0 && pos.y >= 0 && pos.x < image_w && pos.y < image_h) {
int w = min(tile_size.x, image_w - pos.x);
int h = min(tile_size.y, image_h - pos.y);
- tile_list->push_front(Tile(tile_index, pos.x, pos.y, w, h, cur_device));
+ int2 ipos = pos / tile_size;
+ int idx = ipos.y*tile_w + ipos.x;
+ state.tiles[idx] = Tile(idx, pos.x, pos.y, w, h, cur_device, Tile::RENDER);
+ tile_list->push_front(idx);
cur_tiles++;
- tile_index++;
if(cur_tiles == tiles_per_device) {
tile_list++;
@@ -271,27 +295,28 @@ int TileManager::gen_tiles(bool sliced)
break;
}
}
- return tile_index;
+ return tile_w*tile_h;
}
+ int idx = 0;
for(int slice = 0; slice < slice_num; slice++) {
int slice_y = (image_h/slice_num)*slice;
int slice_h = (slice == slice_num-1)? image_h - slice*(image_h/slice_num): image_h/slice_num;
- int tile_w = (tile_size.x >= image_w)? 1: (image_w + tile_size.x - 1)/tile_size.x;
- int tile_h = (tile_size.y >= slice_h)? 1: (slice_h + tile_size.y - 1)/tile_size.y;
+ int tile_h = (tile_size.y >= slice_h)? 1: divide_up(slice_h, tile_size.y);
- int tiles_per_device = (tile_w * tile_h + num - 1) / num;
+ int tiles_per_device = divide_up(tile_w * tile_h, num);
int cur_device = 0, cur_tiles = 0;
for(int tile_y = 0; tile_y < tile_h; tile_y++) {
- for(int tile_x = 0; tile_x < tile_w; tile_x++, tile_index++) {
+ for(int tile_x = 0; tile_x < tile_w; tile_x++, idx++) {
int x = tile_x * tile_size.x;
int y = tile_y * tile_size.y;
int w = (tile_x == tile_w-1)? image_w - x: tile_size.x;
int h = (tile_y == tile_h-1)? slice_h - y: tile_size.y;
- tile_list->push_back(Tile(tile_index, x, y + slice_y, w, h, sliced? slice: cur_device));
+ state.tiles.push_back(Tile(idx, x, y + slice_y, w, h, sliced? slice: cur_device, Tile::RENDER));
+ tile_list->push_back(idx);
if(!sliced) {
cur_tiles++;
@@ -299,7 +324,7 @@ int TileManager::gen_tiles(bool sliced)
if(cur_tiles == tiles_per_device) {
/* Tiles are already generated in Bottom-to-Top order, so no sort is necessary in that case. */
if(tile_order != TILE_BOTTOM_TO_TOP) {
- tile_list->sort(TileComparator(tile_order, center));
+ tile_list->sort(TileComparator(tile_order, center, &state.tiles[0]));
}
tile_list++;
cur_tiles = 0;
@@ -313,7 +338,7 @@ int TileManager::gen_tiles(bool sliced)
}
}
- return tile_index;
+ return idx;
}
void TileManager::set_tiles()
@@ -333,15 +358,111 @@ void TileManager::set_tiles()
state.buffer.full_height = max(1, params.full_height/resolution);
}
-bool TileManager::next_tile(Tile& tile, int device)
+int TileManager::get_neighbor_index(int index, int neighbor)
+{
+ static const int dx[] = {-1, 0, 1, -1, 1, -1, 0, 1, 0}, dy[] = {-1, -1, -1, 0, 0, 1, 1, 1, 0};
+
+ int resolution = state.resolution_divider;
+ int image_w = max(1, params.width/resolution);
+ int image_h = max(1, params.height/resolution);
+ int tile_w = (tile_size.x >= image_w)? 1: divide_up(image_w, tile_size.x);
+ int tile_h = (tile_size.y >= image_h)? 1: divide_up(image_h, tile_size.y);
+
+ int nx = state.tiles[index].x/tile_size.x + dx[neighbor], ny = state.tiles[index].y/tile_size.y + dy[neighbor];
+ if(nx < 0 || ny < 0 || nx >= tile_w || ny >= tile_h)
+ return -1;
+
+ return ny*state.tile_stride + nx;
+}
+
+/* Checks whether all neighbors of a tile (as well as the tile itself) are at least at state min_state. */
+bool TileManager::check_neighbor_state(int index, Tile::State min_state)
+{
+ if(index < 0 || state.tiles[index].state < min_state) {
+ return false;
+ }
+ for(int neighbor = 0; neighbor < 9; neighbor++) {
+ int nindex = get_neighbor_index(index, neighbor);
+ /* Out-of-bounds tiles don't matter. */
+ if(nindex >= 0 && state.tiles[nindex].state < min_state) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/* Returns whether the tile should be written (and freed if no denoising is used) instead of updating. */
+bool TileManager::finish_tile(int index, bool &delete_tile)
+{
+ delete_tile = false;
+
+ switch(state.tiles[index].state) {
+ case Tile::RENDER:
+ {
+ if(!schedule_denoising) {
+ state.tiles[index].state = Tile::DONE;
+ delete_tile = true;
+ return true;
+ }
+ state.tiles[index].state = Tile::RENDERED;
+ /* For each neighbor and the tile itself, check whether all of its neighbors have been rendered. If yes, it can be denoised. */
+ for(int neighbor = 0; neighbor < 9; neighbor++) {
+ int nindex = get_neighbor_index(index, neighbor);
+ if(check_neighbor_state(nindex, Tile::RENDERED)) {
+ state.tiles[nindex].state = Tile::DENOISE;
+ state.denoising_tiles[state.tiles[nindex].device].push_back(nindex);
+ }
+ }
+ return false;
+ }
+ case Tile::DENOISE:
+ {
+ state.tiles[index].state = Tile::DENOISED;
+ /* For each neighbor and the tile itself, check whether all of its neighbors have been denoised. If yes, it can be freed. */
+ for(int neighbor = 0; neighbor < 9; neighbor++) {
+ int nindex = get_neighbor_index(index, neighbor);
+ if(check_neighbor_state(nindex, Tile::DENOISED)) {
+ state.tiles[nindex].state = Tile::DONE;
+ /* It can happen that the tile just finished denoising and already can be freed here.
+ * However, in that case it still has to be written before deleting, so we can't delete it yet. */
+ if(neighbor == 8) {
+ delete_tile = true;
+ }
+ else {
+ delete state.tiles[nindex].buffers;
+ state.tiles[nindex].buffers = NULL;
+ }
+ }
+ }
+ return true;
+ }
+ default:
+ assert(false);
+ return true;
+ }
+}
+
+bool TileManager::next_tile(Tile* &tile, int device)
{
int logical_device = preserve_tile_device? device: 0;
- if((logical_device >= state.tiles.size()) || state.tiles[logical_device].empty())
+ if(logical_device >= state.render_tiles.size())
+ return false;
+
+ if(!state.denoising_tiles[logical_device].empty()) {
+ int idx = state.denoising_tiles[logical_device].front();
+ state.denoising_tiles[logical_device].pop_front();
+ tile = &state.tiles[idx];
+ return true;
+ }
+
+ if(state.render_tiles[logical_device].empty())
return false;
- tile = Tile(state.tiles[logical_device].front());
- state.tiles[logical_device].pop_front();
+ int idx = state.render_tiles[logical_device].front();
+ state.render_tiles[logical_device].pop_front();
+ tile = &state.tiles[idx];
return true;
}
diff --git a/intern/cycles/render/tile.h b/intern/cycles/render/tile.h
index 622b89f7670..e39a8f0627a 100644
--- a/intern/cycles/render/tile.h
+++ b/intern/cycles/render/tile.h
@@ -31,12 +31,20 @@ public:
int index;
int x, y, w, h;
int device;
+ /* RENDER: The tile has to be rendered.
+ * RENDERED: The tile has been rendered, but can't be denoised yet (waiting for neighbors).
+ * DENOISE: The tile can be denoised now.
+ * DENOISED: The tile has been denoised, but can't be freed yet (waiting for neighbors).
+ * DONE: The tile is finished and has been freed. */
+ typedef enum { RENDER = 0, RENDERED, DENOISE, DENOISED, DONE } State;
+ State state;
+ RenderBuffers *buffers;
Tile()
{}
- Tile(int index_, int x_, int y_, int w_, int h_, int device_)
- : index(index_), x(x_), y(y_), w(w_), h(h_), device(device_) {}
+ Tile(int index_, int x_, int y_, int w_, int h_, int device_, State state_ = RENDER)
+ : index(index_), x(x_), y(y_), w(w_), h(h_), device(device_), state(state_), buffers(NULL) {}
};
/* Tile order */
@@ -58,6 +66,8 @@ public:
BufferParams params;
struct State {
+ vector<Tile> tiles;
+ int tile_stride;
BufferParams buffer;
int sample;
int num_samples;
@@ -67,9 +77,12 @@ public:
/* Total samples over all pixels: Generally num_samples*num_pixels,
* but can be higher due to the initial resolution division for previews. */
uint64_t total_pixel_samples;
- /* This vector contains a list of tiles for every logical device in the session.
- * In each list, the tiles are sorted according to the tile order setting. */
- vector<list<Tile> > tiles;
+
+ /* These lists contain the indices of the tiles to be rendered/denoised and are used
+ * when acquiring a new tile for the device.
+ * Each list in each vector is for one logical device. */
+ vector<list<int> > render_tiles;
+ vector<list<int> > denoising_tiles;
} state;
int num_samples;
@@ -78,10 +91,12 @@ public:
bool preserve_tile_device, bool background, TileOrder tile_order, int num_devices = 1);
~TileManager();
+ void free_device();
void reset(BufferParams& params, int num_samples);
void set_samples(int num_samples);
bool next();
- bool next_tile(Tile& tile, int device = 0);
+ bool next_tile(Tile* &tile, int device = 0);
+ bool finish_tile(int index, bool& delete_tile);
bool done();
void set_tile_order(TileOrder tile_order_) { tile_order = tile_order_; }
@@ -96,6 +111,9 @@ public:
/* Get number of actual samples to render. */
int get_num_effective_samples();
+
+ /* Schedule tiles for denoising after they've been rendered. */
+ bool schedule_denoising;
protected:
void set_tiles();
@@ -127,6 +145,9 @@ protected:
/* Generate tile list, return number of tiles. */
int gen_tiles(bool sliced);
+
+ int get_neighbor_index(int index, int neighbor);
+ bool check_neighbor_state(int index, Tile::State state);
};
CCL_NAMESPACE_END