1
1

Compare commits

...

28 Commits

Author SHA1 Message Date
3de2575ffb Realtime Compositor: Update task link 2022-06-28 13:06:37 +02:00
2356c3f4cd Realtime Compositor: Use more consistent names 2022-06-17 10:34:57 +02:00
78cf05c7e8 Realtime Compositor: Add Separate Combine color node 2022-06-17 10:31:47 +02:00
82bb7ee943 Realtime Compositor: Cleanup split viewer node 2022-06-16 16:01:58 +02:00
443e24204e Realtime Compositor: Support multi-view images 2022-06-16 16:01:31 +02:00
9fa78b3e8b Realtime Compositor: Correct multiply by alpha 2022-06-16 11:52:18 +02:00
082c87bf96 Merge branch 'master' into temp-viewport-compositor-merge 2022-06-15 14:35:44 +02:00
034cea1547 Realtime Compositor: Add as an experimental option 2022-06-15 11:50:41 +02:00
580675b0b4 Realtime Compositor: Validate node tree
Check if the node tree is valid and display an error message if it
isn't. This also removes unsupported nodes and simply displays an error
if the node tree contains one of them.
2022-06-15 10:48:48 +02:00
2bb58991ad Realtime Compositor: Cleanup comments complete renames 2022-06-15 08:13:58 +02:00
08a1425947 Realtime Compositor: Use more explicit member name 2022-06-14 10:23:12 +02:00
f1de6d9348 Realtime Compositor: Cleanup UI names 2022-06-14 10:22:03 +02:00
59f5d10d73 Merge branch 'master' into temp-viewport-compositor-merge 2022-06-14 09:01:36 +02:00
67650fb999 Realtime Compositor: Rename processor operation 2022-05-31 17:26:19 +02:00
dbb1afffc8 Realtime Compositor: Rename GPU material related code 2022-05-31 16:51:16 +02:00
af0d66c164 Realtime Compositor: Rename and move to compositor module
This patch renames the viewport compositor to realtime compositor, moves
it under the compositor module, and uses the COM prefix instead of the
VPC prefix.
2022-05-31 09:39:01 +02:00
c469934ba9 Merge branch 'master' into temp-viewport-compositor-merge 2022-05-30 15:08:23 +02:00
da2fb695b3 Viewport Compositor: Add box mask node 2022-05-20 16:42:55 +02:00
5877e33f23 Viewport Compositor: Add ellipse mask node 2022-05-20 16:27:46 +02:00
30132dec01 Merge branch 'master' into temp-viewport-compositor-merge 2022-05-20 13:11:03 +02:00
48006f8b5f Viewport Compositor: Avoid using mat3 uniforms
Mat3 uniforms suffer from alignment issues that are not easy to fix, so
just use mat4 uniforms for such matrices.
2022-05-20 13:00:43 +02:00
da8844d73e Viewport Compositor: Fix compiler warnings 2022-05-16 15:46:35 +02:00
c0c31994ab Viewport Compositor: Fix output storer on Nvidia
The GLSL specification is not clear about passing images to functions and consequently functions
with image parameters are not portable across driver implementations or even non-functioning in
some drivers. See https://github.com/KhronosGroup/GLSL/issues/57.

To work around this, we use macros instead of functions. However, to make those macros usable in
the GPU material library, we also define function counterparts that are guarded with #if 0 such
that they are not used in the shader but are parsed by the GPU shader dependency parser.
2022-05-16 15:44:20 +02:00
1328d9a575 Merge branch 'master' into temp-viewport-compositor-merge 2022-05-12 15:08:18 +02:00
2e8e7bd7b9 Viewport Compositor: Only update engine after init
The update callback might get called after the engine is initialized, so
make sure it is first.
2022-05-12 14:45:15 +02:00
562fd83b91 Merge branch 'master' into temp-viewport-compositor-merge 2022-05-06 15:22:14 +02:00
e9ce424492 Viewport Compositor: Fix and refactor reference counting
After using eager evaluation and retaining results, reference counting
no longer worked. This patch refactors the reference counting system to
make it work again.
2022-05-06 10:12:41 +02:00
e21f04070e Viewport Compositor: Rebase on master 2022-05-05 12:29:46 +02:00
177 changed files with 11168 additions and 200 deletions

View File

@@ -2260,6 +2260,7 @@ class USERPREF_PT_experimental_new_features(ExperimentalPanel, Panel):
({"property": "use_sculpt_tools_tilt"}, "T82877"),
({"property": "use_extended_asset_browser"}, ("project/view/130/", "Project Page")),
({"property": "use_override_templates"}, ("T73318", "Milestone 4")),
({"property": "use_realtime_compositor"}, "T99210"),
),
)

View File

