diff options
Diffstat (limited to 'source/blender/render/intern/pipeline.cc')
-rw-r--r-- | source/blender/render/intern/pipeline.cc | 2664 |
1 files changed, 2664 insertions, 0 deletions
diff --git a/source/blender/render/intern/pipeline.cc b/source/blender/render/intern/pipeline.cc new file mode 100644 index 00000000000..4b52fb62bee --- /dev/null +++ b/source/blender/render/intern/pipeline.cc @@ -0,0 +1,2664 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2006 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup render + */ + +#include <cerrno> +#include <climits> +#include <cmath> +#include <cstddef> +#include <cstdlib> +#include <cstring> + +#include "DNA_anim_types.h" +#include "DNA_collection_types.h" +#include "DNA_image_types.h" +#include "DNA_node_types.h" +#include "DNA_object_types.h" +#include "DNA_particle_types.h" +#include "DNA_scene_types.h" +#include "DNA_sequence_types.h" +#include "DNA_space_types.h" +#include "DNA_userdef_types.h" + +#include "MEM_guardedalloc.h" + +#include "BLI_fileops.h" +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_path_util.h" +#include "BLI_rect.h" +#include "BLI_string.h" +#include "BLI_threads.h" +#include "BLI_timecode.h" +#include "BLI_vector.hh" + +#include "BLT_translation.h" + +#include "BKE_anim_data.h" +#include "BKE_animsys.h" /* <------ should this be here?, needed for sequencer update */ +#include "BKE_callbacks.h" +#include "BKE_camera.h" +#include "BKE_colortools.h" +#include "BKE_global.h" +#include "BKE_image.h" +#include "BKE_image_format.h" +#include "BKE_image_save.h" +#include "BKE_layer.h" +#include "BKE_lib_id.h" +#include "BKE_lib_remap.h" +#include "BKE_main.h" +#include "BKE_mask.h" +#include "BKE_modifier.h" +#include "BKE_node.h" +#include "BKE_object.h" +#include "BKE_pointcache.h" +#include "BKE_report.h" +#include "BKE_scene.h" +#include "BKE_sound.h" +#include "BKE_writeavi.h" /* <------ should be replaced once with generic movie module */ + +#include "NOD_composite.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_build.h" +#include "DEG_depsgraph_debug.h" +#include "DEG_depsgraph_query.h" + +#include "IMB_colormanagement.h" +#include "IMB_imbuf.h" +#include "IMB_imbuf_types.h" +#include "IMB_metadata.h" +#include "PIL_time.h" + +#include "RE_engine.h" +#include "RE_pipeline.h" +#include "RE_texture.h" + +#include "SEQ_relations.h" +#include "SEQ_render.h" + +#include "GPU_context.h" +#include "WM_api.h" +#include "wm_window.h" + +#ifdef WITH_FREESTYLE +# include "FRS_freestyle.h" +#endif + +/* internal */ +#include "pipeline.h" +#include "render_result.h" +#include "render_types.h" + +/* render flow + * + * 1) Initialize state + * - state data, tables + * - movie/image file init + * - everything that doesn't change during animation + * + * 2) Initialize data + * - camera, world, matrices + * - make render verts, faces, halos, strands + * - everything can change per frame/field + * + * 3) Render Processor + * - multiple layers + * - tiles, rect, baking + * - layers/tiles optionally to disk or directly in Render Result + * + * 4) Composite Render Result + * - also read external files etc + * + * 5) Image Files + * - save file or append in movie + */ + +/* -------------------------------------------------------------------- */ +/** \name Globals + * \{ */ + +/* here we store all renders */ +static struct { + ListBase renderlist; +} RenderGlobal = {{nullptr, nullptr}}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Callbacks + * \{ */ + +static void render_callback_exec_null(Render *re, Main *bmain, eCbEvent evt) +{ + if (re->r.scemode & R_BUTS_PREVIEW) { + return; + } + BKE_callback_exec_null(bmain, evt); +} + +static void render_callback_exec_id(Render *re, Main *bmain, ID *id, eCbEvent evt) +{ + if (re->r.scemode & R_BUTS_PREVIEW) { + return; + } + BKE_callback_exec_id(bmain, id, evt); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Allocation & Free + * \{ */ + +static bool do_write_image_or_movie(Render *re, + Main *bmain, + Scene *scene, + bMovieHandle *mh, + const int totvideos, + const char *name_override); + +/* default callbacks, set in each new render */ +static void result_nothing(void *UNUSED(arg), RenderResult *UNUSED(rr)) +{ +} +static void result_rcti_nothing(void *UNUSED(arg), + RenderResult *UNUSED(rr), + struct rcti *UNUSED(rect)) +{ +} +static void current_scene_nothing(void *UNUSED(arg), Scene *UNUSED(scene)) +{ +} +static void stats_nothing(void *UNUSED(arg), RenderStats *UNUSED(rs)) +{ +} +static void float_nothing(void *UNUSED(arg), float UNUSED(val)) +{ +} +static int default_break(void *UNUSED(arg)) +{ + return G.is_break == true; +} + +static void stats_background(void *UNUSED(arg), RenderStats *rs) +{ + if (rs->infostr == nullptr) { + return; + } + + uintptr_t mem_in_use, peak_memory; + float megs_used_memory, megs_peak_memory; + char info_time_str[32]; + + mem_in_use = MEM_get_memory_in_use(); + peak_memory = MEM_get_peak_memory(); + + megs_used_memory = (mem_in_use) / (1024.0 * 1024.0); + megs_peak_memory = (peak_memory) / (1024.0 * 1024.0); + + BLI_timecode_string_from_time_simple( + info_time_str, sizeof(info_time_str), PIL_check_seconds_timer() - rs->starttime); + + /* Compositor calls this from multiple threads, mutex lock to ensure we don't + * get garbled output. */ + static ThreadMutex mutex = BLI_MUTEX_INITIALIZER; + BLI_mutex_lock(&mutex); + + fprintf(stdout, + TIP_("Fra:%d Mem:%.2fM (Peak %.2fM) "), + rs->cfra, + megs_used_memory, + megs_peak_memory); + + fprintf(stdout, TIP_("| Time:%s | "), info_time_str); + + fprintf(stdout, "%s", rs->infostr); + + /* Flush stdout to be sure python callbacks are printing stuff after blender. */ + fflush(stdout); + + /* NOTE: using G_MAIN seems valid here??? + * Not sure it's actually even used anyway, we could as well pass nullptr? */ + BKE_callback_exec_null(G_MAIN, BKE_CB_EVT_RENDER_STATS); + + fputc('\n', stdout); + fflush(stdout); + + BLI_mutex_unlock(&mutex); +} + +void RE_FreeRenderResult(RenderResult *rr) +{ + render_result_free(rr); +} + +float *RE_RenderLayerGetPass(RenderLayer *rl, const char *name, const char *viewname) +{ + RenderPass *rpass = RE_pass_find_by_name(rl, name, viewname); + return rpass ? rpass->rect : nullptr; +} + +RenderLayer *RE_GetRenderLayer(RenderResult *rr, const char *name) +{ + if (rr == nullptr) { + return nullptr; + } + + return static_cast<RenderLayer *>( + BLI_findstring(&rr->layers, name, offsetof(RenderLayer, name))); +} + +bool RE_HasSingleLayer(Render *re) +{ + return (re->r.scemode & R_SINGLE_LAYER); +} + +RenderResult *RE_MultilayerConvert( + void *exrhandle, const char *colorspace, bool predivide, int rectx, int recty) +{ + return render_result_new_from_exr(exrhandle, colorspace, predivide, rectx, recty); +} + +RenderLayer *render_get_active_layer(Render *re, RenderResult *rr) +{ + ViewLayer *view_layer = static_cast<ViewLayer *>( + BLI_findlink(&re->view_layers, re->active_view_layer)); + + if (view_layer) { + RenderLayer *rl = RE_GetRenderLayer(rr, view_layer->name); + + if (rl) { + return rl; + } + } + + return static_cast<RenderLayer *>(rr->layers.first); +} + +static bool render_scene_has_layers_to_render(Scene *scene, ViewLayer *single_layer) +{ + if (single_layer) { + return true; + } + + LISTBASE_FOREACH (ViewLayer *, view_layer, &scene->view_layers) { + if (view_layer->flag & VIEW_LAYER_RENDER) { + return true; + } + } + return false; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Public Render API + * \{ */ + +Render *RE_GetRender(const char *name) +{ + /* search for existing renders */ + LISTBASE_FOREACH (Render *, re, &RenderGlobal.renderlist) { + if (STREQLEN(re->name, name, RE_MAXNAME)) { + return re; + } + } + + return nullptr; +} + +RenderResult *RE_AcquireResultRead(Render *re) +{ + if (re) { + BLI_rw_mutex_lock(&re->resultmutex, THREAD_LOCK_READ); + return re->result; + } + + return nullptr; +} + +RenderResult *RE_AcquireResultWrite(Render *re) +{ + if (re) { + BLI_rw_mutex_lock(&re->resultmutex, THREAD_LOCK_WRITE); + render_result_passes_allocated_ensure(re->result); + return re->result; + } + + return nullptr; +} + +void RE_ClearResult(Render *re) +{ + if (re) { + render_result_free(re->result); + re->result = nullptr; + } +} + +void RE_SwapResult(Render *re, RenderResult **rr) +{ + /* for keeping render buffers */ + if (re) { + SWAP(RenderResult *, re->result, *rr); + } +} + +void RE_ReleaseResult(Render *re) +{ + if (re) { + BLI_rw_mutex_unlock(&re->resultmutex); + } +} + +Scene *RE_GetScene(Render *re) +{ + if (re) { + return re->scene; + } + return nullptr; +} + +void RE_SetScene(Render *re, Scene *sce) +{ + if (re) { + re->scene = sce; + } +} + +void RE_AcquireResultImageViews(Render *re, RenderResult *rr) +{ + memset(rr, 0, sizeof(RenderResult)); + + if (re) { + BLI_rw_mutex_lock(&re->resultmutex, THREAD_LOCK_READ); + + if (re->result) { + rr->rectx = re->result->rectx; + rr->recty = re->result->recty; + + /* creates a temporary duplication of views */ + render_result_views_shallowcopy(rr, re->result); + + RenderView *rv = static_cast<RenderView *>(rr->views.first); + rr->have_combined = (rv->rectf != nullptr); + + /* active layer */ + RenderLayer *rl = render_get_active_layer(re, re->result); + + if (rl) { + if (rv->rectf == nullptr) { + LISTBASE_FOREACH (RenderView *, rview, &rr->views) { + rview->rectf = RE_RenderLayerGetPass(rl, RE_PASSNAME_COMBINED, rview->name); + } + } + + if (rv->rectz == nullptr) { + LISTBASE_FOREACH (RenderView *, rview, &rr->views) { + rview->rectz = RE_RenderLayerGetPass(rl, RE_PASSNAME_Z, rview->name); + } + } + } + + rr->layers = re->result->layers; + rr->xof = re->disprect.xmin; + rr->yof = re->disprect.ymin; + rr->stamp_data = re->result->stamp_data; + } + } +} + +void RE_ReleaseResultImageViews(Render *re, RenderResult *rr) +{ + if (re) { + if (rr) { + render_result_views_shallowdelete(rr); + } + BLI_rw_mutex_unlock(&re->resultmutex); + } +} + +void RE_AcquireResultImage(Render *re, RenderResult *rr, const int view_id) +{ + memset(rr, 0, sizeof(RenderResult)); + + if (re) { + BLI_rw_mutex_lock(&re->resultmutex, THREAD_LOCK_READ); + + if (re->result) { + RenderLayer *rl; + RenderView *rv; + + rr->rectx = re->result->rectx; + rr->recty = re->result->recty; + + /* `scene.rd.actview` view. */ + rv = RE_RenderViewGetById(re->result, view_id); + rr->have_combined = (rv->rectf != nullptr); + + rr->rectf = rv->rectf; + rr->rectz = rv->rectz; + rr->rect32 = rv->rect32; + + /* active layer */ + rl = render_get_active_layer(re, re->result); + + if (rl) { + if (rv->rectf == nullptr) { + rr->rectf = RE_RenderLayerGetPass(rl, RE_PASSNAME_COMBINED, rv->name); + } + + if (rv->rectz == nullptr) { + rr->rectz = RE_RenderLayerGetPass(rl, RE_PASSNAME_Z, rv->name); + } + } + + rr->layers = re->result->layers; + rr->views = re->result->views; + + rr->xof = re->disprect.xmin; + rr->yof = re->disprect.ymin; + + rr->stamp_data = re->result->stamp_data; + } + } +} + +void RE_ReleaseResultImage(Render *re) +{ + if (re) { + BLI_rw_mutex_unlock(&re->resultmutex); + } +} + +void RE_ResultGet32(Render *re, unsigned int *rect) +{ + RenderResult rres; + const int view_id = BKE_scene_multiview_view_id_get(&re->r, re->viewname); + + RE_AcquireResultImageViews(re, &rres); + render_result_rect_get_pixels(&rres, + rect, + re->rectx, + re->recty, + &re->scene->view_settings, + &re->scene->display_settings, + view_id); + RE_ReleaseResultImageViews(re, &rres); +} + +void RE_AcquiredResultGet32(Render *re, + RenderResult *result, + unsigned int *rect, + const int view_id) +{ + render_result_rect_get_pixels(result, + rect, + re->rectx, + re->recty, + &re->scene->view_settings, + &re->scene->display_settings, + view_id); +} + +RenderStats *RE_GetStats(Render *re) +{ + return &re->i; +} + +Render *RE_NewRender(const char *name) +{ + Render *re; + + /* only one render per name exists */ + re = RE_GetRender(name); + if (re == nullptr) { + + /* new render data struct */ + re = MEM_cnew<Render>("new render"); + BLI_addtail(&RenderGlobal.renderlist, re); + BLI_strncpy(re->name, name, RE_MAXNAME); + BLI_rw_mutex_init(&re->resultmutex); + BLI_mutex_init(&re->engine_draw_mutex); + BLI_mutex_init(&re->highlighted_tiles_mutex); + } + + RE_InitRenderCB(re); + + return re; +} + +/* MAX_ID_NAME + sizeof(Library->name) + space + null-terminator. */ +#define MAX_SCENE_RENDER_NAME (MAX_ID_NAME + 1024 + 2) + +static void scene_render_name_get(const Scene *scene, const size_t max_size, char *render_name) +{ + if (ID_IS_LINKED(scene)) { + BLI_snprintf(render_name, max_size, "%s %s", scene->id.lib->id.name, scene->id.name); + } + else { + BLI_snprintf(render_name, max_size, "%s", scene->id.name); + } +} + +Render *RE_GetSceneRender(const Scene *scene) +{ + char render_name[MAX_SCENE_RENDER_NAME]; + scene_render_name_get(scene, sizeof(render_name), render_name); + return RE_GetRender(render_name); +} + +Render *RE_NewSceneRender(const Scene *scene) +{ + char render_name[MAX_SCENE_RENDER_NAME]; + scene_render_name_get(scene, sizeof(render_name), render_name); + return RE_NewRender(render_name); +} + +void RE_InitRenderCB(Render *re) +{ + /* set default empty callbacks */ + re->display_init = result_nothing; + re->display_clear = result_nothing; + re->display_update = result_rcti_nothing; + re->current_scene_update = current_scene_nothing; + re->progress = float_nothing; + re->test_break = default_break; + if (G.background) { + re->stats_draw = stats_background; + } + else { + re->stats_draw = stats_nothing; + } + /* clear callback handles */ + re->dih = re->dch = re->duh = re->sdh = re->prh = re->tbh = nullptr; +} + +void RE_FreeRender(Render *re) +{ + if (re->engine) { + RE_engine_free(re->engine); + } + + BLI_rw_mutex_end(&re->resultmutex); + BLI_mutex_end(&re->engine_draw_mutex); + BLI_mutex_end(&re->highlighted_tiles_mutex); + + BLI_freelistN(&re->view_layers); + BLI_freelistN(&re->r.views); + + BKE_curvemapping_free_data(&re->r.mblur_shutter_curve); + + if (re->highlighted_tiles != nullptr) { + BLI_gset_free(re->highlighted_tiles, MEM_freeN); + } + + /* main dbase can already be invalid now, some database-free code checks it */ + re->main = nullptr; + re->scene = nullptr; + + render_result_free(re->result); + render_result_free(re->pushedresult); + + BLI_remlink(&RenderGlobal.renderlist, re); + MEM_freeN(re); +} + +void RE_FreeAllRender(void) +{ + while (RenderGlobal.renderlist.first) { + RE_FreeRender(static_cast<Render *>(RenderGlobal.renderlist.first)); + } + +#ifdef WITH_FREESTYLE + /* finalize Freestyle */ + FRS_exit(); +#endif +} + +void RE_FreeAllRenderResults(void) +{ + LISTBASE_FOREACH (Render *, re, &RenderGlobal.renderlist) { + render_result_free(re->result); + render_result_free(re->pushedresult); + + re->result = nullptr; + re->pushedresult = nullptr; + } +} + +void RE_FreeAllPersistentData(void) +{ + LISTBASE_FOREACH (Render *, re, &RenderGlobal.renderlist) { + if (re->engine != nullptr) { + BLI_assert(!(re->engine->flag & RE_ENGINE_RENDERING)); + RE_engine_free(re->engine); + re->engine = nullptr; + } + } +} + +static void re_free_persistent_data(Render *re) +{ + /* If engine is currently rendering, just wait for it to be freed when it finishes rendering. */ + if (re->engine && !(re->engine->flag & RE_ENGINE_RENDERING)) { + RE_engine_free(re->engine); + re->engine = nullptr; + } +} + +void RE_FreePersistentData(const Scene *scene) +{ + /* Render engines can be kept around for quick re-render, this clears all or one scene. */ + if (scene) { + Render *re = RE_GetSceneRender(scene); + if (re) { + re_free_persistent_data(re); + } + } + else { + LISTBASE_FOREACH (Render *, re, &RenderGlobal.renderlist) { + re_free_persistent_data(re); + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Initialize State + * \{ */ + +static void re_init_resolution(Render *re, Render *source, int winx, int winy, rcti *disprect) +{ + re->winx = winx; + re->winy = winy; + if (source && (source->r.mode & R_BORDER)) { + /* NOTE(@sergey): doesn't seem original bordered `disprect` is storing anywhere + * after insertion on black happening in #do_render_engine(), + * so for now simply re-calculate `disprect` using border from source renderer. */ + + re->disprect.xmin = source->r.border.xmin * winx; + re->disprect.xmax = source->r.border.xmax * winx; + + re->disprect.ymin = source->r.border.ymin * winy; + re->disprect.ymax = source->r.border.ymax * winy; + + re->rectx = BLI_rcti_size_x(&re->disprect); + re->recty = BLI_rcti_size_y(&re->disprect); + + /* copy border itself, since it could be used by external engines */ + re->r.border = source->r.border; + } + else if (disprect) { + re->disprect = *disprect; + re->rectx = BLI_rcti_size_x(&re->disprect); + re->recty = BLI_rcti_size_y(&re->disprect); + } + else { + re->disprect.xmin = re->disprect.ymin = 0; + re->disprect.xmax = winx; + re->disprect.ymax = winy; + re->rectx = winx; + re->recty = winy; + } +} + +void render_copy_renderdata(RenderData *to, RenderData *from) +{ + BLI_freelistN(&to->views); + BKE_curvemapping_free_data(&to->mblur_shutter_curve); + + memcpy(to, from, sizeof(*to)); + + BLI_duplicatelist(&to->views, &from->views); + BKE_curvemapping_copy_data(&to->mblur_shutter_curve, &from->mblur_shutter_curve); +} + +void RE_InitState(Render *re, + Render *source, + RenderData *rd, + ListBase *render_layers, + ViewLayer *single_layer, + int winx, + int winy, + rcti *disprect) +{ + bool had_freestyle = (re->r.mode & R_EDGE_FRS) != 0; + + re->ok = true; /* maybe flag */ + + re->i.starttime = PIL_check_seconds_timer(); + + /* copy render data and render layers for thread safety */ + render_copy_renderdata(&re->r, rd); + BLI_freelistN(&re->view_layers); + BLI_duplicatelist(&re->view_layers, render_layers); + re->active_view_layer = 0; + + if (source) { + /* reuse border flags from source renderer */ + re->r.mode &= ~(R_BORDER | R_CROP); + re->r.mode |= source->r.mode & (R_BORDER | R_CROP); + + /* dimensions shall be shared between all renderers */ + re->r.xsch = source->r.xsch; + re->r.ysch = source->r.ysch; + re->r.size = source->r.size; + } + + re_init_resolution(re, source, winx, winy, disprect); + + /* disable border if it's a full render anyway */ + if (re->r.border.xmin == 0.0f && re->r.border.xmax == 1.0f && re->r.border.ymin == 0.0f && + re->r.border.ymax == 1.0f) { + re->r.mode &= ~R_BORDER; + } + + if (re->rectx < 1 || re->recty < 1 || + (BKE_imtype_is_movie(rd->im_format.imtype) && (re->rectx < 16 || re->recty < 16))) { + BKE_report(re->reports, RPT_ERROR, "Image too small"); + re->ok = 0; + return; + } + + if (single_layer) { + int index = BLI_findindex(render_layers, single_layer); + if (index != -1) { + re->active_view_layer = index; + re->r.scemode |= R_SINGLE_LAYER; + } + } + + /* if preview render, we try to keep old result */ + BLI_rw_mutex_lock(&re->resultmutex, THREAD_LOCK_WRITE); + + if (re->r.scemode & R_BUTS_PREVIEW) { + if (had_freestyle || (re->r.mode & R_EDGE_FRS)) { + /* freestyle manipulates render layers so always have to free */ + render_result_free(re->result); + re->result = nullptr; + } + else if (re->result) { + ViewLayer *active_render_layer = static_cast<ViewLayer *>( + BLI_findlink(&re->view_layers, re->active_view_layer)); + bool have_layer = false; + + LISTBASE_FOREACH (RenderLayer *, rl, &re->result->layers) { + if (STREQ(rl->name, active_render_layer->name)) { + have_layer = true; + } + } + + if (re->result->rectx == re->rectx && re->result->recty == re->recty && have_layer) { + /* keep render result, this avoids flickering black tiles + * when the preview changes */ + } + else { + /* free because resolution changed */ + render_result_free(re->result); + re->result = nullptr; + } + } + } + else { + + /* make empty render result, so display callbacks can initialize */ + render_result_free(re->result); + re->result = MEM_cnew<RenderResult>("new render result"); + re->result->rectx = re->rectx; + re->result->recty = re->recty; + render_result_view_new(re->result, ""); + } + + BLI_rw_mutex_unlock(&re->resultmutex); + + RE_init_threadcount(re); + + RE_point_density_fix_linking(); +} + +void render_update_anim_renderdata(Render *re, RenderData *rd, ListBase *render_layers) +{ + /* filter */ + re->r.gauss = rd->gauss; + + /* motion blur */ + re->r.blurfac = rd->blurfac; + + /* freestyle */ + re->r.line_thickness_mode = rd->line_thickness_mode; + re->r.unit_line_thickness = rd->unit_line_thickness; + + /* render layers */ + BLI_freelistN(&re->view_layers); + BLI_duplicatelist(&re->view_layers, render_layers); + + /* render views */ + BLI_freelistN(&re->r.views); + BLI_duplicatelist(&re->r.views, &rd->views); +} + +void RE_display_init_cb(Render *re, void *handle, void (*f)(void *handle, RenderResult *rr)) +{ + re->display_init = f; + re->dih = handle; +} +void RE_display_clear_cb(Render *re, void *handle, void (*f)(void *handle, RenderResult *rr)) +{ + re->display_clear = f; + re->dch = handle; +} +void RE_display_update_cb(Render *re, + void *handle, + void (*f)(void *handle, RenderResult *rr, rcti *rect)) +{ + re->display_update = f; + re->duh = handle; +} +void RE_current_scene_update_cb(Render *re, void *handle, void (*f)(void *handle, Scene *scene)) +{ + re->current_scene_update = f; + re->suh = handle; +} +void RE_stats_draw_cb(Render *re, void *handle, void (*f)(void *handle, RenderStats *rs)) +{ + re->stats_draw = f; + re->sdh = handle; +} +void RE_progress_cb(Render *re, void *handle, void (*f)(void *handle, float)) +{ + re->progress = f; + re->prh = handle; +} + +void RE_draw_lock_cb(Render *re, void *handle, void (*f)(void *handle, bool lock)) +{ + re->draw_lock = f; + re->dlh = handle; +} + +void RE_test_break_cb(Render *re, void *handle, int (*f)(void *handle)) +{ + re->test_break = f; + re->tbh = handle; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name OpenGL Context + * \{ */ + +void RE_gl_context_create(Render *re) +{ + /* Needs to be created in the main ogl thread. */ + re->gl_context = WM_opengl_context_create(); + /* So we activate the window's one afterwards. */ + wm_window_reset_drawable(); +} + +void RE_gl_context_destroy(Render *re) +{ + /* Needs to be called from the thread which used the ogl context for rendering. */ + if (re->gl_context) { + if (re->gpu_context) { + WM_opengl_context_activate(re->gl_context); + GPU_context_active_set(static_cast<GPUContext *>(re->gpu_context)); + GPU_context_discard(static_cast<GPUContext *>(re->gpu_context)); + re->gpu_context = nullptr; + } + + WM_opengl_context_dispose(re->gl_context); + re->gl_context = nullptr; + } +} + +void *RE_gl_context_get(Render *re) +{ + return re->gl_context; +} + +void *RE_gpu_context_get(Render *re) +{ + if (re->gpu_context == nullptr) { + re->gpu_context = GPU_context_create(nullptr); + } + return re->gpu_context; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Render & Composite Scenes (Implementation & Public API) + * + * Main high-level functions defined here are: + * - #RE_RenderFrame + * - #RE_RenderAnim + * \{ */ + +/* ************ This part uses API, for rendering Blender scenes ********** */ + +/* make sure disprect is not affected by the render border */ +static void render_result_disprect_to_full_resolution(Render *re) +{ + re->disprect.xmin = re->disprect.ymin = 0; + re->disprect.xmax = re->winx; + re->disprect.ymax = re->winy; + re->rectx = re->winx; + re->recty = re->winy; +} + +static void render_result_uncrop(Render *re) +{ + /* when using border render with crop disabled, insert render result into + * full size with black pixels outside */ + if (re->result && (re->r.mode & R_BORDER)) { + if ((re->r.mode & R_CROP) == 0) { + RenderResult *rres; + + /* backup */ + const rcti orig_disprect = re->disprect; + const int orig_rectx = re->rectx, orig_recty = re->recty; + + BLI_rw_mutex_lock(&re->resultmutex, THREAD_LOCK_WRITE); + + /* sub-rect for merge call later on */ + re->result->tilerect = re->disprect; + + /* weak is: it chances disprect from border */ + render_result_disprect_to_full_resolution(re); + + rres = render_result_new(re, &re->disprect, RR_ALL_LAYERS, RR_ALL_VIEWS); + rres->stamp_data = BKE_stamp_data_copy(re->result->stamp_data); + + render_result_clone_passes(re, rres, nullptr); + render_result_passes_allocated_ensure(rres); + + render_result_merge(rres, re->result); + render_result_free(re->result); + re->result = rres; + + /* Weak, the display callback wants an active render-layer pointer. */ + re->result->renlay = render_get_active_layer(re, re->result); + + BLI_rw_mutex_unlock(&re->resultmutex); + + re->display_init(re->dih, re->result); + re->display_update(re->duh, re->result, nullptr); + + /* restore the disprect from border */ + re->disprect = orig_disprect; + re->rectx = orig_rectx; + re->recty = orig_recty; + } + else { + /* set offset (again) for use in compositor, disprect was manipulated. */ + re->result->xof = 0; + re->result->yof = 0; + } + } +} + +/* Render scene into render result, with a render engine. */ +static void do_render_engine(Render *re) +{ + Object *camera = RE_GetCamera(re); + /* also check for camera here */ + if (camera == nullptr) { + BKE_report(re->reports, RPT_ERROR, "Cannot render, no camera"); + G.is_break = true; + return; + } + + /* now use renderdata and camera to set viewplane */ + RE_SetCamera(re, camera); + + re->current_scene_update(re->suh, re->scene); + RE_engine_render(re, false); + + /* when border render, check if we have to insert it in black */ + render_result_uncrop(re); +} + +/* Render scene into render result, within a compositor node tree. + * Uses the same image dimensions, does not recursively perform compositing. */ +static void do_render_compositor_scene(Render *re, Scene *sce, int cfra) +{ + Render *resc = RE_NewSceneRender(sce); + int winx = re->winx, winy = re->winy; + + sce->r.cfra = cfra; + + BKE_scene_camera_switch_update(sce); + + /* exception: scene uses own size (unfinished code) */ + if (false) { + BKE_render_resolution(&sce->r, false, &winx, &winy); + } + + /* initial setup */ + RE_InitState(resc, re, &sce->r, &sce->view_layers, nullptr, winx, winy, &re->disprect); + + /* We still want to use 'rendercache' setting from org (main) scene... */ + resc->r.scemode = (resc->r.scemode & ~R_EXR_CACHE_FILE) | (re->r.scemode & R_EXR_CACHE_FILE); + + /* still unsure entity this... */ + resc->main = re->main; + resc->scene = sce; + + /* copy callbacks */ + resc->display_update = re->display_update; + resc->duh = re->duh; + resc->test_break = re->test_break; + resc->tbh = re->tbh; + resc->stats_draw = re->stats_draw; + resc->sdh = re->sdh; + resc->current_scene_update = re->current_scene_update; + resc->suh = re->suh; + + do_render_engine(resc); +} + +/* helper call to detect if this scene needs a render, + * or if there's a any render layer to render. */ +static int compositor_needs_render(Scene *sce, int this_scene) +{ + bNodeTree *ntree = sce->nodetree; + + if (ntree == nullptr) { + return 1; + } + if (sce->use_nodes == false) { + return 1; + } + if ((sce->r.scemode & R_DOCOMP) == 0) { + return 1; + } + + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { + if (node->type == CMP_NODE_R_LAYERS && (node->flag & NODE_MUTED) == 0) { + if (this_scene == 0 || node->id == nullptr || node->id == &sce->id) { + return 1; + } + } + } + return 0; +} + +/* Render all scenes within a compositor node tree. */ +static void do_render_compositor_scenes(Render *re) +{ + int cfra = re->scene->r.cfra; + Scene *restore_scene = re->scene; + + if (re->scene->nodetree == nullptr) { + return; + } + + bool changed_scene = false; + + /* now foreach render-result node we do a full render */ + /* results are stored in a way compositor will find it */ + GSet *scenes_rendered = BLI_gset_ptr_new(__func__); + LISTBASE_FOREACH (bNode *, node, &re->scene->nodetree->nodes) { + if (node->type == CMP_NODE_R_LAYERS && (node->flag & NODE_MUTED) == 0) { + if (node->id && node->id != (ID *)re->scene) { + Scene *scene = (Scene *)node->id; + if (!BLI_gset_haskey(scenes_rendered, scene) && + render_scene_has_layers_to_render(scene, nullptr)) { + do_render_compositor_scene(re, scene, cfra); + BLI_gset_add(scenes_rendered, scene); + node->typeinfo->updatefunc(restore_scene->nodetree, node); + + if (scene != re->scene) { + changed_scene = true; + } + } + } + } + } + BLI_gset_free(scenes_rendered, nullptr); + + if (changed_scene) { + /* If rendered another scene, switch back to the current scene with compositing nodes. */ + re->current_scene_update(re->suh, re->scene); + } +} + +/* bad call... need to think over proper method still */ +static void render_compositor_stats(void *arg, const char *str) +{ + Render *re = (Render *)arg; + + RenderStats i; + memcpy(&i, &re->i, sizeof(i)); + i.infostr = str; + re->stats_draw(re->sdh, &i); +} + +/* Render compositor nodes, along with any scenes required for them. + * The result will be output into a compositing render layer in the render result. */ +static void do_render_compositor(Render *re) +{ + bNodeTree *ntree = re->pipeline_scene_eval->nodetree; + int update_newframe = 0; + + if (compositor_needs_render(re->pipeline_scene_eval, 1)) { + /* save memory... free all cached images */ + ntreeFreeCache(ntree); + + /* render the frames + * it could be optimized to render only the needed view + * but what if a scene has a different number of views + * than the main scene? */ + do_render_engine(re); + } + else { + re->i.cfra = re->r.cfra; + + /* ensure new result gets added, like for regular renders */ + BLI_rw_mutex_lock(&re->resultmutex, THREAD_LOCK_WRITE); + + render_result_free(re->result); + if ((re->r.mode & R_CROP) == 0) { + render_result_disprect_to_full_resolution(re); + } + re->result = render_result_new(re, &re->disprect, RR_ALL_LAYERS, RR_ALL_VIEWS); + + BLI_rw_mutex_unlock(&re->resultmutex); + + /* scene render process already updates animsys */ + update_newframe = 1; + } + + /* swap render result */ + if (re->r.scemode & R_SINGLE_LAYER) { + BLI_rw_mutex_lock(&re->resultmutex, THREAD_LOCK_WRITE); + render_result_single_layer_end(re); + BLI_rw_mutex_unlock(&re->resultmutex); + } + + if (!re->test_break(re->tbh)) { + + if (ntree) { + ntreeCompositTagRender(re->pipeline_scene_eval); + } + + if (ntree && re->scene->use_nodes && re->r.scemode & R_DOCOMP) { + /* checks if there are render-result nodes that need scene */ + if ((re->r.scemode & R_SINGLE_LAYER) == 0) { + do_render_compositor_scenes(re); + } + + if (!re->test_break(re->tbh)) { + ntree->stats_draw = render_compositor_stats; + ntree->test_break = re->test_break; + ntree->progress = re->progress; + ntree->sdh = re; + ntree->tbh = re->tbh; + ntree->prh = re->prh; + + if (update_newframe) { + /* If we have consistent depsgraph now would be a time to update them. */ + } + + LISTBASE_FOREACH (RenderView *, rv, &re->result->views) { + ntreeCompositExecTree( + re->pipeline_scene_eval, ntree, &re->r, true, G.background == 0, rv->name); + } + + ntree->stats_draw = nullptr; + ntree->test_break = nullptr; + ntree->progress = nullptr; + ntree->tbh = ntree->sdh = ntree->prh = nullptr; + } + } + } + + /* Weak: the display callback wants an active render-layer pointer. */ + if (re->result != nullptr) { + re->result->renlay = render_get_active_layer(re, re->result); + re->display_update(re->duh, re->result, nullptr); + } +} + +static void renderresult_stampinfo(Render *re) +{ + RenderResult rres; + int nr = 0; + + /* this is the basic trick to get the displayed float or char rect from render result */ + LISTBASE_FOREACH (RenderView *, rv, &re->result->views) { + RE_SetActiveRenderView(re, rv->name); + RE_AcquireResultImage(re, &rres, nr); + + Object *ob_camera_eval = DEG_get_evaluated_object(re->pipeline_depsgraph, RE_GetCamera(re)); + BKE_image_stamp_buf(re->scene, + ob_camera_eval, + (re->r.stamp & R_STAMP_STRIPMETA) ? rres.stamp_data : nullptr, + (unsigned char *)rres.rect32, + rres.rectf, + rres.rectx, + rres.recty, + 4); + RE_ReleaseResultImage(re); + nr++; + } +} + +int RE_seq_render_active(Scene *scene, RenderData *rd) +{ + Editing *ed = scene->ed; + + if (!(rd->scemode & R_DOSEQ) || !ed || !ed->seqbase.first) { + return 0; + } + + LISTBASE_FOREACH (Sequence *, seq, &ed->seqbase) { + if (seq->type != SEQ_TYPE_SOUND_RAM) { + return 1; + } + } + + return 0; +} + +/* Render sequencer strips into render result. */ +static void do_render_sequencer(Render *re) +{ + static int recurs_depth = 0; + struct ImBuf *out; + RenderResult *rr; /* don't assign re->result here as it might change during give_ibuf_seq */ + int cfra = re->r.cfra; + SeqRenderData context; + int view_id, tot_views; + int re_x, re_y; + + re->i.cfra = cfra; + + recurs_depth++; + + if ((re->r.mode & R_BORDER) && (re->r.mode & R_CROP) == 0) { + /* if border rendering is used and cropping is disabled, final buffer should + * be as large as the whole frame */ + re_x = re->winx; + re_y = re->winy; + } + else { + re_x = re->result->rectx; + re_y = re->result->recty; + } + + tot_views = BKE_scene_multiview_num_views_get(&re->r); + blender::Vector<ImBuf *> ibuf_arr(tot_views); + + SEQ_render_new_render_data(re->main, + re->pipeline_depsgraph, + re->scene, + re_x, + re_y, + SEQ_RENDER_SIZE_SCENE, + true, + &context); + + /* The render-result gets destroyed during the rendering, so we first collect all ibufs + * and then we populate the final render-result. */ + + for (view_id = 0; view_id < tot_views; view_id++) { + context.view_id = view_id; + out = SEQ_render_give_ibuf(&context, cfra, 0); + + if (out) { + ibuf_arr[view_id] = IMB_dupImBuf(out); + IMB_metadata_copy(ibuf_arr[view_id], out); + IMB_freeImBuf(out); + SEQ_render_imbuf_from_sequencer_space(re->pipeline_scene_eval, ibuf_arr[view_id]); + } + else { + ibuf_arr[view_id] = nullptr; + } + } + + rr = re->result; + + BLI_rw_mutex_lock(&re->resultmutex, THREAD_LOCK_WRITE); + render_result_views_new(rr, &re->r); + BLI_rw_mutex_unlock(&re->resultmutex); + + for (view_id = 0; view_id < tot_views; view_id++) { + RenderView *rv = RE_RenderViewGetById(rr, view_id); + BLI_rw_mutex_lock(&re->resultmutex, THREAD_LOCK_WRITE); + + if (ibuf_arr[view_id]) { + /* copy ibuf into combined pixel rect */ + RE_render_result_rect_from_ibuf(rr, ibuf_arr[view_id], view_id); + + if (ibuf_arr[view_id]->metadata && (re->r.stamp & R_STAMP_STRIPMETA)) { + /* ensure render stamp info first */ + BKE_render_result_stamp_info(nullptr, nullptr, rr, true); + BKE_stamp_info_from_imbuf(rr, ibuf_arr[view_id]); + } + + if (recurs_depth == 0) { /* With nested scenes, only free on top-level. */ + Editing *ed = re->pipeline_scene_eval->ed; + if (ed) { + SEQ_relations_free_imbuf(re->pipeline_scene_eval, &ed->seqbase, true); + } + } + IMB_freeImBuf(ibuf_arr[view_id]); + } + else { + /* render result is delivered empty in most cases, nevertheless we handle all cases */ + render_result_rect_fill_zero(rr, view_id); + } + + BLI_rw_mutex_unlock(&re->resultmutex); + + /* would mark display buffers as invalid */ + RE_SetActiveRenderView(re, rv->name); + re->display_update(re->duh, re->result, nullptr); + } + + recurs_depth--; + + /* just in case this flag went missing at some point */ + re->r.scemode |= R_DOSEQ; + + /* set overall progress of sequence rendering */ + if (re->r.efra != re->r.sfra) { + re->progress(re->prh, (float)(cfra - re->r.sfra) / (re->r.efra - re->r.sfra)); + } + else { + re->progress(re->prh, 1.0f); + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +/* Render full pipeline, using render engine, sequencer and compositing nodes. */ +static void do_render_full_pipeline(Render *re) +{ + bool render_seq = false; + + re->current_scene_update(re->suh, re->scene); + + BKE_scene_camera_switch_update(re->scene); + + re->i.starttime = PIL_check_seconds_timer(); + + /* ensure no images are in memory from previous animated sequences */ + BKE_image_all_free_anim_ibufs(re->main, re->r.cfra); + SEQ_cache_cleanup(re->scene); + + if (RE_engine_render(re, true)) { + /* in this case external render overrides all */ + } + else if (RE_seq_render_active(re->scene, &re->r)) { + /* NOTE: do_render_sequencer() frees rect32 when sequencer returns float images. */ + if (!re->test_break(re->tbh)) { + do_render_sequencer(re); + render_seq = true; + } + + re->stats_draw(re->sdh, &re->i); + re->display_update(re->duh, re->result, nullptr); + } + else { + do_render_compositor(re); + } + + re->i.lastframetime = PIL_check_seconds_timer() - re->i.starttime; + + re->stats_draw(re->sdh, &re->i); + + /* save render result stamp if needed */ + if (re->result != nullptr) { + /* sequence rendering should have taken care of that already */ + if (!(render_seq && (re->r.stamp & R_STAMP_STRIPMETA))) { + Object *ob_camera_eval = DEG_get_evaluated_object(re->pipeline_depsgraph, RE_GetCamera(re)); + BKE_render_result_stamp_info(re->scene, ob_camera_eval, re->result, false); + } + + /* stamp image info here */ + if ((re->r.stamp & R_STAMP_ALL) && (re->r.stamp & R_STAMP_DRAW)) { + renderresult_stampinfo(re); + re->display_update(re->duh, re->result, nullptr); + } + } +} + +static bool check_valid_compositing_camera(Scene *scene, Object *camera_override) +{ + if (scene->r.scemode & R_DOCOMP && scene->use_nodes) { + LISTBASE_FOREACH (bNode *, node, &scene->nodetree->nodes) { + if (node->type == CMP_NODE_R_LAYERS && (node->flag & NODE_MUTED) == 0) { + Scene *sce = node->id ? (Scene *)node->id : scene; + if (sce->camera == nullptr) { + sce->camera = BKE_view_layer_camera_find(sce, BKE_view_layer_default_render(sce)); + } + if (sce->camera == nullptr) { + /* all render layers nodes need camera */ + return false; + } + } + } + + return true; + } + + return (camera_override != nullptr || scene->camera != nullptr); +} + +static bool check_valid_camera_multiview(Scene *scene, Object *camera, ReportList *reports) +{ + bool active_view = false; + + if (camera == nullptr || (scene->r.scemode & R_MULTIVIEW) == 0) { + return true; + } + + LISTBASE_FOREACH (SceneRenderView *, srv, &scene->r.views) { + if (BKE_scene_multiview_is_render_view_active(&scene->r, srv)) { + active_view = true; + + if (scene->r.views_format == SCE_VIEWS_FORMAT_MULTIVIEW) { + Object *view_camera; + view_camera = BKE_camera_multiview_render(scene, camera, srv->name); + + if (view_camera == camera) { + /* if the suffix is not in the camera, means we are using the fallback camera */ + if (!BLI_str_endswith(view_camera->id.name + 2, srv->suffix)) { + BKE_reportf(reports, + RPT_ERROR, + "Camera \"%s\" is not a multi-view camera", + camera->id.name + 2); + return false; + } + } + } + } + } + + if (!active_view) { + BKE_reportf(reports, RPT_ERROR, "No active view found in scene \"%s\"", scene->id.name + 2); + return false; + } + + return true; +} + +static int check_valid_camera(Scene *scene, Object *camera_override, ReportList *reports) +{ + const char *err_msg = "No camera found in scene \"%s\""; + + if (camera_override == nullptr && scene->camera == nullptr) { + scene->camera = BKE_view_layer_camera_find(scene, BKE_view_layer_default_render(scene)); + } + + if (!check_valid_camera_multiview(scene, scene->camera, reports)) { + return false; + } + + if (RE_seq_render_active(scene, &scene->r)) { + if (scene->ed) { + LISTBASE_FOREACH (Sequence *, seq, &scene->ed->seqbase) { + if ((seq->type == SEQ_TYPE_SCENE) && ((seq->flag & SEQ_SCENE_STRIPS) == 0) && + (seq->scene != nullptr)) { + if (!seq->scene_camera) { + if (!seq->scene->camera && + !BKE_view_layer_camera_find(seq->scene, + BKE_view_layer_default_render(seq->scene))) { + /* camera could be unneeded due to composite nodes */ + Object *override = (seq->scene == scene) ? camera_override : nullptr; + + if (!check_valid_compositing_camera(seq->scene, override)) { + BKE_reportf(reports, RPT_ERROR, err_msg, seq->scene->id.name + 2); + return false; + } + } + } + else if (!check_valid_camera_multiview(seq->scene, seq->scene_camera, reports)) { + return false; + } + } + } + } + } + else if (!check_valid_compositing_camera(scene, camera_override)) { + BKE_reportf(reports, RPT_ERROR, err_msg, scene->id.name + 2); + return false; + } + + return true; +} + +static bool node_tree_has_compositor_output(bNodeTree *ntree) +{ + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { + if (ELEM(node->type, CMP_NODE_COMPOSITE, CMP_NODE_OUTPUT_FILE)) { + return true; + } + if (ELEM(node->type, NODE_GROUP, NODE_CUSTOM_GROUP)) { + if (node->id) { + if (node_tree_has_compositor_output((bNodeTree *)node->id)) { + return true; + } + } + } + } + + return false; +} + +static int check_compositor_output(Scene *scene) +{ + return node_tree_has_compositor_output(scene->nodetree); +} + +bool RE_is_rendering_allowed(Scene *scene, + ViewLayer *single_layer, + Object *camera_override, + ReportList *reports) +{ + const int scemode = scene->r.scemode; + + if (scene->r.mode & R_BORDER) { + if (scene->r.border.xmax <= scene->r.border.xmin || + scene->r.border.ymax <= scene->r.border.ymin) { + BKE_report(reports, RPT_ERROR, "No border area selected"); + return false; + } + } + + if (RE_seq_render_active(scene, &scene->r)) { + /* Sequencer */ + if (scene->r.mode & R_BORDER) { + BKE_report(reports, RPT_ERROR, "Border rendering is not supported by sequencer"); + return false; + } + } + else if ((scemode & R_DOCOMP) && scene->use_nodes) { + /* Compositor */ + if (!scene->nodetree) { + BKE_report(reports, RPT_ERROR, "No node tree in scene"); + return false; + } + + if (!check_compositor_output(scene)) { + BKE_report(reports, RPT_ERROR, "No render output node in scene"); + return false; + } + } + else { + /* Regular Render */ + if (!render_scene_has_layers_to_render(scene, single_layer)) { + BKE_report(reports, RPT_ERROR, "All render layers are disabled"); + return false; + } + } + + /* check valid camera, without camera render is OK (compo, seq) */ + if (!check_valid_camera(scene, camera_override, reports)) { + return false; + } + + return true; +} + +static void update_physics_cache(Render *re, + Scene *scene, + ViewLayer *view_layer, + int UNUSED(anim_init)) +{ + PTCacheBaker baker; + + memset(&baker, 0, sizeof(baker)); + baker.bmain = re->main; + baker.scene = scene; + baker.view_layer = view_layer; + baker.depsgraph = BKE_scene_ensure_depsgraph(re->main, scene, view_layer); + baker.bake = 0; + baker.render = 1; + baker.anim_init = 1; + baker.quick_step = 1; + + BKE_ptcache_bake(&baker); +} + +void RE_SetActiveRenderView(Render *re, const char *viewname) +{ + BLI_strncpy(re->viewname, viewname, sizeof(re->viewname)); +} + +const char *RE_GetActiveRenderView(Render *re) +{ + return re->viewname; +} + +/* evaluating scene options for general Blender render */ +static int render_init_from_main(Render *re, + const RenderData *rd, + Main *bmain, + Scene *scene, + ViewLayer *single_layer, + Object *camera_override, + int anim, + int anim_init) +{ + int winx, winy; + rcti disprect; + + /* r.xsch and r.ysch has the actual view window size + * r.border is the clipping rect */ + + /* calculate actual render result and display size */ + BKE_render_resolution(rd, false, &winx, &winy); + + /* We always render smaller part, inserting it in larger image is compositor business, + * it uses 'disprect' for it. */ + if (scene->r.mode & R_BORDER) { + disprect.xmin = rd->border.xmin * winx; + disprect.xmax = rd->border.xmax * winx; + + disprect.ymin = rd->border.ymin * winy; + disprect.ymax = rd->border.ymax * winy; + } + else { + disprect.xmin = disprect.ymin = 0; + disprect.xmax = winx; + disprect.ymax = winy; + } + + re->main = bmain; + re->scene = scene; + re->camera_override = camera_override; + re->viewname[0] = '\0'; + + /* not too nice, but it survives anim-border render */ + if (anim) { + render_update_anim_renderdata(re, &scene->r, &scene->view_layers); + re->disprect = disprect; + return 1; + } + + /* + * Disabled completely for now, + * can be later set as render profile option + * and default for background render. + */ + if (false) { + /* make sure dynamics are up to date */ + ViewLayer *view_layer = BKE_view_layer_context_active_PLACEHOLDER(scene); + update_physics_cache(re, scene, view_layer, anim_init); + } + + if (single_layer || scene->r.scemode & R_SINGLE_LAYER) { + BLI_rw_mutex_lock(&re->resultmutex, THREAD_LOCK_WRITE); + render_result_single_layer_begin(re); + BLI_rw_mutex_unlock(&re->resultmutex); + } + + RE_InitState(re, nullptr, &scene->r, &scene->view_layers, single_layer, winx, winy, &disprect); + if (!re->ok) { /* if an error was printed, abort */ + return 0; + } + + /* initstate makes new result, have to send changed tags around */ + ntreeCompositTagRender(re->scene); + + re->display_init(re->dih, re->result); + re->display_clear(re->dch, re->result); + + return 1; +} + +void RE_SetReports(Render *re, ReportList *reports) +{ + re->reports = reports; +} + +static void render_update_depsgraph(Render *re) +{ + Scene *scene = re->scene; + DEG_evaluate_on_framechange(re->pipeline_depsgraph, BKE_scene_frame_get(scene)); + BKE_scene_update_sound(re->pipeline_depsgraph, re->main); +} + +static void render_init_depsgraph(Render *re) +{ + Scene *scene = re->scene; + ViewLayer *view_layer = BKE_view_layer_default_render(re->scene); + + re->pipeline_depsgraph = DEG_graph_new(re->main, scene, view_layer, DAG_EVAL_RENDER); + DEG_debug_name_set(re->pipeline_depsgraph, "RENDER PIPELINE"); + + /* Make sure there is a correct evaluated scene pointer. */ + DEG_graph_build_for_render_pipeline(re->pipeline_depsgraph); + + /* Update immediately so we have proper evaluated scene. */ + render_update_depsgraph(re); + + re->pipeline_scene_eval = DEG_get_evaluated_scene(re->pipeline_depsgraph); +} + +/* Free data only needed during rendering operation. */ +static void render_pipeline_free(Render *re) +{ + if (re->engine && !RE_engine_use_persistent_data(re->engine)) { + RE_engine_free(re->engine); + re->engine = nullptr; + } + if (re->pipeline_depsgraph != nullptr) { + DEG_graph_free(re->pipeline_depsgraph); + re->pipeline_depsgraph = nullptr; + re->pipeline_scene_eval = nullptr; + } + /* Destroy the opengl context in the correct thread. */ + RE_gl_context_destroy(re); + + /* In the case the engine did not mark tiles as finished (un-highlight, which could happen in the + * case of cancelled render) ensure the storage is empty. */ + if (re->highlighted_tiles != nullptr) { + BLI_mutex_lock(&re->highlighted_tiles_mutex); + + /* Rendering is supposed to be finished here, so no new tiles are expected to be written. + * Only make it so possible read-only access to the highlighted tiles is thread-safe. */ + BLI_assert(re->highlighted_tiles); + + BLI_gset_free(re->highlighted_tiles, MEM_freeN); + re->highlighted_tiles = nullptr; + + BLI_mutex_unlock(&re->highlighted_tiles_mutex); + } +} + +void RE_RenderFrame(Render *re, + Main *bmain, + Scene *scene, + ViewLayer *single_layer, + Object *camera_override, + const int frame, + const float subframe, + const bool write_still) +{ + render_callback_exec_id(re, re->main, &scene->id, BKE_CB_EVT_RENDER_INIT); + + /* Ugly global still... + * is to prevent preview events and signal subdivision-surface etc to make full resolution. */ + G.is_rendering = true; + + scene->r.cfra = frame; + scene->r.subframe = subframe; + + if (render_init_from_main(re, &scene->r, bmain, scene, single_layer, camera_override, 0, 0)) { + RenderData rd; + memcpy(&rd, &scene->r, sizeof(rd)); + MEM_reset_peak_memory(); + + render_callback_exec_id(re, re->main, &scene->id, BKE_CB_EVT_RENDER_PRE); + + render_init_depsgraph(re); + + do_render_full_pipeline(re); + + if (write_still && !G.is_break) { + if (BKE_imtype_is_movie(rd.im_format.imtype)) { + /* operator checks this but in case its called from elsewhere */ + printf("Error: can't write single images with a movie format!\n"); + } + else { + char name[FILE_MAX]; + BKE_image_path_from_imformat(name, + rd.pic, + BKE_main_blendfile_path(bmain), + scene->r.cfra, + &rd.im_format, + (rd.scemode & R_EXTENSION) != 0, + false, + nullptr); + + /* reports only used for Movie */ + do_write_image_or_movie(re, bmain, scene, nullptr, 0, name); + } + } + + /* keep after file save */ + render_callback_exec_id(re, re->main, &scene->id, BKE_CB_EVT_RENDER_POST); + if (write_still) { + render_callback_exec_id(re, re->main, &scene->id, BKE_CB_EVT_RENDER_WRITE); + } + } + + render_callback_exec_id(re, + re->main, + &scene->id, + G.is_break ? BKE_CB_EVT_RENDER_CANCEL : BKE_CB_EVT_RENDER_COMPLETE); + + render_pipeline_free(re); + + /* UGLY WARNING */ + G.is_rendering = false; +} + +#ifdef WITH_FREESTYLE + +/* Not freestyle specific, currently only used by free-style. */ +static void change_renderdata_engine(Render *re, const char *new_engine) +{ + if (!STREQ(re->r.engine, new_engine)) { + if (re->engine) { + RE_engine_free(re->engine); + re->engine = nullptr; + } + BLI_strncpy(re->r.engine, new_engine, sizeof(re->r.engine)); + } +} + +static bool use_eevee_for_freestyle_render(Render *re) +{ + RenderEngineType *type = RE_engines_find(re->r.engine); + return !(type->flag & RE_USE_CUSTOM_FREESTYLE); +} + +void RE_RenderFreestyleStrokes(Render *re, Main *bmain, Scene *scene, int render) +{ + re->result_ok = 0; + if (render_init_from_main(re, &scene->r, bmain, scene, nullptr, nullptr, 0, 0)) { + if (render) { + char scene_engine[32]; + BLI_strncpy(scene_engine, re->r.engine, sizeof(scene_engine)); + if (use_eevee_for_freestyle_render(re)) { + change_renderdata_engine(re, RE_engine_id_BLENDER_EEVEE); + } + + RE_engine_render(re, false); + + change_renderdata_engine(re, scene_engine); + } + } + re->result_ok = 1; +} + +void RE_RenderFreestyleExternal(Render *re) +{ + if (re->test_break(re->tbh)) { + return; + } + + FRS_init_stroke_renderer(re); + + LISTBASE_FOREACH (RenderView *, rv, &re->result->views) { + RE_SetActiveRenderView(re, rv->name); + + ViewLayer *active_view_layer = static_cast<ViewLayer *>( + BLI_findlink(&re->view_layers, re->active_view_layer)); + FRS_begin_stroke_rendering(re); + + LISTBASE_FOREACH (ViewLayer *, view_layer, &re->view_layers) { + if ((re->r.scemode & R_SINGLE_LAYER) && view_layer != active_view_layer) { + continue; + } + + if (FRS_is_freestyle_enabled(view_layer)) { + FRS_do_stroke_rendering(re, view_layer); + } + } + + FRS_end_stroke_rendering(re); + } +} +#endif + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Read/Write Render Result (Images & Movies) + * \{ */ + +bool RE_WriteRenderViewsMovie(ReportList *reports, + RenderResult *rr, + Scene *scene, + RenderData *rd, + bMovieHandle *mh, + void **movie_ctx_arr, + const int totvideos, + bool preview) +{ + bool ok = true; + + if (!rr) { + return false; + } + + ImageFormatData image_format; + BKE_image_format_init_for_write(&image_format, scene, nullptr); + + const bool is_mono = BLI_listbase_count_at_most(&rr->views, 2) < 2; + const float dither = scene->r.dither_intensity; + + if (is_mono || (image_format.views_format == R_IMF_VIEWS_INDIVIDUAL)) { + int view_id; + for (view_id = 0; view_id < totvideos; view_id++) { + const char *suffix = BKE_scene_multiview_view_id_suffix_get(&scene->r, view_id); + ImBuf *ibuf = RE_render_result_rect_to_ibuf(rr, &rd->im_format, dither, view_id); + + IMB_colormanagement_imbuf_for_write(ibuf, true, false, &image_format); + + if (!mh->append_movie(movie_ctx_arr[view_id], + rd, + preview ? scene->r.psfra : scene->r.sfra, + scene->r.cfra, + (int *)ibuf->rect, + ibuf->x, + ibuf->y, + suffix, + reports)) { + ok = false; + } + + /* imbuf knows which rects are not part of ibuf */ + IMB_freeImBuf(ibuf); + } + printf("Append frame %d\n", scene->r.cfra); + } + else { /* R_IMF_VIEWS_STEREO_3D */ + const char *names[2] = {STEREO_LEFT_NAME, STEREO_RIGHT_NAME}; + ImBuf *ibuf_arr[3] = {nullptr}; + int i; + + BLI_assert((totvideos == 1) && (image_format.views_format == R_IMF_VIEWS_STEREO_3D)); + + for (i = 0; i < 2; i++) { + int view_id = BLI_findstringindex(&rr->views, names[i], offsetof(RenderView, name)); + ibuf_arr[i] = RE_render_result_rect_to_ibuf(rr, &rd->im_format, dither, view_id); + + IMB_colormanagement_imbuf_for_write(ibuf_arr[i], true, false, &image_format); + } + + ibuf_arr[2] = IMB_stereo3d_ImBuf(&image_format, ibuf_arr[0], ibuf_arr[1]); + + if (!mh->append_movie(movie_ctx_arr[0], + rd, + preview ? scene->r.psfra : scene->r.sfra, + scene->r.cfra, + (int *)ibuf_arr[2]->rect, + ibuf_arr[2]->x, + ibuf_arr[2]->y, + "", + reports)) { + ok = false; + } + + for (i = 0; i < 3; i++) { + /* imbuf knows which rects are not part of ibuf */ + IMB_freeImBuf(ibuf_arr[i]); + } + } + + BKE_image_format_free(&image_format); + + return ok; +} + +static bool do_write_image_or_movie(Render *re, + Main *bmain, + Scene *scene, + bMovieHandle *mh, + const int totvideos, + const char *name_override) +{ + char name[FILE_MAX]; + RenderResult rres; + double render_time; + bool ok = true; + RenderEngineType *re_type = RE_engines_find(re->r.engine); + + /* Only disable file writing if postprocessing is also disabled. */ + const bool do_write_file = !(re_type->flag & RE_USE_NO_IMAGE_SAVE) || + (re_type->flag & RE_USE_POSTPROCESS); + + if (do_write_file) { + RE_AcquireResultImageViews(re, &rres); + + /* write movie or image */ + if (BKE_imtype_is_movie(scene->r.im_format.imtype)) { + RE_WriteRenderViewsMovie( + re->reports, &rres, scene, &re->r, mh, re->movie_ctx_arr, totvideos, false); + } + else { + if (name_override) { + BLI_strncpy(name, name_override, sizeof(name)); + } + else { + BKE_image_path_from_imformat(name, + scene->r.pic, + BKE_main_blendfile_path(bmain), + scene->r.cfra, + &scene->r.im_format, + (scene->r.scemode & R_EXTENSION) != 0, + true, + nullptr); + } + + /* write images as individual images or stereo */ + ok = BKE_image_render_write(re->reports, &rres, scene, true, name); + } + + RE_ReleaseResultImageViews(re, &rres); + } + + render_time = re->i.lastframetime; + re->i.lastframetime = PIL_check_seconds_timer() - re->i.starttime; + + BLI_timecode_string_from_time_simple(name, sizeof(name), re->i.lastframetime); + printf(" Time: %s", name); + + /* Flush stdout to be sure python callbacks are printing stuff after blender. */ + fflush(stdout); + + /* NOTE: using G_MAIN seems valid here??? + * Not sure it's actually even used anyway, we could as well pass nullptr? */ + render_callback_exec_null(re, G_MAIN, BKE_CB_EVT_RENDER_STATS); + + if (do_write_file) { + BLI_timecode_string_from_time_simple(name, sizeof(name), re->i.lastframetime - render_time); + printf(" (Saving: %s)\n", name); + } + + fputc('\n', stdout); + fflush(stdout); + + return ok; +} + +static void get_videos_dimensions(const Render *re, + const RenderData *rd, + size_t *r_width, + size_t *r_height) +{ + size_t width, height; + if (re->r.mode & R_BORDER) { + if ((re->r.mode & R_CROP) == 0) { + width = re->winx; + height = re->winy; + } + else { + width = re->rectx; + height = re->recty; + } + } + else { + width = re->rectx; + height = re->recty; + } + + BKE_scene_multiview_videos_dimensions_get(rd, width, height, r_width, r_height); +} + +static void re_movie_free_all(Render *re, bMovieHandle *mh, int totvideos) +{ + int i; + + for (i = 0; i < totvideos; i++) { + mh->end_movie(re->movie_ctx_arr[i]); + mh->context_free(re->movie_ctx_arr[i]); + } + + MEM_SAFE_FREE(re->movie_ctx_arr); +} + +void RE_RenderAnim(Render *re, + Main *bmain, + Scene *scene, + ViewLayer *single_layer, + Object *camera_override, + int sfra, + int efra, + int tfra) +{ + /* Call hooks before taking a copy of scene->r, so user can alter the render settings prior to + * copying (e.g. alter the output path). */ + render_callback_exec_id(re, re->main, &scene->id, BKE_CB_EVT_RENDER_INIT); + + RenderData rd; + memcpy(&rd, &scene->r, sizeof(rd)); + bMovieHandle *mh = nullptr; + const int cfra_old = rd.cfra; + const float subframe_old = rd.subframe; + int nfra, totrendered = 0, totskipped = 0; + const int totvideos = BKE_scene_multiview_num_videos_get(&rd); + const bool is_movie = BKE_imtype_is_movie(rd.im_format.imtype); + const bool is_multiview_name = ((rd.scemode & R_MULTIVIEW) != 0 && + (rd.im_format.views_format == R_IMF_VIEWS_INDIVIDUAL)); + + /* do not fully call for each frame, it initializes & pops output window */ + if (!render_init_from_main(re, &rd, bmain, scene, single_layer, camera_override, 0, 1)) { + return; + } + + RenderEngineType *re_type = RE_engines_find(re->r.engine); + + /* Only disable file writing if postprocessing is also disabled. */ + const bool do_write_file = !(re_type->flag & RE_USE_NO_IMAGE_SAVE) || + (re_type->flag & RE_USE_POSTPROCESS); + + render_init_depsgraph(re); + + if (is_movie && do_write_file) { + size_t width, height; + int i; + bool is_error = false; + + get_videos_dimensions(re, &rd, &width, &height); + + mh = BKE_movie_handle_get(rd.im_format.imtype); + if (mh == nullptr) { + BKE_report(re->reports, RPT_ERROR, "Movie format unsupported"); + return; + } + + re->movie_ctx_arr = MEM_cnew_array<void *>(totvideos, "Movies' Context"); + + for (i = 0; i < totvideos; i++) { + const char *suffix = BKE_scene_multiview_view_id_suffix_get(&re->r, i); + + re->movie_ctx_arr[i] = mh->context_create(); + + if (!mh->start_movie(re->movie_ctx_arr[i], + re->pipeline_scene_eval, + &re->r, + width, + height, + re->reports, + false, + suffix)) { + is_error = true; + break; + } + } + + if (is_error) { + /* report is handled above */ + re_movie_free_all(re, mh, i + 1); + render_pipeline_free(re); + return; + } + } + + /* Ugly global still... is to prevent renderwin events and signal subdivision-surface etc + * to make full resolution is also set by caller renderwin.c */ + G.is_rendering = true; + + re->flag |= R_ANIMATION; + + { + scene->r.subframe = 0.0f; + for (nfra = sfra, scene->r.cfra = sfra; scene->r.cfra <= efra; scene->r.cfra++) { + char name[FILE_MAX]; + + /* A feedback loop exists here -- render initialization requires updated + * render layers settings which could be animated, but scene evaluation for + * the frame happens later because it depends on what layers are visible to + * render engine. + * + * The idea here is to only evaluate animation data associated with the scene, + * which will make sure render layer settings are up-to-date, initialize the + * render database itself and then perform full scene update with only needed + * layers. + * -sergey- + */ + { + float ctime = BKE_scene_ctime_get(scene); + AnimData *adt = BKE_animdata_from_id(&scene->id); + const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct( + re->pipeline_depsgraph, ctime); + BKE_animsys_evaluate_animdata(&scene->id, adt, &anim_eval_context, ADT_RECALC_ALL, false); + } + + render_update_depsgraph(re); + + /* Only border now, TODO(ton): camera lens. */ + render_init_from_main(re, &rd, bmain, scene, single_layer, camera_override, 1, 0); + + if (nfra != scene->r.cfra) { + /* Skip this frame, but could update for physics and particles system. */ + continue; + } + + nfra += tfra; + + /* Touch/NoOverwrite options are only valid for image's */ + if (is_movie == false && do_write_file) { + if (rd.mode & (R_NO_OVERWRITE | R_TOUCH)) { + BKE_image_path_from_imformat(name, + rd.pic, + BKE_main_blendfile_path(bmain), + scene->r.cfra, + &rd.im_format, + (rd.scemode & R_EXTENSION) != 0, + true, + nullptr); + } + + if (rd.mode & R_NO_OVERWRITE) { + if (!is_multiview_name) { + if (BLI_exists(name)) { + printf("skipping existing frame \"%s\"\n", name); + totskipped++; + continue; + } + } + else { + bool is_skip = false; + char filepath[FILE_MAX]; + + LISTBASE_FOREACH (SceneRenderView *, srv, &scene->r.views) { + if (!BKE_scene_multiview_is_render_view_active(&scene->r, srv)) { + continue; + } + + BKE_scene_multiview_filepath_get(srv, name, filepath); + + if (BLI_exists(filepath)) { + is_skip = true; + printf("skipping existing frame \"%s\" for view \"%s\"\n", filepath, srv->name); + } + } + + if (is_skip) { + totskipped++; + continue; + } + } + } + + if (rd.mode & R_TOUCH) { + if (!is_multiview_name) { + if (!BLI_exists(name)) { + BLI_make_existing_file(name); /* makes the dir if its not there */ + BLI_file_touch(name); + } + } + else { + char filepath[FILE_MAX]; + + LISTBASE_FOREACH (SceneRenderView *, srv, &scene->r.views) { + if (!BKE_scene_multiview_is_render_view_active(&scene->r, srv)) { + continue; + } + + BKE_scene_multiview_filepath_get(srv, name, filepath); + + if (!BLI_exists(filepath)) { + BLI_make_existing_file(filepath); /* makes the dir if its not there */ + BLI_file_touch(filepath); + } + } + } + } + } + + re->r.cfra = scene->r.cfra; /* weak.... */ + re->r.subframe = scene->r.subframe; + + /* run callbacks before rendering, before the scene is updated */ + render_callback_exec_id(re, re->main, &scene->id, BKE_CB_EVT_RENDER_PRE); + + do_render_full_pipeline(re); + totrendered++; + + if (re->test_break(re->tbh) == 0) { + if (!G.is_break) { + if (!do_write_image_or_movie(re, bmain, scene, mh, totvideos, nullptr)) { + G.is_break = true; + } + } + } + else { + G.is_break = true; + } + + if (G.is_break == true) { + /* remove touched file */ + if (is_movie == false && do_write_file) { + if (rd.mode & R_TOUCH) { + if (!is_multiview_name) { + if ((BLI_file_size(name) == 0)) { + /* BLI_exists(name) is implicit */ + BLI_delete(name, false, false); + } + } + else { + char filepath[FILE_MAX]; + + LISTBASE_FOREACH (SceneRenderView *, srv, &scene->r.views) { + if (!BKE_scene_multiview_is_render_view_active(&scene->r, srv)) { + continue; + } + + BKE_scene_multiview_filepath_get(srv, name, filepath); + + if ((BLI_file_size(filepath) == 0)) { + /* BLI_exists(filepath) is implicit */ + BLI_delete(filepath, false, false); + } + } + } + } + } + + break; + } + + if (G.is_break == false) { + /* keep after file save */ + render_callback_exec_id(re, re->main, &scene->id, BKE_CB_EVT_RENDER_POST); + render_callback_exec_id(re, re->main, &scene->id, BKE_CB_EVT_RENDER_WRITE); + } + } + } + + /* end movie */ + if (is_movie && do_write_file) { + re_movie_free_all(re, mh, totvideos); + } + + if (totskipped && totrendered == 0) { + BKE_report(re->reports, RPT_INFO, "No frames rendered, skipped to not overwrite"); + } + + scene->r.cfra = cfra_old; + scene->r.subframe = subframe_old; + + re->flag &= ~R_ANIMATION; + + render_callback_exec_id(re, + re->main, + &scene->id, + G.is_break ? BKE_CB_EVT_RENDER_CANCEL : BKE_CB_EVT_RENDER_COMPLETE); + BKE_sound_reset_scene_specs(re->pipeline_scene_eval); + + render_pipeline_free(re); + + /* UGLY WARNING */ + G.is_rendering = false; +} + +void RE_PreviewRender(Render *re, Main *bmain, Scene *sce) +{ + Object *camera; + int winx, winy; + + BKE_render_resolution(&sce->r, false, &winx, &winy); + + RE_InitState(re, nullptr, &sce->r, &sce->view_layers, nullptr, winx, winy, nullptr); + + re->main = bmain; + re->scene = sce; + + camera = RE_GetCamera(re); + RE_SetCamera(re, camera); + + RE_engine_render(re, false); + + /* No persistent data for preview render. */ + if (re->engine) { + RE_engine_free(re->engine); + re->engine = nullptr; + } +} + +/* NOTE: repeated win/disprect calc... solve that nicer, also in compo. */ + +bool RE_ReadRenderResult(Scene *scene, Scene *scenode) +{ + Render *re; + int winx, winy; + bool success; + rcti disprect; + + /* calculate actual render result and display size */ + BKE_render_resolution(&scene->r, false, &winx, &winy); + + /* only in movie case we render smaller part */ + if (scene->r.mode & R_BORDER) { + disprect.xmin = scene->r.border.xmin * winx; + disprect.xmax = scene->r.border.xmax * winx; + + disprect.ymin = scene->r.border.ymin * winy; + disprect.ymax = scene->r.border.ymax * winy; + } + else { + disprect.xmin = disprect.ymin = 0; + disprect.xmax = winx; + disprect.ymax = winy; + } + + if (scenode) { + scene = scenode; + } + + /* get render: it can be called from UI with draw callbacks */ + re = RE_GetSceneRender(scene); + if (re == nullptr) { + re = RE_NewSceneRender(scene); + } + RE_InitState(re, nullptr, &scene->r, &scene->view_layers, nullptr, winx, winy, &disprect); + re->scene = scene; + + BLI_rw_mutex_lock(&re->resultmutex, THREAD_LOCK_WRITE); + success = render_result_exr_file_cache_read(re); + BLI_rw_mutex_unlock(&re->resultmutex); + + render_result_uncrop(re); + + return success; +} + +void RE_layer_load_from_file( + RenderLayer *layer, ReportList *reports, const char *filepath, int x, int y) +{ + /* OCIO_TODO: assume layer was saved in default color space */ + ImBuf *ibuf = IMB_loadiffname(filepath, IB_rect, nullptr); + RenderPass *rpass = nullptr; + + /* multiview: since the API takes no 'view', we use the first combined pass found */ + for (rpass = static_cast<RenderPass *>(layer->passes.first); rpass; rpass = rpass->next) { + if (STREQ(rpass->name, RE_PASSNAME_COMBINED)) { + break; + } + } + + if (rpass == nullptr) { + BKE_reportf(reports, + RPT_ERROR, + "%s: no Combined pass found in the render layer '%s'", + __func__, + filepath); + } + + if (ibuf && (ibuf->rect || ibuf->rect_float)) { + if (ibuf->x == layer->rectx && ibuf->y == layer->recty) { + if (ibuf->rect_float == nullptr) { + IMB_float_from_rect(ibuf); + } + + memcpy(rpass->rect, ibuf->rect_float, sizeof(float[4]) * layer->rectx * layer->recty); + } + else { + if ((ibuf->x - x >= layer->rectx) && (ibuf->y - y >= layer->recty)) { + ImBuf *ibuf_clip; + + if (ibuf->rect_float == nullptr) { + IMB_float_from_rect(ibuf); + } + + ibuf_clip = IMB_allocImBuf(layer->rectx, layer->recty, 32, IB_rectfloat); + if (ibuf_clip) { + IMB_rectcpy(ibuf_clip, ibuf, 0, 0, x, y, layer->rectx, layer->recty); + + memcpy( + rpass->rect, ibuf_clip->rect_float, sizeof(float[4]) * layer->rectx * layer->recty); + IMB_freeImBuf(ibuf_clip); + } + else { + BKE_reportf( + reports, RPT_ERROR, "%s: failed to allocate clip buffer '%s'", __func__, filepath); + } + } + else { + BKE_reportf(reports, + RPT_ERROR, + "%s: incorrect dimensions for partial copy '%s'", + __func__, + filepath); + } + } + + IMB_freeImBuf(ibuf); + } + else { + BKE_reportf(reports, RPT_ERROR, "%s: failed to load '%s'", __func__, filepath); + } +} + +void RE_result_load_from_file(RenderResult *result, ReportList *reports, const char *filepath) +{ + if (!render_result_exr_file_read_path(result, nullptr, filepath)) { + BKE_reportf(reports, RPT_ERROR, "%s: failed to load '%s'", __func__, filepath); + return; + } +} + +bool RE_layers_have_name(struct RenderResult *result) +{ + switch (BLI_listbase_count_at_most(&result->layers, 2)) { + case 0: + return false; + case 1: + return (((RenderLayer *)result->layers.first)->name[0] != '\0'); + default: + return true; + } + return false; +} + +bool RE_passes_have_name(struct RenderLayer *rl) +{ + LISTBASE_FOREACH (RenderPass *, rp, &rl->passes) { + if (!STREQ(rp->name, "Combined")) { + return true; + } + } + + return false; +} + +RenderPass *RE_pass_find_by_name(RenderLayer *rl, const char *name, const char *viewname) +{ + LISTBASE_FOREACH_BACKWARD (RenderPass *, rp, &rl->passes) { + if (STREQ(rp->name, name)) { + if (viewname == nullptr || viewname[0] == '\0') { + return rp; + } + if (STREQ(rp->view, viewname)) { + return rp; + } + } + } + return nullptr; +} + +RenderPass *RE_pass_find_by_type(RenderLayer *rl, int passtype, const char *viewname) +{ +#define CHECK_PASS(NAME) \ + if (passtype == SCE_PASS_##NAME) { \ + return RE_pass_find_by_name(rl, RE_PASSNAME_##NAME, viewname); \ + } \ + ((void)0) + + CHECK_PASS(COMBINED); + CHECK_PASS(Z); + CHECK_PASS(VECTOR); + CHECK_PASS(NORMAL); + CHECK_PASS(UV); + CHECK_PASS(EMIT); + CHECK_PASS(SHADOW); + CHECK_PASS(AO); + CHECK_PASS(ENVIRONMENT); + CHECK_PASS(INDEXOB); + CHECK_PASS(INDEXMA); + CHECK_PASS(MIST); + CHECK_PASS(DIFFUSE_DIRECT); + CHECK_PASS(DIFFUSE_INDIRECT); + CHECK_PASS(DIFFUSE_COLOR); + CHECK_PASS(GLOSSY_DIRECT); + CHECK_PASS(GLOSSY_INDIRECT); + CHECK_PASS(GLOSSY_COLOR); + CHECK_PASS(TRANSM_DIRECT); + CHECK_PASS(TRANSM_INDIRECT); + CHECK_PASS(TRANSM_COLOR); + CHECK_PASS(SUBSURFACE_DIRECT); + CHECK_PASS(SUBSURFACE_INDIRECT); + CHECK_PASS(SUBSURFACE_COLOR); + +#undef CHECK_PASS + + return nullptr; +} + +RenderPass *RE_create_gp_pass(RenderResult *rr, const char *layername, const char *viewname) +{ + RenderLayer *rl = RE_GetRenderLayer(rr, layername); + /* only create render layer if not exist */ + if (!rl) { + rl = MEM_cnew<RenderLayer>(layername); + BLI_addtail(&rr->layers, rl); + BLI_strncpy(rl->name, layername, sizeof(rl->name)); + rl->layflag = SCE_LAY_SOLID; + rl->passflag = SCE_PASS_COMBINED; + rl->rectx = rr->rectx; + rl->recty = rr->recty; + } + + /* Clear previous pass if exist or the new image will be over previous one. */ + RenderPass *rp = RE_pass_find_by_name(rl, RE_PASSNAME_COMBINED, viewname); + if (rp) { + if (rp->rect) { + MEM_freeN(rp->rect); + } + BLI_freelinkN(&rl->passes, rp); + } + /* create a totally new pass */ + return render_layer_add_pass(rr, rl, 4, RE_PASSNAME_COMBINED, viewname, "RGBA", true); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Miscellaneous Public Render API + * \{ */ + +bool RE_allow_render_generic_object(Object *ob) +{ + /* override not showing object when duplis are used with particles */ + if (ob->transflag & OB_DUPLIPARTS) { + /* pass */ /* let particle system(s) handle showing vs. not showing */ + } + else if (ob->transflag & OB_DUPLI) { + return false; + } + return true; +} + +void RE_init_threadcount(Render *re) +{ + re->r.threads = BKE_render_num_threads(&re->r); +} + +/** \} */ |