mirror of
https://github.com/blender/blender
synced 2026-09-29 04:37:17 +03:00
Refactor: Cycles: Colorspace changes to prepare for texture cache
* Move code into util/ module to be reused by tx generator. * Move logic from ShaderManager into colorspace.cpp. * Improve naming distinction between scene linear and data, and sRGB and scene linear compressed as sRGB. * Use interop ID instead of enum for known scene linear color spaces. * Support colorspace conversion on image buffers with arbitrary y stride. Pull Request: https://projects.blender.org/blender/blender/pulls/152665
This commit is contained in:
parent
78efebec1e
commit
ca4458f6ea
16 changed files with 946 additions and 744 deletions
|
|
@ -47,12 +47,14 @@ bool BlenderImageLoader::load_metadata(const ImageDeviceFeatures & /*features*/,
|
|||
ImageMetaData &metadata)
|
||||
{
|
||||
bool is_float = false;
|
||||
bool is_data = false;
|
||||
|
||||
{
|
||||
void *lock;
|
||||
blender::ImBuf *ibuf = BKE_image_acquire_ibuf(b_image, &b_iuser, &lock);
|
||||
if (ibuf) {
|
||||
is_float = ibuf->float_buffer.data != nullptr;
|
||||
is_data = ibuf->colormanage_flag & blender::IMB_COLORMANAGE_IS_DATA;
|
||||
metadata.width = ibuf->x;
|
||||
metadata.height = ibuf->y;
|
||||
metadata.channels = (is_float) ? ibuf->channels : 4;
|
||||
|
|
@ -76,12 +78,13 @@ bool BlenderImageLoader::load_metadata(const ImageDeviceFeatures & /*features*/,
|
|||
|
||||
/* Float images are already converted on the Blender side,
|
||||
* no need to do anything in Cycles. */
|
||||
metadata.colorspace = u_colorspace_raw;
|
||||
metadata.colorspace = (is_data) ? u_colorspace_data : u_colorspace_scene_linear;
|
||||
}
|
||||
else {
|
||||
/* In some cases (e.g. #94135), the colorspace setting in Blender gets updated as part of the
|
||||
* metadata queries in this function, so update the colorspace setting here. */
|
||||
metadata.colorspace = b_image->colorspace_settings.name;
|
||||
metadata.colorspace = (is_data) ? u_colorspace_data :
|
||||
ustring(b_image->colorspace_settings.name);
|
||||
metadata.type = IMAGE_DATA_TYPE_BYTE4;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@
|
|||
|
||||
#include <cstring>
|
||||
|
||||
#include "scene/colorspace.h"
|
||||
#include "scene/object.h"
|
||||
|
||||
#include "util/colorspace.h"
|
||||
#include "util/log.h"
|
||||
#include "util/string.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ set(SRC
|
|||
background.cpp
|
||||
bake.cpp
|
||||
camera.cpp
|
||||
colorspace.cpp
|
||||
constant_fold.cpp
|
||||
devicescene.cpp
|
||||
film.cpp
|
||||
|
|
@ -56,7 +55,6 @@ set(SRC_HEADERS
|
|||
bake.h
|
||||
background.h
|
||||
camera.h
|
||||
colorspace.h
|
||||
constant_fold.h
|
||||
devicescene.h
|
||||
film.h
|
||||
|
|
@ -110,16 +108,6 @@ if(WITH_CYCLES_OSL)
|
|||
)
|
||||
endif()
|
||||
|
||||
if(WITH_OPENCOLORIO)
|
||||
add_definitions(-DWITH_OCIO)
|
||||
list(APPEND LIB
|
||||
bf::dependencies::optional::opencolorio
|
||||
)
|
||||
if(WIN32 AND NOT USD_OVERRIDE_OPENCOLORIO)
|
||||
add_definitions(-DOpenColorIO_SKIP_IMPORTS)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(WITH_OPENVDB)
|
||||
list(APPEND LIB
|
||||
bf::dependencies::optional::openvdb
|
||||
|
|
|
|||
|
|
@ -900,7 +900,7 @@ void Camera::set_osl_camera(Scene *scene,
|
|||
{
|
||||
#ifdef WITH_OSL
|
||||
/* Ensure shading system exists before we try to load a shader. */
|
||||
scene->osl_manager->shading_system_init(scene->shader_manager->get_scene_linear_space());
|
||||
scene->osl_manager->shading_system_init(scene->shader_manager->get_scene_linear_interop_id());
|
||||
|
||||
/* Load the shader. */
|
||||
const char *hash;
|
||||
|
|
|
|||
|
|
@ -1,528 +0,0 @@
|
|||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include "scene/colorspace.h"
|
||||
|
||||
#include "util/color.h"
|
||||
#include "util/image.h"
|
||||
#include "util/log.h"
|
||||
#include "util/map.h"
|
||||
#include "util/math.h"
|
||||
#include "util/thread.h"
|
||||
#include "util/vector.h"
|
||||
|
||||
#ifdef WITH_OCIO
|
||||
# include <OpenColorIO/OpenColorIO.h>
|
||||
namespace OCIO = OCIO_NAMESPACE;
|
||||
#endif
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* Builtin colorspaces. */
|
||||
ustring u_colorspace_auto;
|
||||
ustring u_colorspace_raw("__builtin_raw");
|
||||
ustring u_colorspace_srgb("__builtin_srgb");
|
||||
|
||||
/* Cached data. */
|
||||
#ifdef WITH_OCIO
|
||||
static thread_mutex cache_colorspaces_mutex;
|
||||
static unordered_map<ustring, ustring> cached_colorspaces;
|
||||
|
||||
static thread_mutex cache_processors_mutex;
|
||||
static unordered_map<ustring, OCIO::ConstProcessorRcPtr> cached_processors;
|
||||
|
||||
static thread_mutex cache_scene_linear_mutex;
|
||||
static string cache_scene_linear_name;
|
||||
|
||||
static void check_invalidate_caches()
|
||||
{
|
||||
/* Invalidate cached processors and colorspace, in case Blender changed it.
|
||||
* Note this should not happen during rendering, all render should be stopped
|
||||
* before it is changed. */
|
||||
const thread_scoped_lock cache_scene_linear_lock(cache_scene_linear_mutex);
|
||||
OCIO::ConstConfigRcPtr config = nullptr;
|
||||
try {
|
||||
config = OCIO::GetCurrentConfig();
|
||||
}
|
||||
catch (const OCIO::Exception &exception) {
|
||||
LOG_ERROR << "OCIO config error: " << exception.what();
|
||||
return;
|
||||
}
|
||||
|
||||
const OCIO::ConstColorSpaceRcPtr scene_linear_colorspace = config->getColorSpace("scene_linear");
|
||||
if (scene_linear_colorspace && cache_scene_linear_name != scene_linear_colorspace->getName()) {
|
||||
cache_scene_linear_name = scene_linear_colorspace->getName();
|
||||
{
|
||||
const thread_scoped_lock cache_processors_lock(cache_processors_mutex);
|
||||
cached_processors.clear();
|
||||
}
|
||||
{
|
||||
const thread_scoped_lock cache_lock(cache_colorspaces_mutex);
|
||||
cached_colorspaces.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
ColorSpaceProcessor *ColorSpaceManager::get_processor(ustring colorspace)
|
||||
{
|
||||
#ifdef WITH_OCIO
|
||||
/* Only use this for OpenColorIO color spaces, not the builtin ones. */
|
||||
assert(colorspace != u_colorspace_srgb && colorspace != u_colorspace_auto);
|
||||
|
||||
if (colorspace == u_colorspace_raw) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
OCIO::ConstConfigRcPtr config = nullptr;
|
||||
try {
|
||||
config = OCIO::GetCurrentConfig();
|
||||
}
|
||||
catch (const OCIO::Exception &exception) {
|
||||
LOG_ERROR << "OCIO config error: " << exception.what();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!config) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
check_invalidate_caches();
|
||||
|
||||
/* Cache processor until free_memory(), memory overhead is expected to be
|
||||
* small and the processor is likely to be reused. */
|
||||
const thread_scoped_lock cache_processors_lock(cache_processors_mutex);
|
||||
|
||||
if (cached_processors.find(colorspace) == cached_processors.end()) {
|
||||
try {
|
||||
cached_processors[colorspace] = config->getProcessor(colorspace.c_str(), "scene_linear");
|
||||
}
|
||||
catch (const OCIO::Exception &exception) {
|
||||
cached_processors[colorspace] = OCIO::ConstProcessorRcPtr();
|
||||
LOG_WARNING << "Colorspace " << colorspace.c_str()
|
||||
<< " cannot be converted to scene_linear: " << exception.what();
|
||||
}
|
||||
}
|
||||
|
||||
const OCIO::Processor *processor = cached_processors[colorspace].get();
|
||||
return (ColorSpaceProcessor *)processor;
|
||||
#else
|
||||
/* No OpenColorIO. */
|
||||
(void)colorspace;
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool ColorSpaceManager::colorspace_is_data(ustring colorspace)
|
||||
{
|
||||
if (colorspace == u_colorspace_auto || colorspace == u_colorspace_raw ||
|
||||
colorspace == u_colorspace_srgb)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef WITH_OCIO
|
||||
OCIO::ConstConfigRcPtr config = nullptr;
|
||||
try {
|
||||
config = OCIO::GetCurrentConfig();
|
||||
}
|
||||
catch (const OCIO::Exception &exception) {
|
||||
LOG_ERROR << "OCIO config error: " << exception.what();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const OCIO::ConstColorSpaceRcPtr space = config->getColorSpace(colorspace.c_str());
|
||||
return space && space->isData();
|
||||
}
|
||||
catch (const OCIO::Exception &) {
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
ustring ColorSpaceManager::detect_known_colorspace(ustring colorspace,
|
||||
const char *file_colorspace,
|
||||
const char *file_format,
|
||||
bool is_float)
|
||||
{
|
||||
if (colorspace == u_colorspace_auto) {
|
||||
/* Auto detect sRGB or raw if none specified. */
|
||||
if (is_float) {
|
||||
const bool srgb = (strcmp(file_colorspace, "sRGB") == 0 ||
|
||||
strcmp(file_colorspace, "GammaCorrected") == 0 ||
|
||||
(file_colorspace[0] == '\0' &&
|
||||
(strcmp(file_format, "png") == 0 || strcmp(file_format, "jpeg") == 0 ||
|
||||
strcmp(file_format, "tiff") == 0 || strcmp(file_format, "dpx") == 0 ||
|
||||
strcmp(file_format, "jpeg2000") == 0)));
|
||||
return srgb ? u_colorspace_srgb : u_colorspace_raw;
|
||||
}
|
||||
return u_colorspace_srgb;
|
||||
}
|
||||
|
||||
/* Builtin colorspaces. */
|
||||
if (colorspace == u_colorspace_srgb || colorspace == u_colorspace_raw) {
|
||||
return colorspace;
|
||||
}
|
||||
|
||||
/* Use OpenColorIO. */
|
||||
#ifdef WITH_OCIO
|
||||
check_invalidate_caches();
|
||||
|
||||
{
|
||||
const thread_scoped_lock cache_lock(cache_colorspaces_mutex);
|
||||
/* Cached lookup. */
|
||||
if (cached_colorspaces.find(colorspace) != cached_colorspaces.end()) {
|
||||
return cached_colorspaces[colorspace];
|
||||
}
|
||||
}
|
||||
|
||||
/* Detect if it matches a simple builtin colorspace. */
|
||||
bool is_scene_linear;
|
||||
bool is_srgb;
|
||||
is_builtin_colorspace(colorspace, is_scene_linear, is_srgb);
|
||||
|
||||
const thread_scoped_lock cache_lock(cache_colorspaces_mutex);
|
||||
if (is_scene_linear) {
|
||||
LOG_INFO << "Colorspace " << colorspace.string() << " is no-op";
|
||||
cached_colorspaces[colorspace] = u_colorspace_raw;
|
||||
return u_colorspace_raw;
|
||||
}
|
||||
if (is_srgb) {
|
||||
LOG_INFO << "Colorspace " << colorspace.string() << " is sRGB";
|
||||
cached_colorspaces[colorspace] = u_colorspace_srgb;
|
||||
return u_colorspace_srgb;
|
||||
}
|
||||
|
||||
/* Verify if we can convert from the requested color space. */
|
||||
if (!get_processor(colorspace)) {
|
||||
OCIO::ConstConfigRcPtr config = nullptr;
|
||||
try {
|
||||
config = OCIO::GetCurrentConfig();
|
||||
}
|
||||
catch (const OCIO::Exception &exception) {
|
||||
LOG_ERROR << "OCIO config error: " << exception.what();
|
||||
return u_colorspace_raw;
|
||||
}
|
||||
|
||||
if (!config || !config->getColorSpace(colorspace.c_str())) {
|
||||
LOG_WARNING << "Colorspace " << colorspace.c_str() << " not found, using raw instead";
|
||||
}
|
||||
else {
|
||||
LOG_WARNING << "Colorspace " << colorspace.c_str()
|
||||
<< " can't be converted to scene_linear, using raw instead";
|
||||
}
|
||||
cached_colorspaces[colorspace] = u_colorspace_raw;
|
||||
return u_colorspace_raw;
|
||||
}
|
||||
|
||||
/* Convert to/from colorspace with OpenColorIO. */
|
||||
LOG_INFO << "Colorspace " << colorspace.string() << " handled through OpenColorIO";
|
||||
cached_colorspaces[colorspace] = colorspace;
|
||||
return colorspace;
|
||||
#else
|
||||
LOG_WARNING << "Colorspace " << colorspace.c_str()
|
||||
<< " not available, built without OpenColorIO";
|
||||
return u_colorspace_raw;
|
||||
#endif
|
||||
}
|
||||
|
||||
void ColorSpaceManager::is_builtin_colorspace(ustring colorspace,
|
||||
bool &is_scene_linear,
|
||||
bool &is_srgb)
|
||||
{
|
||||
#ifdef WITH_OCIO
|
||||
const OCIO::Processor *processor = (const OCIO::Processor *)get_processor(colorspace);
|
||||
if (!processor) {
|
||||
is_scene_linear = false;
|
||||
is_srgb = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const OCIO::ConstCPUProcessorRcPtr device_processor = processor->getDefaultCPUProcessor();
|
||||
is_scene_linear = true;
|
||||
is_srgb = true;
|
||||
for (int i = 0; i < 256; i++) {
|
||||
const float v = i / 255.0f;
|
||||
|
||||
float cR[3] = {v, 0, 0};
|
||||
float cG[3] = {0, v, 0};
|
||||
float cB[3] = {0, 0, v};
|
||||
float cW[3] = {v, v, v};
|
||||
device_processor->applyRGB(cR);
|
||||
device_processor->applyRGB(cG);
|
||||
device_processor->applyRGB(cB);
|
||||
device_processor->applyRGB(cW);
|
||||
|
||||
/* Make sure that there is no channel crosstalk. */
|
||||
if (fabsf(cR[1]) > 1e-5f || fabsf(cR[2]) > 1e-5f || fabsf(cG[0]) > 1e-5f ||
|
||||
fabsf(cG[2]) > 1e-5f || fabsf(cB[0]) > 1e-5f || fabsf(cB[1]) > 1e-5f)
|
||||
{
|
||||
is_scene_linear = false;
|
||||
is_srgb = false;
|
||||
break;
|
||||
}
|
||||
/* Make sure that the three primaries combine linearly. */
|
||||
if (!compare_floats(cR[0], cW[0], 1e-6f, 64) || !compare_floats(cG[1], cW[1], 1e-6f, 64) ||
|
||||
!compare_floats(cB[2], cW[2], 1e-6f, 64))
|
||||
{
|
||||
is_scene_linear = false;
|
||||
is_srgb = false;
|
||||
break;
|
||||
}
|
||||
/* Make sure that the three channels behave identically. */
|
||||
if (!compare_floats(cW[0], cW[1], 1e-6f, 64) || !compare_floats(cW[1], cW[2], 1e-6f, 64)) {
|
||||
is_scene_linear = false;
|
||||
is_srgb = false;
|
||||
break;
|
||||
}
|
||||
|
||||
const float out_v = average(make_float3(cW[0], cW[1], cW[2]));
|
||||
if (!compare_floats(v, out_v, 1e-6f, 64)) {
|
||||
is_scene_linear = false;
|
||||
}
|
||||
if (!compare_floats(color_srgb_to_linear(v), out_v, 1e-4f, 64)) {
|
||||
is_srgb = false;
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)colorspace;
|
||||
is_scene_linear = false;
|
||||
is_srgb = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef WITH_OCIO
|
||||
|
||||
template<typename T> inline float4 cast_to_float4(T *data)
|
||||
{
|
||||
return make_float4(util_image_cast_to_float(data[0]),
|
||||
util_image_cast_to_float(data[1]),
|
||||
util_image_cast_to_float(data[2]),
|
||||
util_image_cast_to_float(data[3]));
|
||||
}
|
||||
|
||||
template<typename T> inline void cast_from_float4(T *data, const float4 value)
|
||||
{
|
||||
data[0] = util_image_cast_from_float<T>(value.x);
|
||||
data[1] = util_image_cast_from_float<T>(value.y);
|
||||
data[2] = util_image_cast_from_float<T>(value.z);
|
||||
data[3] = util_image_cast_from_float<T>(value.w);
|
||||
}
|
||||
|
||||
/* Slower versions for other all data types, which needs to convert to float and back. */
|
||||
template<typename T, bool compress_as_srgb = false>
|
||||
inline void processor_apply_pixels_rgba(const OCIO::Processor *processor,
|
||||
T *pixels,
|
||||
const size_t num_pixels,
|
||||
const bool ignore_alpha)
|
||||
{
|
||||
/* TODO: implement faster version for when we know the conversion
|
||||
* is a simple matrix transform between linear spaces. In that case
|
||||
* un-premultiply is not needed. */
|
||||
const OCIO::ConstCPUProcessorRcPtr device_processor = processor->getDefaultCPUProcessor();
|
||||
|
||||
/* Process large images in chunks to keep temporary memory requirement down. */
|
||||
const size_t chunk_size = std::min((size_t)(16 * 1024 * 1024), num_pixels);
|
||||
vector<float4> float_pixels(chunk_size);
|
||||
|
||||
for (size_t j = 0; j < num_pixels; j += chunk_size) {
|
||||
const size_t width = std::min(chunk_size, num_pixels - j);
|
||||
|
||||
for (size_t i = 0; i < width; i++) {
|
||||
float4 value = cast_to_float4(pixels + 4 * (j + i));
|
||||
|
||||
if (!ignore_alpha && !(value.w <= 0.0f || value.w == 1.0f)) {
|
||||
const float inv_alpha = 1.0f / value.w;
|
||||
value.x *= inv_alpha;
|
||||
value.y *= inv_alpha;
|
||||
value.z *= inv_alpha;
|
||||
}
|
||||
|
||||
float_pixels[i] = value;
|
||||
}
|
||||
|
||||
const OCIO::PackedImageDesc desc((float *)float_pixels.data(), width, 1, 4);
|
||||
device_processor->apply(desc);
|
||||
|
||||
for (size_t i = 0; i < width; i++) {
|
||||
float4 value = float_pixels[i];
|
||||
|
||||
if (compress_as_srgb) {
|
||||
value = color_linear_to_srgb_v4(value);
|
||||
}
|
||||
|
||||
if (!ignore_alpha && !(value.w <= 0.0f || value.w == 1.0f)) {
|
||||
value.x *= value.w;
|
||||
value.y *= value.w;
|
||||
value.z *= value.w;
|
||||
}
|
||||
|
||||
cast_from_float4(pixels + 4 * (j + i), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, bool compress_as_srgb = false>
|
||||
inline void processor_apply_pixels_grayscale(const OCIO::Processor *processor,
|
||||
T *pixels,
|
||||
const size_t num_pixels)
|
||||
{
|
||||
const OCIO::ConstCPUProcessorRcPtr device_processor = processor->getDefaultCPUProcessor();
|
||||
|
||||
/* Process large images in chunks to keep temporary memory requirement down. */
|
||||
const size_t chunk_size = std::min((size_t)(16 * 1024 * 1024), num_pixels);
|
||||
vector<float> float_pixels(chunk_size * 3);
|
||||
|
||||
for (size_t j = 0; j < num_pixels; j += chunk_size) {
|
||||
const size_t width = std::min(chunk_size, num_pixels - j);
|
||||
|
||||
/* Convert to 3 channels, since that's the minimum required by OpenColorIO. */
|
||||
{
|
||||
const T *pixel = pixels + j;
|
||||
float *fpixel = float_pixels.data();
|
||||
for (size_t i = 0; i < width; i++, pixel++, fpixel += 3) {
|
||||
const float f = util_image_cast_to_float<T>(*pixel);
|
||||
fpixel[0] = f;
|
||||
fpixel[1] = f;
|
||||
fpixel[2] = f;
|
||||
}
|
||||
}
|
||||
|
||||
const OCIO::PackedImageDesc desc((float *)float_pixels.data(), width, 1, 3);
|
||||
device_processor->apply(desc);
|
||||
|
||||
{
|
||||
T *pixel = pixels + j;
|
||||
const float *fpixel = float_pixels.data();
|
||||
for (size_t i = 0; i < width; i++, pixel++, fpixel += 3) {
|
||||
float f = average(make_float3(fpixel[0], fpixel[1], fpixel[2]));
|
||||
if (compress_as_srgb) {
|
||||
f = color_linear_to_srgb(f);
|
||||
}
|
||||
*pixel = util_image_cast_from_float<T>(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
template<typename T>
|
||||
void ColorSpaceManager::to_scene_linear(ustring colorspace,
|
||||
T *pixels,
|
||||
const size_t num_pixels,
|
||||
bool is_rgba,
|
||||
bool compress_as_srgb,
|
||||
bool ignore_alpha)
|
||||
{
|
||||
#ifdef WITH_OCIO
|
||||
const OCIO::Processor *processor = (const OCIO::Processor *)get_processor(colorspace);
|
||||
|
||||
if (processor) {
|
||||
if (is_rgba) {
|
||||
if (compress_as_srgb) {
|
||||
/* Compress output as sRGB. */
|
||||
processor_apply_pixels_rgba<T, true>(processor, pixels, num_pixels, ignore_alpha);
|
||||
}
|
||||
else {
|
||||
/* Write output as scene linear directly. */
|
||||
processor_apply_pixels_rgba<T>(processor, pixels, num_pixels, ignore_alpha);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (compress_as_srgb) {
|
||||
/* Compress output as sRGB. */
|
||||
processor_apply_pixels_grayscale<T, true>(processor, pixels, num_pixels);
|
||||
}
|
||||
else {
|
||||
/* Write output as scene linear directly. */
|
||||
processor_apply_pixels_grayscale<T>(processor, pixels, num_pixels);
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)colorspace;
|
||||
(void)pixels;
|
||||
(void)num_pixels;
|
||||
(void)is_rgba;
|
||||
(void)compress_as_srgb;
|
||||
#endif
|
||||
}
|
||||
|
||||
void ColorSpaceManager::to_scene_linear(ColorSpaceProcessor *processor_,
|
||||
float *pixel,
|
||||
const int channels)
|
||||
{
|
||||
#ifdef WITH_OCIO
|
||||
const OCIO::Processor *processor = (const OCIO::Processor *)processor_;
|
||||
|
||||
if (processor) {
|
||||
const OCIO::ConstCPUProcessorRcPtr device_processor = processor->getDefaultCPUProcessor();
|
||||
if (channels == 1) {
|
||||
float3 rgb = make_float3(pixel[0], pixel[0], pixel[0]);
|
||||
device_processor->applyRGB(&rgb.x);
|
||||
pixel[0] = average(rgb);
|
||||
}
|
||||
if (channels == 3) {
|
||||
device_processor->applyRGB(pixel);
|
||||
}
|
||||
else if (channels == 4) {
|
||||
if (pixel[3] == 1.0f || pixel[3] == 0.0f) {
|
||||
/* Fast path for RGBA. */
|
||||
device_processor->applyRGB(pixel);
|
||||
}
|
||||
else {
|
||||
/* Un-associate and associate alpha since color management should not
|
||||
* be affected by transparency. */
|
||||
const float alpha = pixel[3];
|
||||
const float inv_alpha = 1.0f / alpha;
|
||||
|
||||
pixel[0] *= inv_alpha;
|
||||
pixel[1] *= inv_alpha;
|
||||
pixel[2] *= inv_alpha;
|
||||
|
||||
device_processor->applyRGB(pixel);
|
||||
|
||||
pixel[0] *= alpha;
|
||||
pixel[1] *= alpha;
|
||||
pixel[2] *= alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)processor_;
|
||||
(void)pixel;
|
||||
(void)channels;
|
||||
#endif
|
||||
}
|
||||
|
||||
void ColorSpaceManager::free_memory()
|
||||
{
|
||||
#ifdef WITH_OCIO
|
||||
map_free_memory(cached_colorspaces);
|
||||
map_free_memory(cached_processors);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ColorSpaceManager::init_fallback_config()
|
||||
{
|
||||
#ifdef WITH_OCIO
|
||||
OCIO::SetCurrentConfig(OCIO::Config::CreateRaw());
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Template instantiations so we don't have to inline functions. */
|
||||
template void ColorSpaceManager::to_scene_linear(ustring, uchar *, size_t, bool, bool, bool);
|
||||
template void ColorSpaceManager::to_scene_linear(ustring, ushort *, size_t, bool, bool, bool);
|
||||
template void ColorSpaceManager::to_scene_linear(ustring, half *, size_t, bool, bool, bool);
|
||||
template void ColorSpaceManager::to_scene_linear(ustring, float *, size_t, bool, bool, bool);
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
|
@ -2,14 +2,15 @@
|
|||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include "scene/image.h"
|
||||
#include "device/device.h"
|
||||
#include "scene/colorspace.h"
|
||||
|
||||
#include "scene/image.h"
|
||||
#include "scene/image_oiio.h"
|
||||
#include "scene/image_vdb.h"
|
||||
#include "scene/scene.h"
|
||||
#include "scene/stats.h"
|
||||
|
||||
#include "util/colorspace.h"
|
||||
#include "util/image.h"
|
||||
#include "util/image_impl.h"
|
||||
#include "util/log.h"
|
||||
|
|
@ -232,7 +233,7 @@ ImageMetaData::ImageMetaData()
|
|||
height(0),
|
||||
byte_size(0),
|
||||
type(IMAGE_DATA_NUM_TYPES),
|
||||
colorspace(u_colorspace_raw),
|
||||
colorspace(u_colorspace_scene_linear),
|
||||
colorspace_file_format(""),
|
||||
use_transform_3d(false),
|
||||
compress_as_srgb(false)
|
||||
|
|
@ -259,10 +260,10 @@ void ImageMetaData::detect_colorspace()
|
|||
colorspace = ColorSpaceManager::detect_known_colorspace(
|
||||
colorspace, colorspace_file_hint.c_str(), colorspace_file_format, is_float());
|
||||
|
||||
if (colorspace == u_colorspace_raw) {
|
||||
if (colorspace == u_colorspace_scene_linear || colorspace == u_colorspace_data) {
|
||||
/* Nothing to do. */
|
||||
}
|
||||
else if (colorspace == u_colorspace_srgb) {
|
||||
else if (colorspace == u_colorspace_scene_linear_srgb) {
|
||||
/* Keep sRGB colorspace stored as sRGB, to save memory and/or loading time
|
||||
* for the common case of 8bit sRGB images like PNG. */
|
||||
compress_as_srgb = true;
|
||||
|
|
@ -620,15 +621,18 @@ bool ImageManager::file_load_image(Image *img, const int texture_limit)
|
|||
}
|
||||
}
|
||||
|
||||
if (img->metadata.colorspace != u_colorspace_raw &&
|
||||
img->metadata.colorspace != u_colorspace_srgb)
|
||||
if (img->metadata.colorspace != u_colorspace_scene_linear &&
|
||||
img->metadata.colorspace != u_colorspace_scene_linear_srgb &&
|
||||
img->metadata.colorspace != u_colorspace_data)
|
||||
{
|
||||
/* Convert to scene linear. */
|
||||
const bool ignore_alpha = img->params.alpha_type == IMAGE_ALPHA_IGNORE ||
|
||||
img->params.alpha_type == IMAGE_ALPHA_CHANNEL_PACKED;
|
||||
ColorSpaceManager::to_scene_linear(img->metadata.colorspace,
|
||||
pixels,
|
||||
num_pixels,
|
||||
width,
|
||||
height,
|
||||
width * (is_rgba ? 4 : 1),
|
||||
is_rgba,
|
||||
img->metadata.compress_as_srgb,
|
||||
ignore_alpha);
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@
|
|||
|
||||
#include "device/memory.h"
|
||||
|
||||
#include "scene/colorspace.h"
|
||||
|
||||
#include "util/colorspace.h"
|
||||
#include "util/string.h"
|
||||
#include "util/thread.h"
|
||||
#include "util/transform.h"
|
||||
|
|
@ -38,7 +37,7 @@ class ImageParams {
|
|||
ustring colorspace;
|
||||
float frame = 0.0f;
|
||||
|
||||
ImageParams() : colorspace(u_colorspace_raw) {}
|
||||
ImageParams() : colorspace(u_colorspace_scene_linear) {}
|
||||
|
||||
bool operator==(const ImageParams &other) const
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
#include "scene/background.h"
|
||||
#include "scene/camera.h"
|
||||
#include "scene/colorspace.h"
|
||||
#include "scene/light.h"
|
||||
#include "scene/osl.h"
|
||||
#include "scene/scene.h"
|
||||
|
|
@ -124,7 +123,7 @@ bool OSLManager::need_update() const
|
|||
void OSLManager::device_update_pre(Device *device, Scene *scene)
|
||||
{
|
||||
if (scene->shader_manager->use_osl() || !scene->camera->script_name.empty()) {
|
||||
shading_system_init(scene->shader_manager->get_scene_linear_space());
|
||||
shading_system_init(scene->shader_manager->get_scene_linear_interop_id());
|
||||
}
|
||||
|
||||
if (!need_update()) {
|
||||
|
|
@ -343,7 +342,7 @@ void OSLManager::texture_system_free()
|
|||
}
|
||||
}
|
||||
|
||||
void OSLManager::shading_system_init(ShaderManager::SceneLinearSpace colorspace)
|
||||
void OSLManager::shading_system_init(const string &colorspace_interop_id)
|
||||
{
|
||||
/* No need to do anything if we already have shading systems. */
|
||||
if (!ss_map.empty()) {
|
||||
|
|
@ -353,7 +352,7 @@ void OSLManager::shading_system_init(ShaderManager::SceneLinearSpace colorspace)
|
|||
/* create shading system, shared between different renders to reduce memory usage */
|
||||
const thread_scoped_lock lock(ss_shared_mutex);
|
||||
|
||||
foreach_osl_device(device_, [this, colorspace](Device *sub_device, OSLGlobals *) {
|
||||
foreach_osl_device(device_, [this, colorspace_interop_id](Device *sub_device, OSLGlobals *) {
|
||||
const DeviceType device_type = sub_device->info.type;
|
||||
|
||||
if (!ss_shared[device_type]) {
|
||||
|
|
@ -385,18 +384,14 @@ void OSLManager::shading_system_init(ShaderManager::SceneLinearSpace colorspace)
|
|||
ss->attribute("greedyjit", 1);
|
||||
|
||||
/* OSL doesn't accept an arbitrary space, so support a few specific spaces. */
|
||||
switch (colorspace) {
|
||||
case ShaderManager::SceneLinearSpace::Rec709:
|
||||
ss->attribute("colorspace", OSL::Strings::Rec709);
|
||||
break;
|
||||
case ShaderManager::SceneLinearSpace::Rec2020:
|
||||
ss->attribute("colorspace", OSL::Strings::HDTV);
|
||||
break;
|
||||
case ShaderManager::SceneLinearSpace::ACEScg:
|
||||
ss->attribute("colorspace", OSL::Strings::ACEScg);
|
||||
break;
|
||||
case ShaderManager::SceneLinearSpace::Unknown:
|
||||
break;
|
||||
if (colorspace_interop_id == "lin_rec709_scene") {
|
||||
ss->attribute("colorspace", OSL::Strings::Rec709);
|
||||
}
|
||||
else if (colorspace_interop_id == "lin_rec2020_scene") {
|
||||
ss->attribute("colorspace", OSL::Strings::HDTV);
|
||||
}
|
||||
else if (colorspace_interop_id == "lin_ap1_scene") {
|
||||
ss->attribute("colorspace", OSL::Strings::ACEScg);
|
||||
}
|
||||
|
||||
const char *groupdata_alloc_str = getenv("CYCLES_OSL_GROUPDATA_ALLOC");
|
||||
|
|
@ -758,7 +753,7 @@ OSLNode *OSLShaderManager::osl_node(ShaderGraph *graph,
|
|||
}
|
||||
|
||||
/* Ensure shading system exists before we try to load a shader. */
|
||||
scene->osl_manager->shading_system_init(scene->shader_manager->get_scene_linear_space());
|
||||
scene->osl_manager->shading_system_init(scene->shader_manager->get_scene_linear_interop_id());
|
||||
|
||||
/* Load shader code. */
|
||||
const char *hash;
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ class OSLManager {
|
|||
const char *shader_load_filepath(string filepath);
|
||||
OSLShaderInfo *shader_loaded_info(const string &hash);
|
||||
|
||||
void shading_system_init(ShaderManager::SceneLinearSpace colorspace);
|
||||
void shading_system_init(const string &colorspace_interop_id);
|
||||
|
||||
OSL::ShadingSystem *get_shading_system(Device *sub_device);
|
||||
OSL::TextureSystem *get_texture_system();
|
||||
|
|
|
|||
|
|
@ -20,15 +20,11 @@
|
|||
#include "scene/tables.h"
|
||||
#include "scene/volume.h"
|
||||
|
||||
#include "util/colorspace.h"
|
||||
#include "util/log.h"
|
||||
#include "util/murmurhash.h"
|
||||
#include "util/transform.h"
|
||||
|
||||
#ifdef WITH_OCIO
|
||||
# include <OpenColorIO/OpenColorIO.h>
|
||||
namespace OCIO = OCIO_NAMESPACE;
|
||||
#endif
|
||||
|
||||
#include "scene/shader.tables"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
|
@ -734,7 +730,7 @@ void ShaderManager::device_update_common(Device * /*device*/,
|
|||
kfilm->rec709_to_r = make_float4(rec709_to_r);
|
||||
kfilm->rec709_to_g = make_float4(rec709_to_g);
|
||||
kfilm->rec709_to_b = make_float4(rec709_to_b);
|
||||
kfilm->is_rec709 = scene_linear_space == SceneLinearSpace::Rec709;
|
||||
kfilm->is_rec709 = scene_linear_interop_id == "lin_rec709_scene";
|
||||
}
|
||||
|
||||
void ShaderManager::device_free_common(Device * /*device*/, DeviceScene *dscene, Scene *scene)
|
||||
|
|
@ -919,37 +915,6 @@ bool ShaderManager::need_update() const
|
|||
return update_flags != UPDATE_NONE;
|
||||
}
|
||||
|
||||
#ifdef WITH_OCIO
|
||||
static bool to_scene_linear_transform(OCIO::ConstConfigRcPtr &config,
|
||||
const char *colorspace,
|
||||
Transform &to_scene_linear)
|
||||
{
|
||||
OCIO::ConstProcessorRcPtr processor;
|
||||
try {
|
||||
processor = config->getProcessor("scene_linear", colorspace);
|
||||
}
|
||||
catch (OCIO::Exception &) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!processor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const OCIO::ConstCPUProcessorRcPtr device_processor = processor->getDefaultCPUProcessor();
|
||||
if (!device_processor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
to_scene_linear = transform_identity();
|
||||
device_processor->applyRGB(&to_scene_linear.x.x);
|
||||
device_processor->applyRGB(&to_scene_linear.y.x);
|
||||
device_processor->applyRGB(&to_scene_linear.z.x);
|
||||
to_scene_linear = transform_transposed_inverse(to_scene_linear);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void ShaderManager::compute_thin_film_table(const Transform &xyz_to_rgb)
|
||||
{
|
||||
/* Our implementation of Thin Film Fresnel is based on
|
||||
|
|
@ -1015,84 +980,21 @@ void ShaderManager::compute_thin_film_table(const Transform &xyz_to_rgb)
|
|||
|
||||
void ShaderManager::init_xyz_transforms()
|
||||
{
|
||||
/* Default to ITU-BT.709 in case no appropriate transform found.
|
||||
* Note XYZ here is defined as having a D65 white point. */
|
||||
const Transform xyz_to_rec709 = make_transform(3.2404542f,
|
||||
-1.5371385f,
|
||||
-0.4985314f,
|
||||
0.0f,
|
||||
-0.9692660f,
|
||||
1.8760108f,
|
||||
0.0415560f,
|
||||
0.0f,
|
||||
0.0556434f,
|
||||
-0.2040259f,
|
||||
1.0572252f,
|
||||
0.0f);
|
||||
const Transform xyz_to_rgb = ColorSpaceManager::get_xyz_to_scene_linear_rgb();
|
||||
|
||||
xyz_to_r = make_float3(xyz_to_rec709.x);
|
||||
xyz_to_g = make_float3(xyz_to_rec709.y);
|
||||
xyz_to_b = make_float3(xyz_to_rec709.z);
|
||||
rgb_to_y = make_float3(0.2126729f, 0.7151522f, 0.0721750f);
|
||||
white_xyz = make_float3(0.95047f, 1.0f, 1.08883f);
|
||||
scene_linear_interop_id = ColorSpaceManager::get_scene_linear_interop_id();
|
||||
|
||||
rec709_to_r = make_float3(1.0f, 0.0f, 0.0f);
|
||||
rec709_to_g = make_float3(0.0f, 1.0f, 0.0f);
|
||||
rec709_to_b = make_float3(0.0f, 0.0f, 1.0f);
|
||||
scene_linear_space = SceneLinearSpace::Rec709;
|
||||
|
||||
compute_thin_film_table(xyz_to_rec709);
|
||||
|
||||
#ifdef WITH_OCIO
|
||||
/* Get from OpenColorO config if it has the required roles. */
|
||||
OCIO::ConstConfigRcPtr config = nullptr;
|
||||
try {
|
||||
config = OCIO::GetCurrentConfig();
|
||||
}
|
||||
catch (OCIO::Exception &exception) {
|
||||
LOG_WARNING << "OCIO config error: " << exception.what();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(config && config->hasRole("scene_linear"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
Transform xyz_to_rgb;
|
||||
|
||||
if (config->hasRole("aces_interchange")) {
|
||||
/* Standard OpenColorIO role, defined as ACES AP0 (ACES2065-1). */
|
||||
Transform aces_to_rgb;
|
||||
if (!to_scene_linear_transform(config, "aces_interchange", aces_to_rgb)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* This is the OpenColorIO builtin transform:
|
||||
* UTILITY - ACES-AP0_to_CIE-XYZ-D65_BFD. */
|
||||
const Transform ACES_AP0_to_xyz_D65 = make_transform(0.938280f,
|
||||
-0.004451f,
|
||||
0.016628f,
|
||||
0.000000f,
|
||||
0.337369f,
|
||||
0.729522f,
|
||||
-0.066890f,
|
||||
0.000000f,
|
||||
0.001174f,
|
||||
-0.003711f,
|
||||
1.091595f,
|
||||
0.000000f);
|
||||
const Transform xyz_to_aces = transform_inverse(ACES_AP0_to_xyz_D65);
|
||||
xyz_to_rgb = aces_to_rgb * xyz_to_aces;
|
||||
}
|
||||
else if (config->hasRole("XYZ")) {
|
||||
/* Custom role used before the standard existed. */
|
||||
if (!to_scene_linear_transform(config, "XYZ", xyz_to_rgb)) {
|
||||
return;
|
||||
}
|
||||
if (scene_linear_interop_id == "lin_rec709_scene") {
|
||||
rec709_to_r = make_float3(1.0f, 0.0f, 0.0f);
|
||||
rec709_to_g = make_float3(0.0f, 1.0f, 0.0f);
|
||||
rec709_to_b = make_float3(0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
else {
|
||||
/* No reference role found to determine XYZ. */
|
||||
return;
|
||||
const Transform xyz_to_rec709 = ColorSpaceManager::get_xyz_to_rec709();
|
||||
const Transform rec709_to_rgb = xyz_to_rgb * transform_inverse(xyz_to_rec709);
|
||||
rec709_to_r = make_float3(rec709_to_rgb.x);
|
||||
rec709_to_g = make_float3(rec709_to_rgb.y);
|
||||
rec709_to_b = make_float3(rec709_to_rgb.z);
|
||||
}
|
||||
|
||||
xyz_to_r = make_float3(xyz_to_rgb.x);
|
||||
|
|
@ -1103,51 +1005,7 @@ void ShaderManager::init_xyz_transforms()
|
|||
rgb_to_y = make_float3(rgb_to_xyz.y);
|
||||
white_xyz = transform_direction(&rgb_to_xyz, one_float3());
|
||||
|
||||
const Transform rec709_to_rgb = xyz_to_rgb * transform_inverse(xyz_to_rec709);
|
||||
rec709_to_r = make_float3(rec709_to_rgb.x);
|
||||
rec709_to_g = make_float3(rec709_to_rgb.y);
|
||||
rec709_to_b = make_float3(rec709_to_rgb.z);
|
||||
|
||||
compute_thin_film_table(xyz_to_rgb);
|
||||
|
||||
const Transform xyz_to_rec2020 = make_transform(1.7166512f,
|
||||
-0.3556708f,
|
||||
-0.2533663f,
|
||||
0.0f,
|
||||
-0.6666844,
|
||||
1.6164812f,
|
||||
0.0157685f,
|
||||
0.0f,
|
||||
0.0176399f,
|
||||
-0.0427706f,
|
||||
0.9421031f,
|
||||
0.0f);
|
||||
const Transform acescg_to_xyz = make_transform(0.652238f,
|
||||
0.128237f,
|
||||
0.169983f,
|
||||
0.0f,
|
||||
0.267672f,
|
||||
0.674340f,
|
||||
0.057988f,
|
||||
0.0f,
|
||||
-0.005382f,
|
||||
0.001369f,
|
||||
1.093071f,
|
||||
0.0f);
|
||||
|
||||
if (transform_equal_threshold(xyz_to_rgb, xyz_to_rec709, 0.001f)) {
|
||||
scene_linear_space = SceneLinearSpace::Rec709;
|
||||
}
|
||||
else if (transform_equal_threshold(xyz_to_rgb, xyz_to_rec2020, 0.001f)) {
|
||||
scene_linear_space = SceneLinearSpace::Rec2020;
|
||||
}
|
||||
else if (transform_equal_threshold(rgb_to_xyz, acescg_to_xyz, 0.001f)) {
|
||||
scene_linear_space = SceneLinearSpace::ACEScg;
|
||||
}
|
||||
else {
|
||||
scene_linear_space = SceneLinearSpace::Unknown;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t ShaderManager::ensure_bsdf_table_impl(DeviceScene *dscene,
|
||||
|
|
|
|||
|
|
@ -224,11 +224,9 @@ class ShaderManager {
|
|||
|
||||
void init_xyz_transforms();
|
||||
|
||||
enum class SceneLinearSpace { Rec709, Rec2020, ACEScg, Unknown };
|
||||
|
||||
SceneLinearSpace get_scene_linear_space()
|
||||
const string &get_scene_linear_interop_id()
|
||||
{
|
||||
return scene_linear_space;
|
||||
return scene_linear_interop_id;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
|
@ -254,7 +252,7 @@ class ShaderManager {
|
|||
float3 rec709_to_r;
|
||||
float3 rec709_to_g;
|
||||
float3 rec709_to_b;
|
||||
SceneLinearSpace scene_linear_space;
|
||||
string scene_linear_interop_id;
|
||||
vector<float> thin_film_table;
|
||||
|
||||
template<std::size_t n>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
#include "scene/shader_nodes.h"
|
||||
#include "kernel/svm/types.h"
|
||||
#include "kernel/types.h"
|
||||
#include "scene/colorspace.h"
|
||||
#include "scene/constant_fold.h"
|
||||
#include "scene/film.h"
|
||||
#include "scene/image.h"
|
||||
|
|
@ -21,7 +20,7 @@
|
|||
#include "sky_nishita.h"
|
||||
|
||||
#include "util/color.h"
|
||||
|
||||
#include "util/colorspace.h"
|
||||
#include "util/log.h"
|
||||
#include "util/math_base.h"
|
||||
#include "util/string.h"
|
||||
|
|
@ -266,7 +265,7 @@ NODE_DEFINE(ImageTextureNode)
|
|||
|
||||
ImageTextureNode::ImageTextureNode() : ImageSlotTextureNode(get_node_type())
|
||||
{
|
||||
colorspace = u_colorspace_raw;
|
||||
colorspace = u_colorspace_scene_linear;
|
||||
animated = false;
|
||||
}
|
||||
|
||||
|
|
@ -466,7 +465,7 @@ void ImageTextureNode::compile(OSLCompiler &compiler)
|
|||
|
||||
if (handle.svm_slot() == -1) {
|
||||
compiler.parameter_texture(
|
||||
"filename", filename, compress_as_srgb ? u_colorspace_raw : known_colorspace);
|
||||
"filename", filename, compress_as_srgb ? u_colorspace_scene_linear : known_colorspace);
|
||||
}
|
||||
else {
|
||||
compiler.parameter_texture("filename", handle);
|
||||
|
|
@ -535,7 +534,7 @@ NODE_DEFINE(EnvironmentTextureNode)
|
|||
|
||||
EnvironmentTextureNode::EnvironmentTextureNode() : ImageSlotTextureNode(get_node_type())
|
||||
{
|
||||
colorspace = u_colorspace_raw;
|
||||
colorspace = u_colorspace_scene_linear;
|
||||
animated = false;
|
||||
}
|
||||
|
||||
|
|
@ -618,7 +617,7 @@ void EnvironmentTextureNode::compile(OSLCompiler &compiler)
|
|||
|
||||
if (handle.svm_slot() == -1) {
|
||||
compiler.parameter_texture(
|
||||
"filename", filename, compress_as_srgb ? u_colorspace_raw : known_colorspace);
|
||||
"filename", filename, compress_as_srgb ? u_colorspace_scene_linear : known_colorspace);
|
||||
}
|
||||
else {
|
||||
compiler.parameter_texture("filename", handle);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
#include "device/device.h"
|
||||
|
||||
#include "scene/colorspace.h"
|
||||
#include "scene/scene.h"
|
||||
#include "scene/shader_graph.h"
|
||||
#include "scene/shader_nodes.h"
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ set(INC_SYS
|
|||
|
||||
set(SRC
|
||||
aligned_malloc.cpp
|
||||
colorspace.cpp
|
||||
debug.cpp
|
||||
ies.cpp
|
||||
log.cpp
|
||||
|
|
@ -45,6 +46,7 @@ set(SRC_HEADERS
|
|||
atomic.h
|
||||
boundbox.h
|
||||
color.h
|
||||
colorspace.h
|
||||
concurrent_set.h
|
||||
concurrent_vector.h
|
||||
debug.h
|
||||
|
|
@ -150,4 +152,14 @@ if(WITH_OPENVDB)
|
|||
endif()
|
||||
endif()
|
||||
|
||||
if(WITH_OPENCOLORIO)
|
||||
add_definitions(-DWITH_OCIO)
|
||||
list(APPEND LIB
|
||||
bf::dependencies::optional::opencolorio
|
||||
)
|
||||
if(WIN32 AND NOT USD_OVERRIDE_OPENCOLORIO)
|
||||
add_definitions(-DOpenColorIO_SKIP_IMPORTS)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
cycles_add_library(cycles_util "${LIB}" ${SRC} ${SRC_HEADERS})
|
||||
|
|
|
|||
850
intern/cycles/util/colorspace.cpp
Normal file
850
intern/cycles/util/colorspace.cpp
Normal file
|
|
@ -0,0 +1,850 @@
|
|||
/* SPDX-FileCopyrightText: 2011-2026 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include "util/colorspace.h"
|
||||
#include "util/color.h"
|
||||
#include "util/image.h"
|
||||
#include "util/log.h"
|
||||
#include "util/map.h"
|
||||
#include "util/math.h"
|
||||
#include "util/string.h"
|
||||
#include "util/thread.h"
|
||||
#include "util/transform.h"
|
||||
#include "util/vector.h"
|
||||
|
||||
#ifdef WITH_OCIO
|
||||
# include <OpenColorIO/OpenColorIO.h>
|
||||
namespace OCIO = OCIO_NAMESPACE;
|
||||
#endif
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* Builtin colorspaces. */
|
||||
ustring u_colorspace_auto;
|
||||
ustring u_colorspace_data("data");
|
||||
ustring u_colorspace_scene_linear("scene_linear");
|
||||
ustring u_colorspace_scene_linear_srgb("scene_linear_srgb");
|
||||
ustring u_colorspace_srgb("__builtin_srgb");
|
||||
|
||||
/* Cached data. */
|
||||
#ifdef WITH_OCIO
|
||||
static thread_mutex cache_processors_mutex;
|
||||
static unordered_map<ustring, OCIO::ConstProcessorRcPtr> cache_processors;
|
||||
|
||||
static thread_mutex cache_colorspaces_mutex;
|
||||
static unordered_map<ustring, ustring> cached_colorspaces;
|
||||
|
||||
static thread_mutex cache_scene_linear_interop_id_mutex;
|
||||
static bool cache_scene_linear_interop_id_done = false;
|
||||
static const char *cache_scene_linear_interop_id = "";
|
||||
static const char *cache_scene_linear_srgb_interop_id = "";
|
||||
#endif
|
||||
|
||||
static thread_mutex cache_xyz_to_scene_linear_mutex;
|
||||
static string cache_xyz_to_scene_linear_hash;
|
||||
|
||||
ColorSpaceProcessor *ColorSpaceManager::get_processor(ustring colorspace)
|
||||
{
|
||||
#ifdef WITH_OCIO
|
||||
/* Only use this for OpenColorIO color spaces, not the builtin ones. */
|
||||
assert(colorspace != u_colorspace_scene_linear_srgb && colorspace != u_colorspace_auto);
|
||||
|
||||
if (colorspace == u_colorspace_data || colorspace == u_colorspace_scene_linear) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
OCIO::ConstConfigRcPtr config = nullptr;
|
||||
try {
|
||||
config = OCIO::GetCurrentConfig();
|
||||
}
|
||||
catch (const OCIO::Exception &exception) {
|
||||
LOG_WARNING << "OCIO config error: " << exception.what();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!config) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Cache processor until free_memory(), memory overhead is expected to be
|
||||
* small and the processor is likely to be reused. */
|
||||
const thread_scoped_lock cache_processors_lock(cache_processors_mutex);
|
||||
if (cache_processors.find(colorspace) == cache_processors.end()) {
|
||||
try {
|
||||
if (colorspace == u_colorspace_srgb) {
|
||||
/* Linear Rec.709 to sRGB is handled separately in to_scene_linear, here
|
||||
* we only need the matrix transform from scene_linear to Linear Rec.709. */
|
||||
if (strcmp(get_scene_linear_interop_id(), "lin_rec709_scene") == 0) {
|
||||
cache_processors[colorspace] = nullptr;
|
||||
}
|
||||
else {
|
||||
const Transform rgb_to_rec709 = transform_inverse(get_xyz_to_scene_linear_rgb()) *
|
||||
get_xyz_to_rec709();
|
||||
const double data[16] = {rgb_to_rec709.x.x,
|
||||
rgb_to_rec709.x.y,
|
||||
rgb_to_rec709.x.z,
|
||||
rgb_to_rec709.x.w,
|
||||
rgb_to_rec709.y.x,
|
||||
rgb_to_rec709.y.y,
|
||||
rgb_to_rec709.y.z,
|
||||
rgb_to_rec709.y.w,
|
||||
rgb_to_rec709.z.x,
|
||||
rgb_to_rec709.z.y,
|
||||
rgb_to_rec709.z.z,
|
||||
rgb_to_rec709.z.w,
|
||||
0.0f,
|
||||
0.0f,
|
||||
0.0f,
|
||||
1.0f};
|
||||
OCIO::MatrixTransformRcPtr tfm = OCIO::MatrixTransform::Create();
|
||||
tfm->setMatrix(data);
|
||||
cache_processors[colorspace] = config->getProcessor(tfm, OCIO::TRANSFORM_DIR_FORWARD);
|
||||
}
|
||||
}
|
||||
else {
|
||||
cache_processors[colorspace] = config->getProcessor(colorspace.c_str(), "scene_linear");
|
||||
}
|
||||
}
|
||||
catch (const OCIO::Exception &exception) {
|
||||
cache_processors[colorspace] = OCIO::ConstProcessorRcPtr();
|
||||
LOG_WARNING << "Colorspace " << colorspace.c_str()
|
||||
<< " can't be converted to scene_linear: " << exception.what();
|
||||
}
|
||||
}
|
||||
|
||||
const OCIO::Processor *processor = cache_processors[colorspace].get();
|
||||
return (ColorSpaceProcessor *)processor;
|
||||
#else
|
||||
/* No OpenColorIO. */
|
||||
(void)colorspace;
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool ColorSpaceManager::colorspace_is_data(ustring colorspace)
|
||||
{
|
||||
if (colorspace == u_colorspace_data) {
|
||||
return true;
|
||||
}
|
||||
if (colorspace == u_colorspace_auto || colorspace == u_colorspace_scene_linear ||
|
||||
colorspace == u_colorspace_scene_linear_srgb || colorspace == u_colorspace_srgb)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef WITH_OCIO
|
||||
OCIO::ConstConfigRcPtr config = nullptr;
|
||||
try {
|
||||
config = OCIO::GetCurrentConfig();
|
||||
}
|
||||
catch (const OCIO::Exception &exception) {
|
||||
LOG_WARNING << "OCIO config error: " << exception.what();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const OCIO::ConstColorSpaceRcPtr space = config->getColorSpace(colorspace.c_str());
|
||||
return space && space->isData();
|
||||
}
|
||||
catch (const OCIO::Exception &) {
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
const char *ColorSpaceManager::colorspace_interop_id(ustring colorspace)
|
||||
{
|
||||
if (colorspace == u_colorspace_auto) {
|
||||
return nullptr;
|
||||
}
|
||||
if (colorspace == u_colorspace_data) {
|
||||
return "data";
|
||||
}
|
||||
if (colorspace == u_colorspace_srgb) {
|
||||
return "srgb_rec709_scene";
|
||||
}
|
||||
if (colorspace == u_colorspace_scene_linear) {
|
||||
const char *interop_id = get_scene_linear_interop_id(false);
|
||||
if (!strcmp(interop_id, "unknown")) {
|
||||
return interop_id;
|
||||
}
|
||||
}
|
||||
else if (colorspace == u_colorspace_scene_linear_srgb) {
|
||||
const char *interop_id = get_scene_linear_interop_id(true);
|
||||
if (!strcmp(interop_id, "unknown")) {
|
||||
return interop_id;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WITH_OCIO
|
||||
OCIO::ConstConfigRcPtr config = nullptr;
|
||||
try {
|
||||
config = OCIO::GetCurrentConfig();
|
||||
if (!config) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
catch (const OCIO::Exception &) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
try {
|
||||
const OCIO::ConstColorSpaceRcPtr space = config->getColorSpace(colorspace.c_str());
|
||||
if (space) {
|
||||
if (space->isData()) {
|
||||
return "data";
|
||||
}
|
||||
/* From https://github.com/AcademySoftwareFoundation/ColorInterop. */
|
||||
/* TODO: Use native interop ID support in OpenColorIO 2.5. */
|
||||
const char *interop_ids[] = {"lin_rec709_scene",
|
||||
"lin_p3d65_scene",
|
||||
"lin_rec2020_scene",
|
||||
"lin_adobergb_scene",
|
||||
"lin_ap1_scene",
|
||||
"lin_ap0_scene",
|
||||
"lin_ciexyzd65_scene",
|
||||
"srgb_rec709_scene",
|
||||
"g22_rec709_scene",
|
||||
"g18_rec709_scene",
|
||||
"srgb_ap1_scene",
|
||||
"g22_ap1_scene",
|
||||
"srgb_p3d65_scene",
|
||||
"g22_adobergb_scene"};
|
||||
for (const char *interop_id : interop_ids) {
|
||||
if (space->hasAlias(interop_id)) {
|
||||
return interop_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const OCIO::Exception &) {
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ustring ColorSpaceManager::detect_known_colorspace(ustring colorspace,
|
||||
const char *file_colorspace,
|
||||
const char *file_format,
|
||||
bool is_float)
|
||||
{
|
||||
if (colorspace == u_colorspace_auto) {
|
||||
/* Auto detect sRGB or raw if none specified. */
|
||||
if (is_float) {
|
||||
const bool srgb = (strcmp(file_colorspace, "sRGB") == 0 ||
|
||||
strcmp(file_colorspace, "GammaCorrected") == 0 ||
|
||||
(file_colorspace[0] == '\0' &&
|
||||
(strcmp(file_format, "png") == 0 || strcmp(file_format, "jpeg") == 0 ||
|
||||
strcmp(file_format, "tiff") == 0 || strcmp(file_format, "dpx") == 0 ||
|
||||
strcmp(file_format, "jpeg2000") == 0)));
|
||||
return srgb ? u_colorspace_srgb : u_colorspace_scene_linear;
|
||||
}
|
||||
return u_colorspace_srgb;
|
||||
}
|
||||
|
||||
/* Builtin colorspaces. */
|
||||
if (colorspace == u_colorspace_srgb || colorspace == u_colorspace_scene_linear ||
|
||||
colorspace == u_colorspace_scene_linear_srgb || colorspace == u_colorspace_data)
|
||||
{
|
||||
return colorspace;
|
||||
}
|
||||
|
||||
if (colorspace_is_data(colorspace)) {
|
||||
return u_colorspace_data;
|
||||
}
|
||||
|
||||
/* Use OpenColorIO. */
|
||||
#ifdef WITH_OCIO
|
||||
{
|
||||
const thread_scoped_lock cache_lock(cache_colorspaces_mutex);
|
||||
/* Cached lookup. */
|
||||
if (cached_colorspaces.find(colorspace) != cached_colorspaces.end()) {
|
||||
return cached_colorspaces[colorspace];
|
||||
}
|
||||
}
|
||||
|
||||
/* Detect if it matches a simple builtin colorspace. */
|
||||
bool is_scene_linear;
|
||||
bool is_scene_linear_srgb;
|
||||
is_builtin_colorspace(colorspace, is_scene_linear, is_scene_linear_srgb);
|
||||
|
||||
const thread_scoped_lock cache_lock(cache_colorspaces_mutex);
|
||||
if (is_scene_linear) {
|
||||
LOG_INFO << "Colorspace " << colorspace.string() << " is no-op";
|
||||
cached_colorspaces[colorspace] = u_colorspace_scene_linear;
|
||||
return u_colorspace_scene_linear;
|
||||
}
|
||||
if (is_scene_linear_srgb) {
|
||||
LOG_INFO << "Colorspace " << colorspace.string() << " is scene linear sRGB";
|
||||
cached_colorspaces[colorspace] = u_colorspace_scene_linear_srgb;
|
||||
return u_colorspace_scene_linear_srgb;
|
||||
}
|
||||
|
||||
/* Verify if we can convert from the requested color space. */
|
||||
if (!get_processor(colorspace)) {
|
||||
OCIO::ConstConfigRcPtr config = nullptr;
|
||||
try {
|
||||
config = OCIO::GetCurrentConfig();
|
||||
}
|
||||
catch (const OCIO::Exception &exception) {
|
||||
LOG_WARNING << "OCIO config error: " << exception.what();
|
||||
return u_colorspace_scene_linear;
|
||||
}
|
||||
|
||||
if (!config || !config->getColorSpace(colorspace.c_str())) {
|
||||
LOG_WARNING << "Colorspace " << colorspace.c_str() << " not found, using raw instead";
|
||||
}
|
||||
else {
|
||||
LOG_WARNING << "Colorspace " << colorspace.c_str()
|
||||
<< " can't be converted to scene_linear, using raw instead";
|
||||
}
|
||||
cached_colorspaces[colorspace] = u_colorspace_scene_linear;
|
||||
return u_colorspace_scene_linear;
|
||||
}
|
||||
|
||||
/* Convert to/from colorspace with OpenColorIO. */
|
||||
LOG_INFO << "Colorspace " << colorspace.string() << " handled through OpenColorIO";
|
||||
cached_colorspaces[colorspace] = colorspace;
|
||||
return colorspace;
|
||||
#else
|
||||
LOG_WARNING << "Colorspace " << colorspace.c_str()
|
||||
<< " not available, built without OpenColorIO";
|
||||
return u_colorspace_scene_linear;
|
||||
#endif
|
||||
}
|
||||
|
||||
void ColorSpaceManager::is_builtin_colorspace(ustring colorspace,
|
||||
bool &is_scene_linear,
|
||||
bool &is_scene_linear_srgb)
|
||||
{
|
||||
#ifdef WITH_OCIO
|
||||
const OCIO::Processor *processor = (const OCIO::Processor *)get_processor(colorspace);
|
||||
if (!processor) {
|
||||
is_scene_linear = false;
|
||||
is_scene_linear_srgb = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const OCIO::ConstCPUProcessorRcPtr device_processor = processor->getDefaultCPUProcessor();
|
||||
is_scene_linear = true;
|
||||
is_scene_linear_srgb = true;
|
||||
for (int i = 0; i < 256; i++) {
|
||||
const float v = i / 255.0f;
|
||||
|
||||
float cR[3] = {v, 0, 0};
|
||||
float cG[3] = {0, v, 0};
|
||||
float cB[3] = {0, 0, v};
|
||||
float cW[3] = {v, v, v};
|
||||
device_processor->applyRGB(cR);
|
||||
device_processor->applyRGB(cG);
|
||||
device_processor->applyRGB(cB);
|
||||
device_processor->applyRGB(cW);
|
||||
|
||||
/* Make sure that there is no channel crosstalk. */
|
||||
if (fabsf(cR[1]) > 1e-5f || fabsf(cR[2]) > 1e-5f || fabsf(cG[0]) > 1e-5f ||
|
||||
fabsf(cG[2]) > 1e-5f || fabsf(cB[0]) > 1e-5f || fabsf(cB[1]) > 1e-5f)
|
||||
{
|
||||
is_scene_linear = false;
|
||||
is_scene_linear_srgb = false;
|
||||
break;
|
||||
}
|
||||
/* Make sure that the three primaries combine linearly. */
|
||||
if (!compare_floats(cR[0], cW[0], 1e-6f, 64) || !compare_floats(cG[1], cW[1], 1e-6f, 64) ||
|
||||
!compare_floats(cB[2], cW[2], 1e-6f, 64))
|
||||
{
|
||||
is_scene_linear = false;
|
||||
is_scene_linear_srgb = false;
|
||||
break;
|
||||
}
|
||||
/* Make sure that the three channels behave identically. */
|
||||
if (!compare_floats(cW[0], cW[1], 1e-6f, 64) || !compare_floats(cW[1], cW[2], 1e-6f, 64)) {
|
||||
is_scene_linear = false;
|
||||
is_scene_linear_srgb = false;
|
||||
break;
|
||||
}
|
||||
|
||||
const float out_v = average(make_float3(cW[0], cW[1], cW[2]));
|
||||
if (!compare_floats(v, out_v, 1e-6f, 64)) {
|
||||
is_scene_linear = false;
|
||||
}
|
||||
if (!compare_floats(color_srgb_to_linear(v), out_v, 1e-4f, 64)) {
|
||||
is_scene_linear_srgb = false;
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)colorspace;
|
||||
is_scene_linear = false;
|
||||
is_scene_linear_srgb = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef WITH_OCIO
|
||||
|
||||
template<typename T> inline float4 cast_to_float4(T *data)
|
||||
{
|
||||
return make_float4(util_image_cast_to_float(data[0]),
|
||||
util_image_cast_to_float(data[1]),
|
||||
util_image_cast_to_float(data[2]),
|
||||
util_image_cast_to_float(data[3]));
|
||||
}
|
||||
|
||||
template<typename T> inline void cast_from_float4(T *data, const float4 value)
|
||||
{
|
||||
data[0] = util_image_cast_from_float<T>(value.x);
|
||||
data[1] = util_image_cast_from_float<T>(value.y);
|
||||
data[2] = util_image_cast_from_float<T>(value.z);
|
||||
data[3] = util_image_cast_from_float<T>(value.w);
|
||||
}
|
||||
|
||||
/* Slower versions for other all data types, which needs to convert to float and back. */
|
||||
template<typename T, bool compress_as_srgb = false>
|
||||
inline void processor_apply_pixels_rgba(const OCIO::Processor *processor,
|
||||
T *pixels,
|
||||
const int64_t width,
|
||||
const int64_t height,
|
||||
const int64_t y_stride,
|
||||
const bool ignore_alpha)
|
||||
{
|
||||
/* TODO: implement faster version for when we know the conversion
|
||||
* is a simple matrix transform between linear spaces. In that case
|
||||
* un-premultiply is not needed. */
|
||||
const OCIO::ConstCPUProcessorRcPtr device_processor = (processor) ?
|
||||
processor->getDefaultCPUProcessor() :
|
||||
nullptr;
|
||||
|
||||
/* Process large images in chunks to keep temporary memory requirement down. */
|
||||
const int64_t chunk_rows = divide_up(std::min((int64_t)(16 * 1024 * 1024), width * height),
|
||||
width);
|
||||
vector<float4> float_pixels(chunk_rows * width);
|
||||
|
||||
for (int64_t row = 0; row < height; row += chunk_rows) {
|
||||
const int64_t num_rows = std::min(chunk_rows, height - row);
|
||||
|
||||
for (int64_t j = 0; j < num_rows; j++) {
|
||||
T *pixel = pixels + (row + j) * y_stride;
|
||||
float4 *float_pixel = float_pixels.data() + j * width;
|
||||
for (int64_t i = 0; i < width; i++, pixel += 4, float_pixel++) {
|
||||
float4 value = cast_to_float4(pixel);
|
||||
|
||||
if (!ignore_alpha && !(value.w <= 0.0f || value.w == 1.0f)) {
|
||||
const float inv_alpha = 1.0f / value.w;
|
||||
value.x *= inv_alpha;
|
||||
value.y *= inv_alpha;
|
||||
value.z *= inv_alpha;
|
||||
}
|
||||
|
||||
*float_pixel = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (processor) {
|
||||
const OCIO::PackedImageDesc desc((float *)float_pixels.data(), num_rows * width, 1, 4);
|
||||
device_processor->apply(desc);
|
||||
}
|
||||
|
||||
for (int64_t j = 0; j < num_rows; j++) {
|
||||
T *pixel = pixels + (row + j) * y_stride;
|
||||
float4 *float_pixel = float_pixels.data() + j * width;
|
||||
for (int64_t i = 0; i < width; i++, pixel += 4, float_pixel++) {
|
||||
float4 value = *float_pixel;
|
||||
|
||||
if (compress_as_srgb) {
|
||||
value = color_linear_to_srgb_v4(value);
|
||||
}
|
||||
|
||||
if (!ignore_alpha && !(value.w <= 0.0f || value.w == 1.0f)) {
|
||||
value.x *= value.w;
|
||||
value.y *= value.w;
|
||||
value.z *= value.w;
|
||||
}
|
||||
|
||||
cast_from_float4(pixel, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, bool compress_as_srgb = false>
|
||||
inline void processor_apply_pixels_grayscale(const OCIO::Processor *processor,
|
||||
T *pixels,
|
||||
const int64_t width,
|
||||
const int64_t height,
|
||||
const int64_t y_stride)
|
||||
{
|
||||
const OCIO::ConstCPUProcessorRcPtr device_processor = (processor) ?
|
||||
processor->getDefaultCPUProcessor() :
|
||||
nullptr;
|
||||
|
||||
/* Process large images in chunks to keep temporary memory requirement down. */
|
||||
const int64_t chunk_rows = divide_up(std::min((int64_t)(16 * 1024 * 1024), width * height),
|
||||
width);
|
||||
vector<float> float_pixels(chunk_rows * width * 3);
|
||||
|
||||
for (int64_t row = 0; row < height; row += chunk_rows) {
|
||||
const int64_t num_rows = std::min(chunk_rows, height - row);
|
||||
|
||||
/* Convert to 3 channels, since that's the minimum required by OpenColorIO. */
|
||||
for (int64_t j = 0; j < num_rows; j++) {
|
||||
T *pixel = pixels + (row + j) * y_stride;
|
||||
float *float_pixel = float_pixels.data() + j * width * 3;
|
||||
for (int64_t i = 0; i < width; i++, pixel++, float_pixel += 3) {
|
||||
const float f = util_image_cast_to_float<T>(*pixel);
|
||||
float_pixel[0] = f;
|
||||
float_pixel[1] = f;
|
||||
float_pixel[2] = f;
|
||||
}
|
||||
}
|
||||
|
||||
if (processor) {
|
||||
const OCIO::PackedImageDesc desc((float *)float_pixels.data(), num_rows * width, 1, 3);
|
||||
device_processor->apply(desc);
|
||||
}
|
||||
|
||||
for (int64_t j = 0; j < num_rows; j++) {
|
||||
T *pixel = pixels + (row + j) * y_stride;
|
||||
float *float_pixel = float_pixels.data() + j * width * 3;
|
||||
for (int64_t i = 0; i < width; i++, pixel++, float_pixel += 3) {
|
||||
float f = average(make_float3(float_pixel[0], float_pixel[1], float_pixel[2]));
|
||||
if (compress_as_srgb) {
|
||||
f = color_linear_to_srgb(f);
|
||||
}
|
||||
*pixel = util_image_cast_from_float<T>(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
template<typename T>
|
||||
void ColorSpaceManager::to_scene_linear(ustring colorspace,
|
||||
T *pixels,
|
||||
const int64_t width,
|
||||
const int64_t height,
|
||||
const int64_t y_stride,
|
||||
bool is_rgba,
|
||||
bool compress_as_srgb,
|
||||
bool ignore_alpha)
|
||||
{
|
||||
#ifdef WITH_OCIO
|
||||
const OCIO::Processor *processor = (const OCIO::Processor *)get_processor(colorspace);
|
||||
|
||||
/* The processor for sRGB does not include the Linear Rec.709 to sRGB transform, that
|
||||
* is handled by compress_as_srgb for better performance when scene_linear is Rec.709 */
|
||||
if (colorspace == u_colorspace_srgb) {
|
||||
assert(!compress_as_srgb);
|
||||
compress_as_srgb = true;
|
||||
}
|
||||
|
||||
if (is_rgba) {
|
||||
if (compress_as_srgb) {
|
||||
/* Compress output as sRGB. */
|
||||
processor_apply_pixels_rgba<T, true>(
|
||||
processor, pixels, width, height, y_stride, ignore_alpha);
|
||||
}
|
||||
else {
|
||||
/* Write output as scene linear directly. */
|
||||
processor_apply_pixels_rgba<T>(processor, pixels, width, height, y_stride, ignore_alpha);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (compress_as_srgb) {
|
||||
/* Compress output as sRGB. */
|
||||
processor_apply_pixels_grayscale<T, true>(processor, pixels, width, height, y_stride);
|
||||
}
|
||||
else {
|
||||
/* Write output as scene linear directly. */
|
||||
processor_apply_pixels_grayscale<T>(processor, pixels, width, height, y_stride);
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)colorspace;
|
||||
(void)pixels;
|
||||
(void)width;
|
||||
(void)height;
|
||||
(void)y_stride;
|
||||
(void)is_rgba;
|
||||
(void)compress_as_srgb;
|
||||
#endif
|
||||
}
|
||||
|
||||
void ColorSpaceManager::to_scene_linear(ColorSpaceProcessor *processor_,
|
||||
float *pixel,
|
||||
const int channels)
|
||||
{
|
||||
#ifdef WITH_OCIO
|
||||
const OCIO::Processor *processor = (const OCIO::Processor *)processor_;
|
||||
|
||||
if (processor) {
|
||||
const OCIO::ConstCPUProcessorRcPtr device_processor = processor->getDefaultCPUProcessor();
|
||||
if (channels == 1) {
|
||||
float3 rgb = make_float3(pixel[0], pixel[0], pixel[0]);
|
||||
device_processor->applyRGB(&rgb.x);
|
||||
pixel[0] = average(rgb);
|
||||
}
|
||||
if (channels == 3) {
|
||||
device_processor->applyRGB(pixel);
|
||||
}
|
||||
else if (channels == 4) {
|
||||
if (pixel[3] == 1.0f || pixel[3] == 0.0f) {
|
||||
/* Fast path for RGBA. */
|
||||
device_processor->applyRGB(pixel);
|
||||
}
|
||||
else {
|
||||
/* Un-associate and associate alpha since color management should not
|
||||
* be affected by transparency. */
|
||||
const float alpha = pixel[3];
|
||||
const float inv_alpha = 1.0f / alpha;
|
||||
|
||||
pixel[0] *= inv_alpha;
|
||||
pixel[1] *= inv_alpha;
|
||||
pixel[2] *= inv_alpha;
|
||||
|
||||
device_processor->applyRGB(pixel);
|
||||
|
||||
pixel[0] *= alpha;
|
||||
pixel[1] *= alpha;
|
||||
pixel[2] *= alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)processor_;
|
||||
(void)pixel;
|
||||
(void)channels;
|
||||
#endif
|
||||
}
|
||||
|
||||
void ColorSpaceManager::free_memory()
|
||||
{
|
||||
#ifdef WITH_OCIO
|
||||
map_free_memory(cached_colorspaces);
|
||||
map_free_memory(cache_processors);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ColorSpaceManager::init_fallback_config()
|
||||
{
|
||||
#ifdef WITH_OCIO
|
||||
OCIO::SetCurrentConfig(OCIO::Config::CreateRaw());
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef WITH_OCIO
|
||||
static bool to_scene_linear_transform(OCIO::ConstConfigRcPtr &config,
|
||||
const char *colorspace,
|
||||
Transform &to_scene_linear)
|
||||
{
|
||||
OCIO::ConstProcessorRcPtr processor;
|
||||
try {
|
||||
processor = config->getProcessor("scene_linear", colorspace);
|
||||
}
|
||||
catch (OCIO::Exception &) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!processor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const OCIO::ConstCPUProcessorRcPtr device_processor = processor->getDefaultCPUProcessor();
|
||||
if (!device_processor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
to_scene_linear = transform_identity();
|
||||
device_processor->applyRGB(&to_scene_linear.x.x);
|
||||
device_processor->applyRGB(&to_scene_linear.y.x);
|
||||
device_processor->applyRGB(&to_scene_linear.z.x);
|
||||
to_scene_linear = transform_transposed_inverse(to_scene_linear);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
Transform ColorSpaceManager::get_xyz_to_rec709()
|
||||
{
|
||||
/* Default to ITU-BT.709 in case no appropriate transform found.
|
||||
* Note XYZ here is defined as having a D65 white point. */
|
||||
return make_transform(3.2404542f,
|
||||
-1.5371385f,
|
||||
-0.4985314f,
|
||||
0.0f,
|
||||
-0.9692660f,
|
||||
1.8760108f,
|
||||
0.0415560f,
|
||||
0.0f,
|
||||
0.0556434f,
|
||||
-0.2040259f,
|
||||
1.0572252f,
|
||||
0.0f);
|
||||
}
|
||||
|
||||
Transform ColorSpaceManager::get_xyz_to_rec2020()
|
||||
{
|
||||
return make_transform(1.7166512f,
|
||||
-0.3556708f,
|
||||
-0.2533663f,
|
||||
0.0f,
|
||||
-0.6666844,
|
||||
1.6164812f,
|
||||
0.0157685f,
|
||||
0.0f,
|
||||
0.0176399f,
|
||||
-0.0427706f,
|
||||
0.9421031f,
|
||||
0.0f);
|
||||
}
|
||||
|
||||
Transform ColorSpaceManager::get_xyz_to_acescg()
|
||||
{
|
||||
return transform_inverse(make_transform(0.652238f,
|
||||
0.128237f,
|
||||
0.169983f,
|
||||
0.0f,
|
||||
0.267672f,
|
||||
0.674340f,
|
||||
0.057988f,
|
||||
0.0f,
|
||||
-0.005382f,
|
||||
0.001369f,
|
||||
1.093071f,
|
||||
0.0f));
|
||||
}
|
||||
|
||||
Transform ColorSpaceManager::get_xyz_to_scene_linear_rgb()
|
||||
{
|
||||
Transform xyz_to_rgb = get_xyz_to_rec709();
|
||||
|
||||
#ifdef WITH_OCIO
|
||||
/* Get from OpenColorO config if it has the required roles. */
|
||||
OCIO::ConstConfigRcPtr config = nullptr;
|
||||
try {
|
||||
config = OCIO::GetCurrentConfig();
|
||||
}
|
||||
catch (OCIO::Exception &exception) {
|
||||
LOG_WARNING << "OCIO config error: " << exception.what();
|
||||
return xyz_to_rgb;
|
||||
}
|
||||
|
||||
if (!(config && config->hasRole("scene_linear"))) {
|
||||
return xyz_to_rgb;
|
||||
}
|
||||
|
||||
if (config->hasRole("aces_interchange")) {
|
||||
/* Standard OpenColorIO role, defined as ACES AP0 (ACES2065-1). */
|
||||
Transform aces_to_rgb;
|
||||
if (!to_scene_linear_transform(config, "aces_interchange", aces_to_rgb)) {
|
||||
return xyz_to_rgb;
|
||||
}
|
||||
|
||||
/* This is the OpenColorIO builtin transform:
|
||||
* UTILITY - ACES-AP0_to_CIE-XYZ-D65_BFD. */
|
||||
const Transform ACES_AP0_to_xyz_D65 = make_transform(0.938280f,
|
||||
-0.004451f,
|
||||
0.016628f,
|
||||
0.000000f,
|
||||
0.337369f,
|
||||
0.729522f,
|
||||
-0.066890f,
|
||||
0.000000f,
|
||||
0.001174f,
|
||||
-0.003711f,
|
||||
1.091595f,
|
||||
0.000000f);
|
||||
const Transform xyz_to_aces = transform_inverse(ACES_AP0_to_xyz_D65);
|
||||
xyz_to_rgb = aces_to_rgb * xyz_to_aces;
|
||||
return xyz_to_rgb;
|
||||
}
|
||||
|
||||
if (config->hasRole("XYZ")) {
|
||||
/* Custom role used before the standard existed. */
|
||||
if (to_scene_linear_transform(config, "XYZ", xyz_to_rgb)) {
|
||||
return xyz_to_rgb;
|
||||
}
|
||||
}
|
||||
|
||||
/* No reference role found to determine XYZ. */
|
||||
#endif
|
||||
|
||||
return xyz_to_rgb;
|
||||
}
|
||||
|
||||
const std::string &ColorSpaceManager::get_xyz_to_scene_linear_rgb_string()
|
||||
{
|
||||
/* NOTE: Be careful not to change existing hashes if at all possible, as this
|
||||
* will cause all texture files to be regenerated with significantly increased
|
||||
* disk usage. */
|
||||
const thread_scoped_lock cache_lock(cache_xyz_to_scene_linear_mutex);
|
||||
|
||||
if (cache_xyz_to_scene_linear_hash.empty()) {
|
||||
/* TODO: Verify this results in the same hash across platforms. */
|
||||
const Transform xyz_to_rgb = get_xyz_to_scene_linear_rgb();
|
||||
cache_xyz_to_scene_linear_hash = string_printf(
|
||||
"%.4g_%.4g_%.4g_%.4g_%.4g_%.4g_%.4g_%.4g_%.4g_%.4g_%.4g_%.4g",
|
||||
xyz_to_rgb.x.x,
|
||||
xyz_to_rgb.x.y,
|
||||
xyz_to_rgb.x.z,
|
||||
xyz_to_rgb.x.w,
|
||||
xyz_to_rgb.y.x,
|
||||
xyz_to_rgb.y.y,
|
||||
xyz_to_rgb.y.z,
|
||||
xyz_to_rgb.y.w,
|
||||
xyz_to_rgb.z.x,
|
||||
xyz_to_rgb.z.y,
|
||||
xyz_to_rgb.z.z,
|
||||
xyz_to_rgb.z.w);
|
||||
}
|
||||
|
||||
return cache_xyz_to_scene_linear_hash;
|
||||
}
|
||||
|
||||
const char *ColorSpaceManager::get_scene_linear_interop_id(const bool srgb_encoded)
|
||||
{
|
||||
const thread_scoped_lock cache_lock(cache_scene_linear_interop_id_mutex);
|
||||
|
||||
if (!cache_scene_linear_interop_id_done) {
|
||||
const Transform xyz_to_rgb = get_xyz_to_scene_linear_rgb();
|
||||
|
||||
if (transform_equal_threshold(xyz_to_rgb, get_xyz_to_rec709(), 0.0001f)) {
|
||||
cache_scene_linear_interop_id = "lin_rec709_scene";
|
||||
cache_scene_linear_srgb_interop_id = "srgb_rec709_scene";
|
||||
}
|
||||
else if (transform_equal_threshold(xyz_to_rgb, get_xyz_to_rec2020(), 0.0001f)) {
|
||||
cache_scene_linear_interop_id = "lin_rec2020_scene";
|
||||
cache_scene_linear_srgb_interop_id = "srgb_rec2020_scene";
|
||||
}
|
||||
else if (transform_equal_threshold(xyz_to_rgb, get_xyz_to_acescg(), 0.0001f)) {
|
||||
cache_scene_linear_interop_id = "lin_ap1_scene";
|
||||
cache_scene_linear_srgb_interop_id = "srgb_ap1_scene";
|
||||
}
|
||||
else {
|
||||
cache_scene_linear_interop_id = "unknown";
|
||||
cache_scene_linear_srgb_interop_id = "unknown";
|
||||
}
|
||||
|
||||
cache_scene_linear_interop_id_done = true;
|
||||
}
|
||||
|
||||
return (srgb_encoded) ? cache_scene_linear_srgb_interop_id : cache_scene_linear_interop_id;
|
||||
}
|
||||
|
||||
/* Template instantiations so we don't have to inline functions. */
|
||||
template void ColorSpaceManager::to_scene_linear(
|
||||
ustring, uchar *, int64_t, int64_t, int64_t, bool, bool, bool);
|
||||
template void ColorSpaceManager::to_scene_linear(
|
||||
ustring, ushort *, int64_t, int64_t, int64_t, bool, bool, bool);
|
||||
template void ColorSpaceManager::to_scene_linear(
|
||||
ustring, half *, int64_t, int64_t, int64_t, bool, bool, bool);
|
||||
template void ColorSpaceManager::to_scene_linear(
|
||||
ustring, float *, int64_t, int64_t, int64_t, bool, bool, bool);
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
|
@ -1,18 +1,27 @@
|
|||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
/* SPDX-FileCopyrightText: 2011-2026 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "util/param.h"
|
||||
#include "util/string.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* Automatically determine colorspace. */
|
||||
extern ustring u_colorspace_auto;
|
||||
extern ustring u_colorspace_raw;
|
||||
/* Non-color data. */
|
||||
extern ustring u_colorspace_data;
|
||||
/* Scene linear colorspace used for rendering. */
|
||||
extern ustring u_colorspace_scene_linear;
|
||||
/* Scene linear + sRGB transfer function . */
|
||||
extern ustring u_colorspace_scene_linear_srgb;
|
||||
/* sRGB. */
|
||||
extern ustring u_colorspace_srgb;
|
||||
|
||||
class ColorSpaceProcessor;
|
||||
struct Transform;
|
||||
|
||||
class ColorSpaceManager {
|
||||
public:
|
||||
|
|
@ -26,13 +35,17 @@ class ColorSpaceManager {
|
|||
|
||||
/* Test if colorspace is for non-color data. */
|
||||
static bool colorspace_is_data(ustring colorspace);
|
||||
/* Return color interop forum interop ID. */
|
||||
static const char *colorspace_interop_id(ustring colorspace);
|
||||
|
||||
/* Convert pixels in the specified colorspace to scene linear color for
|
||||
* rendering. Must be a colorspace returned from detect_known_colorspace. */
|
||||
template<typename T>
|
||||
static void to_scene_linear(ustring colorspace,
|
||||
T *pixels,
|
||||
const size_t num_pixels,
|
||||
const int64_t width,
|
||||
const int64_t height,
|
||||
const int64_t y_stride,
|
||||
bool is_rgba,
|
||||
bool compress_as_srgb,
|
||||
bool ignore_alpha);
|
||||
|
|
@ -52,8 +65,20 @@ class ColorSpaceManager {
|
|||
* valid without knowing the actual configuration used by the final application. */
|
||||
static void init_fallback_config();
|
||||
|
||||
/* Compute matrix to convert from XYZ to scene linear RGB, based on the config. */
|
||||
static Transform get_xyz_to_scene_linear_rgb();
|
||||
static Transform get_xyz_to_rec709();
|
||||
static Transform get_xyz_to_rec2020();
|
||||
static Transform get_xyz_to_acescg();
|
||||
/* Compute unique string for texture cache hashing and metadata. */
|
||||
static const string &get_xyz_to_scene_linear_rgb_string();
|
||||
/* Determine if scene linear is a common known space. */
|
||||
static const char *get_scene_linear_interop_id(const bool srgb_encoded = false);
|
||||
|
||||
private:
|
||||
static void is_builtin_colorspace(ustring colorspace, bool &is_scene_linear, bool &is_srgb);
|
||||
static void is_builtin_colorspace(ustring colorspace,
|
||||
bool &is_scene_linear,
|
||||
bool &is_scene_linear_srgb);
|
||||
};
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
Loading…
Add table
Add a link
Reference in a new issue