Cycles: Resumable render implementation for Cycles

This feature is also known by the name Samples Offset, which allows
artists to render animation with given amount of samples N, but then
render more samples, starting from N and ending with M (where M > N)
and merge renders together as if they rendered exactly M samples.

Surely such effect could be achieved by changing Seed variable, but
that has possible issues with correlation artifacts and requiring to
manually deal with per render layer samples and such.

While we can't support all possible renderfarm-related features in
Cycles it's nice to support really commonly used stuff.

Here's a command how to run Blender with the new feature enabled:

  blender -- --cycles-resumable-num-chunks 24 --cycles-resumable-current-chunk 2

This command will divide samples range in 24 parts and render
range #2 (chunk number is 1-based).

This feature might be changed a bit after we'll do some tests here
in the studio with it.
This commit is contained in:
Sergey Sharybin 2016-03-30 15:55:12 +02:00
parent ee364b6358
commit f8b9f4e9bb
Notes: blender-bot 2023-02-14 19:27:41 +01:00
Referenced by issue blender/blender-addons#56092, OSX: Command line switch -- disables ability to use python arguments beginning with -h
6 changed files with 151 additions and 13 deletions

View File

@ -50,10 +50,39 @@ def _workaround_buggy_drivers():
_cycles.opencl_disable()
def _parse_command_line():
import sys
argv = sys.argv
if "--" not in argv:
return
argv = argv[argv.index("--") + 1:]
num_resumable_chunks = None
current_resumable_chunk = None
# TODO(sergey): Add some nice error ptins if argument is not used properly.
idx = 0
while idx < len(argv) - 1:
arg = argv[idx]
if arg == '--cycles-resumable-num-chunks':
num_resumable_chunks = int(argv[idx + 1])
elif arg == '--cycles-resumable-current-chunk':
current_resumable_chunk = int(argv[idx + 1])
idx += 1
if num_resumable_chunks is not None and current_resumable_chunk is not None:
import _cycles
_cycles.set_resumable_chunks(num_resumable_chunks,
current_resumable_chunk)
def init():
import bpy
import _cycles
import os.path
import sys
# Workaround possibly buggy legacy drivers which crashes on the OpenCL
# device enumeration.
@ -72,7 +101,7 @@ def init():
user_path = os.path.dirname(os.path.abspath(bpy.utils.user_resource('CONFIG', '')))
_cycles.init(path, user_path, bpy.app.background)
_parse_command_line()
def exit():
import _cycles

View File

