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
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 :
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
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 {
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()) {
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()) {
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. */
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. */
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. */
433 VectorSet<std::unique_ptr<std::string>> resource_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
in reality light always falls off quadratically Particle Retrieve the data of the particle that spawned the object for example to give variation to multiple instances of an object Point Retrieve information about points in a point cloud Retrieve the edges of an object as it appears to Cycles topology will always appear triangulated Convert a blackbody temperature to an RGB value Normal Map
struct GPUShader GPUShader
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: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