This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/editors/render/render_internal.cc
Monique Dewanchand 68589a31eb ViewLayer: Lazy sync of scene data.
When a change happens which invalidates view layers the syncing will be postponed until the first usage.
This will improve importing or adding many objects in a single operation/script.

`BKE_view_layer_need_resync_tag` is used to tag the view layer to be out of sync. Before accessing
`BKE_view_layer_active_base_get`, `BKE_view_layer_active_object_get`, `BKE_view_layer_active_collection`
or `BKE_view_layer_object_bases` the caller should call `BKE_view_layer_synced_ensure`.

Having two functions ensures that partial syncing could be added as smaller patches in the future. Tagging a
view layer out of sync could be replaced with a partial sync. Eventually the number of full resyncs could be
reduced. After all tagging has been replaced with partial syncs the ensure_sync could be phased out.

This patch has been added to discuss the details and consequences of the current approach. For clarity
the call to BKE_view_layer_ensure_sync is placed close to the getters.
In the future this could be placed in more strategical places to reduce the number of calls or improve
performance. Finding those strategical places isn't that clear. When multiple operations are grouped
in a single script you might want to always check for resync.

Some areas found that can be improved. This list isn't complete.
These areas aren't addressed by this patch as these changes would be hard to detect to the reviewer.
The idea is to add changes to these areas as a separate patch. It might be that the initial commit would reduce
performance compared to master, but will be fixed by the additional patches.

**Object duplication**
During object duplication the syncing is temporarily disabled. With this patch this isn't useful as when disabled
the view_layer is accessed to locate bases. This can be improved by first locating the source bases, then duplicate
and sync and locate the new bases. Will be solved in a separate patch for clarity reasons ({D15886}).

**Object add**
`BKE_object_add` not only adds a new object, but also selects and activates the new base. This requires the
view_layer to be resynced. Some callers reverse the selection and activation (See `get_new_constraint_target`).
We should make the selection and activation optional. This would make it possible to add multiple objects
without having to resync per object.

**Postpone Activate Base**
Setting the basact is done in many locations. They follow a rule as after an action find the base and set
the basact. Finding the base could require a resync. The idea is to store in the view_layer the object which
base will be set in the basact during the next sync, reducing the times resyncing needs to happen.

Reviewed By: mont29

Maniphest Tasks: T73411

Differential Revision: https://developer.blender.org/D15885
2022-09-14 21:34:38 +02:00

1213 lines
36 KiB
C++

