Realtime Compositor: Implement Convert Color Space node
This patch implements the Convert Color Space node for the realtime compositor. A custom OCIO GpuShaderCreator was implemented to use the ShaderCreateInfo in constructing the OCIO GPU processor shader. That shader is then cached inside the cache manager and is invalidated when the OCIO configuration changes. Pull Request: https://projects.blender.org/blender/blender/pulls/107878
This commit is contained in:
@@ -73,6 +73,7 @@ set(SRC
|
||||
cached_resources/intern/cached_mask.cc
|
||||
cached_resources/intern/cached_texture.cc
|
||||
cached_resources/intern/morphological_distance_feather_weights.cc
|
||||
cached_resources/intern/ocio_color_space_conversion_shader.cc
|
||||
cached_resources/intern/smaa_precomputed_textures.cc
|
||||
cached_resources/intern/symmetric_blur_weights.cc
|
||||
cached_resources/intern/symmetric_separable_blur_weights.cc
|
||||
@@ -81,6 +82,7 @@ set(SRC
|
||||
cached_resources/COM_cached_resource.hh
|
||||
cached_resources/COM_cached_texture.hh
|
||||
cached_resources/COM_morphological_distance_feather_weights.hh
|
||||
cached_resources/COM_ocio_color_space_conversion_shader.hh
|
||||
cached_resources/COM_smaa_precomputed_textures.hh
|
||||
cached_resources/COM_symmetric_blur_weights.hh
|
||||
cached_resources/COM_symmetric_separable_blur_weights.hh
|
||||
@@ -173,6 +175,7 @@ set(GLSL_SRC
|
||||
shaders/library/gpu_shader_compositor_main.glsl
|
||||
shaders/library/gpu_shader_compositor_map_value.glsl
|
||||
shaders/library/gpu_shader_compositor_normal.glsl
|
||||
shaders/library/gpu_shader_compositor_ocio_processor.glsl
|
||||
shaders/library/gpu_shader_compositor_posterize.glsl
|
||||
shaders/library/gpu_shader_compositor_separate_combine.glsl
|
||||
shaders/library/gpu_shader_compositor_set_alpha.glsl
|
||||
@@ -268,4 +271,18 @@ if(WITH_TBB)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(WITH_OPENCOLORIO)
|
||||
add_definitions(
|
||||
-DWITH_OCIO
|
||||
)
|
||||
|
||||
list(APPEND INC_SYS
|
||||
${OPENCOLORIO_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
list(APPEND LIB
|
||||
${OPENCOLORIO_LIBRARIES}
|
||||
)
|
||||
endif()
|
||||
|
||||
blender_add_lib(bf_realtime_compositor "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "COM_cached_mask.hh"
|
||||
#include "COM_cached_texture.hh"
|
||||
#include "COM_morphological_distance_feather_weights.hh"
|
||||
#include "COM_ocio_color_space_conversion_shader.hh"
|
||||
#include "COM_smaa_precomputed_textures.hh"
|
||||
#include "COM_symmetric_blur_weights.hh"
|
||||
#include "COM_symmetric_separable_blur_weights.hh"
|
||||
@@ -41,6 +42,7 @@ class StaticCacheManager {
|
||||
CachedTextureContainer cached_textures;
|
||||
CachedMaskContainer cached_masks;
|
||||
SMAAPrecomputedTexturesContainer smaa_precomputed_textures;
|
||||
OCIOColorSpaceConversionShaderContainer ocio_color_space_conversion_shaders;
|
||||
|
||||
/* Reset the cache manager by deleting the cached resources that are no longer needed because
|
||||
* they weren't used in the last evaluation and prepare the remaining cached resources to track
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "COM_cached_resource.hh"
|
||||
|
||||
namespace blender::realtime_compositor {
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------
|
||||
* OCIO Color Space Conversion Shader Key.
|
||||
*/
|
||||
class OCIOColorSpaceConversionShaderKey {
|
||||
public:
|
||||
std::string source;
|
||||
std::string target;
|
||||
std::string config_cache_id;
|
||||
|
||||
OCIOColorSpaceConversionShaderKey(std::string source,
|
||||
std::string target,
|
||||
std::string config_cache_id);
|
||||
|
||||
uint64_t hash() const;
|
||||
};
|
||||
|
||||
bool operator==(const OCIOColorSpaceConversionShaderKey &a,
|
||||
const OCIOColorSpaceConversionShaderKey &b);
|
||||
|
||||
class GPUShaderCreator;
|
||||
|
||||
/* -------------------------------------------------------------------------------------------------
|
||||
* OCIO Color Space Conversion Shader.
|
||||
*
|
||||
* A cached resource that creates and caches a GPU shader that converts the source OCIO color space
|
||||
* of an image into a different target OCIO color space. */
|
||||
class OCIOColorSpaceConversionShader : public CachedResource {
|
||||
private:
|
||||
std::shared_ptr<GPUShaderCreator> shader_creator_;
|
||||
|
||||
public:
|
||||
OCIOColorSpaceConversionShader(std::string source, std::string target);
|
||||
|
||||
GPUShader *bind_shader_and_resources();
|
||||
|
||||
void unbind_shader_and_resources();
|
||||
|
||||
const char *input_sampler_name();
|
||||
|
||||
const char *output_image_name();
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------
|
||||
* OCIO Color Space Conversion Shader Container.
|
||||
*/
|
||||
class OCIOColorSpaceConversionShaderContainer : CachedResourceContainer {
|
||||
private:
|
||||
Map<OCIOColorSpaceConversionShaderKey, std::unique_ptr<OCIOColorSpaceConversionShader>> map_;
|
||||
|
||||
public:
|
||||
void reset() override;
|
||||
|
||||
/* Check if there is an available OCIOColorSpaceConversionShader cached resource with the given
|
||||
* parameters in the container, if one exists, return it, otherwise, return a newly created one
|
||||
* and add it to the container. In both cases, tag the cached resource as needed to keep it
|
||||
* cached for the next evaluation. */
|
||||
OCIOColorSpaceConversionShader &get(std::string source, std::string target);
|
||||
};
|
||||
|
||||
} // namespace blender::realtime_compositor
|
||||
+502
@@ -0,0 +1,502 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "BLI_assert.h"
|
||||
#include "BLI_hash.hh"
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_vector.hh"
|
||||
#include "BLI_vector_set.hh"
|
||||
|
||||
#include "GPU_capabilities.h"
|
||||
#include "GPU_shader.h"
|
||||
#include "GPU_texture.h"
|
||||
#include "GPU_uniform_buffer.h"
|
||||
|
||||
#include "gpu_shader_create_info.hh"
|
||||
|
||||
#include "COM_ocio_color_space_conversion_shader.hh"
|
||||
|
||||
#if defined(WITH_OCIO)
|
||||
# include <OpenColorIO/OpenColorIO.h>
|
||||
#endif
|
||||
|
||||
namespace blender::realtime_compositor {
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------
|
||||
* OCIO Color Space Conversion Shader Key.
|
||||
*/
|
||||
|
||||
OCIOColorSpaceConversionShaderKey::OCIOColorSpaceConversionShaderKey(std::string source,
|
||||
std::string target,
|
||||
std::string config_cache_id)
|
||||
: source(source), target(target), config_cache_id(config_cache_id)
|
||||
{
|
||||
}
|
||||
|
||||
uint64_t OCIOColorSpaceConversionShaderKey::hash() const
|
||||
{
|
||||
return get_default_hash_3(source, target, config_cache_id);
|
||||
}
|
||||
|
||||
bool operator==(const OCIOColorSpaceConversionShaderKey &a,
|
||||
const OCIOColorSpaceConversionShaderKey &b)
|
||||
{
|
||||
return a.source == b.source && a.target == b.target && a.config_cache_id == b.config_cache_id;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* GPU Shader Creator.
|
||||
*/
|
||||
|
||||
#if defined(WITH_OCIO)
|
||||
|
||||
namespace OCIO = OCIO_NAMESPACE;
|
||||
using namespace blender::gpu::shader;
|
||||
|
||||
/* A subclass of OCIO::GpuShaderCreator that constructs the shader using a ShaderCreateInfo. The
|
||||
* Create method should be used to construct the creator, then the extractGpuShaderInfo() method of
|
||||
* the appropriate OCIO::GPUProcessor should be called passing in the creator. After construction,
|
||||
* the constructed compute shader can be used by calling the bind_shader_and_resources() method,
|
||||
* followed by binding the input texture and output image using their names input_sampler_name()
|
||||
* and output_image_name(), following by dispatching the shader on the domain of the input, and
|
||||
* finally calling the unbind_shader_and_resources() method.
|
||||
*
|
||||
* Upon calling the extractGpuShaderInfo(), all the transforms in the GPU processor will add their
|
||||
* needed resources by calling the respective addUniform() and add[3D]Texture() methods. Then, the
|
||||
* shader code of all transforms will be generated and passed to the createShaderText() method,
|
||||
* generating the full code of the processor. Finally, the finalize() method will be called to
|
||||
* finally create the shader. */
|
||||
class GPUShaderCreator : public OCIO::GpuShaderCreator {
|
||||
public:
|
||||
static std::shared_ptr<GPUShaderCreator> Create()
|
||||
{
|
||||
std::shared_ptr<GPUShaderCreator> instance = std::make_shared<GPUShaderCreator>();
|
||||
instance->setLanguage(OCIO::GPU_LANGUAGE_GLSL_4_0);
|
||||
return instance;
|
||||
}
|
||||
|
||||
/* Not used, but needs to be overridden, so return a nullptr. */
|
||||
OCIO::GpuShaderCreatorRcPtr clone() const override
|
||||
{
|
||||
return OCIO::GpuShaderCreatorRcPtr();
|
||||
}
|
||||
|
||||
/* This is ignored since we query using our own GPU capabilities system. */
|
||||
void setTextureMaxWidth(unsigned max_width) override {}
|
||||
|
||||
unsigned getTextureMaxWidth() const noexcept override
|
||||
{
|
||||
return GPU_max_texture_size();
|
||||
}
|
||||
|
||||
bool addUniform(const char *name, const DoubleGetter &get_double) override
|
||||
{
|
||||
/* Check if a resource exists with the same name and assert if it is the case, returning false
|
||||
* indicates failure to add the uniform for the shader creator. */
|
||||
if (!resource_names_.add(std::make_unique<std::string>(name))) {
|
||||
BLI_assert_unreachable();
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Don't use the name argument directly since ShaderCreateInfo only stores references to
|
||||
* resource names, instead, use the name that is stored in resource_names_. */
|
||||
std::string &resource_name = *resource_names_[resource_names_.size() - 1];
|
||||
shader_create_info_.push_constant(Type::FLOAT, resource_name);
|
||||
|
||||
float_uniforms_.add(resource_name, get_double);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool addUniform(const char *name, const BoolGetter &get_bool) override
|
||||
{
|
||||
/* Check if a resource exists with the same name and assert if it is the case, returning false
|
||||
* indicates failure to add the uniform for the shader creator. */
|
||||
if (!resource_names_.add(std::make_unique<std::string>(name))) {
|
||||
BLI_assert_unreachable();
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Don't use the name argument directly since ShaderCreateInfo only stores references to
|
||||
* resource names, instead, use the name that is stored in resource_names_. */
|
||||
std::string &resource_name = *resource_names_[resource_names_.size() - 1];
|
||||
shader_create_info_.push_constant(Type::BOOL, resource_name);
|
||||
|
||||
boolean_uniforms_.add(name, get_bool);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool addUniform(const char *name, const Float3Getter &get_float3) override
|
||||
{
|
||||
/* Check if a resource exists with the same name and assert if it is the case, returning false
|
||||
* indicates failure to add the uniform for the shader creator. */
|
||||
if (!resource_names_.add(std::make_unique<std::string>(name))) {
|
||||
BLI_assert_unreachable();
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Don't use the name argument directly since ShaderCreateInfo only stores references to
|
||||
* resource names, instead, use the name that is stored in resource_names_. */
|
||||
std::string &resource_name = *resource_names_[resource_names_.size() - 1];
|
||||
shader_create_info_.push_constant(Type::VEC3, resource_name);
|
||||
|
||||
vector_uniforms_.add(resource_name, get_float3);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool addUniform(const char *name,
|
||||
const SizeGetter &get_size,
|
||||
const VectorFloatGetter &get_vector_float) override
|
||||
{
|
||||
/* Check if a resource exists with the same name and assert if it is the case, returning false
|
||||
* indicates failure to add the uniform for the shader creator. */
|
||||
if (!resource_names_.add(std::make_unique<std::string>(name))) {
|
||||
BLI_assert_unreachable();
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Don't use the name argument directly since ShaderCreateInfo only stores references to
|
||||
* resource names, instead, use the name that is stored in resource_names_. */
|
||||
std::string &resource_name = *resource_names_[resource_names_.size() - 1];
|
||||
shader_create_info_.uniform_buf(buffers_sizes_.size(), "float", resource_name);
|
||||
|
||||
float_buffers_.add(resource_name, get_vector_float);
|
||||
buffers_sizes_.add(resource_name, get_size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool addUniform(const char *name,
|
||||
const SizeGetter &get_size,
|
||||
const VectorIntGetter &get_vector_int) override
|
||||
{
|
||||
/* Check if a resource exists with the same name and assert if it is the case, returning false
|
||||
* indicates failure to add the uniform for the shader creator. */
|
||||
if (!resource_names_.add(std::make_unique<std::string>(name))) {
|
||||
BLI_assert_unreachable();
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Don't use the name argument directly since ShaderCreateInfo only stores references to
|
||||
* resource names, instead, use the name that is stored in resource_names_. */
|
||||
std::string &resource_name = *resource_names_[resource_names_.size() - 1];
|
||||
shader_create_info_.uniform_buf(buffers_sizes_.size(), "int", resource_name);
|
||||
|
||||
int_buffers_.add(name, get_vector_int);
|
||||
buffers_sizes_.add(name, get_size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void addTexture(const char *texture_name,
|
||||
const char *sampler_name,
|
||||
unsigned width,
|
||||
unsigned height,
|
||||
TextureType channel,
|
||||
OCIO::Interpolation interpolation,
|
||||
const float *values) override
|
||||
{
|
||||
/* Check if a resource exists with the same name and assert if it is the case. */
|
||||
if (!resource_names_.add(std::make_unique<std::string>(sampler_name))) {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
|
||||
/* Don't use the name argument directly since ShaderCreateInfo only stores references to
|
||||
* resource names, instead, use the name that is stored in resource_names_. */
|
||||
std::string &resource_name = *resource_names_[resource_names_.size() - 1];
|
||||
|
||||
GPUTexture *texture;
|
||||
eGPUTextureFormat texture_format = (channel == TEXTURE_RGB_CHANNEL) ? GPU_RGB16F : GPU_R16F;
|
||||
/* A height of 1 indicates a 1D texture according to the OCIO API. */
|
||||
if (height == 1) {
|
||||
texture = GPU_texture_create_1d(
|
||||
texture_name, width, 1, texture_format, GPU_TEXTURE_USAGE_SHADER_READ, values);
|
||||
shader_create_info_.sampler(textures_.size() + 1, ImageType::FLOAT_1D, resource_name);
|
||||
}
|
||||
else {
|
||||
texture = GPU_texture_create_2d(
|
||||
texture_name, width, height, 1, texture_format, GPU_TEXTURE_USAGE_SHADER_READ, values);
|
||||
shader_create_info_.sampler(textures_.size() + 1, ImageType::FLOAT_2D, resource_name);
|
||||
}
|
||||
GPU_texture_filter_mode(texture, interpolation != OCIO::INTERP_NEAREST);
|
||||
|
||||
textures_.add(sampler_name, texture);
|
||||
}
|
||||
|
||||
void add3DTexture(const char *texture_name,
|
||||
const char *sampler_name,
|
||||
unsigned size,
|
||||
OCIO::Interpolation interpolation,
|
||||
const float *values) override
|
||||
{
|
||||
/* Check if a resource exists with the same name and assert if it is the case. */
|
||||
if (!resource_names_.add(std::make_unique<std::string>(sampler_name))) {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
|
||||
/* Don't use the name argument directly since ShaderCreateInfo only stores references to
|
||||
* resource names, instead, use the name that is stored in resource_names_. */
|
||||
std::string &resource_name = *resource_names_[resource_names_.size() - 1];
|
||||
shader_create_info_.sampler(textures_.size() + 1, ImageType::FLOAT_3D, resource_name);
|
||||
|
||||
GPUTexture *texture = GPU_texture_create_3d(
|
||||
texture_name, size, size, size, 1, GPU_RGB16F, GPU_TEXTURE_USAGE_SHADER_READ, values);
|
||||
GPU_texture_filter_mode(texture, interpolation != OCIO::INTERP_NEAREST);
|
||||
|
||||
textures_.add(sampler_name, texture);
|
||||
}
|
||||
|
||||
/* This gets called before the finalize() method to construct the shader code. We just
|
||||
* concatenate the code except for the declarations section. That's because the ShaderCreateInfo
|
||||
* will add the declaration itself. */
|
||||
void createShaderText(const char *declarations,
|
||||
const char *helper_methods,
|
||||
const char *function_header,
|
||||
const char *function_body,
|
||||
const char *function_footer) override
|
||||
{
|
||||
shader_code_ += helper_methods;
|
||||
shader_code_ += function_header;
|
||||
shader_code_ += function_body;
|
||||
shader_code_ += function_footer;
|
||||
}
|
||||
|
||||
/* This gets called when all resources were added using the respective addUniform() or
|
||||
* add[3D]Texture() methods and the shader code was generated using the createShaderText()
|
||||
* method. That is, we are ready to complete the ShaderCreateInfo and create a shader from it. */
|
||||
void finalize() override
|
||||
{
|
||||
GpuShaderCreator::finalize();
|
||||
|
||||
shader_create_info_.local_group_size(16, 16);
|
||||
shader_create_info_.sampler(0, ImageType::FLOAT_2D, input_sampler_name());
|
||||
shader_create_info_.image(
|
||||
0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, output_image_name());
|
||||
shader_create_info_.compute_source("gpu_shader_compositor_ocio_processor.glsl");
|
||||
shader_create_info_.compute_source_generated += shader_code_;
|
||||
|
||||
GPUShaderCreateInfo *info = reinterpret_cast<GPUShaderCreateInfo *>(&shader_create_info_);
|
||||
shader_ = GPU_shader_create_from_info(info);
|
||||
}
|
||||
|
||||
GPUShader *bind_shader_and_resources()
|
||||
{
|
||||
GPU_shader_bind(shader_);
|
||||
|
||||
for (auto item : float_uniforms_.items()) {
|
||||
GPU_shader_uniform_1f(shader_, item.key.c_str(), item.value());
|
||||
}
|
||||
|
||||
for (auto item : boolean_uniforms_.items()) {
|
||||
GPU_shader_uniform_1b(shader_, item.key.c_str(), item.value());
|
||||
}
|
||||
|
||||
for (auto item : vector_uniforms_.items()) {
|
||||
GPU_shader_uniform_3fv(shader_, item.key.c_str(), item.value().data());
|
||||
}
|
||||
|
||||
for (auto item : float_buffers_.items()) {
|
||||
GPUUniformBuf *buffer = GPU_uniformbuf_create_ex(
|
||||
buffers_sizes_.lookup(item.key)(), item.value(), item.key.c_str());
|
||||
const int ubo_location = GPU_shader_get_ubo_binding(shader_, item.key.c_str());
|
||||
GPU_uniformbuf_bind(buffer, ubo_location);
|
||||
uniform_buffers_.append(buffer);
|
||||
}
|
||||
|
||||
for (auto item : int_buffers_.items()) {
|
||||
GPUUniformBuf *buffer = GPU_uniformbuf_create_ex(
|
||||
buffers_sizes_.lookup(item.key)(), item.value(), item.key.c_str());
|
||||
const int ubo_location = GPU_shader_get_ubo_binding(shader_, item.key.c_str());
|
||||
GPU_uniformbuf_bind(buffer, ubo_location);
|
||||
uniform_buffers_.append(buffer);
|
||||
}
|
||||
|
||||
for (auto item : textures_.items()) {
|
||||
const int texture_image_unit = GPU_shader_get_sampler_binding(shader_, item.key.c_str());
|
||||
GPU_texture_bind(item.value, texture_image_unit);
|
||||
}
|
||||
|
||||
return shader_;
|
||||
}
|
||||
|
||||
void unbind_shader_and_resources()
|
||||
{
|
||||
for (GPUUniformBuf *buffer : uniform_buffers_) {
|
||||
GPU_uniformbuf_unbind(buffer);
|
||||
GPU_uniformbuf_free(buffer);
|
||||
}
|
||||
|
||||
for (GPUTexture *texture : textures_.values()) {
|
||||
GPU_texture_unbind(texture);
|
||||
}
|
||||
|
||||
GPU_shader_unbind();
|
||||
}
|
||||
|
||||
const char *input_sampler_name()
|
||||
{
|
||||
return "input_tx";
|
||||
}
|
||||
|
||||
const char *output_image_name()
|
||||
{
|
||||
return "output_img";
|
||||
}
|
||||
|
||||
~GPUShaderCreator() override
|
||||
{
|
||||
for (GPUTexture *texture : textures_.values()) {
|
||||
GPU_texture_free(texture);
|
||||
}
|
||||
|
||||
GPU_shader_free(shader_);
|
||||
}
|
||||
|
||||
private:
|
||||
/* The processor shader and the ShaderCreateInfo used to construct it. Constructed and
|
||||
* initialized in the finalize() method. */
|
||||
GPUShader *shader_ = nullptr;
|
||||
ShaderCreateInfo shader_create_info_ = ShaderCreateInfo("OCIO Processor");
|
||||
|
||||
/* Stores the generated OCIOMain function as well as a number of helper functions. Initialized in
|
||||
* the createShaderText() method. */
|
||||
std::string shader_code_;
|
||||
|
||||
/* Maps that associates the name of a uniform with a getter function that returns its value.
|
||||
* Initialized in the respective addUniform() methods. */
|
||||
Map<std::string, DoubleGetter> float_uniforms_;
|
||||
Map<std::string, BoolGetter> boolean_uniforms_;
|
||||
Map<std::string, Float3Getter> vector_uniforms_;
|
||||
|
||||
/* Maps that associates the name of uniform buffer objects with a getter function that returns
|
||||
* its values. Initialized in the respective addUniform() methods. */
|
||||
Map<std::string, VectorFloatGetter> float_buffers_;
|
||||
Map<std::string, VectorIntGetter> int_buffers_;
|
||||
|
||||
/* A map that associates the name of uniform buffer objects with a getter functions that returns
|
||||
* its number of elements. Initialized in the respective addUniform() methods. */
|
||||
Map<std::string, SizeGetter> buffers_sizes_;
|
||||
|
||||
/* A map that associates the name of a sampler with its corresponding texture. Initialized in the
|
||||
* addTexture() and add3DTexture() methods. */
|
||||
Map<std::string, GPUTexture *> textures_;
|
||||
|
||||
/* A vector set that stores the names of all the resources used by the shader. This is used to:
|
||||
* 1. Check for name collisions when adding new resources.
|
||||
* 2. Store the resource names throughout the construction of the shader since the
|
||||
* ShaderCreateInfo class only stores references to resources names. */
|
||||
VectorSet<std::unique_ptr<std::string>> resource_names_;
|
||||
|
||||
/* A vectors that stores the created uniform buffers when bind_shader_and_resources() is called,
|
||||
* so that they can be properly unbound and freed in the unbind_shader_and_resources() method. */
|
||||
Vector<GPUUniformBuf *> uniform_buffers_;
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
/* A stub implementation in case OCIO is disabled at build time. */
|
||||
class GPUShaderCreator {
|
||||
public:
|
||||
GPUShader *bind_shader_and_resources()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void unbind_shader_and_resources() {}
|
||||
|
||||
const char *input_sampler_name()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char *output_image_name()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* OCIO Color Space Conversion Shader.
|
||||
*/
|
||||
|
||||
OCIOColorSpaceConversionShader::OCIOColorSpaceConversionShader(std::string source,
|
||||
std::string target)
|
||||
{
|
||||
#if defined(WITH_OCIO)
|
||||
/* Get a GPU processor that transforms the source color space to the target color space. */
|
||||
OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
|
||||
OCIO::ConstProcessorRcPtr processor = config->getProcessor(source.c_str(), target.c_str());
|
||||
OCIO::ConstGPUProcessorRcPtr gpu_processor = processor->getDefaultGPUProcessor();
|
||||
|
||||
/* Create a GPU shader creator and construct it based on the transforms in the default GPU
|
||||
* processor. */
|
||||
shader_creator_ = GPUShaderCreator::Create();
|
||||
auto ocio_shader_creator = std::static_pointer_cast<OCIO::GpuShaderCreator>(shader_creator_);
|
||||
gpu_processor->extractGpuShaderInfo(ocio_shader_creator);
|
||||
#endif
|
||||
}
|
||||
|
||||
GPUShader *OCIOColorSpaceConversionShader::bind_shader_and_resources()
|
||||
{
|
||||
return shader_creator_->bind_shader_and_resources();
|
||||
}
|
||||
|
||||
void OCIOColorSpaceConversionShader::unbind_shader_and_resources()
|
||||
{
|
||||
shader_creator_->unbind_shader_and_resources();
|
||||
}
|
||||
|
||||
const char *OCIOColorSpaceConversionShader::input_sampler_name()
|
||||
{
|
||||
return shader_creator_->input_sampler_name();
|
||||
}
|
||||
|
||||
const char *OCIOColorSpaceConversionShader::output_image_name()
|
||||
{
|
||||
return shader_creator_->output_image_name();
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* OCIO Color Space Conversion Shader Container.
|
||||
*/
|
||||
|
||||
void OCIOColorSpaceConversionShaderContainer::reset()
|
||||
{
|
||||
/* First, delete all resources that are no longer needed. */
|
||||
map_.remove_if([](auto item) { return !item.value->needed; });
|
||||
|
||||
/* Second, reset the needed status of the remaining resources to false to ready them to track
|
||||
* their needed status for the next evaluation. */
|
||||
for (auto &value : map_.values()) {
|
||||
value->needed = false;
|
||||
}
|
||||
}
|
||||
|
||||
OCIOColorSpaceConversionShader &OCIOColorSpaceConversionShaderContainer::get(std::string source,
|
||||
std::string target)
|
||||
{
|
||||
#if defined(WITH_OCIO)
|
||||
/* Use the config cache ID in the cache key in case the configuration changed at runtime. */
|
||||
std::string config_cache_id = OCIO::GetCurrentConfig()->getCacheID();
|
||||
#else
|
||||
std::string config_cache_id;
|
||||
#endif
|
||||
|
||||
const OCIOColorSpaceConversionShaderKey key(source, target, config_cache_id);
|
||||
|
||||
OCIOColorSpaceConversionShader &shader = *map_.lookup_or_add_cb(
|
||||
key, [&]() { return std::make_unique<OCIOColorSpaceConversionShader>(source, target); });
|
||||
|
||||
shader.needed = true;
|
||||
return shader;
|
||||
}
|
||||
|
||||
} // namespace blender::realtime_compositor
|
||||
@@ -12,6 +12,7 @@ void StaticCacheManager::reset()
|
||||
cached_textures.reset();
|
||||
cached_masks.reset();
|
||||
smaa_precomputed_textures.reset();
|
||||
ocio_color_space_conversion_shaders.reset();
|
||||
}
|
||||
|
||||
} // namespace blender::realtime_compositor
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
|
||||
|
||||
/* OCIOMain will be dynamically generated in the OCIOColorSpaceConversionShader class and appended
|
||||
* at the end of this file, so forward declare it. Such forward declarations are not supported nor
|
||||
* needed on Metal. */
|
||||
#if !defined(GPU_METAL)
|
||||
vec4 OCIOMain(vec4 inPixel);
|
||||
#endif
|
||||
|
||||
void main()
|
||||
{
|
||||
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
|
||||
imageStore(output_img, texel, OCIOMain(texture_load(input_tx, texel)));
|
||||
}
|
||||
@@ -441,6 +441,11 @@ struct GPUSource {
|
||||
int64_t cursor = -1;
|
||||
StringRef func_return_type, func_name, func_args;
|
||||
while (function_parse(input, cursor, func_return_type, func_name, func_args)) {
|
||||
/* Main functions needn't be handled because they are the entry point of the shader. */
|
||||
if (func_name == "main") {
|
||||
continue;
|
||||
}
|
||||
|
||||
GPUFunction *func = MEM_new<GPUFunction>(__func__);
|
||||
func_name.copy(func->name, sizeof(func->name));
|
||||
func->source = reinterpret_cast<void *>(this);
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
* \ingroup cmpnodes
|
||||
*/
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
#include "BLT_translation.h"
|
||||
|
||||
#include "RNA_access.h"
|
||||
@@ -16,13 +14,23 @@
|
||||
|
||||
#include "IMB_colormanagement.h"
|
||||
|
||||
#include "GPU_shader.h"
|
||||
|
||||
#include "COM_node_operation.hh"
|
||||
#include "COM_ocio_color_space_conversion_shader.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender::nodes::node_composite_convert_color_space_cc {
|
||||
|
||||
NODE_STORAGE_FUNCS(NodeConvertColorSpace)
|
||||
|
||||
static void CMP_NODE_CONVERT_COLOR_SPACE_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f});
|
||||
b.add_input<decl::Color>(N_("Image"))
|
||||
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
|
||||
.compositor_domain_priority(0);
|
||||
b.add_output<decl::Color>(N_("Image"));
|
||||
}
|
||||
|
||||
@@ -59,8 +67,79 @@ class ConvertColorSpaceOperation : public NodeOperation {
|
||||
|
||||
void execute() override
|
||||
{
|
||||
get_input("Image").pass_through(get_result("Image"));
|
||||
context().set_info_message("Viewport compositor setup not fully supported");
|
||||
Result &input_image = get_input("Image");
|
||||
Result &output_image = get_result("Image");
|
||||
if (is_identity()) {
|
||||
input_image.pass_through(output_image);
|
||||
return;
|
||||
}
|
||||
|
||||
if (input_image.is_single_value()) {
|
||||
execute_single();
|
||||
return;
|
||||
}
|
||||
|
||||
const char *source = node_storage(bnode()).from_color_space;
|
||||
const char *target = node_storage(bnode()).to_color_space;
|
||||
|
||||
OCIOColorSpaceConversionShader &ocio_shader =
|
||||
context().cache_manager().ocio_color_space_conversion_shaders.get(source, target);
|
||||
|
||||
GPUShader *shader = ocio_shader.bind_shader_and_resources();
|
||||
|
||||
/* A null shader indicates that the conversion shader is just a stub implementation since OCIO
|
||||
* is disabled at compile time, so pass the input through in that case. */
|
||||
if (!shader) {
|
||||
input_image.pass_through(output_image);
|
||||
return;
|
||||
}
|
||||
|
||||
input_image.bind_as_texture(shader, ocio_shader.input_sampler_name());
|
||||
|
||||
const Domain domain = compute_domain();
|
||||
output_image.allocate_texture(domain);
|
||||
output_image.bind_as_image(shader, ocio_shader.output_image_name());
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.size);
|
||||
|
||||
input_image.unbind_as_texture();
|
||||
output_image.unbind_as_image();
|
||||
ocio_shader.unbind_shader_and_resources();
|
||||
}
|
||||
|
||||
void execute_single()
|
||||
{
|
||||
const char *source = node_storage(bnode()).from_color_space;
|
||||
const char *target = node_storage(bnode()).to_color_space;
|
||||
ColormanageProcessor *color_processor = IMB_colormanagement_colorspace_processor_new(source,
|
||||
target);
|
||||
|
||||
Result &input_image = get_input("Image");
|
||||
float4 color = input_image.get_color_value();
|
||||
|
||||
IMB_colormanagement_processor_apply_pixel(color_processor, color, 3);
|
||||
IMB_colormanagement_processor_free(color_processor);
|
||||
|
||||
Result &output_image = get_result("Image");
|
||||
output_image.allocate_single_value();
|
||||
output_image.set_color_value(color);
|
||||
}
|
||||
|
||||
bool is_identity()
|
||||
{
|
||||
const char *source = node_storage(bnode()).from_color_space;
|
||||
const char *target = node_storage(bnode()).to_color_space;
|
||||
|
||||
if (STREQ(source, target)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Data color spaces ignore any color transformation that gets applied to them. */
|
||||
if (IMB_colormanagement_space_name_is_data(source)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -85,8 +164,6 @@ void register_node_type_cmp_convert_color_space(void)
|
||||
node_type_storage(
|
||||
&ntype, "NodeConvertColorSpace", node_free_standard_storage, node_copy_standard_storage);
|
||||
ntype.get_compositor_operation = file_ns::get_compositor_operation;
|
||||
ntype.realtime_compositor_unsupported_message = N_(
|
||||
"Node not supported in the Viewport compositor");
|
||||
|
||||
nodeRegisterType(&ntype);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user