Files
test2/source/blender/editors/render/render_opengl.cc
Sybren A. Stüvel 7d6b55ab3f Anim: remove "Render Viewport Keyframes" operator
Remove the "Render Viewport Keyframes" mode of the `RENDER_OT_opengl`
operator. This was implemented 6 years ago to avoid camera strobing when
animating on 2s, and was (AFAIK) little used after that initial need
(mostly because of usability issues and limited functionality).

The Fix to Camera operator deals with this at an animation level; that's
a better solution, as it also works for regular (non-viewport) renders.

See #146749

Pull Request: https://projects.blender.org/blender/blender/pulls/147463
2025-10-07 11:07:19 +02:00

1250 lines
38 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;
/* 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 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_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 (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;
}
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);
}
/* 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 "";
}
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, "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);
}