/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2008 Blender Foundation. All rights reserved. */
/** \file
* \ingroup edrend
*/
#include <cmath>
#include <cstddef>
#include <cstring>
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLI_rect.h"
#include "BLI_threads.h"
#include "BLI_timecode.h"
#include "BLI_utildefines.h"
#include "PIL_time.h"
#include "BLT_translation.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_userdef_types.h"
#include "DNA_view3d_types.h"
#include "BKE_colortools.h"
#include "BKE_context.h"
#include "BKE_global.h"
#include "BKE_image.h"
#include "BKE_image_format.h"
#include "BKE_layer.h"
#include "BKE_lib_id.h"
#include "BKE_main.h"
#include "BKE_node.h"
#include "BKE_node_tree_update.h"
#include "BKE_object.h"
#include "BKE_report.h"
#include "BKE_scene.h"
#include "BKE_screen.h"
#include "NOD_composite.h"
#include "DEG_depsgraph.h"
#include "WM_api.h"
#include "WM_types.h"
#include "ED_render.h"
#include "ED_screen.h"
#include "ED_util.h"
#include "BIF_glutil.h"
#include "RE_engine.h"
#include "RE_pipeline.h"
#include "IMB_colormanagement.h"
#include "IMB_imbuf_types.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "SEQ_relations.h"
#include "render_intern.hh"
/* Render Callbacks */
static int render_break(void *rjv);
struct RenderJob {
Main *main;
Scene *scene;
ViewLayer *single_layer;
Scene *current_scene;
/* TODO(sergey): Should not be needed once engine will have its own
* depsgraph and copy-on-write will be implemented.
*/
Depsgraph *depsgraph;
Render *re;
struct Object *camera_override;
bool v3d_override;
bool anim, write_still;
Image *image;
ImageUser iuser;
bool image_outdated;
short *stop;
short *do_update;
float *progress;
ReportList *reports;
int orig_layer;
int last_layer;
ScrArea *area;
ColorManagedViewSettings view_settings;
ColorManagedDisplaySettings display_settings;
bool supports_glsl_draw;
bool interface_locked;
};
/* called inside thread! */
static bool image_buffer_calc_tile_rect(const RenderResult *rr,
const ImBuf *ibuf,
rcti *renrect,
rcti *r_ibuf_rect,
int *r_offset_x,
int *r_offset_y)
{
int tile_y, tile_height, tile_x, tile_width;
/* When `renrect` argument is not nullptr, we only refresh scan-lines. */
if (renrect) {
/* `if (tile_height == recty)`, rendering of layer is ready,
* we should not draw, other things happen... */
if (rr->renlay == nullptr || renrect->ymax >= rr->recty) {
return false;
}
/* `tile_x` here is first sub-rectangle x coord, tile_width defines sub-rectangle width. */
tile_x = renrect->xmin;
tile_width = renrect->xmax - tile_x;
if (tile_width < 2) {
return false;
}
tile_y = renrect->ymin;
tile_height = renrect->ymax - tile_y;
if (tile_height < 2) {
return false;
}
renrect->ymin = renrect->ymax;
}
else {
tile_x = tile_y = 0;
tile_width = rr->rectx;
tile_height = rr->recty;
}
/* tile_x tile_y is in tile coords. transform to ibuf */
int offset_x = rr->tilerect.xmin;
if (offset_x >= ibuf->x) {
return false;
}
int offset_y = rr->tilerect.ymin;
if (offset_y >= ibuf->y) {
return false;
}
if (offset_x + tile_width > ibuf->x) {
tile_width = ibuf->x - offset_x;
}
if (offset_y + tile_height > ibuf->y) {
tile_height = ibuf->y - offset_y;
}
if (tile_width < 1 || tile_height < 1) {
return false;
}
r_ibuf_rect->xmax = tile_x + tile_width;
r_ibuf_rect->ymax = tile_y + tile_height;
r_ibuf_rect->xmin = tile_x;
r_ibuf_rect->ymin = tile_y;
*r_offset_x = offset_x;
*r_offset_y = offset_y;
return true;
}
static void image_buffer_rect_update(RenderJob *rj,
RenderResult *rr,
ImBuf *ibuf,
ImageUser *iuser,
const rcti *tile_rect,
int offset_x,
int offset_y,
const char *viewname)
{
Scene *scene = rj->scene;
const float *rectf = nullptr;
int linear_stride, linear_offset_x, linear_offset_y;
ColorManagedViewSettings *view_settings;
ColorManagedDisplaySettings *display_settings;
if (ibuf->userflags & IB_DISPLAY_BUFFER_INVALID) {
/* The whole image buffer is to be color managed again anyway. */
return;
}
/* The thing here is, the logic below (which was default behavior
* of how rectf is acquiring since forever) gives float buffer for
* composite output only. This buffer can not be used for other
* passes obviously.
*
* We might try finding corresponding for pass buffer in render result
* (which is actually missing when rendering with Cycles, who only
* writes all the passes when the tile is finished) or use float
* buffer from image buffer as reference, which is easier to use and
* contains all the data we need anyway.
* - sergey -
*/
/* TODO(sergey): Need to check has_combined here? */
if (iuser->pass == 0) {
RenderView *rv;
const int view_id = BKE_scene_multiview_view_id_get(&scene->r, viewname);
rv = RE_RenderViewGetById(rr, view_id);
/* find current float rect for display, first case is after composite... still weak */
if (rv->rectf) {
rectf = rv->rectf;
}
else {
if (rv->rect32) {
/* special case, currently only happens with sequencer rendering,
* which updates the whole frame, so we can only mark display buffer
* as invalid here (sergey)
*/
ibuf->userflags |= IB_DISPLAY_BUFFER_INVALID;
return;
}
if (rr->renlay == nullptr) {
return;
}
rectf = RE_RenderLayerGetPass(rr->renlay, RE_PASSNAME_COMBINED, viewname);
}
if (rectf == nullptr) {
return;
}
rectf += 4 * (rr->rectx * tile_rect->ymin + tile_rect->xmin);
linear_stride = rr->rectx;
linear_offset_x = offset_x;
linear_offset_y = offset_y;
}
else {
rectf = ibuf->rect_float;
linear_stride = ibuf->x;
linear_offset_x = 0;
linear_offset_y = 0;
}
view_settings = &scene->view_settings;
display_settings = &scene->display_settings;
IMB_partial_display_buffer_update(ibuf,
rectf,
nullptr,
linear_stride,
linear_offset_x,
linear_offset_y,
view_settings,
display_settings,
offset_x,
offset_y,
offset_x + BLI_rcti_size_x(tile_rect),
offset_y + BLI_rcti_size_y(tile_rect));
}
/* ****************************** render invoking ***************** */
/* set callbacks, exported to sequence render too.
* Only call in foreground (UI) renders. */
static void screen_render_single_layer_set(
wmOperator *op, Main *mainp, ViewLayer *active_layer, Scene **scene, ViewLayer **single_layer)
{
/* single layer re-render */
if (RNA_struct_property_is_set(op->ptr, "scene")) {
Scene *scn;
char scene_name[MAX_ID_NAME - 2];
RNA_string_get(op->ptr, "scene", scene_name);
scn = (Scene *)BLI_findstring(&mainp->scenes, scene_name, offsetof(ID, name) + 2);
if (scn) {
/* camera switch won't have updated */
scn->r.cfra = (*scene)->r.cfra;
BKE_scene_camera_switch_update(scn);
*scene = scn;
}
}
if (RNA_struct_property_is_set(op->ptr, "layer")) {
ViewLayer *rl;
char rl_name[RE_MAXNAME];
RNA_string_get(op->ptr, "layer", rl_name);
rl = (ViewLayer *)BLI_findstring(&(*scene)->view_layers, rl_name, offsetof(ViewLayer, name));
if (rl) {
*single_layer = rl;
}
}
else if (((*scene)->r.scemode & R_SINGLE_LAYER) && active_layer) {
*single_layer = active_layer;
}
}
/* executes blocking render */
static int screen_render_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
RenderEngineType *re_type = RE_engines_find(scene->r.engine);
ViewLayer *active_layer = CTX_data_view_layer(C);
ViewLayer *single_layer = nullptr;
Render *re;
Image *ima;
View3D *v3d = CTX_wm_view3d(C);
Main *mainp = CTX_data_main(C);
const bool is_animation = RNA_boolean_get(op->ptr, "animation");
const bool is_write_still = RNA_boolean_get(op->ptr, "write_still");
struct Object *camera_override = v3d ? V3D_CAMERA_LOCAL(v3d) : nullptr;
/* Cannot do render if there is not this function. */
if (re_type->render == nullptr) {
return OPERATOR_CANCELLED;
}
/* custom scene and single layer re-render */
screen_render_single_layer_set(op, mainp, active_layer, &scene, &single_layer);
if (!is_animation && is_write_still && BKE_imtype_is_movie(scene->r.im_format.imtype)) {
BKE_report(
op->reports, RPT_ERROR, "Cannot write a single file with an animation format selected");
return OPERATOR_CANCELLED;
}
re = RE_NewSceneRender(scene);
G.is_break = false;
RE_draw_lock_cb(re, nullptr, nullptr);
RE_test_break_cb(re, nullptr, render_break);
ima = BKE_image_ensure_viewer(mainp, IMA_TYPE_R_RESULT, "Render Result");
BKE_image_signal(mainp, ima, nullptr, IMA_SIGNAL_FREE);
BKE_image_backup_render(scene, ima, true);
/* cleanup sequencer caches before starting user triggered render.
* otherwise, invalidated cache entries can make their way into
* the output rendering. We can't put that into RE_RenderFrame,
* since sequence rendering can call that recursively... (peter) */
SEQ_cache_cleanup(scene);
RE_SetReports(re, op->reports);
if (is_animation) {
RE_RenderAnim(re,
mainp,
scene,
single_layer,
camera_override,
scene->r.sfra,
scene->r.efra,
scene->r.frame_step);
}
else {
RE_RenderFrame(re,
mainp,
scene,
single_layer,
camera_override,
scene->r.cfra,
scene->r.subframe,
is_write_still);
}
RE_SetReports(re, nullptr);
/* No redraw needed, we leave state as we entered it. */
ED_update_for_newframe(mainp, CTX_data_depsgraph_pointer(C));
WM_event_add_notifier(C, NC_SCENE | ND_RENDER_RESULT, scene);
return OPERATOR_FINISHED;
}
static void render_freejob(void *rjv)
{
RenderJob *rj = static_cast<RenderJob *>(rjv);
BKE_color_managed_view_settings_free(&rj->view_settings);
MEM_freeN(rj);
}
/* str is IMA_MAX_RENDER_TEXT in size */
static void make_renderinfo_string(const RenderStats *rs,
const Scene *scene,
const bool v3d_override,
const char *error,
char *str)
{
char info_time_str[32]; /* used to be extern to header_info.c */
uintptr_t mem_in_use, peak_memory;
float megs_used_memory, megs_peak_memory;
char *spos = str;
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);
/* local view */
if (rs->localview) {
spos += sprintf(spos, "%s | ", TIP_("3D Local View"));
}
else if (v3d_override) {
spos += sprintf(spos, "%s | ", TIP_("3D View"));
}
/* frame number */
spos += sprintf(spos, TIP_("Frame:%d "), (scene->r.cfra));
/* previous and elapsed time */
BLI_timecode_string_from_time_simple(info_time_str, sizeof(info_time_str), rs->lastframetime);
if (rs->infostr && rs->infostr[0]) {
if (rs->lastframetime != 0.0) {
spos += sprintf(spos, TIP_("| Last:%s "), info_time_str);
}
else {
spos += sprintf(spos, "| ");
}
BLI_timecode_string_from_time_simple(
info_time_str, sizeof(info_time_str), PIL_check_seconds_timer() - rs->starttime);
}
else {
spos += sprintf(spos, "| ");
}
spos += sprintf(spos, TIP_("Time:%s "), info_time_str);
/* statistics */
if (rs->statstr) {
if (rs->statstr[0]) {
spos += sprintf(spos, "| %s ", rs->statstr);
}
}
else {
if (rs->mem_peak == 0.0f) {
spos += sprintf(spos, TIP_("| Mem:%.2fM (Peak %.2fM) "), megs_used_memory, megs_peak_memory);
}
else {
spos += sprintf(spos, TIP_("| Mem:%.2fM, Peak: %.2fM "), rs->mem_used, rs->mem_peak);
}
}
/* extra info */
if (rs->infostr && rs->infostr[0]) {
spos += sprintf(spos, "| %s ", rs->infostr);
}
else if (error && error[0]) {
spos += sprintf(spos, "| %s ", error);
}
/* very weak... but 512 characters is quite safe */
if (spos >= str + IMA_MAX_RENDER_TEXT) {
if (G.debug & G_DEBUG) {
printf("WARNING! renderwin text beyond limit\n");
}
}
}
static void image_renderinfo_cb(void *rjv, RenderStats *rs)
{
RenderJob *rj = static_cast<RenderJob *>(rjv);
RenderResult *rr;
rr = RE_AcquireResultRead(rj->re);
if (rr) {
/* malloc OK here, stats_draw is not in tile threads */
if (rr->text == nullptr) {
rr->text = static_cast<char *>(MEM_callocN(IMA_MAX_RENDER_TEXT, "rendertext"));
}
make_renderinfo_string(rs, rj->scene, rj->v3d_override, rr->error, rr->text);
}
RE_ReleaseResult(rj->re);
/* make jobs timer to send notifier */
*(rj->do_update) = true;
}
static void render_progress_update(void *rjv, float progress)
{
RenderJob *rj = static_cast<RenderJob *>(rjv);
if (rj->progress && *rj->progress != progress) {
*rj->progress = progress;
/* make jobs timer to send notifier */
*(rj->do_update) = true;
}
}
/* Not totally reliable, but works fine in most of cases and
* in worst case would just make it so extra color management
* for the whole render result is applied (which was already
* happening already).
*/
static void render_image_update_pass_and_layer(RenderJob *rj, RenderResult *rr, ImageUser *iuser)
{
wmWindowManager *wm;
ScrArea *first_area = nullptr, *matched_area = nullptr;
/* image window, compo node users */
for (wm = static_cast<wmWindowManager *>(rj->main->wm.first); wm && matched_area == nullptr;
wm = static_cast<wmWindowManager *>(wm->id.next)) { /* only 1 wm */
wmWindow *win;
for (win = static_cast<wmWindow *>(wm->windows.first); win && matched_area == nullptr;
win = win->next) {
const bScreen *screen = WM_window_get_active_screen(win);
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
if (area->spacetype == SPACE_IMAGE) {
SpaceImage *sima = static_cast<SpaceImage *>(area->spacedata.first);
/* area->spacedata might be empty when toggling full-screen mode. */
if (sima != nullptr && sima->image == rj->image) {
if (first_area == nullptr) {
first_area = area;
}
if (area == rj->area) {
matched_area = area;
break;
}
}
}
}
}
}
if (matched_area == nullptr) {
matched_area = first_area;
}
if (matched_area) {
SpaceImage *sima = static_cast<SpaceImage *>(matched_area->spacedata.first);
RenderResult *main_rr = RE_AcquireResultRead(rj->re);
/* TODO(sergey): is there faster way to get the layer index? */
if (rr->renlay) {
int layer = BLI_findstringindex(
&main_rr->layers, (char *)rr->renlay->name, offsetof(RenderLayer, name));
sima->iuser.layer = layer;
rj->last_layer = layer;
}
iuser->pass = sima->iuser.pass;
iuser->layer = sima->iuser.layer;
RE_ReleaseResult(rj->re);
}
}
static void image_rect_update(void *rjv, RenderResult *rr, rcti *renrect)
{
RenderJob *rj = static_cast<RenderJob *>(rjv);
Image *ima = rj->image;
ImBuf *ibuf;
void *lock;
const char *viewname = RE_GetActiveRenderView(rj->re);
/* only update if we are displaying the slot being rendered */
if (ima->render_slot != ima->last_render_slot) {
rj->image_outdated = true;
return;
}
if (rj->image_outdated) {
/* Free all render buffer caches when switching slots, with lock to ensure main
* thread is not drawing the buffer at the same time. */
rj->image_outdated = false;
ibuf = BKE_image_acquire_ibuf(ima, &rj->iuser, &lock);
BKE_image_free_buffers(ima);
BKE_image_release_ibuf(ima, ibuf, lock);
*(rj->do_update) = true;
return;
}
if (rr == nullptr) {
return;
}
/* update part of render */
render_image_update_pass_and_layer(rj, rr, &rj->iuser);
rcti tile_rect;
int offset_x;
int offset_y;
ibuf = BKE_image_acquire_ibuf(ima, &rj->iuser, &lock);
if (ibuf) {
if (!image_buffer_calc_tile_rect(rr, ibuf, renrect, &tile_rect, &offset_x, &offset_y)) {
BKE_image_release_ibuf(ima, ibuf, lock);
return;
}
/* Don't waste time on CPU side color management if
* image will be displayed using GLSL.
*
* Need to update rect if Save Buffers enabled because in
* this case GLSL doesn't have original float buffer to
* operate with.
*/
if (!rj->supports_glsl_draw || ibuf->channels == 1 ||
ED_draw_imbuf_method(ibuf) != IMAGE_DRAW_METHOD_GLSL) {
image_buffer_rect_update(rj, rr, ibuf, &rj->iuser, &tile_rect, offset_x, offset_y, viewname);
}
ImageTile *image_tile = BKE_image_get_tile(ima, 0);
BKE_image_update_gputexture_delayed(ima,
image_tile,
ibuf,
offset_x,
offset_y,
BLI_rcti_size_x(&tile_rect),
BLI_rcti_size_y(&tile_rect));
/* make jobs timer to send notifier */
*(rj->do_update) = true;
}
BKE_image_release_ibuf(ima, ibuf, lock);
}
static void current_scene_update(void *rjv, Scene *scene)
{
RenderJob *rj = static_cast<RenderJob *>(rjv);
if (rj->current_scene != scene) {
/* Image must be updated when rendered scene changes. */
BKE_image_partial_update_mark_full_update(rj->image);
}
rj->current_scene = scene;
rj->iuser.scene = scene;
}
static void render_startjob(void *rjv, short *stop, short *do_update, float *progress)
{
RenderJob *rj = static_cast<RenderJob *>(rjv);
rj->stop = stop;
rj->do_update = do_update;
rj->progress = progress;
RE_SetReports(rj->re, rj->reports);
if (rj->anim) {
RE_RenderAnim(rj->re,
rj->main,
rj->scene,
rj->single_layer,
rj->camera_override,
rj->scene->r.sfra,
rj->scene->r.efra,
rj->scene->r.frame_step);
}
else {
RE_RenderFrame(rj->re,
rj->main,
rj->scene,
rj->single_layer,
rj->camera_override,
rj->scene->r.cfra,
rj->scene->r.subframe,
rj->write_still);
}
RE_SetReports(rj->re, nullptr);
}
static void render_image_restore_layer(RenderJob *rj)
{
wmWindowManager *wm;
/* image window, compo node users */
for (wm = static_cast<wmWindowManager *>(rj->main->wm.first); wm;
wm = static_cast<wmWindowManager *>(wm->id.next)) { /* only 1 wm */
wmWindow *win;
for (win = static_cast<wmWindow *>(wm->windows.first); win; win = win->next) {
const bScreen *screen = WM_window_get_active_screen(win);
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
if (area == rj->area) {
if (area->spacetype == SPACE_IMAGE) {
SpaceImage *sima = static_cast<SpaceImage *>(area->spacedata.first);
if (RE_HasSingleLayer(rj->re)) {
/* For single layer renders keep the active layer
* visible, or show the compositing result. */
RenderResult *rr = RE_AcquireResultRead(rj->re);
if (RE_HasCombinedLayer(rr)) {
sima->iuser.layer = 0;
}
RE_ReleaseResult(rj->re);
}
else {
/* For multiple layer render, set back the layer
* that was set at the start of rendering. */
sima->iuser.layer = rj->orig_layer;
}
}
return;
}
}
}
}
}
static void render_endjob(void *rjv)
{
RenderJob *rj = static_cast<RenderJob *>(rjv);
/* This render may be used again by the sequencer without the active
* 'Render' where the callbacks would be re-assigned. assign dummy callbacks
* to avoid referencing freed render-jobs bug T24508. */
RE_InitRenderCB(rj->re);
if (rj->main != G_MAIN) {
BKE_main_free(rj->main);
}
/* else the frame will not update for the original value */
if (rj->anim && !(rj->scene->r.scemode & R_NO_FRAME_UPDATE)) {
/* possible this fails of loading new file while rendering */
if (G_MAIN->wm.first) {
ED_update_for_newframe(G_MAIN, rj->depsgraph);
}
}
/* XXX above function sets all tags in nodes */
ntreeCompositClearTags(rj->scene->nodetree);
/* potentially set by caller */
rj->scene->r.scemode &= ~R_NO_FRAME_UPDATE;
if (rj->single_layer) {
BKE_ntree_update_tag_id_changed(rj->main, &rj->scene->id);
BKE_ntree_update_main(rj->main, nullptr);
WM_main_add_notifier(NC_NODE | NA_EDITED, rj->scene);
}
if (rj->area) {
render_image_restore_layer(rj);
}
/* XXX render stability hack */
G.is_rendering = false;
WM_main_add_notifier(NC_SCENE | ND_RENDER_RESULT, nullptr);
/* Partial render result will always update display buffer
* for first render layer only. This is nice because you'll
* see render progress during rendering, but it ends up in
* wrong display buffer shown after rendering.
*
* The code below will mark display buffer as invalid after
* rendering in case multiple layers were rendered, which
* ensures display buffer matches render layer after
* rendering.
*
* Perhaps proper way would be to toggle active render
* layer in image editor and job, so we always display
* layer being currently rendered. But this is not so much
* trivial at this moment, especially because of external
* engine API, so lets use simple and robust way for now
* - sergey -
*/
if (rj->scene->view_layers.first != rj->scene->view_layers.last || rj->image_outdated) {
void *lock;
Image *ima = rj->image;
ImBuf *ibuf = BKE_image_acquire_ibuf(ima, &rj->iuser, &lock);
if (ibuf) {
ibuf->userflags |= IB_DISPLAY_BUFFER_INVALID;
}
BKE_image_release_ibuf(ima, ibuf, lock);
}
/* Finally unlock the user interface (if it was locked). */
if (rj->interface_locked) {
/* Interface was locked, so window manager couldn't have been changed
* and using one from Global will unlock exactly the same manager as
* was locked before running the job.
*/
WM_set_locked_interface(static_cast<wmWindowManager *>(G_MAIN->wm.first), false);
DEG_tag_on_visible_update(G_MAIN, false);
}
}
/* called by render, check job 'stop' value or the global */
static int render_breakjob(void *rjv)
{
RenderJob *rj = static_cast<RenderJob *>(rjv);
if (G.is_break) {
return 1;
}
if (rj->stop && *(rj->stop)) {
return 1;
}
return 0;
}
/**
* For exec() when there is no render job
* NOTE: this won't check for the escape key being pressed, but doing so isn't thread-safe.
*/
static int render_break(void *UNUSED(rjv))
{
if (G.is_break) {
return 1;
}
return 0;
}
/* runs in thread, no cursor setting here works. careful with notifiers too (malloc conflicts) */
/* maybe need a way to get job send notifier? */
static void render_drawlock(void *rjv, bool lock)
{
RenderJob *rj = static_cast<RenderJob *>(rjv);
/* If interface is locked, renderer callback shall do nothing. */
if (!rj->interface_locked) {
BKE_spacedata_draw_locks(lock);
}
}
/** Catch escape key to cancel. */
static int screen_render_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
Scene *scene = (Scene *)op->customdata;
/* no running blender, remove handler and pass through */
if (0 == WM_jobs_test(CTX_wm_manager(C), scene, WM_JOB_TYPE_RENDER)) {
return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH;
}
/* running render */
switch (event->type) {
case EVT_ESCKEY:
return OPERATOR_RUNNING_MODAL;
}
return OPERATOR_PASS_THROUGH;
}
static void screen_render_cancel(bContext *C, wmOperator *op)
{
wmWindowManager *wm = CTX_wm_manager(C);
Scene *scene = (Scene *)op->customdata;
/* kill on cancel, because job is using op->reports */
WM_jobs_kill_type(wm, scene, WM_JOB_TYPE_RENDER);
}
static void clean_viewport_memory_base(Base *base)
{
if ((base->flag & BASE_ENABLED_AND_MAYBE_VISIBLE_IN_VIEWPORT) == 0) {
return;
}
Object *object = base->object;
if (object->id.tag & LIB_TAG_DOIT) {
return;
}
object->id.tag &= ~LIB_TAG_DOIT;
if (RE_allow_render_generic_object(object)) {
BKE_object_free_derived_caches(object);
}
}
static void clean_viewport_memory(Main *bmain, Scene *scene)
{
Scene *sce_iter;
Base *base;
/* Tag all the available objects. */
BKE_main_id_tag_listbase(&bmain->objects, LIB_TAG_DOIT, true);
/* Go over all the visible objects. */
for (wmWindowManager *wm = static_cast<wmWindowManager *>(bmain->wm.first); wm;
wm = static_cast<wmWindowManager *>(wm->id.next)) {
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
ViewLayer *view_layer = WM_window_get_active_view_layer(win);
BKE_view_layer_synced_ensure(scene, view_layer);
LISTBASE_FOREACH (Base *, b, BKE_view_layer_object_bases_get(view_layer)) {
clean_viewport_memory_base(b);
}
}
}
for (SETLOOPER_SET_ONLY(scene, sce_iter, base)) {
clean_viewport_memory_base(base);
}
}
/* using context, starts job */
static int screen_render_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
/* new render clears all callbacks */
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
ViewLayer *active_layer = CTX_data_view_layer(C);
ViewLayer *single_layer = nullptr;
RenderEngineType *re_type = RE_engines_find(scene->r.engine);
Render *re;
wmJob *wm_job;
RenderJob *rj;
Image *ima;
const bool is_animation = RNA_boolean_get(op->ptr, "animation");
const bool is_write_still = RNA_boolean_get(op->ptr, "write_still");
const bool use_viewport = RNA_boolean_get(op->ptr, "use_viewport");
View3D *v3d = use_viewport ? CTX_wm_view3d(C) : nullptr;
struct Object *camera_override = v3d ? V3D_CAMERA_LOCAL(v3d) : nullptr;
const char *name;
ScrArea *area;
/* Cannot do render if there is not this function. */
if (re_type->render == nullptr) {
return OPERATOR_CANCELLED;
}
/* custom scene and single layer re-render */
screen_render_single_layer_set(op, bmain, active_layer, &scene, &single_layer);
/* only one render job at a time */
if (WM_jobs_test(CTX_wm_manager(C), scene, WM_JOB_TYPE_RENDER)) {
return OPERATOR_CANCELLED;
}
if (!RE_is_rendering_allowed(scene, single_layer, camera_override, op->reports)) {
return OPERATOR_CANCELLED;
}
if (!is_animation && is_write_still && BKE_imtype_is_movie(scene->r.im_format.imtype)) {
BKE_report(
op->reports, RPT_ERROR, "Cannot write a single file with an animation format selected");
return OPERATOR_CANCELLED;
}
/* Reports are done inside check function, and it will return false if there are other strips to
* render. */
if ((scene->r.scemode & R_DOSEQ) && SEQ_relations_check_scene_recursion(scene, op->reports)) {
return OPERATOR_CANCELLED;
}
/* stop all running jobs, except screen one. currently previews frustrate Render */
WM_jobs_kill_all_except(CTX_wm_manager(C), CTX_wm_screen(C));
/* cancel animation playback */
if (ED_screen_animation_playing(CTX_wm_manager(C))) {
ED_screen_animation_play(C, 0, 0);
}
/* handle UI stuff */
WM_cursor_wait(true);
/* flush sculpt and editmode changes */
ED_editors_flush_edits_ex(bmain, true, false);
/* cleanup sequencer caches before starting user triggered render.
* otherwise, invalidated cache entries can make their way into
* the output rendering. We can't put that into RE_RenderFrame,
* since sequence rendering can call that recursively... (peter) */
SEQ_cache_cleanup(scene);
/* store spare
* get view3d layer, local layer, make this nice api call to render
* store spare */
/* ensure at least 1 area shows result */
area = render_view_open(C, event->xy[0], event->xy[1], op->reports);
/* job custom data */
rj = MEM_cnew<RenderJob>("render job");
rj->main = bmain;
rj->scene = scene;
rj->current_scene = rj->scene;
rj->single_layer = single_layer;
/* TODO(sergey): Render engine should be using its own depsgraph.
*
* NOTE: Currently is only used by ED_update_for_newframe() at the end of the render, so no
* need to ensure evaluation here. */
rj->depsgraph = CTX_data_depsgraph_pointer(C);
rj->camera_override = camera_override;
rj->anim = is_animation;
rj->write_still = is_write_still && !is_animation;
rj->iuser.scene = scene;
rj->reports = op->reports;
rj->orig_layer = 0;
rj->last_layer = 0;
rj->area = area;
rj->supports_glsl_draw = IMB_colormanagement_support_glsl_draw(&scene->view_settings);
BKE_color_managed_display_settings_copy(&rj->display_settings, &scene->display_settings);
BKE_color_managed_view_settings_copy(&rj->view_settings, &scene->view_settings);
if (area) {
SpaceImage *sima = static_cast<SpaceImage *>(area->spacedata.first);
rj->orig_layer = sima->iuser.layer;
}
if (v3d) {
if (camera_override && camera_override != scene->camera) {
rj->v3d_override = true;
}
}
/* Lock the user interface depending on render settings. */
if (scene->r.use_lock_interface) {
WM_set_locked_interface(CTX_wm_manager(C), true);
/* Set flag interface need to be unlocked.
*
* This is so because we don't have copy of render settings
* accessible from render job and copy is needed in case
* of non-locked rendering, so we wouldn't try to unlock
* anything if option was initially unset but then was
* enabled during rendering.
*/
rj->interface_locked = true;
/* Clean memory used by viewport? */
clean_viewport_memory(rj->main, scene);
}
/* setup job */
if (RE_seq_render_active(scene, &scene->r)) {
name = "Sequence Render";
}
else {
name = "Render";
}
wm_job = WM_jobs_get(CTX_wm_manager(C),
CTX_wm_window(C),
scene,
name,
WM_JOB_EXCL_RENDER | WM_JOB_PRIORITY | WM_JOB_PROGRESS,
WM_JOB_TYPE_RENDER);
WM_jobs_customdata_set(wm_job, rj, render_freejob);
WM_jobs_timer(wm_job, 0.2, NC_SCENE | ND_RENDER_RESULT, 0);
WM_jobs_callbacks(wm_job, render_startjob, nullptr, nullptr, render_endjob);
if (RNA_struct_property_is_set(op->ptr, "layer")) {
WM_jobs_delay_start(wm_job, 0.2);
}
/* get a render result image, and make sure it is empty */
ima = BKE_image_ensure_viewer(bmain, IMA_TYPE_R_RESULT, "Render Result");
BKE_image_signal(rj->main, ima, nullptr, IMA_SIGNAL_FREE);
BKE_image_backup_render(rj->scene, ima, true);
rj->image = ima;
/* setup new render */
re = RE_NewSceneRender(scene);
RE_test_break_cb(re, rj, render_breakjob);
RE_draw_lock_cb(re, rj, render_drawlock);
RE_display_update_cb(re, rj, image_rect_update);
RE_current_scene_update_cb(re, rj, current_scene_update);
RE_stats_draw_cb(re, rj, image_renderinfo_cb);
RE_progress_cb(re, rj, render_progress_update);
RE_gl_context_create(re);
rj->re = re;
G.is_break = false;
/* store actual owner of job, so modal operator could check for it,
* the reason of this is that active scene could change when rendering
* several layers from compositor T31800. */
op->customdata = scene;
WM_jobs_start(CTX_wm_manager(C), wm_job);
WM_cursor_wait(false);
WM_event_add_notifier(C, NC_SCENE | ND_RENDER_RESULT, scene);
/* we set G.is_rendering here already instead of only in the job, this ensure
* main loop or other scene updates are disabled in time, since they may
* have started before the job thread */
G.is_rendering = true;
/* add modal handler for ESC */
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
}
void RENDER_OT_render(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Render";
ot->description = "Render active scene";
ot->idname = "RENDER_OT_render";
/* api callbacks */
ot->invoke = screen_render_invoke;
ot->modal = screen_render_modal;
ot->cancel = screen_render_cancel;
ot->exec = screen_render_exec;
/* This isn't needed, causes failure in background mode. */
#if 0
ot->poll = ED_operator_screenactive;
#endif
prop = RNA_def_boolean(ot->srna,
"animation",
false,
"Animation",
"Render files from the animation range of this scene");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
RNA_def_boolean(
ot->srna,
"write_still",
false,
"Write Image",
"Save rendered the image to the output path (used only when animation is disabled)");
prop = RNA_def_boolean(ot->srna,
"use_viewport",
false,
"Use 3D Viewport",
"When inside a 3D viewport, use layers and camera of the viewport");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_string(ot->srna,
"layer",
nullptr,
RE_MAXNAME,
"Render Layer",
"Single render layer to re-render (used only when animation is disabled)");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_string(ot->srna,
"scene",
nullptr,
MAX_ID_NAME - 2,
"Scene",
"Scene to render, current scene if not specified");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
Scene *ED_render_job_get_scene(const bContext *C)
{
wmWindowManager *wm = CTX_wm_manager(C);
RenderJob *rj = (RenderJob *)WM_jobs_customdata_from_type(
wm, CTX_data_scene(C), WM_JOB_TYPE_RENDER);
if (rj) {
return rj->scene;
}
return nullptr;
}
Scene *ED_render_job_get_current_scene(const bContext *C)
{
wmWindowManager *wm = CTX_wm_manager(C);
RenderJob *rj = (RenderJob *)WM_jobs_customdata_from_type(
wm, CTX_data_scene(C), WM_JOB_TYPE_RENDER);
if (rj) {
return rj->current_scene;
}
return nullptr;
}
/* Motion blur curve preset */
static int render_shutter_curve_preset_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
CurveMapping *mblur_shutter_curve = &scene->r.mblur_shutter_curve;
CurveMap *cm = mblur_shutter_curve->cm;
int preset = RNA_enum_get(op->ptr, "shape");
mblur_shutter_curve->flag &= ~CUMA_EXTEND_EXTRAPOLATE;
mblur_shutter_curve->preset = preset;
BKE_curvemap_reset(
cm, &mblur_shutter_curve->clipr, mblur_shutter_curve->preset, CURVEMAP_SLOPE_POS_NEG);
BKE_curvemapping_changed(mblur_shutter_curve, false);
return OPERATOR_FINISHED;
}
void RENDER_OT_shutter_curve_preset(wmOperatorType *ot)
{
PropertyRNA *prop;
static const EnumPropertyItem prop_shape_items[] = {
{CURVE_PRESET_SHARP, "SHARP", 0, "Sharp", ""},
{CURVE_PRESET_SMOOTH, "SMOOTH", 0, "Smooth", ""},
{CURVE_PRESET_MAX, "MAX", 0, "Max", ""},
{CURVE_PRESET_LINE, "LINE", 0, "Line", ""},
{CURVE_PRESET_ROUND, "ROUND", 0, "Round", ""},
{CURVE_PRESET_ROOT, "ROOT", 0, "Root", ""},
{0, nullptr, 0, nullptr, nullptr},
};
ot->name = "Shutter Curve Preset";
ot->description = "Set shutter curve";
ot->idname = "RENDER_OT_shutter_curve_preset";
ot->exec = render_shutter_curve_preset_exec;
prop = RNA_def_enum(ot->srna, "shape", prop_shape_items, CURVE_PRESET_SMOOTH, "Mode", "");
RNA_def_property_translation_context(prop,
BLT_I18NCONTEXT_ID_CURVE_LEGACY); /* Abusing id_curve :/ */
}