Compare commits
28 Commits
temp-asset
...
temp-viewp
Author | SHA1 | Date | |
---|---|---|---|
3de2575ffb | |||
2356c3f4cd | |||
78cf05c7e8 | |||
82bb7ee943 | |||
443e24204e | |||
9fa78b3e8b | |||
082c87bf96 | |||
034cea1547 | |||
580675b0b4 | |||
2bb58991ad | |||
08a1425947 | |||
f1de6d9348 | |||
59f5d10d73 | |||
67650fb999 | |||
dbb1afffc8 | |||
af0d66c164 | |||
c469934ba9 | |||
da2fb695b3 | |||
5877e33f23 | |||
30132dec01 | |||
48006f8b5f | |||
da8844d73e | |||
c0c31994ab | |||
1328d9a575 | |||
2e8e7bd7b9 | |||
562fd83b91 | |||
e9ce424492 | |||
e21f04070e |
@@ -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"),
|
||||
),
|
||||
)
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -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 ¶ms);
|
||||
|
||||
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;
|
||||
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
66
source/blender/compositor/realtime_compositor/CMakeLists.txt
Normal file
66
source/blender/compositor/realtime_compositor/CMakeLists.txt
Normal 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}")
|
@@ -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
|
71
source/blender/compositor/realtime_compositor/COM_context.hh
Normal file
71
source/blender/compositor/realtime_compositor/COM_context.hh
Normal 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
|
@@ -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
|
167
source/blender/compositor/realtime_compositor/COM_domain.hh
Normal file
167
source/blender/compositor/realtime_compositor/COM_domain.hh
Normal 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
|
173
source/blender/compositor/realtime_compositor/COM_evaluator.hh
Normal file
173
source/blender/compositor/realtime_compositor/COM_evaluator.hh
Normal 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
|
@@ -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
|
@@ -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
|
@@ -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
|
175
source/blender/compositor/realtime_compositor/COM_operation.hh
Normal file
175
source/blender/compositor/realtime_compositor/COM_operation.hh
Normal 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
|
@@ -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
|
@@ -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
|
234
source/blender/compositor/realtime_compositor/COM_result.hh
Normal file
234
source/blender/compositor/realtime_compositor/COM_result.hh
Normal 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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
258
source/blender/compositor/realtime_compositor/intern/result.cc
Normal file
258
source/blender/compositor/realtime_compositor/intern/result.cc
Normal 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
|
@@ -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 Sethi–Ullman 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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
||||
)
|
||||
|
||||
|
203
source/blender/draw/engines/compositor/compositor_engine.cc
Normal file
203
source/blender/draw/engines/compositor/compositor_engine.cc
Normal 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 */
|
||||
};
|
||||
}
|
13
source/blender/draw/engines/compositor/compositor_engine.h
Normal file
13
source/blender/draw/engines/compositor/compositor_engine.h
Normal 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
|
@@ -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
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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:
|
||||
|
@@ -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 "")
|
||||
|
@@ -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
|
||||
|
@@ -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]);
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
|
@@ -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";
|
||||
}
|
||||
|
@@ -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_);
|
||||
|
@@ -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");
|
||||
}
|
||||
};
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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,
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
20
source/blender/gpu/shaders/compositor/compositor_blur.glsl
Normal file
20
source/blender/gpu/shaders/compositor/compositor_blur.glsl
Normal 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);
|
||||
}
|
@@ -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));
|
||||
}
|
@@ -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);
|
||||
}
|
@@ -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));
|
||||
}
|
22
source/blender/gpu/shaders/compositor/compositor_filter.glsl
Normal file
22
source/blender/gpu/shaders/compositor/compositor_filter.glsl
Normal 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);
|
||||
}
|
15
source/blender/gpu/shaders/compositor/compositor_flip.glsl
Normal file
15
source/blender/gpu/shaders/compositor/compositor_flip.glsl
Normal 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));
|
||||
}
|
@@ -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));
|
||||
}
|
@@ -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));
|
||||
}
|
@@ -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));
|
||||
}
|
@@ -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));
|
||||
}
|
@@ -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);
|
||||
}
|
@@ -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);
|
@@ -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);
|
@@ -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);
|
@@ -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);
|
@@ -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);
|
@@ -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);
|
@@ -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);
|
@@ -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);
|
@@ -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);
|
@@ -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);
|
@@ -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);
|
@@ -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);
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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
|
@@ -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;
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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);
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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
Reference in New Issue
Block a user