Compositor: Support passes in new CPU compositor
This patch adds support for passes in the new CPU compositor. This involves rewriting the get_input_texture method into a get_pass methods that returns a result as opposed to a texture. The result wraps the cached GPU texture or image buffer depending on the execution device. The Render Layers node was implemented for CPU execution and a new utility constructor for the result class was added to determine type and precision based on GPU texture format. The fallback depth pass that was retrieved from the viewport frame buffer was removed, as it was a hack that can no longer be supported due to the use of stencil format. Pull Request: https://projects.blender.org/blender/blender/pulls/129154
This commit is contained in:
@@ -69,7 +69,7 @@ class Context {
|
||||
virtual const RenderData &get_render_data() const = 0;
|
||||
|
||||
/* Get the width and height of the render passes and of the output texture returned by the
|
||||
* get_input_texture and get_output_texture methods respectively. */
|
||||
* get_pass and get_output_texture methods respectively. */
|
||||
virtual int2 get_render_size() const = 0;
|
||||
|
||||
/* Get the rectangular region representing the area of the input that the compositor will operate
|
||||
@@ -91,11 +91,8 @@ class Context {
|
||||
bool is_data,
|
||||
ResultPrecision precision) = 0;
|
||||
|
||||
/* Get the texture where the given render pass is stored. This should be called by the Render
|
||||
* Layer node to populate its outputs. */
|
||||
virtual GPUTexture *get_input_texture(const Scene *scene,
|
||||
int view_layer,
|
||||
const char *pass_name) = 0;
|
||||
/* Get the result where the given render pass is stored. */
|
||||
virtual Result get_pass(const Scene *scene, int view_layer, const char *pass_name) = 0;
|
||||
|
||||
/* Get the name of the view currently being rendered. */
|
||||
virtual StringRef get_view_name() const = 0;
|
||||
|
||||
@@ -169,6 +169,10 @@ class Result {
|
||||
/* Construct a result of the given type and precision within the given context. */
|
||||
Result(Context &context, ResultType type, ResultPrecision precision);
|
||||
|
||||
/* Construct a result of an appropriate type and precision based on the given GPU texture format
|
||||
* within the given context. */
|
||||
Result(Context &context, eGPUTextureFormat format);
|
||||
|
||||
/* Returns the appropriate GPU texture format based on the given result type and precision. */
|
||||
static eGPUTextureFormat gpu_texture_format(ResultType type, ResultPrecision precision);
|
||||
|
||||
|
||||
@@ -26,6 +26,11 @@ Result::Result(Context &context, ResultType type, ResultPrecision precision)
|
||||
{
|
||||
}
|
||||
|
||||
Result::Result(Context &context, eGPUTextureFormat format)
|
||||
: context_(&context), type_(Result::type(format)), precision_(Result::precision(format))
|
||||
{
|
||||
}
|
||||
|
||||
eGPUTextureFormat Result::gpu_texture_format(ResultType type, ResultPrecision precision)
|
||||
{
|
||||
switch (precision) {
|
||||
|
||||
@@ -152,37 +152,40 @@ class Context : public realtime_compositor::Context {
|
||||
return result;
|
||||
}
|
||||
|
||||
GPUTexture *get_input_texture(const Scene *scene, int view_layer, const char *pass_name) override
|
||||
realtime_compositor::Result get_pass(const Scene *scene,
|
||||
int view_layer,
|
||||
const char *pass_name) override
|
||||
{
|
||||
if (DEG_get_original_id(const_cast<ID *>(&scene->id)) !=
|
||||
DEG_get_original_id(&DRW_context_state_get()->scene->id))
|
||||
{
|
||||
return nullptr;
|
||||
return realtime_compositor::Result(*this);
|
||||
}
|
||||
|
||||
if (view_layer != 0) {
|
||||
return nullptr;
|
||||
return realtime_compositor::Result(*this);
|
||||
}
|
||||
|
||||
/* The combined pass is a special case where we return the viewport color texture, because it
|
||||
* includes Grease Pencil objects since GP is drawn using their own engine. */
|
||||
if (STREQ(pass_name, RE_PASSNAME_COMBINED)) {
|
||||
return DRW_viewport_texture_list_get()->color;
|
||||
GPUTexture *combined_texture = DRW_viewport_texture_list_get()->color;
|
||||
realtime_compositor::Result pass = realtime_compositor::Result(
|
||||
*this, GPU_texture_format(combined_texture));
|
||||
pass.wrap_external(combined_texture);
|
||||
return pass;
|
||||
}
|
||||
|
||||
/* Return the pass that was written by the engine if such pass was found. */
|
||||
GPUTexture *pass_texture = DRW_viewport_pass_texture_get(pass_name).gpu_texture();
|
||||
if (pass_texture) {
|
||||
return pass_texture;
|
||||
realtime_compositor::Result pass = realtime_compositor::Result(
|
||||
*this, GPU_texture_format(pass_texture));
|
||||
pass.wrap_external(pass_texture);
|
||||
return pass;
|
||||
}
|
||||
|
||||
/* If no Z pass was found above, return the viewport depth as a fallback, which might be
|
||||
* populated if overlays are enabled. */
|
||||
if (STREQ(pass_name, RE_PASSNAME_Z)) {
|
||||
return DRW_viewport_texture_list_get()->depth;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
return realtime_compositor::Result(*this);
|
||||
}
|
||||
|
||||
StringRef get_view_name() const override
|
||||
|
||||
@@ -572,8 +572,7 @@ class CryptoMatteOperation : public BaseCryptoMatteOperation {
|
||||
const int cryptomatte_layers_count = int(math::ceil(view_layer->cryptomatte_levels / 2.0f));
|
||||
for (int i = 0; i < cryptomatte_layers_count; i++) {
|
||||
const std::string pass_name = fmt::format("{}{:02}", cryptomatte_type, i);
|
||||
GPUTexture *pass_texture = context().get_input_texture(
|
||||
scene, view_layer_index, pass_name.c_str());
|
||||
GPUTexture *pass_texture = context().get_pass(scene, view_layer_index, pass_name.c_str());
|
||||
|
||||
/* If this Cryptomatte layer wasn't found, then all later Cryptomatte layers can't be used
|
||||
* even if they were found. */
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
#include "BLI_assert.h"
|
||||
#include "BLI_linklist.h"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
#include "BLI_rect.h"
|
||||
@@ -665,104 +666,135 @@ class RenderLayerOperation : public NodeOperation {
|
||||
|
||||
void execute() override
|
||||
{
|
||||
/* Not yet supported on CPU. */
|
||||
if (!context().use_gpu()) {
|
||||
for (const bNodeSocket *output : this->node()->output_sockets()) {
|
||||
Result &output_result = get_result(output->identifier);
|
||||
if (output_result.should_compute()) {
|
||||
output_result.allocate_invalid();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
const Scene *scene = reinterpret_cast<const Scene *>(this->bnode().id);
|
||||
const int view_layer = this->bnode().custom1;
|
||||
|
||||
const Scene *scene = reinterpret_cast<const Scene *>(bnode().id);
|
||||
const int view_layer = bnode().custom1;
|
||||
|
||||
Result &image_result = get_result("Image");
|
||||
Result &alpha_result = get_result("Alpha");
|
||||
Result &image_result = this->get_result("Image");
|
||||
Result &alpha_result = this->get_result("Alpha");
|
||||
|
||||
if (image_result.should_compute() || alpha_result.should_compute()) {
|
||||
GPUTexture *combined_texture = context().get_input_texture(
|
||||
const Result combined_pass = this->context().get_pass(
|
||||
scene, view_layer, RE_PASSNAME_COMBINED);
|
||||
if (image_result.should_compute()) {
|
||||
execute_pass(image_result, combined_texture, "compositor_read_input_color");
|
||||
this->execute_pass(combined_pass, image_result);
|
||||
}
|
||||
if (alpha_result.should_compute()) {
|
||||
execute_pass(alpha_result, combined_texture, "compositor_read_input_alpha");
|
||||
this->execute_pass(combined_pass, alpha_result);
|
||||
}
|
||||
}
|
||||
|
||||
/* Other output passes are not supported for now, so allocate them as invalid. */
|
||||
for (const bNodeSocket *output : this->node()->output_sockets()) {
|
||||
if (STR_ELEM(output->identifier, "Image", "Alpha")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Result &result = get_result(output->identifier);
|
||||
Result &result = this->get_result(output->identifier);
|
||||
if (!result.should_compute()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
context().populate_meta_data_for_pass(
|
||||
this->context().populate_meta_data_for_pass(
|
||||
scene, view_layer, output->identifier, result.meta_data);
|
||||
|
||||
GPUTexture *pass_texture = context().get_input_texture(
|
||||
scene, view_layer, output->identifier);
|
||||
if (output->type == SOCK_FLOAT) {
|
||||
execute_pass(result, pass_texture, "compositor_read_input_float");
|
||||
}
|
||||
else if (output->type == SOCK_VECTOR) {
|
||||
execute_pass(result, pass_texture, "compositor_read_input_vector");
|
||||
}
|
||||
else if (output->type == SOCK_RGBA) {
|
||||
execute_pass(result, pass_texture, "compositor_read_input_color");
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
const Result pass = this->context().get_pass(scene, view_layer, output->identifier);
|
||||
this->execute_pass(pass, result);
|
||||
}
|
||||
}
|
||||
|
||||
void execute_pass(Result &result, GPUTexture *pass_texture, const char *shader_name)
|
||||
void execute_pass(const Result &pass, Result &result)
|
||||
{
|
||||
if (pass_texture == nullptr) {
|
||||
if (!pass.is_allocated()) {
|
||||
/* Pass not rendered yet, or not supported by viewport. */
|
||||
result.allocate_invalid();
|
||||
context().set_info_message("Viewport compositor setup not fully supported");
|
||||
this->context().set_info_message("Viewport compositor setup not fully supported");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!context().is_valid_compositing_region()) {
|
||||
if (!this->context().is_valid_compositing_region()) {
|
||||
result.allocate_invalid();
|
||||
return;
|
||||
}
|
||||
|
||||
const ResultPrecision precision = Result::precision(GPU_texture_format(pass_texture));
|
||||
GPUShader *shader = context().get_shader(shader_name, precision);
|
||||
if (this->context().use_gpu()) {
|
||||
this->execute_pass_gpu(pass, result);
|
||||
}
|
||||
else {
|
||||
this->execute_pass_cpu(pass, result);
|
||||
}
|
||||
}
|
||||
|
||||
void execute_pass_gpu(const Result &pass, Result &result)
|
||||
{
|
||||
result.set_precision(pass.precision());
|
||||
|
||||
GPUShader *shader = this->context().get_shader(this->get_shader_name(pass, result),
|
||||
result.precision());
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
/* The compositing space might be limited to a subset of the pass texture, so only read that
|
||||
* compositing region into an appropriately sized texture. */
|
||||
const rcti compositing_region = context().get_compositing_region();
|
||||
* compositing region into an appropriately sized result. */
|
||||
const rcti compositing_region = this->context().get_compositing_region();
|
||||
const int2 lower_bound = int2(compositing_region.xmin, compositing_region.ymin);
|
||||
GPU_shader_uniform_2iv(shader, "lower_bound", lower_bound);
|
||||
|
||||
const int input_unit = GPU_shader_get_sampler_binding(shader, "input_tx");
|
||||
GPU_texture_bind(pass_texture, input_unit);
|
||||
pass.bind_as_texture(shader, "input_tx");
|
||||
|
||||
result.set_precision(precision);
|
||||
|
||||
const int2 compositing_region_size = context().get_compositing_region_size();
|
||||
result.allocate_texture(Domain(compositing_region_size));
|
||||
result.allocate_texture(Domain(this->context().get_compositing_region_size()));
|
||||
result.bind_as_image(shader, "output_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, compositing_region_size);
|
||||
compute_dispatch_threads_at_least(shader, result.domain().size);
|
||||
|
||||
GPU_shader_unbind();
|
||||
GPU_texture_unbind(pass_texture);
|
||||
pass.unbind_as_texture();
|
||||
result.unbind_as_image();
|
||||
}
|
||||
|
||||
const char *get_shader_name(const Result &pass, const Result &result)
|
||||
{
|
||||
/* Special case for alpha output. */
|
||||
if (pass.type() == ResultType::Color && result.type() == ResultType::Float) {
|
||||
return "compositor_read_input_alpha";
|
||||
}
|
||||
|
||||
switch (pass.type()) {
|
||||
case ResultType::Float:
|
||||
return "compositor_read_input_float";
|
||||
case ResultType::Vector:
|
||||
return "compositor_read_input_vector";
|
||||
case ResultType::Color:
|
||||
return "compositor_read_input_color";
|
||||
case ResultType::Float3:
|
||||
return "compositor_read_input_vector";
|
||||
default:
|
||||
/* Other types are internal and needn't be handled by operations. */
|
||||
break;
|
||||
}
|
||||
|
||||
BLI_assert_unreachable();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void execute_pass_cpu(const Result &pass, Result &result)
|
||||
{
|
||||
/* The compositing space might be limited to a subset of the pass texture, so only read that
|
||||
* compositing region into an appropriately sized result. */
|
||||
const rcti compositing_region = this->context().get_compositing_region();
|
||||
const int2 lower_bound = int2(compositing_region.xmin, compositing_region.ymin);
|
||||
|
||||
result.allocate_texture(Domain(this->context().get_compositing_region_size()));
|
||||
|
||||
/* Special case for alpha output. */
|
||||
if (pass.type() == ResultType::Color && result.type() == ResultType::Float) {
|
||||
parallel_for(result.domain().size, [&](const int2 texel) {
|
||||
result.store_pixel(texel, float4(pass.load_pixel(texel + lower_bound).w));
|
||||
});
|
||||
}
|
||||
else {
|
||||
parallel_for(result.domain().size, [&](const int2 texel) {
|
||||
result.store_pixel(texel, pass.load_pixel(texel + lower_bound));
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static NodeOperation *get_compositor_operation(Context &context, DNode node)
|
||||
|
||||
@@ -160,8 +160,11 @@ class Context : public realtime_compositor::Context {
|
||||
/* Viewer output result. */
|
||||
realtime_compositor::Result viewer_output_result_;
|
||||
|
||||
/* Cached textures that the compositor took ownership of. */
|
||||
Vector<GPUTexture *> textures_;
|
||||
/* Cached GPU and CPU passes that the compositor took ownership of. Those had their reference
|
||||
* count incremented when accessed and need to be freed/have their reference count decremented
|
||||
* when destroying the context. */
|
||||
Vector<GPUTexture *> cached_gpu_passes_;
|
||||
Vector<ImBuf *> cached_cpu_passes_;
|
||||
|
||||
public:
|
||||
Context(const ContextInputData &input_data, TexturePool &texture_pool)
|
||||
@@ -176,8 +179,11 @@ class Context : public realtime_compositor::Context {
|
||||
{
|
||||
output_result_.release();
|
||||
viewer_output_result_.release();
|
||||
for (GPUTexture *texture : textures_) {
|
||||
GPU_texture_free(texture);
|
||||
for (GPUTexture *pass : cached_gpu_passes_) {
|
||||
GPU_texture_free(pass);
|
||||
}
|
||||
for (ImBuf *pass : cached_cpu_passes_) {
|
||||
IMB_freeImBuf(pass);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,61 +290,71 @@ class Context : public realtime_compositor::Context {
|
||||
return viewer_output_result_;
|
||||
}
|
||||
|
||||
GPUTexture *get_input_texture(const Scene *scene,
|
||||
int view_layer_id,
|
||||
const char *pass_name) override
|
||||
realtime_compositor::Result get_pass(const Scene *scene,
|
||||
int view_layer_id,
|
||||
const char *pass_name) override
|
||||
{
|
||||
if (!scene) {
|
||||
return nullptr;
|
||||
return realtime_compositor::Result(*this);
|
||||
}
|
||||
|
||||
ViewLayer *view_layer = static_cast<ViewLayer *>(
|
||||
BLI_findlink(&scene->view_layers, view_layer_id));
|
||||
if (!view_layer) {
|
||||
return nullptr;
|
||||
return realtime_compositor::Result(*this);
|
||||
}
|
||||
|
||||
Render *render = RE_GetSceneRender(scene);
|
||||
if (!render) {
|
||||
return nullptr;
|
||||
return realtime_compositor::Result(*this);
|
||||
}
|
||||
|
||||
RenderResult *render_result = RE_AcquireResultRead(render);
|
||||
if (!render_result) {
|
||||
RE_ReleaseResult(render);
|
||||
return nullptr;
|
||||
return realtime_compositor::Result(*this);
|
||||
}
|
||||
|
||||
RenderLayer *render_layer = RE_GetRenderLayer(render_result, view_layer->name);
|
||||
if (!render_layer) {
|
||||
RE_ReleaseResult(render);
|
||||
return nullptr;
|
||||
return realtime_compositor::Result(*this);
|
||||
}
|
||||
|
||||
RenderPass *render_pass = RE_pass_find_by_name(
|
||||
render_layer, pass_name, this->get_view_name().data());
|
||||
if (!render_pass) {
|
||||
RE_ReleaseResult(render);
|
||||
return nullptr;
|
||||
return realtime_compositor::Result(*this);
|
||||
}
|
||||
|
||||
if (!render_pass || !render_pass->ibuf || !render_pass->ibuf->float_buffer.data) {
|
||||
RE_ReleaseResult(render);
|
||||
return nullptr;
|
||||
return realtime_compositor::Result(*this);
|
||||
}
|
||||
|
||||
GPUTexture *texture = RE_pass_ensure_gpu_texture_cache(render, render_pass);
|
||||
if (!texture) {
|
||||
RE_ReleaseResult(render);
|
||||
return nullptr;
|
||||
}
|
||||
const eGPUTextureFormat format = (render_pass->channels == 1) ? GPU_R32F :
|
||||
(render_pass->channels == 3) ? GPU_RGB32F :
|
||||
GPU_RGBA32F;
|
||||
realtime_compositor::Result pass = realtime_compositor::Result(*this, format);
|
||||
|
||||
/* Don't assume render keeps texture around, add our own reference. */
|
||||
GPU_texture_ref(texture);
|
||||
textures_.append(texture);
|
||||
if (this->use_gpu()) {
|
||||
GPUTexture *pass_texture = RE_pass_ensure_gpu_texture_cache(render, render_pass);
|
||||
/* Don't assume render will keep pass data stored, add our own reference. */
|
||||
GPU_texture_ref(pass_texture);
|
||||
pass.wrap_external(pass_texture);
|
||||
cached_gpu_passes_.append(pass_texture);
|
||||
}
|
||||
else {
|
||||
/* Don't assume render will keep pass data stored, add our own reference. */
|
||||
IMB_refImBuf(render_pass->ibuf);
|
||||
pass.wrap_external(render_pass->ibuf->float_buffer.data,
|
||||
int2(render_pass->ibuf->x, render_pass->ibuf->y));
|
||||
cached_cpu_passes_.append(render_pass->ibuf);
|
||||
}
|
||||
|
||||
RE_ReleaseResult(render);
|
||||
return texture;
|
||||
return pass;
|
||||
}
|
||||
|
||||
StringRef get_view_name() const override
|
||||
|
||||
Reference in New Issue
Block a user