Welcome to mirror list, hosted at ThFree Co, Russian Federation.

shader_operation.cc « intern « realtime_compositor « compositor « blender « source - git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 8e52baf63ec2c032b7a7ddd43570ebd6be4d8200 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
/* SPDX-License-Identifier: GPL-2.0-or-later */

#include <memory>
#include <string>

#include "BLI_listbase.h"
#include "BLI_map.hh"
#include "BLI_string_ref.hh"
#include "BLI_utildefines.h"

#include "DNA_customdata_types.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(&construct_material, &generate_code, this);
  GPU_material_status_set(material_, GPU_MAT_QUEUED);
  GPU_material_compile(material_);
}

ShaderOperation::~ShaderOperation()
{
  GPU_material_free_single(material_);
}

void ShaderOperation::execute()
{
  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_threads_at_least(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_socket)
{
  return output_sockets_to_output_identifiers_map_.lookup(output_socket);
}

Map<std::string, DOutputSocket> &ShaderOperation::get_inputs_to_linked_outputs_map()
{
  return inputs_to_linked_outputs_map_;
}

void ShaderOperation::compute_results_reference_counts(const Schedule &schedule)
{
  for (const auto &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 curve and ramp nodes. */
  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)
{
  /* Attributes represents the inputs of the operation and their names match those of the inputs of
   * the operation as well as the corresponding texture samples in the shader. */
  ListBase attributes = GPU_material_attributes(material_);
  LISTBASE_FOREACH (GPUMaterialAttribute *, attribute, &attributes) {
    get_input(attribute->name).bind_as_texture(shader, attribute->name);
  }
}

void ShaderOperation::bind_outputs(GPUShader *shader)
{
  for (StringRefNull output_identifier : output_sockets_to_output_identifiers_map_.values()) {
    get_result(output_identifier).bind_as_image(shader, output_identifier.c_str());
  }
}

void ShaderOperation::construct_material(void *thunk, GPUMaterial *material)
{
  ShaderOperation *operation = static_cast<ShaderOperation *>(thunk);
  for (DNode node : operation->compile_unit_) {
    ShaderNode *shader_node = node->typeinfo->get_compositor_shader_node(node);
    operation->shader_nodes_.add_new(node, std::unique_ptr<ShaderNode>(shader_node));

    operation->link_node_inputs(node, material);

    shader_node->compile(material);

    operation->populate_results_for_node(node, material);
  }
}

void ShaderOperation::link_node_inputs(DNode node, GPUMaterial *material)
{
  for (const bNodeSocket *input : node->input_sockets()) {
    const DInputSocket dinput{node.context(), input};

    /* 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) {
      continue;
    }

    /* 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);
      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, material);
  }
}

void ShaderOperation::link_node_input_internal(DInputSocket input_socket,
                                               DOutputSocket output_socket)
{
  ShaderNode &output_node = *shader_nodes_.lookup(output_socket.node());
  GPUNodeStack &output_stack = output_node.get_output(output_socket->identifier);

  ShaderNode &input_node = *shader_nodes_.lookup(input_socket.node());
  GPUNodeStack &input_stack = input_node.get_input(input_socket->identifier);

  input_stack.link = output_stack.link;
}

void ShaderOperation::link_node_input_external(DInputSocket input_socket,
                                               DOutputSocket output_socket,
                                               GPUMaterial *material)
{

  ShaderNode &node = *shader_nodes_.lookup(input_socket.node());
  GPUNodeStack &stack = node.get_input(input_socket->identifier);

  /* An input was already declared for that same output socket, so no need to declare it again. */
  if (!output_to_material_attribute_map_.contains(output_socket)) {
    declare_operation_input(input_socket, output_socket, material);
  }

  /* Link the attribute representing the shader operation input corresponding to the given output
   * 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::Vector:
      return "set_rgb";
    case ResultType::Color:
      return "set_rgba";
  }

  BLI_assert_unreachable();
  return nullptr;
}

void ShaderOperation::declare_operation_input(DInputSocket input_socket,
                                              DOutputSocket output_socket,
                                              GPUMaterial *material)
{
  const int input_index = output_to_material_attribute_map_.size();
  std::string input_identifier = "input" + std::to_string(input_index);

  /* Declare the input descriptor for this input and prefer to declare its type to be the same as
   * the type of the output socket because doing type conversion in the shader is much cheaper. */
  InputDescriptor input_descriptor = input_descriptor_from_input_socket(input_socket.bsocket());
  input_descriptor.type = get_node_socket_result_type(output_socket.bsocket());
  declare_input_descriptor(input_identifier, input_descriptor);

  /* Add a new GPU attribute representing an input to the GPU material. Instead of using the
   * attribute directly, we link it to an appropriate set function and use its output link instead.
   * This is needed because the `gputype` member of the attribute is only initialized if it is
   * linked to a GPU node. */
  GPUNodeLink *attribute_link;
  GPU_link(material,
           get_set_function_name(input_descriptor.type),
           GPU_attribute(material, CD_AUTO_FROM_NAME, input_identifier.c_str()),
           &attribute_link);

  /* Map the output socket to the attribute that was created for it. */
  output_to_material_attribute_map_.add(output_socket, attribute_link);

  /* Map the identifier of the operation input to the output socket it is linked to. */
  inputs_to_linked_outputs_map_.add_new(input_identifier, output_socket);
}

void ShaderOperation::populate_results_for_node(DNode node, GPUMaterial *material)
{
  for (const bNodeSocket *output : node->output_sockets()) {
    const DOutputSocket doutput{node.context(), output};

    /* If any of the nodes linked to the output are not part of the shader operation, then an
     * output result needs to be populated for it. */
    const bool need_to_populate_result = is_output_linked_to_node_conditioned(
        doutput, [&](DNode node) { return !compile_unit_.contains(node); });

    if (need_to_populate_result) {
      populate_operation_result(doutput, material);
    }
  }
}

static const char *get_store_function_name(ResultType type)
{
  switch (type) {
    case ResultType::Float:
      return "node_compositor_store_output_float";
    case ResultType::Vector:
      return "node_compositor_store_output_vector";
    case ResultType::Color:
      return "node_compositor_store_output_color";
  }

  BLI_assert_unreachable();
  return nullptr;
}

void ShaderOperation::populate_operation_result(DOutputSocket output_socket, GPUMaterial *material)
{
  const unsigned int output_id = output_sockets_to_output_identifiers_map_.size();
  std::string output_identifier = "output" + std::to_string(output_id);

  const ResultType result_type = get_node_socket_result_type(output_socket.bsocket());
  const Result result = Result(result_type, texture_pool());
  populate_result(output_identifier, result);

  /* Map the output socket to the identifier of the newly populated result. */
  output_sockets_to_output_identifiers_map_.add_new(output_socket, output_identifier);

  ShaderNode &node = *shader_nodes_.lookup(output_socket.node());
  GPUNodeLink *output_link = node.get_output(output_socket->identifier).link;

  /* Link the output node stack to an output storer storing in the appropriate result. The result
   * is identified by its index in the operation and the index is encoded as a float to be passed
   * to the GPU function. Additionally, create an output link from the storer node to declare as an
   * output to the GPU material. This storer output link is a dummy link in the sense that its
   * value is ignored since it is already written in the output, but it is used to track nodes that
   * contribute to the output of the compositor node tree. */
  GPUNodeLink *storer_output_link;
  GPUNodeLink *id_link = GPU_constant((float *)&output_id);
  const char *store_function_name = get_store_function_name(result_type);
  GPU_link(material, store_function_name, id_link, output_link, &storer_output_link);

  /* Declare the output link of the storer node as an output of the GPU material to help the GPU
   * code generator to track the nodes that contribute to the output of the shader. */
  GPU_material_add_output_link_composite(material, storer_output_link);
}

using namespace gpu::shader;

void ShaderOperation::generate_code(void *thunk,
                                    GPUMaterial *material,
                                    GPUCodegenOutput *code_generator_output)
{
  ShaderOperation *operation = static_cast<ShaderOperation *>(thunk);
  ShaderCreateInfo &shader_create_info = *reinterpret_cast<ShaderCreateInfo *>(
      code_generator_output->create_info);

  shader_create_info.local_group_size(16, 16);

  /* The resources are added without explicit locations, so make sure it is done by the
   * shader creator. */
  shader_create_info.auto_resource_location(true);

  /* Add implementation for implicit conversion operations inserted by the code generator. This
   * file should include the functions [float|vec3|vec4]_from_[float|vec3|vec4]. */
  shader_create_info.typedef_source("gpu_shader_compositor_type_conversion.glsl");

  /* The source shader is a compute shader with a main function that calls the dynamically
   * generated evaluate function. The evaluate function includes the serialized GPU material graph
   * preceded by code that initialized the inputs of the operation. Additionally, the storer
   * functions that writes the outputs are defined outside the evaluate function. */
  shader_create_info.compute_source("gpu_shader_compositor_main.glsl");

  /* The main function is emitted in the shader before the evaluate function, so the evaluate
   * function needs to be forward declared here. */
  shader_create_info.typedef_source_generated += "void evaluate();\n";

  operation->generate_code_for_outputs(shader_create_info);

  shader_create_info.compute_source_generated += "void evaluate()\n{\n";

  operation->generate_code_for_inputs(material, shader_create_info);

  shader_create_info.compute_source_generated += code_generator_output->composite;

  shader_create_info.compute_source_generated += "}\n";
}

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;
}

/* Texture storers in the shader always take a vec4 as an argument, so encode each type in a vec4
 * appropriately. */
static const char *glsl_store_expression_from_result_type(ResultType type)
{
  switch (type) {
    case ResultType::Float:
      return "vec4(value)";
    case ResultType::Vector:
      return "vec4(vector, 0.0)";
    case ResultType::Color:
      return "color";
  }

  BLI_assert_unreachable();
  return nullptr;
}

void ShaderOperation::generate_code_for_outputs(ShaderCreateInfo &shader_create_info)
{
  const std::string store_float_function_header = "void store_float(const uint id, float value)";
  const std::string store_vector_function_header = "void store_vector(const uint id, vec3 vector)";
  const std::string store_color_function_header = "void store_color(const uint id, vec4 color)";

  /* The store functions are used by the node_compositor_store_output_[float|vector|color]
   * functions but are only defined later as part of the compute source, so they need to be forward
   * declared. */
  shader_create_info.typedef_source_generated += store_float_function_header + ";\n";
  shader_create_info.typedef_source_generated += store_vector_function_header + ";\n";
  shader_create_info.typedef_source_generated += store_color_function_header + ";\n";

  /* Each of the store functions is essentially a single switch case on the given ID, so start by
   * opening the function with a curly bracket followed by opening a switch statement in each of
   * the functions. */
  std::stringstream store_float_function;
  std::stringstream store_vector_function;
  std::stringstream store_color_function;
  const std::string store_function_start = "\n{\n  switch (id) {\n";
  store_float_function << store_float_function_header << store_function_start;
  store_vector_function << store_vector_function_header << store_function_start;
  store_color_function << store_color_function_header << store_function_start;

  for (StringRefNull output_identifier : output_sockets_to_output_identifiers_map_.values()) {
    const Result &result = get_result(output_identifier);

    /* Add a write-only image for this output where its values will be written. */
    shader_create_info.image(0,
                             texture_format_from_result_type(result.type()),
                             Qualifier::WRITE,
                             ImageType::FLOAT_2D,
                             output_identifier,
                             Frequency::BATCH);

    /* Add a case for the index of this output followed by a break statement. */
    std::stringstream case_code;
    const std::string store_expression = glsl_store_expression_from_result_type(result.type());
    const std::string texel = ", ivec2(gl_GlobalInvocationID.xy), ";
    case_code << "    case " << StringRef(output_identifier).drop_known_prefix("output") << ":\n"
              << "      imageStore(" << output_identifier << texel << store_expression << ");\n"
              << "      break;\n";

    /* Only add the case to the function with the matching type. */
    switch (result.type()) {
      case ResultType::Float:
        store_float_function << case_code.str();
        break;
      case ResultType::Vector:
        store_vector_function << case_code.str();
        break;
      case ResultType::Color:
        store_color_function << case_code.str();
        break;
    }
  }

  /* Close the previously opened switch statement as well as the function itself. */
  const std::string store_function_end = "  }\n}\n\n";
  store_float_function << store_function_end;
  store_vector_function << store_function_end;
  store_color_function << store_function_end;

  shader_create_info.compute_source_generated += store_float_function.str() +
                                                 store_vector_function.str() +
                                                 store_color_function.str();
}

static const char *glsl_type_from_result_type(ResultType type)
{
  switch (type) {
    case ResultType::Float:
      return "float";
    case ResultType::Vector:
      return "vec3";
    case ResultType::Color:
      return "vec4";
  }

  BLI_assert_unreachable();
  return nullptr;
}

/* Texture loaders in the shader always return a vec4, so a swizzle is needed to retrieve the
 * actual value for each type. */
static const char *glsl_swizzle_from_result_type(ResultType type)
{
  switch (type) {
    case ResultType::Float:
      return "x";
    case ResultType::Vector:
      return "xyz";
    case ResultType::Color:
      return "rgba";
  }

  BLI_assert_unreachable();
  return nullptr;
}

void ShaderOperation::generate_code_for_inputs(GPUMaterial *material,
                                               ShaderCreateInfo &shader_create_info)
{
  /* The attributes of the GPU material represents the inputs of the operation. */
  ListBase attributes = GPU_material_attributes(material);

  if (BLI_listbase_is_empty(&attributes)) {
    return;
  }

  /* Add a texture sampler for each of the inputs with the same name as the attribute. */
  LISTBASE_FOREACH (GPUMaterialAttribute *, attribute, &attributes) {
    shader_create_info.sampler(0, ImageType::FLOAT_2D, attribute->name, Frequency::BATCH);
  }

  /* Declare a struct called var_attrs that includes an appropriately typed member for each of the
   * inputs. The names of the members should be the letter v followed by the ID of the attribute
   * corresponding to the input. Such names are expected by the code generator. */
  std::stringstream declare_attributes;
  declare_attributes << "struct {\n";
  LISTBASE_FOREACH (GPUMaterialAttribute *, attribute, &attributes) {
    const InputDescriptor &input_descriptor = get_input_descriptor(attribute->name);
    const std::string type = glsl_type_from_result_type(input_descriptor.type);
    declare_attributes << "  " << type << " v" << attribute->id << ";\n";
  }
  declare_attributes << "} var_attrs;\n\n";

  shader_create_info.compute_source_generated += declare_attributes.str();

  /* The texture loader utilities are needed to sample the input textures and initialize the
   * attributes. */
  shader_create_info.typedef_source("gpu_shader_compositor_texture_utilities.glsl");

  /* Initialize each member of the previously declared struct by loading its corresponding texture
   * with an appropriate swizzle for its type. */
  std::stringstream initialize_attributes;
  LISTBASE_FOREACH (GPUMaterialAttribute *, attribute, &attributes) {
    const InputDescriptor &input_descriptor = get_input_descriptor(attribute->name);
    const std::string swizzle = glsl_swizzle_from_result_type(input_descriptor.type);
    initialize_attributes << "var_attrs.v" << attribute->id << " = "
                          << "texture_load(" << attribute->name
                          << ", ivec2(gl_GlobalInvocationID.xy))." << swizzle << ";\n";
  }
  initialize_attributes << "\n";

  shader_create_info.compute_source_generated += initialize_attributes.str();
}

}  // namespace blender::realtime_compositor