Compositor: first steps to use realtime compositor outside viewport #108629

Manually merged
Brecht Van Lommel merged 4 commits from brecht/blender:realtime-render-compositor into main 2023-06-07 14:18:58 +02:00
16 changed files with 457 additions and 33 deletions
Showing only changes of commit cfbcb2fd8e - Show all commits

View File

@ -764,20 +764,26 @@ class NODE_PT_quality(bpy.types.Panel):
tree = snode.node_tree
prefs = bpy.context.preferences
use_realtime = False
col = layout.column()
if prefs.experimental.use_full_frame_compositor:
if prefs.experimental.use_experimental_compositors:
col.prop(tree, "execution_mode")
use_realtime = tree.execution_mode == 'REALTIME'
col = layout.column()
col.active = not use_realtime
col.prop(tree, "render_quality", text="Render")
col.prop(tree, "edit_quality", text="Edit")
col.prop(tree, "chunk_size")
col = layout.column()
col.active = not use_realtime
col.prop(tree, "use_opencl")
col.prop(tree, "use_groupnode_buffer")
col.prop(tree, "use_two_pass")
col.prop(tree, "use_viewer_border")
col.separator()
col = layout.column()
col.prop(snode, "use_auto_render")

View File

@ -2396,7 +2396,7 @@ class USERPREF_PT_experimental_prototypes(ExperimentalPanel, Panel):
({"property": "use_new_curves_tools"}, ("blender/blender/issues/68981", "#68981")),
({"property": "use_new_point_cloud_type"}, ("blender/blender/issues/75717", "#75717")),
({"property": "use_sculpt_texture_paint"}, ("blender/blender/issues/96225", "#96225")),
({"property": "use_full_frame_compositor"}, ("blender/blender/issues/88150", "#88150")),
({"property": "use_experimental_compositors"}, ("blender/blender/issues/88150", "#88150")),
({"property": "enable_eevee_next"}, ("blender/blender/issues/93220", "#93220")),
({"property": "enable_workbench_next"}, ("blender/blender/issues/101619", "#101619")),
({"property": "use_grease_pencil_version3"}, ("blender/blender/projects/40", "Grease Pencil 3.0")),

View File

