Blender V4.3
shader_operation.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include <memory>
6#include <string>
7
8#include "BLI_assert.h"
9#include "BLI_listbase.h"
10#include "BLI_map.hh"
11#include "BLI_string_ref.hh"
12#include "BLI_utildefines.h"
13
15
16#include "GPU_context.hh"
17#include "GPU_material.hh"
18#include "GPU_shader.hh"
19#include "GPU_texture.hh"
20#include "GPU_uniform_buffer.hh"
21
23
26
27#include "COM_context.hh"
28#include "COM_operation.hh"
30#include "COM_result.hh"
31#include "COM_scheduler.hh"
32#include "COM_shader_node.hh"
34#include "COM_utilities.hh"
35
36#include <sstream>
37
39
40using namespace nodes::derived_node_tree_types;
41
43 PixelCompileUnit &compile_unit,
44 const Schedule &schedule)
45 : PixelOperation(context, compile_unit, schedule)
46{
48 GPU_MAT_COMPOSITOR, &construct_material, &generate_code, this);
50 GPU_material_compile(material_);
51}
52
57
59{
60 const Domain domain = compute_domain();
62 Result &result = get_result(identifier);
63 result.allocate_texture(domain);
64 }
65
66 GPUShader *shader = GPU_material_get_shader(material_);
67 GPU_shader_bind(shader);
68
69 bind_material_resources(shader);
70 bind_inputs(shader);
71 bind_outputs(shader);
72
73 compute_dispatch_threads_at_least(shader, domain.size);
74
79}
80
81void ShaderOperation::bind_material_resources(GPUShader *shader)
82{
83 /* Bind the uniform buffer of the material if it exists. It may not exist if the GPU material has
84 * no uniforms. */
85 GPUUniformBuf *ubo = GPU_material_uniform_buffer_get(material_);
86 if (ubo) {
88 }
89
90 /* Bind color band textures needed by curve and ramp nodes. */
91 ListBase textures = GPU_material_textures(material_);
92 LISTBASE_FOREACH (GPUMaterialTexture *, texture, &textures) {
93 if (texture->colorband) {
94 const int texture_image_unit = GPU_shader_get_sampler_binding(shader, texture->sampler_name);
95 GPU_texture_bind(*texture->colorband, texture_image_unit);
96 }
97 }
98}
99
100void ShaderOperation::bind_inputs(GPUShader *shader)
101{
102 /* Attributes represents the inputs of the operation and their names match those of the inputs of
103 * the operation as well as the corresponding texture samples in the shader. */
104 ListBase attributes = GPU_material_attributes(material_);
105 LISTBASE_FOREACH (GPUMaterialAttribute *, attribute, &attributes) {
106 get_input(attribute->name).bind_as_texture(shader, attribute->name);
107 }
108}
109
110void ShaderOperation::bind_outputs(GPUShader *shader)
111{
112 for (StringRefNull output_identifier : output_sockets_to_output_identifiers_map_.values()) {
113 get_result(output_identifier).bind_as_image(shader, output_identifier.c_str());
114 }
115}
116
117void ShaderOperation::construct_material(void *thunk, GPUMaterial *material)
118{
119 ShaderOperation *operation = static_cast<ShaderOperation *>(thunk);
120 operation->material_ = material;
121 for (DNode node : operation->compile_unit_) {
122 ShaderNode *shader_node = node->typeinfo->get_compositor_shader_node(node);
123 operation->shader_nodes_.add_new(node, std::unique_ptr<ShaderNode>(shader_node));
124
125 operation->link_node_inputs(node);
126
127 shader_node->compile(material);
128
129 operation->populate_results_for_node(node);
130 }
131}
132
133void ShaderOperation::link_node_inputs(DNode node)
134{
135 for (const bNodeSocket *input : node->input_sockets()) {
136 const DInputSocket dinput{node.context(), input};
137
138 /* Get the output linked to the input. If it is null, that means the input is unlinked.
139 * Unlinked inputs are linked by the node compile method, so skip this here. */
140 const DOutputSocket doutput = get_output_linked_to_input(dinput);
141 if (!doutput) {
142 continue;
143 }
144
145 /* If the origin node is part of the shader operation, then the link is internal to the GPU
146 * material graph and is linked appropriately. */
147 if (compile_unit_.contains(doutput.node())) {
148 link_node_input_internal(dinput, doutput);
149 continue;
150 }
151
152 /* Otherwise, the origin node is not part of the shader operation, then the link is external to
153 * the GPU material graph and an input to the shader operation must be declared and linked to
154 * the node input. */
155 link_node_input_external(dinput, doutput);
156 }
157}
158
159void ShaderOperation::link_node_input_internal(DInputSocket input_socket,
160 DOutputSocket output_socket)
161{
162 ShaderNode &output_node = *shader_nodes_.lookup(output_socket.node());
163 GPUNodeStack &output_stack = output_node.get_output(output_socket->identifier);
164
165 ShaderNode &input_node = *shader_nodes_.lookup(input_socket.node());
166 GPUNodeStack &input_stack = input_node.get_input(input_socket->identifier);
167
168 input_stack.link = output_stack.link;
169}
170
171void ShaderOperation::link_node_input_external(DInputSocket input_socket,
172 DOutputSocket output_socket)
173{
174
175 ShaderNode &node = *shader_nodes_.lookup(input_socket.node());
176 GPUNodeStack &stack = node.get_input(input_socket->identifier);
177
178 if (!output_to_material_attribute_map_.contains(output_socket)) {
179 /* No input was declared for that output yet, so declare it. */
180 declare_operation_input(input_socket, output_socket);
181 }
182 else {
183 /* An input was already declared for that same output socket, so no need to declare it again.
184 * But we update the domain priority of the input descriptor to be the higher priority of the
185 * existing descriptor and the descriptor of the new input socket. That's because the same
186 * output might be connected to multiple inputs inside the shader operation which have
187 * different proprieties. */
188 const std::string input_identifier = outputs_to_declared_inputs_map_.lookup(output_socket);
189 InputDescriptor &input_descriptor = this->get_input_descriptor(input_identifier);
190 input_descriptor.domain_priority = math::min(
191 input_descriptor.domain_priority,
193 }
194
195 /* Link the attribute representing the shader operation input corresponding to the given output
196 * socket. */
197 stack.link = output_to_material_attribute_map_.lookup(output_socket);
198}
199
200static const char *get_set_function_name(ResultType type)
201{
202 switch (type) {
204 return "set_value";
206 return "set_rgb";
208 return "set_rgba";
209 default:
210 /* Other types are internal and needn't be handled by operations. */
211 break;
212 }
213
215 return nullptr;
216}
217
218void ShaderOperation::declare_operation_input(DInputSocket input_socket,
219 DOutputSocket output_socket)
220{
221 const int input_index = output_to_material_attribute_map_.size();
222 std::string input_identifier = "input" + std::to_string(input_index);
223
224 /* Declare the input descriptor for this input and prefer to declare its type to be the same as
225 * the type of the output socket because doing type conversion in the shader is much cheaper. */
226 InputDescriptor input_descriptor = input_descriptor_from_input_socket(input_socket.bsocket());
227 input_descriptor.type = get_node_socket_result_type(output_socket.bsocket());
228 declare_input_descriptor(input_identifier, input_descriptor);
229
230 /* Add a new GPU attribute representing an input to the GPU material. Instead of using the
231 * attribute directly, we link it to an appropriate set function and use its output link instead.
232 * This is needed because the `gputype` member of the attribute is only initialized if it is
233 * linked to a GPU node. */
234 GPUNodeLink *attribute_link;
235 GPU_link(material_,
236 get_set_function_name(input_descriptor.type),
237 GPU_attribute(material_, CD_AUTO_FROM_NAME, input_identifier.c_str()),
238 &attribute_link);
239
240 /* Map the output socket to the attribute that was created for it. */
241 output_to_material_attribute_map_.add(output_socket, attribute_link);
242
243 /* Map the identifier of the operation input to the output socket it is linked to. */
244 inputs_to_linked_outputs_map_.add_new(input_identifier, output_socket);
245
246 /* Map the output socket to the identifier of the operation input that was declared for it. */
247 outputs_to_declared_inputs_map_.add_new(output_socket, input_identifier);
248}
249
250void ShaderOperation::populate_results_for_node(DNode node)
251{
252 const DOutputSocket preview_output = find_preview_output_socket(node);
253
254 for (const bNodeSocket *output : node->output_sockets()) {
255 const DOutputSocket doutput{node.context(), output};
256
257 /* If any of the nodes linked to the output are not part of the shader operation but are part
258 * of the execution schedule, then an output result needs to be populated for it. */
259 const bool is_operation_output = is_output_linked_to_node_conditioned(
260 doutput,
261 [&](DNode node) { return schedule_.contains(node) && !compile_unit_.contains(node); });
262
263 /* If the output is used as the node preview, then an output result needs to be populated for
264 * it, and we additionally keep track of that output to later compute the previews from. */
265 const bool is_preview_output = doutput == preview_output;
266 if (is_preview_output) {
267 preview_outputs_.add(doutput);
268 }
269
270 if (is_operation_output || is_preview_output) {
271 populate_operation_result(doutput);
272 }
273 }
274}
275
276static const char *get_store_function_name(ResultType type)
277{
278 switch (type) {
280 return "node_compositor_store_output_float";
282 return "node_compositor_store_output_vector";
284 return "node_compositor_store_output_color";
285 default:
286 /* Other types are internal and needn't be handled by operations. */
287 break;
288 }
289
291 return nullptr;
292}
293
294void ShaderOperation::populate_operation_result(DOutputSocket output_socket)
295{
297 std::string output_identifier = "output" + std::to_string(output_id);
298
299 const ResultType result_type = get_node_socket_result_type(output_socket.bsocket());
300 const Result result = context().create_result(result_type);
301 populate_result(output_identifier, result);
302
303 /* Map the output socket to the identifier of the newly populated result. */
304 output_sockets_to_output_identifiers_map_.add_new(output_socket, output_identifier);
305
306 ShaderNode &node = *shader_nodes_.lookup(output_socket.node());
307 GPUNodeLink *output_link = node.get_output(output_socket->identifier).link;
308
309 /* Link the output node stack to an output storer storing in the appropriate result. The result
310 * is identified by its index in the operation and the index is encoded as a float to be passed
311 * to the GPU function. Additionally, create an output link from the storer node to declare as an
312 * output to the GPU material. This storer output link is a dummy link in the sense that its
313 * value is ignored since it is already written in the output, but it is used to track nodes that
314 * contribute to the output of the compositor node tree. */
315 GPUNodeLink *storer_output_link;
316 GPUNodeLink *id_link = GPU_constant((float *)&output_id);
317 const char *store_function_name = get_store_function_name(result_type);
318 GPU_link(material_, store_function_name, id_link, output_link, &storer_output_link);
319
320 /* Declare the output link of the storer node as an output of the GPU material to help the GPU
321 * code generator to track the nodes that contribute to the output of the shader. */
322 GPU_material_add_output_link_composite(material_, storer_output_link);
323}
324
325using namespace gpu::shader;
326
327void ShaderOperation::generate_code(void *thunk,
328 GPUMaterial *material,
329 GPUCodegenOutput *code_generator_output)
330{
331 ShaderOperation *operation = static_cast<ShaderOperation *>(thunk);
332 ShaderCreateInfo &shader_create_info = *reinterpret_cast<ShaderCreateInfo *>(
333 code_generator_output->create_info);
334
335 shader_create_info.local_group_size(16, 16);
336
337 /* The resources are added without explicit locations, so make sure it is done by the shader
338 * creator. */
339 shader_create_info.auto_resource_location(true);
340
341 /* Add implementation for implicit conversion operations inserted by the code generator. This
342 * file should include the functions [float|vec3|vec4]_from_[float|vec3|vec4]. */
343 shader_create_info.typedef_source("gpu_shader_compositor_type_conversion.glsl");
344
345 /* The source shader is a compute shader with a main function that calls the dynamically
346 * generated evaluate function. The evaluate function includes the serialized GPU material graph
347 * preceded by code that initialized the inputs of the operation. Additionally, the storer
348 * functions that writes the outputs are defined outside the evaluate function. */
349 shader_create_info.compute_source("gpu_shader_compositor_main.glsl");
350
351 /* The main function is emitted in the shader before the evaluate function, so the evaluate
352 * function needs to be forward declared here.
353 * NOTE(Metal): Metal does not require forward declarations. */
355 shader_create_info.typedef_source_generated += "void evaluate();\n";
356 }
357
358 operation->generate_code_for_outputs(shader_create_info);
359
360 shader_create_info.compute_source_generated += "void evaluate()\n{\n";
361
362 operation->generate_code_for_inputs(material, shader_create_info);
363
364 shader_create_info.compute_source_generated += code_generator_output->composite;
365
366 shader_create_info.compute_source_generated += "}\n";
367}
368
369/* Texture storers in the shader always take a vec4 as an argument, so encode each type in a vec4
370 * appropriately. */
372{
373 switch (type) {
375 return "vec4(value)";
377 return "vec4(vector, 0.0)";
379 return "color";
380 default:
381 /* Other types are internal and needn't be handled by operations. */
382 break;
383 }
384
386 return nullptr;
387}
388
389void ShaderOperation::generate_code_for_outputs(ShaderCreateInfo &shader_create_info)
390{
391 const std::string store_float_function_header = "void store_float(const uint id, float value)";
392 const std::string store_vector_function_header = "void store_vector(const uint id, vec3 vector)";
393 const std::string store_color_function_header = "void store_color(const uint id, vec4 color)";
394
395 /* The store functions are used by the node_compositor_store_output_[float|vector|color]
396 * functions but are only defined later as part of the compute source, so they need to be forward
397 * declared.
398 * NOTE(Metal): Metal does not require forward declarations. */
400 shader_create_info.typedef_source_generated += store_float_function_header + ";\n";
401 shader_create_info.typedef_source_generated += store_vector_function_header + ";\n";
402 shader_create_info.typedef_source_generated += store_color_function_header + ";\n";
403 }
404
405 /* Each of the store functions is essentially a single switch case on the given ID, so start by
406 * opening the function with a curly bracket followed by opening a switch statement in each of
407 * the functions. */
408 std::stringstream store_float_function;
409 std::stringstream store_vector_function;
410 std::stringstream store_color_function;
411 const std::string store_function_start = "\n{\n switch (id) {\n";
412 store_float_function << store_float_function_header << store_function_start;
413 store_vector_function << store_vector_function_header << store_function_start;
414 store_color_function << store_color_function_header << store_function_start;
415
416 for (StringRefNull output_identifier : output_sockets_to_output_identifiers_map_.values()) {
417 const Result &result = get_result(output_identifier);
418
419 /* Add a write-only image for this output where its values will be written. */
420 shader_create_info.image(0,
421 result.get_gpu_texture_format(),
422 Qualifier::WRITE,
423 ImageType::FLOAT_2D,
424 output_identifier,
425 Frequency::PASS);
426
427 /* Add a case for the index of this output followed by a break statement. */
428 std::stringstream case_code;
429 const std::string store_expression = glsl_store_expression_from_result_type(result.type());
430 const std::string texel = ", ivec2(gl_GlobalInvocationID.xy), ";
431 case_code << " case " << StringRef(output_identifier).drop_known_prefix("output") << ":\n"
432 << " imageStore(" << output_identifier << texel << store_expression << ");\n"
433 << " break;\n";
434
435 /* Only add the case to the function with the matching type. */
436 switch (result.type()) {
438 store_float_function << case_code.str();
439 break;
441 store_vector_function << case_code.str();
442 break;
444 store_color_function << case_code.str();
445 break;
446 default:
447 /* Other types are internal and needn't be handled by operations. */
449 break;
450 }
451 }
452
453 /* Close the previously opened switch statement as well as the function itself. */
454 const std::string store_function_end = " }\n}\n\n";
455 store_float_function << store_function_end;
456 store_vector_function << store_function_end;
457 store_color_function << store_function_end;
458
459 shader_create_info.compute_source_generated += store_float_function.str() +
460 store_vector_function.str() +
461 store_color_function.str();
462}
463
465{
466 switch (type) {
468 return "float";
470 return "vec3";
472 return "vec4";
473 default:
474 /* Other types are internal and needn't be handled by operations. */
475 break;
476 }
477
479 return nullptr;
480}
481
482/* Texture loaders in the shader always return a vec4, so a swizzle is needed to retrieve the
483 * actual value for each type. */
485{
486 switch (type) {
488 return "x";
490 return "xyz";
492 return "rgba";
493 default:
494 /* Other types are internal and needn't be handled by operations. */
495 break;
496 }
497
499 return nullptr;
500}
501
502void ShaderOperation::generate_code_for_inputs(GPUMaterial *material,
503 ShaderCreateInfo &shader_create_info)
504{
505 /* The attributes of the GPU material represents the inputs of the operation. */
506 ListBase attributes = GPU_material_attributes(material);
507
508 if (BLI_listbase_is_empty(&attributes)) {
509 return;
510 }
511
512 /* Add a texture sampler for each of the inputs with the same name as the attribute. */
513 LISTBASE_FOREACH (GPUMaterialAttribute *, attribute, &attributes) {
514 shader_create_info.sampler(0, ImageType::FLOAT_2D, attribute->name, Frequency::PASS);
515 }
516
517 /* Declare a struct called var_attrs that includes an appropriately typed member for each of the
518 * inputs. The names of the members should be the letter v followed by the ID of the attribute
519 * corresponding to the input. Such names are expected by the code generator. */
520 std::stringstream declare_attributes;
521 declare_attributes << "struct {\n";
522 LISTBASE_FOREACH (GPUMaterialAttribute *, attribute, &attributes) {
523 const InputDescriptor &input_descriptor = get_input_descriptor(attribute->name);
524 const std::string type = glsl_type_from_result_type(input_descriptor.type);
525 declare_attributes << " " << type << " v" << attribute->id << ";\n";
526 }
527 declare_attributes << "} var_attrs;\n\n";
528
529 shader_create_info.compute_source_generated += declare_attributes.str();
530
531 /* The texture loader utilities are needed to sample the input textures and initialize the
532 * attributes. */
533 shader_create_info.typedef_source("gpu_shader_compositor_texture_utilities.glsl");
534
535 /* Initialize each member of the previously declared struct by loading its corresponding texture
536 * with an appropriate swizzle for its type. */
537 std::stringstream initialize_attributes;
538 LISTBASE_FOREACH (GPUMaterialAttribute *, attribute, &attributes) {
539 const InputDescriptor &input_descriptor = get_input_descriptor(attribute->name);
540 const std::string swizzle = glsl_swizzle_from_result_type(input_descriptor.type);
541 initialize_attributes << "var_attrs.v" << attribute->id << " = "
542 << "texture_load(" << attribute->name
543 << ", ivec2(gl_GlobalInvocationID.xy))." << swizzle << ";\n";
544 }
545 initialize_attributes << "\n";
546
547 shader_create_info.compute_source_generated += initialize_attributes.str();
548}
549
550} // namespace blender::realtime_compositor
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
BLI_INLINE bool BLI_listbase_is_empty(const struct ListBase *lb)
#define LISTBASE_FOREACH(type, var, list)
unsigned int uint
@ CD_AUTO_FROM_NAME
eGPUBackendType GPU_backend_get_type()
GPUNodeLink * GPU_constant(const float *num)
void GPU_material_compile(GPUMaterial *mat)
ListBase GPU_material_attributes(const GPUMaterial *material)
void GPU_material_status_set(GPUMaterial *mat, eGPUMaterialStatus status)
void GPU_material_free_single(GPUMaterial *material)
@ GPU_MAT_COMPOSITOR
GPUShader * GPU_material_get_shader(GPUMaterial *material)
ListBase GPU_material_textures(GPUMaterial *material)
void GPU_material_add_output_link_composite(GPUMaterial *material, GPUNodeLink *link)
GPUMaterial * GPU_material_from_callbacks(eGPUMaterialEngine engine, ConstructGPUMaterialFn construct_function_cb, GPUCodegenCallbackFn generate_code_function_cb, void *thunk)
@ GPU_MAT_QUEUED
GPUNodeLink * GPU_attribute(GPUMaterial *mat, eCustomDataType type, const char *name)
bool GPU_link(GPUMaterial *mat, const char *name,...)
GPUUniformBuf * GPU_material_uniform_buffer_get(GPUMaterial *material)
int GPU_shader_get_sampler_binding(GPUShader *shader, const char *name)
int GPU_shader_get_ubo_binding(GPUShader *shader, const char *name)
void GPU_shader_bind(GPUShader *shader)
void GPU_shader_unbind()
void GPU_texture_bind(GPUTexture *texture, int unit)
void GPU_texture_image_unbind_all()
void GPU_texture_unbind_all()
#define GPU_UBO_BLOCK_NAME
void GPU_uniformbuf_debug_unbind_all()
void GPU_uniformbuf_bind(GPUUniformBuf *ubo, int slot)
struct GPUShader GPUShader
virtual void compile(SVMCompiler &compiler)=0
bool add(const Key &key, const Value &value)
Definition BLI_map.hh:271
const Value & lookup(const Key &key) const
Definition BLI_map.hh:506
ValueIterator values() const
Definition BLI_map.hh:846
void add_new(const Key &key, const Value &value)
Definition BLI_map.hh:241
int64_t size() const
Definition BLI_map.hh:927
bool contains(const Key &key) const
Definition BLI_map.hh:329
bool add(const Key &key)
bool contains(const Key &key) const
Result create_result(ResultType type, ResultPrecision precision)
Result & get_input(StringRef identifier) const
Definition operation.cc:144
Result & get_result(StringRef identifier)
Definition operation.cc:46
InputDescriptor & get_input_descriptor(StringRef identifier)
Definition operation.cc:164
void populate_result(StringRef identifier, Result result)
Definition operation.cc:154
void declare_input_descriptor(StringRef identifier, InputDescriptor descriptor)
Definition operation.cc:159
Map< DOutputSocket, std::string > output_sockets_to_output_identifiers_map_
Map< std::string, DOutputSocket > inputs_to_linked_outputs_map_
void bind_as_image(GPUShader *shader, const char *image_name, bool read=false) const
Definition result.cc:264
void bind_as_texture(GPUShader *shader, const char *texture_name) const
Definition result.cc:253
ShaderOperation(Context &context, PixelCompileUnit &compile_unit, const Schedule &schedule)
Material material
T min(const T &a, const T &b)
bool is_output_linked_to_node_conditioned(DOutputSocket output, FunctionRef< bool(DNode)> condition)
Definition utilities.cc:78
static const char * glsl_swizzle_from_result_type(ResultType type)
static const char * get_set_function_name(ResultType type)
DOutputSocket get_output_linked_to_input(DInputSocket input)
Definition utilities.cc:47
DOutputSocket find_preview_output_socket(const DNode &node)
Definition utilities.cc:161
ResultType get_node_socket_result_type(const bNodeSocket *socket)
Definition utilities.cc:63
InputDescriptor input_descriptor_from_input_socket(const bNodeSocket *socket)
Definition utilities.cc:109
static const char * get_store_function_name(ResultType type)
static const char * glsl_store_expression_from_result_type(ResultType type)
void compute_dispatch_threads_at_least(GPUShader *shader, int2 threads_range, int2 local_size=int2(16))
Definition utilities.cc:131
static const char * glsl_type_from_result_type(ResultType type)
std::string composite
GPUShaderCreateInfo * create_info
GPUNodeLink * link