Blender V5.0
gpu_shader_binder.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2025 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
6
7#include <sstream>
8
9#include "MEM_guardedalloc.h"
10
11#include "BLI_assert.h"
12#include "BLI_build_config.h"
13#include "BLI_string_utils.hh"
14#include "BLI_vector.hh"
15
16#include "BKE_colortools.hh"
17
18#include "GPU_immediate.hh"
19#include "GPU_shader.hh"
20#include "GPU_texture.hh"
21#include "GPU_uniform_buffer.hh"
22
24
25#include "OCIO_config.hh"
26
28#include "source_processor.hh"
29#include "white_point.hh"
30
31namespace blender::ocio {
32namespace internal {
33/* -------------------------------------------------------------------- */
36
38{
39 for (GPULutTexture &lut : luts) {
40 GPU_texture_free(lut.texture);
41 }
42 if (dummy) {
44 }
45 if (uniforms_buffer) {
47 }
48}
49
51{
53
54 luts.clear();
55 uniforms.clear();
56
57 return (dummy != nullptr);
58}
59
61
62/* -------------------------------------------------------------------- */
65
67{
68 if (lut) {
70 }
71
72 if (texture) {
74 }
75 if (buffer) {
77 }
78}
79
81{
82 if (lut) {
84 }
85
86 BKE_curvemapping_init(&curve_mapping);
87 BKE_curvemapping_premultiply(&curve_mapping, false);
88 BKE_curvemapping_table_RGBA(&curve_mapping, &lut, &lut_size);
89}
90
91bool GPUCurveMappping::initialize_common(const bool use_curve_mapping)
92{
93 if (!use_curve_mapping) {
94 return true;
95 }
96
97 texture = GPU_texture_create_1d("OCIOCurveMap",
99 1,
100 blender::gpu::TextureFormat::SFLOAT_16_16_16_16,
102 nullptr);
105
107
108 if (texture == nullptr || buffer == nullptr) {
109 return false;
110 }
111
112 return true;
113}
114
116
117/* -------------------------------------------------------------------- */
120
130
131bool GPUDisplayShader::matches(const GPUDisplayParameters &display_parameters) const
132{
133 const bool use_curve_mapping = (display_parameters.curve_mapping != nullptr);
134 return (this->from_colorspace == display_parameters.from_colorspace &&
135 this->view == display_parameters.view && this->display == display_parameters.display &&
136 this->look == display_parameters.look && this->use_curve_mapping == use_curve_mapping &&
137 this->use_hdr_buffer == display_parameters.use_hdr_buffer &&
138 this->use_hdr_display == display_parameters.use_hdr_display &&
139 this->use_display_emulation == display_parameters.use_display_emulation);
140}
141
143{
144 if (!textures.initialize_common()) {
145 is_valid = false;
146 return false;
147 }
148
149 if (!curve_mapping.initialize_common(use_curve_mapping)) {
150 is_valid = false;
151 return false;
152 }
153
154 return true;
155}
156
158
159/* -------------------------------------------------------------------- */
162
167
169{
170 for (std::list<GPUDisplayShader>::iterator it = cache_.begin(); it != cache_.end(); it++) {
171 if (it->matches(display_parameters)) {
172 /* Move to front of the cache to mark as most recently used. */
173 if (it != cache_.begin()) {
174 cache_.splice(cache_.begin(), cache_, it);
175 }
176 return &(*it);
177 }
178 }
179 return nullptr;
180}
181
183{
184 /* Remove least recently used element from cache. */
185 while (cache_.size() >= MAX_SIZE) {
186 cache_.pop_back();
187 }
188
189 /* Create GPU shader. */
190 cache_.emplace_front();
191
192 return cache_.front();
193}
194
196{
197 cache_.clear();
198}
199
201
202} // namespace internal
203
204namespace {
205
206/* -------------------------------------------------------------------- */
209
216static void process_source(std::string &source)
217{
219 source = GPU_shader_preprocess_source(source);
220
221 /* Comparison operator in Metal returns per-element comparison and returns a vector of booleans.
222 * Need a special syntax to see if two vec3 are matched.
223 *
224 * NOTE: The replacement is optimized for transforming code generated by
225 * GradingPrimaryTransform. A more general approach is possible, but for now prefer processing
226 * speed.
227 *
228 * NOTE: The syntax works for all backends Blender supports. */
230 source, "if ( gamma != vec3(1., 1., 1.) )", "if (! all(equal(gamma, vec3(1., 1., 1.))) )");
231}
232
233static void gpu_curve_mapping_update(internal::GPUCurveMappping &gpu_curve_mapping,
234 CurveMapping &curve_mapping)
235{
236 /* Test if we need to update. */
237 /* TODO(sergey): Use more reliable cache identifier.
238 * Something like monotonously incrementing change counter feels to have less collisions. */
239 const size_t cache_id = size_t(&curve_mapping) + curve_mapping.changed_timestamp;
240 if (gpu_curve_mapping.cache_id == cache_id) {
241 return;
242 }
243 gpu_curve_mapping.cache_id = cache_id;
244
245 /* Update texture. */
246 const int offset[3] = {0, 0, 0};
247 const int extent[3] = {gpu_curve_mapping.lut_size, 0, 0};
248 GPU_texture_update_sub(gpu_curve_mapping.texture,
250 gpu_curve_mapping.lut,
251 UNPACK3(offset),
252 UNPACK3(extent));
253
254 /* Update uniforms. */
255 OCIO_GPUCurveMappingParameters data;
256 for (int i = 0; i < 4; i++) {
257 const CurveMap &curve_map = curve_mapping.cm[i];
258 data.range[i] = curve_map.range;
259 data.mintable[i] = curve_map.mintable;
260 data.ext_in_x[i] = curve_map.ext_in[0];
261 data.ext_in_y[i] = curve_map.ext_in[1];
262 data.ext_out_x[i] = curve_map.ext_out[0];
263 data.ext_out_y[i] = curve_map.ext_out[1];
264 data.first_x[i] = curve_map.table[0].x;
265 data.first_y[i] = curve_map.table[0].y;
266 data.last_x[i] = curve_map.table[CM_TABLE].x;
267 data.last_y[i] = curve_map.table[CM_TABLE].y;
268 }
269 for (int i = 0; i < 3; i++) {
270 data.black[i] = curve_mapping.black[i];
271 data.bwmul[i] = curve_mapping.bwmul[i];
272 }
273 data.lut_size = gpu_curve_mapping.lut_size;
274 data.use_extend_extrapolate = (curve_mapping.flag & CUMA_EXTEND_EXTRAPOLATE) != 0;
275
276 GPU_uniformbuf_update(gpu_curve_mapping.buffer, &data);
277}
278
279static void gpu_display_shader_parameters_update(internal::GPUDisplayShader &display_shader,
280 const GPUDisplayParameters &display_parameters,
281 float4x4 scene_linear_matrix)
282{
283 bool do_update = false;
284 if (display_shader.parameters_buffer == nullptr) {
285 display_shader.parameters_buffer = GPU_uniformbuf_create(sizeof(OCIO_GPUParameters));
286 do_update = true;
287 }
288
289 OCIO_GPUParameters &data = display_shader.parameters;
290 if (data.scene_linear_matrix != scene_linear_matrix) {
291 data.scene_linear_matrix = scene_linear_matrix;
292 do_update = true;
293 }
294 if (data.exponent != display_parameters.exponent) {
295 data.exponent = display_parameters.exponent;
296 do_update = true;
297 }
298 if (data.dither != display_parameters.dither) {
299 data.dither = display_parameters.dither;
300 do_update = true;
301 }
302 if (bool(data.use_predivide) != display_parameters.use_predivide) {
303 data.use_predivide = display_parameters.use_predivide;
304 do_update = true;
305 }
306 if (bool(data.do_overlay_merge) != display_parameters.do_overlay_merge) {
307 data.do_overlay_merge = display_parameters.do_overlay_merge;
308 do_update = true;
309 }
310 if (bool(data.use_hdr_display) != display_parameters.use_hdr_display) {
311 data.use_hdr_display = display_parameters.use_hdr_display;
312 do_update = true;
313 }
314
315 if (do_update) {
316 GPU_uniformbuf_update(display_shader.parameters_buffer, &data);
317 }
318}
319
320/* Bind the shader and update parameters and uniforms. */
321static bool gpu_shader_bind(const Config &config,
322 internal::GPUDisplayShader &display_shader,
323 const GPUDisplayParameters &display_parameters)
324{
327
328 /* Verify the shader is valid. */
329 if (!display_shader.is_valid) {
330 return false;
331 }
332
333 /* Update and bind curve mapping data. */
334 if (display_parameters.curve_mapping) {
335 gpu_curve_mapping_update(display_shader.curve_mapping, *display_parameters.curve_mapping);
336 GPU_uniformbuf_bind(display_shader.curve_mapping.buffer, UniformBufferSlot::CURVE_MAPPING);
337 GPU_texture_bind(display_shader.curve_mapping.texture, TextureSlot::CURVE_MAPPING);
338 /* TODO(sergey): Can free the curve mapping's lookup table.
339 * Seems minor, maybe not that important. */
340 }
341
342 /* Bind textures to sampler units. Texture 0 is set by caller.
343 * Uniforms have already been set for texture bind points. */
344 if (!display_parameters.do_overlay_merge) {
345 /* Avoid missing binds. */
346 GPU_texture_bind(display_shader.textures.dummy, TextureSlot::OVERLAY);
347 }
348 for (int i = 0; i < display_shader.textures.luts.size(); i++) {
349 GPU_texture_bind(display_shader.textures.luts[i].texture, TextureSlot::LUTS_OFFSET + i);
350 }
351
352 if (display_shader.textures.uniforms_buffer) {
353 GPU_uniformbuf_bind(display_shader.textures.uniforms_buffer, UniformBufferSlot::LUTS);
354 }
355
356 float3x3 matrix = float3x3::identity() * display_parameters.scale;
357 if (display_parameters.use_white_balance) {
359 config, display_parameters.temperature, display_parameters.tint);
360 }
361
362 gpu_display_shader_parameters_update(display_shader, display_parameters, float4x4(matrix));
363 GPU_uniformbuf_bind(display_shader.parameters_buffer, UniformBufferSlot::DISPLAY);
364
365 /* TODO(fclem): remove remains of IMM. */
366 immBindShader(display_shader.shader);
367
368 return true;
369}
370
372
373} // namespace
374
376 : display_cache_(std::make_unique<internal::GPUShaderCache>()),
377 scene_linear_cache_(std::make_unique<internal::GPUShaderCache>()),
378 config_(config)
379{
380}
381
382/* Keep private to the translation unit to allow proper destruction of smart pointers to internal
383 * data. */
385
386bool GPUShaderBinder::display_bind(const GPUDisplayParameters &display_parameters) const
387{
388 /* Attempt to get shader from the cache. */
389 internal::GPUDisplayShader *display_shader = display_cache_->get(display_parameters);
390
391 if (!display_shader) {
392 display_shader = &display_cache_->create_default();
393 BLI_assert(display_shader);
394
395 display_shader->from_colorspace = display_parameters.from_colorspace;
396 display_shader->view = display_parameters.view;
397 display_shader->display = display_parameters.display;
398 display_shader->look = display_parameters.look;
399 display_shader->use_curve_mapping = (display_parameters.curve_mapping != nullptr);
400 display_shader->use_hdr_buffer = display_parameters.use_hdr_buffer;
401 display_shader->use_hdr_display = display_parameters.use_hdr_display;
402 display_shader->use_display_emulation = display_parameters.use_display_emulation;
403 display_shader->is_valid = false;
404
405 if (display_parameters.curve_mapping) {
406 /* Rasterize curve mapping early so that texture allocation can know the size of the lookup
407 * table. */
408 display_shader->curve_mapping.rasterize(*display_parameters.curve_mapping);
409 }
410
411 if (display_shader->initialize_common()) {
412 construct_display_shader(*display_shader);
413 }
414 }
415 else if (display_parameters.curve_mapping) {
416 /* Update curve mapping's lookup table. */
417 display_shader->curve_mapping.rasterize(*display_parameters.curve_mapping);
418 }
419
420 return gpu_shader_bind(config_, *display_shader, display_parameters);
421}
422
424 const bool use_predivide) const
425{
426 /* Re-use code and logic with the conversion to the display space. This assumes that empty names
427 * for display, view, and look are not valid for the OpenColorIO configuration, and so they can
428 * be used to indicate that the processor is used to convert from the given space to the linear.
429 */
430
431 GPUDisplayParameters display_parameters;
432 display_parameters.from_colorspace = from_colorspace;
433 display_parameters.use_predivide = use_predivide;
434
435 /* Attempt to get shader from the cache. */
436 internal::GPUDisplayShader *shader = scene_linear_cache_->get(display_parameters);
437
438 if (!shader) {
439 shader = &scene_linear_cache_->create_default();
440 BLI_assert(shader);
441
442 shader->from_colorspace = display_parameters.from_colorspace;
443
444 if (shader->initialize_common()) {
446 }
447 }
448
449 return gpu_shader_bind(config_, *shader, display_parameters);
450}
451
453{
455}
456
458 internal::GPUDisplayShader &display_shader,
459 StringRefNull fragment_source,
460 const Span<std::array<StringRefNull, 2>> additional_defines)
461{
462 using namespace blender::gpu::shader;
463
464 StageInterfaceInfo iface("OCIO_Interface", "");
465 iface.smooth(Type::float2_t, "texCoord_interp");
466
467 ShaderCreateInfo info("OCIO_Display");
468
469 for (const auto &additional_define : additional_defines) {
470 info.define(additional_define[0], additional_define[1]);
471 }
472
473 /* Work around OpenColorIO not supporting latest GLSL yet. */
474 info.define("texture1D", "texture");
475 info.define("texture2D", "texture");
476 info.define("texture3D", "texture");
477
478 /* Work around unsupported in keyword in Metal GLSL emulation. */
479#if OS_MAC
480 info.define("in", "");
481#endif
482
483 info.typedef_source("ocio_shader_shared.hh");
484 info.sampler(internal::TextureSlot::IMAGE, ImageType::Float2D, "image_texture");
485 info.sampler(internal::TextureSlot::OVERLAY, ImageType::Float2D, "overlay_texture");
486 info.uniform_buf(internal::UniformBufferSlot::DISPLAY, "OCIO_GPUParameters", "parameters");
487 info.push_constant(Type::float4x4_t, "ModelViewProjectionMatrix");
488 info.vertex_in(0, Type::float2_t, "pos");
489 info.vertex_in(1, Type::float2_t, "texCoord");
490 info.vertex_out(iface);
491 info.fragment_out(0, Type::float4_t, "fragColor");
492 info.vertex_source("gpu_shader_display_transform_vert.glsl");
493 info.fragment_source("gpu_shader_display_transform_frag.glsl");
494
495 info.fragment_source_generated = fragment_source;
496 process_source(info.fragment_source_generated);
497
498 /* #96502: Work around for incorrect OCIO GLSL code generation when using
499 * GradingPrimaryTransform. Should be reevaluated when changing to a next version of OCIO.
500 * (currently v2.1.1). */
501 info.define("inf 1e32");
502
503 if (display_shader.use_curve_mapping) {
504 info.define("USE_CURVE_MAPPING");
506 "OCIO_GPUCurveMappingParameters",
507 "curve_mapping");
508 info.sampler(
509 internal::TextureSlot::CURVE_MAPPING, ImageType::Float1D, "curve_mapping_texture");
510 }
511
512 /* Set LUT textures. */
514 for (const internal::GPULutTexture &texture : display_shader.textures.luts) {
515 const int dimensions = GPU_texture_dimensions(texture.texture);
516 const ImageType type = (dimensions == 1) ? ImageType::Float1D :
517 (dimensions == 2) ? ImageType::Float2D :
518 ImageType::Float3D;
519 info.sampler(slot++, type, texture.sampler_name.c_str());
520 }
521
522 /* Set LUT uniforms. */
523#if defined(WITH_OPENCOLORIO)
524 if (!display_shader.textures.uniforms.is_empty()) {
525 /* NOTE: For simplicity, we pad everything to size of vec4 avoiding sorting and alignment
526 * issues. It is unlikely that this becomes a real issue. */
527 const size_t ubo_size = display_shader.textures.uniforms.size() * sizeof(float) * 4;
528 Vector<uint8_t> ubo_data_buf(ubo_size);
529
530 uint32_t *ubo_data = reinterpret_cast<uint32_t *>(ubo_data_buf.data());
531
532 std::stringstream ss;
533 ss << "struct OCIO_GPULutParameters {\n";
534
535 int index = 0;
536 for (internal::GPUUniform &uniform : display_shader.textures.uniforms) {
537 index += 1;
538 const OCIO_NAMESPACE::GpuShaderDesc::UniformData &data = uniform.data;
539 const char *name = uniform.name.c_str();
540 char prefix = ' ';
541 int vec_len;
542 switch (data.m_type) {
543 case OCIO_NAMESPACE::UNIFORM_DOUBLE: {
544 vec_len = 1;
545 float value = float(data.m_getDouble());
546 memcpy(ubo_data, &value, sizeof(float));
547 break;
548 }
549 case OCIO_NAMESPACE::UNIFORM_BOOL: {
550 prefix = 'b';
551 vec_len = 1;
552 int value = int(data.m_getBool());
553 memcpy(ubo_data, &value, sizeof(int));
554 break;
555 }
556 case OCIO_NAMESPACE::UNIFORM_FLOAT3:
557 vec_len = 3;
558 memcpy(ubo_data, data.m_getFloat3().data(), sizeof(float) * 3);
559 break;
560 case OCIO_NAMESPACE::UNIFORM_VECTOR_FLOAT:
561 vec_len = data.m_vectorFloat.m_getSize();
562 memcpy(ubo_data, data.m_vectorFloat.m_getVector(), sizeof(float) * vec_len);
563 break;
564 case OCIO_NAMESPACE::UNIFORM_VECTOR_INT:
565 prefix = 'i';
566 vec_len = data.m_vectorInt.m_getSize();
567 memcpy(ubo_data, data.m_vectorInt.m_getVector(), sizeof(int) * vec_len);
568 break;
569 default:
570 continue;
571 }
572 /* Align every member to 16 bytes. */
573 ubo_data += 4;
574 /* Use a generic variable name because some GLSL compilers can interpret the preprocessor
575 * define as recursive. */
576 ss << " " << prefix << "vec4 var" << index << ";\n";
577 /* Use a define to keep the generated code working. */
578 StringRef suffix = StringRefNull("xyzw").substr(0, vec_len);
579 ss << "#define " << name << " lut_parameters.var" << index << "." << suffix << "\n";
580 }
581 ss << "};\n";
582 info.typedef_source_generated = ss.str();
583
584 info.uniform_buf(internal::UniformBufferSlot::LUTS, "OCIO_GPULutParameters", "lut_parameters");
585
587 ubo_size, ubo_data_buf.data(), "OCIO_LutParameters");
588 }
589#endif
590
591 display_shader.shader = GPU_shader_create_from_info(
592 reinterpret_cast<GPUShaderCreateInfo *>(&info));
593
594 return (display_shader.shader != nullptr);
595}
596
598{
599 scene_linear_cache_->clear();
600 display_cache_->clear();
601}
602
603} // namespace blender::ocio
void BKE_curvemapping_premultiply(CurveMapping *cumap, bool restore)
void BKE_curvemapping_init(CurveMapping *cumap)
void BKE_curvemapping_table_RGBA(const CurveMapping *cumap, float **array, int *size)
#define BLI_assert(a)
Definition BLI_assert.h:46
void BLI_string_replace(std::string &haystack, blender::StringRef needle, blender::StringRef other)
#define UNPACK3(a)
@ CUMA_EXTEND_EXTRAPOLATE
struct CurveMap CurveMap
#define CM_TABLE
void immUnbindProgram()
void immBindShader(blender::gpu::Shader *shader)
void GPU_shader_free(blender::gpu::Shader *shader)
blender::gpu::Shader * GPU_shader_create_from_info(const GPUShaderCreateInfo *_info)
std::string GPU_shader_preprocess_source(blender::StringRefNull original)
void GPU_texture_update_sub(blender::gpu::Texture *texture, eGPUDataFormat data_format, const void *pixels, int offset_x, int offset_y, int offset_z, int width, int height, int depth)
@ GPU_DATA_FLOAT
void GPU_texture_extend_mode(blender::gpu::Texture *texture, GPUSamplerExtendMode extend_mode)
@ GPU_TEXTURE_USAGE_SHADER_READ
@ GPU_SAMPLER_EXTEND_MODE_EXTEND
int GPU_texture_dimensions(const blender::gpu::Texture *texture)
void GPU_texture_filter_mode(blender::gpu::Texture *texture, bool use_filter)
void GPU_texture_bind(blender::gpu::Texture *texture, int unit)
blender::gpu::Texture * GPU_texture_create_error(int dimension, bool array)
void GPU_texture_free(blender::gpu::Texture *texture)
blender::gpu::Texture * GPU_texture_create_1d(const char *name, int width, int mip_len, blender::gpu::TextureFormat format, eGPUTextureUsage usage, const float *data)
void GPU_uniformbuf_free(blender::gpu::UniformBuf *ubo)
void GPU_uniformbuf_bind(blender::gpu::UniformBuf *ubo, int slot)
#define GPU_uniformbuf_create(size)
blender::gpu::UniformBuf * GPU_uniformbuf_create_ex(size_t size, const void *data, const char *name)
void GPU_uniformbuf_update(blender::gpu::UniformBuf *ubo, const void *data)
Read Guarded memory(de)allocation.
BMesh const char void * data
constexpr StringRef substr(int64_t start, int64_t size) const
virtual void construct_display_shader(internal::GPUDisplayShader &display_shader) const =0
static bool create_gpu_shader(internal::GPUDisplayShader &display_shader, StringRefNull fragment_source, Span< std::array< StringRefNull, 2 > > additional_defines)
bool to_scene_linear_bind(StringRefNull from_colorspace, bool use_predivide) const
bool display_bind(const GPUDisplayParameters &display_parameters) const
virtual void construct_scene_linear_shader(internal::GPUDisplayShader &display_shader) const =0
GPUShaderBinder(const Config &config)
void rasterize(CurveMapping &curve_mapping)
bool initialize_common(bool use_curve_mapping)
bool matches(const GPUDisplayParameters &display_parameters) const
GPUDisplayShader * get(const GPUDisplayParameters &display_parameters)
nullptr float
TEX_TEMPLATE DataVec texture(T, FltCoord, float=0.0f) RET
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
float3x3 calculate_white_point_matrix(const Config &config, const float temperature, const float tint)
void source_comment_out_uniforms(std::string &source)
MatBase< float, 4, 4 > float4x4
MatBase< float, 3, 3 > float3x3
const char * name
CurveMapPoint * table
float ext_out[2]
float ext_in[2]
CurveMap cm[4]
Describe inputs & outputs, stage interfaces, resources and sources of a shader. If all data is correc...
Self & fragment_source(StringRefNull filename)
std::string fragment_source_generated
Self & vertex_in(int slot, Type type, StringRefNull name)
Self & push_constant(Type type, StringRefNull name, int array_size=0)
Self & typedef_source(StringRefNull filename)
Self & fragment_out(int slot, Type type, StringRefNull name, DualBlend blend=DualBlend::NONE, int raster_order_group=-1)
Self & vertex_out(StageInterfaceInfo &interface)
Self & vertex_source(StringRefNull filename)
Self & sampler(int slot, ImageType type, StringRefNull name, Frequency freq=Frequency::PASS, GPUSamplerState sampler=GPUSamplerState::internal_sampler())
Self & uniform_buf(int slot, StringRefNull type_name, StringRefNull name, Frequency freq=Frequency::PASS)
Self & define(StringRefNull name, StringRefNull value="")
Self & smooth(Type type, StringRefNull _name)
i
Definition text_draw.cc:230