Color Management: Add option to control display emulation

In Render properties > Color Management > Display.

* Off: Directly output image as produced by OpenColorIO. This is not correct
  in general, but may be used when the system configuration and actual display
  device is known to match the chosen display.
* Automatic: Display images consistent with most other applications, to preview
  images and video for export. A best effort is made to emulate the chosen
  display on the actual display device.

The option is grayed out when the current OpenColorIO config and display/view
does not support emulation.

Ref #145022, #144911

Pull Request: https://projects.blender.org/blender/blender/pulls/146808
This commit is contained in:
Brecht Van Lommel
2025-09-26 17:05:18 +02:00
committed by Brecht Van Lommel
parent 31dfbc4bef
commit fff8d35e3f
12 changed files with 144 additions and 5 deletions

View File

@@ -117,6 +117,28 @@ class RENDER_PT_color_management_working_space(RenderButtonsPanel, Panel):
col.prop(scene.sequencer_colorspace_settings, "name", text="Sequencer")
class RENDER_PT_color_management_advanced(RenderButtonsPanel, Panel):
bl_label = "Advanced"
bl_parent_id = "RENDER_PT_color_management"
bl_options = {'DEFAULT_CLOSED'}
COMPAT_ENGINES = {
'BLENDER_RENDER',
'BLENDER_EEVEE',
'BLENDER_WORKBENCH',
}
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False # No animation.
scene = context.scene
col = layout.column()
col.active = scene.view_settings.support_emulation
col.prop(scene.display_settings, "emulation")
class RENDER_PT_color_management_curves(RenderButtonsPanel, Panel):
bl_label = "Curves"
bl_parent_id = "RENDER_PT_color_management"
@@ -1139,10 +1161,11 @@ classes = (
RENDER_PT_opengl_film,
RENDER_PT_hydra_debug,
RENDER_PT_color_management,
RENDER_PT_color_management_working_space,
RENDER_PT_color_management_curves,
RENDER_PT_color_management_white_balance_presets,
RENDER_PT_color_management_white_balance,
RENDER_PT_color_management_working_space,
RENDER_PT_color_management_advanced,
)
if __name__ == "__main__": # only for live edit.

View File

