Blender V4.3
ocio_color_space_conversion_shader.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 <cstdint>
6#include <memory>
7#include <string>
8
9#include "BLI_assert.h"
10#include "BLI_hash.hh"
11#include "BLI_map.hh"
12#include "BLI_string_ref.hh"
13#include "BLI_vector.hh"
14#include "BLI_vector_set.hh"
15
16#include "GPU_capabilities.hh"
17#include "GPU_shader.hh"
18#include "GPU_texture.hh"
19#include "GPU_uniform_buffer.hh"
20
22
23#include "COM_context.hh"
25#include "COM_result.hh"
26
27#if defined(WITH_OCIO)
28# include <OpenColorIO/OpenColorIO.h>
29#endif
30
32
33/* ------------------------------------------------------------------------------------------------
34 * OCIO Color Space Conversion Shader Key.
35 */
36
38 const std::string &source, const std::string &target, const std::string &config_cache_id)
39 : source(source), target(target), config_cache_id(config_cache_id)
40{
41}
42
47
50{
51 return a.source == b.source && a.target == b.target && a.config_cache_id == b.config_cache_id;
52}
53
54/* --------------------------------------------------------------------
55 * GPU Shader Creator.
56 */
57
58#if defined(WITH_OCIO)
59
60namespace OCIO = OCIO_NAMESPACE;
61using namespace blender::gpu::shader;
62
63/* A subclass of OCIO::GpuShaderCreator that constructs the shader using a ShaderCreateInfo. The
64 * Create method should be used to construct the creator, then the extractGpuShaderInfo() method of
65 * the appropriate OCIO::GPUProcessor should be called passing in the creator. After construction,
66 * the constructed compute shader can be used by calling the bind_shader_and_resources() method,
67 * followed by binding the input texture and output image using their names input_sampler_name()
68 * and output_image_name(), following by dispatching the shader on the domain of the input, and
69 * finally calling the unbind_shader_and_resources() method.
70 *
71 * Upon calling the extractGpuShaderInfo(), all the transforms in the GPU processor will add their
72 * needed resources by calling the respective addUniform() and add[3D]Texture() methods. Then, the
73 * shader code of all transforms will be generated and passed to the createShaderText() method,
74 * generating the full code of the processor. Finally, the finalize() method will be called to
75 * finally create the shader. */
76class GPUShaderCreator : public OCIO::GpuShaderCreator {
77 public:
78 static std::shared_ptr<GPUShaderCreator> Create(ResultPrecision precision)
79 {
80 std::shared_ptr<GPUShaderCreator> instance = std::make_shared<GPUShaderCreator>();
81 instance->setLanguage(OCIO::GPU_LANGUAGE_GLSL_4_0);
82 instance->precision_ = precision;
83 return instance;
84 }
85
86 /* Not used, but needs to be overridden, so return a nullptr. */
87 OCIO::GpuShaderCreatorRcPtr clone() const override
88 {
89 return OCIO::GpuShaderCreatorRcPtr();
90 }
91
92 /* This is ignored since we query using our own GPU capabilities system. */
93 void setTextureMaxWidth(uint /*max_width*/) override {}
94
95 uint getTextureMaxWidth() const noexcept override
96 {
97 return GPU_max_texture_size();
98 }
99
100# if OCIO_VERSION_HEX >= 0x02030000
101 void setAllowTexture1D(bool allowed) override
102 {
103 allow_texture_1D_ = allowed;
104 }
105
106 bool getAllowTexture1D() const override
107 {
108 return allow_texture_1D_;
109 }
110# endif
111
112 bool addUniform(const char *name, const DoubleGetter &get_double) override
113 {
114 /* Check if a resource exists with the same name and assert if it is the case, returning false
115 * indicates failure to add the uniform for the shader creator. */
116 if (!resource_names_.add(std::make_unique<std::string>(name))) {
118 return false;
119 }
120
121 /* Don't use the name argument directly since ShaderCreateInfo only stores references to
122 * resource names, instead, use the name that is stored in resource_names_. */
123 std::string &resource_name = *resource_names_[resource_names_.size() - 1];
124 shader_create_info_.push_constant(Type::FLOAT, resource_name);
125
126 float_uniforms_.add(resource_name, get_double);
127
128 return true;
129 }
130
131 bool addUniform(const char *name, const BoolGetter &get_bool) override
132 {
133 /* Check if a resource exists with the same name and assert if it is the case, returning false
134 * indicates failure to add the uniform for the shader creator. */
135 if (!resource_names_.add(std::make_unique<std::string>(name))) {
137 return false;
138 }
139
140 /* Don't use the name argument directly since ShaderCreateInfo only stores references to
141 * resource names, instead, use the name that is stored in resource_names_. */
142 const std::string &resource_name = *resource_names_[resource_names_.size() - 1];
143 shader_create_info_.push_constant(Type::BOOL, resource_name);
144
145 boolean_uniforms_.add(name, get_bool);
146
147 return true;
148 }
149
150 bool addUniform(const char *name, const Float3Getter &get_float3) override
151 {
152 /* Check if a resource exists with the same name and assert if it is the case, returning false
153 * indicates failure to add the uniform for the shader creator. */
154 if (!resource_names_.add(std::make_unique<std::string>(name))) {
156 return false;
157 }
158
159 /* Don't use the name argument directly since ShaderCreateInfo only stores references to
160 * resource names, instead, use the name that is stored in resource_names_. */
161 std::string &resource_name = *resource_names_[resource_names_.size() - 1];
162 shader_create_info_.push_constant(Type::VEC3, resource_name);
163
164 vector_uniforms_.add(resource_name, get_float3);
165
166 return true;
167 }
168
169 bool addUniform(const char *name,
170 const SizeGetter &get_size,
171 const VectorFloatGetter &get_vector_float) override
172 {
173 /* Check if a resource exists with the same name and assert if it is the case, returning false
174 * indicates failure to add the uniform for the shader creator. */
175 if (!resource_names_.add(std::make_unique<std::string>(name))) {
177 return false;
178 }
179
180 /* Don't use the name argument directly since ShaderCreateInfo only stores references to
181 * resource names, instead, use the name that is stored in resource_names_. */
182 std::string &resource_name = *resource_names_[resource_names_.size() - 1];
183 shader_create_info_.uniform_buf(buffers_sizes_.size(), "float", resource_name);
184
185 float_buffers_.add(resource_name, get_vector_float);
186 buffers_sizes_.add(resource_name, get_size);
187
188 return true;
189 }
190
191 bool addUniform(const char *name,
192 const SizeGetter &get_size,
193 const VectorIntGetter &get_vector_int) override
194 {
195 /* Check if a resource exists with the same name and assert if it is the case, returning false
196 * indicates failure to add the uniform for the shader creator. */
197 if (!resource_names_.add(std::make_unique<std::string>(name))) {
199 return false;
200 }
201
202 /* Don't use the name argument directly since ShaderCreateInfo only stores references to
203 * resource names, instead, use the name that is stored in resource_names_. */
204 std::string &resource_name = *resource_names_[resource_names_.size() - 1];
205 shader_create_info_.uniform_buf(buffers_sizes_.size(), "int", resource_name);
206
207 int_buffers_.add(name, get_vector_int);
208 buffers_sizes_.add(name, get_size);
209
210 return true;
211 }
212
213 void addTexture(const char *texture_name,
214 const char *sampler_name,
215 uint width,
216 uint height,
217 TextureType channel,
218# if OCIO_VERSION_HEX >= 0x02030000
219 OCIO::GpuShaderDesc::TextureDimensions dimensions,
220# endif
221 OCIO::Interpolation interpolation,
222 const float *values) override
223 {
224 /* Check if a resource exists with the same name and assert if it is the case. */
225 if (!resource_names_.add(std::make_unique<std::string>(sampler_name))) {
227 }
228
229 /* Don't use the name argument directly since ShaderCreateInfo only stores references to
230 * resource names, instead, use the name that is stored in resource_names_. */
231 const std::string &resource_name = *resource_names_[resource_names_.size() - 1];
232
233 GPUTexture *texture;
234 const ResultType result_type = (channel == TEXTURE_RGB_CHANNEL) ? ResultType::Float3 :
235 ResultType::Float;
236 const eGPUTextureFormat texture_format = Result::gpu_texture_format(result_type, precision_);
237 /* A height of 1 indicates a 1D texture according to the OCIO API. */
238# if OCIO_VERSION_HEX >= 0x02030000
239 if (dimensions == OCIO::GpuShaderDesc::TEXTURE_1D) {
240# else
241 if (height == 1) {
242# endif
243 texture = GPU_texture_create_1d(
244 texture_name, width, 1, texture_format, GPU_TEXTURE_USAGE_SHADER_READ, values);
245 shader_create_info_.sampler(textures_.size() + 1, ImageType::FLOAT_1D, resource_name);
246 }
247 else {
248 texture = GPU_texture_create_2d(
249 texture_name, width, height, 1, texture_format, GPU_TEXTURE_USAGE_SHADER_READ, values);
250 shader_create_info_.sampler(textures_.size() + 1, ImageType::FLOAT_2D, resource_name);
251 }
252 GPU_texture_filter_mode(texture, interpolation != OCIO::INTERP_NEAREST);
253
254 textures_.add(sampler_name, texture);
255 }
256
257 void add3DTexture(const char *texture_name,
258 const char *sampler_name,
259 uint size,
260 OCIO::Interpolation interpolation,
261 const float *values) override
262 {
263 /* Check if a resource exists with the same name and assert if it is the case. */
264 if (!resource_names_.add(std::make_unique<std::string>(sampler_name))) {
266 }
267
268 /* Don't use the name argument directly since ShaderCreateInfo only stores references to
269 * resource names, instead, use the name that is stored in resource_names_. */
270 const std::string &resource_name = *resource_names_[resource_names_.size() - 1];
271 shader_create_info_.sampler(textures_.size() + 1, ImageType::FLOAT_3D, resource_name);
272
273 GPUTexture *texture = GPU_texture_create_3d(
274 texture_name,
275 size,
276 size,
277 size,
278 1,
281 values);
282 GPU_texture_filter_mode(texture, interpolation != OCIO::INTERP_NEAREST);
283
284 textures_.add(sampler_name, texture);
285 }
286
287 /* This gets called before the finalize() method to construct the shader code. We just
288 * concatenate the code except for the declarations section. That's because the ShaderCreateInfo
289 * will add the declaration itself. */
290 void createShaderText(const char * /*declarations*/,
291 const char *helper_methods,
292 const char *function_header,
293 const char *function_body,
294 const char *function_footer) override
295 {
296 shader_code_ += helper_methods;
297 shader_code_ += function_header;
298 shader_code_ += function_body;
299 shader_code_ += function_footer;
300 }
301
302 /* This gets called when all resources were added using the respective addUniform() or
303 * add[3D]Texture() methods and the shader code was generated using the createShaderText()
304 * method. That is, we are ready to complete the ShaderCreateInfo and create a shader from it. */
305 void finalize() override
306 {
307 GpuShaderCreator::finalize();
308
309 shader_create_info_.local_group_size(16, 16);
310 shader_create_info_.sampler(0, ImageType::FLOAT_2D, input_sampler_name());
311 shader_create_info_.image(0,
313 Qualifier::WRITE,
314 ImageType::FLOAT_2D,
316 shader_create_info_.compute_source("gpu_shader_compositor_ocio_processor.glsl");
317 shader_create_info_.compute_source_generated += shader_code_;
318
319 GPUShaderCreateInfo *info = reinterpret_cast<GPUShaderCreateInfo *>(&shader_create_info_);
320 shader_ = GPU_shader_create_from_info(info);
321 }
322
324 {
325 if (!shader_) {
326 return nullptr;
327 }
328
329 GPU_shader_bind(shader_);
330
331 for (auto item : float_uniforms_.items()) {
332 GPU_shader_uniform_1f(shader_, item.key.c_str(), item.value());
333 }
334
335 for (auto item : boolean_uniforms_.items()) {
336 GPU_shader_uniform_1b(shader_, item.key.c_str(), item.value());
337 }
338
339 for (auto item : vector_uniforms_.items()) {
340 GPU_shader_uniform_3fv(shader_, item.key.c_str(), item.value().data());
341 }
342
343 for (auto item : float_buffers_.items()) {
344 GPUUniformBuf *buffer = GPU_uniformbuf_create_ex(
345 buffers_sizes_.lookup(item.key)(), item.value(), item.key.c_str());
346 const int ubo_location = GPU_shader_get_ubo_binding(shader_, item.key.c_str());
347 GPU_uniformbuf_bind(buffer, ubo_location);
348 uniform_buffers_.append(buffer);
349 }
350
351 for (auto item : int_buffers_.items()) {
352 GPUUniformBuf *buffer = GPU_uniformbuf_create_ex(
353 buffers_sizes_.lookup(item.key)(), item.value(), item.key.c_str());
354 const int ubo_location = GPU_shader_get_ubo_binding(shader_, item.key.c_str());
355 GPU_uniformbuf_bind(buffer, ubo_location);
356 uniform_buffers_.append(buffer);
357 }
358
359 for (auto item : textures_.items()) {
360 const int texture_image_unit = GPU_shader_get_sampler_binding(shader_, item.key.c_str());
361 GPU_texture_bind(item.value, texture_image_unit);
362 }
363
364 return shader_;
365 }
366
368 {
369 for (GPUUniformBuf *buffer : uniform_buffers_) {
370 GPU_uniformbuf_unbind(buffer);
371 GPU_uniformbuf_free(buffer);
372 }
373
374 for (GPUTexture *texture : textures_.values()) {
375 GPU_texture_unbind(texture);
376 }
377
379 }
380
381 const char *input_sampler_name()
382 {
383 return "input_tx";
384 }
385
386 const char *output_image_name()
387 {
388 return "output_img";
389 }
390
391 ~GPUShaderCreator() override
392 {
393 for (GPUTexture *texture : textures_.values()) {
394 GPU_texture_free(texture);
395 }
396
397 GPU_shader_free(shader_);
398 }
399
400 private:
401 /* The processor shader and the ShaderCreateInfo used to construct it. Constructed and
402 * initialized in the finalize() method. */
403 GPUShader *shader_ = nullptr;
404 ShaderCreateInfo shader_create_info_ = ShaderCreateInfo("OCIO Processor");
405
406 /* Stores the generated OCIOMain function as well as a number of helper functions. Initialized in
407 * the createShaderText() method. */
408 std::string shader_code_;
409
410 /* Maps that associates the name of a uniform with a getter function that returns its value.
411 * Initialized in the respective addUniform() methods. */
412 Map<std::string, DoubleGetter> float_uniforms_;
413 Map<std::string, BoolGetter> boolean_uniforms_;
414 Map<std::string, Float3Getter> vector_uniforms_;
415
416 /* Maps that associates the name of uniform buffer objects with a getter function that returns
417 * its values. Initialized in the respective addUniform() methods. */
418 Map<std::string, VectorFloatGetter> float_buffers_;
419 Map<std::string, VectorIntGetter> int_buffers_;
420
421 /* A map that associates the name of uniform buffer objects with a getter functions that returns
422 * its number of elements. Initialized in the respective addUniform() methods. */
423 Map<std::string, SizeGetter> buffers_sizes_;
424
425 /* A map that associates the name of a sampler with its corresponding texture. Initialized in the
426 * addTexture() and add3DTexture() methods. */
427 Map<std::string, GPUTexture *> textures_;
428
429 /* A vector set that stores the names of all the resources used by the shader. This is used to:
430 * 1. Check for name collisions when adding new resources.
431 * 2. Store the resource names throughout the construction of the shader since the
432 * ShaderCreateInfo class only stores references to resources names. */
434
435 /* A vectors that stores the created uniform buffers when bind_shader_and_resources() is called,
436 * so that they can be properly unbound and freed in the unbind_shader_and_resources() method. */
437 Vector<GPUUniformBuf *> uniform_buffers_;
438
439# if OCIO_VERSION_HEX >= 0x02030000
440 /* Allow creating 1D textures, or only use 2D textures. */
441 bool allow_texture_1D_ = true;
442# endif
443
444 /* The precision of the OCIO resources as well as the output image. */
445 ResultPrecision precision_;
446};
447
448#else
449
450/* A stub implementation in case OCIO is disabled at build time. */
452 public:
453 static std::shared_ptr<GPUShaderCreator> Create(ResultPrecision /*precision*/)
454 {
455 return std::make_shared<GPUShaderCreator>();
456 }
457
459 {
460 return nullptr;
461 }
462
464
465 const char *input_sampler_name()
466 {
467 return nullptr;
468 }
469
470 const char *output_image_name()
471 {
472 return nullptr;
473 }
474};
475
476#endif
477
478/* --------------------------------------------------------------------
479 * OCIO Color Space Conversion Shader.
480 */
481
483 std::string source,
484 std::string target)
485{
486 /* Create a GPU shader creator and construct it based on the transforms in the default GPU
487 * processor. */
488 shader_creator_ = GPUShaderCreator::Create(context.get_precision());
489
490#if defined(WITH_OCIO)
491 /* Get a GPU processor that transforms the source color space to the target color space. */
492 try {
493 OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
494 OCIO::ConstProcessorRcPtr processor = config->getProcessor(source.c_str(), target.c_str());
495 OCIO::ConstGPUProcessorRcPtr gpu_processor = processor->getDefaultGPUProcessor();
496
497 auto ocio_shader_creator = std::static_pointer_cast<OCIO::GpuShaderCreator>(shader_creator_);
498 gpu_processor->extractGpuShaderInfo(ocio_shader_creator);
499 }
500 catch (const OCIO::Exception &) {
501 }
502#else
503 UNUSED_VARS(source, target);
504#endif
505}
506
508{
509 return shader_creator_->bind_shader_and_resources();
510}
511
513{
514 shader_creator_->unbind_shader_and_resources();
515}
516
518{
519 return shader_creator_->input_sampler_name();
520}
521
523{
524 return shader_creator_->output_image_name();
525}
526
527/* --------------------------------------------------------------------
528 * OCIO Color Space Conversion Shader Container.
529 */
530
532{
533 /* First, delete all resources that are no longer needed. */
534 map_.remove_if([](auto item) { return !item.value->needed; });
535
536 /* Second, reset the needed status of the remaining resources to false to ready them to track
537 * their needed status for the next evaluation. */
538 for (auto &value : map_.values()) {
539 value->needed = false;
540 }
541}
542
544 std::string source,
545 std::string target)
546{
547#if defined(WITH_OCIO)
548 /* Use the config cache ID in the cache key in case the configuration changed at runtime. */
549 std::string config_cache_id = OCIO::GetCurrentConfig()->getCacheID();
550#else
551 std::string config_cache_id;
552#endif
553
554 const OCIOColorSpaceConversionShaderKey key(source, target, config_cache_id);
555
556 OCIOColorSpaceConversionShader &shader = *map_.lookup_or_add_cb(key, [&]() {
557 return std::make_unique<OCIOColorSpaceConversionShader>(context, source, target);
558 });
559
560 shader.needed = true;
561 return shader;
562}
563
564} // namespace blender::realtime_compositor
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
unsigned int uint
#define UNUSED_VARS(...)
int GPU_max_texture_size()
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_uniform_1f(GPUShader *sh, const char *name, float value)
void GPU_shader_uniform_3fv(GPUShader *sh, const char *name, const float data[3])
void GPU_shader_bind(GPUShader *shader)
void GPU_shader_uniform_1b(GPUShader *sh, const char *name, bool value)
GPUShader * GPU_shader_create_from_info(const GPUShaderCreateInfo *_info)
void GPU_shader_free(GPUShader *shader)
void GPU_shader_unbind()
void GPU_texture_bind(GPUTexture *texture, int unit)
GPUTexture * GPU_texture_create_2d(const char *name, int width, int height, int mip_len, eGPUTextureFormat format, eGPUTextureUsage usage, const float *data)
GPUTexture * GPU_texture_create_1d(const char *name, int width, int mip_len, eGPUTextureFormat format, eGPUTextureUsage usage, const float *data)
void GPU_texture_free(GPUTexture *texture)
void GPU_texture_unbind(GPUTexture *texture)
@ GPU_TEXTURE_USAGE_SHADER_READ
GPUTexture * GPU_texture_create_3d(const char *name, int width, int height, int depth, int mip_len, eGPUTextureFormat format, eGPUTextureUsage usage, const void *data)
void GPU_texture_filter_mode(GPUTexture *texture, bool use_filter)
eGPUTextureFormat
GPUUniformBuf * GPU_uniformbuf_create_ex(size_t size, const void *data, const char *name)
void GPU_uniformbuf_unbind(GPUUniformBuf *ubo)
void GPU_uniformbuf_free(GPUUniformBuf *ubo)
void GPU_uniformbuf_bind(GPUUniformBuf *ubo, int slot)
in reality light always falls off quadratically Particle Retrieve the data of the particle that spawned the object instance
struct GPUShader GPUShader
static std::shared_ptr< GPUShaderCreator > Create(ResultPrecision)
OCIOColorSpaceConversionShader & get(Context &context, std::string source, std::string target)
OCIOColorSpaceConversionShaderKey(const std::string &source, const std::string &target, const std::string &config_cache_id)
OCIOColorSpaceConversionShader(Context &context, std::string source, std::string target)
static eGPUTextureFormat gpu_texture_format(ResultType type, ResultPrecision precision)
Definition result.cc:29
local_group_size(16, 16) .push_constant(Type b
local_group_size(16, 16) .push_constant(Type texture
static float3 get_float3(const BL::Array< float, 2 > &array)
bool operator==(const BokehKernelKey &a, const BokehKernelKey &b)
uint64_t get_default_hash(const T &v)
Definition BLI_hash.hh:219
unsigned __int64 uint64_t
Definition stdint.h:90
Describe inputs & outputs, stage interfaces, resources and sources of a shader. If all data is correc...