Realtime Compositor: Add basic output nodes

This patch implements the following nodes for the realtime compositor:

- Composite node.
- Viewer node.
- Split viewer node.

Differential Revision: https://developer.blender.org/D15226

Reviewed By: Clement Foucault
This commit is contained in:
Omar Emara 2022-08-10 09:40:07 +02:00
parent 624b0ac656
commit 365fbb447e
11 changed files with 406 additions and 0 deletions

View File

@ -325,6 +325,8 @@ set(GLSL_SRC
shaders/compositor/compositor_convert.glsl
shaders/compositor/compositor_realize_on_domain.glsl
shaders/compositor/compositor_set_alpha.glsl
shaders/compositor/compositor_split_viewer.glsl
shaders/compositor/library/gpu_shader_compositor_main.glsl
shaders/compositor/library/gpu_shader_compositor_store_output.glsl
@ -538,6 +540,8 @@ set(SRC_SHADER_CREATE_INFOS
shaders/compositor/infos/compositor_convert_info.hh
shaders/compositor/infos/compositor_realize_on_domain_info.hh
shaders/compositor/infos/compositor_set_alpha_info.hh
shaders/compositor/infos/compositor_split_viewer_info.hh
)
set(SHADER_CREATE_INFOS_CONTENT "")

View File

@ -0,0 +1,8 @@
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
vec4 color = vec4(texture_load(image_tx, texel).rgb, texture_load(alpha_tx, texel).x);
imageStore(output_img, texel, color);
}

View File

@ -0,0 +1,14 @@
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
#if defined(SPLIT_HORIZONTAL)
bool condition = (view_size.x * split_ratio) < texel.x;
#elif defined(SPLIT_VERTICAL)
bool condition = (view_size.y * split_ratio) < texel.y;
#endif
vec4 color = condition ? texture_load(first_image_tx, texel) :
texture_load(second_image_tx, texel);
imageStore(output_img, texel, color);
}

View File

