OpenGL render: Move file writing to a separate thread

The idea is to have a dedicated thread which is responsive for all the
file writing to a separate thread, so slow disk will not slow down
OpenGL itself.

Gives really nice speedup around 1.5x when exporting barber shop layout
file to h264 video.
This commit is contained in:
Sergey Sharybin 2016-09-16 10:28:41 +02:00
parent 04bfea0d67
commit 6f92604e53
Notes: blender-bot 2023-02-14 07:15:25 +01:00
Referenced by commit cade262c47, OpenGL render: Bugfix (unreported) after rB6f92604e539b2114763150fb1ace60d28e59a889
Referenced by issue #50542, Frame metadata incorrect when running opengl render in console for pngs (and probably other file formats)
3 changed files with 210 additions and 26 deletions

View File

@ -40,6 +40,7 @@
#include "BLI_utildefines.h"
#include "BLI_jitter.h"
#include "BLI_threads.h"
#include "BLI_task.h"
#include "DNA_scene_types.h"
#include "DNA_object_types.h"
@ -130,6 +131,10 @@ typedef struct OGLRender {
wmTimer *timer; /* use to check if running modal or not (invoke'd or exec'd)*/
void **movie_ctx_arr;
TaskPool *task_pool;
bool pool_ok;
SpinLock reports_lock;
#ifdef DEBUG_TIME
double time_start;
#endif
@ -685,6 +690,24 @@ static bool screen_opengl_render_init(bContext *C, wmOperator *op)
oglrender->mh = NULL;
oglrender->movie_ctx_arr = NULL;
if (is_animation) {
TaskScheduler *task_scheduler = BLI_task_scheduler_get();
if (BKE_imtype_is_movie(scene->r.im_format.imtype)) {
oglrender->task_pool = BLI_task_pool_create_background(task_scheduler,
oglrender);
BLI_pool_set_num_threads(oglrender->task_pool, 1);
}
else {
oglrender->task_pool = BLI_task_pool_create(task_scheduler,
oglrender);
}
}
else {
oglrender->task_pool = NULL;
}
oglrender->pool_ok = true;
BLI_spin_init(&oglrender->reports_lock);
#ifdef DEBUG_TIME
oglrender->time_start = PIL_check_seconds_timer();
#endif
@ -698,6 +721,10 @@ static void screen_opengl_render_end(bContext *C, OGLRender *oglrender)
Scene *scene = oglrender->scene;
int i;
BLI_task_pool_work_and_wait(oglrender->task_pool);
BLI_task_pool_free(oglrender->task_pool);
BLI_spin_end(&oglrender->reports_lock);
#ifdef DEBUG_TIME
printf("Total render time: %f\n", PIL_check_seconds_timer() - oglrender->time_start);
#endif
@ -796,6 +823,102 @@ static bool screen_opengl_render_anim_initialize(bContext *C, wmOperator *op)
return true;
}
typedef struct WriteTaskData {
RenderResult *rr;
int cfra;
} WriteTaskData;
static void write_result_func(TaskPool * __restrict pool,
void *task_data_v,
int UNUSED(thread_id))
{
OGLRender *oglrender = (OGLRender *) BLI_task_pool_userdata(pool);
WriteTaskData *task_data = (WriteTaskData *) task_data_v;
Scene *scene = oglrender->scene;
RenderResult *rr = task_data->rr;
const bool is_movie = BKE_imtype_is_movie(scene->r.im_format.imtype);
const int cfra = task_data->cfra;
bool ok;
/* Construct local thread0safe copy of reports structure which we can
* safely pass to the underlying functions.
*/
ReportList reports;
BKE_reports_init(&reports, oglrender->reports->flag & ~RPT_PRINT);
/* Do actual save logic here, depending on the file format. */
if (is_movie) {
/* We have to construct temporary scene with proper scene->r.cfra.
* This is because underlying calls do not use r.cfra but use scene
* for that.
*/
Scene tmp_scene = *scene;
tmp_scene.r.cfra = cfra;
ok = RE_WriteRenderViewsMovie(&reports,
rr,
&tmp_scene,
&tmp_scene.r,
oglrender->mh,
oglrender->movie_ctx_arr,
oglrender->totvideos,
PRVRANGEON != 0);
}
else {
/* TODO(sergey): We can in theory save some CPU ticks here because we
* calculate file name again here.
*/
char name[FILE_MAX];
BKE_image_path_from_imformat(name,
scene->r.pic,
oglrender->bmain->name,
cfra,
&scene->r.im_format,
(scene->r.scemode & R_EXTENSION) != 0,
true,
NULL);
BKE_render_result_stamp_info(scene, scene->camera, rr, false);
ok = RE_WriteRenderViewsImage(NULL, rr, scene, true, name);
if (!ok) {
BKE_reportf(&reports,
RPT_ERROR,
"Write error: cannot save %s",
name);
}
}
if (reports.list.first != NULL) {
BLI_spin_lock(&oglrender->reports_lock);
for (Report *report = reports.list.first;
report != NULL;
report = report->next)
{
BKE_report(oglrender->reports,
report->type,
report->message);
}
BLI_spin_unlock(&oglrender->reports_lock);
}
if (!ok) {
oglrender->pool_ok = false;
}
RE_FreeRenderResult(rr);
}
static bool schedule_write_result(OGLRender *oglrender, RenderResult *rr)
{
if (!oglrender->pool_ok) {
return false;
}
Scene *scene = oglrender->scene;
WriteTaskData *task_data = MEM_mallocN(sizeof(WriteTaskData), "write task data");
task_data->rr = rr;
task_data->cfra = scene->r.cfra;
BLI_task_pool_push(oglrender->task_pool,
write_result_func,
task_data,
true,
TASK_PRIORITY_LOW);
return true;
}
static bool screen_opengl_render_anim_step(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
@ -828,7 +951,9 @@ static bool screen_opengl_render_anim_step(bContext *C, wmOperator *op)
&scene->r.im_format, (scene->r.scemode & R_EXTENSION) != 0, true, NULL);
if ((scene->r.mode & R_NO_OVERWRITE) && BLI_exists(name)) {
BLI_spin_lock(&oglrender->reports_lock);
BKE_reportf(op->reports, RPT_INFO, "Skipping existing frame \"%s\"", name);
BLI_spin_unlock(&oglrender->reports_lock);
ok = true;
goto finally;
}
@ -856,34 +981,10 @@ static bool screen_opengl_render_anim_step(bContext *C, wmOperator *op)
/* save to disk */
rr = RE_AcquireResultRead(oglrender->re);
if (is_movie) {
ok = RE_WriteRenderViewsMovie(oglrender->reports, rr, scene, &scene->r, oglrender->mh,
oglrender->movie_ctx_arr, oglrender->totvideos, PRVRANGEON != 0);
if (ok) {
printf("Append frame %d", scene->r.cfra);
BKE_reportf(op->reports, RPT_INFO, "Appended frame: %d", scene->r.cfra);
}
}
else {
BKE_render_result_stamp_info(scene, scene->camera, rr, false);
ok = RE_WriteRenderViewsImage(op->reports, rr, scene, true, name);
if (ok) {
printf("Saved: %s", name);
BKE_reportf(op->reports, RPT_INFO, "Saved file: %s", name);
}
else {
printf("Write error: cannot save %s\n", name);
BKE_reportf(op->reports, RPT_ERROR, "Write error: cannot save %s", name);
}
}
RenderResult *new_rr = RE_DuplicateRenderResult(rr);
RE_ReleaseResult(oglrender->re);
/* movie stats prints have no line break */
printf("\n");
ok = schedule_write_result(oglrender, new_rr);
finally: /* Step the frame and bail early if needed */