@ -638,6 +638,24 @@ static PyObject *debug_flags_reset_func(PyObject * /*self*/, PyObject * /*args*/
Py_RETURN_NONE;
}
static PyObject *set_resumable_chunks_func(PyObject * /*self*/, PyObject *args)
{
int num_resumable_chunks, current_resumable_chunk;
if(!PyArg_ParseTuple(args, "ii",
&num_resumable_chunks,
&current_resumable_chunk)) {
return NULL;
}
VLOG(1) << "Initialized resumable render: "
<< "num_resumable_chunks=" << num_resumable_chunks << ", "
<< "current_resumable_chunk=" << current_resumable_chunk;
BlenderSession::num_resumable_chunks = num_resumable_chunks;
BlenderSession::current_resumable_chunk = current_resumable_chunk;
Py_RETURN_NONE;
}
static PyMethodDef methods[] = {
{"init", init_func, METH_VARARGS, ""},
{"exit", exit_func, METH_VARARGS, ""},
@ -657,8 +675,14 @@ static PyMethodDef methods[] = {
#ifdef WITH_OPENCL
{"opencl_disable", opencl_disable_func, METH_NOARGS, ""},
#endif
/* Debugging routines */
{"debug_flags_update", debug_flags_update_func, METH_VARARGS, ""},
{"debug_flags_reset", debug_flags_reset_func, METH_NOARGS, ""},
/* Resumable render */
{"set_resumable_chunks", set_resumable_chunks_func, METH_VARARGS, ""},
{NULL, NULL, 0, NULL},
};

View File

@ -43,6 +43,8 @@
CCL_NAMESPACE_BEGIN
bool BlenderSession::headless = false;
int BlenderSession::num_resumable_chunks = 0;
int BlenderSession::current_resumable_chunk = 0;
BlenderSession::BlenderSession(BL::RenderEngine& b_engine,
BL::UserPreferences& b_userpref,
@ -161,6 +163,8 @@ void BlenderSession::create_session()
session->reset(buffer_params, session_params.samples);
b_engine.use_highlight_tiles(session_params.progressive_refine == false);
update_resumable_tile_manager(session_params.samples);
}
void BlenderSession::reset_session(BL::BlendData& b_data_, BL::Scene& b_scene_)
@ -510,14 +514,21 @@ void BlenderSession::render()
&python_thread_state,
b_rlay_name.c_str());
/* update number of samples per layer */
/* Update number of samples per layer. */
int samples = sync->get_layer_samples();
bool bound_samples = sync->get_layer_bound_samples();
int effective_layer_samples;
if(samples != 0 && (!bound_samples || (samples < session_params.samples)))
session->reset(buffer_params, samples);
effective_layer_samples = samples;
else
session->reset(buffer_params, session_params.samples);
effective_layer_samples = session_params.samples;
/* Update tile manager if we're doing resumable render. */
update_resumable_tile_manager(effective_layer_samples);
/* Update session itself. */
session->reset(buffer_params, effective_layer_samples);
/* render */
session->start();
@ -712,6 +723,13 @@ void BlenderSession::do_write_update_render_result(BL::RenderResult& b_rr,
vector<float> pixels(params.width*params.height*4);
/* Adjust absolute sample number to the range. */
int sample = rtile.sample;
const int range_start_sample = session->tile_manager.range_start_sample;
if(range_start_sample != -1) {
sample -= range_start_sample;
}
if(!do_update_only) {
/* copy each pass */
BL::RenderLayer::passes_iterator b_iter;
@ -724,7 +742,7 @@ void BlenderSession::do_write_update_render_result(BL::RenderResult& b_rr,
int components = b_pass.channels();
/* copy pixels */
if(!buffers->get_pass_rect(pass_type, exposure, rtile.sample, components, &pixels[0]))
if(!buffers->get_pass_rect(pass_type, exposure, sample, components, &pixels[0]))
memset(&pixels[0], 0, pixels.size()*sizeof(float));
b_pass.rect(&pixels[0]);
@ -733,7 +751,7 @@ void BlenderSession::do_write_update_render_result(BL::RenderResult& b_rr,
else {
/* copy combined pass */
BL::RenderPass b_combined_pass(b_rlay.passes.find_by_type(BL::RenderPass::type_COMBINED, b_rview_name.c_str()));
if(buffers->get_pass_rect(PASS_COMBINED, exposure, rtile.sample, 4, &pixels[0]))
if(buffers->get_pass_rect(PASS_COMBINED, exposure, sample, 4, &pixels[0]))
b_combined_pass.rect(&pixels[0]);
}
@ -909,12 +927,12 @@ void BlenderSession::get_progress(float& progress, double& total_time, double& r
int tile, sample, samples_per_tile;
int tile_total = session->tile_manager.state.num_tiles;
int samples = session->tile_manager.state.sample + 1;
int total_samples = session->tile_manager.num_samples;
int total_samples = session->tile_manager.get_num_effective_samples();
session->progress.get_tile(tile, total_time, render_time, tile_time);
sample = session->progress.get_sample();
samples_per_tile = session->tile_manager.num_samples;
samples_per_tile = session->tile_manager.get_num_effective_samples();
if(background && samples_per_tile && tile_total)
progress = ((float)sample / (float)(tile_total * samples_per_tile));
@ -1277,5 +1295,26 @@ bool BlenderSession::builtin_image_float_pixels(const string &builtin_name, void
return false;
}
CCL_NAMESPACE_END
void BlenderSession::update_resumable_tile_manager(int num_samples)
{
const int num_resumable_chunks = BlenderSession::num_resumable_chunks,
current_resumable_chunk = BlenderSession::current_resumable_chunk;
if(num_resumable_chunks == 0) {
return;
}
int num_samples_per_chunk = (int)ceilf((float)num_samples / num_resumable_chunks);
int range_start_sample = num_samples_per_chunk * (current_resumable_chunk - 1);
int range_num_samples = num_samples_per_chunk;
if(range_start_sample + range_num_samples > num_samples) {
range_num_samples = num_samples - range_num_samples;
}
VLOG(1) << "Samples range start is " << range_start_sample << ", "
<< "number of samples to render is " << range_num_samples;
session->tile_manager.range_start_sample = range_start_sample;
session->tile_manager.range_num_samples = range_num_samples;
}
CCL_NAMESPACE_END

View File

@ -95,7 +95,6 @@ public:
void update_bake_progress();
bool background;
static bool headless;
Session *session;
Scene *scene;
BlenderSync *sync;
@ -120,6 +119,24 @@ public:
void *python_thread_state;
/* Global state which is common for all render sessions created from Blender.
* Usually denotes command line arguments.
*/
/* Blender is running from the command line, no windows are shown and some
* extra render optimization is possible (possible to free draw-only data and
* so on.
*/
static bool headless;
/* ** Resumable render ** */
/* Overall number of chunks in which the sample range is to be devided. */
static int num_resumable_chunks;
/* Current resumable chunk index to render. */
static int current_resumable_chunk;
protected:
void do_write_update_render_result(BL::RenderResult& b_rr,
BL::RenderLayer& b_rlay,
@ -131,6 +148,9 @@ protected:
void builtin_image_info(const string &builtin_name, void *builtin_data, bool &is_float, int &width, int &height, int &depth, int &channels);
bool builtin_image_pixels(const string &builtin_name, void *builtin_data, unsigned char *pixels);
bool builtin_image_float_pixels(const string &builtin_name, void *builtin_data, float *pixels);
/* Update tile manager to reflect resumable render settings. */
void update_resumable_tile_manager(int num_samples);
};
CCL_NAMESPACE_END

View File

@ -97,6 +97,9 @@ TileManager::TileManager(bool progressive_, int num_samples_, int2 tile_size_, i
preserve_tile_device = preserve_tile_device_;
background = background_;
range_start_sample = 0;
range_num_samples = -1;
BufferParams buffer_params;
reset(buffer_params, 0);
}
@ -124,7 +127,7 @@ void TileManager::reset(BufferParams& params_, int num_samples_)
num_samples = num_samples_;
state.buffer = BufferParams();
state.sample = -1;
state.sample = range_start_sample - 1;
state.num_tiles = 0;
state.num_rendered_tiles = 0;
state.num_samples = 0;
@ -325,7 +328,11 @@ bool TileManager::next_tile(Tile& tile, int device)
bool TileManager::done()
{
return (state.sample+state.num_samples >= num_samples && state.resolution_divider == 1);
int end_sample = (range_num_samples == -1)
? num_samples
: range_start_sample + range_num_samples;
return (state.resolution_divider == 1) &&
(state.sample+state.num_samples >= end_sample);
}
bool TileManager::next()
@ -344,8 +351,10 @@ bool TileManager::next()
if(progressive)
state.num_samples = 1;
else
else if(range_num_samples == -1)
state.num_samples = num_samples;
else
state.num_samples = range_num_samples;
state.resolution_divider = 1;
set_tiles();
@ -354,5 +363,11 @@ bool TileManager::next()
return true;
}
int TileManager::get_num_effective_samples()
{
return (range_num_samples == -1) ? num_samples
: range_num_samples;
}
CCL_NAMESPACE_END

View File

@ -82,6 +82,17 @@ public:
bool done();
void set_tile_order(TileOrder tile_order_) { tile_order = tile_order_; }
/* ** Sample range rendering. ** */
/* Start sample in the range. */
int range_start_sample;
/* Number to samples in the rendering range. */
int range_num_samples;
/* get number of actual samples to render. */
int get_num_effective_samples();
protected:
void set_tiles();