@@ -6104,6 +6104,24 @@ class VIEW3D_PT_shading_render_pass(Panel):
layout.prop(shading, "render_pass", text="")
class VIEW3D_PT_shading_compositor(Panel):
bl_space_type = 'VIEW_3D'
bl_region_type = 'HEADER'
bl_label = "Realtime Compositor"
bl_parent_id = 'VIEW3D_PT_shading'
@classmethod
def poll(cls, context):
return (context.space_data.shading.type in ('MATERIAL', 'RENDERED') and
context.preferences.experimental.use_realtime_compositor)
def draw(self, context):
shading = context.space_data.shading
layout = self.layout
layout.prop(shading, "use_compositor")
class VIEW3D_PT_gizmo_display(Panel):
bl_space_type = 'VIEW_3D'
bl_region_type = 'HEADER'
@@ -7890,6 +7908,7 @@ classes = (
VIEW3D_PT_shading_options_shadow,
VIEW3D_PT_shading_options_ssao,
VIEW3D_PT_shading_render_pass,
VIEW3D_PT_shading_compositor,
VIEW3D_PT_gizmo_display,
VIEW3D_PT_overlay,
VIEW3D_PT_overlay_guides,

View File

@@ -101,6 +101,7 @@ typedef struct bNodeSocketTemplate {
namespace blender {
class CPPType;
namespace nodes {
class DNode;
class NodeMultiFunctionBuilder;
class GeoNodeExecParams;
class NodeDeclarationBuilder;
@@ -109,6 +110,11 @@ class GatherLinkSearchOpParams;
namespace fn {
class MFDataType;
} // namespace fn
namespace realtime_compositor {
class Context;
class NodeOperation;
class ShaderNode;
} // namespace realtime_compositor
} // namespace blender
using CPPTypeHandle = blender::CPPType;
@@ -123,7 +129,14 @@ using SocketGetGeometryNodesCPPValueFunction = void (*)(const struct bNodeSocket
using NodeGatherSocketLinkOperationsFunction =
void (*)(blender::nodes::GatherLinkSearchOpParams &params);
using NodeGetCompositorOperationFunction = blender::realtime_compositor::NodeOperation
*(*)(blender::realtime_compositor::Context &context, blender::nodes::DNode node);
using NodeGetCompositorShaderNodeFunction =
blender::realtime_compositor::ShaderNode *(*)(blender::nodes::DNode node);
#else
typedef void *NodeGetCompositorOperationFunction;
typedef void *NodeGetCompositorShaderNodeFunction;
typedef void *NodeMultiFunctionBuildFunction;
typedef void *NodeGeometryExecFunction;
typedef void *NodeDeclareFunction;
@@ -309,6 +322,14 @@ typedef struct bNodeType {
/* gpu */
NodeGPUExecFunction gpu_fn;
/* Get an instance of this node's compositor operation. Freeing the instance is the
* responsibility of the caller. */
NodeGetCompositorOperationFunction get_compositor_operation;
/* Get an instance of this node's compositor shader node. Freeing the instance is the
* responsibility of the caller. */
NodeGetCompositorShaderNodeFunction get_compositor_shader_node;
/* Build a multi-function for this node. */
NodeMultiFunctionBuildFunction build_multi_function;

View File

@@ -354,6 +354,7 @@ static GPUTexture *image_get_gpu_texture(Image *ima,
ima->gpu_pass = requested_pass;
ima->gpu_layer = requested_layer;
ima->gpu_view = requested_view;
BKE_image_partial_update_mark_full_update(ima);
}
#undef GPU_FLAGS_TO_CHECK

View File

@@ -1,6 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright 2011 Blender Foundation. All rights reserved.
add_subdirectory(realtime_compositor)
set(INC
.
intern

View File

@@ -0,0 +1,66 @@
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC
.
../../gpu
../../nodes
../../imbuf
../../blenlib
../../makesdna
../../makesrna
../../blenkernel
../../gpu/intern
../../../../intern/guardedalloc
)
set(SRC
intern/compile_state.cc
intern/context.cc
intern/conversion_operation.cc
intern/domain.cc
intern/evaluator.cc
intern/input_single_value_operation.cc
intern/node_operation.cc
intern/operation.cc
intern/realize_on_domain_operation.cc
intern/reduce_to_single_value_operation.cc
intern/result.cc
intern/scheduler.cc
intern/shader_node.cc
intern/shader_operation.cc
intern/shader_pool.cc
intern/simple_operation.cc
intern/texture_pool.cc
intern/utilities.cc
COM_compile_state.hh
COM_context.hh
COM_conversion_operation.hh
COM_domain.hh
COM_evaluator.hh
COM_input_descriptor.hh
COM_input_single_value_operation.hh
COM_node_operation.hh
COM_operation.hh
COM_realize_on_domain_operation.hh
COM_reduce_to_single_value_operation.hh
COM_result.hh
COM_scheduler.hh
COM_shader_node.hh
COM_shader_operation.hh
COM_shader_pool.hh
COM_simple_operation.hh
COM_texture_pool.hh
COM_utilities.hh
)
set(LIB
bf_gpu
bf_nodes
bf_imbuf
bf_blenlib
bf_blenkernel
)
blender_add_lib(bf_realtime_compositor "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")

View File

@@ -0,0 +1,169 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_map.hh"
#include "NOD_derived_node_tree.hh"
#include "COM_domain.hh"
#include "COM_node_operation.hh"
#include "COM_scheduler.hh"
#include "COM_shader_operation.hh"
namespace blender::realtime_compositor {
using namespace nodes::derived_node_tree_types;
/* ------------------------------------------------------------------------------------------------
* Compile State
*
* The compile state a utility class used to track the state of compilation when compiling the node
* tree. In particular, it tracks two important pieces of information, each of which is described
* in one of the following sections.
*
* First, it stores a mapping between all nodes and the operations they were compiled into. The
* mapping are stored independently depending on the type of the operation in the node_operations_
* and shader_operations_ maps. So those two maps are mutually exclusive. The compiler should call
* the map_node_to_node_operation and map_node_to_shader_operation methods to populate those maps
* as soon as it compiles a node into an operation. Those maps are used to retrieve the results of
* outputs linked to the inputs of operations. See the get_result_from_output_socket method for
* more details. For the node tree shown below, nodes 1, 2, and 6 are mapped to their compiled
* operations in the node_operation_ map. While nodes 3 and 4 are both mapped to the first shader
* operation, and node 5 is mapped to the second shader operation in the shader_operations_ map.
*
* Shader Operation 1 Shader Operation 2
* +-----------------------------------+ +------------------+
* .------------. | .------------. .------------. | | .------------. | .------------.
* | Node 1 | | | Node 3 | | Node 4 | | | | Node 5 | | | Node 6 |
* | |----|--| |--| |---|-----|--| |--|--| |
* | | .-|--| | | | | .--|--| | | | |
* '------------' | | '------------' '------------' | | | '------------' | '------------'
* | +-----------------------------------+ | +------------------+
* .------------. | |
* | Node 2 | | |
* | |--'----------------------------------------'
* | |
* '------------'
*
* Second, it stores the shader compile unit as well as its domain. One should first go over the
* discussion in COM_evaluator.hh for a high level description of the mechanism of the compile
* unit. The one important detail in this class is the should_compile_shader_compile_unit method,
* which implements the criteria of whether the compile unit should be compiled given the node
* currently being processed as an argument. Those criteria are described as follows. If the
* compile unit is empty as is the case when processing nodes 1, 2, and 3, then it plainly
* shouldn't be compiled. If the given node is not a shader node, then it can't be added to the
* compile unit and the unit is considered complete and should be compiled, as is the case when
* processing node 6. If the computed domain of the given node is not compatible with the domain of
* the compiled unit, then it can't be added to the unit and the unit is considered complete and
* should be compiled, as is the case when processing node 5, more on this in the next section.
* Otherwise, the given node is compatible with the compile unit and can be added to it, so the
* unit shouldn't be compiled just yet, as is the case when processing node 4.
*
* Special attention should be given to the aforementioned domain compatibility criterion. One
* should first go over the discussion in COM_domain.hh for more information on domains. When a
* compile unit gets eventually compiled to a shader operation, that operation will have a certain
* operation domain, and any node that gets added to the compile unit should itself have a computed
* node domain that is compatible with that operation domain, otherwise, had the node been compiled
* into its own operation separately, the result would have been be different. For instance,
* consider the above node tree where node 1 outputs a 100x100 result, node 2 outputs a 50x50
* result, the first input in node 3 has the highest domain priority, and the second input in node
* 5 has the highest domain priority. In this case, shader operation 1 will output a 100x100
* result, and shader operation 2 will output a 50x50 result, because that's the computed operation
* domain for each of them. So node 6 will get a 50x50 result. Now consider the same node tree, but
* where all three nodes 3, 4, and 5 were compiled into a single shader operation as shown the node
* tree below. In that case, shader operation 1 will output a 100x100 result, because that's its
* computed operation domain. So node 6 will get a 100x100 result. As can be seen, the final result
* is different even though the node tree is the same. That's why the compiler can decide to
* compile the compile unit early even though further nodes can still be technically added to it.
*
* Shader Operation 1
* +------------------------------------------------------+
* .------------. | .------------. .------------. .------------. | .------------.
* | Node 1 | | | Node 3 | | Node 4 | | Node 5 | | | Node 6 |
* | |----|--| |--| |------| |--|--| |
* | | .-|--| | | | .---| | | | |
* '------------' | | '------------' '------------' | '------------' | '------------'
* | +----------------------------------|-------------------+
* .------------. | |
* | Node 2 | | |
* | |--'------------------------------------'
* | |
* '------------'
*
* To check for the domain compatibility between the compile unit and the node being processed,
* the domain of the compile unit is assumed to be the domain of the first node whose computed
* domain is not an identity domain. Identity domains corresponds to single value results, so those
* are always compatible with any domain. The domain of the compile unit is computed and set in
* the add_node_to_shader_compile_unit method. When processing a node, the computed domain of node
* is compared to the compile unit domain in the should_compile_shader_compile_unit method, noting
* that identity domains are always compatible. Node domains are computed in the
* compute_shader_node_domain method, which is analogous to Operation::compute_domain for nodes
* that are not yet compiled. */
class CompileState {
private:
/* A reference to the node execution schedule that is being compiled. */
const Schedule &schedule_;
/* Those two maps associate each node with the operation it was compiled into. Each node is
* either compiled into a node operation and added to node_operations, or compiled into a shader
* operation and added to shader_operations. Those maps are used to retrieve the results of
* outputs linked to the inputs of operations. See the get_result_from_output_socket method for
* more information. */
Map<DNode, NodeOperation *> node_operations_;
Map<DNode, ShaderOperation *> shader_operations_;
/* A contiguous subset of the node execution schedule that contains the group of nodes that will
* be compiled together into a Shader Operation. See the discussion in COM_evaluator.hh for
* more information. */
ShaderCompileUnit shader_compile_unit_;
/* The domain of the shader compile unit. */
Domain shader_compile_unit_domain_ = Domain::identity();
public:
/* Construct a compile state from the node execution schedule being compiled. */
CompileState(const Schedule &schedule);
/* Get a reference to the node execution schedule being compiled. */
const Schedule &get_schedule();
/* Add an association between the given node and the give node operation that the node was
* compiled into in the node_operations_ map. */
void map_node_to_node_operation(DNode node, NodeOperation *operation);
/* Add an association between the given node and the give shader operation that the node was
* compiled into in the shader_operations_ map. */
void map_node_to_shader_operation(DNode node, ShaderOperation *operation);
/* Returns a reference to the result of the operation corresponding to the given output that the
* given output's node was compiled to. */
Result &get_result_from_output_socket(DOutputSocket output);
/* Add the given node to the compile unit. And if the domain of the compile unit is not yet
* determined or was determined to be an identity domain, update it to the computed domain for
* the give node. */
void add_node_to_shader_compile_unit(DNode node);
/* Get a reference to the shader compile unit. */
ShaderCompileUnit &get_shader_compile_unit();
/* Clear the compile unit. This should be called once the compile unit is compiled to ready it to
* track the next potential compile unit. */
void reset_shader_compile_unit();
/* Determines if the compile unit should be compiled based on a number of criteria give the node
* currently being processed. Those criteria are as follows:
* - If compile unit is empty, then it can't and shouldn't be compiled.
* - If the given node is not a shader node, then it can't be added to the compile unit
* and the unit is considered complete and should be compiled.
* - If the computed domain of the given node is not compatible with the domain of the compile
* unit, then it can't be added to it and the unit is considered complete and should be
* compiled. */
bool should_compile_shader_compile_unit(DNode node);
private:
/* Compute the node domain of the given shader node. This is analogous to the
* Operation::compute_domain method, except it is computed from the node itself as opposed to a
* compiled operation. See the discussion in COM_domain.hh for more information. */
Domain compute_shader_node_domain(DNode node);
};
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,71 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_math_vec_types.hh"
#include "BLI_string_ref.hh"
#include "DNA_scene_types.h"
#include "GPU_texture.h"
#include "COM_shader_pool.hh"
#include "COM_texture_pool.hh"
namespace blender::realtime_compositor {
/* ------------------------------------------------------------------------------------------------
* Context
*
* A Context is an abstract class that is implemented by the caller of the evaluator to provide the
* necessary data and functionalities for the correct operation of the evaluator. This includes
* providing input data like render passes and the active scene, as well as references to the data
* where the output of the evaluator will be written. The class also provides a reference to the
* texture pool which should be implemented by the caller and provided during construction.
* Finally, the class have an instance of a shader pool for convenient shader acquisition. */
class Context {
private:
/* A texture pool that can be used to allocate textures for the compositor efficiently. */
TexturePool &texture_pool_;
/* A shader pool that can be used to create shaders for the compositor efficiently. */
ShaderPool shader_pool_;
public:
Context(TexturePool &texture_pool);
/* Get the active compositing scene. */
virtual const Scene *get_scene() const = 0;
/* Get the dimensions of the viewport. */
virtual int2 get_viewport_size() = 0;
/* Get the texture representing the viewport where the result of the compositor should be
* written. This should be called by output nodes to get their target texture. */
virtual GPUTexture *get_viewport_texture() = 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_pass_texture(int view_layer, eScenePassType pass_type) = 0;
/* Get the name of the view currently being rendered. */
virtual StringRef get_view_name() = 0;
/* Set an info message. This is called by the compositor evaluator to inform or warn the user
* about something, typically an error. The implementation should display the message in an
* appropriate place, which can be directly in the UI or just logged to the output stream. */
virtual void set_info_message(StringRef message) const = 0;
/* Get the current frame number of the active scene. */
int get_frame_number() const;
/* Get the current time in seconds of the active scene. */
float get_time() const;
/* Get a reference to the texture pool of this context. */
TexturePool &texture_pool();
/* Get a reference to the shader pool of this context. */
ShaderPool &shader_pool();
};
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,126 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "GPU_shader.h"
#include "COM_context.hh"
#include "COM_input_descriptor.hh"
#include "COM_result.hh"
#include "COM_simple_operation.hh"
namespace blender::realtime_compositor {
/* -------------------------------------------------------------------------------------------------
* Conversion Operation
*
* A simple operation that converts a result from a certain type to another. See the derived
* classes for more details. */
class ConversionOperation : public SimpleOperation {
public:
using SimpleOperation::SimpleOperation;
/* If the input result is a single value, execute_single is called. Otherwise, the shader
* provided by get_conversion_shader is dispatched. */
void execute() override;
/* Determine if a conversion operation is needed for the input with the given result and
* descriptor. If it is not needed, return a null pointer. If it is needed, return an instance of
* the appropriate conversion operation. */
static SimpleOperation *construct_if_needed(Context &context,
const Result &input_result,
const InputDescriptor &input_descriptor);
protected:
/* Convert the input single value result to the output single value result. */
virtual void execute_single(const Result &input, Result &output) = 0;
/* Get the shader the will be used for conversion. */
virtual GPUShader *get_conversion_shader() const = 0;
};
/* -------------------------------------------------------------------------------------------------
* Convert Float To Vector Operation
*
* Takes a float result and outputs a vector result. All three components of the output are filled
* with the input float. */
class ConvertFloatToVectorOperation : public ConversionOperation {
public:
ConvertFloatToVectorOperation(Context &context);
void execute_single(const Result &input, Result &output) override;
GPUShader *get_conversion_shader() const override;
};
/* -------------------------------------------------------------------------------------------------
* Convert Float To Color Operation
*
* Takes a float result and outputs a color result. All three color channels of the output are
* filled with the input float and the alpha channel is set to 1. */
class ConvertFloatToColorOperation : public ConversionOperation {
public:
ConvertFloatToColorOperation(Context &context);
void execute_single(const Result &input, Result &output) override;
GPUShader *get_conversion_shader() const override;
};
/* -------------------------------------------------------------------------------------------------
* Convert Color To Float Operation
*
* Takes a color result and outputs a float result. The output is the average of the three color
* channels, the alpha channel is ignored. */
class ConvertColorToFloatOperation : public ConversionOperation {
public:
ConvertColorToFloatOperation(Context &context);
void execute_single(const Result &input, Result &output) override;
GPUShader *get_conversion_shader() const override;
};
/* -------------------------------------------------------------------------------------------------
* Convert Color To Vector Operation
*
* Takes a color result and outputs a vector result. The output is a copy of the three color
* channels to the three vector components. */
class ConvertColorToVectorOperation : public ConversionOperation {
public:
ConvertColorToVectorOperation(Context &context);
void execute_single(const Result &input, Result &output) override;
GPUShader *get_conversion_shader() const override;
};
/* -------------------------------------------------------------------------------------------------
* Convert Vector To Float Operation
*
* Takes a vector result and outputs a float result. The output is the average of the three
* components. */
class ConvertVectorToFloatOperation : public ConversionOperation {
public:
ConvertVectorToFloatOperation(Context &context);
void execute_single(const Result &input, Result &output) override;
GPUShader *get_conversion_shader() const override;
};
/* -------------------------------------------------------------------------------------------------
* Convert Vector To Color Operation
*
* Takes a vector result and outputs a color result. The output is a copy of the three vector
* components to the three color channels with the alpha channel set to 1. */
class ConvertVectorToColorOperation : public ConversionOperation {
public:
ConvertVectorToColorOperation(Context &context);
void execute_single(const Result &input, Result &output) override;
GPUShader *get_conversion_shader() const override;
};
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,167 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <cstdint>
#include "BLI_float3x3.hh"
#include "BLI_math_vec_types.hh"
namespace blender::realtime_compositor {
/* Possible interpolations to use when realizing an input result of some domain on another domain.
* See the RealizationOptions class for more information. */
enum class Interpolation : uint8_t {
Nearest,
Bilinear,
Bicubic,
};
/* ------------------------------------------------------------------------------------------------
* Realization Options
*
* The options that describe how an input result prefer to be realized on some other domain. This
* is used by the Realize On Domain Operation to identify the appropriate method of realization.
* See the Domain class for more information. */
class RealizationOptions {
public:
/* The interpolation method that should be used when performing realization. Since realizing a
* result involves projecting it on a different domain, which in turn, involves sampling the
* result at arbitrary locations, the interpolation identifies the method used for computing the
* value at those arbitrary locations. */
Interpolation interpolation = Interpolation::Nearest;
/* If true, the result will be repeated infinitely along the horizontal axis when realizing the
* result. If false, regions outside of bounds of the result along the horizontal axis will be
* filled with zeros. */
bool repeat_x = false;
/* If true, the result will be repeated infinitely along the vertical axis when realizing the
* result. If false, regions outside of bounds of the result along the vertical axis will be
* filled with zeros. */
bool repeat_y = false;
};
/* ------------------------------------------------------------------------------------------------
* Domain
*
* The compositor is designed in such a way as to allow compositing in an infinite virtual
* compositing space. Consequently, any result of an operation is not only represented by its image
* output, but also by its transformation in that virtual space. The transformation of the result
* together with the dimension of its image is stored and represented by a Domain. In the figure
* below, two results of different domains are illustrated on the virtual compositing space. One of
* the results is centered in space with an image dimension of 800px × 600px, and the other result
* is scaled down and translated such that it lies in the upper right quadrant of the space with an
* image dimension of 800px × 400px. The position of the domain is in pixel space, and the domain
* is considered centered if it has an identity transformation. Note that both results have the
* same resolution, but occupy different areas of the virtual compositing space.
*
* y
* ^
* 800px x 600px |
* .---------------------|---------------------.
* | | 800px x 600px |
* | | .-------------. |
* | | | | |
* | | '-------------' |
* ------|---------------------|---------------------|------> x
* | | |
* | | |
* | | |
* | | |
* '---------------------|---------------------'
* |
*
* By default, results have domains of identity transformations, that is, they are centered in
* space, but a transformation operation like the rotate, translate, or transform operations will
* adjust the transformation to make the result reside somewhere different in space. The domain of
* a single value result is irrelevant and always set to an identity domain.
*
* An operation is typically only concerned about a subset of the virtual compositing space, this
* subset is represented by a domain which is called the Operation Domain. It follows that before
* the operation itself is executed, inputs will typically be realized on the operation domain to
* be in the same domain and have the same dimension as that of the operation domain. This process
* is called Domain Realization and is implemented using an operation called the Realize On Domain
* Operation. Realization involves projecting the result onto the target domain, copying the area
* of the result that intersects the target domain, and filling the rest with zeros or repetitions
* of the result depending on the realization options that can be set by the user. Consequently,
* operations can generally expect their inputs to have the same dimension and can operate on them
* directly and transparently. For instance, if an operation takes both results illustrated in
* the figure above, and the operation has an operation domain that matches the bigger domain, the
* result with the bigger domain will not need to be realized because it already has a domain that
* matches that of the operation domain, but the result with the smaller domain will have to be
* realized into a new result that has the same domain as the domain of the bigger result. Assuming
* no repetition, the output of the realization will be an all zeros image with dimension 800px ×
* 600px with a small scaled version of the smaller result copied into the upper right quadrant of
* the image. The following figure illustrates the realization process on a different set of
* results
*
* Realized Result
* +-------------+ +-------------+
* | Operation | | |
* | Domain | | Zeros |
* | | ----> | |
* +-----|-----+ | |-----+ |
* | | C | | | C | |
* | +-----|-------+ +-----'-------+
* | Domain Of |
* | Input |
* +-----------+
*
* An operation can operate in an arbitrary operation domain, but in most cases, the operation
* domain is inferred from the inputs of the operation. In particular, one of the inputs is said to
* be the Domain Input of the operation and the operation domain is inferred from its domain. It
* follows that this particular input will not need realization, because it already has the correct
* domain. The domain input selection mechanism is as follows. Each of the inputs are assigned a
* value by the developer called the Domain Priority, the domain input is then chosen as the
* non-single value input with the highest domain priority, zero being the highest priority. See
* Operation::compute_domain for more information.
*
* The aforementioned logic for operation domain computation is only a default that works for most
* cases, but an operation can override the compute_domain method to implement a different logic.
* For instance, output nodes have an operation domain the same size as the viewport and with an
* identity transformation, their operation domain doesn't depend on the inputs at all.
*
* For instance, a filter operation has two inputs, a factor and a color, the latter of which is
* assigned a domain priority of 0 and the former is assigned a domain priority of 1. If the color
* input is not a single value input, then the color input is considered to be the domain input of
* the operation and the operation domain is computed to be the same domain as the color input,
* because it has the highest priority. It follows that if the factor input has a different domain
* than the computed domain of the operation, it will be projected and realized on it to have the
* same domain as described above. On the other hand, if the color input is a single value input,
* then the factor input is considered to be the domain input and the operation domain will be the
* same as the domain of the factor input, because it has the second highest domain priority.
* Finally, if both inputs are single value inputs, the operation domain will be an identity domain
* and is irrelevant, because the output will be a domain-less single value. */
class Domain {
public:
/* The size of the domain in pixels. */
int2 size;
/* The 2D transformation of the domain defining its translation in pixels, rotation, and scale in
* the virtual compositing space. */
float3x3 transformation;
/* The options that describe how this domain prefer to be realized on some other domain. See the
* RealizationOptions class for more information. */
RealizationOptions realization_options;
public:
/* A size only constructor that sets the transformation to identity. */
Domain(int2 size);
Domain(int2 size, float3x3 transformation);
/* Transform the domain by the given transformation. This effectively pre-multiply the given
* transformation by the current transformation of the domain. */
void transform(const float3x3 &transformation);
/* Returns a domain of size 1x1 and an identity transformation. */
static Domain identity();
};
/* Compare the size and transformation of the domain. The realization_options are not compared
* because they only describe the method of realization on another domain, which is not technically
* a property of the domain itself. */
bool operator==(const Domain &a, const Domain &b);
/* Inverse of the above equality operator. */
bool operator!=(const Domain &a, const Domain &b);
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,173 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <memory>
#include "BLI_vector.hh"
#include "DNA_node_types.h"
#include "NOD_derived_node_tree.hh"
#include "COM_compile_state.hh"
#include "COM_context.hh"
#include "COM_node_operation.hh"
#include "COM_operation.hh"
#include "COM_shader_operation.hh"
namespace blender::realtime_compositor {
using namespace nodes::derived_node_tree_types;
/* ------------------------------------------------------------------------------------------------
* Evaluator
*
* The evaluator is the main class of the compositor and the entry point of its execution. The
* evaluator compiles the compositor node tree and evaluates it to compute its output. It is
* constructed from a compositor node tree and a compositor context. Upon calling the evaluate
* method, the evaluator will check if the node tree is already compiled into an operations stream,
* and if it is, it will go over it and evaluate the operations in order. It is then the
* responsibility of the caller to call the reset method when the node tree changes to invalidate
* the operations stream. A reset is also required if the resources used by the node tree change,
* for instances, when the dimensions of an image used by the node tree changes. This is necessary
* because the evaluator compiles the node tree into an operations stream that is specifically
* optimized for the structure of the resources used by the node tree.
*
* Otherwise, if the node tree is not yet compiled, the evaluator will compile it into an
* operations stream, evaluating the operations in the process. It should be noted that operations
* are evaluated as soon as they are compiled, as opposed to compiling the whole operations stream
* and then evaluating it in a separate step. This is important because, as mentioned before, the
* operations stream is optimized specifically for the structure of the resources used by the node
* tree, which is only known after the operations are evaluated. In other words, the evaluator uses
* the evaluated results of previously compiled operations to compile the operations that follow
* them in an optimized manner.
*
* Compilation starts by computing an optimized node execution schedule by calling the
* compute_schedule function, see the discussion in COM_scheduler.hh for more details. For the node
* tree shown below, the execution schedule is denoted by the node numbers. The compiler then goes
* over the execution schedule in order and compiles each node into either a Node Operation or a
* Shader Operation, depending on the node type, see the is_shader_node function. A Shader
* operation is constructed from a group of nodes forming a contiguous subset of the node execution
* schedule. For instance, in the node tree shown below, nodes 3 and 4 are compiled together into a
* shader operation and node 5 is compiled into its own shader operation, both of which are
* contiguous subsets of the node execution schedule. This process is described in details in the
* following section.
*
* Shader Operation 1 Shader Operation 2
* +-----------------------------------+ +------------------+
* .------------. | .------------. .------------. | | .------------. | .------------.
* | Node 1 | | | Node 3 | | Node 4 | | | | Node 5 | | | Node 6 |
* | |----|--| |--| |---|-----|--| |--|--| |
* | | .-|--| | | | | .--|--| | | | |
* '------------' | | '------------' '------------' | | | '------------' | '------------'
* | +-----------------------------------+ | +------------------+
* .------------. | |
* | Node 2 | | |
* | |--'----------------------------------------'
* | |
* '------------'
*
* For non shader nodes, the compilation process is straight forward, the compiler instantiates a
* node operation from the node, map its inputs to the results of the outputs they are linked to,
* and evaluates the operations. However, for shader nodes, since a group of nodes can be compiled
* together into a shader operation, the compilation process is a bit involved. The compiler uses
* an instance of the Compile State class to keep track of the compilation process. The compiler
* state stores the so called "shader compile unit", which is the current group of nodes that will
* eventually be compiled together into a shader operation. While going over the schedule, the
* compiler adds the shader nodes to the compile unit until it decides that the compile unit is
* complete and should be compiled. This is typically decided when the current node is not
* compatible with the compile unit and can't be added to it, only then it compiles the compile
* unit into a shader operation and resets it to ready it to track the next potential group of
* nodes that will form a shader operation. This decision is made based on various criteria in the
* should_compile_shader_compile_unit function. See the discussion in COM_compile_state.hh for more
* details of those criteria, but perhaps the most evident of which is whether the node is actually
* a shader node, if it isn't, then it evidently can't be added to the compile unit and the compile
* unit is should be compiled.
*
* For the node tree above, the compilation process is as follows. The compiler goes over the node
* execution schedule in order considering each node. Nodes 1 and 2 are not shader node so they are
* compiled into node operations and added to the operations stream. The current compile unit is
* empty, so it is not compiled. Node 3 is a shader node, and since the compile unit is currently
* empty, it is unconditionally added to it. Node 4 is a shader node, it was decided---for the sake
* of the demonstration---that it is compatible with the compile unit and can be added to it. Node
* 5 is a shader node, but it was decided---for the sake of the demonstration---that it is not
* compatible with the compile unit, so the compile unit is considered complete and is compiled
* first, adding the first shader operation to the operations stream and resetting the compile
* unit. Node 5 is then added to the now empty compile unit similar to node 3. Node 6 is not a
* shader node, so the compile unit is considered complete and is compiled first, adding the first
* shader operation to the operations stream and resetting the compile unit. Finally, node 6 is
* compiled into a node operation similar to nodes 1 and 2 and added to the operations stream. */
class Evaluator {
private:
/* A reference to the compositor context. */
Context &context_;
/* A reference to the compositor node tree. */
bNodeTree &node_tree_;
/* The derived and reference node trees representing the compositor node tree. Those are
* initialized when the node tree is compiled and freed when the evaluator resets. */
NodeTreeRefMap node_tree_reference_map_;
std::unique_ptr<DerivedNodeTree> derived_node_tree_;
/* The compiled operations stream. This contains ordered pointers to the operations that were
* compiled. This is initialized when the node tree is compiled and freed when the evaluator
* resets. The is_compiled_ member indicates whether the operation stream can be used or needs to
* be compiled first. Note that the operations stream can be empty even when compiled, this can
* happen when the node tree is empty or invalid for instance. */
Vector<std::unique_ptr<Operation>> operations_stream_;
/* True if the node tree is already compiled into an operations stream that can be evaluated
* directly. False if the node tree is not compiled yet and needs to be compiled. */
bool is_compiled_ = false;
public:
/* Construct an evaluator from a compositor node tree and a context. */
Evaluator(Context &context, bNodeTree &node_tree);
/* Evaluate the compositor node tree. If the node tree is already compiled into an operations
* stream, that stream will be evaluated directly. Otherwise, the node tree will be compiled and
* evaluated. */
void evaluate();
/* Invalidate the operations stream that was compiled for the node tree. This should be called
* when the node tree changes or the structure of any of the resources used by it changes. By
* structure, we mean things like the dimensions of the used images, while changes to their
* contents do not necessitate a reset. */
void reset();
private:
/* Check if the compositor node tree is valid by checking if it has:
* - Cyclic links.
* - Undefined nodes or sockets.
* - Unsupported nodes.
* If the node tree is valid, true is returned. Otherwise, false is returned, and an appropriate
* error message is set by calling the context's set_info_message method. */
bool validate_node_tree();
/* Compile the node tree into an operations stream and evaluate it. */
void compile_and_evaluate();
/* Compile the given node into a node operation, map each input to the result of the output
* linked to it, update the compile state, add the newly created operation to the operations
* stream, and evaluate the operation. */
void compile_and_evaluate_node(DNode node, CompileState &compile_state);
/* Map each input of the node operation to the result of the output linked to it. Unlinked inputs
* are mapped to the result of a newly created Input Single Value Operation, which is added to
* the operations stream and evaluated. Since this method might add operations to the operations
* stream, the actual node operation should only be added to the stream once this method is
* called. */
void map_node_operation_inputs_to_their_results(DNode node,
NodeOperation *operation,
CompileState &compile_state);
/* Compile the shader compile unit into a shader operation, map each input of the operation to
* the result of the output linked to it, update the compile state, add the newly created
* operation to the operations stream, evaluate the operation, and finally reset the shader
* compile unit. */
void compile_and_evaluate_shader_compile_unit(CompileState &compile_state);
/* Map each input of the shader operation to the result of the output linked to it. */
void map_shader_operation_inputs_to_their_results(ShaderOperation *operation,
CompileState &compile_state);
};
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,34 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "COM_result.hh"
namespace blender::realtime_compositor {
/* ------------------------------------------------------------------------------------------------
* Input Descriptor
*
* A class that describes an input of an operation. */
class InputDescriptor {
public:
/* The type of input. This may be different that the type of result that the operation will
* receive for the input, in which case, an implicit conversion operation will be added as an
* input processor to convert it to the required type. */
ResultType type;
/* If true, then the input does not need to be realized on the domain of the operation before its
* execution. See the discussion in COM_domain.hh for more information. */
bool skip_realization = false;
/* The priority of the input for determining the operation domain. The non-single value input
* with the highest priority will be used to infer the operation domain, the highest priority
* being zero. See the discussion in COM_domain.hh for more information. */
int domain_priority = 0;
/* If true, the input expects a single value, and if a non-single value is provided, a default
* single value will be used instead, see the get_<type>_value_default methods in the Result
* class. It follows that this also implies skip_realization, because we don't need to realize a
* result that will be discarded anyways. If false, the input can work with both single and
* non-single values. */
bool expects_single_value = false;
};
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,46 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_string_ref.hh"
#include "NOD_derived_node_tree.hh"
#include "COM_context.hh"
#include "COM_operation.hh"
#include "COM_result.hh"
namespace blender::realtime_compositor {
using namespace nodes::derived_node_tree_types;
/* ------------------------------------------------------------------------------------------------
* Input Single Value Operation
*
* An input single value operation is an operation that outputs a single value result whose value
* is the value of an unlinked input socket. This is typically used to initialize the values of
* unlinked node input sockets. */
class InputSingleValueOperation : public Operation {
private:
/* The identifier of the output. */
static const StringRef output_identifier_;
/* The input socket whose value will be computed as the operation's result. */
DInputSocket input_socket_;
public:
InputSingleValueOperation(Context &context, DInputSocket input_socket);
/* Allocate a single value result and set its value to the default value of the input socket. */
void execute() override;
/* Get a reference to the output result of the operation, this essentially calls the super
* get_result with the output identifier of the operation. */
Result &get_result();
private:
/* Populate the result of the operation, this essentially calls the super populate_result method
* with the output identifier of the operation. */
void populate_result(Result result);
};
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,56 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_string_ref.hh"
#include "DNA_node_types.h"
#include "NOD_derived_node_tree.hh"
#include "COM_context.hh"
#include "COM_operation.hh"
#include "COM_scheduler.hh"
namespace blender::realtime_compositor {
using namespace nodes::derived_node_tree_types;
/* ------------------------------------------------------------------------------------------------
* Node Operation
*
* A node operation is a subclass of operation that nodes should implement and instantiate in the
* get_compositor_operation function of bNodeType, passing the inputs given to that function to the
* constructor. This class essentially just implements a default constructor that populates output
* results for all outputs of the node as well as input descriptors for all inputs of the nodes
* based on their socket declaration. The class also provides some utility methods for easier
* implementation of nodes. */
class NodeOperation : public Operation {
private:
/* The node that this operation represents. */
DNode node_;
public:
/* Populate the output results based on the node outputs and populate the input descriptors based
* on the node inputs. */
NodeOperation(Context &context, DNode node);
/* Compute and set the initial reference counts of all the results of the operation. The
* reference counts of the results are the number of operations that use those results, which is
* computed as the number of inputs whose node is part of the schedule and is linked to the
* output corresponding to each result. The node execution schedule is given as an input. */
void compute_results_reference_counts(const Schedule &schedule);
protected:
/* Returns a reference to the derived node that this operation represents. */
const DNode &node() const;
/* Returns a reference to the node that this operation represents. */
const bNode &bnode() const;
/* Returns true if the output identified by the given identifier is needed and should be
* computed, otherwise returns false. */
bool should_compute_output(StringRef identifier);
};
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,175 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <memory>
#include "BLI_map.hh"
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
#include "COM_context.hh"
#include "COM_domain.hh"
#include "COM_input_descriptor.hh"
#include "COM_result.hh"
#include "COM_shader_pool.hh"
#include "COM_texture_pool.hh"
namespace blender::realtime_compositor {
/* Forward declare simple operation because it is used in the operation definition. */
class SimpleOperation;
/* A type representing a vector of simple operations that store the input processors for a
* particular input. */
using ProcessorsVector = Vector<std::unique_ptr<SimpleOperation>>;
/* ------------------------------------------------------------------------------------------------
* Operation
*
* The operation is the basic unit of the compositor. The evaluator compiles the compositor node
* tree into an ordered stream of operations which are then executed in order during evaluation.
* The operation class can be sub-classed to implement a new operation. Operations have a number of
* inputs and outputs that are declared during construction and are identified by string
* identifiers. Inputs are declared by calling declare_input_descriptor providing an appropriate
* descriptor. Those inputs are mapped to the results computed by other operations whose outputs
* are linked to the inputs. Such mappings are established by the compiler during compilation by
* calling the map_input_to_result method. Outputs are populated by calling the populate_result
* method, providing a result of an appropriate type. Upon execution, the operation allocates a
* result for each of its outputs and computes their value based on its inputs and options.
*
* Each input may have one or more input processors, which are simple operations that process the
* inputs before the operation is executed, see the discussion in COM_simple_operation.hh for more
* information. And thus the effective input of the operation is the result of the last input
* processor if one exists. Input processors are added and evaluated by calling the
* add_and_evaluate_input_processors method, which provides a default implementation that does
* things like implicit conversion, domain realization, and more. This default implementation can,
* however, be overridden, extended, or removed. Once the input processors are added and evaluated
* for the first time, they are stored in the operation and future evaluations can evaluate them
* directly without having to add them again.
*
* The operation is evaluated by calling the evaluate method, which first adds the input processors
* if they weren't added already and evaluates them, then it resets the results of the operation,
* then it calls the execute method of the operation, and finally it releases the results mapped to
* the inputs to declare that they are no longer needed. */
class Operation {
private:
/* A reference to the compositor context. This member references the same object in all
* operations but is included in the class for convenience. */
Context &context_;
/* A mapping between each output of the operation identified by its identifier and the result for
* that output. A result for each output of the operation should be constructed and added to the
* map during operation construction by calling the populate_result method. The results should be
* allocated and their contents should be computed in the execute method. */
Map<StringRef, Result> results_;
/* A mapping between each input of the operation identified by its identifier and its input
* descriptor. Those descriptors should be declared during operation construction by calling the
* declare_input_descriptor method. */
Map<StringRef, InputDescriptor> input_descriptors_;
/* A mapping between each input of the operation identified by its identifier and a pointer to
* the computed result providing its data. The mapped result is either one that was computed by
* another operation or one that was internally computed in the operation by the last input
* processor for that input. It is the responsibility of the evaluator to map the inputs to their
* linked results before evaluating the operation by calling the map_input_to_result method. */
Map<StringRef, Result *> results_mapped_to_inputs_;
/* A mapping between each input of the operation identified by its identifier and an ordered list
* of simple operations to process that input. This is initialized the first time the input
* processors are evaluated by calling the add_and_evaluate_input_processors method. Further
* evaluations will evaluate the processors directly without the need to add them again. The
* input_processors_added_ member indicates whether the processors were already added and can be
* evaluated directly or need to be added and evaluated. */
Map<StringRef, ProcessorsVector> input_processors_;
/* True if the input processors were already added and can be evaluated directly. False if the
* input processors are not yet added and needs to be added. */
bool input_processors_added_ = false;
public:
Operation(Context &context);
virtual ~Operation();
/* Evaluate the operation by:
* 1. Evaluating the input processors.
* 2. Resetting the results of the operation.
* 3. Calling the execute method of the operation.
* 4. Releasing the results mapped to the inputs. */
void evaluate();
/* Get a reference to the output result identified by the given identifier. */
Result &get_result(StringRef identifier);
/* Map the input identified by the given identifier to the result providing its data. See
* results_mapped_to_inputs_ for more details. This should be called by the evaluator to
* establish links between different operations. */
void map_input_to_result(StringRef identifier, Result *result);
protected:
/* Compute the operation domain of this operation. By default, this implements a default logic
* that infers the operation domain from the inputs, which may be overridden for a different
* logic. See the discussion in COM_domain.hh for the inference logic and more information. */
virtual Domain compute_domain();
/* Add and evaluate any needed input processors, which essentially just involves calling the
* add_and_evaluate_input_processor method with the needed processors. This is called before
* executing the operation to prepare its inputs. The class defines a default implementation
* which adds typically needed processors, but derived classes can override the method to have
* a different implementation, extend the implementation, or remove it entirely. */
virtual void add_and_evaluate_input_processors();
/* Given the identifier of an input of the operation and a processor operation:
* - Add the given processor to the list of input processors for the input.
* - Map the input of the processor to be the result of the last input processor or the result
* mapped to the input if no previous processors exists.
* - Switch the result mapped to the input to be the output result of the processor.
* - Evaluate the processor. */
void add_and_evaluate_input_processor(StringRef identifier, SimpleOperation *processor);
/* This method should allocate the operation results, execute the operation, and compute the
* output results. */
virtual void execute() = 0;
/* Get a reference to the result connected to the input identified by the given identifier. */
Result &get_input(StringRef identifier) const;
/* Switch the result mapped to the input identified by the given identifier with the given
* result. */
void switch_result_mapped_to_input(StringRef identifier, Result *result);
/* Add the given result to the results_ map identified by the given output identifier. This
* should be called during operation construction for all outputs. The provided result shouldn't
* be allocated or initialized, this will happen later during execution. */
void populate_result(StringRef identifier, Result result);
/* Declare the descriptor of the input identified by the given identifier to be the given
* descriptor. Adds the given descriptor to the input_descriptors_ map identified by the given
* input identifier. This should be called during operation constructor for all inputs. */
void declare_input_descriptor(StringRef identifier, InputDescriptor descriptor);
/* Get a reference to the descriptor of the input identified by the given identified. */
InputDescriptor &get_input_descriptor(StringRef identified);
/* Returns a reference to the compositor context. */
Context &context();
/* Returns a reference to the texture pool of the compositor context. */
TexturePool &texture_pool() const;
/* Returns a reference to the shader pool of the compositor context. */
ShaderPool &shader_pool() const;
private:
/* Evaluate the input processors. If the input processors were already added they will be
* evaluated directly. Otherwise, the input processors will be added and evaluated. */
void evaluate_input_processors();
/* Resets the results of the operation. See the reset method in the Result class for more
* information. */
void reset_results();
/* Release the results that are mapped to the inputs of the operation. This is called after the
* evaluation of the operation to declare that the results are no longer needed by this
* operation. */
void release_inputs();
};
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,49 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "GPU_shader.h"
#include "COM_context.hh"
#include "COM_domain.hh"
#include "COM_input_descriptor.hh"
#include "COM_result.hh"
#include "COM_simple_operation.hh"
namespace blender::realtime_compositor {
/* ------------------------------------------------------------------------------------------------
* Realize On Domain Operation
*
* A simple operation that projects the input on a certain target domain, copies the area of the
* input that intersects the target domain, and fill the rest with zeros or repetitions of the
* input depending on the realization options of the target domain. See the discussion in
* COM_domain.hh for more information. */
class RealizeOnDomainOperation : public SimpleOperation {
private:
/* The target domain to realize the input on. */
Domain domain_;
public:
RealizeOnDomainOperation(Context &context, Domain domain, ResultType type);
void execute() override;
/* Determine if a realize on domain operation is needed for the input with the given result and
* descriptor in an operation with the given operation domain. If it is not needed, return a null
* pointer. If it is needed, return an instance of the operation. */
static SimpleOperation *construct_if_needed(Context &context,
const Result &input_result,
const InputDescriptor &input_descriptor,
const Domain &operaiton_domain);
protected:
/* The operation domain is just the target domain. */
Domain compute_domain() override;
private:
/* Get the realization shader of the appropriate type. */
GPUShader *get_realization_shader();
};
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,31 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "COM_context.hh"
#include "COM_result.hh"
#include "COM_simple_operation.hh"
namespace blender::realtime_compositor {
/* ------------------------------------------------------------------------------------------------
* Reduce To Single Value Operation
*
* A simple operation that reduces its input result into a single value output result. The input is
* assumed to be a texture result of size 1x1, that is, a texture composed of a single pixel, the
* value of which shall serve as the single value of the output result. */
class ReduceToSingleValueOperation : public SimpleOperation {
public:
ReduceToSingleValueOperation(Context &context, ResultType type);
/* Download the input pixel from the GPU texture and set its value to the value of the allocated
* single value output result. */
void execute() override;
/* Determine if a reduce to single value operation is needed for the input with the
* given result. If it is not needed, return a null pointer. If it is needed, return an instance
* of the operation. */
static SimpleOperation *construct_if_needed(Context &context, const Result &input_result);
};
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,234 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_float3x3.hh"
#include "BLI_math_vec_types.hh"
#include "GPU_shader.h"
#include "GPU_texture.h"
#include "COM_domain.hh"
#include "COM_texture_pool.hh"
namespace blender::realtime_compositor {
/* Possible data types that operations can operate on. They either represent the base type of the
* result texture or a single value result. */
enum class ResultType : uint8_t {
Float,
Vector,
Color,
};
/* ------------------------------------------------------------------------------------------------
* Result
*
* A result represents the computed value of an output of an operation. A result can either
* represent an image or a single value. A result is typed, and can be of type color, vector, or
* float. Single value results are stored in 1x1 textures to make them easily accessible in
* shaders. But the same value is also stored in the value union member of the result for any
* host-side processing. The texture of the result is allocated from the texture pool referenced by
* the result.
*
* Results are reference counted and their textures are released once their reference count reaches
* zero. After constructing a result, the set_initial_reference_count method is called to declare
* the number of operations that needs this result. Once each operation that needs the result no
* longer needs it, the release method is called and the reference count is decremented, until it
* reaches zero, where the result's texture is then released. Since results are eventually
* decremented to zero by the end of every evaluation, the reference count is restored before every
* evaluation to its initial reference count by calling the reset method, which is why a separate
* member initial_reference_count_ is stored to keep track of the initial value.
*
* A result not only represents an image, but also the area it occupies in the virtual compositing
* space. This area is called the Domain of the result, see the discussion in COM_domain.hh for
* more information.
*
* A result can be a proxy result that merely wraps another master result, in which case, it shares
* its values and delegates all reference counting to it. While a proxy result shares the value of
* the master result, it can have a different domain. Consequently, transformation operations are
* implemented using proxy results, where their results are proxy results of their inputs but with
* their domains transformed based on their options. Moreover, proxy results can also be used as
* the results of identity operations, that is, operations that do nothing to their inputs in
* certain configurations. In which case, the proxy result is left as is with no extra
* transformation on its domain whatsoever. Proxy results can be created by calling the
* pass_through method, see that method for more details. */
class Result {
private:
/* The base type of the texture or the type of the single value. */
ResultType type_;
/* If true, the result is a single value, otherwise, the result is a texture. */
bool is_single_value_;
/* A GPU texture storing the result data. This will be a 1x1 texture if the result is a single
* value, the value of which will be identical to that of the value member. See class description
* for more information. */
GPUTexture *texture_ = nullptr;
/* The texture pool used to allocate the texture of the result, this should be initialized during
* construction. */
TexturePool *texture_pool_ = nullptr;
/* The number of operations that currently needs this result. At the time when the result is
* computed, this member will have a value that matches initial_reference_count_. Once each
* operation that needs the result no longer needs it, the release method is called and the
* reference count is decremented, until it reaches zero, where the result's texture is then
* released. If this result have a master result, then this reference count is irrelevant and
* shadowed by the reference count of the master result. */
int reference_count_;
/* The number of operations that reference and use this result at the time when it was initially
* computed. Since reference_count_ is decremented and always becomes zero at the end of the
* evaluation, this member is used to reset the reference count of the results for later
* evaluations by calling the reset method. This member is also used to determine if this result
* should be computed by calling the should_compute method. */
int initial_reference_count_;
/* If the result is a single value, this member stores the value of the result, the value of
* which will be identical to that stored in the texture member. The active union member depends
* on the type of the result. This member is uninitialized and should not be used if the result
* is a texture. */
union {
float float_value_;
float3 vector_value_;
float4 color_value_;
};
/* The domain of the result. This only matters if the result was a texture. See the discussion in
* COM_domain.hh for more information. */
Domain domain_ = Domain::identity();
/* If not nullptr, then this result wraps and shares the value of another master result. In this
* case, calls to texture-related methods like increment_reference_count and release should
* operate on the master result as opposed to this result. This member is typically set upon
* calling the pass_through method, which sets this result to be the master of a target result.
* See that method for more information. */
Result *master_ = nullptr;
public:
/* Construct a result of the given type with the given texture pool that will be used to allocate
* and release the result's texture. */
Result(ResultType type, TexturePool &texture_pool);
/* Declare the result to be a texture result, allocate a texture of an appropriate type with
* the size of the given domain from the result's texture pool, and set the domain of the result
* to the given domain. */
void allocate_texture(Domain domain);
/* Declare the result to be a single value result, allocate a texture of an appropriate
* type with size 1x1 from the result's texture pool, and set the domain to be an identity
* domain. See class description for more information. */
void allocate_single_value();
/* Allocate a single value result and set its value to zero. This is called for results whose
* value can't be computed and are considered invalid. */
void allocate_invalid();
/* Bind the texture of the result to the texture image unit with the given name in the currently
* bound given shader. This also inserts a memory barrier for texture fetches to ensure any prior
* writes to the texture are reflected before reading from it. */
void bind_as_texture(GPUShader *shader, const char *texture_name) const;
/* Bind the texture of the result to the image unit with the given name in the currently bound
* given shader. */
void bind_as_image(GPUShader *shader, const char *image_name) const;
/* Unbind the texture which was previously bound using bind_as_texture. */
void unbind_as_texture() const;
/* Unbind the texture which was previously bound using bind_as_image. */
void unbind_as_image() const;
/* Pass this result through to a target result, in which case, the target result becomes a proxy
* result with this result as its master result. This is done by making the target result a copy
* of this result, essentially having identical values between the two and consequently sharing
* the underlying texture. An exception is the initial reference count, whose value is retained
* and not copied, because it is a property of the original result and is needed for correctly
* resetting the result before the next evaluation. Additionally, this result is set to be the
* master of the target result, by setting the master member of the target. Finally, the
* reference count of the result is incremented by the reference count of the target result. See
* the discussion above for more information. */
void pass_through(Result &target);
/* Transform the result by the given transformation. This effectively pre-multiply the given
* transformation by the current transformation of the domain of the result. */
void transform(const float3x3 &transformation);
/* Get a reference to the realization options of this result. See the RealizationOptions class
* for more information. */
RealizationOptions &get_realization_options();
/* If the result is a single value result of type float, return its float value. Otherwise, an
* uninitialized value is returned. */
float get_float_value() const;
/* If the result is a single value result of type vector, return its vector value. Otherwise, an
* uninitialized value is returned. */
float3 get_vector_value() const;
/* If the result is a single value result of type color, return its color value. Otherwise, an
* uninitialized value is returned. */
float4 get_color_value() const;
/* Same as get_float_value but returns a default value if the result is not a single value. */
float get_float_value_default(float default_value) const;
/* Same as get_vector_value but returns a default value if the result is not a single value. */
float3 get_vector_value_default(const float3 &default_value) const;
/* Same as get_color_value but returns a default value if the result is not a single value. */
float4 get_color_value_default(const float4 &default_value) const;
/* If the result is a single value result of type float, set its float value and upload it to the
* texture. Otherwise, an undefined behavior is invoked. */
void set_float_value(float value);
/* If the result is a single value result of type vector, set its vector value and upload it to
* the texture. Otherwise, an undefined behavior is invoked. */
void set_vector_value(const float3 &value);
/* If the result is a single value result of type color, set its color value and upload it to the
* texture. Otherwise, an undefined behavior is invoked. */
void set_color_value(const float4 &value);
/* Set the value of initial_reference_count_, see that member for more details. This should be
* called after constructing the result to declare the number of operations that needs it. */
void set_initial_reference_count(int count);
/* Reset the result to prepare it for a new evaluation. This should be called before evaluating
* the operation that computes this result. First, set the value of reference_count_ to the value
* of initial_reference_count_ since reference_count_ may have already been decremented to zero
* in a previous evaluation. Second, set master_ to nullptr because the result may have been
* turned into a proxy result in a previous evaluation. Other fields don't need to be reset
* because they are runtime and overwritten during evaluation. */
void reset();
/* Increment the reference count of the result by the given count. If this result have a master
* result, the reference count of the master result is incremented instead. */
void increment_reference_count(int count = 1);
/* Decrement the reference count of the result and release the its texture back into the texture
* pool if the reference count reaches zero. This should be called when an operation that used
* this result no longer needs it. If this result have a master result, the master result is
* released instead. */
void release();
/* Returns true if this result should be computed and false otherwise. The result should be
* computed if its reference count is not zero, that is, its result is used by at least one
* operation. */
bool should_compute();
/* Returns the type of the result. */
ResultType type() const;
/* Returns true if the result is a texture and false of it is a single value. */
bool is_texture() const;
/* Returns true if the result is a single value and false of it is a texture. */
bool is_single_value() const;
/* Returns the allocated GPU texture of the result. */
GPUTexture *texture() const;
/* Returns the reference count of the result. If this result have a master result, then the
* reference count of the master result is returned instead. */
int reference_count() const;
/* Returns a reference to the domain of the result. See the Domain class. */
const Domain &domain() const;
};
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,21 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_vector_set.hh"
#include "NOD_derived_node_tree.hh"
namespace blender::realtime_compositor {
using namespace nodes::derived_node_tree_types;
/* A type representing the ordered set of nodes defining the schedule of node execution. */
using Schedule = VectorSet<DNode>;
/* Computes the execution schedule of the node tree. This is essentially a post-order depth first
* traversal of the node tree from the output node to the leaf input nodes, with informed order of
* traversal of dependencies based on a heuristic estimation of the number of needed buffers. */
Schedule compute_schedule(DerivedNodeTree &tree);
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,74 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_vector.hh"
#include "DNA_node_types.h"
#include "GPU_material.h"
#include "NOD_derived_node_tree.hh"
namespace blender::realtime_compositor {
using namespace nodes::derived_node_tree_types;
/* ------------------------------------------------------------------------------------------------
* Shader Node
*
* A shader node encapsulates a compositor node tree that is capable of being used together with
* other shader nodes to construct a Shader Operation using the GPU material compiler. A GPU node
* stack for each of the node inputs and outputs is stored and populated during construction in
* order to represent the node as a GPU node inside the GPU material graph, see GPU_material.h for
* more information. Derived classes should implement the compile method to add the node and link
* it to the GPU material given to the method. The compiler is expected to initialize the input
* links of the node before invoking the compile method. See the discussion in
* COM_shader_operation.hh for more information. */
class ShaderNode {
private:
/* The node that this operation represents. */
DNode node_;
/* The GPU node stacks of the inputs of the node. Those are populated during construction in the
* populate_inputs method. The links of the inputs are initialized by the GPU material compiler
* prior to calling the compile method. There is an extra stack at the end to mark the end of the
* array, as this is what the GPU module functions expect. */
Vector<GPUNodeStack> inputs_;
/* The GPU node stacks of the outputs of the node. Those are populated during construction in the
* populate_outputs method. There is an extra stack at the end to mark the end of the array, as
* this is what the GPU module functions expect. */
Vector<GPUNodeStack> outputs_;
public:
/* Construct the node by populating both its inputs and outputs. */
ShaderNode(DNode node);
virtual ~ShaderNode() = default;
/* Compile the node by adding the appropriate GPU material graph nodes and linking the
* appropriate resources. */
virtual void compile(GPUMaterial *material) = 0;
/* Returns a contiguous array containing the GPU node stacks of each input. */
GPUNodeStack *get_inputs_array();
/* Returns a contiguous array containing the GPU node stacks of each output. */
GPUNodeStack *get_outputs_array();
protected:
/* Returns a reference to the derived node that this operation represents. */
const DNode &node() const;
/* Returns a reference to the node this operations represents. */
bNode &bnode() const;
private:
/* Populate the inputs of the node. The input link is set to nullptr and is expected to be
* initialized by the GPU material compiler before calling the compile method. */
void populate_inputs();
/* Populate the outputs of the node. The output link is set to nullptr and is expected to be
* initialized by the compile method. */
void populate_outputs();
};
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,234 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <memory>
#include "BLI_map.hh"
#include "BLI_string_ref.hh"
#include "BLI_vector_set.hh"
#include "GPU_material.h"
#include "GPU_shader.h"
#include "NOD_derived_node_tree.hh"
#include "COM_context.hh"
#include "COM_operation.hh"
#include "COM_scheduler.hh"
namespace blender::realtime_compositor {
using namespace nodes::derived_node_tree_types;
/* A type representing a contiguous subset of the node execution schedule that will be compiled
* into a Shader Operation. */
using ShaderCompileUnit = VectorSet<DNode>;
/* A type representing a map that associates the identifier of each input of the operation with the
* output socket it is linked to. */
using InputsToLinkedOutputsMap = Map<StringRef, DOutputSocket>;
/* A type representing a map that associates the output socket that provides the result of an
* output of the operation with the identifier of that output. */
using OutputSocketsToOutputIdentifiersMap = Map<DOutputSocket, StringRef>;
/* ------------------------------------------------------------------------------------------------
* Shader Operation
*
* An operation that evaluates a shader compiled from a contiguous subset of the node execution
* schedule using the GPU material compiler, see GPU_material.h for more information. The subset
* of the node execution schedule is called a shader compile unit, see the discussion in
* COM_compile_state.hh for more information.
*
* Consider the following node graph with a node execution schedule denoted by the number on each
* node. The compiler may decide to compile a subset of the execution schedule into a shader
* operation, in this case, the nodes from 3 to 5 were compiled together into a shader operation.
* This subset is called the shader compile unit. See the discussion in COM_evaluator.hh for more
* information on the compilation process. Each of the nodes inside the compile unit implements a
* Shader Node which is instantiated, stored in shader_nodes_, and used during compilation. See the
* discussion in COM_shader_node.hh for more information. Links that are internal to the shader
* operation are established between the input and outputs of the shader nodes, for instance, the
* links between nodes 3 and 4 as well as those between nodes 4 and 5. However, links that cross
* the boundary of the shader operation needs special handling.
*
* Shader Operation
* +------------------------------------------------------+
* .------------. | .------------. .------------. .------------. | .------------.
* | Node 1 | | | Node 3 | | Node 4 | | Node 5 | | | Node 6 |
* | |----|--| |--| |------| |--|--| |
* | | .-|--| | | | .---| | | | |
* '------------' | | '------------' '------------' | '------------' | '------------'
* | +----------------------------------|-------------------+
* .------------. | |
* | Node 2 | | |
* | |--'------------------------------------'
* | |
* '------------'
*
* Links from nodes that are not part of the shader operation to nodes that are part of the shader
* operation are considered inputs of the operation itself and are declared as such. For instance,
* the link from node 1 to node 3 is declared as an input to the operation, and the same applies
* for the links from node 2 to nodes 3 and 5. Note, however, that only one input is declared for
* each distinct output socket, so both links from node 2 share the same input of the operation.
* Once an input is declared for a distinct output socket:
*
* 1. A material texture is added to the GPU material. This texture will be bound to the result of
* the output socket during evaluation.
* 2. The newly added texture is mapped to the output socket in output_to_material_texture_map_
* to share that same texture for all inputs linked to the same output socket.
* 3. A texture loader GPU material node that samples the newly added material texture is added and
* linked to the GPU input stacks of shader nodes that the output socket is linked to.
* 4. The input of the operation is mapped to the output socket to help the compiler in
* establishing links between the operations. See the get_inputs_to_linked_outputs_map method.
*
* Links from nodes that are part of the shader operation to nodes that are not part of the shader
* operation are considered outputs of the operation itself and are declared as such. For instance,
* the link from node 5 to node 6 is declared as an output to the operation. Once an output is
* declared for an output socket:
*
* 1. A material image is added to the GPU material. This image will be bound to the result of
* the operation output during evaluation. This is the image where the result of that output
* will be written.
* 2. An image storer GPU material node that stores the output value in the newly added material
* image is added and linked to the GPU output stack of the output.
* 4. The output of the operation is mapped to the output socket to help the compiler in
* establishing links between the operations. See the get_inputs_to_linked_outputs_map method.
*
* The GPU material is declared as a compute material and its compute source is used to construct a
* compute shader that is then dispatched during operation evaluation after binding the inputs,
* outputs, and any necessary resources. */
class ShaderOperation : public Operation {
private:
/* The compile unit that will be compiled into this shader operation. */
ShaderCompileUnit compile_unit_;
/* The GPU material backing the operation. This is created and compiled during construction and
* freed during destruction. */
GPUMaterial *material_;
/* A map that associates each node in the compile unit with an instance of its shader node. */
Map<DNode, std::unique_ptr<ShaderNode>> shader_nodes_;
/* A map that associates the identifier of each input of the operation with the output socket it
* is linked to. See the above discussion for more information. */
InputsToLinkedOutputsMap inputs_to_linked_outputs_map_;
/* A map that associates the output socket that provides the result of an output of the operation
* with the identifier of that output. See the above discussion for more information. */
OutputSocketsToOutputIdentifiersMap output_sockets_to_output_identifiers_map_;
/* A map that associates the output socket of a node that is not part of the shader operation to
* the material texture that was created for it. This is used to share the same material texture
* with all inputs that are linked to the same output socket. */
Map<DOutputSocket, GPUMaterialTexture *> output_to_material_texture_map_;
public:
/* Construct and compile a GPU material from the given shader compile unit by calling
* GPU_material_from_callbacks with the appropriate callbacks. */
ShaderOperation(Context &context, ShaderCompileUnit &compile_unit);
/* Free the GPU material. */
~ShaderOperation();
/* Allocate the output results, bind the shader and all its needed resources, then dispatch the
* shader. */
void execute() override;
/* Get the identifier of the operation output corresponding to the given output socket. This is
* called by the compiler to identify the operation output that provides the result for an input
* by providing the output socket that the input is linked to. See
* output_sockets_to_output_identifiers_map_ for more information. */
StringRef get_output_identifier_from_output_socket(DOutputSocket output);
/* Get a reference to the inputs to linked outputs map of the operation. This is called by the
* compiler to identify the output that each input of the operation is linked to for correct
* input mapping. See inputs_to_linked_outputs_map_ for more information. */
InputsToLinkedOutputsMap &get_inputs_to_linked_outputs_map();
/* Compute and set the initial reference counts of all the results of the operation. The
* reference counts of the results are the number of operations that use those results, which is
* computed as the number of inputs whose node is part of the schedule and is linked to the
* output corresponding to each of the results of the operation. The node execution schedule is
* given as an input. */
void compute_results_reference_counts(const Schedule &schedule);
private:
/* Bind the uniform buffer of the GPU material as well as any color band textures needed by the
* GPU material. Other resources like attributes and textures that reference images are not bound
* because the GPU material is guaranteed not to have any of them. Textures that reference the
* inputs of the operation and images that reference the outputs of the operation are bound in
* the bind_inputs and bind_outputs methods respectively. The compiled shader of the material is
* given as an argument and assumed to be bound. */
void bind_material_resources(GPUShader *shader);
/* Bind the input results of the operation to the appropriate textures in the GPU material. The
* material textures stored in output_to_material_texture_map_ have sampler names that match
* the identifiers of the operation inputs that they correspond to. The compiled shader of the
* material is given as an argument and assumed to be bound. */
void bind_inputs(GPUShader *shader);
/* Bind the output results of the operation to the appropriate images in the GPU material. Every
* image in the GPU material corresponds to one of the outputs of the operation, an output whose
* identifier is the name of the image in the GPU material shader. The compiled shader of the
* material is given as an argument and assumed to be bound. */
void bind_outputs(GPUShader *shader);
/* A static callback method of interface GPUMaterialSetupFn that is passed to
* GPU_material_from_callbacks to setup the GPU material. The thunk parameter will be a pointer
* to the instance of ShaderOperation that is being compiled. This methods setup the GPU
* material as a compute one. */
static void setup_material(void *thunk, GPUMaterial *material);
/* A static callback method of interface GPUMaterialCompileFn that is passed to
* GPU_material_from_callbacks to compile the GPU material. The thunk parameter will be a pointer
* to the instance of ShaderOperation that is being compiled. The method goes over the compile
* unit and does the following for each node:
*
* - Instantiate a ShaderNode from the node and add it to shader_nodes_.
* - Link the inputs of the node if needed. The inputs are either linked to other nodes in the
* GPU material graph or they are exposed as inputs to the shader operation itself if they are
* linked to nodes that are not part of the shader operation.
* - Call the compile method of the shader node to actually add and link the GPU material graph
* nodes.
* - If any of the outputs of the node are linked to nodes that are not part of the shader
* operation, they are exposed as outputs to the shader operation itself. */
static void compile_material(void *thunk, GPUMaterial *material);
/* Link the inputs of the node if needed. Unlinked inputs are ignored as they will be linked by
* the node compile method. If the input is linked to a node that is not part of the shader
* operation, the input will be exposed as an input to the shader operation and linked to it.
* While if the input is linked to a node that is part of the shader operation, then it is linked
* to that node in the GPU material node graph. */
void link_node_inputs(DNode node, GPUMaterial *material);
/* Given the input of a node that is part of the shader operation which is linked to the given
* output of a node that is also part of the shader operation, map the output link of the GPU
* node stack of the output to the input link of the GPU node stack of the input. This
* essentially establishes the needed links in the GPU material node graph. */
void map_node_input(DInputSocket input, DOutputSocket output);
/* Given the input of a node that is part of the shader operation which is linked to the given
* output of a node that is not part of the shader operation, declare a new input to the
* operation and link it appropriately as detailed in the discussion above. */
void declare_operation_input_if_needed(DInputSocket input,
DOutputSocket output,
GPUMaterial *material);
/* Link the input node stack corresponding to the given input to an input loader GPU material
* node sampling the material texture corresponding to the given output. */
void link_material_input_loader(DInputSocket input, DOutputSocket output, GPUMaterial *material);
/* Populate the output results of the shader operation for outputs of the given node that
* are linked to nodes outside of the shader operation. */
void populate_results_for_node(DNode node, GPUMaterial *material);
/* Given the output of a node that is part of the shader operation which is linked to an input of
* a node that is not part of the shader operation, declare a new output to the operation and
* link it appropriately as detailed in the discussion above. */
void populate_operation_result(DOutputSocket output, GPUMaterial *material);
/* A static callback method of interface GPUCodegenCallbackFn that is passed to
* GPU_material_from_callbacks to create the shader create info of the GPU material. The thunk
* parameter will be a pointer to the instance of ShaderOperation that is being compiled.
* This method setup the shader create info as a compute shader and sets its generated source
* based on the GPU material code generator output. */
static void generate_material(void *thunk,
GPUMaterial *material,
GPUCodegenOutput *code_generator);
};
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,31 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_map.hh"
#include "BLI_string_ref.hh"
#include "GPU_shader.h"
namespace blender::realtime_compositor {
/* -------------------------------------------------------------------------------------------------
* Shader Pool
*
* A shader pool is a pool of shaders identified by their info name that can be reused throughout
* the evaluation of the compositor and are only freed when the shader pool is destroyed. */
class ShaderPool {
private:
/* The set of shaders identified by their info name that are currently available in the pool to
* be acquired. */
Map<StringRef, GPUShader *> shaders_;
public:
~ShaderPool();
/* Check if there is an available shader with the given info name in the pool, if such shader
* exists, return it, otherwise, return a newly created shader and add it to the pool. */
GPUShader *acquire(const char *info_name);
};
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,64 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_string_ref.hh"
#include "COM_operation.hh"
#include "COM_result.hh"
namespace blender::realtime_compositor {
/* ------------------------------------------------------------------------------------------------
* Simple Operation
*
* A simple operation is an operation that takes exactly one input and computes exactly one output.
* Moreover, the output is guaranteed to only have a single user, that is, its reference count will
* be one. Such operations can be attached to the inputs of operations to pre-process the inputs to
* prepare them before the operation is executed.*/
class SimpleOperation : public Operation {
private:
/* The identifier of the output. This is constant for all operations. */
static const StringRef output_identifier_;
/* The identifier of the input. This is constant for all operations. */
static const StringRef input_identifier_;
public:
using Operation::Operation;
/* Get a reference to the output result of the operation, this essentially calls the super
* get_result method with the output identifier of the operation. */
Result &get_result();
/* Map the input of the operation to the given result, this essentially calls the super
* map_input_to_result method with the input identifier of the operation. */
void map_input_to_result(Result *result);
protected:
/* Simple operations don't need input processors, so override with an empty implementation. */
void add_and_evaluate_input_processors() override;
/* Get a reference to the input result of the operation, this essentially calls the super
* get_result method with the input identifier of the operation. */
Result &get_input();
/* Switch the result mapped to the input with the given result, this essentially calls the super
* switch_result_mapped_to_input method with the input identifier of the operation. */
void switch_result_mapped_to_input(Result *result);
/* Populate the result of the operation, this essentially calls the super populate_result method
* with the output identifier of the operation and sets the initial reference count of the result
* to 1, since the result of an operation operation is guaranteed to have a single user. */
void populate_result(Result result);
/* Declare the descriptor of the input of the operation to be the given descriptor, this
* essentially calls the super declare_input_descriptor method with the input identifier of the
* operation. */
void declare_input_descriptor(InputDescriptor descriptor);
/* Get a reference to the descriptor of the input, this essentially calls the super
* get_input_descriptor method with the input identifier of the operation. */
InputDescriptor &get_input_descriptor();
};
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,86 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <cstdint>
#include "BLI_map.hh"
#include "BLI_math_vec_types.hh"
#include "BLI_vector.hh"
#include "GPU_texture.h"
namespace blender::realtime_compositor {
/* ------------------------------------------------------------------------------------------------
* Texture Pool Key
*
* A key used to identify a texture specification in a texture pool. Defines a hash and an equality
* operator for use in a hash map. */
class TexturePoolKey {
public:
int2 size;
eGPUTextureFormat format;
/* Construct a key from the given texture size and format. */
TexturePoolKey(int2 size, eGPUTextureFormat format);
/* Construct a key from the size and format of the given texture. */
TexturePoolKey(const GPUTexture *texture);
uint64_t hash() const;
};
bool operator==(const TexturePoolKey &a, const TexturePoolKey &b);
/* ------------------------------------------------------------------------------------------------
* Texture Pool
*
* A texture pool allows the allocation and reuse of textures throughout the execution of the
* compositor to avoid memory fragmentation and texture allocation overheads. The texture pool
* delegates the actual texture allocation to an allocate_texture method that should be implemented
* by the caller of the compositor evaluator, allowing a more agnostic and flexible execution that
* can be controlled by the caller. If the compositor is expected to execute frequently, like on
* every redraw, then the allocation method should use a persistent texture pool to allow
* cross-evaluation texture pooling, for instance, by using the DRWTexturePool. But if the
* evaluator is expected to execute infrequently, the allocated textures can just be freed when the
* evaluator is done, that is, when the pool is destructed. */
class TexturePool {
private:
/* The set of textures in the pool that are available to acquire for each distinct texture
* specification. */
Map<TexturePoolKey, Vector<GPUTexture *>> textures_;
public:
/* Check if there is an available texture with the given specification in the pool, if such
* texture exists, return it, otherwise, return a newly allocated texture. Expect the texture to
* be uncleared and possibly contains garbage data. */
GPUTexture *acquire(int2 size, eGPUTextureFormat format);
/* Shorthand for acquire with GPU_RGBA16F format. */
GPUTexture *acquire_color(int2 size);
/* Shorthand for acquire with GPU_RGBA16F format. Identical to acquire_color because vectors
* are stored in RGBA textures, due to the limited support for RGB textures. */
GPUTexture *acquire_vector(int2 size);
/* Shorthand for acquire with GPU_R16F format. */
GPUTexture *acquire_float(int2 size);
/* Put the texture back into the pool, potentially to be acquired later by another user. Expects
* the texture to be one that was acquired using the same texture pool. */
void release(GPUTexture *texture);
/* Reset the texture pool by clearing all available textures without freeing the textures. If the
* textures will no longer be needed, they should be freed in the destructor. This should be
* called after the compositor is done evaluating. */
void reset();
private:
/* Returns a newly allocated texture with the given specification. This method should be
* implemented by the caller of the compositor evaluator. See the class description for more
* information. */
virtual GPUTexture *allocate_texture(int2 size, eGPUTextureFormat format) = 0;
};
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,59 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_function_ref.hh"
#include "BLI_math_vec_types.hh"
#include "NOD_derived_node_tree.hh"
#include "GPU_shader.h"
#include "COM_input_descriptor.hh"
#include "COM_result.hh"
namespace blender::realtime_compositor {
using namespace nodes::derived_node_tree_types;
/* Get the origin socket of the given node input. If the input is not linked, the socket itself is
* returned. If the input is linked, the socket that is linked to it is returned, which could
* either be an input or an output. An input socket is returned when the given input is connected
* to an unlinked input of a group input node. */
DSocket get_input_origin_socket(DInputSocket input);
/* Get the output socket linked to the given node input. If the input is not linked to an output, a
* null output is returned. */
DOutputSocket get_output_linked_to_input(DInputSocket input);
/* Get the result type that corresponds to the type of the given socket. */
ResultType get_node_socket_result_type(const SocketRef *socket);
/* Returns true if any of the nodes linked to the given output satisfies the given condition, and
* false otherwise. */
bool is_output_linked_to_node_conditioned(DOutputSocket output,
FunctionRef<bool(DNode)> condition);
/* Returns the number of inputs linked to the given output that satisfy the given condition. */
int number_of_inputs_linked_to_output_conditioned(DOutputSocket output,
FunctionRef<bool(DInputSocket)> condition);
/* A node is a shader node if it defines a method to get a shader node operation. */
bool is_shader_node(DNode node);
/* Returns true if the given node is supported, that is, have an implementation. Returns false
* otherwise. */
bool is_node_supported(DNode node);
/* Get the input descriptor of the given input socket. */
InputDescriptor input_descriptor_from_input_socket(const InputSocketRef *socket);
/* Dispatch the given compute shader in a 2D compute space such that the number of invocations in
* both dimensions is as small as possible but at least covers the entirety of global_size assuming
* the shader has a local group size given by local_size. That means that the number of invocations
* might be a bit larger than global_size, so shaders has to put that into consideration. A default
* local size of 16x16 is assumed, which is the optimal local size for many image processing
* shaders. */
void compute_dispatch_global(GPUShader *shader, int2 global_size, int2 local_size = int2(16));
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,166 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <limits>
#include "BLI_math_vec_types.hh"
#include "DNA_node_types.h"
#include "NOD_derived_node_tree.hh"
#include "COM_compile_state.hh"
#include "COM_domain.hh"
#include "COM_input_descriptor.hh"
#include "COM_node_operation.hh"
#include "COM_result.hh"
#include "COM_scheduler.hh"
#include "COM_shader_operation.hh"
#include "COM_utilities.hh"
namespace blender::realtime_compositor {
using namespace nodes::derived_node_tree_types;
CompileState::CompileState(const Schedule &schedule) : schedule_(schedule)
{
}
const Schedule &CompileState::get_schedule()
{
return schedule_;
}
void CompileState::map_node_to_node_operation(DNode node, NodeOperation *operations)
{
return node_operations_.add_new(node, operations);
}
void CompileState::map_node_to_shader_operation(DNode node, ShaderOperation *operations)
{
return shader_operations_.add_new(node, operations);
}
Result &CompileState::get_result_from_output_socket(DOutputSocket output)
{
/* The output belongs to a node that was compiled into a standard node operation, so return a
* reference to the result from that operation using the output identifier. */
if (node_operations_.contains(output.node())) {
NodeOperation *operation = node_operations_.lookup(output.node());
return operation->get_result(output->identifier());
}
/* Otherwise, the output belongs to a node that was compiled into a shader operation, so
* retrieve the internal identifier of that output and return a reference to the result from
* that operation using the retrieved identifier. */
ShaderOperation *operation = shader_operations_.lookup(output.node());
return operation->get_result(operation->get_output_identifier_from_output_socket(output));
}
void CompileState::add_node_to_shader_compile_unit(DNode node)
{
/* Add the node to the shader compile unit. */
shader_compile_unit_.add_new(node);
/* If the domain of the shader compile unit is not yet determined or was determined to be
* an identity domain, update it to be the computed domain of the node. */
if (shader_compile_unit_domain_ == Domain::identity()) {
shader_compile_unit_domain_ = compute_shader_node_domain(node);
}
}
ShaderCompileUnit &CompileState::get_shader_compile_unit()
{
return shader_compile_unit_;
}
void CompileState::reset_shader_compile_unit()
{
return shader_compile_unit_.clear();
}
bool CompileState::should_compile_shader_compile_unit(DNode node)
{
/* If the shader compile unit is empty, then it can't be compiled yet. */
if (shader_compile_unit_.is_empty()) {
return false;
}
/* If the node is not a shader node, then it can't be added to the shader compile unit and the
* shader compile unit is considered complete and should be compiled. */
if (!is_shader_node(node)) {
return true;
}
/* If the computed domain of the node doesn't matches the domain of the shader compile unit, then
* it can't be added to the shader compile unit and the shader compile unit is considered
* complete and should be compiled. Identity domains are an exception as they are always
* compatible because they represents single values. */
if (shader_compile_unit_domain_ != Domain::identity() &&
shader_compile_unit_domain_ != compute_shader_node_domain(node)) {
return true;
}
/* Otherwise, the node is compatible and can be added to the compile unit and it shouldn't be
* compiled just yet. */
return false;
}
Domain CompileState::compute_shader_node_domain(DNode node)
{
/* Default to an identity domain in case no domain input was found, most likely because all
* inputs are single values. */
Domain node_domain = Domain::identity();
int current_domain_priority = std::numeric_limits<int>::max();
/* Go over the inputs and find the domain of the non single value input with the highest domain
* priority. */
for (const InputSocketRef *input_ref : node->inputs()) {
const DInputSocket input{node.context(), input_ref};
/* Get the output linked to the input. If it is null, that means the input is unlinked, so skip
* it. */
const DOutputSocket output = get_output_linked_to_input(input);
if (!output) {
continue;
}
/* Get the input descriptor of the input. */
const InputDescriptor input_descriptor = input_descriptor_from_input_socket(input_ref);
/* If the output belongs to a node that is part of the shader compile unit, then the domain of
* the input is the domain of the compile unit itself. */
if (shader_compile_unit_.contains(output.node())) {
/* Single value inputs can't be domain inputs. */
if (shader_compile_unit_domain_.size == int2(1)) {
continue;
}
/* Notice that the lower the domain priority value is, the higher the priority is, hence the
* less than comparison. */
if (input_descriptor.domain_priority < current_domain_priority) {
node_domain = shader_compile_unit_domain_;
current_domain_priority = input_descriptor.domain_priority;
}
continue;
}
/* Get the result linked to the input. */
const Result &result = get_result_from_output_socket(output);
/* A single value input can't be a domain input. */
if (result.is_single_value() || input_descriptor.expects_single_value) {
continue;
}
/* Notice that the lower the domain priority value is, the higher the priority is, hence the
* less than comparison. */
if (input_descriptor.domain_priority < current_domain_priority) {
node_domain = result.domain();
current_domain_priority = input_descriptor.domain_priority;
}
}
return node_domain;
}
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,36 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "COM_context.hh"
#include "COM_shader_pool.hh"
#include "COM_texture_pool.hh"
namespace blender::realtime_compositor {
Context::Context(TexturePool &texture_pool) : texture_pool_(texture_pool)
{
}
int Context::get_frame_number() const
{
return get_scene()->r.cfra;
}
float Context::get_time() const
{
const float frame_number = static_cast<float>(get_frame_number());
const float frame_rate = static_cast<float>(get_scene()->r.frs_sec) /
static_cast<float>(get_scene()->r.frs_sec_base);
return frame_number / frame_rate;
}
TexturePool &Context::texture_pool()
{
return texture_pool_;
}
ShaderPool &Context::shader_pool()
{
return shader_pool_;
}
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,220 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_vec_types.hh"
#include "GPU_shader.h"
#include "COM_context.hh"
#include "COM_conversion_operation.hh"
#include "COM_input_descriptor.hh"
#include "COM_result.hh"
#include "COM_utilities.hh"
namespace blender::realtime_compositor {
/* -------------------------------------------------------------------------------------------------
* Conversion Operation.
*/
void ConversionOperation::execute()
{
Result &result = get_result();
const Result &input = get_input();
if (input.is_single_value()) {
result.allocate_single_value();
execute_single(input, result);
return;
}
result.allocate_texture(input.domain());
GPUShader *shader = get_conversion_shader();
GPU_shader_bind(shader);
input.bind_as_texture(shader, "input_sampler");
result.bind_as_image(shader, "output_image");
compute_dispatch_global(shader, input.domain().size);
input.unbind_as_texture();
result.unbind_as_image();
GPU_shader_unbind();
}
SimpleOperation *ConversionOperation::construct_if_needed(Context &context,
const Result &input_result,
const InputDescriptor &input_descriptor)
{
ResultType result_type = input_result.type();
ResultType expected_type = input_descriptor.type;
/* If the result type differs from the expected type, return an instance of an appropriate
* conversion operation. Otherwise, return a null pointer. */
if (result_type == ResultType::Float && expected_type == ResultType::Vector) {
return new ConvertFloatToVectorOperation(context);
}
else if (result_type == ResultType::Float && expected_type == ResultType::Color) {
return new ConvertFloatToColorOperation(context);
}
else if (result_type == ResultType::Color && expected_type == ResultType::Float) {
return new ConvertColorToFloatOperation(context);
}
else if (result_type == ResultType::Color && expected_type == ResultType::Vector) {
return new ConvertColorToVectorOperation(context);
}
else if (result_type == ResultType::Vector && expected_type == ResultType::Float) {
return new ConvertVectorToFloatOperation(context);
}
else if (result_type == ResultType::Vector && expected_type == ResultType::Color) {
return new ConvertVectorToColorOperation(context);
}
else {
return nullptr;
}
}
/* -------------------------------------------------------------------------------------------------
* Convert Float To Vector Operation.
*/
ConvertFloatToVectorOperation::ConvertFloatToVectorOperation(Context &context)
: ConversionOperation(context)
{
InputDescriptor input_descriptor;
input_descriptor.type = ResultType::Float;
declare_input_descriptor(input_descriptor);
populate_result(Result(ResultType::Vector, texture_pool()));
}
void ConvertFloatToVectorOperation::execute_single(const Result &input, Result &output)
{
output.set_vector_value(float3(input.get_float_value()));
}
GPUShader *ConvertFloatToVectorOperation::get_conversion_shader() const
{
return shader_pool().acquire("compositor_convert_float_to_vector");
}
/* -------------------------------------------------------------------------------------------------
* Convert Float To Color Operation.
*/
ConvertFloatToColorOperation::ConvertFloatToColorOperation(Context &context)
: ConversionOperation(context)
{
InputDescriptor input_descriptor;
input_descriptor.type = ResultType::Float;
declare_input_descriptor(input_descriptor);
populate_result(Result(ResultType::Color, texture_pool()));
}
void ConvertFloatToColorOperation::execute_single(const Result &input, Result &output)
{
float4 color = float4(input.get_float_value());
color[3] = 1.0f;
output.set_color_value(color);
}
GPUShader *ConvertFloatToColorOperation::get_conversion_shader() const
{
return shader_pool().acquire("compositor_convert_float_to_color");
}
/* -------------------------------------------------------------------------------------------------
* Convert Color To Float Operation.
*/
ConvertColorToFloatOperation::ConvertColorToFloatOperation(Context &context)
: ConversionOperation(context)
{
InputDescriptor input_descriptor;
input_descriptor.type = ResultType::Color;
declare_input_descriptor(input_descriptor);
populate_result(Result(ResultType::Float, texture_pool()));
}
void ConvertColorToFloatOperation::execute_single(const Result &input, Result &output)
{
float4 color = input.get_color_value();
output.set_float_value((color[0] + color[1] + color[2]) / 3.0f);
}
GPUShader *ConvertColorToFloatOperation::get_conversion_shader() const
{
return shader_pool().acquire("compositor_convert_color_to_float");
}
/* -------------------------------------------------------------------------------------------------
* Convert Color To Vector Operation.
*/
ConvertColorToVectorOperation::ConvertColorToVectorOperation(Context &context)
: ConversionOperation(context)
{
InputDescriptor input_descriptor;
input_descriptor.type = ResultType::Color;
declare_input_descriptor(input_descriptor);
populate_result(Result(ResultType::Vector, texture_pool()));
}
void ConvertColorToVectorOperation::execute_single(const Result &input, Result &output)
{
float4 color = input.get_color_value();
output.set_vector_value(float3(color));
}
GPUShader *ConvertColorToVectorOperation::get_conversion_shader() const
{
return shader_pool().acquire("compositor_convert_color_to_vector");
}
/* -------------------------------------------------------------------------------------------------
* Convert Vector To Float Operation.
*/
ConvertVectorToFloatOperation::ConvertVectorToFloatOperation(Context &context)
: ConversionOperation(context)
{
InputDescriptor input_descriptor;
input_descriptor.type = ResultType::Vector;
declare_input_descriptor(input_descriptor);
populate_result(Result(ResultType::Float, texture_pool()));
}
void ConvertVectorToFloatOperation::execute_single(const Result &input, Result &output)
{
float3 vector = input.get_vector_value();
output.set_float_value((vector[0] + vector[1] + vector[2]) / 3.0f);
}
GPUShader *ConvertVectorToFloatOperation::get_conversion_shader() const
{
return shader_pool().acquire("compositor_convert_vector_to_float");
}
/* -------------------------------------------------------------------------------------------------
* Convert Vector To Color Operation.
*/
ConvertVectorToColorOperation::ConvertVectorToColorOperation(Context &context)
: ConversionOperation(context)
{
InputDescriptor input_descriptor;
input_descriptor.type = ResultType::Vector;
declare_input_descriptor(input_descriptor);
populate_result(Result(ResultType::Color, texture_pool()));
}
void ConvertVectorToColorOperation::execute_single(const Result &input, Result &output)
{
output.set_color_value(float4(input.get_vector_value(), 1.0f));
}
GPUShader *ConvertVectorToColorOperation::get_conversion_shader() const
{
return shader_pool().acquire("compositor_convert_vector_to_color");
}
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,38 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_float3x3.hh"
#include "BLI_math_vec_types.hh"
#include "COM_domain.hh"
namespace blender::realtime_compositor {
Domain::Domain(int2 size) : size(size), transformation(float3x3::identity())
{
}
Domain::Domain(int2 size, float3x3 transformation) : size(size), transformation(transformation)
{
}
void Domain::transform(const float3x3 &input_transformation)
{
transformation = input_transformation * transformation;
}
Domain Domain::identity()
{
return Domain(int2(1), float3x3::identity());
}
bool operator==(const Domain &a, const Domain &b)
{
return a.size == b.size && a.transformation == b.transformation;
}
bool operator!=(const Domain &a, const Domain &b)
{
return !(a == b);
}
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,218 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <string>
#include "DNA_node_types.h"
#include "NOD_derived_node_tree.hh"
#include "COM_compile_state.hh"
#include "COM_context.hh"
#include "COM_evaluator.hh"
#include "COM_input_single_value_operation.hh"
#include "COM_node_operation.hh"
#include "COM_operation.hh"
#include "COM_result.hh"
#include "COM_scheduler.hh"
#include "COM_shader_operation.hh"
#include "COM_utilities.hh"
namespace blender::realtime_compositor {
using namespace nodes::derived_node_tree_types;
Evaluator::Evaluator(Context &context, bNodeTree &node_tree)
: context_(context), node_tree_(node_tree)
{
}
void Evaluator::evaluate()
{
/* Reset the texture pool that was potentially populated from a previous evaluation. */
context_.texture_pool().reset();
/* The node tree is not compiled yet, so compile and evaluate the node tree. */
if (!is_compiled_) {
compile_and_evaluate();
is_compiled_ = true;
return;
}
/* The node tree is already compiled, so just go over the operations stream and evaluate the
* operations in order. */
for (const std::unique_ptr<Operation> &operation : operations_stream_) {
operation->evaluate();
}
}
void Evaluator::reset()
{
/* Reset evaluator state. */
operations_stream_.clear();
derived_node_tree_.reset();
node_tree_reference_map_.clear();
/* Mark the node tree as in need to be compiled. */
is_compiled_ = false;
}
bool Evaluator::validate_node_tree()
{
if (derived_node_tree_->has_link_cycles()) {
context_.set_info_message("Compositor node tree has cyclic links!");
return false;
}
if (derived_node_tree_->has_undefined_nodes_or_sockets()) {
context_.set_info_message("Compositor node tree has undefined nodes or sockets!");
return false;
}
/* Find any of the unsupported nodes in the node tree. We only track one of them because we
* display a message for only one at a time to avoid long messages. */
DNode unsupported_node;
derived_node_tree_->foreach_node([&](DNode node) {
if (!is_node_supported(node)) {
unsupported_node = node;
}
});
/* unsupported_node is null if no unsupported node was found. */
if (unsupported_node) {
std::string message = "Compositor node tree has an unsupported node: ";
context_.set_info_message(message + unsupported_node->idname());
return false;
}
return true;
}
void Evaluator::compile_and_evaluate()
{
/* Construct and initialize a derived node tree from the compositor node tree. */
derived_node_tree_.reset(new DerivedNodeTree(node_tree_, node_tree_reference_map_));
/* Validate the node tree and do nothing if it is invalid. */
if (!validate_node_tree()) {
return;
}
/* Compute the node execution schedule. */
const Schedule schedule = compute_schedule(*derived_node_tree_);
/* Declare a compile state to use for tracking the state of the compilation. */
CompileState compile_state(schedule);
/* Go over the nodes in the schedule, compiling them into either node operations or shader
* operations. */
for (const DNode &node : schedule) {
/* Ask the compile state if now would be a good time to compile the shader compile unit given
* the current node, and if it is, compile and evaluate it. */
if (compile_state.should_compile_shader_compile_unit(node)) {
compile_and_evaluate_shader_compile_unit(compile_state);
}
/* If the node is a shader node, then add it to the shader compile unit. */
if (is_shader_node(node)) {
compile_state.add_node_to_shader_compile_unit(node);
}
else {
/* Otherwise, compile and evaluate the node into a node operation. */
compile_and_evaluate_node(node, compile_state);
}
}
}
void Evaluator::compile_and_evaluate_node(DNode node, CompileState &compile_state)
{
/* Get an instance of the node's compositor operation. */
NodeOperation *operation = node->typeinfo()->get_compositor_operation(context_, node);
/* Map the node to the compiled operation. */
compile_state.map_node_to_node_operation(node, operation);
/* Map the inputs of the operation to the results of the outputs they are linked to. */
map_node_operation_inputs_to_their_results(node, operation, compile_state);
/* Add the operation to the operations stream. This has to be done after input mapping because
* the method may add Input Single Value Operations to the operations stream. */
operations_stream_.append(std::unique_ptr<Operation>(operation));
/* Compute the initial reference counts of the results of the operation. */
operation->compute_results_reference_counts(compile_state.get_schedule());
/* Evaluate the operation. */
operation->evaluate();
}
void Evaluator::map_node_operation_inputs_to_their_results(DNode node,
NodeOperation *operation,
CompileState &compile_state)
{
for (const InputSocketRef *input_ref : node->inputs()) {
const DInputSocket input{node.context(), input_ref};
/* Get the output linked to the input. */
const DOutputSocket output = get_output_linked_to_input(input);
/* The output is not null, which means the input is linked. So map the input to the result we
* get from the output. */
if (output) {
Result &result = compile_state.get_result_from_output_socket(output);
operation->map_input_to_result(input->identifier(), &result);
continue;
}
/* Otherwise, the output is null, which means the input is unlinked. So map the input to the
* result of a newly created Input Single Value Operation. */
InputSingleValueOperation *input_operation = new InputSingleValueOperation(context_, input);
operation->map_input_to_result(input->identifier(), &input_operation->get_result());
/* Add the input operation to the operations stream. */
operations_stream_.append(std::unique_ptr<InputSingleValueOperation>(input_operation));
/* Evaluate the input operation. */
input_operation->evaluate();
}
}
void Evaluator::compile_and_evaluate_shader_compile_unit(CompileState &compile_state)
{
/* Compile the shader compile unit into a shader operation. */
ShaderCompileUnit &compile_unit = compile_state.get_shader_compile_unit();
ShaderOperation *operation = new ShaderOperation(context_, compile_unit);
/* Map each of the nodes in the compile unit to the compiled operation. */
for (DNode node : compile_unit) {
compile_state.map_node_to_shader_operation(node, operation);
}
/* Map the inputs of the operation to the results of the outputs they are linked to. */
map_shader_operation_inputs_to_their_results(operation, compile_state);
/* Add the operation to the operations stream. */
operations_stream_.append(std::unique_ptr<Operation>(operation));
/* Compute the initial reference counts of the results of the operation. */
operation->compute_results_reference_counts(compile_state.get_schedule());
/* Evaluate the operation. */
operation->evaluate();
/* Clear the shader compile unit to ready it for tracking the next shader operation. */
compile_state.reset_shader_compile_unit();
}
void Evaluator::map_shader_operation_inputs_to_their_results(ShaderOperation *operation,
CompileState &compile_state)
{
/* For each input of the operation, retrieve the result of the output linked to it, and map the
* result to the input. */
InputsToLinkedOutputsMap &map = operation->get_inputs_to_linked_outputs_map();
for (const InputsToLinkedOutputsMap::Item &item : map.items()) {
Result &result = compile_state.get_result_from_output_socket(item.value);
operation->map_input_to_result(item.key, &result);
}
}
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,58 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_vec_types.hh"
#include "COM_input_single_value_operation.hh"
#include "COM_operation.hh"
#include "COM_result.hh"
#include "COM_utilities.hh"
namespace blender::realtime_compositor {
const StringRef InputSingleValueOperation::output_identifier_ = StringRef("Output");
InputSingleValueOperation::InputSingleValueOperation(Context &context, DInputSocket input_socket)
: Operation(context), input_socket_(input_socket)
{
/* Populate the output result. */
const ResultType result_type = get_node_socket_result_type(input_socket_.socket_ref());
Result result = Result(result_type, texture_pool());
/* The result of an input single value operation is guaranteed to have a single user. */
result.set_initial_reference_count(1);
populate_result(result);
}
void InputSingleValueOperation::execute()
{
/* Allocate a single value for the result. */
Result &result = get_result();
result.allocate_single_value();
/* Set the value of the result to the default value of the input socket. */
switch (result.type()) {
case ResultType::Float:
result.set_float_value(input_socket_->default_value<bNodeSocketValueFloat>()->value);
break;
case ResultType::Vector:
result.set_vector_value(
float3(input_socket_->default_value<bNodeSocketValueVector>()->value));
break;
case ResultType::Color:
result.set_color_value(float4(input_socket_->default_value<bNodeSocketValueRGBA>()->value));
break;
}
}
Result &InputSingleValueOperation::get_result()
{
return Operation::get_result(output_identifier_);
}
void InputSingleValueOperation::populate_result(Result result)
{
Operation::populate_result(output_identifier_, result);
}
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,69 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <memory>
#include "BLI_map.hh"
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
#include "DNA_node_types.h"
#include "NOD_derived_node_tree.hh"
#include "NOD_node_declaration.hh"
#include "COM_context.hh"
#include "COM_input_descriptor.hh"
#include "COM_node_operation.hh"
#include "COM_operation.hh"
#include "COM_result.hh"
#include "COM_scheduler.hh"
#include "COM_utilities.hh"
namespace blender::realtime_compositor {
using namespace nodes::derived_node_tree_types;
NodeOperation::NodeOperation(Context &context, DNode node) : Operation(context), node_(node)
{
/* Populate the output results. */
for (const OutputSocketRef *output : node->outputs()) {
const ResultType result_type = get_node_socket_result_type(output);
const Result result = Result(result_type, texture_pool());
populate_result(output->identifier(), result);
}
/* Populate the input descriptors. */
for (const InputSocketRef *input : node->inputs()) {
const InputDescriptor input_descriptor = input_descriptor_from_input_socket(input);
declare_input_descriptor(input->identifier(), input_descriptor);
}
}
void NodeOperation::compute_results_reference_counts(const Schedule &schedule)
{
for (const OutputSocketRef *output_ref : node()->outputs()) {
const DOutputSocket output{node().context(), output_ref};
const int reference_count = number_of_inputs_linked_to_output_conditioned(
output, [&](DInputSocket input) { return schedule.contains(input.node()); });
get_result(output->identifier()).set_initial_reference_count(reference_count);
}
}
const DNode &NodeOperation::node() const
{
return node_;
}
const bNode &NodeOperation::bnode() const
{
return *node_->bnode();
}
bool NodeOperation::should_compute_output(StringRef identifier)
{
return get_result(identifier).should_compute();
}
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,203 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <limits>
#include <memory>
#include "BLI_map.hh"
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
#include "COM_context.hh"
#include "COM_conversion_operation.hh"
#include "COM_domain.hh"
#include "COM_input_descriptor.hh"
#include "COM_operation.hh"
#include "COM_realize_on_domain_operation.hh"
#include "COM_reduce_to_single_value_operation.hh"
#include "COM_result.hh"
#include "COM_shader_pool.hh"
#include "COM_simple_operation.hh"
#include "COM_texture_pool.hh"
namespace blender::realtime_compositor {
Operation::Operation(Context &context) : context_(context)
{
}
Operation::~Operation() = default;
void Operation::evaluate()
{
evaluate_input_processors();
reset_results();
execute();
release_inputs();
}
Result &Operation::get_result(StringRef identifier)
{
return results_.lookup(identifier);
}
void Operation::map_input_to_result(StringRef identifier, Result *result)
{
results_mapped_to_inputs_.add_new(identifier, result);
}
Domain Operation::compute_domain()
{
/* Default to an identity domain in case no domain input was found, most likely because all
* inputs are single values. */
Domain operation_domain = Domain::identity();
int current_domain_priority = std::numeric_limits<int>::max();
/* Go over the inputs and find the domain of the non single value input with the highest domain
* priority. */
for (StringRef identifier : input_descriptors_.keys()) {
const Result &result = get_input(identifier);
const InputDescriptor &descriptor = get_input_descriptor(identifier);
/* A single value input can't be a domain input. */
if (result.is_single_value() || descriptor.expects_single_value) {
continue;
}
/* Notice that the lower the domain priority value is, the higher the priority is, hence the
* less than comparison. */
if (descriptor.domain_priority < current_domain_priority) {
operation_domain = result.domain();
current_domain_priority = descriptor.domain_priority;
}
}
return operation_domain;
}
void Operation::add_and_evaluate_input_processors()
{
/* Add and evaluate reduce to single value input processors if needed. */
for (const StringRef &identifier : results_mapped_to_inputs_.keys()) {
SimpleOperation *single_value = ReduceToSingleValueOperation::construct_if_needed(
context(), get_input(identifier));
add_and_evaluate_input_processor(identifier, single_value);
}
/* Add and evaluate conversion input processors if needed. */
for (const StringRef &identifier : results_mapped_to_inputs_.keys()) {
SimpleOperation *conversion = ConversionOperation::construct_if_needed(
context(), get_input(identifier), get_input_descriptor(identifier));
add_and_evaluate_input_processor(identifier, conversion);
}
/* Add and evaluate realize on domain input processors if needed. */
for (const StringRef &identifier : results_mapped_to_inputs_.keys()) {
SimpleOperation *realize_on_domain = RealizeOnDomainOperation::construct_if_needed(
context(), get_input(identifier), get_input_descriptor(identifier), compute_domain());
add_and_evaluate_input_processor(identifier, realize_on_domain);
}
}
void Operation::add_and_evaluate_input_processor(StringRef identifier, SimpleOperation *processor)
{
/* Allow null inputs to facilitate construct_if_needed pattern of addition. For instance, see the
* implementation of the add_and_evaluate_input_processors method. */
if (!processor) {
return;
}
/* Get a reference to the input processors vector for the given input. */
ProcessorsVector &processors = input_processors_.lookup_or_add_default(identifier);
/* Get the result that should serve as the input for the processor. This is either the result
* mapped to the input or the result of the last processor depending on whether this is the first
* processor or not. */
Result &result = processors.is_empty() ? get_input(identifier) : processors.last()->get_result();
/* Map the input result of the processor and add it to the processors vector. */
processor->map_input_to_result(&result);
processors.append(std::unique_ptr<SimpleOperation>(processor));
/* Switch the result mapped to the input to be the output result of the processor. */
switch_result_mapped_to_input(identifier, &processor->get_result());
/* Evaluate the input processor. */
processor->evaluate();
}
Result &Operation::get_input(StringRef identifier) const
{
return *results_mapped_to_inputs_.lookup(identifier);
}
void Operation::switch_result_mapped_to_input(StringRef identifier, Result *result)
{
results_mapped_to_inputs_.lookup(identifier) = result;
}
void Operation::populate_result(StringRef identifier, Result result)
{
results_.add_new(identifier, result);
}
void Operation::declare_input_descriptor(StringRef identifier, InputDescriptor descriptor)
{
input_descriptors_.add_new(identifier, descriptor);
}
InputDescriptor &Operation::get_input_descriptor(StringRef identifier)
{
return input_descriptors_.lookup(identifier);
}
Context &Operation::context()
{
return context_;
}
TexturePool &Operation::texture_pool() const
{
return context_.texture_pool();
}
ShaderPool &Operation::shader_pool() const
{
return context_.shader_pool();
}
void Operation::evaluate_input_processors()
{
/* The input processors are not added yet, so add and evaluate the input processors. */
if (!input_processors_added_) {
add_and_evaluate_input_processors();
input_processors_added_ = true;
return;
}
/* The input processors are already added, so just go over the input processors and evaluate
* them. */
for (const ProcessorsVector &processors : input_processors_.values()) {
for (const std::unique_ptr<SimpleOperation> &processor : processors) {
processor->evaluate();
}
}
}
void Operation::reset_results()
{
for (Result &result : results_.values()) {
result.reset();
}
}
void Operation::release_inputs()
{
for (Result *result : results_mapped_to_inputs_.values()) {
result->release();
}
}
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,131 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_float3x3.hh"
#include "BLI_math_vec_types.hh"
#include "BLI_utildefines.h"
#include "GPU_shader.h"
#include "GPU_texture.h"
#include "COM_context.hh"
#include "COM_domain.hh"
#include "COM_input_descriptor.hh"
#include "COM_realize_on_domain_operation.hh"
#include "COM_result.hh"
#include "COM_utilities.hh"
namespace blender::realtime_compositor {
RealizeOnDomainOperation::RealizeOnDomainOperation(Context &context,
Domain domain,
ResultType type)
: SimpleOperation(context), domain_(domain)
{
InputDescriptor input_descriptor;
input_descriptor.type = type;
declare_input_descriptor(input_descriptor);
populate_result(Result(type, texture_pool()));
}
void RealizeOnDomainOperation::execute()
{
Result &input = get_input();
Result &result = get_result();
result.allocate_texture(domain_);
GPUShader *shader = get_realization_shader();
GPU_shader_bind(shader);
/* Transform the input space into the domain space. */
const float3x3 local_transformation = input.domain().transformation *
domain_.transformation.inverted();
/* Set the origin of the transformation to be the center of the domain. */
const float3x3 transformation = float3x3::from_origin_transformation(
local_transformation, float2(domain_.size) / 2.0f);
/* Invert the transformation because the shader transforms the domain coordinates instead of the
* input image itself and thus expect the inverse. */
const float3x3 inverse_transformation = transformation.inverted();
/* Set the inverse of the transform to the shader. */
GPU_shader_uniform_mat3_as_mat4(shader, "inverse_transformation", inverse_transformation.ptr());
/* The texture sampler should use bilinear interpolation for both the bilinear and bicubic
* cases, as the logic used by the bicubic realization shader expects textures to use bilinear
* interpolation. */
const bool use_bilinear = ELEM(input.get_realization_options().interpolation,
Interpolation::Bilinear,
Interpolation::Bicubic);
GPU_texture_filter_mode(input.texture(), use_bilinear);
/* Make out-of-bound texture access return zero by clamping to border color. And make texture
* wrap appropriately if the input repeats. */
const bool repeats = input.get_realization_options().repeat_x ||
input.get_realization_options().repeat_y;
GPU_texture_wrap_mode(input.texture(), repeats, false);
input.bind_as_texture(shader, "input_sampler");
result.bind_as_image(shader, "domain");
compute_dispatch_global(shader, domain_.size);
input.unbind_as_texture();
result.unbind_as_image();
GPU_shader_unbind();
}
GPUShader *RealizeOnDomainOperation::get_realization_shader()
{
switch (get_result().type()) {
case ResultType::Color:
return shader_pool().acquire("compositor_realize_on_domain_color");
case ResultType::Vector:
return shader_pool().acquire("compositor_realize_on_domain_vector");
case ResultType::Float:
return shader_pool().acquire("compositor_realize_on_domain_float");
}
BLI_assert_unreachable();
return nullptr;
}
Domain RealizeOnDomainOperation::compute_domain()
{
return domain_;
}
SimpleOperation *RealizeOnDomainOperation::construct_if_needed(
Context &context,
const Result &input_result,
const InputDescriptor &input_descriptor,
const Domain &operation_domain)
{
/* This input wants to skip realization, the operation is not needed. */
if (input_descriptor.skip_realization) {
return nullptr;
}
/* The input expects a single value and if no single value is provided, it will be ignored and a
* default value will be used, so no need to realize it and the operation is not needed. */
if (input_descriptor.expects_single_value) {
return nullptr;
}
/* Input result is a single value and does not need realization, the operation is not needed. */
if (input_result.is_single_value()) {
return nullptr;
}
/* The input have an identical domain to the operation domain, so no need to realize it and the
* operation is not needed. */
if (input_result.domain() == operation_domain) {
return nullptr;
}
/* Otherwise, realization is needed. */
return new RealizeOnDomainOperation(context, operation_domain, input_descriptor.type);
}
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,68 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "GPU_state.h"
#include "GPU_texture.h"
#include "MEM_guardedalloc.h"
#include "COM_context.hh"
#include "COM_input_descriptor.hh"
#include "COM_reduce_to_single_value_operation.hh"
#include "COM_result.hh"
namespace blender::realtime_compositor {
ReduceToSingleValueOperation::ReduceToSingleValueOperation(Context &context, ResultType type)
: SimpleOperation(context)
{
InputDescriptor input_descriptor;
input_descriptor.type = type;
declare_input_descriptor(input_descriptor);
populate_result(Result(type, texture_pool()));
}
void ReduceToSingleValueOperation::execute()
{
/* Download the input pixel from the GPU texture. */
const Result &input = get_input();
GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE);
float *pixel = static_cast<float *>(GPU_texture_read(input.texture(), GPU_DATA_FLOAT, 0));
/* Allocate a single value result and set its value to the value of the downloaded pixel. */
Result &result = get_result();
result.allocate_single_value();
switch (result.type()) {
case ResultType::Color:
result.set_color_value(pixel);
break;
case ResultType::Vector:
result.set_vector_value(pixel);
break;
case ResultType::Float:
result.set_float_value(*pixel);
break;
}
/* Free the downloaded pixel. */
MEM_freeN(pixel);
}
SimpleOperation *ReduceToSingleValueOperation::construct_if_needed(Context &context,
const Result &input_result)
{
/* Input result is already a single value, the operation is not needed. */
if (input_result.is_single_value()) {
return nullptr;
}
/* The input is a full sized texture and can't be reduced to a single value, the operation is not
* needed. */
if (input_result.domain().size != int2(1)) {
return nullptr;
}
/* The input is a texture of a single pixel and can be reduced to a single value. */
return new ReduceToSingleValueOperation(context, input_result.type());
}
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,258 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_float3x3.hh"
#include "BLI_math_vec_types.hh"
#include "GPU_shader.h"
#include "GPU_state.h"
#include "GPU_texture.h"
#include "COM_domain.hh"
#include "COM_result.hh"
#include "COM_texture_pool.hh"
namespace blender::realtime_compositor {
Result::Result(ResultType type, TexturePool &texture_pool)
: type_(type), texture_pool_(&texture_pool)
{
}
void Result::allocate_texture(Domain domain)
{
is_single_value_ = false;
switch (type_) {
case ResultType::Float:
texture_ = texture_pool_->acquire_float(domain.size);
break;
case ResultType::Vector:
texture_ = texture_pool_->acquire_vector(domain.size);
break;
case ResultType::Color:
texture_ = texture_pool_->acquire_color(domain.size);
break;
}
domain_ = domain;
}
void Result::allocate_single_value()
{
is_single_value_ = true;
/* Single values are stored in 1x1 textures as well as the single value members. */
const int2 texture_size{1, 1};
switch (type_) {
case ResultType::Float:
texture_ = texture_pool_->acquire_float(texture_size);
break;
case ResultType::Vector:
texture_ = texture_pool_->acquire_vector(texture_size);
break;
case ResultType::Color:
texture_ = texture_pool_->acquire_color(texture_size);
break;
}
domain_ = Domain::identity();
}
void Result::allocate_invalid()
{
allocate_single_value();
switch (type_) {
case ResultType::Float:
set_float_value(0.0f);
break;
case ResultType::Vector:
set_vector_value(float3(0.0f));
break;
case ResultType::Color:
set_color_value(float4(0.0f));
break;
}
}
void Result::bind_as_texture(GPUShader *shader, const char *texture_name) const
{
/* Make sure any prior writes to the texture are reflected before reading from it. */
GPU_memory_barrier(GPU_BARRIER_TEXTURE_FETCH);
const int texture_image_unit = GPU_shader_get_texture_binding(shader, texture_name);
GPU_texture_bind(texture_, texture_image_unit);
}
void Result::bind_as_image(GPUShader *shader, const char *image_name) const
{
const int image_unit = GPU_shader_get_texture_binding(shader, image_name);
GPU_texture_image_bind(texture_, image_unit);
}
void Result::unbind_as_texture() const
{
GPU_texture_unbind(texture_);
}
void Result::unbind_as_image() const
{
GPU_texture_image_unbind(texture_);
}
void Result::pass_through(Result &target)
{
/* Increment the reference count of the master by the original reference count of the target. */
increment_reference_count(target.reference_count());
/* Make the target an exact copy of this result, but keep the initial reference count, as this is
* a property of the original result and is needed for correctly resetting the result before the
* next evaluation. */
const int initial_reference_count = target.initial_reference_count_;
target = *this;
target.initial_reference_count_ = initial_reference_count;
/* Set the master of the target to be this result. */
target.master_ = this;
}
void Result::transform(const float3x3 &transformation)
{
domain_.transform(transformation);
}
RealizationOptions &Result::get_realization_options()
{
return domain_.realization_options;
}
float Result::get_float_value() const
{
return float_value_;
}
float3 Result::get_vector_value() const
{
return vector_value_;
}
float4 Result::get_color_value() const
{
return color_value_;
}
float Result::get_float_value_default(float default_value) const
{
if (is_single_value()) {
return get_float_value();
}
return default_value;
}
float3 Result::get_vector_value_default(const float3 &default_value) const
{
if (is_single_value()) {
return get_vector_value();
}
return default_value;
}
float4 Result::get_color_value_default(const float4 &default_value) const
{
if (is_single_value()) {
return get_color_value();
}
return default_value;
}
void Result::set_float_value(float value)
{
float_value_ = value;
GPU_texture_update(texture_, GPU_DATA_FLOAT, &float_value_);
}
void Result::set_vector_value(const float3 &value)
{
vector_value_ = value;
GPU_texture_update(texture_, GPU_DATA_FLOAT, vector_value_);
}
void Result::set_color_value(const float4 &value)
{
color_value_ = value;
GPU_texture_update(texture_, GPU_DATA_FLOAT, color_value_);
}
void Result::set_initial_reference_count(int count)
{
initial_reference_count_ = count;
}
void Result::reset()
{
master_ = nullptr;
reference_count_ = initial_reference_count_;
}
void Result::increment_reference_count(int count)
{
/* If there is a master result, increment its reference count instead. */
if (master_) {
master_->increment_reference_count(count);
return;
}
reference_count_ += count;
}
void Result::release()
{
/* If there is a master result, release it instead. */
if (master_) {
master_->release();
return;
}
/* Decrement the reference count, and if it reaches zero, release the texture back into the
* texture pool. */
reference_count_--;
if (reference_count_ == 0) {
texture_pool_->release(texture_);
}
}
bool Result::should_compute()
{
return initial_reference_count_ != 0;
}
ResultType Result::type() const
{
return type_;
}
bool Result::is_texture() const
{
return !is_single_value_;
}
bool Result::is_single_value() const
{
return is_single_value_;
}
GPUTexture *Result::texture() const
{
return texture_;
}
int Result::reference_count() const
{
/* If there is a master result, return its reference count instead. */
if (master_) {
return master_->reference_count();
}
return reference_count_;
}
const Domain &Result::domain() const
{
return domain_;
}
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,315 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_map.hh"
#include "BLI_set.hh"
#include "BLI_stack.hh"
#include "BLI_vector.hh"
#include "BLI_vector_set.hh"
#include "NOD_derived_node_tree.hh"
#include "COM_scheduler.hh"
#include "COM_utilities.hh"
namespace blender::realtime_compositor {
using namespace nodes::derived_node_tree_types;
/* Compute the output node whose result should be computed. The output node is the node marked as
* NODE_DO_OUTPUT. If multiple types of output nodes are marked, then the preference will be
* CMP_NODE_COMPOSITE > CMP_NODE_VIEWER > CMP_NODE_SPLITVIEWER. If no output node exists, a null
* node will be returned. */
static DNode compute_output_node(DerivedNodeTree &tree)
{
/* Get the top most node tree reference from the derived node tree. */
const NodeTreeRef &root_tree = tree.root_context().tree();
/* First search over composite nodes. */
for (const NodeRef *node : root_tree.nodes_by_type("CompositorNodeComposite")) {
if (node->bnode()->flag & NODE_DO_OUTPUT) {
return DNode(&tree.root_context(), node);
}
}
/* Then search over viewer nodes. */
for (const NodeRef *node : root_tree.nodes_by_type("CompositorNodeViewer")) {
if (node->bnode()->flag & NODE_DO_OUTPUT) {
return DNode(&tree.root_context(), node);
}
}
/* Finally search over split viewer nodes. */
for (const NodeRef *node : root_tree.nodes_by_type("CompositorNodeSplitViewer")) {
if (node->bnode()->flag & NODE_DO_OUTPUT) {
return DNode(&tree.root_context(), node);
}
}
/* No output node found, return a null node. */
return DNode();
}
/* A type representing a mapping that associates each node with a heuristic estimation of the
* number of intermediate buffers needed to compute it and all of its dependencies. See the
* compute_number_of_needed_buffers function for more information. */
using NeededBuffers = Map<DNode, int>;
/* Compute a heuristic estimation of the number of intermediate buffers needed to compute each node
* and all of its dependencies for all nodes that the given node depends on. The output is a map
* that maps each node with the number of intermediate buffers needed to compute it and all of its
* dependencies.
*
* Consider a node that takes n number of buffers as an input from a number of node dependencies,
* which we shall call the input nodes. The node also computes and outputs m number of buffers.
* In order for the node to compute its output, a number of intermediate buffers will be needed.
* Since the node takes n buffers and outputs m buffers, then the number of buffers directly
* needed by the node is (n + m). But each of the input buffers are computed by a node that, in
* turn, needs a number of buffers to compute its output. So the total number of buffers needed
* to compute the output of the node is max(n + m, d) where d is the number of buffers needed by
* the input node that needs the largest number of buffers. We only consider the input node that
* needs the largest number of buffers, because those buffers can be reused by any input node
* that needs a lesser number of buffers.
*
* Shader nodes, however, are a special case because links between two shader nodes inside the same
* shader operation don't pass a buffer, but a single value in the compiled shader. So for shader
* nodes, only inputs and outputs linked to nodes that are not shader nodes should be considered.
* Note that this might not actually be true, because the compiler may decide to split a shader
* operation into multiples ones that will pass buffers, but this is not something that can be
* known at scheduling-time. See the discussion in COM_compile_state.hh, COM_evaluator.hh, and
* COM_shader_operation.hh for more information. In the node tree shown below, node 4 will have
* exactly the same number of needed buffers by node 3, because its inputs and outputs are all
* internally linked in the shader operation.
*
* Shader Operation
* +------------------------------------------------------+
* .------------. | .------------. .------------. .------------. | .------------.
* | Node 1 | | | Node 3 | | Node 4 | | Node 5 | | | Node 6 |
* | |----|--| |--| |------| |--|--| |
* | | .-|--| | | | .---| | | | |
* '------------' | | '------------' '------------' | '------------' | '------------'
* | +----------------------------------|-------------------+
* .------------. | |
* | Node 2 | | |
* | |--'------------------------------------'
* | |
* '------------'
*
* Note that the computed output is not guaranteed to be accurate, and will not be in most cases.
* The computation is merely a heuristic estimation that works well in most cases. This is due to a
* number of reasons:
* - The node tree is actually a graph that allows output sharing, which is not something that was
* taken into consideration in this implementation because it is difficult to correctly consider.
* - Each node may allocate any number of internal buffers, which is not taken into account in this
* implementation because it rarely affects the output and is done by very few nodes.
* - The compiler may decide to compiler the schedule differently depending on runtime information
* which we can merely speculate at scheduling-time as described above. */
static NeededBuffers compute_number_of_needed_buffers(DNode output_node)
{
NeededBuffers needed_buffers;
/* A stack of nodes used to traverse the node tree starting from the output node. */
Stack<DNode> node_stack = {output_node};
/* Traverse the node tree in a post order depth first manner and compute the number of needed
* buffers for each node. Post order traversal guarantee that all the node dependencies of each
* node are computed before it. This is done by pushing all the uncomputed node dependencies to
* the node stack first and only popping and computing the node when all its node dependencies
* were computed. */
while (!node_stack.is_empty()) {
/* Do not pop the node immediately, as it may turn out that we can't compute its number of
* needed buffers just yet because its dependencies weren't computed, it will be popped later
* when needed. */
DNode &node = node_stack.peek();
/* Go over the node dependencies connected to the inputs of the node and push them to the node
* stack if they were not computed already. */
Set<DNode> pushed_nodes;
for (const InputSocketRef *input_ref : node->inputs()) {
const DInputSocket input{node.context(), input_ref};
/* Get the output linked to the input. If it is null, that means the input is unlinked and
* has no dependency node. */
const DOutputSocket output = get_output_linked_to_input(input);
if (!output) {
continue;
}
/* The node dependency was already computed or pushed before, so skip it. */
if (needed_buffers.contains(output.node()) || pushed_nodes.contains(output.node())) {
continue;
}
/* The output node needs to be computed, push the node dependency to the node stack and
* indicate that it was pushed. */
node_stack.push(output.node());
pushed_nodes.add_new(output.node());
}
/* If any of the node dependencies were pushed, that means that not all of them were computed
* and consequently we can't compute the number of needed buffers for this node just yet. */
if (!pushed_nodes.is_empty()) {
continue;
}
/* We don't need to store the result of the pop because we already peeked at it before. */
node_stack.pop();
/* Compute the number of buffers that the node takes as an input as well as the number of
* buffers needed to compute the most demanding of the node dependencies. */
int number_of_input_buffers = 0;
int buffers_needed_by_dependencies = 0;
for (const InputSocketRef *input_ref : node->inputs()) {
const DInputSocket input{node.context(), input_ref};
/* Get the output linked to the input. If it is null, that means the input is unlinked.
* Unlinked inputs do not take a buffer, so skip those inputs. */
const DOutputSocket output = get_output_linked_to_input(input);
if (!output) {
continue;
}
/* Since this input is linked, if the link is not between two shader nodes, it means that the
* node takes a buffer through this input and so we increment the number of input buffers. */
if (!is_shader_node(node) || !is_shader_node(output.node())) {
number_of_input_buffers++;
}
/* If the number of buffers needed by the node dependency is more than the total number of
* buffers needed by the dependencies, then update the latter to be the former. This is
* computing the "d" in the aforementioned equation "max(n + m, d)". */
const int buffers_needed_by_dependency = needed_buffers.lookup(output.node());
if (buffers_needed_by_dependency > buffers_needed_by_dependencies) {
buffers_needed_by_dependencies = buffers_needed_by_dependency;
}
}
/* Compute the number of buffers that will be computed/output by this node. */
int number_of_output_buffers = 0;
for (const OutputSocketRef *output_ref : node->outputs()) {
const DOutputSocket output{node.context(), output_ref};
/* The output is not linked, it outputs no buffer. */
if (output->logically_linked_sockets().is_empty()) {
continue;
}
/* If any of the links is not between two shader nodes, it means that the node outputs
* a buffer through this output and so we increment the number of output buffers. */
if (!is_output_linked_to_node_conditioned(output, is_shader_node) || !is_shader_node(node)) {
number_of_output_buffers++;
}
}
/* Compute the heuristic estimation of the number of needed intermediate buffers to compute
* this node and all of its dependencies. This is computing the aforementioned equation
* "max(n + m, d)". */
const int total_buffers = MAX2(number_of_input_buffers + number_of_output_buffers,
buffers_needed_by_dependencies);
needed_buffers.add(node, total_buffers);
}
return needed_buffers;
}
/* There are multiple different possible orders of evaluating a node graph, each of which needs
* to allocate a number of intermediate buffers to store its intermediate results. It follows
* that we need to find the evaluation order which uses the least amount of intermediate buffers.
* For instance, consider a node that takes two input buffers A and B. Each of those buffers is
* computed through a number of nodes constituting a sub-graph whose root is the node that
* outputs that buffer. Suppose the number of intermediate buffers needed to compute A and B are
* N(A) and N(B) respectively and N(A) > N(B). Then evaluating the sub-graph computing A would be
* a better option than that of B, because had B was computed first, its outputs will need to be
* stored in extra buffers in addition to the buffers needed by A. The number of buffers needed by
* each node is estimated as described in the compute_number_of_needed_buffers function.
*
* This is a heuristic generalization of the SethiUllman algorithm, a generalization that
* doesn't always guarantee an optimal evaluation order, as the optimal evaluation order is very
* difficult to compute, however, this method works well in most cases. Moreover it assumes that
* all buffers will have roughly the same size, which may not always be the case. */
Schedule compute_schedule(DerivedNodeTree &tree)
{
Schedule schedule;
/* Compute the output node whose result should be computed. */
const DNode output_node = compute_output_node(tree);
/* No output node, the node tree has no effect, return an empty schedule. */
if (!output_node) {
return schedule;
}
/* Compute the number of buffers needed by each node connected to the output. */
const NeededBuffers needed_buffers = compute_number_of_needed_buffers(output_node);
/* A stack of nodes used to traverse the node tree starting from the output node. */
Stack<DNode> node_stack = {output_node};
/* Traverse the node tree in a post order depth first manner, scheduling the nodes in an order
* informed by the number of buffers needed by each node. Post order traversal guarantee that all
* the node dependencies of each node are scheduled before it. This is done by pushing all the
* unscheduled node dependencies to the node stack first and only popping and scheduling the node
* when all its node dependencies were scheduled. */
while (!node_stack.is_empty()) {
/* Do not pop the node immediately, as it may turn out that we can't schedule it just yet
* because its dependencies weren't scheduled, it will be popped later when needed. */
DNode &node = node_stack.peek();
/* Compute the nodes directly connected to the node inputs sorted by their needed buffers such
* that the node with the lowest number of needed buffers comes first. Note that we actually
* want the node with the highest number of needed buffers to be schedule first, but since
* those are pushed to the traversal stack, we need to push them in reverse order. */
Vector<DNode> sorted_dependency_nodes;
for (const InputSocketRef *input_ref : node->inputs()) {
const DInputSocket input{node.context(), input_ref};
/* Get the output linked to the input. If it is null, that means the input is unlinked and
* has no dependency node, so skip it. */
const DOutputSocket output = get_output_linked_to_input(input);
if (!output) {
continue;
}
/* The dependency node was added before, so skip it. The number of dependency nodes is very
* small, typically less than 3, so a linear search is okay. */
if (sorted_dependency_nodes.contains(output.node())) {
continue;
}
/* The dependency node was already schedule, so skip it. */
if (schedule.contains(output.node())) {
continue;
}
/* Sort in ascending order on insertion, the number of dependency nodes is very small,
* typically less than 3, so insertion sort is okay. */
int insertion_position = 0;
for (int i = 0; i < sorted_dependency_nodes.size(); i++) {
if (needed_buffers.lookup(output.node()) >
needed_buffers.lookup(sorted_dependency_nodes[i])) {
insertion_position++;
}
else {
break;
}
}
sorted_dependency_nodes.insert(insertion_position, output.node());
}
/* Push the sorted dependency nodes to the node stack in order. */
for (const DNode &dependency_node : sorted_dependency_nodes) {
node_stack.push(dependency_node);
}
/* If there are no sorted dependency nodes, that means they were all already scheduled or that
* none exists in the first place, so we can pop and schedule the node now. */
if (sorted_dependency_nodes.is_empty()) {
/* The node might have already been scheduled, so we don't use add_new here and simply don't
* add it if it was already scheduled. */
schedule.add(node_stack.pop());
}
}
return schedule;
}
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,132 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_assert.h"
#include "BLI_math_vector.h"
#include "DNA_node_types.h"
#include "NOD_derived_node_tree.hh"
#include "GPU_material.h"
#include "COM_shader_node.hh"
#include "COM_utilities.hh"
namespace blender::realtime_compositor {
using namespace nodes::derived_node_tree_types;
ShaderNode::ShaderNode(DNode node) : node_(node)
{
populate_inputs();
populate_outputs();
}
GPUNodeStack *ShaderNode::get_inputs_array()
{
return inputs_.data();
}
GPUNodeStack *ShaderNode::get_outputs_array()
{
return outputs_.data();
}
const DNode &ShaderNode::node() const
{
return node_;
}
bNode &ShaderNode::bnode() const
{
return *node_->bnode();
}
static eGPUType gpu_type_from_socket_type(eNodeSocketDatatype type)
{
switch (type) {
case SOCK_FLOAT:
return GPU_FLOAT;
case SOCK_VECTOR:
return GPU_VEC3;
case SOCK_RGBA:
return GPU_VEC4;
default:
BLI_assert_unreachable();
return GPU_NONE;
}
}
static void gpu_stack_vector_from_socket(float *vector, const SocketRef *socket)
{
switch (socket->bsocket()->type) {
case SOCK_FLOAT:
vector[0] = socket->default_value<bNodeSocketValueFloat>()->value;
return;
case SOCK_VECTOR:
copy_v3_v3(vector, socket->default_value<bNodeSocketValueVector>()->value);
return;
case SOCK_RGBA:
copy_v4_v4(vector, socket->default_value<bNodeSocketValueRGBA>()->value);
return;
default:
BLI_assert_unreachable();
}
}
static void populate_gpu_node_stack(DSocket socket, GPUNodeStack &stack)
{
/* Make sure this stack is not marked as the end of the stack array. */
stack.end = false;
/* This will be initialized later by the GPU material compiler or the compile method. */
stack.link = nullptr;
/* Socket type and its corresponding GPU type. */
stack.sockettype = socket->bsocket()->type;
stack.type = gpu_type_from_socket_type((eNodeSocketDatatype)socket->bsocket()->type);
if (socket->is_input()) {
/* Get the origin socket connected to the input if any. */
const DInputSocket input{socket.context(), &socket->as_input()};
DSocket origin = get_input_origin_socket(input);
/* The input is linked if the origin socket is not null and is an output socket. Had it been an
* input socket, then it is an unlinked input of a group input node. */
stack.hasinput = origin->is_output();
/* Get the socket value from the origin if it is an input, because then it would be an unlinked
* input of a group input node, otherwise, get the value from the socket itself. */
if (origin->is_input()) {
gpu_stack_vector_from_socket(stack.vec, origin.socket_ref());
}
else {
gpu_stack_vector_from_socket(stack.vec, socket.socket_ref());
}
}
else {
stack.hasoutput = socket->is_logically_linked();
}
}
void ShaderNode::populate_inputs()
{
/* Reserve a stack for each input in addition to an extra stack at the end to mark the end of the
* array, as this is what the GPU module functions expect. */
inputs_.resize(node_->inputs().size() + 1);
inputs_.last().end = true;
for (int i = 0; i < node_->inputs().size(); i++) {
populate_gpu_node_stack(node_.input(i), inputs_[i]);
}
}
void ShaderNode::populate_outputs()
{
/* Reserve a stack for each output in addition to an extra stack at the end to mark the end of
* the array, as this is what the GPU module functions expect. */
outputs_.resize(node_->outputs().size() + 1);
outputs_.last().end = true;
for (int i = 0; i < node_->outputs().size(); i++) {
populate_gpu_node_stack(node_.output(i), outputs_[i]);
}
}
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,345 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <memory>
#include "BLI_listbase.h"
#include "BLI_string_ref.hh"
#include "BLI_utildefines.h"
#include "GPU_material.h"
#include "GPU_shader.h"
#include "GPU_texture.h"
#include "GPU_uniform_buffer.h"
#include "gpu_shader_create_info.hh"
#include "NOD_derived_node_tree.hh"
#include "NOD_node_declaration.hh"
#include "COM_context.hh"
#include "COM_operation.hh"
#include "COM_result.hh"
#include "COM_scheduler.hh"
#include "COM_shader_node.hh"
#include "COM_shader_operation.hh"
#include "COM_utilities.hh"
namespace blender::realtime_compositor {
using namespace nodes::derived_node_tree_types;
ShaderOperation::ShaderOperation(Context &context, ShaderCompileUnit &compile_unit)
: Operation(context), compile_unit_(compile_unit)
{
material_ = GPU_material_from_callbacks(
&setup_material, &compile_material, &generate_material, this);
GPU_material_status_set(material_, GPU_MAT_QUEUED);
GPU_material_compile(material_);
}
ShaderOperation::~ShaderOperation()
{
GPU_material_free_single(material_);
}
void ShaderOperation::execute()
{
/* Allocate all the outputs of the operation on its computed domain. */
const Domain domain = compute_domain();
for (StringRef identifier : output_sockets_to_output_identifiers_map_.values()) {
Result &result = get_result(identifier);
result.allocate_texture(domain);
}
GPUShader *shader = GPU_material_get_shader(material_);
GPU_shader_bind(shader);
bind_material_resources(shader);
bind_inputs(shader);
bind_outputs(shader);
compute_dispatch_global(shader, domain.size);
GPU_texture_unbind_all();
GPU_texture_image_unbind_all();
GPU_uniformbuf_unbind_all();
GPU_shader_unbind();
}
StringRef ShaderOperation::get_output_identifier_from_output_socket(DOutputSocket output)
{
return output_sockets_to_output_identifiers_map_.lookup(output);
}
InputsToLinkedOutputsMap &ShaderOperation::get_inputs_to_linked_outputs_map()
{
return inputs_to_linked_outputs_map_;
}
void ShaderOperation::compute_results_reference_counts(const Schedule &schedule)
{
for (const OutputSocketsToOutputIdentifiersMap::Item &item :
output_sockets_to_output_identifiers_map_.items()) {
const int reference_count = number_of_inputs_linked_to_output_conditioned(
item.key, [&](DInputSocket input) { return schedule.contains(input.node()); });
get_result(item.value).set_initial_reference_count(reference_count);
}
}
void ShaderOperation::bind_material_resources(GPUShader *shader)
{
/* Bind the uniform buffer of the material if it exists. It may not exist if the GPU material has
* no uniforms. */
GPUUniformBuf *ubo = GPU_material_uniform_buffer_get(material_);
if (ubo) {
GPU_uniformbuf_bind(ubo, GPU_shader_get_uniform_block_binding(shader, GPU_UBO_BLOCK_NAME));
}
/* Bind color band textures needed by the material. */
ListBase textures = GPU_material_textures(material_);
LISTBASE_FOREACH (GPUMaterialTexture *, texture, &textures) {
if (texture->colorband) {
const int texture_image_unit = GPU_shader_get_texture_binding(shader, texture->sampler_name);
GPU_texture_bind(*texture->colorband, texture_image_unit);
}
}
}
void ShaderOperation::bind_inputs(GPUShader *shader)
{
for (const GPUMaterialTexture *material_texture : output_to_material_texture_map_.values()) {
const char *sampler_name = material_texture->sampler_name;
get_input(sampler_name).bind_as_texture(shader, sampler_name);
}
}
void ShaderOperation::bind_outputs(GPUShader *shader)
{
ListBase images = GPU_material_images(material_);
LISTBASE_FOREACH (GPUMaterialImage *, image, &images) {
get_result(image->name_in_shader).bind_as_image(shader, image->name_in_shader);
}
}
void ShaderOperation::setup_material(void *UNUSED(thunk), GPUMaterial *material)
{
GPU_material_is_compute_set(material, true);
}
void ShaderOperation::compile_material(void *thunk, GPUMaterial *material)
{
ShaderOperation *operation = static_cast<ShaderOperation *>(thunk);
for (DNode node : operation->compile_unit_) {
/* Instantiate a shader node for the node and add it to the shader_nodes_ map. */
ShaderNode *shader_node = node->typeinfo()->get_compositor_shader_node(node);
operation->shader_nodes_.add_new(node, std::unique_ptr<ShaderNode>(shader_node));
/* Link the inputs of the shader node if needed. */
operation->link_node_inputs(node, material);
/* Compile the node itself. */
shader_node->compile(material);
/* Populate the output results for the shader node if needed. */
operation->populate_results_for_node(node, material);
}
}
void ShaderOperation::link_node_inputs(DNode node, GPUMaterial *material)
{
for (const InputSocketRef *input_ref : node->inputs()) {
const DInputSocket input{node.context(), input_ref};
/* Get the output linked to the input. If it is null, that means the input is unlinked.
* Unlinked inputs are linked by the node compile method, so skip this here. */
const DOutputSocket output = get_output_linked_to_input(input);
if (!output) {
continue;
}
/* If the origin node is part of the shader operation, then just map the output stack link to
* the input stack link. */
if (compile_unit_.contains(output.node())) {
map_node_input(input, output);
continue;
}
/* Otherwise, the origin node is not part of the shader operation, so an input to the shader
* operation must be declared if it wasn't declared for the same output already. */
declare_operation_input_if_needed(input, output, material);
/* Link the input to an input loader GPU material node sampling the result of the output. */
link_material_input_loader(input, output, material);
}
}
void ShaderOperation::map_node_input(DInputSocket input, DOutputSocket output)
{
/* Get the GPU node stack of the output. */
ShaderNode &output_node = *shader_nodes_.lookup(output.node());
GPUNodeStack &output_stack = output_node.get_outputs_array()[output->index()];
/* Get the GPU node stack of the input. */
ShaderNode &input_node = *shader_nodes_.lookup(input.node());
GPUNodeStack &input_stack = input_node.get_inputs_array()[input->index()];
/* Map the output link to the input link. */
input_stack.link = output_stack.link;
}
static const char *get_load_function_name(DInputSocket input)
{
switch (input->bsocket()->type) {
case SOCK_FLOAT:
return "load_input_float";
case SOCK_VECTOR:
return "load_input_vector";
case SOCK_RGBA:
return "load_input_color";
default:
BLI_assert_unreachable();
return "";
}
}
void ShaderOperation::declare_operation_input_if_needed(DInputSocket input,
DOutputSocket output,
GPUMaterial *material)
{
/* An input was already declared for that same output, so no need to declare it again. */
if (output_to_material_texture_map_.contains(output)) {
return;
}
/* Add a new material texture to the GPU material. */
GPUMaterialTexture *material_texture = GPU_material_add_texture(material, GPU_SAMPLER_DEFAULT);
/* Map the output socket to the material texture that was created for it. */
output_to_material_texture_map_.add(output, material_texture);
/* Declare the input descriptor using the name of the sampler in the shader as the identifier. */
StringRef identifier = material_texture->sampler_name;
const InputDescriptor input_descriptor = input_descriptor_from_input_socket(input.socket_ref());
declare_input_descriptor(identifier, input_descriptor);
/* Map the operation input to the output socket it is linked to. */
inputs_to_linked_outputs_map_.add_new(identifier, output);
}
void ShaderOperation::link_material_input_loader(DInputSocket input,
DOutputSocket output,
GPUMaterial *material)
{
/* Create a link from the material texture that corresponds to the given output. */
GPUMaterialTexture *material_texture = output_to_material_texture_map_.lookup(output);
GPUNodeLink *input_texture_link = GPU_image_from_material_texture(material_texture);
/* Get the node stack of the input. */
ShaderNode &node = *shader_nodes_.lookup(input.node());
GPUNodeStack &stack = node.get_inputs_array()[input->index()];
/* Link the input node stack to an input loader sampling the input texture. */
const char *load_function_name = get_load_function_name(input);
GPU_link(material, load_function_name, input_texture_link, &stack.link);
}
void ShaderOperation::populate_results_for_node(DNode node, GPUMaterial *material)
{
for (const OutputSocketRef *output_ref : node->outputs()) {
const DOutputSocket output{node.context(), output_ref};
/* If any of the nodes linked to the output are not part of the shader operation, then an
* output result needs to be populated. */
const bool need_to_populate_result = is_output_linked_to_node_conditioned(
output, [&](DNode node) { return !compile_unit_.contains(node); });
if (need_to_populate_result) {
populate_operation_result(output, material);
}
}
}
static const char *get_store_function_name(ResultType type)
{
switch (type) {
case ResultType::Float:
return "store_output_float";
case ResultType::Vector:
return "store_output_vector";
case ResultType::Color:
return "store_output_color";
}
BLI_assert_unreachable();
return nullptr;
}
static eGPUTextureFormat texture_format_from_result_type(ResultType type)
{
switch (type) {
case ResultType::Float:
return GPU_R16F;
case ResultType::Vector:
return GPU_RGBA16F;
case ResultType::Color:
return GPU_RGBA16F;
}
BLI_assert_unreachable();
return GPU_RGBA16F;
}
void ShaderOperation::populate_operation_result(DOutputSocket output, GPUMaterial *material)
{
/* Construct a result of an appropriate type. */
const ResultType result_type = get_node_socket_result_type(output.socket_ref());
const Result result = Result(result_type, texture_pool());
/* Add a new material image to the GPU material. */
const eGPUTextureFormat format = texture_format_from_result_type(result_type);
GPUMaterialImage *material_image = GPU_material_add_image_texture(material, format);
/* Add the result using the name of the image in the shader as the identifier. */
StringRef identifier = material_image->name_in_shader;
populate_result(identifier, result);
/* Map the output socket to the identifier of the result. */
output_sockets_to_output_identifiers_map_.add_new(output, identifier);
/* Create a link from the material image that corresponds to the given output. */
GPUNodeLink *output_image_link = GPU_image_texture_from_material_image(material_image);
/* Get the node stack of the output. */
ShaderNode &node = *shader_nodes_.lookup(output.node());
GPUNodeLink *output_link = node.get_outputs_array()[output->index()].link;
/* Link the output node stack to an output storer storing in the newly added image. */
const char *store_function_name = get_store_function_name(result_type);
GPU_link(material, store_function_name, output_image_link, output_link);
}
void ShaderOperation::generate_material(void *UNUSED(thunk),
GPUMaterial *UNUSED(material),
GPUCodegenOutput *code_generator_output)
{
gpu::shader::ShaderCreateInfo &info = *reinterpret_cast<gpu::shader::ShaderCreateInfo *>(
code_generator_output->create_info);
/* The GPU material adds resources without explicit locations, so make sure it is done by the
* shader creator. */
info.auto_resource_location(true);
info.local_group_size(16, 16);
/* Add implementation for implicit conversion operations inserted by the code generator. */
info.typedef_source("gpu_shader_compositor_type_conversion.glsl");
/* Add the compute source code of the generator in a main function and set it as the generated
* compute source of the shader create info. */
std::string source = "void main()\n{\n" + std::string(code_generator_output->compute) + "}\n";
info.compute_source_generated = source;
}
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,24 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "GPU_shader.h"
#include "COM_shader_pool.hh"
namespace blender::realtime_compositor {
ShaderPool::~ShaderPool()
{
for (GPUShader *shader : shaders_.values()) {
GPU_shader_free(shader);
}
}
GPUShader *ShaderPool::acquire(const char *info_name)
{
/* If a shader with the same info name already exists in the pool, return it, otherwise, create a
* new shader from the info name and return it. */
return shaders_.lookup_or_add_cb(
info_name, [info_name]() { return GPU_shader_create_from_info_name(info_name); });
}
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,54 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "COM_input_descriptor.hh"
#include "COM_operation.hh"
#include "COM_result.hh"
#include "COM_simple_operation.hh"
namespace blender::realtime_compositor {
const StringRef SimpleOperation::input_identifier_ = StringRef("Input");
const StringRef SimpleOperation::output_identifier_ = StringRef("Output");
Result &SimpleOperation::get_result()
{
return Operation::get_result(output_identifier_);
}
void SimpleOperation::map_input_to_result(Result *result)
{
Operation::map_input_to_result(input_identifier_, result);
}
void SimpleOperation::add_and_evaluate_input_processors()
{
}
Result &SimpleOperation::get_input()
{
return Operation::get_input(input_identifier_);
}
void SimpleOperation::switch_result_mapped_to_input(Result *result)
{
Operation::switch_result_mapped_to_input(input_identifier_, result);
}
void SimpleOperation::populate_result(Result result)
{
Operation::populate_result(output_identifier_, result);
/* The result of a simple operation is guaranteed to have a single user. */
get_result().set_initial_reference_count(1);
}
void SimpleOperation::declare_input_descriptor(InputDescriptor descriptor)
{
Operation::declare_input_descriptor(input_identifier_, descriptor);
}
InputDescriptor &SimpleOperation::get_input_descriptor()
{
return Operation::get_input_descriptor(input_identifier_);
}
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,84 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <cstdint>
#include "BLI_hash.hh"
#include "BLI_map.hh"
#include "BLI_math_vec_types.hh"
#include "BLI_vector.hh"
#include "GPU_texture.h"
#include "COM_texture_pool.hh"
namespace blender::realtime_compositor {
/* --------------------------------------------------------------------
* Texture Pool Key.
*/
TexturePoolKey::TexturePoolKey(int2 size, eGPUTextureFormat format) : size(size), format(format)
{
}
TexturePoolKey::TexturePoolKey(const GPUTexture *texture)
{
size = int2(GPU_texture_width(texture), GPU_texture_height(texture));
format = GPU_texture_format(texture);
}
uint64_t TexturePoolKey::hash() const
{
return get_default_hash_3(size.x, size.y, format);
}
bool operator==(const TexturePoolKey &a, const TexturePoolKey &b)
{
return a.size == b.size && a.format == b.format;
}
/* --------------------------------------------------------------------
* Texture Pool.
*/
GPUTexture *TexturePool::acquire(int2 size, eGPUTextureFormat format)
{
/* Check if there is an available texture with the required specification, and if one exists,
* return it. */
const TexturePoolKey key = TexturePoolKey(size, format);
Vector<GPUTexture *> &available_textures = textures_.lookup_or_add_default(key);
if (!available_textures.is_empty()) {
return available_textures.pop_last();
}
/* Otherwise, allocate a new texture. */
return allocate_texture(size, format);
}
GPUTexture *TexturePool::acquire_color(int2 size)
{
return acquire(size, GPU_RGBA16F);
}
GPUTexture *TexturePool::acquire_vector(int2 size)
{
/* Vectors are stored in RGBA textures because RGB textures have limited support. */
return acquire(size, GPU_RGBA16F);
}
GPUTexture *TexturePool::acquire_float(int2 size)
{
return acquire(size, GPU_R16F);
}
void TexturePool::release(GPUTexture *texture)
{
textures_.lookup(TexturePoolKey(texture)).append(texture);
}
void TexturePool::reset()
{
textures_.clear();
}
} // namespace blender::realtime_compositor

View File

@@ -0,0 +1,134 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_assert.h"
#include "BLI_function_ref.hh"
#include "BLI_math_vec_types.hh"
#include "BLI_utildefines.h"
#include "DNA_node_types.h"
#include "NOD_derived_node_tree.hh"
#include "NOD_node_declaration.hh"
#include "GPU_compute.h"
#include "GPU_shader.h"
#include "COM_operation.hh"
#include "COM_result.hh"
#include "COM_utilities.hh"
namespace blender::realtime_compositor {
using namespace nodes::derived_node_tree_types;
using TargetSocketPathInfo = DOutputSocket::TargetSocketPathInfo;
DSocket get_input_origin_socket(DInputSocket input)
{
/* The input is unlinked. Return the socket itself. */
if (input->logically_linked_sockets().is_empty()) {
return input;
}
/* Only a single origin socket is guaranteed to exist. */
DSocket socket;
input.foreach_origin_socket([&](const DSocket origin) { socket = origin; });
return socket;
}
DOutputSocket get_output_linked_to_input(DInputSocket input)
{
/* Get the origin socket of this input, which will be an output socket if the input is linked
* to an output. */
const DSocket origin = get_input_origin_socket(input);
/* If the origin socket is an input, that means the input is unlinked, return a null output
* socket. */
if (origin->is_input()) {
return DOutputSocket();
}
/* Now that we know the origin is an output, return a derived output from it. */
return DOutputSocket(origin.context(), &origin->as_output());
}
ResultType get_node_socket_result_type(const SocketRef *socket)
{
switch (socket->bsocket()->type) {
case SOCK_FLOAT:
return ResultType::Float;
case SOCK_VECTOR:
return ResultType::Vector;
case SOCK_RGBA:
return ResultType::Color;
default:
BLI_assert_unreachable();
return ResultType::Float;
}
}
bool is_output_linked_to_node_conditioned(DOutputSocket output, FunctionRef<bool(DNode)> condition)
{
bool condition_satisfied = false;
output.foreach_target_socket(
[&](DInputSocket target, const TargetSocketPathInfo &UNUSED(path_info)) {
if (condition(target.node())) {
condition_satisfied = true;
return;
}
});
return condition_satisfied;
}
int number_of_inputs_linked_to_output_conditioned(DOutputSocket output,
FunctionRef<bool(DInputSocket)> condition)
{
int count = 0;
output.foreach_target_socket(
[&](DInputSocket target, const TargetSocketPathInfo &UNUSED(path_info)) {
if (condition(target)) {
count++;
}
});
return count;
}
bool is_shader_node(DNode node)
{
return node->typeinfo()->get_compositor_shader_node;
}
bool is_node_supported(DNode node)
{
return node->typeinfo()->get_compositor_operation ||
node->typeinfo()->get_compositor_shader_node;
}
InputDescriptor input_descriptor_from_input_socket(const InputSocketRef *socket)
{
using namespace nodes;
InputDescriptor input_descriptor;
input_descriptor.type = get_node_socket_result_type(socket);
const NodeDeclaration *node_declaration = socket->node().declaration();
/* Not every node have a declaration, in which case, we assume the default values for the rest of
* the properties. */
if (!node_declaration) {
return input_descriptor;
}
const SocketDeclarationPtr &socket_declaration = node_declaration->inputs()[socket->index()];
input_descriptor.domain_priority = socket_declaration->compositor_domain_priority();
input_descriptor.expects_single_value = socket_declaration->compositor_expects_single_value();
return input_descriptor;
}
void compute_dispatch_global(GPUShader *shader, int2 global_size, int2 local_size)
{
/* If the global size is divisible by the local size, dispatch the number of needed groups, which
* is their division. If it is not divisible, then dispatch an extra group to cover the remaining
* invocations, which means the actual global size of the invocation will be a bit larger than
* the give one. */
const int x = (global_size.x / local_size.x) + (global_size.x % local_size.x != 0);
const int y = (global_size.y / local_size.y) + (global_size.y % local_size.y != 0);
GPU_compute_dispatch(shader, x, y, 1);
}
} // namespace blender::realtime_compositor

View File

@@ -23,6 +23,7 @@ set(INC
../nodes
../render
../render/intern
../compositor/realtime_compositor
../windowmanager
../../../intern/atomic
@@ -104,6 +105,7 @@ set(SRC
intern/smaa_textures.c
engines/basic/basic_engine.c
engines/basic/basic_shader.c
engines/compositor/compositor_engine.cc
engines/image/image_engine.cc
engines/image/image_shader.cc
engines/eevee/eevee_bloom.c
@@ -225,6 +227,7 @@ set(SRC
intern/smaa_textures.h
engines/basic/basic_engine.h
engines/basic/basic_private.h
engines/compositor/compositor_engine.h
engines/eevee/eevee_engine.h
engines/eevee/eevee_lightcache.h
engines/eevee/eevee_lut.h
@@ -256,6 +259,7 @@ set(SRC
set(LIB
bf_blenkernel
bf_blenlib
bf_realtime_compositor
bf_windowmanager
)

View File

@@ -0,0 +1,203 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_listbase.h"
#include "BLI_math_vec_types.hh"
#include "BLI_string_ref.hh"
#include "BLI_utildefines.h"
#include "BLT_translation.h"
#include "DNA_ID_enums.h"
#include "DNA_scene_types.h"
#include "DEG_depsgraph_query.h"
#include "DRW_render.h"
#include "IMB_colormanagement.h"
#include "COM_context.hh"
#include "COM_evaluator.hh"
#include "COM_texture_pool.hh"
#include "GPU_texture.h"
namespace blender::realtime_compositor {
class DRWTexturePool : public TexturePool {
public:
GPUTexture *allocate_texture(int2 size, eGPUTextureFormat format) override
{
DrawEngineType *owner = (DrawEngineType *)this;
return DRW_texture_pool_query_2d(size.x, size.y, format, owner);
}
};
class DRWContext : public Context {
private:
/* A pointer to the info message of the compositor engine. This is a char array of size
* GPU_INFO_SIZE. The message is cleared prior to updating or evaluating the compositor. */
char *info_message_;
public:
DRWContext(TexturePool &texture_pool, char *info_message)
: Context(texture_pool), info_message_(info_message)
{
}
const Scene *get_scene() const override
{
return DRW_context_state_get()->scene;
}
int2 get_viewport_size() override
{
return int2(float2(DRW_viewport_size_get()));
}
GPUTexture *get_viewport_texture() override
{
return DRW_viewport_texture_list_get()->color;
}
GPUTexture *get_pass_texture(int UNUSED(view_layer), eScenePassType UNUSED(pass_type)) override
{
return get_viewport_texture();
}
StringRef get_view_name() override
{
const SceneRenderView *view = static_cast<SceneRenderView *>(
BLI_findlink(&get_scene()->r.views, DRW_context_state_get()->v3d->multiview_eye));
return view->name;
}
void set_info_message(StringRef message) const override
{
message.copy(info_message_, GPU_INFO_SIZE);
}
};
class Engine {
private:
DRWTexturePool texture_pool_;
DRWContext context_;
Evaluator evaluator_;
/* Stores the viewport size at the time the last compositor evaluation happened. See the
* update_viewport_size method for more information. */
int2 last_viewport_size_;
public:
Engine(char *info_message)
: context_(texture_pool_, info_message),
evaluator_(context_, node_tree()),
last_viewport_size_(context_.get_viewport_size())
{
}
/* Update the viewport size and evaluate the compositor. */
void draw()
{
update_viewport_size();
evaluator_.evaluate();
}
/* If the size of the viewport changed from the last time the compositor was evaluated, update
* the viewport size and reset the evaluator. That's because the evaluator compiles the node tree
* in a manner that is specifically optimized for the size of the viewport. This should be called
* before evaluating the compositor. */
void update_viewport_size()
{
if (last_viewport_size_ == context_.get_viewport_size()) {
return;
}
last_viewport_size_ = context_.get_viewport_size();
evaluator_.reset();
}
/* If the compositor node tree changed, reset the evaluator. */
void update(const Depsgraph *depsgraph)
{
if (DEG_id_type_updated(depsgraph, ID_NT)) {
evaluator_.reset();
}
}
/* Get a reference to the compositor node tree. */
static bNodeTree &node_tree()
{
return *DRW_context_state_get()->scene->nodetree;
}
};
} // namespace blender::realtime_compositor
using namespace blender::realtime_compositor;
typedef struct CompositorData {
DrawEngineType *engine_type;
DRWViewportEmptyList *fbl;
DRWViewportEmptyList *txl;
DRWViewportEmptyList *psl;
DRWViewportEmptyList *stl;
Engine *instance_data;
char info[GPU_INFO_SIZE];
} CompositorData;
static void compositor_engine_init(void *data)
{
CompositorData *compositor_data = static_cast<CompositorData *>(data);
if (!compositor_data->instance_data) {
compositor_data->instance_data = new Engine(compositor_data->info);
}
}
static void compositor_engine_free(void *instance_data)
{
Engine *engine = static_cast<Engine *>(instance_data);
delete engine;
}
static void compositor_engine_draw(void *data)
{
const CompositorData *compositor_data = static_cast<CompositorData *>(data);
compositor_data->instance_data->draw();
}
static void compositor_engine_update(void *data)
{
CompositorData *compositor_data = static_cast<CompositorData *>(data);
/* Clear any info message that was set in a previous update. */
compositor_data->info[0] = '\0';
if (compositor_data->instance_data) {
compositor_data->instance_data->update(DRW_context_state_get()->depsgraph);
}
}
extern "C" {
static const DrawEngineDataSize compositor_data_size = DRW_VIEWPORT_DATA_SIZE(CompositorData);
DrawEngineType draw_engine_compositor_type = {
nullptr, /* next */
nullptr, /* prev */
N_("Compositor"), /* idname */
&compositor_data_size, /* vedata_size */
&compositor_engine_init, /* engine_init */
nullptr, /* engine_free */
&compositor_engine_free, /* instance_free */
nullptr, /* cache_init */
nullptr, /* cache_populate */
nullptr, /* cache_finish */
&compositor_engine_draw, /* draw_scene */
&compositor_engine_update, /* view_update */
nullptr, /* id_update */
nullptr, /* render_to_image */
nullptr, /* store_metadata */
};
}

View File

@@ -0,0 +1,13 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
extern DrawEngineType draw_engine_compositor_type;
#ifdef __cplusplus
}
#endif

View File

@@ -43,6 +43,7 @@
#include "DNA_camera_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_userdef_types.h"
#include "DNA_world_types.h"
#include "ED_gpencil.h"
@@ -84,6 +85,7 @@
#include "draw_cache_impl.h"
#include "engines/basic/basic_engine.h"
#include "engines/compositor/compositor_engine.h"
#include "engines/eevee/eevee_engine.h"
#include "engines/eevee_next/eevee_engine.h"
#include "engines/external/external_engine.h"
@@ -1225,6 +1227,31 @@ static void drw_engines_enable_editors(void)
}
}
static bool is_compositor_enabled(void)
{
if (!U.experimental.use_realtime_compositor) {
return false;
}
if (!(DST.draw_ctx.v3d->shading.flag & V3D_SHADING_COMPOSITOR)) {
return false;
}
if (!(DST.draw_ctx.v3d->shading.type > OB_MATERIAL)) {
return false;
}
if (!DST.draw_ctx.scene->use_nodes) {
return false;
}
if (!DST.draw_ctx.scene->nodetree) {
return false;
}
return true;
}
static void drw_engines_enable(ViewLayer *UNUSED(view_layer),
RenderEngineType *engine_type,
bool gpencil_engine_needed)
@@ -1237,6 +1264,11 @@ static void drw_engines_enable(ViewLayer *UNUSED(view_layer),
if (gpencil_engine_needed && ((drawtype >= OB_SOLID) || !use_xray)) {
use_drw_engine(&draw_engine_gpencil_type);
}
if (is_compositor_enabled()) {
use_drw_engine(&draw_engine_compositor_type);
}
drw_engines_enable_overlays();
#ifdef WITH_DRAW_DEBUG
@@ -1608,7 +1640,6 @@ void DRW_draw_render_loop_ex(struct Depsgraph *depsgraph,
GPUViewport *viewport,
const bContext *evil_C)
{
Scene *scene = DEG_get_evaluated_scene(depsgraph);
ViewLayer *view_layer = DEG_get_evaluated_view_layer(depsgraph);
RegionView3D *rv3d = region->regiondata;
@@ -2959,6 +2990,7 @@ void DRW_engines_register(void)
DRW_engine_register(&draw_engine_overlay_type);
DRW_engine_register(&draw_engine_select_type);
DRW_engine_register(&draw_engine_basic_type);
DRW_engine_register(&draw_engine_compositor_type);
#ifdef WITH_DRAW_DEBUG
DRW_engine_register(&draw_engine_debug_select_type);
#endif

View File

@@ -95,20 +95,20 @@ void ED_render_view3d_update(Depsgraph *depsgraph,
CTX_free(C);
}
else {
RenderEngineType *engine_type = ED_view3d_engine_type(scene, v3d->shading.type);
if (updated) {
DRWUpdateContext drw_context = {nullptr};
drw_context.bmain = bmain;
drw_context.depsgraph = depsgraph;
drw_context.scene = scene;
drw_context.view_layer = view_layer;
drw_context.region = region;
drw_context.v3d = v3d;
drw_context.engine_type = engine_type;
DRW_notify_view_update(&drw_context);
}
if (!updated) {
continue;
}
DRWUpdateContext drw_context = {nullptr};
drw_context.bmain = bmain;
drw_context.depsgraph = depsgraph;
drw_context.scene = scene;
drw_context.view_layer = view_layer;
drw_context.region = region;
drw_context.v3d = v3d;
drw_context.engine_type = ED_view3d_engine_type(scene, v3d->shading.type);
DRW_notify_view_update(&drw_context);
}
}

View File

@@ -1210,6 +1210,9 @@ static void view3d_main_region_listener(const wmRegionListenerParams *params)
break;
}
break;
case NC_NODE:
ED_region_tag_redraw(region);
break;
case NC_WORLD:
switch (wmn->data) {
case ND_WORLD_DRAW:

View File

@@ -312,6 +312,46 @@ set(GLSL_SRC
shaders/common/gpu_shader_common_math_utils.glsl
shaders/common/gpu_shader_common_mix_rgb.glsl
shaders/compositor/compositor_alpha_crop.glsl
shaders/compositor/compositor_blur.glsl
shaders/compositor/compositor_box_mask.glsl
shaders/compositor/compositor_convert.glsl
shaders/compositor/compositor_ellipse_mask.glsl
shaders/compositor/compositor_filter.glsl
shaders/compositor/compositor_flip.glsl
shaders/compositor/compositor_image_crop.glsl
shaders/compositor/compositor_projector_lens_distortion.glsl
shaders/compositor/compositor_realize_on_domain.glsl
shaders/compositor/compositor_screen_lens_distortion.glsl
shaders/compositor/compositor_split_viewer.glsl
shaders/compositor/library/gpu_shader_compositor_alpha_over.glsl
shaders/compositor/library/gpu_shader_compositor_bright_contrast.glsl
shaders/compositor/library/gpu_shader_compositor_channel_matte.glsl
shaders/compositor/library/gpu_shader_compositor_chroma_matte.glsl
shaders/compositor/library/gpu_shader_compositor_color_balance.glsl
shaders/compositor/library/gpu_shader_compositor_color_correction.glsl
shaders/compositor/library/gpu_shader_compositor_color_matte.glsl
shaders/compositor/library/gpu_shader_compositor_color_spill.glsl
shaders/compositor/library/gpu_shader_compositor_color_to_luminance.glsl
shaders/compositor/library/gpu_shader_compositor_difference_matte.glsl
shaders/compositor/library/gpu_shader_compositor_distance_matte.glsl
shaders/compositor/library/gpu_shader_compositor_exposure.glsl
shaders/compositor/library/gpu_shader_compositor_gamma.glsl
shaders/compositor/library/gpu_shader_compositor_hue_correct.glsl
shaders/compositor/library/gpu_shader_compositor_hue_saturation_value.glsl
shaders/compositor/library/gpu_shader_compositor_invert.glsl
shaders/compositor/library/gpu_shader_compositor_load_input.glsl
shaders/compositor/library/gpu_shader_compositor_luminance_matte.glsl
shaders/compositor/library/gpu_shader_compositor_map_value.glsl
shaders/compositor/library/gpu_shader_compositor_normal.glsl
shaders/compositor/library/gpu_shader_compositor_posterize.glsl
shaders/compositor/library/gpu_shader_compositor_separate_combine.glsl
shaders/compositor/library/gpu_shader_compositor_set_alpha.glsl
shaders/compositor/library/gpu_shader_compositor_store_output.glsl
shaders/compositor/library/gpu_shader_compositor_texture_utilities.glsl
shaders/compositor/library/gpu_shader_compositor_type_conversion.glsl
shaders/material/gpu_shader_material_add_shader.glsl
shaders/material/gpu_shader_material_ambient_occlusion.glsl
shaders/material/gpu_shader_material_anisotropic.glsl
@@ -511,6 +551,19 @@ set(SRC_SHADER_CREATE_INFOS
shaders/infos/gpu_shader_simple_lighting_info.hh
shaders/infos/gpu_shader_text_info.hh
shaders/infos/gpu_srgb_to_framebuffer_space_info.hh
shaders/compositor/infos/compositor_alpha_crop_info.hh
shaders/compositor/infos/compositor_blur_info.hh
shaders/compositor/infos/compositor_box_mask_info.hh
shaders/compositor/infos/compositor_convert_info.hh
shaders/compositor/infos/compositor_ellipse_mask_info.hh
shaders/compositor/infos/compositor_filter_info.hh
shaders/compositor/infos/compositor_flip_info.hh
shaders/compositor/infos/compositor_image_crop_info.hh
shaders/compositor/infos/compositor_projector_lens_distortion_info.hh
shaders/compositor/infos/compositor_realize_on_domain_info.hh
shaders/compositor/infos/compositor_screen_lens_distortion_info.hh
shaders/compositor/infos/compositor_split_viewer_info.hh
)
set(SHADER_CREATE_INFOS_CONTENT "")

View File

@@ -59,6 +59,7 @@ typedef enum eGPUType {
GPU_TEX2D = 1002,
GPU_TEX2D_ARRAY = 1003,
GPU_TEX3D = 1004,
GPU_IMAGE_2D = 1005,
/* GLSL Struct types */
GPU_CLOSURE = 1007,
@@ -121,6 +122,7 @@ typedef struct GPUCodegenOutput {
char *surface;
char *volume;
char *thickness;
char *compute;
char *material_functions;
GPUShaderCreateInfo *create_info;
@@ -213,6 +215,7 @@ GPUMaterial *GPU_material_from_nodetree(struct Scene *scene,
void *thunk);
void GPU_material_compile(GPUMaterial *mat);
void GPU_material_free_single(GPUMaterial *material);
void GPU_material_free(struct ListBase *gpumaterial);
void GPU_material_acquire(GPUMaterial *mat);
@@ -233,6 +236,9 @@ struct Material *GPU_material_get_material(GPUMaterial *material);
eGPUMaterialStatus GPU_material_status(GPUMaterial *mat);
void GPU_material_status_set(GPUMaterial *mat, eGPUMaterialStatus status);
bool GPU_material_is_compute(GPUMaterial *material);
void GPU_material_is_compute_set(GPUMaterial *material, bool is_compute);
struct GPUUniformBuf *GPU_material_uniform_buffer_get(GPUMaterial *material);
/**
* Create dynamic UBO from parameters
@@ -280,8 +286,16 @@ typedef struct GPUMaterialTexture {
int sampler_state; /* eGPUSamplerState */
} GPUMaterialTexture;
/* A reference to a write only 2D image of a specific format. */
typedef struct GPUMaterialImage {
struct GPUMaterialImage *next, *prev;
eGPUTextureFormat format;
char name_in_shader[32];
} GPUMaterialImage;
ListBase GPU_material_attributes(GPUMaterial *material);
ListBase GPU_material_textures(GPUMaterial *material);
ListBase GPU_material_images(GPUMaterial *material);
typedef struct GPUUniformAttr {
struct GPUUniformAttr *next, *prev;
@@ -308,6 +322,26 @@ struct GHash *GPU_uniform_attr_list_hash_new(const char *info);
void GPU_uniform_attr_list_copy(GPUUniformAttrList *dest, GPUUniformAttrList *src);
void GPU_uniform_attr_list_free(GPUUniformAttrList *set);
GPUMaterialTexture *GPU_material_add_texture(GPUMaterial *material,
eGPUSamplerState sampler_state);
GPUMaterialImage *GPU_material_add_image_texture(GPUMaterial *material, eGPUTextureFormat format);
GPUNodeLink *GPU_image_from_material_texture(GPUMaterialTexture *texture);
GPUNodeLink *GPU_image_texture_from_material_image(GPUMaterialImage *image);
typedef void (*GPUMaterialSetupFn)(void *thunk, GPUMaterial *material);
typedef void (*GPUMaterialCompileFn)(void *thunk, GPUMaterial *material);
/* Construct a GPU material from a set of callbacks. The setup callback should set the appropriate
* flags or members to the material. The compile callback should construct the material graph by
* adding and linking the necessary GPU material graph nodes. The generate function should
* construct the needed shader by initializing the passed shader create info structure. The given
* thunk will be passed as the first parameter of each callback. */
GPUMaterial *GPU_material_from_callbacks(GPUMaterialSetupFn setup_function,
GPUMaterialCompileFn compile_function,
GPUCodegenCallbackFn generate_function,
void *thunk);
#ifdef __cplusplus
}
#endif

View File

@@ -177,7 +177,9 @@ void GPU_shader_uniform_4f(GPUShader *sh, const char *name, float x, float y, fl
void GPU_shader_uniform_2fv(GPUShader *sh, const char *name, const float data[2]);
void GPU_shader_uniform_3fv(GPUShader *sh, const char *name, const float data[3]);
void GPU_shader_uniform_4fv(GPUShader *sh, const char *name, const float data[4]);
void GPU_shader_uniform_2iv(GPUShader *sh, const char *name, const int data[2]);
void GPU_shader_uniform_mat4(GPUShader *sh, const char *name, const float data[4][4]);
void GPU_shader_uniform_mat3_as_mat4(GPUShader *sh, const char *name, const float data[3][3]);
void GPU_shader_uniform_2fv_array(GPUShader *sh, const char *name, int len, const float (*val)[2]);
void GPU_shader_uniform_4fv_array(GPUShader *sh, const char *name, int len, const float (*val)[4]);

View File

@@ -188,6 +188,8 @@ static std::ostream &operator<<(std::ostream &stream, const GPUInput *input)
return stream << input->texture->sampler_name;
case GPU_SOURCE_TEX_TILED_MAPPING:
return stream << input->texture->tiled_mapping_name;
case GPU_SOURCE_IMAGE:
return stream << input->image->name_in_shader;
default:
BLI_assert(0);
return stream;
@@ -260,6 +262,7 @@ class GPUCodegen {
MEM_SAFE_FREE(output.volume);
MEM_SAFE_FREE(output.thickness);
MEM_SAFE_FREE(output.displacement);
MEM_SAFE_FREE(output.compute);
MEM_SAFE_FREE(output.material_functions);
delete create_info;
BLI_freelistN(&ubo_inputs_);
@@ -281,6 +284,7 @@ class GPUCodegen {
void node_serialize(std::stringstream &eval_ss, const GPUNode *node);
char *graph_serialize(eGPUNodeTag tree_tag, GPUNodeLink *output_link);
char *graph_serialize_compute();
static char *extract_c_str(std::stringstream &stream)
{
@@ -373,6 +377,16 @@ void GPUCodegen::generate_resources()
}
}
/* Images. */
LISTBASE_FOREACH (GPUMaterialImage *, image, &graph.images) {
info.image(0,
image->format,
Qualifier::WRITE,
ImageType::FLOAT_2D,
image->name_in_shader,
Frequency::BATCH);
}
if (!BLI_listbase_is_empty(&ubo_inputs_)) {
/* NOTE: generate_uniform_buffer() should have sorted the inputs before this. */
ss << "struct NodeTree {\n";
@@ -467,7 +481,9 @@ void GPUCodegen::node_serialize(std::stringstream &eval_ss, const GPUNode *node)
eval_ss << input;
break;
}
eval_ss << ", ";
if (input->next || !BLI_listbase_is_empty(&node->outputs)) {
eval_ss << ", ";
}
}
/* Output arguments. */
LISTBASE_FOREACH (GPUOutput *, output, &node->outputs) {
@@ -501,6 +517,18 @@ char *GPUCodegen::graph_serialize(eGPUNodeTag tree_tag, GPUNodeLink *output_link
return eval_c_str;
}
char *GPUCodegen::graph_serialize_compute()
{
/* Serialize all nodes. */
std::stringstream eval_ss;
LISTBASE_FOREACH (GPUNode *, node, &graph.nodes) {
node_serialize(eval_ss, node);
}
char *eval_c_str = extract_c_str(eval_ss);
BLI_hash_mm2a_add(&hm2a_, (uchar *)eval_c_str, eval_ss.str().size());
return eval_c_str;
}
void GPUCodegen::generate_uniform_buffer()
{
/* Extract uniform inputs. */
@@ -540,6 +568,9 @@ void GPUCodegen::generate_graphs()
output.volume = graph_serialize(GPU_NODE_TAG_VOLUME, graph.outlink_volume);
output.displacement = graph_serialize(GPU_NODE_TAG_DISPLACEMENT, graph.outlink_displacement);
output.thickness = graph_serialize(GPU_NODE_TAG_THICKNESS, graph.outlink_thickness);
if (GPU_material_is_compute(&mat)) {
output.compute = graph_serialize_compute();
}
if (!BLI_listbase_is_empty(&graph.material_functions)) {
std::stringstream eval_ss;
@@ -570,9 +601,15 @@ GPUPass *GPU_generate_pass(GPUMaterial *material,
GPUCodegenCallbackFn finalize_source_cb,
void *thunk)
{
/* Prune the unused nodes and extract attributes before compiling so the
* generated VBOs are ready to accept the future shader. */
gpu_node_graph_prune_unused(graph);
/* Only prune unused nodes if the GPU material is not a compute one, as nodes in compute
* materials can make arbitrary reads and writes in any node. It is then the responsibility of
* the caller to make sure no unused nodes exists. */
if (!GPU_material_is_compute(material)) {
gpu_node_graph_prune_unused(graph);
}
/* Extract attributes before compiling so the generated VBOs are ready to accept the future
* shader. */
gpu_node_graph_finalize_uniform_attrs(graph);
GPUCodegen codegen(material, graph);

View File

@@ -60,6 +60,8 @@ struct GPUMaterial {
eGPUMaterialStatus status;
/** Some flags about the nodetree & the needed resources. */
eGPUMaterialFlag flag;
/** If true, all material nodes will be serialized into a compute source. */
bool is_compute;
/* Identify shader variations (shadow, probe, world background...).
* Should be unique even across render engines. */
uint64_t uuid;
@@ -144,7 +146,7 @@ static void gpu_material_ramp_texture_build(GPUMaterial *mat)
mat->coba_builder = NULL;
}
static void gpu_material_free_single(GPUMaterial *material)
void GPU_material_free_single(GPUMaterial *material)
{
bool do_free = atomic_sub_and_fetch_uint32(&material->refcount, 1) == 0;
if (!do_free) {
@@ -176,7 +178,7 @@ void GPU_material_free(ListBase *gpumaterial)
LISTBASE_FOREACH (LinkData *, link, gpumaterial) {
GPUMaterial *material = link->data;
DRW_deferred_shader_remove(material);
gpu_material_free_single(material);
GPU_material_free_single(material);
}
BLI_freelistN(gpumaterial);
}
@@ -226,6 +228,11 @@ ListBase GPU_material_textures(GPUMaterial *material)
return material->graph.textures;
}
ListBase GPU_material_images(GPUMaterial *material)
{
return material->graph.images;
}
GPUUniformAttrList *GPU_material_uniform_attributes(GPUMaterial *material)
{
GPUUniformAttrList *attrs = &material->graph.uniform_attrs;
@@ -599,6 +606,16 @@ void GPU_material_status_set(GPUMaterial *mat, eGPUMaterialStatus status)
mat->status = status;
}
bool GPU_material_is_compute(GPUMaterial *material)
{
return material->is_compute;
}
void GPU_material_is_compute_set(GPUMaterial *material, bool is_compute)
{
material->is_compute = is_compute;
}
/* Code generation */
bool GPU_material_has_surface_output(GPUMaterial *mat)
@@ -724,7 +741,7 @@ void GPU_material_acquire(GPUMaterial *mat)
void GPU_material_release(GPUMaterial *mat)
{
gpu_material_free_single(mat);
GPU_material_free_single(mat);
}
void GPU_material_compile(GPUMaterial *mat)
@@ -775,3 +792,46 @@ void GPU_materials_free(Main *bmain)
// BKE_world_defaults_free_gpu();
BKE_material_defaults_free_gpu();
}
GPUMaterial *GPU_material_from_callbacks(GPUMaterialSetupFn setup_function,
GPUMaterialCompileFn compile_function,
GPUCodegenCallbackFn generate_function,
void *thunk)
{
/* Allocate a new material and its material graph. */
GPUMaterial *material = MEM_callocN(sizeof(GPUMaterial), "GPUMaterial");
material->graph.used_libraries = BLI_gset_new(
BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, "GPUNodeGraph.used_libraries");
material->refcount = 1;
/* Setup the newly allocated material. */
setup_function(thunk, material);
/* Compile the material. */
compile_function(thunk, material);
/* Create and initialize the texture storing color bands. */
gpu_material_ramp_texture_build(material);
/* Lookup an existing pass in the cache or generate a new one. */
material->pass = GPU_generate_pass(material, &material->graph, generate_function, thunk);
/* The pass already exists in the pass cache but its shader already failed to compile. */
if (material->pass == NULL) {
material->status = GPU_MAT_FAILED;
gpu_node_graph_free(&material->graph);
return material;
}
/* The pass already exists in the pass cache and its shader is already compiled. */
GPUShader *shader = GPU_pass_shader_get(material->pass);
if (shader != NULL) {
material->status = GPU_MAT_SUCCESS;
gpu_node_graph_free_nodes(&material->graph);
return material;
}
/* The material was created successfully but still needs to be compiled. */
material->status = GPU_MAT_CREATED;
return material;
}

View File

@@ -20,9 +20,13 @@ extern "C" {
struct GSet;
typedef enum {
FUNCTION_QUAL_IN,
FUNCTION_QUAL_OUT,
FUNCTION_QUAL_INOUT,
FUNCTION_QUAL_NONE = 0,
FUNCTION_QUAL_IN = 1 << 0,
FUNCTION_QUAL_OUT = 1 << 1,
FUNCTION_QUAL_INOUT = 1 << 2,
FUNCTION_QUAL_CONST = 1 << 3,
FUNCTION_QUAL_RESTRICT = 1 << 4,
FUNCTION_QUAL_WRITEONLY = 1 << 5,
} GPUFunctionQual;
typedef struct GPUFunction {

View File

@@ -99,6 +99,10 @@ static void gpu_node_input_link(GPUNode *node, GPUNodeLink *link, const eGPUType
input->source = GPU_SOURCE_TEX;
input->texture = link->texture;
break;
case GPU_NODE_LINK_IMAGE_TEXTURE:
input->source = GPU_SOURCE_IMAGE;
input->image = link->image;
break;
case GPU_NODE_LINK_IMAGE_TILED_MAPPING:
input->source = GPU_SOURCE_TEX_TILED_MAPPING;
input->texture = link->texture;
@@ -437,7 +441,8 @@ static GPUMaterialTexture *gpu_node_graph_add_texture(GPUNodeGraph *graph,
int num_textures = 0;
GPUMaterialTexture *tex = graph->textures.first;
for (; tex; tex = tex->next) {
if (tex->ima == ima && tex->colorband == colorband && tex->sampler_state == sampler_state) {
if (tex->ima && tex->ima == ima && tex->colorband == colorband &&
tex->sampler_state == sampler_state) {
break;
}
num_textures++;
@@ -466,6 +471,15 @@ static GPUMaterialTexture *gpu_node_graph_add_texture(GPUNodeGraph *graph,
return tex;
}
static GPUMaterialImage *gpu_node_graph_add_image(GPUNodeGraph *graph, eGPUTextureFormat format)
{
GPUMaterialImage *image = MEM_callocN(sizeof(GPUMaterialImage), __func__);
image->format = format;
const int images_count = BLI_listbase_count(&graph->images);
BLI_snprintf(image->name_in_shader, sizeof(image->name_in_shader), "image%d", images_count);
BLI_addtail(&graph->images, image);
return image;
}
/* Creating Inputs */
GPUNodeLink *GPU_attribute(GPUMaterial *mat, const eCustomDataType type, const char *name)
@@ -592,6 +606,34 @@ GPUNodeLink *GPU_color_band(GPUMaterial *mat, int size, float *pixels, float *ro
return link;
}
GPUMaterialTexture *GPU_material_add_texture(GPUMaterial *material, eGPUSamplerState sampler_state)
{
GPUNodeGraph *graph = gpu_material_node_graph(material);
return gpu_node_graph_add_texture(graph, NULL, NULL, NULL, GPU_NODE_LINK_IMAGE, sampler_state);
}
GPUMaterialImage *GPU_material_add_image_texture(GPUMaterial *material, eGPUTextureFormat format)
{
GPUNodeGraph *graph = gpu_material_node_graph(material);
return gpu_node_graph_add_image(graph, format);
}
GPUNodeLink *GPU_image_from_material_texture(GPUMaterialTexture *texture)
{
GPUNodeLink *link = gpu_node_link_create();
link->link_type = GPU_NODE_LINK_IMAGE;
link->texture = texture;
return link;
}
GPUNodeLink *GPU_image_texture_from_material_image(GPUMaterialImage *image)
{
GPUNodeLink *link = gpu_node_link_create();
link->link_type = GPU_NODE_LINK_IMAGE_TEXTURE;
link->image = image;
return link;
}
/* Creating Nodes */
bool GPU_link(GPUMaterial *mat, const char *name, ...)
@@ -613,7 +655,7 @@ bool GPU_link(GPUMaterial *mat, const char *name, ...)
va_start(params, name);
for (i = 0; i < function->totparam; i++) {
if (function->paramqual[i] == FUNCTION_QUAL_OUT) {
if (function->paramqual[i] & (FUNCTION_QUAL_OUT | FUNCTION_QUAL_INOUT)) {
linkptr = va_arg(params, GPUNodeLink **);
gpu_node_output(node, function->paramtype[i], linkptr);
}
@@ -671,7 +713,7 @@ static bool gpu_stack_link_v(GPUMaterial *material,
}
for (i = 0; i < function->totparam; i++) {
if (function->paramqual[i] == FUNCTION_QUAL_OUT) {
if (function->paramqual[i] & (FUNCTION_QUAL_OUT | FUNCTION_QUAL_INOUT)) {
if (totout == 0) {
linkptr = va_arg(params, GPUNodeLink **);
gpu_node_output(node, function->paramtype[i], linkptr);
@@ -787,6 +829,7 @@ void gpu_node_graph_free(GPUNodeGraph *graph)
gpu_node_graph_free_nodes(graph);
BLI_freelistN(&graph->textures);
BLI_freelistN(&graph->images);
BLI_freelistN(&graph->attributes);
GPU_uniform_attr_list_free(&graph->uniform_attrs);

View File

@@ -34,6 +34,7 @@ typedef enum eGPUDataSource {
GPU_SOURCE_STRUCT,
GPU_SOURCE_TEX,
GPU_SOURCE_TEX_TILED_MAPPING,
GPU_SOURCE_IMAGE,
GPU_SOURCE_FUNCTION_CALL,
} eGPUDataSource;
@@ -49,6 +50,7 @@ typedef enum {
GPU_NODE_LINK_OUTPUT,
GPU_NODE_LINK_UNIFORM,
GPU_NODE_LINK_DIFFERENTIATE_FLOAT_FN,
GPU_NODE_LINK_IMAGE_TEXTURE,
} GPUNodeLinkType;
typedef enum {
@@ -96,6 +98,8 @@ struct GPUNodeLink {
struct GPUMaterialTexture *texture;
/* GPU_NODE_LINK_DIFFERENTIATE_FLOAT_FN */
const char *function_name;
/* GPU_NODE_LINK_IMAGE_TEXTURE */
struct GPUMaterialImage *image;
};
};
@@ -130,6 +134,8 @@ typedef struct GPUInput {
struct GPUUniformAttr *uniform_attr;
/* GPU_SOURCE_FUNCTION_CALL */
char function_call[64];
/* GPU_SOURCE_IMAGE */
struct GPUMaterialImage *image;
};
} GPUInput;
@@ -162,6 +168,7 @@ typedef struct GPUNodeGraph {
/* Requested attributes and textures. */
ListBase attributes;
ListBase textures;
ListBase images;
/* The list of uniform attributes. */
GPUUniformAttrList uniform_attrs;

View File

@@ -7,6 +7,7 @@
#include "MEM_guardedalloc.h"
#include "BLI_math_matrix.h"
#include "BLI_string_utils.h"
#include "GPU_capabilities.h"
@@ -370,8 +371,7 @@ GPUShader *GPU_shader_create_from_info(const GPUShaderCreateInfo *_info)
shader->geometry_shader_from_glsl(sources);
}
if (!info.compute_source_.is_empty()) {
auto code = gpu_shader_dependency_get_resolved_source(info.compute_source_);
if (!info.compute_source_.is_empty() || !info.compute_source_generated.empty()) {
std::string layout = shader->compute_layout_declare(info);
Vector<const char *> sources;
@@ -381,7 +381,11 @@ GPUShader *GPU_shader_create_from_info(const GPUShaderCreateInfo *_info)
sources.extend(typedefs);
sources.append(resources.c_str());
sources.append(layout.c_str());
sources.extend(code);
if (!info.compute_source_.is_empty()) {
sources.extend(gpu_shader_dependency_get_resolved_source(info.compute_source_));
}
sources.extend(info.dependencies_generated);
sources.append(info.compute_source_generated.c_str());
shader->compute_shader_from_glsl(sources);
}
@@ -702,12 +706,25 @@ void GPU_shader_uniform_4fv(GPUShader *sh, const char *name, const float data[4]
GPU_shader_uniform_vector(sh, loc, 4, 1, data);
}
void GPU_shader_uniform_2iv(GPUShader *sh, const char *name, const int data[2])
{
const int loc = GPU_shader_get_uniform(sh, name);
GPU_shader_uniform_vector_int(sh, loc, 2, 1, data);
}
void GPU_shader_uniform_mat4(GPUShader *sh, const char *name, const float data[4][4])
{
const int loc = GPU_shader_get_uniform(sh, name);
GPU_shader_uniform_vector(sh, loc, 16, 1, (const float *)data);
}
void GPU_shader_uniform_mat3_as_mat4(GPUShader *sh, const char *name, const float data[3][3])
{
float matrix[4][4];
copy_m4_m3(matrix, data);
GPU_shader_uniform_mat4(sh, name, matrix);
}
void GPU_shader_uniform_2fv_array(GPUShader *sh, const char *name, int len, const float (*val)[2])
{
const int loc = GPU_shader_get_uniform(sh, name);

View File

@@ -145,7 +145,7 @@ std::string ShaderCreateInfo::check_error() const
std::string error;
/* At least a vertex shader and a fragment shader are required, or only a compute shader. */
if (this->compute_source_.is_empty()) {
if (this->compute_source_.is_empty() && this->compute_source_generated.empty()) {
if (this->vertex_source_.is_empty()) {
error += "Missing vertex shader in " + this->name_ + ".\n";
}

View File

@@ -298,6 +298,7 @@ struct ShaderCreateInfo {
/** Manually set generated code. */
std::string vertex_source_generated = "";
std::string fragment_source_generated = "";
std::string compute_source_generated = "";
std::string geometry_source_generated = "";
std::string typedef_source_generated = "";
/** Manually set generated dependencies. */
@@ -818,6 +819,7 @@ struct ShaderCreateInfo {
TEST_EQUAL(*this, b, builtins_);
TEST_EQUAL(*this, b, vertex_source_generated);
TEST_EQUAL(*this, b, fragment_source_generated);
TEST_EQUAL(*this, b, compute_source_generated);
TEST_EQUAL(*this, b, typedef_source_generated);
TEST_VECTOR_EQUAL(*this, b, vertex_inputs_);
TEST_EQUAL(*this, b, geometry_layout_);

View File

@@ -17,6 +17,7 @@
#include "BLI_map.hh"
#include "BLI_set.hh"
#include "BLI_string_ref.hh"
#include "BLI_vector_set.hh"
#include "gpu_material_library.h"
#include "gpu_shader_create_info.hh"
@@ -391,7 +392,7 @@ struct GPUSource {
auto arg_parse = [&](const StringRef str,
int64_t &cursor,
StringRef &out_qualifier,
VectorSet<StringRef> &out_qualifiers,
StringRef &out_type,
StringRef &out_name) -> bool {
int64_t arg_start = cursor + 1;
@@ -405,16 +406,19 @@ struct GPUSource {
}
const StringRef arg = str.substr(arg_start, cursor - arg_start);
/* Add all key words to the qualifiers vector set. Then pop the last element and store it in
* the name then pop the second to last element and store it in the type. The elements left
* are all the specified qualifiers. */
int64_t keyword_cursor = 0;
out_qualifier = keyword_parse(arg, keyword_cursor);
out_type = keyword_parse(arg, keyword_cursor);
out_name = keyword_parse(arg, keyword_cursor);
if (out_name.is_empty()) {
/* No qualifier case. */
out_name = out_type;
out_type = out_qualifier;
out_qualifier = arg.substr(0, 0);
while (true) {
StringRef keyword = keyword_parse(arg, keyword_cursor);
if (keyword.is_empty()) {
break;
}
out_qualifiers.add(keyword);
}
out_name = out_qualifiers.pop();
out_type = out_qualifiers.pop();
return true;
};
@@ -452,8 +456,9 @@ struct GPUSource {
func->totparam = 0;
int64_t args_cursor = -1;
StringRef arg_qualifier, arg_type, arg_name;
while (arg_parse(func_args, args_cursor, arg_qualifier, arg_type, arg_name)) {
StringRef arg_type, arg_name;
VectorSet<StringRef> arg_qualifiers;
while (arg_parse(func_args, args_cursor, arg_qualifiers, arg_type, arg_name)) {
if (func->totparam >= ARRAY_SIZE(func->paramtype)) {
print_error(input, source.find(func_name), "Too much parameter in function");
@@ -461,13 +466,25 @@ struct GPUSource {
}
auto parse_qualifier = [](StringRef qualifier) -> GPUFunctionQual {
if (qualifier == "in") {
return FUNCTION_QUAL_IN;
}
if (qualifier == "out") {
return FUNCTION_QUAL_OUT;
}
if (qualifier == "inout") {
return FUNCTION_QUAL_INOUT;
}
return FUNCTION_QUAL_IN;
if (qualifier == "const") {
return FUNCTION_QUAL_CONST;
}
if (qualifier == "restrict") {
return FUNCTION_QUAL_RESTRICT;
}
if (qualifier == "writeonly") {
return FUNCTION_QUAL_WRITEONLY;
}
return FUNCTION_QUAL_NONE;
};
auto parse_type = [](StringRef type) -> eGPUType {
@@ -504,10 +521,17 @@ struct GPUSource {
if (type == "Closure") {
return GPU_CLOSURE;
}
if (type == "image2D") {
return GPU_IMAGE_2D;
}
return GPU_NONE;
};
func->paramqual[func->totparam] = parse_qualifier(arg_qualifier);
GPUFunctionQual qualifiers = FUNCTION_QUAL_NONE;
for (StringRef qualifier : arg_qualifiers) {
qualifiers = static_cast<GPUFunctionQual>(qualifiers | parse_qualifier(qualifier));
}
func->paramqual[func->totparam] = qualifiers;
func->paramtype[func->totparam] = parse_type(arg_type);
if (func->paramtype[func->totparam] == GPU_NONE) {
@@ -594,7 +618,8 @@ struct GPUSource {
bool is_from_material_library() const
{
return (filename.startswith("gpu_shader_material_") ||
filename.startswith("gpu_shader_common_")) &&
filename.startswith("gpu_shader_common_") ||
filename.startswith("gpu_shader_compositor_")) &&
filename.endswith(".glsl");
}
};

View File

@@ -140,6 +140,84 @@ void hsl_to_rgb(vec4 hsl, out vec4 outcol)
outcol = vec4((nr - 0.5) * chroma + l, (ng - 0.5) * chroma + l, (nb - 0.5) * chroma + l, hsl.w);
}
/* ** YCCA to RGBA ** */
void ycca_to_rgba_itu_601(vec4 ycca, out vec4 color)
{
ycca.xyz *= 255.0;
ycca.xyz -= vec3(16.0, 128.0, 128.0);
color.rgb = mat3(vec3(1.164), 0.0, -0.392, 2.017, 1.596, -0.813, 0.0) * ycca.xyz;
color.rgb /= 255.0;
color.a = ycca.a;
}
void ycca_to_rgba_itu_709(vec4 ycca, out vec4 color)
{
ycca.xyz *= 255.0;
ycca.xyz -= vec3(16.0, 128.0, 128.0);
color.rgb = mat3(vec3(1.164), 0.0, -0.213, 2.115, 1.793, -0.534, 0.0) * ycca.xyz;
color.rgb /= 255.0;
color.a = ycca.a;
}
void ycca_to_rgba_jpeg(vec4 ycca, out vec4 color)
{
ycca.xyz *= 255.0;
color.rgb = mat3(vec3(1.0), 0.0, -0.34414, 1.772, 1.402, -0.71414, 0.0) * ycca.xyz;
color.rgb += vec3(-179.456, 135.45984, -226.816);
color.rgb /= 255.0;
color.a = ycca.a;
}
/* ** RGBA to YCCA ** */
void rgba_to_ycca_itu_601(vec4 rgba, out vec4 ycca)
{
rgba.rgb *= 255.0;
ycca.xyz = mat3(0.257, -0.148, 0.439, 0.504, -0.291, -0.368, 0.098, 0.439, -0.071) * rgba.rgb;
ycca.xyz += vec3(16.0, 128.0, 128.0);
ycca.xyz /= 255.0;
ycca.a = rgba.a;
}
void rgba_to_ycca_itu_709(vec4 rgba, out vec4 ycca)
{
rgba.rgb *= 255.0;
ycca.xyz = mat3(0.183, -0.101, 0.439, 0.614, -0.338, -0.399, 0.062, 0.439, -0.040) * rgba.rgb;
ycca.xyz += vec3(16.0, 128.0, 128.0);
ycca.xyz /= 255.0;
ycca.a = rgba.a;
}
void rgba_to_ycca_jpeg(vec4 rgba, out vec4 ycca)
{
rgba.rgb *= 255.0;
ycca.xyz = mat3(0.299, -0.16874, 0.5, 0.587, -0.33126, -0.41869, 0.114, 0.5, -0.08131) *
rgba.rgb;
ycca.xyz += vec3(0.0, 128.0, 128.0);
ycca.xyz /= 255.0;
ycca.a = rgba.a;
}
/* ** YUVA to RGBA ** */
void yuva_to_rgba_itu_709(vec4 yuva, out vec4 color)
{
color.rgb = mat3(vec3(1.0), 0.0, -0.21482, 2.12798, 1.28033, -0.38059, 0.0) * yuva.xyz;
color.a = yuva.a;
}
/* ** RGBA to YUVA ** */
void rgba_to_yuva_itu_709(vec4 rgba, out vec4 yuva)
{
yuva.xyz = mat3(0.2126, -0.09991, 0.615, 0.7152, -0.33609, -0.55861, 0.0722, 0.436, -0.05639) *
rgba.rgb;
yuva.a = rgba.a;
}
/* ** Alpha Handling ** */
void color_alpha_clear(vec4 color, out vec4 result)
{
result = vec4(color.rgb, 1.0);
@@ -147,15 +225,50 @@ void color_alpha_clear(vec4 color, out vec4 result)
void color_alpha_premultiply(vec4 color, out vec4 result)
{
result = vec4(color.rgb * color.a, 1.0);
result = vec4(color.rgb * color.a, color.a);
}
void color_alpha_unpremultiply(vec4 color, out vec4 result)
{
if (color.a == 0.0 || color.a == 1.0) {
result = vec4(color.rgb, 1.0);
result = color;
}
else {
result = vec4(color.rgb / color.a, 1.0);
result = vec4(color.rgb / color.a, color.a);
}
}
float linear_rgb_to_srgb(float color)
{
if (color < 0.0031308) {
return (color < 0.0) ? 0.0 : color * 12.92;
}
return 1.055 * pow(color, 1.0 / 2.4) - 0.055;
}
vec3 linear_rgb_to_srgb(vec3 color)
{
return vec3(
linear_rgb_to_srgb(color.r), linear_rgb_to_srgb(color.g), linear_rgb_to_srgb(color.b));
}
float srgb_to_linear_rgb(float color)
{
if (color < 0.04045) {
return (color < 0.0) ? 0.0 : color * (1.0 / 12.92);
}
return pow((color + 0.055) * (1.0 / 1.055), 2.4);
}
vec3 srgb_to_linear_rgb(vec3 color)
{
return vec3(
srgb_to_linear_rgb(color.r), srgb_to_linear_rgb(color.g), srgb_to_linear_rgb(color.b));
}
float get_luminance(vec3 color, vec3 luminance_coefficients)
{
return dot(color, luminance_coefficients);
}

View File

@@ -95,6 +95,81 @@ void curves_combined_only(float factor,
result = mix(color, result, factor);
}
/* Contrary to standard tone curve implementations, the film-like implementation tries to preserve
* the hue of the colors as much as possible. To understand why this might be a problem, consider
* the violet color (0.5, 0.0, 1.0). If this color was to be evaluated at a power curve x^4, the
* color will be blue (0.0625, 0.0, 1.0). So the color changes and not just its luminosity, which
* is what film-like tone curves tries to avoid.
*
* First, the channels with the lowest and highest values are identified and evaluated at the
* curve. Then, the third channel---the median---is computed while maintaining the original hue of
* the color. To do that, we look at the equation for deriving the hue from RGB values. Assuming
* the maximum, minimum, and median channels are known, and ignoring the 1/3 period offset of the
* hue, the equation is:
*
* hue = (median - min) / (max - min) [1]
*
* Since we have the new values for the minimum and maximum after evaluating at the curve, we also
* have:
*
* hue = (new_median - new_min) / (new_max - new_min) [2]
*
* Since we want the hue to be equivalent, by equating [1] and [2] and rearranging:
*
* (new_median - new_min) / (new_max - new_min) = (median - min) / (max - min)
* new_median - new_min = (new_max - new_min) * (median - min) / (max - min)
* new_median = new_min + (new_max - new_min) * (median - min) / (max - min)
* new_median = new_min + (median - min) * ((new_max - new_min) / (max - min)) [QED]
*
* Which gives us the median color that preserves the hue. More intuitively, the median is computed
* such that the change in the distance from the median to the minimum is proportional to the
* change in the distance from the minimum to the maximum. Finally, each of the new minimum,
* maximum, and median values are written to the color channel that they were originally extracted
* from. */
void curves_film_like(float factor,
vec4 color,
vec4 black_level,
vec4 white_level,
sampler1DArray curve_map,
const float layer,
float range_minimum,
float range_divider,
float start_slope,
float end_slope,
out vec4 result)
{
vec4 balanced = white_balance(color, black_level, white_level);
/* Find the maximum, minimum, and median of the color channels. */
float minimum = min(balanced.r, min(balanced.g, balanced.b));
float maximum = max(balanced.r, max(balanced.g, balanced.b));
float median = max(min(balanced.r, balanced.g), min(balanced.b, max(balanced.r, balanced.g)));
/* Evaluate alpha curve map at the maximum and minimum channels. The alpha curve is the Combined
* curve in the UI. */
float min_parameter = NORMALIZE_PARAMETER(minimum, range_minimum, range_divider);
float max_parameter = NORMALIZE_PARAMETER(maximum, range_minimum, range_divider);
float new_min = texture(curve_map, vec2(min_parameter, layer)).a;
float new_max = texture(curve_map, vec2(max_parameter, layer)).a;
/* Then, extrapolate if needed. */
new_min = extrapolate_if_needed(min_parameter, new_min, start_slope, end_slope);
new_max = extrapolate_if_needed(max_parameter, new_max, start_slope, end_slope);
/* Compute the new median using the ratio between the new and the original range. */
float scaling_ratio = (new_max - new_min) / (maximum - minimum);
float new_median = new_min + (median - minimum) * scaling_ratio;
/* Write each value to its original channel. */
bvec3 channel_is_min = equal(balanced.rgb, vec3(minimum));
vec3 median_or_min = mix(vec3(new_median), vec3(new_min), channel_is_min);
bvec3 channel_is_max = equal(balanced.rgb, vec3(maximum));
result.rgb = mix(median_or_min, vec3(new_max), channel_is_max);
result.a = color.a;
result = mix(color, result, factor);
}
void curves_vector(vec3 vector,
sampler1DArray curve_map,
const float layer,

View File

@@ -34,6 +34,17 @@ float compatible_pow(float x, float y)
return pow(x, y);
}
/* A version of pow that returns a fallback value if the computation is undefined. From the spec:
* The result is undefined if x < 0 or if x = 0 and y is less than or equal 0. */
float fallback_pow(float x, float y, float fallback)
{
if (x < 0.0 || (x == 0.0 && y <= 0.0)) {
return fallback;
}
return pow(x, y);
}
float wrap(float a, float b, float c)
{
float range = b - c;
@@ -114,8 +125,24 @@ void vector_copy(vec3 normal, out vec3 outnormal)
outnormal = normal;
}
vec3 fallback_pow(vec3 a, float b, vec3 fallback)
{
return vec3(fallback_pow(a.x, b, fallback.x),
fallback_pow(a.y, b, fallback.y),
fallback_pow(a.z, b, fallback.z));
}
/* Matirx Math */
/* Return a 2D rotation matrix with the angle that the input 2D vector makes with the x axis. */
mat2 vector_to_rotation_matrix(vec2 vector)
{
vec2 normalized_vector = normalize(vector);
float cos_angle = normalized_vector.x;
float sin_angle = normalized_vector.y;
return mat2(cos_angle, sin_angle, -sin_angle, cos_angle);
}
mat3 euler_to_mat3(vec3 euler)
{
float cx = cos(euler.x);

View File

@@ -278,6 +278,7 @@ void mix_soft(float fac, vec4 col1, vec4 col2, out vec4 outcol)
vec4 one = vec4(1.0);
vec4 scr = one - (one - col2) * (one - col1);
outcol = facm * col1 + fac * ((one - col1) * col2 * col1 + col1 * scr);
outcol.a = col1.a;
}
void mix_linear(float fac, vec4 col1, vec4 col2, out vec4 outcol)
@@ -285,9 +286,15 @@ void mix_linear(float fac, vec4 col1, vec4 col2, out vec4 outcol)
fac = clamp(fac, 0.0, 1.0);
outcol = col1 + fac * (2.0 * (col2 - vec4(0.5)));
outcol.a = col1.a;
}
void clamp_color(vec3 vec, vec3 min, vec3 max, out vec3 out_vec)
void clamp_color(vec4 vec, const vec4 min, const vec4 max, out vec4 out_vec)
{
out_vec = clamp(vec, min, max);
}
void multiply_by_alpha(float factor, vec4 color, out float result)
{
result = factor * color.a;
}

View File

@@ -0,0 +1,11 @@
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
void main()
{
ivec2 xy = ivec2(gl_GlobalInvocationID.xy);
/* The lower bound is inclusive and upper bound is exclusive. */
bool is_inside = all(greaterThanEqual(xy, lower_bound)) && all(lessThan(xy, upper_bound));
/* Write the pixel color if it is inside the cropping region, otherwise, write zero. */
vec4 color = is_inside ? texture_load(input_image, xy) : vec4(0.0);
imageStore(output_image, xy, color);
}

View File

@@ -0,0 +1,20 @@
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
void main()
{
ivec2 xy = ivec2(gl_GlobalInvocationID.xy);
const int weights_size = texture_size(weights);
const int blur_size = weights_size / 2;
vec4 color = vec4(0.0);
for (int i = 0; i < weights_size; i++) {
#if defined(BLUR_HORIZONTAL)
const ivec2 offset = ivec2(i - blur_size, 0);
#elif defined(BLUR_VERTICAL)
const ivec2 offset = ivec2(0, i - blur_size);
#endif
color += texture_load(input_image, xy + offset) * texture_load(weights, i).x;
}
imageStore(output_image, xy, color);
}

View File

@@ -0,0 +1,27 @@
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
void main()
{
ivec2 xy = ivec2(gl_GlobalInvocationID.xy);
vec2 uv = vec2(xy) / vec2(domain_size - ivec2(1));
uv -= location;
uv.y *= float(domain_size.y) / float(domain_size.x);
uv = mat2(cos_angle, -sin_angle, sin_angle, cos_angle) * uv;
bool is_inside = all(lessThan(abs(uv), size));
float base_mask_value = texture_load(base_mask, xy).x;
float value = texture_load(mask_value, xy).x;
#if defined(CMP_NODE_MASKTYPE_ADD)
float output_mask_value = is_inside ? max(base_mask_value, value) : base_mask_value;
#elif defined(CMP_NODE_MASKTYPE_SUBTRACT)
float output_mask_value = is_inside ? clamp(base_mask_value - value, 0.0, 1.0) : base_mask_value;
#elif defined(CMP_NODE_MASKTYPE_MULTIPLY)
float output_mask_value = is_inside ? base_mask_value * value : 0.0;
#elif defined(CMP_NODE_MASKTYPE_NOT)
float output_mask_value = is_inside ? (base_mask_value > 0.0 ? 0.0 : value) : base_mask_value;
#endif
imageStore(output_mask, xy, vec4(output_mask_value));
}

View File

@@ -0,0 +1,8 @@
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
void main()
{
ivec2 xy = ivec2(gl_GlobalInvocationID.xy);
vec4 texel = texture_load(input_sampler, xy);
imageStore(output_image, xy, CONVERT_EXPRESSION);
}

View File

@@ -0,0 +1,27 @@
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
void main()
{
ivec2 xy = ivec2(gl_GlobalInvocationID.xy);
vec2 uv = vec2(xy) / vec2(domain_size - ivec2(1));
uv -= location;
uv.y *= float(domain_size.y) / float(domain_size.x);
uv = mat2(cos_angle, -sin_angle, sin_angle, cos_angle) * uv;
bool is_inside = length(uv / radius) < 1.0;
float base_mask_value = texture_load(base_mask, xy).x;
float value = texture_load(mask_value, xy).x;
#if defined(CMP_NODE_MASKTYPE_ADD)
float output_mask_value = is_inside ? max(base_mask_value, value) : base_mask_value;
#elif defined(CMP_NODE_MASKTYPE_SUBTRACT)
float output_mask_value = is_inside ? clamp(base_mask_value - value, 0.0, 1.0) : base_mask_value;
#elif defined(CMP_NODE_MASKTYPE_MULTIPLY)
float output_mask_value = is_inside ? base_mask_value * value : 0.0;
#elif defined(CMP_NODE_MASKTYPE_NOT)
float output_mask_value = is_inside ? (base_mask_value > 0.0 ? 0.0 : value) : base_mask_value;
#endif
imageStore(output_mask, xy, vec4(output_mask_value));
}

View File

@@ -0,0 +1,22 @@
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
void main()
{
ivec2 xy = ivec2(gl_GlobalInvocationID.xy);
/* Compute the dot product between the kernel and the 3x3 window around the current pixel. */
vec4 color = vec4(0);
for (int j = 0; j < 3; j++) {
for (int i = 0; i < 3; i++) {
color += texture_load(input_image, xy + ivec2(i - 1, j - 1)) * kernel[j][i];
}
}
/* Mix with the original color at the center of the kernel using the input factor. */
color = mix(texture_load(input_image, xy), color, texture_load(factor, xy).x);
/* Make sure the output is never negative. */
color = max(color, 0.0);
imageStore(output_image, xy, color);
}

View File

@@ -0,0 +1,15 @@
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
void main()
{
ivec2 xy = ivec2(gl_GlobalInvocationID.xy);
ivec2 size = texture_size(input_image);
ivec2 flipped_xy = xy;
#if defined(FLIP_X)
flipped_xy.x = size.x - xy.x - 1;
#endif
#if defined(FLIP_Y)
flipped_xy.y = size.y - xy.y - 1;
#endif
imageStore(output_image, xy, texture_load(input_image, flipped_xy));
}

View File

@@ -0,0 +1,7 @@
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
void main()
{
ivec2 xy = ivec2(gl_GlobalInvocationID.xy);
imageStore(output_image, xy, texture_load(input_image, xy + lower_bound));
}

View File

@@ -0,0 +1,16 @@
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
void main()
{
ivec2 xy = ivec2(gl_GlobalInvocationID.xy);
/* Get the normalized coordinates of the pixel centers. */
vec2 normalized_xy = (vec2(xy) + vec2(0.5)) / vec2(texture_size(input_image));
/* Sample the red and blue channels shifted by the dispersion amount. */
const float red = texture(input_image, normalized_xy + vec2(dispersion, 0.0)).r;
const float green = texture_load(input_image, xy).g;
const float blue = texture(input_image, normalized_xy - vec2(dispersion, 0.0)).b;
imageStore(output_image, xy, vec4(red, green, blue, 1.0));
}

View File

@@ -0,0 +1,25 @@
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
void main()
{
ivec2 xy = ivec2(gl_GlobalInvocationID.xy);
/* First, transform the input image by transforming the domain coordinates with the inverse of
* input image's transformation. The inverse transformation is an affine matrix and thus the
* coordinates should be in homogeneous coordinates. */
vec2 coordinates = (mat3(inverse_transformation) * vec3(xy, 1.0)).xy;
/* Since an input image with an identity transformation is supposed to be centered in the domain,
* we subtract the offset between the lower left corners of the input image and the domain, which
* is half the difference between their sizes, because the difference in size is on both sides of
* the centered image. */
ivec2 domain_size = imageSize(domain);
ivec2 input_size = texture_size(input_sampler);
vec2 offset = (domain_size - input_size) / 2.0;
/* Subtract the offset and divide by the input image size to get the relevant coordinates into
* the sampler's expected [0, 1] range. */
vec2 normalized_coordinates = (coordinates - offset) / input_size;
imageStore(domain, xy, texture(input_sampler, normalized_coordinates));
}

View File

@@ -0,0 +1,151 @@
#pragma BLENDER_REQUIRE(gpu_shader_common_hash.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
/* A model that approximates lens distortion parameterized by a distortion parameter and dependent
* on the squared distance to the center of the image. The distorted pixel is then computed as the
* scalar multiplication of the pixel coordinates with the value returned by this model. See the
* compute_distorted_uv function for more details. */
float compute_distortion_scale(float distortion, float distance_squared)
{
return 1.0 / (1.0 + sqrt(1.0 - distortion * distance_squared));
}
/* A vectorized version of compute_distortion_scale that is applied on the chromatic distortion
* parameters passed to the shader. */
vec3 compute_chromatic_distortion_scale(float distance_squared)
{
return 1.0 / (1.0 + sqrt(1.0 - chromatic_distortion * distance_squared));
}
/* Compute the image coordinates after distortion by the given distortion scale computed by the
* compute_distortion_scale function. Note that the function expects centered normalized UV
* coordinates but outputs non-centered image coordinates. */
vec2 compute_distorted_uv(vec2 uv, float scale)
{
return (uv * scale + 0.5) * texture_size(input_image) - 0.5;
}
/* Compute the number of integration steps that should be used to approximate the distorted pixel
* using a heuristic, see the compute_number_of_steps function for more details. The numbers of
* steps is proportional to the number of pixels spanned by the distortion amount. For jitter
* distortion, the square root of the distortion amount plus 1 is used with a minimum of 2 steps.
* For non-jitter distortion, the distortion amount plus 1 is used as the number of steps */
int compute_number_of_integration_steps_heuristic(float distortion)
{
#if defined(JITTER)
return distortion < 4.0 ? 2 : int(sqrt(distortion + 1.0));
#else
return int(distortion + 1.0);
#endif
}
/* Compute the number of integration steps that should be used to compute each channel of the
* distorted pixel. Each of the channels are distorted by their respective chromatic distortion
* amount, then the amount of distortion between each two consecutive channels is computed, this
* amount is then used to heuristically infer the number of needed integration steps, see the
* integrate_distortion function for more information. */
ivec3 compute_number_of_integration_steps(vec2 uv, float distance_squared)
{
/* Distort each channel by its respective chromatic distortion amount. */
const vec3 distortion_scale = compute_chromatic_distortion_scale(distance_squared);
const vec2 distorted_uv_red = compute_distorted_uv(uv, distortion_scale.r);
const vec2 distorted_uv_green = compute_distorted_uv(uv, distortion_scale.g);
const vec2 distorted_uv_blue = compute_distorted_uv(uv, distortion_scale.b);
/* Infer the number of needed integration steps to compute the distorted red channel starting
* from the green channel. */
const float distortion_red = distance(distorted_uv_red, distorted_uv_green);
const int steps_red = compute_number_of_integration_steps_heuristic(distortion_red);
/* Infer the number of needed integration steps to compute the distorted blue channel starting
* from the green channel. */
const float distortion_blue = distance(distorted_uv_green, distorted_uv_blue);
const int steps_blue = compute_number_of_integration_steps_heuristic(distortion_blue);
/* The number of integration steps used to compute the green channel is the sum of both the red
* and the blue channel steps because it is computed once with each of them. */
return ivec3(steps_red, steps_red + steps_blue, steps_blue);
}
/* Returns a random jitter amount, which is essentially a random value in the [0, 1] range. If
* jitter is not enabled, return a constant 0.5 value instead. */
float get_jitter(int seed)
{
#if defined(JITTER)
return hash_uint3_to_float(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y, seed);
#else
return 0.5;
#endif
}
/* Each color channel may have a different distortion with the guarantee that the red will have the
* lowest distortion while the blue will have the highest one. If each channel is distorted
* independently, the image will look disintegrated, with each channel seemingly merely shifted.
* Consequently, the distorted pixels needs to be computed by integrating along the path of change
* of distortion starting from one channel to another. For instance, to compute the distorted red
* from the distorted green, we accumulate the color of the distorted pixel starting from the
* distortion of the red, taking small steps until we reach the distortion of the green. The pixel
* color is weighted such that it is maximum at the start distortion and zero at the end distortion
* in an arithmetic progression. The integration steps can be augmented with random values to
* simulate lens jitter. Finally, it should be noted that this function integrates both the start
* and end channels in reverse directions for more efficient computation. */
vec3 integrate_distortion(int start, int end, float distance_squared, vec2 uv, int steps)
{
vec3 accumulated_color = vec3(0.0);
const float distortion_amount = chromatic_distortion[end] - chromatic_distortion[start];
for (int i = 0; i < steps; i++) {
/* The increment will be in the [0, 1) range across iterations. */
const float increment = (i + get_jitter(i)) / steps;
const float distortion = chromatic_distortion[start] + increment * distortion_amount;
const float distortion_scale = compute_distortion_scale(distortion, distance_squared);
/* Sample the color at the distorted coordinates and accumulate it weighted by the increment
* value for both the start and end channels. */
const vec2 distorted_uv = compute_distorted_uv(uv, distortion_scale);
const vec4 color = texture(input_image, distorted_uv / texture_size(input_image));
accumulated_color[start] += (1.0 - increment) * color[start];
accumulated_color[end] += increment * color[end];
}
return accumulated_color;
}
void main()
{
ivec2 xy = ivec2(gl_GlobalInvocationID.xy);
/* Compute the UV image coordinates in the range [-1, 1] as well as the squared distance to the
* center of the image, which is at (0, 0) in the UV coordinates. */
const vec2 center = texture_size(input_image) / 2.0;
const vec2 uv = scale * (xy + 0.5 - center) / center;
const float distance_squared = dot(uv, uv);
/* If any of the color channels will get distorted outside of the screen beyond what is possible,
* write a zero transparent color and return. */
if (any(greaterThan(chromatic_distortion * distance_squared, vec3(1.0)))) {
imageStore(output_image, xy, vec4(0.0));
return;
}
/* Compute the number of integration steps that should be used to compute each channel of the
* distorted pixel. */
const ivec3 number_of_steps = compute_number_of_integration_steps(uv, distance_squared);
/* Integrate the distortion of the red and green, then the green and blue channels. That means
* the green will be integrated twice, but this is accounted for in the number of steps which the
* color will later be divided by. See the compute_number_of_integration_steps function for more
* details. */
vec3 color = vec3(0.0);
color += integrate_distortion(0, 1, distance_squared, uv, number_of_steps.r);
color += integrate_distortion(1, 2, distance_squared, uv, number_of_steps.b);
/* The integration above performed weighted accumulation, and thus the color needs to be divided
* by the sum of the weights. Assuming no jitter, the weights are generated as an arithmetic
* progression starting from (0.5 / n) to ((n - 0.5) / n) for n terms. The sum of an arithmetic
* progression can be computed as (n * (start + end) / 2), which when subsisting the start and
* end reduces to (n / 2). So the color should be multiplied by 2 / n. The jitter sequence
* approximately sums to the same value because it is a uniform random value whose mean value is
* 0.5, so the expression doesn't change regardless of jitter. */
color *= 2.0 / vec3(number_of_steps);
imageStore(output_image, xy, vec4(color, 1.0));
}

View File

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

View File

@@ -0,0 +1,13 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2022 Blender Foundation. All rights reserved. */
#include "gpu_shader_create_info.hh"
GPU_SHADER_CREATE_INFO(compositor_alpha_crop)
.local_group_size(16, 16)
.push_constant(Type::IVEC2, "lower_bound")
.push_constant(Type::IVEC2, "upper_bound")
.sampler(0, ImageType::FLOAT_2D, "input_image")
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image")
.compute_source("compositor_alpha_crop.glsl")
.do_static_compilation(true);

View File

@@ -0,0 +1,21 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2022 Blender Foundation. All rights reserved. */
#include "gpu_shader_create_info.hh"
GPU_SHADER_CREATE_INFO(compositor_blur_shared)
.local_group_size(16, 16)
.sampler(0, ImageType::FLOAT_2D, "input_image")
.sampler(1, ImageType::FLOAT_1D, "weights")
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image")
.compute_source("compositor_blur.glsl");
GPU_SHADER_CREATE_INFO(compositor_blur_horizontal)
.additional_info("compositor_blur_shared")
.define("BLUR_HORIZONTAL")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(compositor_blur_vertical)
.additional_info("compositor_blur_shared")
.define("BLUR_VERTICAL")
.do_static_compilation(true);

View File

@@ -0,0 +1,36 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2022 Blender Foundation. All rights reserved. */
#include "gpu_shader_create_info.hh"
GPU_SHADER_CREATE_INFO(compositor_box_mask_shared)
.local_group_size(16, 16)
.push_constant(Type::IVEC2, "domain_size")
.push_constant(Type::VEC2, "location")
.push_constant(Type::VEC2, "size")
.push_constant(Type::FLOAT, "cos_angle")
.push_constant(Type::FLOAT, "sin_angle")
.sampler(0, ImageType::FLOAT_2D, "base_mask")
.sampler(1, ImageType::FLOAT_2D, "mask_value")
.image(0, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_mask")
.compute_source("compositor_box_mask.glsl");
GPU_SHADER_CREATE_INFO(compositor_box_mask_add)
.additional_info("compositor_box_mask_shared")
.define("CMP_NODE_MASKTYPE_ADD")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(compositor_box_mask_subtract)
.additional_info("compositor_box_mask_shared")
.define("CMP_NODE_MASKTYPE_SUBTRACT")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(compositor_box_mask_multiply)
.additional_info("compositor_box_mask_shared")
.define("CMP_NODE_MASKTYPE_MULTIPLY")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(compositor_box_mask_not)
.additional_info("compositor_box_mask_shared")
.define("CMP_NODE_MASKTYPE_NOT")
.do_static_compilation(true);

View File

@@ -0,0 +1,64 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2022 Blender Foundation. All rights reserved. */
#include "gpu_shader_create_info.hh"
GPU_SHADER_CREATE_INFO(compositor_convert_shared)
.local_group_size(16, 16)
.sampler(0, ImageType::FLOAT_2D, "input_sampler")
.typedef_source("gpu_shader_compositor_type_conversion.glsl")
.compute_source("compositor_convert.glsl");
GPU_SHADER_CREATE_INFO(compositor_convert_float_to_vector)
.additional_info("compositor_convert_shared")
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image")
.define("CONVERT_EXPRESSION", "vec4(vec3_from_float(texel.x), 0.0)")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(compositor_convert_float_to_color)
.additional_info("compositor_convert_shared")
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image")
.define("CONVERT_EXPRESSION", "vec4_from_float(texel.x)")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(compositor_convert_color_to_float)
.additional_info("compositor_convert_shared")
.image(0, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image")
.define("CONVERT_EXPRESSION", "vec4(float_from_vec4(texel), vec3(0.0))")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(compositor_convert_color_to_vector)
.additional_info("compositor_convert_shared")
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image")
.define("CONVERT_EXPRESSION", "vec4(vec3_from_vec4(texel), 0.0)")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(compositor_convert_vector_to_float)
.additional_info("compositor_convert_shared")
.image(0, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image")
.define("CONVERT_EXPRESSION", "vec4(float_from_vec3(texel.xyz), vec3(0.0))")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(compositor_convert_vector_to_color)
.additional_info("compositor_convert_shared")
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image")
.define("CONVERT_EXPRESSION", "vec4_from_vec3(texel.xyz)")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(compositor_convert_color_to_alpha)
.additional_info("compositor_convert_shared")
.image(0, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image")
.define("CONVERT_EXPRESSION", "vec4(texel.a, vec3(0.0))")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(compositor_convert_color_to_half_color)
.additional_info("compositor_convert_shared")
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image")
.define("CONVERT_EXPRESSION", "texel")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(compositor_convert_float_to_half_float)
.additional_info("compositor_convert_shared")
.image(0, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image")
.define("CONVERT_EXPRESSION", "vec4(texel.r, vec3(0.0))")
.do_static_compilation(true);

View File

@@ -0,0 +1,36 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2022 Blender Foundation. All rights reserved. */
#include "gpu_shader_create_info.hh"
GPU_SHADER_CREATE_INFO(compositor_ellipse_mask_shared)
.local_group_size(16, 16)
.push_constant(Type::IVEC2, "domain_size")
.push_constant(Type::VEC2, "location")
.push_constant(Type::VEC2, "radius")
.push_constant(Type::FLOAT, "cos_angle")
.push_constant(Type::FLOAT, "sin_angle")
.sampler(0, ImageType::FLOAT_2D, "base_mask")
.sampler(1, ImageType::FLOAT_2D, "mask_value")
.image(0, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_mask")
.compute_source("compositor_ellipse_mask.glsl");
GPU_SHADER_CREATE_INFO(compositor_ellipse_mask_add)
.additional_info("compositor_ellipse_mask_shared")
.define("CMP_NODE_MASKTYPE_ADD")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(compositor_ellipse_mask_subtract)
.additional_info("compositor_ellipse_mask_shared")
.define("CMP_NODE_MASKTYPE_SUBTRACT")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(compositor_ellipse_mask_multiply)
.additional_info("compositor_ellipse_mask_shared")
.define("CMP_NODE_MASKTYPE_MULTIPLY")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(compositor_ellipse_mask_not)
.additional_info("compositor_ellipse_mask_shared")
.define("CMP_NODE_MASKTYPE_NOT")
.do_static_compilation(true);

View File

@@ -0,0 +1,13 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2022 Blender Foundation. All rights reserved. */
#include "gpu_shader_create_info.hh"
GPU_SHADER_CREATE_INFO(compositor_filter)
.local_group_size(16, 16)
.push_constant(Type::MAT4, "kernel")
.sampler(0, ImageType::FLOAT_2D, "input_image")
.sampler(1, ImageType::FLOAT_2D, "factor")
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image")
.compute_source("compositor_filter.glsl")
.do_static_compilation(true);

View File

@@ -0,0 +1,26 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2022 Blender Foundation. All rights reserved. */
#include "gpu_shader_create_info.hh"
GPU_SHADER_CREATE_INFO(compositor_flip_shared)
.local_group_size(16, 16)
.sampler(0, ImageType::FLOAT_2D, "input_image")
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image")
.compute_source("compositor_flip.glsl");
GPU_SHADER_CREATE_INFO(compositor_flip_x)
.additional_info("compositor_flip_shared")
.define("FLIP_X")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(compositor_flip_y)
.additional_info("compositor_flip_shared")
.define("FLIP_Y")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(compositor_flip_x_and_y)
.additional_info("compositor_flip_shared")
.define("FLIP_X")
.define("FLIP_Y")
.do_static_compilation(true);

View File

@@ -0,0 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2022 Blender Foundation. All rights reserved. */
#include "gpu_shader_create_info.hh"
GPU_SHADER_CREATE_INFO(compositor_image_crop)
.local_group_size(16, 16)
.push_constant(Type::IVEC2, "lower_bound")
.sampler(0, ImageType::FLOAT_2D, "input_image")
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image")
.compute_source("compositor_image_crop.glsl")
.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_projector_lens_distortion)
.local_group_size(16, 16)
.push_constant(Type::FLOAT, "dispersion")
.sampler(0, ImageType::FLOAT_2D, "input_image")
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image")
.compute_source("compositor_projector_lens_distortion.glsl")
.do_static_compilation(true);

View File

@@ -0,0 +1,25 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2022 Blender Foundation. All rights reserved. */
#include "gpu_shader_create_info.hh"
GPU_SHADER_CREATE_INFO(compositor_realize_on_domain_shared)
.local_group_size(16, 16)
.push_constant(Type::MAT4, "inverse_transformation")
.sampler(0, ImageType::FLOAT_2D, "input_sampler")
.compute_source("compositor_realize_on_domain.glsl");
GPU_SHADER_CREATE_INFO(compositor_realize_on_domain_color)
.additional_info("compositor_realize_on_domain_shared")
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "domain")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(compositor_realize_on_domain_vector)
.additional_info("compositor_realize_on_domain_shared")
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "domain")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(compositor_realize_on_domain_float)
.additional_info("compositor_realize_on_domain_shared")
.image(0, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "domain")
.do_static_compilation(true);

View File

@@ -0,0 +1,20 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "gpu_shader_create_info.hh"
GPU_SHADER_CREATE_INFO(compositor_screen_lens_distortion_shared)
.local_group_size(16, 16)
.push_constant(Type::VEC3, "chromatic_distortion")
.push_constant(Type::FLOAT, "scale")
.sampler(0, ImageType::FLOAT_2D, "input_image")
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image")
.compute_source("compositor_screen_lens_distortion.glsl");
GPU_SHADER_CREATE_INFO(compositor_screen_lens_distortion)
.additional_info("compositor_screen_lens_distortion_shared")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(compositor_screen_lens_distortion_jitter)
.additional_info("compositor_screen_lens_distortion_shared")
.define("JITTER")
.do_static_compilation(true);

View File

@@ -0,0 +1,23 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2022 Blender Foundation. All rights reserved. */
#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")
.sampler(1, ImageType::FLOAT_2D, "second_image")
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image")
.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

@@ -0,0 +1,51 @@
void node_composite_alpha_over_mixed(
float factor, vec4 color, vec4 over_color, float premultiply_factor, out vec4 result)
{
if (over_color.a <= 0.0) {
result = color;
}
else if (factor == 1.0 && over_color.a >= 1.0) {
result = over_color;
}
else {
float add_factor = 1.0 - premultiply_factor + over_color.a * premultiply_factor;
float premultiplier = factor * add_factor;
float multiplier = 1.0 - factor * over_color.a;
result = multiplier * color + vec2(premultiplier, factor).xxxy * over_color;
}
}
void node_composite_alpha_over_key(float factor, vec4 color, vec4 over_color, out vec4 result)
{
if (over_color.a <= 0.0) {
result = color;
}
else if (factor == 1.0 && over_color.a >= 1.0) {
result = over_color;
}
else {
float premultiplier = factor * over_color.a;
result.rgb = mix(color.rgb, over_color.rgb, premultiplier);
result.a = (1.0 - premultiplier) * color.a + factor * over_color.a;
}
}
void node_composite_alpha_over_premultiply(float factor,
vec4 color,
vec4 over_color,
out vec4 result)
{
if (over_color.a < 0.0) {
result = color;
}
else if (factor == 1.0 && over_color.a >= 1.0) {
result = over_color;
}
else {
float multiplier = 1.0 - factor * over_color.a;
result = multiplier * color + factor * over_color;
}
}

View File

@@ -0,0 +1,38 @@
#pragma BLENDER_REQUIRE(gpu_shader_common_color_utils.glsl)
/* The algorithm is by Werner D. Streidt
* (http://visca.com/ffactory/archives/5-99/msg00021.html)
* Extracted of OpenCV demhist.c
*/
#define FLT_EPSILON 1.192092896e-07F
void node_composite_bright_contrast(
vec4 color, float brightness, float contrast, const float use_premultiply, out vec4 result)
{
brightness /= 100.0;
float delta = contrast / 200.0;
float multiplier, offset;
if (contrast > 0.0) {
multiplier = 1.0 - delta * 2.0;
multiplier = 1.0 / max(multiplier, FLT_EPSILON);
offset = multiplier * (brightness - delta);
}
else {
delta *= -1.0;
multiplier = max(1.0 - delta * 2.0, 0.0);
offset = multiplier * brightness + delta;
}
if (use_premultiply != 0.0) {
color_alpha_unpremultiply(color, color);
}
result.rgb = color.rgb * multiplier + offset;
result.a = color.a;
if (use_premultiply != 0.0) {
color_alpha_premultiply(result, result);
}
}

View File

@@ -0,0 +1,52 @@
#pragma BLENDER_REQUIRE(gpu_shader_common_color_utils.glsl)
#define CMP_NODE_CHANNEL_MATTE_CS_RGB 1.0
#define CMP_NODE_CHANNEL_MATTE_CS_HSV 2.0
#define CMP_NODE_CHANNEL_MATTE_CS_YUV 3.0
#define CMP_NODE_CHANNEL_MATTE_CS_YCC 4.0
void node_composite_channel_matte(vec4 color,
const float color_space,
const float matte_channel,
const vec2 limit_channels,
float max_limit,
float min_limit,
out vec4 result,
out float matte)
{
vec4 channels;
if (color_space == CMP_NODE_CHANNEL_MATTE_CS_HSV) {
rgb_to_hsv(color, channels);
}
else if (color_space == CMP_NODE_CHANNEL_MATTE_CS_YUV) {
rgba_to_yuva_itu_709(color, channels);
}
else if (color_space == CMP_NODE_CHANNEL_MATTE_CS_YCC) {
rgba_to_ycca_itu_709(color, channels);
}
else {
channels = color;
}
float matte_value = channels[int(matte_channel)];
float limit_value = max(channels[int(limit_channels.x)], channels[int(limit_channels.y)]);
float alpha = 1.0 - (matte_value - limit_value);
if (alpha > max_limit) {
alpha = color.a;
}
else if (alpha < min_limit) {
alpha = 0.0;
}
else {
alpha = (alpha - min_limit) / (max_limit - min_limit);
}
matte = min(alpha, color.a);
result = color * matte;
}
#undef CMP_NODE_CHANNEL_MATTE_CS_RGB
#undef CMP_NODE_CHANNEL_MATTE_CS_HSV
#undef CMP_NODE_CHANNEL_MATTE_CS_YUV
#undef CMP_NODE_CHANNEL_MATTE_CS_YCC

View File

@@ -0,0 +1,42 @@
#pragma BLENDER_REQUIRE(gpu_shader_common_math_utils.glsl)
/* Algorithm from the book Video Demystified. Chapter 7. Chroma Keying. */
void node_composite_chroma_matte(vec4 color,
vec4 key,
float acceptance,
float cutoff,
float falloff,
out vec4 result,
out float matte)
{
vec4 color_ycca;
rgba_to_ycca_itu_709(color, color_ycca);
vec4 key_ycca;
rgba_to_ycca_itu_709(key, key_ycca);
/* Normalize the CrCb components into the [-1, 1] range. */
vec2 color_cc = color_ycca.yz * 2.0 - 1.0;
vec2 key_cc = key_ycca.yz * 2.0 - 1.0;
/* Rotate the color onto the space of the key such that x axis of the color space passes through
* the key color. */
color_cc = vector_to_rotation_matrix(key_cc * vec2(1.0, -1.0)) * color_cc;
/* Compute foreground key. If positive, the value is in the [0, 1] range. */
float foreground_key = color_cc.x - (abs(color_cc.y) / acceptance);
/* Negative foreground key values retain the original alpha. Positive values are scaled by the
* falloff, while colors that make an angle less than the cutoff angle get a zero alpha. */
float alpha = color.a;
if (foreground_key > 0.0) {
alpha = 1.0 - (foreground_key / falloff);
if (abs(atan(color_cc.y, color_cc.x)) < (cutoff / 2.0)) {
alpha = 0.0;
}
}
/* Compute output. */
matte = min(alpha, color.a);
result = color * matte;
}

View File

@@ -0,0 +1,34 @@
#pragma BLENDER_REQUIRE(gpu_shader_common_color_utils.glsl)
void node_composite_color_balance_lgg(
float factor, vec4 color, vec3 lift, vec3 gamma, vec3 gain, out vec4 result)
{
lift = 2.0 - lift;
vec3 srgb_color = linear_rgb_to_srgb(color.rgb);
vec3 lift_balanced = ((srgb_color - 1.0) * lift) + 1.0;
vec3 gain_balanced = lift_balanced * gain;
gain_balanced = max(gain_balanced, vec3(0.0));
vec3 linear_color = srgb_to_linear_rgb(gain_balanced);
gamma = mix(gamma, vec3(1e-6), equal(gamma, vec3(0.0)));
vec3 gamma_balanced = pow(linear_color, 1.0 / gamma);
result.rgb = mix(color.rgb, gamma_balanced, min(factor, 1.0));
result.a = color.a;
}
void node_composite_color_balance_asc_cdl(float factor,
vec4 color,
vec3 offset,
vec3 power,
vec3 slope,
float offset_basis,
out vec4 result)
{
offset += offset_basis;
vec3 balanced = color.rgb * slope + offset;
balanced = pow(max(balanced, vec3(0.0)), power);
result.rgb = mix(color.rgb, balanced, min(factor, 1.0));
result.a = color.a;
}

View File

@@ -0,0 +1,87 @@
#pragma BLENDER_REQUIRE(gpu_shader_common_math_utils.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_common_color_utils.glsl)
void node_composite_color_correction(vec4 color,
float mask,
const vec3 enabled_channels,
float start_midtones,
float end_midtones,
float master_saturation,
float master_contrast,
float master_gamma,
float master_gain,
float master_lift,
float shadows_saturation,
float shadows_contrast,
float shadows_gamma,
float shadows_gain,
float shadows_lift,
float midtones_saturation,
float midtones_contrast,
float midtones_gamma,
float midtones_gain,
float midtones_lift,
float highlights_saturation,
float highlights_contrast,
float highlights_gamma,
float highlights_gain,
float highlights_lift,
const vec3 luminance_coefficients,
out vec4 result)
{
const float margin = 0.10;
const float margin_divider = 0.5 / margin;
float level = (color.r + color.g + color.b) / 3.0;
float level_shadows = 0.0;
float level_midtones = 0.0;
float level_highlights = 0.0;
if (level < (start_midtones - margin)) {
level_shadows = 1.0;
}
else if (level < (start_midtones + margin)) {
level_midtones = ((level - start_midtones) * margin_divider) + 0.5;
level_shadows = 1.0 - level_midtones;
}
else if (level < (end_midtones - margin)) {
level_midtones = 1.0;
}
else if (level < (end_midtones + margin)) {
level_highlights = ((level - end_midtones) * margin_divider) + 0.5;
level_midtones = 1.0 - level_highlights;
}
else {
level_highlights = 1.0;
}
float contrast = level_shadows * shadows_contrast;
contrast += level_midtones * midtones_contrast;
contrast += level_highlights * highlights_contrast;
contrast *= master_contrast;
float saturation = level_shadows * shadows_saturation;
saturation += level_midtones * midtones_saturation;
saturation += level_highlights * highlights_saturation;
saturation *= master_saturation;
float gamma = level_shadows * shadows_gamma;
gamma += level_midtones * midtones_gamma;
gamma += level_highlights * highlights_gamma;
gamma *= master_gamma;
float gain = level_shadows * shadows_gain;
gain += level_midtones * midtones_gain;
gain += level_highlights * highlights_gain;
gain *= master_gain;
float lift = level_shadows * shadows_lift;
lift += level_midtones * midtones_lift;
lift += level_highlights * highlights_lift;
lift += master_lift;
float inverse_gamma = 1.0 / gamma;
float luma = get_luminance(color.rgb, luminance_coefficients);
vec3 corrected = luma + saturation * (color.rgb - luma);
corrected = 0.5 + (corrected - 0.5) * contrast;
corrected = fallback_pow(corrected * gain + lift, inverse_gamma, corrected);
corrected = mix(color.rgb, corrected, min(mask, 1.0));
result.rgb = mix(corrected, color.rgb, equal(enabled_channels, vec3(0.0)));
result.a = color.a;
}

View File

@@ -0,0 +1,27 @@
#pragma BLENDER_REQUIRE(gpu_shader_common_color_utils.glsl)
void node_composite_color_matte(vec4 color,
vec4 key,
float hue_epsilon,
float saturation_epsilon,
float value_epsilon,
out vec4 result,
out float matte)
{
vec4 color_hsva;
rgb_to_hsv(color, color_hsva);
vec4 key_hsva;
rgb_to_hsv(key, key_hsva);
bool is_within_saturation = distance(color_hsva.y, key_hsva.y) < saturation_epsilon;
bool is_within_value = distance(color_hsva.z, key_hsva.z) < value_epsilon;
bool is_within_hue = distance(color_hsva.x, key_hsva.x) < hue_epsilon;
/* Hue wraps around, so check the distance around the boundary. */
float min_hue = min(color_hsva.x, key_hsva.x);
float max_hue = max(color_hsva.x, key_hsva.x);
is_within_hue = is_within_hue || ((min_hue + (1.0 - max_hue)) < hue_epsilon);
matte = (is_within_hue && is_within_saturation && is_within_value) ? 0.0 : color.a;
result = color * matte;
}

View File

@@ -0,0 +1,13 @@
void node_composite_color_spill(vec4 color,
float factor,
float spill_channel,
vec3 spill_scale,
vec2 limit_channels,
float limit_scale,
out vec4 result)
{
float average_limit = (color[int(limit_channels.x)] + color[int(limit_channels.y)]) / 2.0;
float map = factor * color[int(spill_channel)] - limit_scale * average_limit;
result.rgb = map > 0.0 ? color.rgb + spill_scale * map : color.rgb;
result.a = color.a;
}

View File

@@ -0,0 +1,6 @@
#pragma BLENDER_REQUIRE(gpu_shader_common_color_utils.glsl)
void color_to_luminance(vec4 color, const vec3 luminance_coefficients, out float result)
{
result = get_luminance(color.rgb, luminance_coefficients);
}

View File

@@ -0,0 +1,10 @@
void node_composite_difference_matte(
vec4 color, vec4 key, float tolerance, float falloff, out vec4 result, out float matte)
{
vec4 difference = abs(color - key);
float average_difference = (difference.r + difference.g + difference.b) / 3.0;
bool is_opaque = average_difference > tolerance + falloff;
float alpha = is_opaque ? color.a : (max(0.0, average_difference - tolerance) / falloff);
matte = min(alpha, color.a);
result = color * matte;
}

View File

@@ -0,0 +1,26 @@
#pragma BLENDER_REQUIRE(gpu_shader_common_color_utils.glsl)
void node_composite_distance_matte_rgba(
vec4 color, vec4 key, float tolerance, float falloff, out vec4 result, out float matte)
{
float difference = distance(color.rgb, key.rgb);
bool is_opaque = difference > tolerance + falloff;
float alpha = is_opaque ? color.a : max(0.0, difference - tolerance) / falloff;
matte = min(alpha, color.a);
result = color * matte;
}
void node_composite_distance_matte_ycca(
vec4 color, vec4 key, float tolerance, float falloff, out vec4 result, out float matte)
{
vec4 color_ycca;
rgba_to_ycca_itu_709(color, color_ycca);
vec4 key_ycca;
rgba_to_ycca_itu_709(key, key_ycca);
float difference = distance(color_ycca.yz, key_ycca.yz);
bool is_opaque = difference > tolerance + falloff;
float alpha = is_opaque ? color.a : max(0.0, difference - tolerance) / falloff;
matte = min(alpha, color.a);
result = color * matte;
}

Some files were not shown because too many files have changed in this diff Show More