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:
authorSergey Sharybin <sergey@blender.org>2021-09-15 17:14:03 +0300
committerSergey Sharybin <sergey@blender.org>2021-09-17 12:18:12 +0300
commitadf85cd552d3d3319b095397f95fecc0c8961bc5 (patch)
tree29069a64ac2301df95c3a0e074c2e16ac10e9cf4
parente5f51c684bf94476864f736aaf6ea25059c345b7 (diff)
Cycles X: Real memory saving when using tiled rendering
The general idea is to delay reading EXR files for until after all view layers are rendered. Once they are all rendered, BlenderSession frees up as much memory as possible, and initiates processing of the on-disk files. The processing includes reading the file, optional denoising of the full frame (if the denoising is enabled), and writing the result via the write callback. The processing is done as a state-machine which routes specific calls to a full-frame processing, which allows to re-use same tile write callback in the software integration. In order to be able to know which view layer and render view is being written the API has been extended to write layer and view names. Done via BufferParams, as layer and view concepts are quite typical for the EXR files. The BufferParams also contains all fields needed for buffers access outside of the scene graph. Differential Revision: https://developer.blender.org/D12503
-rw-r--r--intern/cycles/blender/addon/__init__.py3
-rw-r--r--intern/cycles/blender/addon/engine.py7
-rw-r--r--intern/cycles/blender/blender_python.cpp21
-rw-r--r--intern/cycles/blender/blender_session.cpp46
-rw-r--r--intern/cycles/blender/blender_session.h6
-rw-r--r--intern/cycles/integrator/pass_accessor.cpp14
-rw-r--r--intern/cycles/integrator/pass_accessor.h4
-rw-r--r--intern/cycles/integrator/path_trace.cpp144
-rw-r--r--intern/cycles/integrator/path_trace.h26
-rw-r--r--intern/cycles/integrator/path_trace_work.cpp2
-rw-r--r--intern/cycles/integrator/render_scheduler.cpp4
-rw-r--r--intern/cycles/integrator/render_scheduler.h4
-rw-r--r--intern/cycles/render/buffers.cpp13
-rw-r--r--intern/cycles/render/buffers.h10
-rw-r--r--intern/cycles/render/session.cpp65
-rw-r--r--intern/cycles/render/session.h17
-rw-r--r--intern/cycles/render/tile.cpp130
-rw-r--r--intern/cycles/render/tile.h40
18 files changed, 350 insertions, 206 deletions
diff --git a/intern/cycles/blender/addon/__init__.py b/intern/cycles/blender/addon/__init__.py
index 29bb206d3da..1ce25a253f9 100644
--- a/intern/cycles/blender/addon/__init__.py
+++ b/intern/cycles/blender/addon/__init__.py
@@ -84,6 +84,9 @@ class CyclesRender(bpy.types.RenderEngine):
def render(self, depsgraph):
engine.render(self, depsgraph)
+ def render_frame_finish(self):
+ engine.render_frame_finish(self)
+
def draw(self, context, depsgraph):
engine.draw(self, depsgraph, context.space_data)
diff --git a/intern/cycles/blender/addon/engine.py b/intern/cycles/blender/addon/engine.py
index d7ae8159734..097bd32d034 100644
--- a/intern/cycles/blender/addon/engine.py
+++ b/intern/cycles/blender/addon/engine.py
@@ -132,6 +132,13 @@ def render(engine, depsgraph):
_cycles.render(engine.session, depsgraph.as_pointer())
+def render_frame_finish(engine):
+ if not engine.session:
+ return
+
+ import _cycles
+ _cycles.render_frame_finish(engine.session)
+
def draw(engine, depsgraph, space_image):
if not engine.session:
return
diff --git a/intern/cycles/blender/blender_python.cpp b/intern/cycles/blender/blender_python.cpp
index 996c6e362a8..7594c69c5a2 100644
--- a/intern/cycles/blender/blender_python.cpp
+++ b/intern/cycles/blender/blender_python.cpp
@@ -264,6 +264,26 @@ static PyObject *render_func(PyObject * /*self*/, PyObject *args)
Py_RETURN_NONE;
}
+static PyObject *render_frame_finish_func(PyObject * /*self*/, PyObject *args)
+{
+ PyObject *pysession;
+
+ if (!PyArg_ParseTuple(args, "O", &pysession)) {
+ return nullptr;
+ }
+
+ BlenderSession *session = (BlenderSession *)PyLong_AsVoidPtr(pysession);
+
+ /* Allow Blender to execute other Python scripts. */
+ python_thread_state_save(&session->python_thread_state);
+
+ session->render_frame_finish();
+
+ python_thread_state_restore(&session->python_thread_state);
+
+ Py_RETURN_NONE;
+}
+
static PyObject *draw_func(PyObject * /*self*/, PyObject *args)
{
PyObject *py_session, *py_graph, *py_screen, *py_space_image;
@@ -1021,6 +1041,7 @@ static PyMethodDef methods[] = {
{"create", create_func, METH_VARARGS, ""},
{"free", free_func, METH_O, ""},
{"render", render_func, METH_VARARGS, ""},
+ {"render_frame_finish", render_frame_finish_func, METH_VARARGS, ""},
{"draw", draw_func, METH_VARARGS, ""},
{"bake", bake_func, METH_VARARGS, ""},
{"view_draw", view_draw_func, METH_VARARGS, ""},
diff --git a/intern/cycles/blender/blender_session.cpp b/intern/cycles/blender/blender_session.cpp
index ea365617590..6fd731555fd 100644
--- a/intern/cycles/blender/blender_session.cpp
+++ b/intern/cycles/blender/blender_session.cpp
@@ -38,6 +38,7 @@
#include "util/util_hash.h"
#include "util/util_logging.h"
#include "util/util_murmurhash.h"
+#include "util/util_path.h"
#include "util/util_progress.h"
#include "util/util_time.h"
@@ -269,10 +270,15 @@ void BlenderSession::reset_session(BL::BlendData &b_data, BL::Depsgraph &b_depsg
void BlenderSession::free_session()
{
- session->cancel(true);
+ if (session) {
+ session->cancel(true);
+ }
delete sync;
+ sync = nullptr;
+
delete session;
+ session = nullptr;
}
void BlenderSession::read_render_tile()
@@ -316,13 +322,16 @@ void BlenderSession::write_render_tile()
const int2 tile_offset = session->get_render_tile_offset();
const int2 tile_size = session->get_render_tile_size();
+ const string_view render_layer_name = session->get_render_tile_layer();
+ const string_view render_view_name = session->get_render_tile_view();
+
/* get render result */
BL::RenderResult b_rr = b_engine.begin_result(tile_offset.x,
tile_offset.y,
tile_size.x,
tile_size.y,
- b_rlay_name.c_str(),
- b_rview_name.c_str());
+ render_layer_name.c_str(),
+ render_view_name.c_str());
/* can happen if the intersected rectangle gives 0 width or height */
if (b_rr.ptr.data == NULL) {
@@ -333,8 +342,9 @@ void BlenderSession::write_render_tile()
b_rr.layers.begin(b_single_rlay);
/* layer will be missing if it was disabled in the UI */
- if (b_single_rlay == b_rr.layers.end())
+ if (b_single_rlay == b_rr.layers.end()) {
return;
+ }
BL::RenderLayer b_rlay = *b_single_rlay;
@@ -343,6 +353,11 @@ void BlenderSession::write_render_tile()
b_engine.end_result(b_rr, true, false, true);
}
+void BlenderSession::full_buffer_written(string_view filename)
+{
+ full_buffer_files_.emplace_back(filename);
+}
+
static void add_cryptomatte_layer(BL::RenderResult &b_rr, string name, string manifest)
{
string identifier = string_printf("%08x", util_murmur_hash3(name.c_str(), name.length(), 0));
@@ -421,6 +436,8 @@ void BlenderSession::render(BL::Depsgraph &b_depsgraph_)
session->update_render_tile_cb = [&]() { write_render_tile(); };
}
+ session->full_buffer_written_cb = [&](string_view filename) { full_buffer_written(filename); };
+
BL::ViewLayer b_view_layer = b_depsgraph.view_layer_eval();
/* get buffer parameters */
@@ -458,6 +475,9 @@ void BlenderSession::render(BL::Depsgraph &b_depsgraph_)
++b_view_iter, ++view_index) {
b_rview_name = b_view_iter->name();
+ buffer_params.layer = b_view_layer.name();
+ buffer_params.view = b_rview_name;
+
/* set the current view */
b_engine.active_view_set(b_rview_name.c_str());
@@ -529,10 +549,28 @@ void BlenderSession::render(BL::Depsgraph &b_depsgraph_)
session->progress.get_time(total_time, render_time);
VLOG(1) << "Total render time: " << total_time;
VLOG(1) << "Render time (without synchronization): " << render_time;
+}
+
+void BlenderSession::render_frame_finish()
+{
+ if (!b_render.use_persistent_data()) {
+ /* Free the sync object so that it can properly dereference nodes from the scene graph before
+ * the graph is freed. */
+ delete sync;
+ sync = nullptr;
+
+ session->device_free();
+ }
+
+ for (string_view filename : full_buffer_files_) {
+ session->process_full_buffer_from_disk(filename);
+ path_remove(filename);
+ }
/* clear callback */
session->write_render_tile_cb = function_null;
session->update_render_tile_cb = function_null;
+ session->full_buffer_written_cb = function_null;
}
static PassType bake_type_to_pass(const string &bake_type_str, const int bake_filter)
diff --git a/intern/cycles/blender/blender_session.h b/intern/cycles/blender/blender_session.h
index e0f61f03ad0..dce313a0ad4 100644
--- a/intern/cycles/blender/blender_session.h
+++ b/intern/cycles/blender/blender_session.h
@@ -60,6 +60,8 @@ class BlenderSession {
/* offline render */
void render(BL::Depsgraph &b_depsgraph);
+ void render_frame_finish();
+
void bake(BL::Depsgraph &b_depsgrah,
BL::Object &b_object,
const string &pass_type,
@@ -70,6 +72,8 @@ class BlenderSession {
void write_render_result(BL::RenderLayer &b_rlay);
void write_render_tile();
+ void full_buffer_written(string_view filename);
+
/* update functions are used to update display buffer only after sample was rendered
* only needed for better visual feedback */
void update_render_result(BL::RenderLayer &b_rlay);
@@ -167,6 +171,8 @@ class BlenderSession {
thread_mutex mutex;
int last_pass_index = -1;
} draw_state_;
+
+ vector<string> full_buffer_files_;
};
CCL_NAMESPACE_END
diff --git a/intern/cycles/integrator/pass_accessor.cpp b/intern/cycles/integrator/pass_accessor.cpp
index 5c91d5651bf..87c048b1fa5 100644
--- a/intern/cycles/integrator/pass_accessor.cpp
+++ b/intern/cycles/integrator/pass_accessor.cpp
@@ -16,9 +16,7 @@
#include "integrator/pass_accessor.h"
-#include "render/background.h"
#include "render/buffers.h"
-#include "render/film.h"
#include "util/util_logging.h"
// clang-format off
@@ -32,16 +30,8 @@ CCL_NAMESPACE_BEGIN
* Pass input information.
*/
-PassAccessor::PassAccessInfo::PassAccessInfo(const BufferPass &pass,
- const Film &film,
- const Background &background)
- : type(pass.type),
- mode(pass.mode),
- include_albedo(pass.include_albedo),
- offset(pass.offset),
- use_approximate_shadow_catcher(film.get_use_approximate_shadow_catcher()),
- use_approximate_shadow_catcher_background(use_approximate_shadow_catcher &&
- !background.get_transparent())
+PassAccessor::PassAccessInfo::PassAccessInfo(const BufferPass &pass)
+ : type(pass.type), mode(pass.mode), include_albedo(pass.include_albedo), offset(pass.offset)
{
}
diff --git a/intern/cycles/integrator/pass_accessor.h b/intern/cycles/integrator/pass_accessor.h
index 0298b0f40d9..624bf7d0b2c 100644
--- a/intern/cycles/integrator/pass_accessor.h
+++ b/intern/cycles/integrator/pass_accessor.h
@@ -23,8 +23,6 @@
CCL_NAMESPACE_BEGIN
-class Background;
-class Film;
class RenderBuffers;
class BufferPass;
class BufferParams;
@@ -38,7 +36,7 @@ class PassAccessor {
class PassAccessInfo {
public:
PassAccessInfo() = default;
- PassAccessInfo(const BufferPass &pass, const Film &film, const Background &background);
+ explicit PassAccessInfo(const BufferPass &pass);
PassType type = PASS_NONE;
PassMode mode = PassMode::NOISY;
diff --git a/intern/cycles/integrator/path_trace.cpp b/intern/cycles/integrator/path_trace.cpp
index c69921c8394..b5ab7768c4f 100644
--- a/intern/cycles/integrator/path_trace.cpp
+++ b/intern/cycles/integrator/path_trace.cpp
@@ -134,7 +134,18 @@ void PathTrace::reset(const BufferParams &full_params, const BufferParams &big_t
render_state_.tile_written = false;
did_draw_after_reset_ = false;
- full_frame_buffers_ = nullptr;
+}
+
+void PathTrace::device_free()
+{
+ /* Free render buffers used by the path trace work to reduce memory peak. */
+ BufferParams empty_params;
+ empty_params.pass_stride = 0;
+ empty_params.update_offset_stride();
+ for (auto &&path_trace_work : path_trace_works_) {
+ path_trace_work->get_render_buffers()->reset(empty_params);
+ }
+ render_state_.need_reset_params = true;
}
void PathTrace::set_progress(Progress *progress)
@@ -205,7 +216,7 @@ void PathTrace::render_pipeline(RenderWork render_work)
progress_update_if_needed(render_work);
- process_full_buffer_from_disk(render_work);
+ finalize_full_buffer_on_disk(render_work);
}
void PathTrace::render_init_kernel_execution()
@@ -670,7 +681,7 @@ void PathTrace::write_tile_buffer(const RenderWork &render_work)
}
}
-void PathTrace::process_full_buffer_from_disk(const RenderWork &render_work)
+void PathTrace::finalize_full_buffer_on_disk(const RenderWork &render_work)
{
if (!render_work.full.write) {
return;
@@ -683,41 +694,13 @@ void PathTrace::process_full_buffer_from_disk(const RenderWork &render_work)
return;
}
- /* Free render buffers used by the path trace work to reduce memory peak. */
- BufferParams empty_params;
- empty_params.pass_stride = 0;
- empty_params.update_offset_stride();
- for (auto &&path_trace_work : path_trace_works_) {
- path_trace_work->get_render_buffers()->reset(empty_params);
- }
- render_state_.need_reset_params = true;
-
- /* TODO(sergey): Somehow free up session memory before readsing full frame. */
-
- read_full_buffer_from_disk();
-
- if (render_work.full.denoise) {
- /* File is either scaled up to the final number of samples (when tile is cancelled) or
- * contains samples count pass. In the former case use final number of samples for the
- * denoising, and for the latter one the denoiser will sue sample count pass. */
- const int num_samples = render_scheduler_.get_num_samples();
- denoiser_->denoise_buffer(
- full_frame_buffers_->params, full_frame_buffers_.get(), num_samples, false);
-
- /* TODO(sergey): Report full-frame denoising time. It is different from the tile-based
- * denoising since it wouldn't be fair to use it for average values. */
- }
-
- /* Write the full result pretending that there is a single tile.
- * Requires some state change, but allows to use same communication API with the software. */
-
- tile_buffer_write();
-
- /* Full frame is no longer needed, free it to save up memory. */
- full_frame_buffers_ = nullptr;
+ /* Make sure writing to the file is fully finished.
+ * This will include writing all possible missing tiles, ensuring validness of the file. */
+ tile_manager_.finish_write_tiles();
- /* TODO(sergey): Only remove file if it is in the temporary directory. */
- tile_manager_.remove_tile_file();
+ /* NOTE: The rest of full-frame post-processing (such as full-frame denoising) will be done after
+ * all scenes and layers are rendered by the Session (which happens after freeing Session memory,
+ * so that we never hold scene and full-frame buffer in memory at the same time). */
}
void PathTrace::cancel()
@@ -807,21 +790,6 @@ void PathTrace::tile_buffer_write_to_disk()
}
}
-void PathTrace::read_full_buffer_from_disk()
-{
- VLOG(3) << "Reading full frame render buffer from file.";
-
- /* Make sure writing to the file is fully finished.
- * This will include writing all possible missing tiles, ensuring validness of the file. */
- tile_manager_.finish_write_tiles();
-
- full_frame_buffers_ = make_unique<RenderBuffers>(cpu_device_.get());
-
- if (!tile_manager_.read_full_buffer_from_disk(full_frame_buffers_.get())) {
- LOG(ERROR) << "Error reading tiles from file.";
- }
-}
-
void PathTrace::progress_update_if_needed(const RenderWork &render_work)
{
if (progress_ != nullptr) {
@@ -857,8 +825,8 @@ void PathTrace::copy_from_render_buffers(RenderBuffers *render_buffers)
bool PathTrace::copy_render_tile_from_device()
{
- if (full_frame_buffers_) {
- /* Full frame buffer is always on the host side. */
+ if (full_frame_state_.render_buffers) {
+ /* Full-frame buffer is always allocated on CPU. */
return true;
}
@@ -876,12 +844,51 @@ bool PathTrace::copy_render_tile_from_device()
return success;
}
+void PathTrace::process_full_buffer_from_disk(string_view filename)
+{
+ VLOG(3) << "Processing full frame buffer file " << filename;
+
+ RenderBuffers full_frame_buffers(cpu_device_.get());
+
+ DenoiseParams denoise_params;
+ if (!tile_manager_.read_full_buffer_from_disk(filename, &full_frame_buffers, &denoise_params)) {
+ LOG(ERROR) << "Error reading tiles from file.";
+ return;
+ }
+
+ render_state_.has_denoised_result = false;
+
+ if (denoise_params.use) {
+ /* Re-use the denoiser as much as possible, avoiding possible device re-initialization.
+ *
+ * It will not conflict with the regular rendering as:
+ * - Rendering is supposed to be finished here.
+ * - The next rendering will go via Session's `run_update_for_next_iteration` which will
+ * ensure proper denoiser is used. */
+ set_denoiser_params(denoise_params);
+
+ /* Number of samples doesn't matter too much, since the sampels count pass will be used. */
+ denoiser_->denoise_buffer(full_frame_buffers.params, &full_frame_buffers, 0, false);
+
+ render_state_.has_denoised_result = true;
+ }
+
+ full_frame_state_.render_buffers = &full_frame_buffers;
+
+ /* Write the full result pretending that there is a single tile.
+ * Requires some state change, but allows to use same communication API with the software. */
+ tile_buffer_write();
+
+ full_frame_state_.render_buffers = nullptr;
+}
+
int PathTrace::get_num_render_tile_samples() const
{
- if (full_frame_buffers_) {
- /* When full frame result is read from fisk it has all tiles scaled up to the final number of
- * samples. */
- return render_scheduler_.get_num_samples();
+ if (full_frame_state_.render_buffers) {
+ /* If the full-frame buffer is read from disk the number of samples is not used as there is a
+ * sample count pass for that in the buffer. Just avoid access to badly defined state of the
+ * path state. */
+ return 0;
}
return render_scheduler_.get_num_rendered_samples();
@@ -890,8 +897,8 @@ int PathTrace::get_num_render_tile_samples() const
bool PathTrace::get_render_tile_pixels(const PassAccessor &pass_accessor,
const PassAccessor::Destination &destination)
{
- if (full_frame_buffers_) {
- return pass_accessor.get_render_tile_pixels(full_frame_buffers_.get(), destination);
+ if (full_frame_state_.render_buffers) {
+ return pass_accessor.get_render_tile_pixels(full_frame_state_.render_buffers, destination);
}
bool success = true;
@@ -927,8 +934,9 @@ bool PathTrace::set_render_tile_pixels(PassAccessor &pass_accessor,
int2 PathTrace::get_render_tile_size() const
{
- if (full_frame_buffers_) {
- return make_int2(full_frame_buffers_->params.width, full_frame_buffers_->params.height);
+ if (full_frame_state_.render_buffers) {
+ return make_int2(full_frame_state_.render_buffers->params.width,
+ full_frame_state_.render_buffers->params.height);
}
const Tile &tile = tile_manager_.get_current_tile();
@@ -937,21 +945,21 @@ int2 PathTrace::get_render_tile_size() const
int2 PathTrace::get_render_tile_offset() const
{
- if (full_frame_buffers_) {
- return make_int2(full_frame_buffers_->params.full_x, full_frame_buffers_->params.full_y);
+ if (full_frame_state_.render_buffers) {
+ return make_int2(0, 0);
}
const Tile &tile = tile_manager_.get_current_tile();
return make_int2(tile.x, tile.y);
}
-bool PathTrace::get_render_tile_done() const
+const BufferParams &PathTrace::get_render_tile_params() const
{
- if (full_frame_buffers_) {
- return true;
+ if (full_frame_state_.render_buffers) {
+ return full_frame_state_.render_buffers->params;
}
- return render_state_.tile_written;
+ return big_tile_params_;
}
bool PathTrace::has_denoised_result() const
diff --git a/intern/cycles/integrator/path_trace.h b/intern/cycles/integrator/path_trace.h
index ecc5204f2ec..8e40c72211a 100644
--- a/intern/cycles/integrator/path_trace.h
+++ b/intern/cycles/integrator/path_trace.h
@@ -72,6 +72,8 @@ class PathTrace {
void reset(const BufferParams &full_params, const BufferParams &big_tile_params);
+ void device_free();
+
/* Set progress tracker.
* Used to communicate details about the progress to the outer world, check whether rendering is
* to be canceled.
@@ -126,6 +128,10 @@ class PathTrace {
* Return true if all copies are successful. */
bool copy_render_tile_from_device();
+ /* Read given full-frame file from disk, perform needed processing and write it to the software
+ * via the write callback. */
+ void process_full_buffer_from_disk(string_view filename);
+
/* Get number of samples in the current big tile render buffers. */
int get_num_render_tile_samples() const;
@@ -146,11 +152,17 @@ class PathTrace {
/* Get size and offset (relative to the buffer's full x/y) of the currently rendering tile.
* In the case of tiled rendering this will return full-frame after all tiles has been rendered.
- */
+ *
+ * NOTE: If the full-frame buffer processing is in progress, returns parameters of the full-frame
+ * instead. */
int2 get_render_tile_size() const;
int2 get_render_tile_offset() const;
- bool get_render_tile_done() const;
+ /* Get buffer parameters of the current tile.
+ *
+ * NOTE: If the full-frame buffer processing is in progress, returns parameters of the full-frame
+ * instead. */
+ const BufferParams &get_render_tile_params() const;
/* Generate full multi-line report of the rendering process, including rendering parameters,
* times, and so on. */
@@ -205,7 +217,7 @@ class PathTrace {
void update_display(const RenderWork &render_work);
void rebalance(const RenderWork &render_work);
void write_tile_buffer(const RenderWork &render_work);
- void process_full_buffer_from_disk(const RenderWork &render_work);
+ void finalize_full_buffer_on_disk(const RenderWork &render_work);
/* Get number of samples in the current state of the render buffers. */
int get_num_samples_in_buffer();
@@ -223,9 +235,6 @@ class PathTrace {
/* Write current tile into the file on disk. */
void tile_buffer_write_to_disk();
- /* Read full-frame render result from a file on disk. */
- void read_full_buffer_from_disk();
-
/* Run the progress_update_cb callback if it is needed. */
void progress_update_if_needed(const RenderWork &render_work);
@@ -304,7 +313,10 @@ class PathTrace {
* Used by `ready_to_reset()` to implement logic which feels the most interactive. */
bool did_draw_after_reset_ = true;
- unique_ptr<RenderBuffers> full_frame_buffers_;
+ /* State of the full frame processing and writing to the software. */
+ struct {
+ RenderBuffers *render_buffers = nullptr;
+ } full_frame_state_;
};
CCL_NAMESPACE_END
diff --git a/intern/cycles/integrator/path_trace_work.cpp b/intern/cycles/integrator/path_trace_work.cpp
index 98ea541207b..d9634acac10 100644
--- a/intern/cycles/integrator/path_trace_work.cpp
+++ b/intern/cycles/integrator/path_trace_work.cpp
@@ -182,8 +182,6 @@ PassAccessor::PassAccessInfo PathTraceWork::get_display_pass_access_info(PassMod
pass_access_info.use_approximate_shadow_catcher_background =
kfilm.use_approximate_shadow_catcher && !kbackground.transparent;
- pass_access_info.show_active_pixels = film_->get_show_active_pixels();
-
return pass_access_info;
}
diff --git a/intern/cycles/integrator/render_scheduler.cpp b/intern/cycles/integrator/render_scheduler.cpp
index 7a842f2e4cd..50017daca38 100644
--- a/intern/cycles/integrator/render_scheduler.cpp
+++ b/intern/cycles/integrator/render_scheduler.cpp
@@ -420,10 +420,6 @@ void RenderScheduler::set_full_frame_render_work(RenderWork *render_work)
state_.full_frame_work_scheduled = true;
render_work->full.write = true;
-
- if (denoiser_params_.use) {
- render_work->full.denoise = true;
- }
}
/* Knowing time which it took to complete a task at the current resolution divider approximate how
diff --git a/intern/cycles/integrator/render_scheduler.h b/intern/cycles/integrator/render_scheduler.h
index 9c3b6888f83..10fbcc52cd6 100644
--- a/intern/cycles/integrator/render_scheduler.h
+++ b/intern/cycles/integrator/render_scheduler.h
@@ -71,8 +71,6 @@ class RenderWork {
/* Write full render result.
* Implies reading the partial file from disk. */
bool write = false;
-
- bool denoise = false;
} full;
/* Display which is used to visualize render result. */
@@ -94,7 +92,7 @@ class RenderWork {
inline operator bool() const
{
return path_trace.num_samples || adaptive_sampling.filter || display.update || tile.denoise ||
- tile.write || full.write || full.denoise;
+ tile.write || full.write;
}
};
diff --git a/intern/cycles/render/buffers.cpp b/intern/cycles/render/buffers.cpp
index 378b85f0d62..1cdae3af7f5 100644
--- a/intern/cycles/render/buffers.cpp
+++ b/intern/cycles/render/buffers.cpp
@@ -103,6 +103,12 @@ NODE_DEFINE(BufferParams)
SOCKET_INT(full_width, "Full Width", 0);
SOCKET_INT(full_height, "Full Height", 0);
+ SOCKET_STRING(layer, "Layer", ustring());
+ SOCKET_STRING(view, "View", ustring());
+ SOCKET_FLOAT(exposure, "Exposure", 1.0f);
+ SOCKET_BOOLEAN(use_approximate_shadow_catcher, "Use Approximate Shadow Catcher", false);
+ SOCKET_BOOLEAN(use_transparent_background, "Transparent Background", false);
+
/* Notes:
* - Skip passes since they do not follow typical container socket definition.
* Might look into covering those as a socket in the future.
@@ -230,11 +236,14 @@ bool BufferParams::modified(const BufferParams &other) const
if (!(width == other.width && height == other.height && full_x == other.full_x &&
full_y == other.full_y && full_width == other.full_width &&
full_height == other.full_height && offset == other.offset && stride == other.stride &&
- pass_stride == other.pass_stride)) {
+ pass_stride == other.pass_stride && layer == other.layer && view == other.view &&
+ exposure == other.exposure &&
+ use_approximate_shadow_catcher == other.use_approximate_shadow_catcher &&
+ use_transparent_background == other.use_transparent_background)) {
return true;
}
- return passes != other.passes;
+ return !(passes == other.passes);
}
/* --------------------------------------------------------------------
diff --git a/intern/cycles/render/buffers.h b/intern/cycles/render/buffers.h
index 0bf149eb0ec..c048234167d 100644
--- a/intern/cycles/render/buffers.h
+++ b/intern/cycles/render/buffers.h
@@ -78,11 +78,11 @@ class BufferParams : public Node {
public:
NODE_DECLARE
- /* width/height of the physical buffer */
+ /* Width/height of the physical buffer. */
int width = 0;
int height = 0;
- /* offset into and width/height of the full buffer */
+ /* Offset into and width/height of the full buffer. */
int full_x = 0;
int full_y = 0;
int full_width = 0;
@@ -94,7 +94,13 @@ class BufferParams : public Node {
/* Runtime fields, only valid after `update_passes()`. */
int pass_stride = -1;
+ /* Properties which are used for accessing buffer pixels outside of scene graph. */
vector<BufferPass> passes;
+ ustring layer;
+ ustring view;
+ float exposure = 1.0f;
+ bool use_approximate_shadow_catcher = false;
+ bool use_transparent_background = false;
BufferParams();
diff --git a/intern/cycles/render/session.cpp b/intern/cycles/render/session.cpp
index 643e639149e..2a4f0d405f3 100644
--- a/intern/cycles/render/session.cpp
+++ b/intern/cycles/render/session.cpp
@@ -17,9 +17,11 @@
#include <limits.h>
#include <string.h>
+#include "device/cpu/device.h"
#include "device/device.h"
#include "integrator/pass_accessor_cpu.h"
#include "integrator/path_trace.h"
+#include "render/background.h"
#include "render/bake.h"
#include "render/buffers.h"
#include "render/camera.h"
@@ -83,6 +85,13 @@ Session::Session(const SessionParams &params_, const SceneParams &scene_params)
return true;
};
path_trace_->progress_update_cb = [&]() { update_status_time(); };
+
+ tile_manager_.full_buffer_written_cb = [&](string_view filename) {
+ if (!full_buffer_written_cb) {
+ return;
+ }
+ full_buffer_written_cb(filename);
+ };
}
Session::~Session()
@@ -396,8 +405,14 @@ void Session::do_delayed_reset()
params = delayed_reset_.session_params;
buffer_params_ = delayed_reset_.buffer_params;
+ /* Store parameters used for buffers access outside of scene graph. */
+ buffer_params_.exposure = scene->film->get_exposure();
+ buffer_params_.use_approximate_shadow_catcher =
+ scene->film->get_use_approximate_shadow_catcher();
+ buffer_params_.use_transparent_background = scene->background->get_transparent();
+
/* Tile and work scheduling. */
- tile_manager_.reset(buffer_params_, get_effective_tile_size());
+ tile_manager_.reset_scheduling(buffer_params_, get_effective_tile_size());
render_scheduler_.reset(buffer_params_, params.samples);
/* Passes. */
@@ -407,9 +422,9 @@ void Session::do_delayed_reset()
* changes. */
scene->film->update_passes(scene, tile_manager_.has_multiple_tiles());
- /* Update for new state of passes. */
+ /* Update for new state of scene and passes. */
buffer_params_.update_passes(scene->passes);
- tile_manager_.update_passes(buffer_params_);
+ tile_manager_.update(buffer_params_, scene);
/* Progress. */
progress.reset_sample();
@@ -584,10 +599,7 @@ void Session::update_status_time(bool show_pause, bool show_done)
void Session::device_free()
{
scene->device_free();
-
- /* used from background render only, so no need to
- * re-create render/display buffers here
- */
+ path_trace_->device_free();
}
void Session::collect_statistics(RenderStats *render_stats)
@@ -612,14 +624,16 @@ int2 Session::get_render_tile_offset() const
return path_trace_->get_render_tile_offset();
}
-bool Session::get_render_tile_done() const
+string_view Session::get_render_tile_layer() const
{
- return path_trace_->get_render_tile_done();
+ const BufferParams &buffer_params = path_trace_->get_render_tile_params();
+ return buffer_params.layer;
}
-bool Session::has_multiple_render_tiles() const
+string_view Session::get_render_tile_view() const
{
- return tile_manager_.has_multiple_tiles();
+ const BufferParams &buffer_params = path_trace_->get_render_tile_params();
+ return buffer_params.view;
}
bool Session::copy_render_tile_from_device()
@@ -632,28 +646,32 @@ bool Session::get_render_tile_pixels(const string &pass_name, int num_components
/* NOTE: The code relies on a fact that session is fully update and no scene/buffer modification
* is happenning while this function runs. */
- const BufferPass *pass = buffer_params_.find_pass(pass_name);
+ const BufferParams &buffer_params = path_trace_->get_render_tile_params();
+
+ const BufferPass *pass = buffer_params.find_pass(pass_name);
if (pass == nullptr) {
return false;
}
- /* TODO(sergey): Avoid access to path trace here to allow accessing pixels from render buffers
- * after the rendering is done (for the "Save Buffers" memory optimization implementation). */
const bool has_denoised_result = path_trace_->has_denoised_result();
if (pass->mode == PassMode::DENOISED && !has_denoised_result) {
- pass = buffer_params_.find_pass(pass->type);
+ pass = buffer_params.find_pass(pass->type);
if (pass == nullptr) {
/* Happens when denoised result pass is requested but is never written by the kernel. */
return false;
}
}
- pass = buffer_params_.get_actual_display_pass(pass);
+ pass = buffer_params.get_actual_display_pass(pass);
- const float exposure = scene->film->get_exposure();
+ const float exposure = buffer_params.exposure;
const int num_samples = path_trace_->get_num_render_tile_samples();
- const PassAccessor::PassAccessInfo pass_access_info(*pass, *scene->film, *scene->background);
+ PassAccessor::PassAccessInfo pass_access_info(*pass);
+ pass_access_info.use_approximate_shadow_catcher = buffer_params.use_approximate_shadow_catcher;
+ pass_access_info.use_approximate_shadow_catcher_background =
+ pass_access_info.use_approximate_shadow_catcher && !buffer_params.use_transparent_background;
+
const PassAccessorCPU pass_accessor(pass_access_info, exposure, num_samples);
const PassAccessor::Destination destination(pixels, num_components);
@@ -675,11 +693,20 @@ bool Session::set_render_tile_pixels(const string &pass_name,
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);
+ const PassAccessor::PassAccessInfo pass_access_info(*pass);
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);
}
+/* --------------------------------------------------------------------
+ * Full-frame on-disk storage.
+ */
+
+void Session::process_full_buffer_from_disk(string_view filename)
+{
+ path_trace_->process_full_buffer_from_disk(filename);
+}
+
CCL_NAMESPACE_END
diff --git a/intern/cycles/render/session.h b/intern/cycles/render/session.h
index fe3303fb8a3..8ea8cb67666 100644
--- a/intern/cycles/render/session.h
+++ b/intern/cycles/render/session.h
@@ -118,6 +118,11 @@ class Session {
function<void(void)> update_render_tile_cb;
function<void(void)> read_render_tile_cb;
+ /* Callback is invoked by tile manager whenever on-dist tiles storage file is closed after
+ * writing. Allows an engine integration to keep track of those files without worry about
+ * transfering the information when it needs to re-create session during rendering. */
+ function<void(string_view)> full_buffer_written_cb;
+
explicit Session(const SessionParams &params, const SceneParams &scene_params);
~Session();
@@ -156,14 +161,22 @@ class Session {
int2 get_render_tile_size() const;
int2 get_render_tile_offset() const;
- bool get_render_tile_done() const;
- bool has_multiple_render_tiles() const;
+ string_view get_render_tile_layer() const;
+ string_view get_render_tile_view() const;
bool copy_render_tile_from_device();
bool get_render_tile_pixels(const string &pass_name, int num_components, float *pixels);
bool set_render_tile_pixels(const string &pass_name, int num_components, const float *pixels);
+ /* --------------------------------------------------------------------
+ * Full-frame on-disk storage.
+ */
+
+ /* Read given full-frame file from disk, perform needed processing and write it to the software
+ * via the write callback. */
+ void process_full_buffer_from_disk(string_view filename);
+
protected:
struct DelayedReset {
thread_mutex mutex;
diff --git a/intern/cycles/render/tile.cpp b/intern/cycles/render/tile.cpp
index 1cae4fe8907..eed75cc2372 100644
--- a/intern/cycles/render/tile.cpp
+++ b/intern/cycles/render/tile.cpp
@@ -19,7 +19,10 @@
#include <atomic>
#include "graph/node.h"
-#include "render/pass.h"
+#include "render/background.h"
+#include "render/film.h"
+#include "render/integrator.h"
+#include "render/scene.h"
#include "util/util_algorithm.h"
#include "util/util_foreach.h"
#include "util/util_logging.h"
@@ -30,10 +33,14 @@
CCL_NAMESPACE_BEGIN
-static const char *ATTR_PASSES_COUNT = "cycles.passes.count";
+/* --------------------------------------------------------------------
+ * Internal functions.
+ */
+static const char *ATTR_PASSES_COUNT = "cycles.passes.count";
static const char *ATTR_PASS_SOCKET_PREFIX_FORMAT = "cycles.passes.%d.";
static const char *ATTR_BUFFER_SOCKET_PREFIX = "cycles.buffer.";
+static const char *ATTR_DENOISE_SOCKET_PREFIX = "cycles.denoise.";
/* Global counter of ToleManager object instances. */
static std::atomic<uint64_t> g_instance_index = 0;
@@ -130,6 +137,10 @@ static bool node_socket_to_image_spec_atttributes(ImageSpec *image_spec,
image_spec->attribute(attr_name, node->get_int(socket));
return true;
+ case SocketType::FLOAT:
+ image_spec->attribute(attr_name, node->get_float(socket));
+ return true;
+
case SocketType::BOOLEAN:
image_spec->attribute(attr_name, node->get_bool(socket));
return true;
@@ -173,6 +184,10 @@ static bool node_socket_from_image_spec_atttributes(Node *node,
node->set(socket, image_spec.get_int_attribute(attr_name, 0));
return true;
+ case SocketType::FLOAT:
+ node->set(socket, image_spec.get_float_attribute(attr_name, 0));
+ return true;
+
case SocketType::BOOLEAN:
node->set(socket, static_cast<bool>(image_spec.get_int_attribute(attr_name, 0)));
return true;
@@ -299,28 +314,28 @@ static bool configure_image_spec_from_buffer(ImageSpec *image_spec,
return true;
}
+/* --------------------------------------------------------------------
+ * Tile Manager.
+ */
+
TileManager::TileManager()
{
- /* Append an unique part to the file name, so that if the temp directory is not set to be a
- * process-specific there is no conflit between different Cycles process instances.
- *
- * Use process ID to separate different processes.
+ /* Use process ID to separate different processes.
* To ensure uniqueness from within a process use combination of object address and instance
* index. This solves problem of possible object re-allocation at the same time, and solves
* possible conflict when the counter overflows while there are still active instances of the
* class. */
const int tile_manager_id = g_instance_index.fetch_add(1, std::memory_order_relaxed);
- const string unique_part = to_string(system_self_process_id()) + "-" +
- to_string(reinterpret_cast<uintptr_t>(this)) + "-" +
- to_string(tile_manager_id);
- tile_filepath_ = path_temp_get("cycles-tile-buffer-" + unique_part + ".exr");
+ tile_file_unique_part_ = to_string(system_self_process_id()) + "-" +
+ to_string(reinterpret_cast<uintptr_t>(this)) + "-" +
+ to_string(tile_manager_id);
}
TileManager::~TileManager()
{
}
-void TileManager::reset(const BufferParams &params, int2 tile_size)
+void TileManager::reset_scheduling(const BufferParams &params, int2 tile_size)
{
VLOG(3) << "Using tile size of " << tile_size;
@@ -337,7 +352,7 @@ void TileManager::reset(const BufferParams &params, int2 tile_size)
tile_state_.current_tile = Tile();
}
-void TileManager::update_passes(const BufferParams &params)
+void TileManager::update(const BufferParams &params, const Scene *scene)
{
DCHECK_NE(params.pass_stride, -1);
@@ -346,6 +361,10 @@ void TileManager::update_passes(const BufferParams &params)
/* TODO(sergey): Proper Error handling, so that if configuration has failed we dont' attempt to
* write to a partially configured file. */
configure_image_spec_from_buffer(&write_state_.image_spec, buffer_params_, tile_size_);
+
+ const DenoiseParams denoise_params = scene->integrator->get_denoise_params();
+ node_to_image_spec_atttributes(
+ &write_state_.image_spec, &denoise_params, ATTR_DENOISE_SOCKET_PREFIX);
}
bool TileManager::done()
@@ -394,9 +413,12 @@ const Tile &TileManager::get_current_tile() const
bool TileManager::open_tile_output()
{
- write_state_.tile_out = ImageOutput::create(tile_filepath_);
+ write_state_.filename = path_temp_get("cycles-tile-buffer-" + tile_file_unique_part_ + "-" +
+ to_string(write_state_.tile_file_index) + ".exr");
+
+ write_state_.tile_out = ImageOutput::create(write_state_.filename);
if (!write_state_.tile_out) {
- LOG(ERROR) << "Error creating image output for " << tile_filepath_;
+ LOG(ERROR) << "Error creating image output for " << write_state_.filename;
return false;
}
@@ -405,10 +427,10 @@ bool TileManager::open_tile_output()
return false;
}
- write_state_.tile_out->open(tile_filepath_, write_state_.image_spec);
+ write_state_.tile_out->open(write_state_.filename, write_state_.image_spec);
write_state_.num_tiles_written = 0;
- VLOG(3) << "Opened tile file " << tile_filepath_;
+ VLOG(3) << "Opened tile file " << write_state_.filename;
return true;
}
@@ -475,12 +497,6 @@ bool TileManager::write_tile(const RenderBuffers &tile_buffers)
++write_state_.num_tiles_written;
- if (done()) {
- if (!close_tile_output()) {
- return false;
- }
- }
-
return true;
}
@@ -492,65 +508,56 @@ void TileManager::finish_write_tiles()
return;
}
- vector<float> pixel_storage(tile_size_.x * tile_size_.y * buffer_params_.pass_stride);
+ /* EXR expects all tiles to present in file. So explicitly write missing tiles as all-zero. */
+ if (write_state_.num_tiles_written < tile_state_.num_tiles) {
+ vector<float> pixel_storage(tile_size_.x * tile_size_.y * buffer_params_.pass_stride);
- for (int tile_index = write_state_.num_tiles_written; tile_index < tile_state_.num_tiles;
- ++tile_index) {
- const Tile tile = get_tile_for_index(tile_index);
+ for (int tile_index = write_state_.num_tiles_written; tile_index < tile_state_.num_tiles;
+ ++tile_index) {
+ const Tile tile = get_tile_for_index(tile_index);
- VLOG(3) << "Write dummy tile at " << tile.x << ", " << tile.y;
+ VLOG(3) << "Write dummy tile at " << tile.x << ", " << tile.y;
- write_state_.tile_out->write_tile(tile.x, tile.y, 0, TypeDesc::FLOAT, pixel_storage.data());
+ write_state_.tile_out->write_tile(tile.x, tile.y, 0, TypeDesc::FLOAT, pixel_storage.data());
+ }
}
close_tile_output();
-}
-
-static bool check_image_spec_compatible(const ImageSpec &spec, const ImageSpec &expected_spec)
-{
- if (spec.width != expected_spec.width || spec.height != expected_spec.height ||
- spec.depth != expected_spec.depth) {
- LOG(ERROR) << "Mismatched image dimension.";
- return false;
- }
-
- if (spec.format != expected_spec.format) {
- LOG(ERROR) << "Mismatched image format.";
- }
- if (spec.nchannels != expected_spec.nchannels) {
- LOG(ERROR) << "Mismatched number of channels.";
- return false;
+ if (full_buffer_written_cb) {
+ full_buffer_written_cb(write_state_.filename);
}
- if (spec.channelnames != expected_spec.channelnames) {
- LOG(ERROR) << "Mismatched channel names.";
- return false;
- }
+ /* Advance the counter upon explicit finish of the file.
+ * Makes it possible to re-use tile manager for another scene, and avoids unnecessary increments
+ * of the tile-file-within-session index. */
+ ++write_state_.tile_file_index;
- return true;
+ write_state_.filename = "";
}
-bool TileManager::read_full_buffer_from_disk(RenderBuffers *buffers)
+bool TileManager::read_full_buffer_from_disk(const string_view filename,
+ RenderBuffers *buffers,
+ DenoiseParams *denoise_params)
{
- unique_ptr<ImageInput> in(ImageInput::open(tile_filepath_));
+ unique_ptr<ImageInput> in(ImageInput::open(filename));
if (!in) {
- LOG(ERROR) << "Error opening tile file " << tile_filepath_;
+ LOG(ERROR) << "Error opening tile file " << filename;
return false;
}
- const ImageSpec &spec = in->spec();
- if (!check_image_spec_compatible(spec, write_state_.image_spec)) {
- return false;
- }
+ const ImageSpec &image_spec = in->spec();
BufferParams buffer_params;
- if (!buffer_params_from_image_spec_atttributes(&buffer_params, spec)) {
+ if (!buffer_params_from_image_spec_atttributes(&buffer_params, image_spec)) {
return false;
}
-
buffers->reset(buffer_params);
+ if (!node_from_image_spec_atttributes(denoise_params, image_spec, ATTR_DENOISE_SOCKET_PREFIX)) {
+ return false;
+ }
+
if (!in->read_image(TypeDesc::FLOAT, buffers->buffer.data())) {
LOG(ERROR) << "Error reading pixels from the tile file " << in->geterror();
return false;
@@ -564,11 +571,4 @@ bool TileManager::read_full_buffer_from_disk(RenderBuffers *buffers)
return true;
}
-void TileManager::remove_tile_file() const
-{
- VLOG(3) << "Removing tile file " << tile_filepath_;
-
- path_remove(tile_filepath_);
-}
-
CCL_NAMESPACE_END
diff --git a/intern/cycles/render/tile.h b/intern/cycles/render/tile.h
index c9fc3641e97..124d0b3652c 100644
--- a/intern/cycles/render/tile.h
+++ b/intern/cycles/render/tile.h
@@ -23,6 +23,9 @@
CCL_NAMESPACE_BEGIN
+class DenoiseParams;
+class Scene;
+
/* --------------------------------------------------------------------
* Tile.
*/
@@ -43,6 +46,9 @@ class Tile {
class TileManager {
public:
+ /* This callback is invoked by whenever on-dist tiles storage file is closed after writing. */
+ function<void(string_view)> full_buffer_written_cb;
+
TileManager();
~TileManager();
@@ -52,12 +58,15 @@ class TileManager {
TileManager &operator=(TileManager &&other) = delete;
/* Reset current progress and start new rendering of the full-frame parameters in tiles of the
- * given size. */
+ * given size.
+ * Only touches scheduling-related state of the tile manager. */
/* TODO(sergey): Consider using tile area instead of exact size to help dealing with extreme
* cases of stretched renders. */
- void reset(const BufferParams &params, int2 tile_size);
+ void reset_scheduling(const BufferParams &params, int2 tile_size);
- void update_passes(const BufferParams &params);
+ /* Update for the known buffer passes and scene parameters.
+ * Will store all parameters needed for buffers access outside of the scene graph. */
+ void update(const BufferParams &params, const Scene *scene);
inline int get_num_tiles() const
{
@@ -76,8 +85,7 @@ class TileManager {
/* Write render buffer of a tile to a file on disk.
*
- * Opens file for write when first tile is written, and closes the file when the last tile has
- * been written.
+ * Opens file for write when first tile is written.
*
* Returns true on success. */
bool write_tile(const RenderBuffers &tile_buffers);
@@ -94,13 +102,10 @@ class TileManager {
/* Read full frame render buffer from tiles file on disk.
*
- * The render buffer is configured according to the metadata in the file.
- *
* Returns true on success. */
- bool read_full_buffer_from_disk(RenderBuffers *buffers);
-
- /* Remove file from disk which holds tiles results. */
- void remove_tile_file() const;
+ bool read_full_buffer_from_disk(string_view filename,
+ RenderBuffers *buffers,
+ DenoiseParams *denoise_params);
protected:
/* Get tile configuration for its index.
@@ -110,8 +115,9 @@ class TileManager {
bool open_tile_output();
bool close_tile_output();
- /* Full file name of a file which holds tile results on disk. */
- string tile_filepath_;
+ /* Part of an on-disk tile file name which avoids conflicts between several Cycles instances or
+ * several sessions. */
+ string tile_file_unique_part_;
int2 tile_size_ = make_int2(0, 0);
@@ -130,6 +136,14 @@ class TileManager {
/* State of tiles writing to a file on disk. */
struct {
+ /* Index of a tile file used during the current session.
+ * This number is used for the file name construction, making it possible to render several
+ * scenes throughout duration of the session and keep all results available for later read
+ * access. */
+ int tile_file_index = 0;
+
+ string filename;
+
/* Specification of the tile image which corresponds to the buffer parameters.
* Contains channels configured according to the passes configuration in the path traces.
*