@ -61,3 +61,9 @@ GPU_SHADER_CREATE_INFO(compositor_convert_float_to_half_float)
.image(0, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img")
.define("CONVERT_EXPRESSION(value)", "vec4(value.r, vec3(0.0))")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(compositor_convert_color_to_opaque)
.additional_info("compositor_convert_shared")
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img")
.define("CONVERT_EXPRESSION(value)", "vec4(value.rgb, 1.0)")
.do_static_compilation(true);

View File

@ -0,0 +1,11 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "gpu_shader_create_info.hh"
GPU_SHADER_CREATE_INFO(compositor_set_alpha)
.local_group_size(16, 16)
.sampler(0, ImageType::FLOAT_2D, "image_tx")
.sampler(1, ImageType::FLOAT_2D, "alpha_tx")
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img")
.compute_source("compositor_set_alpha.glsl")
.do_static_compilation(true);

View File

@ -0,0 +1,22 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "gpu_shader_create_info.hh"
GPU_SHADER_CREATE_INFO(compositor_split_viewer_shared)
.local_group_size(16, 16)
.push_constant(Type::FLOAT, "split_ratio")
.push_constant(Type::IVEC2, "view_size")
.sampler(0, ImageType::FLOAT_2D, "first_image_tx")
.sampler(1, ImageType::FLOAT_2D, "second_image_tx")
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img")
.compute_source("compositor_split_viewer.glsl");
GPU_SHADER_CREATE_INFO(compositor_split_viewer_horizontal)
.additional_info("compositor_split_viewer_shared")
.define("SPLIT_HORIZONTAL")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(compositor_split_viewer_vertical)
.additional_info("compositor_split_viewer_shared")
.define("SPLIT_VERTICAL")
.do_static_compilation(true);

View File

@ -1838,6 +1838,12 @@ enum {
/* viewer and composite output. */
#define CMP_NODE_OUTPUT_IGNORE_ALPHA 1
/* Split Viewer Node. Stored in custom2. */
typedef enum CMPNodeSplitViewerAxis {
CMP_NODE_SPLIT_VIEWER_HORIZONTAL = 0,
CMP_NODE_SPLIT_VIEWER_VERTICAL = 1,
} CMPNodeSplitViewerAxis;
/* Plane track deform node. */
enum {

View File

@ -10,11 +10,13 @@ set(INC
../../blenlib
../../blentranslation
../../depsgraph
../../gpu
../../imbuf
../../makesdna
../../makesrna
../../render
../../windowmanager
../../compositor/realtime_compositor
../../../../intern/guardedalloc
# dna_type_offsets.h
@ -120,6 +122,10 @@ set(SRC
node_composite_util.hh
)
set(LIB
bf_realtime_compositor
)
if(WITH_IMAGE_OPENEXR)
add_definitions(-DWITH_OPENEXR)
endif()

View File

@ -5,9 +5,18 @@
* \ingroup cmpnodes
*/
#include "BLI_math_vec_types.hh"
#include "UI_interface.h"
#include "UI_resources.h"
#include "GPU_shader.h"
#include "GPU_state.h"
#include "GPU_texture.h"
#include "COM_node_operation.hh"
#include "COM_utilities.hh"
#include "node_composite_util.hh"
/* **************** COMPOSITE ******************** */
@ -26,6 +35,125 @@ static void node_composit_buts_composite(uiLayout *layout, bContext *UNUSED(C),
uiItemR(layout, ptr, "use_alpha", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE);
}
using namespace blender::realtime_compositor;
class CompositeOperation : public NodeOperation {
public:
using NodeOperation::NodeOperation;
void execute() override
{
const Result &image = get_input("Image");
const Result &alpha = get_input("Alpha");
if (image.is_single_value() && alpha.is_single_value()) {
execute_clear();
}
else if (ignore_alpha()) {
execute_ignore_alpha();
}
else if (!node().input_by_identifier("Alpha")->is_logically_linked()) {
execute_copy();
}
else {
execute_set_alpha();
}
}
/* Executes when all inputs are single values, in which case, the output texture can just be
* cleared to the appropriate color. */
void execute_clear()
{
const Result &image = get_input("Image");
const Result &alpha = get_input("Alpha");
float4 color = image.get_color_value();
if (ignore_alpha()) {
color.w = 1.0f;
}
else if (node().input_by_identifier("Alpha")->is_logically_linked()) {
color.w = alpha.get_float_value();
}
GPU_texture_clear(context().get_output_texture(), GPU_DATA_FLOAT, color);
}
/* Executes when the alpha channel of the image is ignored. */
void execute_ignore_alpha()
{
GPUShader *shader = shader_manager().get("compositor_convert_color_to_opaque");
GPU_shader_bind(shader);
const Result &image = get_input("Image");
image.bind_as_texture(shader, "input_tx");
GPUTexture *output_texture = context().get_output_texture();
const int image_unit = GPU_shader_get_texture_binding(shader, "output_img");
GPU_texture_image_bind(output_texture, image_unit);
compute_dispatch_threads_at_least(shader, compute_domain().size);
image.unbind_as_texture();
GPU_texture_image_unbind(output_texture);
GPU_shader_unbind();
}
/* Executes when the image texture is written with no adjustments and can thus be copied directly
* to the output texture. */
void execute_copy()
{
const Result &image = get_input("Image");
/* Make sure any prior writes to the texture are reflected before copying it. */
GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE);
GPU_texture_copy(context().get_output_texture(), image.texture());
}
/* Executes when the alpha channel of the image is set as the value of the input alpha. */
void execute_set_alpha()
{
GPUShader *shader = shader_manager().get("compositor_set_alpha");
GPU_shader_bind(shader);
const Result &image = get_input("Image");
image.bind_as_texture(shader, "image_tx");
const Result &alpha = get_input("Alpha");
alpha.bind_as_texture(shader, "alpha_tx");
GPUTexture *output_texture = context().get_output_texture();
const int image_unit = GPU_shader_get_texture_binding(shader, "output_img");
GPU_texture_image_bind(output_texture, image_unit);
compute_dispatch_threads_at_least(shader, compute_domain().size);
image.unbind_as_texture();
alpha.unbind_as_texture();
GPU_texture_image_unbind(output_texture);
GPU_shader_unbind();
}
/* If true, the alpha channel of the image is set to 1, that is, it becomes opaque. If false, the
* alpha channel of the image is retained, but only if the alpha input is not linked. If the
* alpha input is linked, it the value of that input will be used as the alpha of the image. */
bool ignore_alpha()
{
return bnode().custom2 & CMP_NODE_OUTPUT_IGNORE_ALPHA;
}
/* The operation domain have the same dimensions of the output without any transformations. */
Domain compute_domain() override
{
return Domain(context().get_output_size());
}
};
static NodeOperation *get_compositor_operation(Context &context, DNode node)
{
return new CompositeOperation(context, node);
}
} // namespace blender::nodes::node_composite_composite_cc
void register_node_type_cmp_composite()
@ -37,6 +165,7 @@ void register_node_type_cmp_composite()
cmp_node_type_base(&ntype, CMP_NODE_COMPOSITE, "Composite", NODE_CLASS_OUTPUT);
ntype.declare = file_ns::cmp_node_composite_declare;
ntype.draw_buttons = file_ns::node_composit_buts_composite;
ntype.get_compositor_operation = file_ns::get_compositor_operation;
ntype.flag |= NODE_PREVIEW;
ntype.no_muting = true;

View File

@ -11,6 +11,12 @@
#include "UI_interface.h"
#include "UI_resources.h"
#include "GPU_shader.h"
#include "GPU_texture.h"
#include "COM_node_operation.hh"
#include "COM_utilities.hh"
#include "node_composite_util.hh"
/* **************** SPLIT VIEWER ******************** */
@ -43,6 +49,70 @@ static void node_composit_buts_splitviewer(uiLayout *layout, bContext *UNUSED(C)
uiItemR(col, ptr, "factor", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE);
}
using namespace blender::realtime_compositor;
class ViewerOperation : public NodeOperation {
public:
using NodeOperation::NodeOperation;
void execute() override
{
GPUShader *shader = get_split_viewer_shader();
GPU_shader_bind(shader);
const int2 size = compute_domain().size;
GPU_shader_uniform_1f(shader, "split_ratio", get_split_ratio());
GPU_shader_uniform_2iv(shader, "view_size", size);
const Result &first_image = get_input("Image");
first_image.bind_as_texture(shader, "first_image_tx");
const Result &second_image = get_input("Image_001");
second_image.bind_as_texture(shader, "second_image_tx");
GPUTexture *output_texture = context().get_output_texture();
const int image_unit = GPU_shader_get_texture_binding(shader, "output_img");
GPU_texture_image_bind(output_texture, image_unit);
compute_dispatch_threads_at_least(shader, size);
first_image.unbind_as_texture();
second_image.unbind_as_texture();
GPU_texture_image_unbind(output_texture);
GPU_shader_unbind();
}
/* The operation domain have the same dimensions of the output without any transformations. */
Domain compute_domain() override
{
return Domain(context().get_output_size());
}
GPUShader *get_split_viewer_shader()
{
if (get_split_axis() == CMP_NODE_SPLIT_VIEWER_HORIZONTAL) {
return shader_manager().get("compositor_split_viewer_horizontal");
}
return shader_manager().get("compositor_split_viewer_vertical");
}
CMPNodeSplitViewerAxis get_split_axis()
{
return (CMPNodeSplitViewerAxis)bnode().custom2;
}
float get_split_ratio()
{
return bnode().custom1 / 100.0f;
}
};
static NodeOperation *get_compositor_operation(Context &context, DNode node)
{
return new ViewerOperation(context, node);
}
} // namespace blender::nodes::node_composite_split_viewer_cc
void register_node_type_cmp_splitviewer()
@ -57,6 +127,7 @@ void register_node_type_cmp_splitviewer()
ntype.flag |= NODE_PREVIEW;
node_type_init(&ntype, file_ns::node_composit_init_splitviewer);
node_type_storage(&ntype, "ImageUser", node_free_standard_storage, node_copy_standard_storage);
ntype.get_compositor_operation = file_ns::get_compositor_operation;
ntype.no_muting = true;

View File

@ -5,6 +5,8 @@
* \ingroup cmpnodes
*/
#include "BLI_math_vec_types.hh"
#include "BKE_global.h"
#include "BKE_image.h"
@ -13,6 +15,13 @@
#include "UI_interface.h"
#include "UI_resources.h"
#include "GPU_shader.h"
#include "GPU_state.h"
#include "GPU_texture.h"
#include "COM_node_operation.hh"
#include "COM_utilities.hh"
#include "node_composite_util.hh"
/* **************** VIEWER ******************** */
@ -55,6 +64,125 @@ static void node_composit_buts_viewer_ex(uiLayout *layout, bContext *UNUSED(C),
}
}
using namespace blender::realtime_compositor;
class ViewerOperation : public NodeOperation {
public:
using NodeOperation::NodeOperation;
void execute() override
{
const Result &image = get_input("Image");
const Result &alpha = get_input("Alpha");
if (image.is_single_value() && alpha.is_single_value()) {
execute_clear();
}
else if (ignore_alpha()) {
execute_ignore_alpha();
}
else if (!node().input_by_identifier("Alpha")->is_logically_linked()) {
execute_copy();
}
else {
execute_set_alpha();
}
}
/* Executes when all inputs are single values, in which case, the output texture can just be
* cleared to the appropriate color. */
void execute_clear()
{
const Result &image = get_input("Image");
const Result &alpha = get_input("Alpha");
float4 color = image.get_color_value();
if (ignore_alpha()) {
color.w = 1.0f;
}
else if (node().input_by_identifier("Alpha")->is_logically_linked()) {
color.w = alpha.get_float_value();
}
GPU_texture_clear(context().get_output_texture(), GPU_DATA_FLOAT, color);
}
/* Executes when the alpha channel of the image is ignored. */
void execute_ignore_alpha()
{
GPUShader *shader = shader_manager().get("compositor_convert_color_to_opaque");
GPU_shader_bind(shader);
const Result &image = get_input("Image");
image.bind_as_texture(shader, "input_tx");
GPUTexture *output_texture = context().get_output_texture();
const int image_unit = GPU_shader_get_texture_binding(shader, "output_img");
GPU_texture_image_bind(output_texture, image_unit);
compute_dispatch_threads_at_least(shader, compute_domain().size);
image.unbind_as_texture();
GPU_texture_image_unbind(output_texture);
GPU_shader_unbind();
}
/* Executes when the image texture is written with no adjustments and can thus be copied directly
* to the output texture. */
void execute_copy()
{
const Result &image = get_input("Image");
/* Make sure any prior writes to the texture are reflected before copying it. */
GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE);
GPU_texture_copy(context().get_output_texture(), image.texture());
}
/* Executes when the alpha channel of the image is set as the value of the input alpha. */
void execute_set_alpha()
{
GPUShader *shader = shader_manager().get("compositor_set_alpha");
GPU_shader_bind(shader);
const Result &image = get_input("Image");
image.bind_as_texture(shader, "image_tx");
const Result &alpha = get_input("Alpha");
alpha.bind_as_texture(shader, "alpha_tx");
GPUTexture *output_texture = context().get_output_texture();
const int image_unit = GPU_shader_get_texture_binding(shader, "output_img");
GPU_texture_image_bind(output_texture, image_unit);
compute_dispatch_threads_at_least(shader, compute_domain().size);
image.unbind_as_texture();
alpha.unbind_as_texture();
GPU_texture_image_unbind(output_texture);
GPU_shader_unbind();
}
/* If true, the alpha channel of the image is set to 1, that is, it becomes opaque. If false, the
* alpha channel of the image is retained, but only if the alpha input is not linked. If the
* alpha input is linked, it the value of that input will be used as the alpha of the image. */
bool ignore_alpha()
{
return bnode().custom2 & CMP_NODE_OUTPUT_IGNORE_ALPHA;
}
/* The operation domain have the same dimensions of the output without any transformations. */
Domain compute_domain() override
{
return Domain(context().get_output_size());
}
};
static NodeOperation *get_compositor_operation(Context &context, DNode node)
{
return new ViewerOperation(context, node);
}
} // namespace blender::nodes::node_composite_viewer_cc
void register_node_type_cmp_viewer()
@ -70,6 +198,7 @@ void register_node_type_cmp_viewer()
ntype.flag |= NODE_PREVIEW;
node_type_init(&ntype, file_ns::node_composit_init_viewer);
node_type_storage(&ntype, "ImageUser", node_free_standard_storage, node_copy_standard_storage);
ntype.get_compositor_operation = file_ns::get_compositor_operation;
ntype.no_muting = true;