Files
test2/source/blender/editors/render/render_opengl.cc
Sybren A. Stüvel 6bf8685628 Anim: drop versioning code for pre-2.50 animation
Drop all support for animation data from Blender versions 2.49 and
older.

- The `IPO` DNA struct is deleted, as is the `IDType_ID_IP` type
  definition.
- Versioning code has been removed in its entirety.
- Loading a pre-2.50 blend file will issue a warning, stating that any
  animation data that was there is now lost, and that the file should
  be loaded & re-saved with Blender 4.5 to properly port the data.

Note that versioning of Action assignments (as in, picking a slot)
still uses the tagging + updating all tagged assignments as a
post-processing step. This is necessary because picking the right slot
is only possible after all Actions (also those from libraries) have
been versioned. We might be able to address this as well, by upgrading
legacy → slotted Actions "on the fly" versioning these Action
assignments. If we do that, I think that would be better in a separate
PR though, as it could introduce issues by itself.

Ref: #134219
Pull Request: https://projects.blender.org/blender/blender/pulls/145188
2025-09-08 14:09:21 +02:00

1456 lines
45 KiB
C++

/* SPDX-FileCopyrightText: 2008 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup render
*/
#include <condition_variable>
#include <cstddef>
#include <cstring>
#include "MEM_guardedalloc.h"
#include "BLI_bitmap.h"
#include "BLI_fileops.h"
#include "BLI_listbase.h"
#include "BLI_math_color_blend.h"
#include "BLI_mutex.hh"
#include "BLI_string_utf8.h"
#include "BLI_task.h"
#include "BLI_task.hh"
#include "BLI_utildefines.h"
#include "BLI_vector.hh"
#include "DNA_anim_types.h"
#include "DNA_curve_types.h"
#include "DNA_gpencil_legacy_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "BKE_anim_data.hh"
#include "BKE_camera.h"
#include "BKE_context.hh"
#include "BKE_customdata.hh"
#include "BKE_fcurve.hh"
#include "BKE_global.hh"
#include "BKE_image.hh"
#include "BKE_image_format.hh"
#include "BKE_image_save.hh"
#include "BKE_lib_query.hh"
#include "BKE_main.hh"
#include "BKE_report.hh"
#include "BKE_scene.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_query.hh"
#include "DRW_engine.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "ED_gpencil_legacy.hh"
#include "ED_screen.hh"
#include "ED_view3d.hh"
#include "ED_view3d_offscreen.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "MOV_write.hh"
#include "RE_pipeline.h"
#include "BLT_translation.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "SEQ_render.hh"
#include "ANIM_action_legacy.hh"
#include "GPU_context.hh"
#include "GPU_framebuffer.hh"
#include "GPU_matrix.hh"
#include "GPU_state.hh"
#include "GPU_viewport.hh"
#include "CLG_log.h"
#include "render_intern.hh"
namespace path_templates = blender::bke::path_templates;
static CLG_LogRef LOG = {"render"};
/* TODO(sergey): Find better approximation of the scheduled frames.
* For really high-resolution renders it might fail still. */
#define MAX_SCHEDULED_FRAMES 8
struct OGLRender : public RenderJobBase {
Main *bmain = nullptr;
Render *re = nullptr;
WorkSpace *workspace = nullptr;
ViewLayer *view_layer = nullptr;
Depsgraph *depsgraph = nullptr;
View3D *v3d = nullptr;
RegionView3D *rv3d = nullptr;
ARegion *region = nullptr;
int views_len = 0; /* multi-view views */
bool is_sequencer = false;
SpaceSeq *sseq = nullptr;
struct {
ImBuf **ibufs_arr = nullptr;
} seq_data;
Image *ima = nullptr;
ImageUser iuser = {};
GPUOffScreen *ofs = nullptr;
int sizex = 0;
int sizey = 0;
int write_still = false;
GPUViewport *viewport = nullptr;
blender::Mutex reports_mutex;
ReportList *reports = nullptr;
int cfrao = 0;
int nfra = 0;
int totvideos = 0;
/* For only rendering frames that have a key in animation data. */
BLI_bitmap *render_frames = nullptr;
/* quick lookup */
int view_id = 0;
/* wm vars for timer and progress cursor */
wmWindowManager *wm = nullptr;
wmWindow *win = nullptr;
blender::Vector<MovieWriter *> movie_writers;
TaskPool *task_pool = nullptr;
bool pool_ok = true;
bool is_animation = false;
eImageFormatDepth color_depth = R_IMF_CHAN_DEPTH_32;
uint num_scheduled_frames = 0;
std::mutex task_mutex;
std::condition_variable task_condition;
wmJob *wm_job = nullptr;
bool ended = false;
};
static bool screen_opengl_is_multiview(OGLRender *oglrender)
{
View3D *v3d = oglrender->v3d;
RegionView3D *rv3d = oglrender->rv3d;
RenderData *rd = &oglrender->scene->r;
if ((rd == nullptr) || ((v3d != nullptr) && (rv3d == nullptr))) {
return false;
}
return (rd->scemode & R_MULTIVIEW) &&
((v3d == nullptr) || (rv3d->persp == RV3D_CAMOB && v3d->camera));
}
static void screen_opengl_views_setup(OGLRender *oglrender)
{
RenderResult *rr;
RenderView *rv;
SceneRenderView *srv;
bool is_multiview;
Object *camera;
View3D *v3d = oglrender->v3d;
RenderData *rd = &oglrender->scene->r;
rr = RE_AcquireResultWrite(oglrender->re);
is_multiview = screen_opengl_is_multiview(oglrender);
if (!is_multiview) {
/* we only have one view when multiview is off */
rv = static_cast<RenderView *>(rr->views.first);
if (rv == nullptr) {
rv = MEM_callocN<RenderView>("new opengl render view");
BLI_addtail(&rr->views, rv);
}
while (rv->next) {
RenderView *rv_del = rv->next;
BLI_remlink(&rr->views, rv_del);
IMB_freeImBuf(rv_del->ibuf);
MEM_freeN(rv_del);
}
}
else {
if (v3d) {
RE_SetOverrideCamera(oglrender->re, V3D_CAMERA_SCENE(oglrender->scene, v3d));
}
/* remove all the views that are not needed */
rv = static_cast<RenderView *>(rr->views.last);
while (rv) {
srv = static_cast<SceneRenderView *>(
BLI_findstring(&rd->views, rv->name, offsetof(SceneRenderView, name)));
if (BKE_scene_multiview_is_render_view_active(rd, srv)) {
rv = rv->prev;
}
else {
RenderView *rv_del = rv;
rv = rv_del->prev;
BLI_remlink(&rr->views, rv_del);
IMB_freeImBuf(rv_del->ibuf);
MEM_freeN(rv_del);
}
}
/* create all the views that are needed */
LISTBASE_FOREACH (SceneRenderView *, srv, &rd->views) {
if (BKE_scene_multiview_is_render_view_active(rd, srv) == false) {
continue;
}
rv = static_cast<RenderView *>(
BLI_findstring(&rr->views, srv->name, offsetof(SceneRenderView, name)));
if (rv == nullptr) {
rv = MEM_callocN<RenderView>("new opengl render view");
STRNCPY_UTF8(rv->name, srv->name);
BLI_addtail(&rr->views, rv);
}
}
}
if (!(is_multiview && BKE_scene_multiview_is_stereo3d(rd))) {
oglrender->iuser.flag &= ~IMA_SHOW_STEREO;
}
/* will only work for non multiview correctly */
if (v3d) {
camera = BKE_camera_multiview_render(oglrender->scene, v3d->camera, "new opengl render view");
BKE_render_result_stamp_info(oglrender->scene, camera, rr, false);
}
else {
BKE_render_result_stamp_info(oglrender->scene, oglrender->scene->camera, rr, false);
}
RE_ReleaseResult(oglrender->re);
}
static void screen_opengl_render_doit(OGLRender *oglrender, RenderResult *rr)
{
Scene *scene = oglrender->scene;
Object *camera = nullptr;
int sizex = oglrender->sizex;
int sizey = oglrender->sizey;
ImBuf *ibuf_result = nullptr;
if (oglrender->is_sequencer) {
SpaceSeq *sseq = oglrender->sseq;
bGPdata *gpd = (sseq && (sseq->flag & SEQ_PREVIEW_SHOW_GPENCIL)) ? sseq->gpd : nullptr;
/* use pre-calculated ImBuf (avoids deadlock), see: */
ImBuf *ibuf = oglrender->seq_data.ibufs_arr[oglrender->view_id];
if (ibuf) {
ibuf_result = IMB_dupImBuf(ibuf);
IMB_freeImBuf(ibuf);
/* OpenGL render is considered to be preview and should be
* as fast as possible. So currently we're making sure sequencer
* result is always byte to simplify color management pipeline.
*
* TODO(sergey): In the case of output to float container (EXR)
* it actually makes sense to keep float buffer instead.
*/
if (ibuf_result->float_buffer.data != nullptr) {
IMB_byte_from_float(ibuf_result);
IMB_free_float_pixels(ibuf_result);
}
BLI_assert((sizex == ibuf->x) && (sizey == ibuf->y));
}
else if (gpd) {
/* If there are no strips, Grease Pencil still needs a buffer to draw on */
ibuf_result = IMB_allocImBuf(sizex, sizey, 32, IB_byte_data);
}
if (gpd) {
int i;
uchar *gp_rect;
uchar *render_rect = ibuf_result->byte_buffer.data;
DRW_gpu_context_enable();
GPU_offscreen_bind(oglrender->ofs, true);
GPU_clear_color(0.0f, 0.0f, 0.0f, 0.0f);
GPU_clear_depth(1.0f);
GPU_matrix_reset();
wmOrtho2(0, scene->r.xsch, 0, scene->r.ysch);
GPU_matrix_translate_2f(scene->r.xsch / 2, scene->r.ysch / 2);
G.f |= G_FLAG_RENDER_VIEWPORT;
ED_annotation_draw_ex(scene, gpd, sizex, sizey, scene->r.cfra, SPACE_SEQ);
G.f &= ~G_FLAG_RENDER_VIEWPORT;
gp_rect = static_cast<uchar *>(
MEM_mallocN(sizeof(uchar[4]) * sizex * sizey, "offscreen rect"));
GPU_offscreen_read_color(oglrender->ofs, GPU_DATA_UBYTE, gp_rect);
for (i = 0; i < sizex * sizey * 4; i += 4) {
blend_color_mix_byte(&render_rect[i], &render_rect[i], &gp_rect[i]);
}
GPU_offscreen_unbind(oglrender->ofs, true);
DRW_gpu_context_disable();
MEM_freeN(gp_rect);
}
}
else {
/* shouldn't suddenly give errors mid-render but possible */
Depsgraph *depsgraph = oglrender->depsgraph;
char err_out[256] = "unknown";
ImBuf *ibuf_view;
bool draw_sky = (scene->r.alphamode == R_ADDSKY);
const int alpha_mode = (draw_sky) ? R_ADDSKY : R_ALPHAPREMUL;
const char *viewname = RE_GetActiveRenderView(oglrender->re);
View3D *v3d = oglrender->v3d;
BKE_scene_graph_evaluated_ensure(depsgraph, oglrender->bmain);
if (v3d != nullptr) {
ARegion *region = oglrender->region;
ibuf_view = ED_view3d_draw_offscreen_imbuf(depsgraph,
scene,
static_cast<eDrawType>(v3d->shading.type),
v3d,
region,
sizex,
sizey,
IB_float_data,
alpha_mode,
viewname,
true,
oglrender->ofs,
oglrender->viewport,
err_out);
/* for stamp only */
if (oglrender->rv3d->persp == RV3D_CAMOB && v3d->camera) {
camera = BKE_camera_multiview_render(oglrender->scene, v3d->camera, viewname);
}
}
else {
ibuf_view = ED_view3d_draw_offscreen_imbuf_simple(depsgraph,
scene,
nullptr,
OB_SOLID,
scene->camera,
sizex,
sizey,
IB_float_data,
V3D_OFSDRAW_SHOW_ANNOTATION,
alpha_mode,
viewname,
oglrender->ofs,
oglrender->viewport,
err_out);
camera = scene->camera;
}
if (ibuf_view) {
ibuf_result = ibuf_view;
}
else {
CLOG_ERROR(&LOG, "%s: failed to get buffer, %s", __func__, err_out);
}
}
if (ibuf_result != nullptr) {
if ((scene->r.stamp & R_STAMP_ALL) && (scene->r.stamp & R_STAMP_DRAW)) {
BKE_image_stamp_buf(scene, camera, nullptr, ibuf_result);
}
RE_render_result_rect_from_ibuf(rr, ibuf_result, oglrender->view_id);
IMB_freeImBuf(ibuf_result);
}
/* Perform render step between renders to allow
* flushing of freed GPUBackend resources. */
DRW_gpu_context_enable();
if (GPU_backend_get_type() == GPU_BACKEND_METAL) {
GPU_flush();
}
GPU_render_step(true);
DRW_gpu_context_disable();
}
static void screen_opengl_render_write(OGLRender *oglrender)
{
Scene *scene = oglrender->scene;
RenderResult *rr;
bool ok;
char filepath[FILE_MAX];
rr = RE_AcquireResultRead(oglrender->re);
path_templates::VariableMap template_variables;
BKE_add_template_variables_general(template_variables, &scene->id);
BKE_add_template_variables_for_render_path(template_variables, *scene);
const char *relbase = BKE_main_blendfile_path(oglrender->bmain);
const blender::Vector<path_templates::Error> errors = BKE_image_path_from_imformat(
filepath,
scene->r.pic,
relbase,
&template_variables,
scene->r.cfra,
&scene->r.im_format,
(scene->r.scemode & R_EXTENSION) != 0,
false,
nullptr);
if (!errors.is_empty()) {
std::unique_lock lock(oglrender->reports_mutex);
BKE_report_path_template_errors(oglrender->reports, RPT_ERROR, scene->r.pic, errors);
ok = false;
}
else {
/* write images as individual images or stereo */
BKE_render_result_stamp_info(scene, scene->camera, rr, false);
ok = BKE_image_render_write(oglrender->reports, rr, scene, false, filepath);
RE_ReleaseResultImage(oglrender->re);
}
if (ok) {
CLOG_INFO_NOCHECK(&LOG, "OpenGL Render written to '%s'", filepath);
}
else {
CLOG_ERROR(&LOG, "OpenGL Render failed to write '%s'", filepath);
}
}
static void UNUSED_FUNCTION(addAlphaOverFloat)(float dest[4], const float source[4])
{
/* `d = s + (1-alpha_s)d` */
float mul;
mul = 1.0f - source[3];
dest[0] = (mul * dest[0]) + source[0];
dest[1] = (mul * dest[1]) + source[1];
dest[2] = (mul * dest[2]) + source[2];
dest[3] = (mul * dest[3]) + source[3];
}
static void screen_opengl_render_apply(OGLRender *oglrender)
{
RenderResult *rr;
RenderView *rv;
int view_id;
ImBuf *ibuf;
void *lock;
if (oglrender->is_sequencer) {
Scene *scene = oglrender->scene;
blender::seq::RenderData context;
SpaceSeq *sseq = oglrender->sseq;
int chanshown = sseq ? sseq->chanshown : 0;
blender::seq::render_new_render_data(oglrender->bmain,
oglrender->depsgraph,
scene,
oglrender->sizex,
oglrender->sizey,
SEQ_RENDER_SIZE_SCENE,
false,
&context);
for (view_id = 0; view_id < oglrender->views_len; view_id++) {
context.view_id = view_id;
context.gpu_offscreen = oglrender->ofs;
context.gpu_viewport = oglrender->viewport;
oglrender->seq_data.ibufs_arr[view_id] = blender::seq::render_give_ibuf(
&context, scene->r.cfra, chanshown);
}
}
rr = RE_AcquireResultRead(oglrender->re);
for (rv = static_cast<RenderView *>(rr->views.first), view_id = 0; rv; rv = rv->next, view_id++)
{
BLI_assert(view_id < oglrender->views_len);
RE_SetActiveRenderView(oglrender->re, rv->name);
oglrender->view_id = view_id;
/* render composite */
screen_opengl_render_doit(oglrender, rr);
}
RE_ReleaseResult(oglrender->re);
ibuf = BKE_image_acquire_ibuf(oglrender->ima, &oglrender->iuser, &lock);
if (ibuf) {
ibuf->userflags |= IB_DISPLAY_BUFFER_INVALID;
}
BKE_image_release_ibuf(oglrender->ima, ibuf, lock);
BKE_image_partial_update_mark_full_update(oglrender->ima);
if (oglrender->write_still) {
screen_opengl_render_write(oglrender);
}
}
static void gather_frames_to_render_for_adt(const OGLRender *oglrender, const AnimData *adt)
{
if (adt == nullptr || adt->action == nullptr) {
return;
}
Scene *scene = oglrender->scene;
int frame_start = PSFRA;
int frame_end = PEFRA;
for (const FCurve *fcu : blender::animrig::legacy::fcurves_for_assigned_action(adt)) {
if (fcu->driver != nullptr || fcu->fpt != nullptr) {
/* Drivers have values for any point in time, so to get "the keyed frames" they are
* useless. Same for baked FCurves, they also have keys for every frame, which is not
* useful for rendering the keyed subset of the frames. */
continue;
}
bool found = false; /* Not interesting, we just want a starting point for the for-loop. */
int key_index = BKE_fcurve_bezt_binarysearch_index(
fcu->bezt, frame_start, fcu->totvert, &found);
for (; key_index < fcu->totvert; key_index++) {
BezTriple *bezt = &fcu->bezt[key_index];
/* The frame range to render uses integer frame numbers, and the frame
* step is also an integer, so we always render on the frame. */
int frame_nr = round_fl_to_int(bezt->vec[1][0]);
/* (frame_nr < frame_start) cannot happen because of the binary search above. */
BLI_assert(frame_nr >= frame_start);
if (frame_nr > frame_end) {
break;
}
BLI_BITMAP_ENABLE(oglrender->render_frames, frame_nr - frame_start);
}
}
}
static void gather_frames_to_render_for_grease_pencil(const OGLRender *oglrender,
const bGPdata *gp)
{
if (gp == nullptr) {
return;
}
Scene *scene = oglrender->scene;
int frame_start = PSFRA;
int frame_end = PEFRA;
LISTBASE_FOREACH (const bGPDlayer *, gp_layer, &gp->layers) {
LISTBASE_FOREACH (const bGPDframe *, gp_frame, &gp_layer->frames) {
if (gp_frame->framenum < frame_start || gp_frame->framenum > frame_end) {
continue;
}
BLI_BITMAP_ENABLE(oglrender->render_frames, gp_frame->framenum - frame_start);
}
}
}
static int gather_frames_to_render_for_id(LibraryIDLinkCallbackData *cb_data)
{
ID **id_p = cb_data->id_pointer;
if (*id_p == nullptr) {
return IDWALK_RET_NOP;
}
ID *id = *id_p;
ID *self_id = cb_data->self_id;
const LibraryForeachIDCallbackFlag cb_flag = cb_data->cb_flag;
if (cb_flag == IDWALK_CB_LOOPBACK || id == self_id) {
/* IDs may end up referencing themselves one way or the other, and those
* (the self_id ones) have always already been processed. */
return IDWALK_RET_STOP_RECURSION;
}
OGLRender *oglrender = static_cast<OGLRender *>(cb_data->user_data);
/* Whitelist of datablocks to follow pointers into. */
const ID_Type id_type = GS(id->name);
switch (id_type) {
/* Whitelist: */
case ID_ME: /* Mesh */
case ID_CU_LEGACY: /* Curve */
case ID_MB: /* MetaBall */
case ID_MA: /* Material */
case ID_TE: /* Tex (Texture) */
case ID_IM: /* Image */
case ID_LT: /* Lattice */
case ID_LA: /* Light */
case ID_CA: /* Camera */
case ID_KE: /* Key (shape key) */
case ID_VF: /* VFont (Vector Font) */
case ID_TXT: /* Text */
case ID_SPK: /* Speaker */
case ID_SO: /* Sound */
case ID_AR: /* bArmature */
case ID_NT: /* bNodeTree */
case ID_PA: /* ParticleSettings */
case ID_MC: /* MovieClip */
case ID_MSK: /* Mask */
case ID_LP: /* LightProbe */
case ID_CV: /* Curves */
case ID_PT: /* PointCloud */
case ID_VO: /* Volume */
break;
/* Blacklist: */
case ID_SCE: /* Scene */
case ID_LI: /* Library */
case ID_OB: /* Object */
case ID_WO: /* World */
case ID_SCR: /* Screen */
case ID_GR: /* Group */
case ID_AC: /* bAction */
case ID_BR: /* Brush */
case ID_WM: /* WindowManager */
case ID_LS: /* FreestyleLineStyle */
case ID_PAL: /* Palette */
case ID_PC: /* PaintCurve */
case ID_CF: /* CacheFile */
case ID_WS: /* WorkSpace */
/* Only follow pointers to specific datablocks, to avoid ending up in
* unrelated datablocks and exploding the number of blocks we follow. If the
* frames of the animation of certain objects should be taken into account,
* they should have been selected by the user. */
return IDWALK_RET_STOP_RECURSION;
/* Special cases: */
case ID_GD_LEGACY: /* bGPdata, (Grease Pencil) */
/* In addition to regular ID's animdata, GreasePencil uses a specific frame-based animation
* system that requires specific handling here. */
gather_frames_to_render_for_grease_pencil(oglrender, (bGPdata *)id);
break;
case ID_GP:
/* TODO: gather frames. */
break;
}
AnimData *adt = BKE_animdata_from_id(id);
gather_frames_to_render_for_adt(oglrender, adt);
return IDWALK_RET_NOP;
}
/**
* Collect the frame numbers for which selected objects have keys in the animation data.
* The frames ares stored in #OGLRender.render_frames.
*
* Note that this follows all pointers to ID blocks, only filtering on ID type,
* so it will pick up keys from pointers in custom properties as well.
*/
static void gather_frames_to_render(bContext *C, OGLRender *oglrender)
{
Scene *scene = oglrender->scene;
int frame_start = PSFRA;
int frame_end = PEFRA;
/* Will be freed in screen_opengl_render_end(). */
oglrender->render_frames = BLI_BITMAP_NEW(frame_end - frame_start + 1,
"OGLRender::render_frames");
/* The first frame should always be rendered, otherwise there is nothing to write to file. */
BLI_BITMAP_ENABLE(oglrender->render_frames, 0);
CTX_DATA_BEGIN (C, Object *, ob, selected_objects) {
ID *id = &ob->id;
/* Gather the frames from the object animation data. */
AnimData *adt = BKE_animdata_from_id(id);
gather_frames_to_render_for_adt(oglrender, adt);
/* Gather the frames from linked data-blocks (materials, shape-keys, etc.). */
BKE_library_foreach_ID_link(
nullptr, id, gather_frames_to_render_for_id, oglrender, IDWALK_RECURSE);
}
CTX_DATA_END;
}
static bool screen_opengl_render_init(bContext *C, wmOperator *op)
{
/* new render clears all callbacks */
wmWindowManager *wm = CTX_wm_manager(C);
wmWindow *win = CTX_wm_window(C);
WorkSpace *workspace = CTX_wm_workspace(C);
const bool is_sequencer = RNA_boolean_get(op->ptr, "sequencer");
Scene *scene = !is_sequencer ? CTX_data_scene(C) : CTX_data_sequencer_scene(C);
if (!scene) {
return false;
}
ScrArea *prev_area = CTX_wm_area(C);
ARegion *prev_region = CTX_wm_region(C);
GPUOffScreen *ofs;
OGLRender *oglrender;
int sizex, sizey;
bool is_view_context = RNA_boolean_get(op->ptr, "view_context");
const bool is_animation = RNA_boolean_get(op->ptr, "animation");
const bool is_render_keyed_only = RNA_boolean_get(op->ptr, "render_keyed_only");
const bool is_write_still = RNA_boolean_get(op->ptr, "write_still");
const eImageFormatDepth color_depth = static_cast<eImageFormatDepth>(
(is_animation) ? (eImageFormatDepth)scene->r.im_format.depth : R_IMF_CHAN_DEPTH_32);
char err_out[256] = "unknown";
if (G.background) {
BKE_report(
op->reports, RPT_ERROR, "Cannot use OpenGL render in background mode (no opengl context)");
return false;
}
/* only one render job at a time */
if (WM_jobs_test(wm, scene, WM_JOB_TYPE_RENDER)) {
return false;
}
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 false;
}
if (is_sequencer) {
is_view_context = false;
}
else {
/* ensure we have a 3d view */
if (!ED_view3d_context_activate(C)) {
RNA_boolean_set(op->ptr, "view_context", false);
is_view_context = false;
}
if (!is_view_context && scene->camera == nullptr) {
BKE_report(op->reports, RPT_ERROR, "Scene has no camera");
return false;
}
}
/* stop all running jobs, except screen one. currently previews frustrate Render */
WM_jobs_kill_all_except(wm, CTX_wm_screen(C));
/* create offscreen buffer */
BKE_render_resolution(&scene->r, false, &sizex, &sizey);
/* corrects render size with actual size, not every card supports non-power-of-two dimensions */
DRW_gpu_context_enable(); /* Off-screen creation needs to be done in DRW context. */
ofs = GPU_offscreen_create(sizex,
sizey,
true,
blender::gpu::TextureFormat::SFLOAT_16_16_16_16,
GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_HOST_READ,
false,
err_out);
DRW_gpu_context_disable();
if (!ofs) {
BKE_reportf(op->reports, RPT_ERROR, "Failed to create OpenGL off-screen buffer, %s", err_out);
CTX_wm_area_set(C, prev_area);
CTX_wm_region_set(C, prev_region);
return false;
}
/* allocate opengl render */
oglrender = MEM_new<OGLRender>("OGLRender");
op->customdata = oglrender;
oglrender->ofs = ofs;
oglrender->sizex = sizex;
oglrender->sizey = sizey;
oglrender->viewport = GPU_viewport_create();
oglrender->bmain = CTX_data_main(C);
oglrender->scene = scene;
oglrender->current_scene = scene;
oglrender->workspace = workspace;
oglrender->view_layer = CTX_data_view_layer(C);
/* NOTE: The depsgraph is not only used to update scene for a new frames, but also to initialize
* output video handles, which does need evaluated scene. */
oglrender->depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
oglrender->cfrao = scene->r.cfra;
oglrender->write_still = is_write_still && !is_animation;
oglrender->is_animation = is_animation;
oglrender->color_depth = color_depth;
oglrender->views_len = BKE_scene_multiview_num_views_get(&scene->r);
oglrender->is_sequencer = is_sequencer;
if (is_sequencer) {
oglrender->sseq = CTX_wm_space_seq(C);
ImBuf **ibufs_arr = static_cast<ImBuf **>(
MEM_callocN(sizeof(*ibufs_arr) * oglrender->views_len, __func__));
oglrender->seq_data.ibufs_arr = ibufs_arr;
}
if (is_view_context) {
/* Prefer rendering camera in quad view if possible. */
if (!ED_view3d_context_user_region(C, &oglrender->v3d, &oglrender->region)) {
/* If not get region activated by ED_view3d_context_activate earlier. */
oglrender->v3d = CTX_wm_view3d(C);
oglrender->region = CTX_wm_region(C);
}
oglrender->rv3d = static_cast<RegionView3D *>(oglrender->region->regiondata);
/* MUST be cleared on exit */
oglrender->scene->customdata_mask_modal = CustomData_MeshMasks{};
ED_view3d_datamask(oglrender->scene,
oglrender->view_layer,
oglrender->v3d,
&oglrender->scene->customdata_mask_modal);
/* apply immediately in case we're rendering from a script,
* running notifiers again will overwrite */
CustomData_MeshMasks_update(&oglrender->scene->customdata_mask,
&oglrender->scene->customdata_mask_modal);
}
/* create render */
oglrender->re = RE_NewSceneRender(scene);
/* create image and image user */
oglrender->ima = BKE_image_ensure_viewer(oglrender->bmain, IMA_TYPE_R_RESULT, "Render Result");
BKE_image_signal(oglrender->bmain, oglrender->ima, nullptr, IMA_SIGNAL_FREE);
BKE_image_backup_render(oglrender->scene, oglrender->ima, true);
oglrender->iuser.scene = scene;
/* create render result */
RE_InitState(
oglrender->re, nullptr, &scene->r, &scene->view_layers, nullptr, sizex, sizey, nullptr);
/* create render views */
screen_opengl_views_setup(oglrender);
/* wm vars */
oglrender->wm = wm;
oglrender->win = win;
if (is_animation) {
if (is_render_keyed_only) {
gather_frames_to_render(C, oglrender);
}
if (BKE_imtype_is_movie(scene->r.im_format.imtype)) {
oglrender->task_pool = BLI_task_pool_create_background_serial(oglrender, TASK_PRIORITY_HIGH);
}
else {
oglrender->task_pool = BLI_task_pool_create(oglrender, TASK_PRIORITY_HIGH);
}
}
CTX_wm_area_set(C, prev_area);
CTX_wm_region_set(C, prev_region);
return true;
}
static void screen_opengl_render_end(OGLRender *oglrender)
{
/* Ensure we don't call this both from the job and operator callbacks. */
if (oglrender->ended) {
return;
}
if (oglrender->task_pool) {
/* Trickery part for movie output:
*
* We MUST write frames in an exact order, so we only let background
* thread to work on that, and main thread is simply waits for that
* thread to do all the dirty work.
*
* After this loop is done work_and_wait() will have nothing to do,
* so we don't run into wrong order of frames written to the stream.
*/
if (BKE_imtype_is_movie(oglrender->scene->r.im_format.imtype)) {
std::unique_lock lock(oglrender->task_mutex);
while (oglrender->num_scheduled_frames > 0) {
oglrender->task_condition.wait(lock);
}
}
BLI_task_pool_work_and_wait(oglrender->task_pool);
BLI_task_pool_free(oglrender->task_pool);
oglrender->task_pool = nullptr;
}
MEM_SAFE_FREE(oglrender->render_frames);
if (!oglrender->movie_writers.is_empty()) {
if (BKE_imtype_is_movie(oglrender->scene->r.im_format.imtype)) {
for (MovieWriter *writer : oglrender->movie_writers) {
MOV_write_end(writer);
}
}
oglrender->movie_writers.clear_and_shrink();
}
if (oglrender->ofs || oglrender->viewport) {
DRW_gpu_context_enable();
GPU_offscreen_free(oglrender->ofs);
GPU_viewport_free(oglrender->viewport);
DRW_gpu_context_disable();
oglrender->ofs = nullptr;
oglrender->viewport = nullptr;
}
MEM_SAFE_FREE(oglrender->seq_data.ibufs_arr);
oglrender->scene->customdata_mask_modal = CustomData_MeshMasks{};
if (oglrender->wm_job) { /* exec will not have a job */
Depsgraph *depsgraph = oglrender->depsgraph;
oglrender->scene->r.cfra = oglrender->cfrao;
BKE_scene_graph_update_for_newframe(depsgraph);
}
else if (oglrender->win) {
WM_cursor_modal_restore(oglrender->win);
}
WM_main_add_notifier(NC_SCENE | ND_RENDER_RESULT, oglrender->scene);
G.is_rendering = false;
oglrender->ended = true;
}
static void screen_opengl_render_cancel(bContext *C, wmOperator *op)
{
wmWindowManager *wm = CTX_wm_manager(C);
OGLRender *oglrender = static_cast<OGLRender *>(op->customdata);
if (oglrender->is_animation) {
WM_jobs_kill_type(wm, oglrender->scene, WM_JOB_TYPE_RENDER);
}
screen_opengl_render_end(oglrender);
MEM_delete(oglrender);
}
/* share between invoke and exec */
static bool screen_opengl_render_anim_init(wmOperator *op)
{
/* initialize animation */
OGLRender *oglrender = static_cast<OGLRender *>(op->customdata);
Scene *scene = oglrender->scene;
ImageFormatData image_format;
BKE_image_format_init_for_write(&image_format, scene, nullptr, true);
oglrender->totvideos = BKE_scene_multiview_num_videos_get(&scene->r, &image_format);
oglrender->reports = op->reports;
if (BKE_imtype_is_movie(image_format.imtype)) {
size_t width, height;
int i;
BKE_scene_multiview_videos_dimensions_get(
&scene->r, &image_format, oglrender->sizex, oglrender->sizey, &width, &height);
oglrender->movie_writers.reserve(oglrender->totvideos);
for (i = 0; i < oglrender->totvideos; i++) {
Scene *scene_eval = DEG_get_evaluated_scene(oglrender->depsgraph);
const char *suffix = BKE_scene_multiview_view_id_suffix_get(&scene->r, i);
MovieWriter *writer = MOV_write_begin(scene_eval,
&scene->r,
&image_format,
oglrender->sizex,
oglrender->sizey,
oglrender->reports,
PRVRANGEON != 0,
suffix);
if (writer == nullptr) {
BKE_image_format_free(&image_format);
screen_opengl_render_end(oglrender);
MEM_delete(oglrender);
return false;
}
oglrender->movie_writers.append(writer);
}
}
BKE_image_format_free(&image_format);
G.is_rendering = true;
oglrender->cfrao = scene->r.cfra;
oglrender->nfra = PSFRA;
scene->r.cfra = PSFRA;
return true;
}
struct WriteTaskData {
RenderResult *rr;
Scene tmp_scene;
};
static void write_result(TaskPool *__restrict pool, WriteTaskData *task_data)
{
OGLRender *oglrender = (OGLRender *)BLI_task_pool_user_data(pool);
Scene *scene = &task_data->tmp_scene;
RenderResult *rr = task_data->rr;
const bool is_movie = BKE_imtype_is_movie(scene->r.im_format.imtype);
const int cfra = scene->r.cfra;
bool ok;
/* Don't attempt to write if we've got an error. */
if (!oglrender->pool_ok) {
RE_FreeRenderResult(rr);
std::scoped_lock lock(oglrender->task_mutex);
oglrender->num_scheduled_frames--;
oglrender->task_condition.notify_all();
return;
}
/* Construct local thread0safe copy of reports structure which we can
* safely pass to the underlying functions.
*/
ReportList reports;
BKE_reports_init(&reports, oglrender->reports->flag & ~RPT_PRINT);
/* Do actual save logic here, depending on the file format.
*
* NOTE: We have to construct temporary scene with proper scene->r.cfra.
* This is because underlying calls do not use r.cfra but use scene
* for that.
*/
if (is_movie) {
ok = RE_WriteRenderViewsMovie(&reports,
rr,
scene,
&scene->r,
oglrender->movie_writers.data(),
oglrender->totvideos,
PRVRANGEON != 0);
}
else {
/* TODO(sergey): We can in theory save some CPU ticks here because we
* calculate file name again here.
*/
char filepath[FILE_MAX];
path_templates::VariableMap template_variables;
BKE_add_template_variables_general(template_variables, &scene->id);
BKE_add_template_variables_for_render_path(template_variables, *scene);
const char *relbase = BKE_main_blendfile_path(oglrender->bmain);
const blender::Vector<path_templates::Error> errors = BKE_image_path_from_imformat(
filepath,
scene->r.pic,
relbase,
&template_variables,
cfra,
&scene->r.im_format,
(scene->r.scemode & R_EXTENSION) != 0,
true,
nullptr);
if (!errors.is_empty()) {
BKE_report_path_template_errors(&reports, RPT_ERROR, scene->r.pic, errors);
ok = false;
}
else {
BKE_render_result_stamp_info(scene, scene->camera, rr, false);
ok = BKE_image_render_write(nullptr, rr, scene, true, filepath);
}
if (!ok) {
BKE_reportf(&reports, RPT_ERROR, "Write error: cannot save %s", filepath);
}
}
if (reports.list.first != nullptr) {
/* TODO: Should rather use new #BKE_reports_move_to_reports ? */
std::unique_lock lock(oglrender->reports_mutex);
for (Report *report = static_cast<Report *>(reports.list.first); report != nullptr;
report = report->next)
{
BKE_report(oglrender->reports, static_cast<eReportType>(report->type), report->message);
}
}
BKE_reports_free(&reports);
if (!ok) {
oglrender->pool_ok = false;
}
RE_FreeRenderResult(rr);
{
std::unique_lock lock(oglrender->task_mutex);
oglrender->num_scheduled_frames--;
oglrender->task_condition.notify_all();
}
}
static void write_result_func(TaskPool *__restrict pool, void *task_data_v)
{
/* Isolate task so that multithreaded image operations don't cause this thread to start
* writing another frame. If that happens we may reach the MAX_SCHEDULED_FRAMES limit,
* and cause the render thread and writing threads to deadlock waiting for each other. */
WriteTaskData *task_data = (WriteTaskData *)task_data_v;
blender::threading::isolate_task([&] { write_result(pool, task_data); });
}
static bool schedule_write_result(OGLRender *oglrender, RenderResult *rr)
{
if (!oglrender->pool_ok) {
RE_FreeRenderResult(rr);
return false;
}
Scene *scene = oglrender->scene;
WriteTaskData *task_data = MEM_callocN<WriteTaskData>("write task data");
task_data->rr = rr;
memcpy(&task_data->tmp_scene, scene, sizeof(task_data->tmp_scene));
{
std::unique_lock lock(oglrender->task_mutex);
oglrender->num_scheduled_frames++;
if (oglrender->num_scheduled_frames > MAX_SCHEDULED_FRAMES) {
oglrender->task_condition.wait(lock);
}
}
BLI_task_pool_push(oglrender->task_pool, write_result_func, task_data, true, nullptr);
return true;
}
static bool screen_opengl_render_anim_step(OGLRender *oglrender)
{
Scene *scene = oglrender->scene;
Depsgraph *depsgraph = oglrender->depsgraph;
char filepath[FILE_MAX];
bool ok = false;
const bool view_context = (oglrender->v3d != nullptr);
bool is_movie;
RenderResult *rr;
/* go to next frame */
if (scene->r.cfra < oglrender->nfra) {
scene->r.cfra++;
}
while (scene->r.cfra < oglrender->nfra) {
BKE_scene_graph_update_for_newframe(depsgraph);
scene->r.cfra++;
}
is_movie = BKE_imtype_is_movie(scene->r.im_format.imtype);
if (!is_movie) {
path_templates::VariableMap template_variables;
BKE_add_template_variables_general(template_variables, &scene->id);
BKE_add_template_variables_for_render_path(template_variables, *scene);
const char *relbase = BKE_main_blendfile_path(oglrender->bmain);
const blender::Vector<path_templates::Error> errors = BKE_image_path_from_imformat(
filepath,
scene->r.pic,
relbase,
&template_variables,
scene->r.cfra,
&scene->r.im_format,
(scene->r.scemode & R_EXTENSION) != 0,
true,
nullptr);
if (!errors.is_empty()) {
std::unique_lock lock(oglrender->reports_mutex);
BKE_report_path_template_errors(oglrender->reports, RPT_ERROR, scene->r.pic, errors);
ok = false;
}
else if ((scene->r.mode & R_NO_OVERWRITE) && BLI_exists(filepath)) {
{
std::unique_lock lock(oglrender->reports_mutex);
BKE_reportf(oglrender->reports, RPT_INFO, "Skipping existing frame \"%s\"", filepath);
}
ok = true;
goto finally;
}
}
if (!oglrender->wm_job && oglrender->win) {
/* When doing blocking animation render without a job from a Python script, show time cursor so
* Blender doesn't appear frozen. */
WM_cursor_time(oglrender->win, scene->r.cfra);
}
BKE_scene_graph_update_for_newframe(depsgraph);
if (view_context) {
if (oglrender->rv3d->persp == RV3D_CAMOB && oglrender->v3d->camera &&
oglrender->v3d->scenelock)
{
/* since BKE_scene_graph_update_for_newframe() is used rather
* then ED_update_for_newframe() the camera needs to be set */
if (BKE_scene_camera_switch_update(scene)) {
oglrender->v3d->camera = scene->camera;
}
}
}
else {
BKE_scene_camera_switch_update(scene);
}
if (oglrender->render_frames == nullptr ||
BLI_BITMAP_TEST_BOOL(oglrender->render_frames, scene->r.cfra - PSFRA))
{
/* render into offscreen buffer */
screen_opengl_render_apply(oglrender);
}
/* save to disk */
rr = RE_AcquireResultRead(oglrender->re);
{
RenderResult *new_rr = RE_DuplicateRenderResult(rr);
RE_ReleaseResult(oglrender->re);
ok = schedule_write_result(oglrender, new_rr);
}
finally: /* Step the frame and bail early if needed */
/* go to next frame */
oglrender->nfra += scene->r.frame_step;
/* stop at the end or on error */
if (scene->r.cfra >= PEFRA || !ok) {
return false;
}
return true;
}
static wmOperatorStatus screen_opengl_render_modal(bContext *C,
wmOperator *op,
const wmEvent *event)
{
OGLRender *oglrender = static_cast<OGLRender *>(op->customdata);
/* Still render completes immediately, but still modal to show some feedback
* in case render initialization takes a while. */
if (!oglrender->is_animation) {
screen_opengl_render_apply(oglrender);
screen_opengl_render_end(oglrender);
MEM_delete(oglrender);
return OPERATOR_FINISHED;
}
/* no running blender, remove handler and pass through */
if (0 == WM_jobs_test(CTX_wm_manager(C), oglrender->scene, WM_JOB_TYPE_RENDER)) {
screen_opengl_render_end(oglrender);
MEM_delete(oglrender);
return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH;
}
/* catch escape key. */
return (event->type == EVT_ESCKEY) ? OPERATOR_RUNNING_MODAL : OPERATOR_PASS_THROUGH;
}
static void opengl_render_startjob(void *customdata, wmJobWorkerStatus *worker_status)
{
OGLRender *oglrender = static_cast<OGLRender *>(customdata);
Scene *scene = oglrender->scene;
bool canceled = false;
bool finished = false;
while (!finished && !canceled) {
/* Render while blocking main thread, since we use 3D viewport resources. */
WM_job_main_thread_lock_acquire(oglrender->wm_job);
if (worker_status->stop || G.is_break) {
canceled = true;
}
else {
finished = !screen_opengl_render_anim_step(oglrender);
worker_status->progress = float(scene->r.cfra - PSFRA + 1) / float(PEFRA - PSFRA + 1);
worker_status->do_update = true;
}
WM_job_main_thread_lock_release(oglrender->wm_job);
if (worker_status->stop || G.is_break) {
canceled = true;
}
}
if (canceled) {
/* Cancel task pool writing images asynchronously. */
oglrender->pool_ok = false;
}
}
static void opengl_render_freejob(void *customdata)
{
/* End the render here, as the modal handler might be called with the window out of focus. */
OGLRender *oglrender = static_cast<OGLRender *>(customdata);
screen_opengl_render_end(oglrender);
}
static wmOperatorStatus screen_opengl_render_invoke(bContext *C,
wmOperator *op,
const wmEvent *event)
{
const bool anim = RNA_boolean_get(op->ptr, "animation");
if (!screen_opengl_render_init(C, op)) {
return OPERATOR_CANCELLED;
}
if (anim) {
if (!screen_opengl_render_anim_init(op)) {
return OPERATOR_CANCELLED;
}
}
OGLRender *oglrender = static_cast<OGLRender *>(op->customdata);
render_view_open(C, event->xy[0], event->xy[1], op->reports);
/* View may be changed above #USER_RENDER_DISPLAY_WINDOW. */
oglrender->win = CTX_wm_window(C);
/* Setup animation job. */
if (anim) {
G.is_break = false;
wmJob *wm_job = WM_jobs_get(CTX_wm_manager(C),
CTX_wm_window(C),
oglrender->scene,
"Rendering viewport...",
WM_JOB_EXCL_RENDER | WM_JOB_PRIORITY | WM_JOB_PROGRESS,
WM_JOB_TYPE_RENDER);
oglrender->wm_job = wm_job;
WM_jobs_customdata_set(wm_job, oglrender, opengl_render_freejob);
WM_jobs_timer(wm_job, 0.01f, NC_SCENE | ND_RENDER_RESULT, 0);
WM_jobs_callbacks(wm_job, opengl_render_startjob, nullptr, nullptr, nullptr);
WM_jobs_start(CTX_wm_manager(C), wm_job);
}
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
}
/* executes blocking render */
static wmOperatorStatus screen_opengl_render_exec(bContext *C, wmOperator *op)
{
if (!screen_opengl_render_init(C, op)) {
return OPERATOR_CANCELLED;
}
OGLRender *oglrender = static_cast<OGLRender *>(op->customdata);
if (!oglrender->is_animation) { /* same as invoke */
screen_opengl_render_apply(oglrender);
screen_opengl_render_end(oglrender);
MEM_delete(oglrender);
return OPERATOR_FINISHED;
}
bool ret = true;
if (!screen_opengl_render_anim_init(op)) {
return OPERATOR_CANCELLED;
}
while (ret) {
ret = screen_opengl_render_anim_step(oglrender);
}
screen_opengl_render_end(oglrender);
MEM_delete(oglrender);
return OPERATOR_FINISHED;
}
static std::string screen_opengl_render_get_description(bContext * /*C*/,
wmOperatorType * /*ot*/,
PointerRNA *ptr)
{
if (!RNA_boolean_get(ptr, "animation")) {
return "";
}
if (RNA_boolean_get(ptr, "render_keyed_only")) {
return TIP_(
"Render the viewport for the animation range of this scene, but only render keyframes of "
"selected objects");
}
return TIP_("Render the viewport for the animation range of this scene");
}
void RENDER_OT_opengl(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Viewport Render";
ot->description = "Take a snapshot of the active viewport";
ot->idname = "RENDER_OT_opengl";
/* API callbacks. */
ot->get_description = screen_opengl_render_get_description;
ot->invoke = screen_opengl_render_invoke;
ot->exec = screen_opengl_render_exec; /* blocking */
ot->modal = screen_opengl_render_modal;
ot->cancel = screen_opengl_render_cancel;
ot->poll = ED_operator_screenactive;
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);
prop = RNA_def_boolean(ot->srna,
"render_keyed_only",
false,
"Render Keyframes Only",
"Render only those frames where selected objects have a key in their "
"animation data. Only used when rendering animation");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_boolean(
ot->srna, "sequencer", false, "Sequencer", "Render using the sequencer's OpenGL display");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_boolean(
ot->srna,
"write_still",
false,
"Write Image",
"Save the rendered image to the output path (used only when animation is disabled)");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna,
"view_context",
true,
"View Context",
"Use the current 3D view for rendering, else use scene settings");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}