Refactor: Compositor: Use GPU setter nodes for unlinked sockets
This patch refactors how values of unlinked sockets are provided to nodes. Previously, the GPU node stack values were initialized at construction time and linked by the node's compile methods. This meant that we needed to handle implicit conversion if the socket is linked to an unlinked node group input. Alternatively, we now insert a GPU setter node for each unlinked socket that carries the value and type of the origin socket, and let the GPU code generator do implicit conversion at the shader level. This has three advantages: - It makes it easier to add new types since we no longer have to handle those types in shader node code and it reduces code duplication. - It makes the code more inline with how we implement multi-function procedures. So refactoring is easier. - It opens the door to implement things like implicit inputs, which will be needed later for things like texture nodes.
This commit is contained in:
@@ -24,10 +24,9 @@ using namespace nodes::derived_node_tree_types;
|
||||
* 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.hh 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. */
|
||||
* more information. The compiler is expected to initialize the input links of the node inputs
|
||||
* 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. */
|
||||
|
||||
@@ -109,13 +109,19 @@ class ShaderOperation : public PixelOperation {
|
||||
* operation, they are exposed as outputs to the shader operation itself. */
|
||||
static void construct_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. */
|
||||
/* Link the inputs of the node if needed. Unlinked inputs will be linked to constant values. 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);
|
||||
|
||||
/* Link the GPU stack of the given unlinked input to a constant value setter GPU node that
|
||||
* supplies the value of the unlinked input. The value us taken from the given origin input,
|
||||
* which will be equal to the input in most cases, but can also be an unlinked input of a group
|
||||
* node */
|
||||
void link_node_input_constant(const DInputSocket input, const DInputSocket origin);
|
||||
|
||||
/* Given the input socket of a node that is part of the shader operation which is linked to the
|
||||
* given output socket of a node that is also part of the shader operation, just link the output
|
||||
* link of the GPU node stack of the output socket to the input link of the GPU node stack of the
|
||||
|
||||
@@ -22,8 +22,8 @@ using namespace nodes::derived_node_tree_types;
|
||||
|
||||
ShaderNode::ShaderNode(DNode node) : node_(node)
|
||||
{
|
||||
populate_inputs();
|
||||
populate_outputs();
|
||||
this->populate_inputs();
|
||||
this->populate_outputs();
|
||||
}
|
||||
|
||||
void ShaderNode::compile(GPUMaterial *material)
|
||||
@@ -61,139 +61,20 @@ static eGPUType gpu_type_from_socket_type(eNodeSocketDatatype type)
|
||||
}
|
||||
}
|
||||
|
||||
/* Initializes the vector value of the given GPU node stack from the default value of the given
|
||||
* socket. Note that the type of the stack may not match that of the socket, so perform implicit
|
||||
* conversion if needed. */
|
||||
static void gpu_stack_vector_from_socket(GPUNodeStack &stack, const bNodeSocket *socket)
|
||||
{
|
||||
const eNodeSocketDatatype input_type = static_cast<eNodeSocketDatatype>(socket->type);
|
||||
const eNodeSocketDatatype expected_type = static_cast<eNodeSocketDatatype>(stack.sockettype);
|
||||
|
||||
switch (input_type) {
|
||||
case SOCK_FLOAT: {
|
||||
const float value = socket->default_value_typed<bNodeSocketValueFloat>()->value;
|
||||
switch (expected_type) {
|
||||
case SOCK_FLOAT:
|
||||
stack.vec[0] = value;
|
||||
return;
|
||||
case SOCK_INT:
|
||||
/* GPUMaterial doesn't support int, so it is passed as a float. */
|
||||
stack.vec[0] = float(float_to_int(value));
|
||||
return;
|
||||
case SOCK_VECTOR:
|
||||
copy_v3_v3(stack.vec, float_to_float3(value));
|
||||
return;
|
||||
case SOCK_RGBA:
|
||||
copy_v4_v4(stack.vec, float_to_color(value));
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_INT: {
|
||||
const int value = socket->default_value_typed<bNodeSocketValueInt>()->value;
|
||||
switch (expected_type) {
|
||||
case SOCK_FLOAT:
|
||||
stack.vec[0] = int_to_float(value);
|
||||
return;
|
||||
case SOCK_INT:
|
||||
/* GPUMaterial doesn't support int, so it is passed as a float. */
|
||||
stack.vec[0] = float(value);
|
||||
return;
|
||||
case SOCK_VECTOR:
|
||||
copy_v3_v3(stack.vec, int_to_float3(value));
|
||||
return;
|
||||
case SOCK_RGBA:
|
||||
copy_v4_v4(stack.vec, int_to_color(value));
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_VECTOR: {
|
||||
const float3 value = float3(socket->default_value_typed<bNodeSocketValueVector>()->value);
|
||||
switch (expected_type) {
|
||||
case SOCK_FLOAT:
|
||||
stack.vec[0] = float3_to_float(value);
|
||||
return;
|
||||
case SOCK_INT:
|
||||
/* GPUMaterial doesn't support int, so it is passed as a float. */
|
||||
stack.vec[0] = float(float3_to_int(value));
|
||||
return;
|
||||
case SOCK_VECTOR:
|
||||
copy_v3_v3(stack.vec, value);
|
||||
return;
|
||||
case SOCK_RGBA:
|
||||
copy_v4_v4(stack.vec, float3_to_color(value));
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_RGBA: {
|
||||
const float4 value = socket->default_value_typed<bNodeSocketValueRGBA>()->value;
|
||||
switch (expected_type) {
|
||||
case SOCK_FLOAT:
|
||||
stack.vec[0] = color_to_float(value);
|
||||
return;
|
||||
case SOCK_INT:
|
||||
/* GPUMaterial doesn't support int, so it is passed as a float. */
|
||||
stack.vec[0] = float(color_to_int(value));
|
||||
return;
|
||||
case SOCK_VECTOR:
|
||||
copy_v3_v3(stack.vec, color_to_float3(value));
|
||||
return;
|
||||
case SOCK_RGBA:
|
||||
copy_v4_v4(stack.vec, value);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
/* Unsupported sockets are skipped by GPU material compiler and we needn't initialize their
|
||||
* value. This is flagged by using the GPU_NONE type, see gpu_type_from_socket_type function
|
||||
* for more information. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
/* This will be initialized by the GPU material compiler if needed. */
|
||||
zero_v4(stack.vec);
|
||||
|
||||
stack.sockettype = socket->type;
|
||||
stack.type = gpu_type_from_socket_type((eNodeSocketDatatype)socket->type);
|
||||
stack.type = gpu_type_from_socket_type(static_cast<eNodeSocketDatatype>(socket->type));
|
||||
|
||||
if (socket->is_input()) {
|
||||
const DInputSocket input(socket);
|
||||
|
||||
DSocket origin = get_input_origin_socket(input);
|
||||
|
||||
/* The input is linked if the origin socket 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 either be an
|
||||
* unlinked input or an unlinked input of a group input node that the socket is linked to,
|
||||
* otherwise, get the value from the socket itself. */
|
||||
if (origin->is_input()) {
|
||||
gpu_stack_vector_from_socket(stack, origin.bsocket());
|
||||
}
|
||||
else {
|
||||
gpu_stack_vector_from_socket(stack, socket.bsocket());
|
||||
}
|
||||
}
|
||||
else {
|
||||
stack.hasoutput = socket->is_logically_linked();
|
||||
}
|
||||
stack.hasinput = socket->is_logically_linked();
|
||||
stack.hasoutput = socket->is_logically_linked();
|
||||
}
|
||||
|
||||
void ShaderNode::populate_inputs()
|
||||
|
||||
@@ -128,30 +128,108 @@ void ShaderOperation::construct_material(void *thunk, GPUMaterial *material)
|
||||
|
||||
void ShaderOperation::link_node_inputs(DNode node)
|
||||
{
|
||||
for (const bNodeSocket *input : node->input_sockets()) {
|
||||
const DInputSocket dinput{node.context(), input};
|
||||
for (int i = 0; i < node->input_sockets().size(); i++) {
|
||||
const DInputSocket input{node.context(), node->input_sockets()[i]};
|
||||
|
||||
/* 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 doutput = get_output_linked_to_input(dinput);
|
||||
if (!doutput) {
|
||||
if (!input->is_available()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* The origin socket is an input, that means the input is unlinked and we link a constant
|
||||
* setter node for it. */
|
||||
const DSocket origin = get_input_origin_socket(input);
|
||||
if (origin->is_input()) {
|
||||
this->link_node_input_constant(input, DInputSocket(origin));
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Otherwise, the origin socket is an output, which means it is linked. */
|
||||
const DOutputSocket output = DOutputSocket(origin);
|
||||
|
||||
/* If the origin node is part of the shader operation, then the link is internal to the GPU
|
||||
* material graph and is linked appropriately. */
|
||||
if (compile_unit_.contains(doutput.node())) {
|
||||
link_node_input_internal(dinput, doutput);
|
||||
if (compile_unit_.contains(output.node())) {
|
||||
this->link_node_input_internal(input, output);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Otherwise, the origin node is not part of the shader operation, then the link is external to
|
||||
* the GPU material graph and an input to the shader operation must be declared and linked to
|
||||
* the node input. */
|
||||
link_node_input_external(dinput, doutput);
|
||||
this->link_node_input_external(input, output);
|
||||
}
|
||||
}
|
||||
|
||||
/* Initializes the vector value of the given GPU node stack from the default value of the given
|
||||
* input socket. */
|
||||
static void initialize_input_stack_value(const DInputSocket input, GPUNodeStack &stack)
|
||||
{
|
||||
switch (input->type) {
|
||||
case SOCK_FLOAT: {
|
||||
const float value = input->default_value_typed<bNodeSocketValueFloat>()->value;
|
||||
stack.vec[0] = value;
|
||||
break;
|
||||
}
|
||||
case SOCK_INT: {
|
||||
/* GPUMaterial doesn't support int, so it is stored as a float. */
|
||||
const int value = input->default_value_typed<bNodeSocketValueInt>()->value;
|
||||
stack.vec[0] = int(value);
|
||||
break;
|
||||
}
|
||||
case SOCK_VECTOR: {
|
||||
const float3 value = float3(input->default_value_typed<bNodeSocketValueVector>()->value);
|
||||
copy_v3_v3(stack.vec, value);
|
||||
break;
|
||||
}
|
||||
case SOCK_RGBA: {
|
||||
const float4 value = float4(input->default_value_typed<bNodeSocketValueRGBA>()->value);
|
||||
copy_v4_v4(stack.vec, value);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *get_set_function_name(const ResultType type)
|
||||
{
|
||||
switch (type) {
|
||||
case ResultType::Float:
|
||||
return "set_value";
|
||||
case ResultType::Int:
|
||||
/* GPUMaterial doesn't support int, so it is passed as a float. */
|
||||
return "set_value";
|
||||
case ResultType::Float3:
|
||||
return "set_rgb";
|
||||
case ResultType::Color:
|
||||
return "set_rgba";
|
||||
case ResultType::Float4:
|
||||
return "set_rgba";
|
||||
case ResultType::Float2:
|
||||
case ResultType::Int2:
|
||||
/* Those types are internal and needn't be handled by operations. */
|
||||
break;
|
||||
}
|
||||
|
||||
BLI_assert_unreachable();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ShaderOperation::link_node_input_constant(const DInputSocket input, const DInputSocket origin)
|
||||
{
|
||||
ShaderNode &node = *shader_nodes_.lookup(input.node());
|
||||
GPUNodeStack &stack = node.get_input(input->identifier);
|
||||
|
||||
/* Create a uniform link that carry the value of the origin. */
|
||||
initialize_input_stack_value(origin, stack);
|
||||
GPUNodeLink *link = GPU_uniform(stack.vec);
|
||||
|
||||
const ResultType type = get_node_socket_result_type(origin.bsocket());
|
||||
const char *function_name = get_set_function_name(type);
|
||||
GPU_link(material_, function_name, link, &stack.link);
|
||||
}
|
||||
|
||||
void ShaderOperation::link_node_input_internal(DInputSocket input_socket,
|
||||
DOutputSocket output_socket)
|
||||
{
|
||||
@@ -196,30 +274,6 @@ void ShaderOperation::link_node_input_external(DInputSocket input_socket,
|
||||
stack.link = output_to_material_attribute_map_.lookup(output_socket);
|
||||
}
|
||||
|
||||
static const char *get_set_function_name(ResultType type)
|
||||
{
|
||||
switch (type) {
|
||||
case ResultType::Float:
|
||||
return "set_value";
|
||||
case ResultType::Int:
|
||||
/* GPUMaterial doesn't support int, so it is passed as a float. */
|
||||
return "set_value";
|
||||
case ResultType::Float3:
|
||||
return "set_rgb";
|
||||
case ResultType::Color:
|
||||
return "set_rgba";
|
||||
case ResultType::Float4:
|
||||
return "set_rgba";
|
||||
case ResultType::Float2:
|
||||
case ResultType::Int2:
|
||||
/* Those types are internal and needn't be handled by operations. */
|
||||
break;
|
||||
}
|
||||
|
||||
BLI_assert_unreachable();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ShaderOperation::declare_operation_input(DInputSocket input_socket,
|
||||
DOutputSocket output_socket)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user