@@ -1881,12 +1881,14 @@ void BKE_color_managed_display_settings_init(ColorManagedDisplaySettings *settin
const char *display_name = IMB_colormanagement_display_get_default_name();
STRNCPY_UTF8(settings->display_device, display_name);
settings->emulation = COLORMANAGE_DISPLAY_EMULATION_AUTO;
}
void BKE_color_managed_display_settings_copy(ColorManagedDisplaySettings *new_settings,
const ColorManagedDisplaySettings *settings)
{
STRNCPY_UTF8(new_settings->display_device, settings->display_device);
new_settings->emulation = settings->emulation;
}
void BKE_color_managed_view_settings_init(ColorManagedViewSettings *view_settings,

View File

@@ -458,7 +458,8 @@ class VKDevice : public NonCopyable {
Shader *vk_backbuffer_blit_sh_get()
{
if (vk_backbuffer_blit_sh_ == nullptr) {
/* See display_as_extended_srgb in libocio_display_processor.cc for details on this choice. */
/* See #system_extended_srgb_transfer_function in libocio_display_processor.cc for
* details on this choice. */
#if defined(_WIN32) || defined(__APPLE__)
vk_backbuffer_blit_sh_ = GPU_shader_create_from_info_name("vk_backbuffer_blit");
#else

View File

@@ -363,6 +363,8 @@ bool IMB_colormanagement_display_is_hdr(const ColorManagedDisplaySettings *displ
const char *view_name);
bool IMB_colormanagement_display_is_wide_gamut(const ColorManagedDisplaySettings *display_settings,
const char *view_name);
bool IMB_colormanagement_display_support_emulation(
const ColorManagedDisplaySettings *display_settings, const char *view_name);
/** \} */

View File

@@ -780,6 +780,18 @@ void IMB_colormanagement_display_settings_from_ctx(
}
}
static bool get_display_emulation(const ColorManagedDisplaySettings &display_settings)
{
switch (display_settings.emulation) {
case COLORMANAGE_DISPLAY_EMULATION_OFF:
return false;
case COLORMANAGE_DISPLAY_EMULATION_AUTO:
return true;
}
return true;
}
static std::shared_ptr<const ocio::CPUProcessor> get_display_buffer_processor(
const ColorManagedDisplaySettings &display_settings,
const char *look,
@@ -807,7 +819,9 @@ static std::shared_ptr<const ocio::CPUProcessor> get_display_buffer_processor(
display_parameters.use_hdr_buffer = GPU_hdr_support();
display_parameters.use_hdr_display = IMB_colormanagement_display_is_hdr(&display_settings,
view_transform);
display_parameters.use_display_emulation = target == DISPLAY_SPACE_DRAW;
display_parameters.use_display_emulation = (target == DISPLAY_SPACE_DRAW) ?
get_display_emulation(display_settings) :
false;
return g_config->get_display_cpu_processor(display_parameters);
}
@@ -3056,6 +3070,17 @@ bool IMB_colormanagement_display_is_wide_gamut(const ColorManagedDisplaySettings
return (view) ? view->gamut() != ocio::Gamut::Rec709 : false;
}
bool IMB_colormanagement_display_support_emulation(
const ColorManagedDisplaySettings *display_settings, const char *view_name)
{
const ocio::Display *display = g_config->get_display_by_name(display_settings->display_device);
if (display == nullptr) {
return false;
}
const ocio::View *view = display->get_view_by_name(view_name);
return (view) ? view->support_emulation() : false;
}
/** \} */
/* -------------------------------------------------------------------- */
@@ -4432,7 +4457,7 @@ bool IMB_colormanagement_setup_glsl_draw_from_space(
display_parameters.use_hdr_buffer = GPU_hdr_support();
display_parameters.use_hdr_display = IMB_colormanagement_display_is_hdr(
display_settings, display_parameters.view.c_str());
display_parameters.use_display_emulation = true;
display_parameters.use_display_emulation = get_display_emulation(*display_settings);
/* Bind shader. Internally GPU shaders are created and cached on demand. */
global_gpu_state.gpu_shader_bound = g_config->get_gpu_shader_binder().display_bind(

View File

@@ -9,7 +9,6 @@
#include "BLI_math_matrix_types.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_string_ref.hh"
#include "DNA_windowmanager_types.h"
namespace blender::ocio {

View File

@@ -55,6 +55,11 @@ class View {
*/
virtual bool is_hdr() const = 0;
/**
* Does this view transform support display emulation?
*/
virtual bool support_emulation() const = 0;
/**
* Gamut of the display colorspace.
*/

View File

@@ -36,6 +36,11 @@ class FallbackDefaultView : public View {
return false;
}
bool support_emulation() const override
{
return false;
}
Gamut gamut() const override
{
return Gamut::Rec709;

View File

@@ -3,6 +3,7 @@
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "libocio_display.hh"
#include "OCIO_display.hh"
#if defined(WITH_OPENCOLORIO)
@@ -73,6 +74,9 @@ LibOCIODisplay::LibOCIODisplay(const int index, const LibOCIOConfig &config) : c
ocio_fallback_display_colorspace.reset();
}
bool support_emulation = config.get_color_space(OCIO_NAMESPACE::ROLE_INTERCHANGE_DISPLAY) !=
nullptr;
views_.reserve(num_views);
for (const int view_index : IndexRange(num_views)) {
const char *view_name = ocio_config->getView(name_.c_str(), view_index);
@@ -114,6 +118,11 @@ LibOCIODisplay::LibOCIODisplay(const int index, const LibOCIOConfig &config) : c
is_hdr_ |= view_is_hdr;
}
/* Detect if display emulation is supported. */
bool view_support_emulation = support_emulation && ocio_display_colorspace &&
ocio_display_colorspace->getReferenceSpaceType() ==
OCIO_NAMESPACE::REFERENCE_SPACE_DISPLAY;
/* Detect gamut and transfer function through interop ID. When unknown, things
* should still work correctly but may miss optimizations. */
Gamut gamut = Gamut::Unknown;
@@ -179,6 +188,7 @@ LibOCIODisplay::LibOCIODisplay(const int index, const LibOCIOConfig &config) : c
view_name,
view_description,
view_is_hdr,
view_support_emulation,
gamut,
transfer_function,
display_colorspace);

View File

@@ -20,6 +20,7 @@ class LibOCIOView : public View {
StringRefNull name_;
StringRefNull description_;
bool is_hdr_ = false;
bool support_emulation_ = false;
Gamut gamut_ = Gamut::Unknown;
TransferFunction transfer_function_ = TransferFunction::Unknown;
const LibOCIOColorSpace *display_colorspace_ = nullptr;
@@ -29,12 +30,14 @@ class LibOCIOView : public View {
const StringRefNull name,
const StringRefNull description,
const bool is_hdr,
const bool support_emulation,
const Gamut gamut,
const TransferFunction transfer_function,
const LibOCIOColorSpace *display_colorspace)
: name_(name),
description_(description),
is_hdr_(is_hdr),
support_emulation_(support_emulation),
gamut_(gamut),
transfer_function_(transfer_function),
display_colorspace_(display_colorspace)
@@ -57,6 +60,11 @@ class LibOCIOView : public View {
return is_hdr_;
}
bool support_emulation() const override
{
return support_emulation_;
}
Gamut gamut() const override
{
return gamut_;

View File

@@ -211,12 +211,20 @@ typedef struct ColorManagedViewSettings {
typedef struct ColorManagedDisplaySettings {
char display_device[64];
char emulation;
char _pad[7];
} ColorManagedDisplaySettings;
typedef struct ColorManagedColorspaceSettings {
char name[/*MAX_COLORSPACE_NAME*/ 64];
} ColorManagedColorspaceSettings;
/** #ColorManagedDisplaySettings.emulation */
enum {
COLORMANAGE_DISPLAY_EMULATION_AUTO = 0,
COLORMANAGE_DISPLAY_EMULATION_OFF = 1,
};
/** #ColorManagedViewSettings.flag */
enum {
COLORMANAGE_VIEW_USE_CURVES = (1 << 0),

View File

@@ -674,6 +674,20 @@ static bool rna_ColorManagedViewSettings_is_hdr_get(PointerRNA *ptr)
view_settings->view_transform);
}
static bool rna_ColorManagedViewSettings_support_emulation_get(PointerRNA *ptr)
{
ColorManagedViewSettings *view_settings = (ColorManagedViewSettings *)ptr->data;
if (GS(ptr->owner_id->name) != ID_SCE) {
return false;
}
const Scene *scene = reinterpret_cast<const Scene *>(ptr->owner_id);
if (&scene->view_settings != view_settings) {
return false;
}
return IMB_colormanagement_display_support_emulation(&scene->display_settings,
view_settings->view_transform);
}
static int rna_ViewSettings_only_view_look_editable(const PointerRNA *ptr, const char **r_info)
{
ColorManagedViewSettings *view_settings = (ColorManagedViewSettings *)ptr->data;
@@ -1353,6 +1367,24 @@ static void rna_def_colormanage(BlenderRNA *brna)
{0, nullptr, 0, nullptr, nullptr},
};
static const EnumPropertyItem emulation_items[] = {
{COLORMANAGE_DISPLAY_EMULATION_OFF,
"OFF",
0,
"Off",
"Directly output image as produced by OpenColorIO. This is not correct in general, but "
"may be used when the system configuration and actual display device is known to match "
"the chosen display"},
{COLORMANAGE_DISPLAY_EMULATION_AUTO,
"AUTO",
0,
"Automatic",
"Display images consistent with most other applications, to preview images and video for "
"export. A best effort is made to emulate the chosen display on the actual display "
"device."},
{0, nullptr, 0, nullptr, nullptr},
};
static const EnumPropertyItem look_items[] = {
{0, "NONE", 0, "None", "Do not modify image in an artistic manner"},
{0, nullptr, 0, nullptr, nullptr},
@@ -1389,6 +1421,15 @@ static void rna_def_colormanage(BlenderRNA *brna)
RNA_def_property_update(
prop, NC_WINDOW, "rna_ColorManagedDisplaySettings_display_device_update");
prop = RNA_def_property(srna, "emulation", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, emulation_items);
RNA_def_property_ui_text(
prop,
"Display Emulation",
"Control how images in the chosen display are mapped to the physical display");
RNA_def_property_update(
prop, NC_WINDOW, "rna_ColorManagedDisplaySettings_display_device_update");
/* ** View Settings ** */
srna = RNA_def_struct(brna, "ColorManagedViewSettings", nullptr);
RNA_def_struct_path_func(srna, "rna_ColorManagedViewSettings_path");
@@ -1495,6 +1536,16 @@ static void rna_def_colormanage(BlenderRNA *brna)
prop, "Is HDR", "The display and view transform supports high dynamic range colors");
RNA_def_property_boolean_funcs(prop, "rna_ColorManagedViewSettings_is_hdr_get", nullptr);
prop = RNA_def_property(srna, "support_emulation", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(
prop,
"Support Emulation",
"The display and view transform supports automatic emulation for another display device, "
"using the display color spaces mechanism in OpenColorIO v2 configurations");
RNA_def_property_boolean_funcs(
prop, "rna_ColorManagedViewSettings_support_emulation_get", nullptr);
/* ** Color-space ** */
srna = RNA_def_struct(brna, "ColorManagedInputColorspaceSettings", nullptr);
RNA_def_struct_path_func(srna, "rna_ColorManagedInputColorspaceSettings_path");