Blender V4.5
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_OPENCOLORIO)
28# include <OpenColorIO/OpenColorIO.h>
29#endif
30
31namespace blender::compositor {
32
33/* ------------------------------------------------------------------------------------------------
34 * OCIO Color Space Conversion Shader Key.
35 */
36
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_OPENCOLORIO)
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_t, 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_t, 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::float3_t, 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
172# if OCIO_VERSION_HEX >= 0x02050000
173 ,
174 const unsigned /*maxSize*/
175# endif
176 ) override
177 {
178 /* Check if a resource exists with the same name and assert if it is the case, returning false
179 * indicates failure to add the uniform for the shader creator. */
180 if (!resource_names_.add(std::make_unique<std::string>(name))) {
182 return false;
183 }
184
185 /* Don't use the name argument directly since ShaderCreateInfo only stores references to
186 * resource names, instead, use the name that is stored in resource_names_. */
187 std::string &resource_name = *resource_names_[resource_names_.size() - 1];
188 shader_create_info_.uniform_buf(buffers_sizes_.size(), "float", resource_name);
189
190 float_buffers_.add(resource_name, get_vector_float);
191 buffers_sizes_.add(resource_name, get_size);
192
193 return true;
194 }
195
196 bool addUniform(const char *name,
197 const SizeGetter &get_size,
198 const VectorIntGetter &get_vector_int
199# if OCIO_VERSION_HEX >= 0x02050000
200 ,
201 const unsigned /*maxSize*/
202# endif
203 ) override
204 {
205 /* Check if a resource exists with the same name and assert if it is the case, returning false
206 * indicates failure to add the uniform for the shader creator. */
207 if (!resource_names_.add(std::make_unique<std::string>(name))) {
209 return false;
210 }
211
212 /* Don't use the name argument directly since ShaderCreateInfo only stores references to
213 * resource names, instead, use the name that is stored in resource_names_. */
214 std::string &resource_name = *resource_names_[resource_names_.size() - 1];
215 shader_create_info_.uniform_buf(buffers_sizes_.size(), "int", resource_name);
216
217 int_buffers_.add(name, get_vector_int);
218 buffers_sizes_.add(name, get_size);
219
220 return true;
221 }
222
223# if OCIO_VERSION_HEX >= 0x02050000
224 unsigned
225# else
226 void
227# endif
228 addTexture(const char *texture_name,
229 const char *sampler_name,
230 uint width,
231 uint height,
232 TextureType channel,
233# if OCIO_VERSION_HEX >= 0x02030000
234 OCIO::GpuShaderDesc::TextureDimensions dimensions,
235# endif
236 OCIO::Interpolation interpolation,
237 const float *values) override
238 {
239 /* Check if a resource exists with the same name and assert if it is the case. */
240 if (!resource_names_.add(std::make_unique<std::string>(sampler_name))) {
242 }
243
244 /* Don't use the name argument directly since ShaderCreateInfo only stores references to
245 * resource names, instead, use the name that is stored in resource_names_. */
246 const std::string &resource_name = *resource_names_[resource_names_.size() - 1];
247
248 GPUTexture *texture;
249 const eGPUTextureFormat base_format = (channel == TEXTURE_RGB_CHANNEL) ? GPU_RGB32F : GPU_R32F;
250 const eGPUTextureFormat texture_format = Result::gpu_texture_format(base_format, precision_);
251 /* A height of 1 indicates a 1D texture according to the OCIO API. */
252# if OCIO_VERSION_HEX >= 0x02030000
253 if (dimensions == OCIO::GpuShaderDesc::TEXTURE_1D)
254# else
255 if (height == 1)
256# endif
257 {
259 texture_name, width, 1, texture_format, GPU_TEXTURE_USAGE_SHADER_READ, values);
260 shader_create_info_.sampler(textures_.size() + 1, ImageType::Float1D, resource_name);
261 }
262 else {
264 texture_name, width, height, 1, texture_format, GPU_TEXTURE_USAGE_SHADER_READ, values);
265 shader_create_info_.sampler(textures_.size() + 1, ImageType::Float2D, resource_name);
266 }
267 GPU_texture_filter_mode(texture, interpolation != OCIO::INTERP_NEAREST);
268
269 textures_.add(sampler_name, texture);
270# if OCIO_VERSION_HEX >= 0x02050000
271 return textures_.size() - 1;
272# endif
273 }
274
275# if OCIO_VERSION_HEX >= 0x02050000
276 unsigned
277# else
278 void
279# endif
280 add3DTexture(const char *texture_name,
281 const char *sampler_name,
282 uint size,
283 OCIO::Interpolation interpolation,
284 const float *values) override
285 {
286 /* Check if a resource exists with the same name and assert if it is the case. */
287 if (!resource_names_.add(std::make_unique<std::string>(sampler_name))) {
289 }
290
291 /* Don't use the name argument directly since ShaderCreateInfo only stores references to
292 * resource names, instead, use the name that is stored in resource_names_. */
293 const std::string &resource_name = *resource_names_[resource_names_.size() - 1];
294 shader_create_info_.sampler(textures_.size() + 1, ImageType::Float3D, resource_name);
295
296 GPUTexture *texture = GPU_texture_create_3d(texture_name,
297 size,
298 size,
299 size,
300 1,
303 values);
304 GPU_texture_filter_mode(texture, interpolation != OCIO::INTERP_NEAREST);
305
306 textures_.add(sampler_name, texture);
307# if OCIO_VERSION_HEX >= 0x02050000
308 return textures_.size() - 1;
309# endif
310 }
311
312 /* This gets called before the finalize() method to construct the shader code. We just
313 * concatenate the code except for the declarations section. That's because the ShaderCreateInfo
314 * will add the declaration itself. */
315 void createShaderText(const char * /*parameter_declarations*/,
316# if OCIO_VERSION_HEX >= 0x02050000
317 const char * /*texture_declarations*/,
318# endif
319 const char *helper_methods,
320 const char *function_header,
321 const char *function_body,
322 const char *function_footer) override
323 {
324 shader_code_ += helper_methods;
325 shader_code_ += function_header;
326 shader_code_ += function_body;
327 shader_code_ += function_footer;
328 }
329
330 /* This gets called when all resources were added using the respective addUniform() or
331 * add[3D]Texture() methods and the shader code was generated using the createShaderText()
332 * method. That is, we are ready to complete the ShaderCreateInfo and create a shader from it. */
333 void finalize() override
334 {
335 GpuShaderCreator::finalize();
336
337 shader_create_info_.local_group_size(16, 16);
338 shader_create_info_.sampler(0, ImageType::Float2D, input_sampler_name());
339 shader_create_info_.image(0,
341 Qualifier::write,
342 ImageReadWriteType::Float2D,
344 shader_create_info_.compute_source("gpu_shader_compositor_ocio_processor.glsl");
345 shader_create_info_.compute_source_generated += shader_code_;
346
347 GPUShaderCreateInfo *info = reinterpret_cast<GPUShaderCreateInfo *>(&shader_create_info_);
348 shader_ = GPU_shader_create_from_info(info);
349 }
350
351 GPUShader *bind_shader_and_resources()
352 {
353 if (!shader_) {
354 return nullptr;
355 }
356
357 GPU_shader_bind(shader_);
358
359 for (auto item : float_uniforms_.items()) {
360 GPU_shader_uniform_1f(shader_, item.key.c_str(), item.value());
361 }
362
363 for (auto item : boolean_uniforms_.items()) {
364 GPU_shader_uniform_1b(shader_, item.key.c_str(), item.value());
365 }
366
367 for (auto item : vector_uniforms_.items()) {
368 GPU_shader_uniform_3fv(shader_, item.key.c_str(), item.value().data());
369 }
370
371 for (auto item : float_buffers_.items()) {
372 GPUUniformBuf *buffer = GPU_uniformbuf_create_ex(
373 buffers_sizes_.lookup(item.key)(), item.value(), item.key.c_str());
374 const int ubo_location = GPU_shader_get_ubo_binding(shader_, item.key.c_str());
375 GPU_uniformbuf_bind(buffer, ubo_location);
376 uniform_buffers_.append(buffer);
377 }
378
379 for (auto item : int_buffers_.items()) {
380 GPUUniformBuf *buffer = GPU_uniformbuf_create_ex(
381 buffers_sizes_.lookup(item.key)(), item.value(), item.key.c_str());
382 const int ubo_location = GPU_shader_get_ubo_binding(shader_, item.key.c_str());
383 GPU_uniformbuf_bind(buffer, ubo_location);
384 uniform_buffers_.append(buffer);
385 }
386
387 for (auto item : textures_.items()) {
388 const int texture_image_unit = GPU_shader_get_sampler_binding(shader_, item.key.c_str());
389 GPU_texture_bind(item.value, texture_image_unit);
390 }
391
392 return shader_;
393 }
394
396 {
397 for (GPUUniformBuf *buffer : uniform_buffers_) {
398 GPU_uniformbuf_unbind(buffer);
399 GPU_uniformbuf_free(buffer);
400 }
401
402 for (GPUTexture *texture : textures_.values()) {
404 }
405
407 }
408
409 const char *input_sampler_name()
410 {
411 return "input_tx";
412 }
413
414 const char *output_image_name()
415 {
416 return "output_img";
417 }
418
419 ~GPUShaderCreator() override
420 {
421 for (GPUTexture *texture : textures_.values()) {
423 }
424
425 GPU_shader_free(shader_);
426 }
427
428 private:
429 /* The processor shader and the ShaderCreateInfo used to construct it. Constructed and
430 * initialized in the finalize() method. */
431 GPUShader *shader_ = nullptr;
432 ShaderCreateInfo shader_create_info_ = ShaderCreateInfo("OCIO Processor");
433
434 /* Stores the generated OCIOMain function as well as a number of helper functions. Initialized in
435 * the createShaderText() method. */
436 std::string shader_code_;
437
438 /* Maps that associates the name of a uniform with a getter function that returns its value.
439 * Initialized in the respective addUniform() methods. */
440 Map<std::string, DoubleGetter> float_uniforms_;
441 Map<std::string, BoolGetter> boolean_uniforms_;
442 Map<std::string, Float3Getter> vector_uniforms_;
443
444 /* Maps that associates the name of uniform buffer objects with a getter function that returns
445 * its values. Initialized in the respective addUniform() methods. */
446 Map<std::string, VectorFloatGetter> float_buffers_;
447 Map<std::string, VectorIntGetter> int_buffers_;
448
449 /* A map that associates the name of uniform buffer objects with a getter functions that returns
450 * its number of elements. Initialized in the respective addUniform() methods. */
451 Map<std::string, SizeGetter> buffers_sizes_;
452
453 /* A map that associates the name of a sampler with its corresponding texture. Initialized in the
454 * addTexture() and add3DTexture() methods. */
455 Map<std::string, GPUTexture *> textures_;
456
457 /* A vector set that stores the names of all the resources used by the shader. This is used to:
458 * 1. Check for name collisions when adding new resources.
459 * 2. Store the resource names throughout the construction of the shader since the
460 * ShaderCreateInfo class only stores references to resources names. */
461 VectorSet<std::unique_ptr<std::string>> resource_names_;
462
463 /* A vectors that stores the created uniform buffers when bind_shader_and_resources() is called,
464 * so that they can be properly unbound and freed in the unbind_shader_and_resources() method. */
465 Vector<GPUUniformBuf *> uniform_buffers_;
466
467# if OCIO_VERSION_HEX >= 0x02030000
468 /* Allow creating 1D textures, or only use 2D textures. */
469 bool allow_texture_1D_ = true;
470# endif
471
472 /* The precision of the OCIO resources as well as the output image. */
473 ResultPrecision precision_;
474};
475
476#else
477
478/* A stub implementation in case OCIO is disabled at build time. */
480 public:
481 static std::shared_ptr<GPUShaderCreator> Create(ResultPrecision /*precision*/)
482 {
483 return std::make_shared<GPUShaderCreator>();
484 }
485
487 {
488 return nullptr;
489 }
490
492
493 const char *input_sampler_name()
494 {
495 return nullptr;
496 }
497
498 const char *output_image_name()
499 {
500 return nullptr;
501 }
502};
503
504#endif
505
506/* --------------------------------------------------------------------
507 * OCIO Color Space Conversion Shader.
508 */
509
511 std::string source,
512 std::string target)
513{
514 /* Create a GPU shader creator and construct it based on the transforms in the default GPU
515 * processor. */
516 shader_creator_ = GPUShaderCreator::Create(context.get_precision());
517
518#if defined(WITH_OPENCOLORIO)
519 /* Get a GPU processor that transforms the source color space to the target color space. */
520 try {
521 OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
522 OCIO::ConstProcessorRcPtr processor = config->getProcessor(source.c_str(), target.c_str());
523 OCIO::ConstGPUProcessorRcPtr gpu_processor = processor->getDefaultGPUProcessor();
524
525 auto ocio_shader_creator = std::static_pointer_cast<OCIO::GpuShaderCreator>(shader_creator_);
526 gpu_processor->extractGpuShaderInfo(ocio_shader_creator);
527 }
528 catch (const OCIO::Exception &) {
529 }
530#else
531 UNUSED_VARS(source, target);
532#endif
533}
534
536{
537 return shader_creator_->bind_shader_and_resources();
538}
539
541{
542 shader_creator_->unbind_shader_and_resources();
543}
544
546{
547 return shader_creator_->input_sampler_name();
548}
549
551{
552 return shader_creator_->output_image_name();
553}
554
555/* --------------------------------------------------------------------
556 * OCIO Color Space Conversion Shader Container.
557 */
558
560{
561 /* First, delete all resources that are no longer needed. */
562 map_.remove_if([](auto item) { return !item.value->needed; });
563
564 /* Second, reset the needed status of the remaining resources to false to ready them to track
565 * their needed status for the next evaluation. */
566 for (auto &value : map_.values()) {
567 value->needed = false;
568 }
569}
570
572 std::string source,
573 std::string target)
574{
575#if defined(WITH_OPENCOLORIO)
576 /* Use the config cache ID in the cache key in case the configuration changed at runtime. */
577 std::string config_cache_id = OCIO::GetCurrentConfig()->getCacheID();
578#else
579 std::string config_cache_id;
580#endif
581
582 const OCIOColorSpaceConversionShaderKey key(source, target, config_cache_id);
583
584 OCIOColorSpaceConversionShader &shader = *map_.lookup_or_add_cb(key, [&]() {
585 return std::make_unique<OCIOColorSpaceConversionShader>(context, source, target);
586 });
587
588 shader.needed = true;
589 return shader;
590}
591
592} // namespace blender::compositor
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
unsigned int uint
#define UNUSED_VARS(...)
float[3] Vector
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_bind(GPUShader *shader, const blender::gpu::shader::SpecializationConstants *constants_state=nullptr)
void GPU_shader_uniform_3fv(GPUShader *sh, const char *name, const float data[3])
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
@ GPU_R32F
@ GPU_RGB32F
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)
unsigned long long int uint64_t
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
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:42
static float3 get_float3(const BL::Array< float, 2 > &array)
TEX_TEMPLATE DataVec texture(T, FltCoord, float=0.0f) RET
bool operator==(const BokehKernelKey &a, const BokehKernelKey &b)
uint64_t get_default_hash(const T &v, const Args &...args)
Definition BLI_hash.hh:233