View File

@ -391,6 +391,8 @@ bool RE_RenderResult_is_stereo(RenderResult *res);
struct RenderView *RE_RenderViewGetById(struct RenderResult *res, const int view_id);
struct RenderView *RE_RenderViewGetByName(struct RenderResult *res, const char *viewname);
RenderResult *RE_DuplicateRenderResult(RenderResult *rr);
/******* Debug pass helper functions *********/
#ifdef WITH_CYCLES_DEBUG

View File

@ -1665,3 +1665,84 @@ RenderView *RE_RenderViewGetByName(RenderResult *res, const char *viewname)
BLI_assert(res->views.first);
return rv ? rv : res->views.first;
}
static RenderPass *duplicate_render_pass(RenderPass *rpass)
{
RenderPass *new_rpass = MEM_mallocN(sizeof(RenderPass), "new render pass");
*new_rpass = *rpass;
new_rpass->next = new_rpass->prev = NULL;
if (new_rpass->rect != NULL) {
new_rpass->rect = MEM_dupallocN(new_rpass->rect);
}
return new_rpass;
}
static RenderLayer *duplicate_render_layer(RenderLayer *rl)
{
RenderLayer *new_rl = MEM_mallocN(sizeof(RenderLayer), "new render layer");
*new_rl = *rl;
new_rl->next = new_rl->prev = NULL;
new_rl->passes.first = new_rl->passes.last = NULL;
new_rl->exrhandle = NULL;
if (new_rl->acolrect != NULL) {
new_rl->acolrect = MEM_dupallocN(new_rl->acolrect);
}
if (new_rl->scolrect != NULL) {
new_rl->scolrect = MEM_dupallocN(new_rl->scolrect);
}
if (new_rl->display_buffer != NULL) {
new_rl->display_buffer = MEM_dupallocN(new_rl->display_buffer);
}
for (RenderPass *rpass = rl->passes.first; rpass != NULL; rpass = rpass->next) {
RenderPass *new_rpass = duplicate_render_pass(rpass);
BLI_addtail(&new_rl->passes, new_rpass);
}
return new_rl;
}
static RenderView *duplicate_render_view(RenderView *rview)
{
RenderView *new_rview = MEM_mallocN(sizeof(RenderView), "new render view");
*new_rview = *rview;
if (new_rview->rectf != NULL) {
new_rview->rectf = MEM_dupallocN(new_rview->rectf);
}
if (new_rview->rectf != NULL) {
new_rview->rectf = MEM_dupallocN(new_rview->rectf);
}
if (new_rview->rectz != NULL) {
new_rview->rectz = MEM_dupallocN(new_rview->rectz);
}
if (new_rview->rect32 != NULL) {
new_rview->rect32 = MEM_dupallocN(new_rview->rect32);
}
return new_rview;
}
RenderResult *RE_DuplicateRenderResult(RenderResult *rr)
{
RenderResult *new_rr = MEM_mallocN(sizeof(RenderResult), "new render result");
*new_rr = *rr;
new_rr->next = new_rr->prev = NULL;
new_rr->layers.first = new_rr->layers.last = NULL;
new_rr->views.first = new_rr->views.last = NULL;
for (RenderLayer *rl = rr->layers.first; rl != NULL; rl = rl->next) {
RenderLayer *new_rl = duplicate_render_layer(rl);
BLI_addtail(&new_rr->layers, new_rl);
}
for (RenderView *rview = rr->views.first; rview != NULL; rview = rview->next) {
RenderView *new_rview = duplicate_render_view(rview);
BLI_addtail(&new_rr->views, new_rview);
}
if (new_rr->rect32 != NULL) {
new_rr->rect32 = MEM_dupallocN(new_rr->rect32);
}
if (new_rr->rectf != NULL) {
new_rr->rectf = MEM_dupallocN(new_rr->rectf);
}
if (new_rr->rectz != NULL) {
new_rr->rectz = MEM_dupallocN(new_rr->rectz);
}
new_rr->stamp_data = MEM_dupallocN(new_rr->stamp_data);
return new_rr;
}