@ -11,6 +11,8 @@
extern "C" {
#endif
struct Render;
/* Keep ascii art. */
/* clang-format off */
@ -294,6 +296,9 @@ extern "C" {
* It can be executed during editing (blenkernel/node.cc) or rendering
* (renderer/pipeline.c)
*
* \param render: [struct Render]
* Render instance for GPU context.
*
* \param render_data: [struct RenderData]
* Render data for this composite, this won't always belong to a scene.
*
@ -326,7 +331,8 @@ extern "C" {
*/
/* clang-format off */
void COM_execute(RenderData *render_data,
void COM_execute(Render *render,
RenderData *render_data,
Scene *scene,
bNodeTree *node_tree,
bool rendering,

View File

@ -14,6 +14,8 @@
#include "COM_WorkScheduler.h"
#include "COM_compositor.h"
#include "RE_compositor.hh"
static struct {
bool is_initialized = false;
ThreadMutex mutex;
@ -47,7 +49,8 @@ static void compositor_reset_node_tree_status(bNodeTree *node_tree)
node_tree->runtime->stats_draw(node_tree->runtime->sdh, IFACE_("Compositing"));
}
void COM_execute(RenderData *render_data,
void COM_execute(Render *render,
RenderData *render_data,
Scene *scene,
bNodeTree *node_tree,
bool rendering,
@ -73,26 +76,41 @@ void COM_execute(RenderData *render_data,
compositor_init_node_previews(render_data, node_tree);
compositor_reset_node_tree_status(node_tree);
/* Initialize workscheduler. */
const bool use_opencl = (node_tree->flag & NTREE_COM_OPENCL) != 0;
blender::compositor::WorkScheduler::initialize(use_opencl, BKE_render_num_threads(render_data));
if (U.experimental.use_full_frame_compositor &&
node_tree->execution_mode == NTREE_EXECUTION_MODE_REALTIME)
{
/* Realtime GPU compositer. */
/* Execute. */
const bool twopass = (node_tree->flag & NTREE_TWO_PASS) && !rendering;
if (twopass) {
blender::compositor::ExecutionSystem fast_pass(
render_data, scene, node_tree, rendering, true, view_name);
fast_pass.execute();
if (node_tree->runtime->test_break(node_tree->runtime->tbh)) {
BLI_mutex_unlock(&g_compositor.mutex);
return;
}
/* TODO: add persistence and depsgraph updates for better performance. */
blender::render::RealtimeCompositor compositer(
*render, *scene, *render_data, *node_tree, rendering, view_name);
compositer.execute();
}
else {
/* Tiled and Full Frame compositers. */
blender::compositor::ExecutionSystem system(
render_data, scene, node_tree, rendering, false, view_name);
system.execute();
/* Initialize workscheduler. */
const bool use_opencl = (node_tree->flag & NTREE_COM_OPENCL) != 0;
blender::compositor::WorkScheduler::initialize(use_opencl,
BKE_render_num_threads(render_data));
/* Execute. */
const bool twopass = (node_tree->flag & NTREE_TWO_PASS) && !rendering;
if (twopass) {
blender::compositor::ExecutionSystem fast_pass(
render_data, scene, node_tree, rendering, true, view_name);
fast_pass.execute();
if (node_tree->runtime->test_break(node_tree->runtime->tbh)) {
BLI_mutex_unlock(&g_compositor.mutex);
return;
}
}
blender::compositor::ExecutionSystem system(
render_data, scene, node_tree, rendering, false, view_name);
system.execute();
}
BLI_mutex_unlock(&g_compositor.mutex);
}

View File

@ -9,6 +9,7 @@ set(INC
../../blenkernel
../../blenlib
../../blentranslation
../../draw
../../gpu
../../imbuf
../../makesdna

View File

@ -3328,6 +3328,12 @@ static bool realtime_compositor_is_in_use(const bContext &context)
return false;
}
if (U.experimental.use_full_frame_compositor &&
scene->nodetree->execution_mode == NTREE_EXECUTION_MODE_REALTIME)
{
return true;
}
const Main *main = CTX_data_main(&context);
LISTBASE_FOREACH (const bScreen *, screen, &main->screens) {
LISTBASE_FOREACH (const ScrArea *, area, &screen->areabase) {

View File

@ -95,6 +95,8 @@ struct CompoJob {
/* Evaluated state/ */
Depsgraph *compositor_depsgraph;
bNodeTree *localtree;
/* Render instance. */
Render *re;
/* Jon system integration. */
const bool *stop;
bool *do_update;
@ -239,6 +241,9 @@ static void compo_initjob(void *cjv)
if (cj->recalc_flags) {
compo_tag_output_nodes(cj->localtree, cj->recalc_flags);
}
cj->re = RE_NewSceneRender(scene);
RE_gl_context_create(cj->re);
}
/* Called before redraw notifiers, it moves finished previews over. */
@ -286,17 +291,19 @@ static void compo_startjob(void *cjv,
BKE_callback_exec_id(cj->bmain, &scene->id, BKE_CB_EVT_COMPOSITE_PRE);
if ((cj->scene->r.scemode & R_MULTIVIEW) == 0) {
ntreeCompositExecTree(cj->scene, ntree, &cj->scene->r, false, true, "");
ntreeCompositExecTree(cj->re, cj->scene, ntree, &cj->scene->r, false, true, "");
}
else {
LISTBASE_FOREACH (SceneRenderView *, srv, &scene->r.views) {
if (BKE_scene_multiview_is_render_view_active(&scene->r, srv) == false) {
continue;
}
ntreeCompositExecTree(cj->scene, ntree, &cj->scene->r, false, true, srv->name);
ntreeCompositExecTree(cj->re, cj->scene, ntree, &cj->scene->r, false, true, srv->name);
}
}
RE_gl_context_destroy(cj->re);
ntree->runtime->test_break = nullptr;
ntree->runtime->stats_draw = nullptr;
ntree->runtime->progress = nullptr;

View File

@ -684,6 +684,7 @@ typedef struct bNodeTree {
typedef enum eNodeTreeExecutionMode {
NTREE_EXECUTION_MODE_TILED = 0,
NTREE_EXECUTION_MODE_FULL_FRAME = 1,
NTREE_EXECUTION_MODE_REALTIME = 2,
} eNodeTreeExecutionMode;
typedef enum eNodeTreeRuntimeFlag {

View File

@ -136,6 +136,11 @@ static const EnumPropertyItem rna_enum_execution_mode_items[] = {
0,
"Full Frame",
"Composites full image result as fast as possible"},
{NTREE_EXECUTION_MODE_REALTIME,
"REALTIME",
0,
"Realtime GPU",
"Use GPU accelerated compositing with more limited functionality"},
{0, NULL, 0, NULL, NULL},
};

View File

@ -6627,12 +6627,13 @@ static void rna_def_userdef_experimental(BlenderRNA *brna)
RNA_def_property_ui_text(
prop, "New Point Cloud Type", "Enable the new point cloud type in the ui");
prop = RNA_def_property(srna, "use_full_frame_compositor", PROP_BOOLEAN, PROP_NONE);
prop = RNA_def_property(srna, "use_experimental_compositors", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "use_full_frame_compositor", 1);
RNA_def_property_ui_text(prop,
"Full Frame Compositor",
"Enable compositor full frame execution mode option (no tiling, "
"reduces execution time and memory usage)");
RNA_def_property_ui_text(
prop,
"Experimental Compositors",
"Enable compositor full frame and realtime GPU execution mode options (no tiling, "
"reduces execution time and memory usage)");
RNA_def_property_update(prop, 0, "rna_userdef_update");
prop = RNA_def_property(srna, "use_new_curves_tools", PROP_BOOLEAN, PROP_NONE);

View File

@ -14,6 +14,11 @@
extern "C" {
#endif
struct Scene;
struct RenderData;
struct Render;
struct ViewLayer;
extern struct bNodeTreeType *ntreeType_Composite;
void node_cmp_rlayers_outputs(struct bNodeTree *ntree, struct bNode *node);
@ -27,7 +32,8 @@ const char *node_cmp_rlayers_sock_to_pass(int sock_index);
void register_node_type_cmp_custom_group(bNodeType *ntype);
void ntreeCompositExecTree(struct Scene *scene,
void ntreeCompositExecTree(struct Render *render,
struct Scene *scene,
struct bNodeTree *ntree,
struct RenderData *rd,
bool rendering,

View File

@ -171,7 +171,8 @@ void register_node_tree_type_cmp()
ntreeTypeAdd(tt);
}
void ntreeCompositExecTree(Scene *scene,
void ntreeCompositExecTree(Render *render,
Scene *scene,
bNodeTree *ntree,
RenderData *rd,
bool rendering,
@ -179,7 +180,7 @@ void ntreeCompositExecTree(Scene *scene,
const char *view_name)
{
#ifdef WITH_COMPOSITOR_CPU
COM_execute(rd, scene, ntree, rendering, view_name);
COM_execute(render, rd, scene, ntree, rendering, view_name);
#else
UNUSED_VARS(scene, ntree, rd, rendering, view_name);
#endif

View File

@ -9,9 +9,12 @@ set(INC
../blenkernel
../blenlib
../blentranslation
../compositor/realtime_compositor
../compositor/realtime_compositor/cached_resources
../depsgraph
../draw
../gpu
../gpu/intern
../imbuf
../makesdna
../makesrna
@ -30,6 +33,7 @@ set(INC_SYS
set(SRC
intern/bake.cc
intern/compositor.cc
intern/engine.cc
intern/initrender.cc
intern/multires_bake.cc
@ -42,6 +46,7 @@ set(SRC
intern/zbuf.c
RE_bake.h
RE_compositor.hh
RE_engine.h
RE_multires_bake.h
RE_pipeline.h
@ -56,6 +61,7 @@ set(SRC
)
set(LIB
bf_realtime_compositor
)
if(WITH_PYTHON)

View File

@ -0,0 +1,59 @@
/* SPDX-FileCopyrightText: 2023 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <memory>
struct bNodeTree;
struct Depsgraph;
struct Render;
struct RenderData;
struct Scene;
namespace blender {
namespace realtime_compositor {
class Evaluator;
}
namespace render {
class Context;
class TexturePool;
/* ------------------------------------------------------------------------------------------------
* Render Realtime Compositor
*
* Implementation of the compositor for final rendering, as opposed to the viewport compositor
* that is part of the draw manager. The input and output of this is pre-existing RenderResult
* buffers in scenes, that are uploaded to and read back from the GPU. */
class RealtimeCompositor {
private:
/* Render instance for GPU context to run compositor in. */
Render &render_;
std::unique_ptr<TexturePool> texture_pool_;
std::unique_ptr<Context> context_;
std::unique_ptr<realtime_compositor::Evaluator> evaluator_;
public:
RealtimeCompositor(Render &render,
const Scene &scene,
const RenderData &render_data,
const bNodeTree &node_tree,
const bool use_file_output,
const char *view_name);
~RealtimeCompositor();
/* Evaluate the compositor and output to the scene render result. */
void execute();
/* If the compositor node tree changed, reset the evaluator. */
void update(const Depsgraph *depsgraph);
};
} // namespace render
} // namespace blender

View File

@ -0,0 +1,301 @@
/* SPDX-FileCopyrightText: 2023 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_threads.h"
#include "BLI_vector.hh"
#include "BKE_global.h"
#include "BKE_image.h"
#include "BKE_node.hh"
#include "BKE_scene.h"
#include "DRW_engine.h"
#include "COM_context.hh"
#include "COM_evaluator.hh"
#include "RE_compositor.hh"
#include "RE_pipeline.h"
namespace blender::render {
/* Render Texture Pool */
class TexturePool : public realtime_compositor::TexturePool {
public:
Vector<GPUTexture *> textures_;
virtual ~TexturePool()
{
for (GPUTexture *texture : textures_) {
GPU_texture_free(texture);
}
}
GPUTexture *allocate_texture(int2 size, eGPUTextureFormat format) override
{
/* TODO: should share pool with draw manager. It needs some globals
* initialization figured out there first. */
#if 0
DrawEngineType *owner = (DrawEngineType *)this;
return DRW_texture_pool_query_2d(size.x, size.y, format, owner);
#else
GPUTexture *texture = GPU_texture_create_2d(
"compositor_texture_pool", size.x, size.y, 1, format, GPU_TEXTURE_USAGE_GENERAL, NULL);
textures_.append(texture);
return texture;
#endif
}
};
/* Render Context */
class Context : public realtime_compositor::Context {
private:
/* Input data. */
const Scene &scene_;
const RenderData &render_data_;
const bNodeTree &node_tree_;
const bool use_file_output_;
const char *view_name_;
/* Output combined texture. */
GPUTexture *output_texture_ = nullptr;
public:
Context(const Scene &scene,
const RenderData &render_data,
const bNodeTree &node_tree,
const bool use_file_output,
const char *view_name,
TexturePool &texture_pool)
: realtime_compositor::Context(texture_pool),
scene_(scene),
render_data_(render_data),
node_tree_(node_tree),
use_file_output_(use_file_output),
view_name_(view_name)
{
}
virtual ~Context()
{
GPU_texture_free(output_texture_);
}
const bNodeTree &get_node_tree() const override
{
return node_tree_;
}
bool use_file_output() const override
{
return use_file_output_;
}
bool use_texture_color_management() const override
{
return BKE_scene_check_color_management_enabled(&scene_);
}
const RenderData &get_render_data() const override
{
return render_data_;
}
int2 get_render_size() const override
{
int width, height;
BKE_render_resolution(&render_data_, false, &width, &height);
return int2(width, height);
}
rcti get_compositing_region() const override
{
const int2 render_size = get_render_size();
const rcti render_region = rcti{0, render_size.x, 0, render_size.y};
return render_region;
}
GPUTexture *get_output_texture() override
{
/* TODO: support outputting for viewers and previews.
* TODO: just a temporary hack, needs to get stored in RenderResult,
* once that supports GPU buffers. */
if (output_texture_ == nullptr) {
const int2 size = get_render_size();
output_texture_ = GPU_texture_create_2d("compositor_output_texture",
size.x,
size.y,
1,
GPU_RGBA16F,
GPU_TEXTURE_USAGE_GENERAL,
NULL);
}
return output_texture_;
}
GPUTexture *get_input_texture(int view_layer_id, const char *pass_name) override
{
/* TODO: eventually this should get cached on the RenderResult itself when
* it supports storing GPU buffers, for faster updates. But will also need
* some eviction strategy to avoid too much GPU memory usage. */
Render *re = RE_GetSceneRender(&scene_);
RenderResult *rr = nullptr;
GPUTexture *input_texture = nullptr;
if (re) {
rr = RE_AcquireResultRead(re);
}
if (rr) {
ViewLayer *view_layer = (ViewLayer *)BLI_findlink(&scene_.view_layers, view_layer_id);
if (view_layer) {
RenderLayer *rl = RE_GetRenderLayer(rr, view_layer->name);
if (rl) {
RenderPass *rpass = (RenderPass *)BLI_findstring(
&rl->passes, pass_name, offsetof(RenderPass, name));
if (rpass && rpass->buffer.data) {
const int2 size(rl->rectx, rl->recty);
if (rpass->channels == 1) {
input_texture = texture_pool().acquire_float(size);
if (input_texture) {
GPU_texture_update(input_texture, GPU_DATA_FLOAT, rpass->buffer.data);
}
}
else if (rpass->channels == 3) {
input_texture = texture_pool().acquire_color(size);
if (input_texture) {
/* TODO: conversion could be done as part of GPU upload somehow? */
const float *rgb_buffer = rpass->buffer.data;
float *rgba_buffer = MEM_cnew_array<float>(4 * size.x * size.y, __func__);
for (size_t i = 0; (size_t)size.x * (size_t)size.y; i++) {
rgba_buffer[i * 4 + 0] = rgb_buffer[i * 3 + 0];
rgba_buffer[i * 4 + 1] = rgb_buffer[i * 3 + 1];
rgba_buffer[i * 4 + 2] = rgb_buffer[i * 3 + 2];
rgba_buffer[i * 4 + 3] = 1.0f;
}
GPU_texture_update(input_texture, GPU_DATA_FLOAT, rgba_buffer);
MEM_freeN(rgba_buffer);
}
}
else if (rpass->channels == 4) {
input_texture = texture_pool().acquire_color(size);
if (input_texture) {
GPU_texture_update(input_texture, GPU_DATA_FLOAT, rpass->buffer.data);
}
}
}
}
}
}
if (re) {
RE_ReleaseResult(re);
re = nullptr;
}
return input_texture;
}
StringRef get_view_name() override
{
return view_name_;
}
void set_info_message(StringRef /* message */) const override
{
/* TODO: ignored for now. Currently only used to communicate incomplete node support
* which is already shown on the node itself.
*
* Perhaps this overall info message could be replaced by a boolean indicating
* incomplete support, and leave more specific message to individual nodes? */
}
IDRecalcFlag query_id_recalc_flag(ID *id) const override
{
/* TODO: implement? */
return IDRecalcFlag(0);
}
void output_to_render_result()
{
Render *re = RE_GetSceneRender(&scene_);
RenderResult *rr = RE_AcquireResultWrite(re);
if (rr) {
RenderView *rv = RE_RenderViewGetByName(rr, view_name_);
GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE);
float *output_buffer = (float *)GPU_texture_read(output_texture_, GPU_DATA_FLOAT, 0);
if (output_buffer) {
RE_RenderBuffer_assign_data(&rv->combined_buffer, output_buffer);
}
/* TODO: z-buffer output. */
rr->have_combined = true;
}
if (re) {
RE_ReleaseResult(re);
re = nullptr;
}
Image *image = BKE_image_ensure_viewer(G.main, IMA_TYPE_R_RESULT, "Render Result");
BKE_image_partial_update_mark_full_update(image);
BLI_thread_lock(LOCK_DRAW_IMAGE);
BKE_image_signal(G.main, image, nullptr, IMA_SIGNAL_FREE);
BLI_thread_unlock(LOCK_DRAW_IMAGE);
}
};
/* Render Realtime Compositor */
RealtimeCompositor::RealtimeCompositor(Render &render,
const Scene &scene,
const RenderData &render_data,
const bNodeTree &node_tree,
const bool use_file_output,
const char *view_name)
: render_(render)
{
/* Create resources with GPU context enabled. */
DRW_render_context_enable(&render_);
texture_pool_ = std::make_unique<TexturePool>();
context_ = std::make_unique<Context>(
scene, render_data, node_tree, use_file_output, view_name, *texture_pool_);
evaluator_ = std::make_unique<realtime_compositor::Evaluator>(*context_);
DRW_render_context_disable(&render_);
}
RealtimeCompositor::~RealtimeCompositor()
{
/* Free resources with GPU context enabled. */
DRW_render_context_enable(&render_);
evaluator_.reset();
context_.reset();
texture_pool_.reset();
DRW_render_context_disable(&render_);
}
void RealtimeCompositor::execute()
{
DRW_render_context_enable(&render_);
evaluator_->evaluate();
context_->output_to_render_result();
DRW_render_context_disable(&render_);
}
void RealtimeCompositor::update(const Depsgraph *depsgraph)
{
/* TODO: implement */
}
} // namespace blender::render

View File

@ -1194,7 +1194,7 @@ static void do_render_compositor(Render *re)
LISTBASE_FOREACH (RenderView *, rv, &re->result->views) {
ntreeCompositExecTree(
re->pipeline_scene_eval, ntree, &re->r, true, G.background == 0, rv->name);
re, re->pipeline_scene_eval, ntree, &re->r, true, G.background == 0, rv->name);
}
ntree->runtime->stats_draw = nullptr;