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:
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
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
¤t_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},
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue