From fff8d35e3fb892ed3569334eba6540125014c8d5 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 26 Sep 2025 17:05:18 +0200 Subject: [PATCH] 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 --- scripts/startup/bl_ui/properties_render.py | 25 ++++++++- .../blender/blenkernel/intern/colortools.cc | 2 + source/blender/gpu/vulkan/vk_device.hh | 3 +- source/blender/imbuf/IMB_colormanagement.hh | 2 + .../blender/imbuf/intern/colormanagement.cc | 29 ++++++++++- .../blender/imbuf/opencolorio/OCIO_config.hh | 1 - source/blender/imbuf/opencolorio/OCIO_view.hh | 5 ++ .../intern/fallback/fallback_default_view.hh | 5 ++ .../intern/libocio/libocio_display.cc | 10 ++++ .../intern/libocio/libocio_view.hh | 8 +++ source/blender/makesdna/DNA_color_types.h | 8 +++ source/blender/makesrna/intern/rna_color.cc | 51 +++++++++++++++++++ 12 files changed, 144 insertions(+), 5 deletions(-) diff --git a/scripts/startup/bl_ui/properties_render.py b/scripts/startup/bl_ui/properties_render.py index b4430ee48fd..8b290570b2a 100644 --- a/scripts/startup/bl_ui/properties_render.py +++ b/scripts/startup/bl_ui/properties_render.py @@ -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. diff --git a/source/blender/blenkernel/intern/colortools.cc b/source/blender/blenkernel/intern/colortools.cc index 0eecb9a53fe..e2854fd1653 100644 --- a/source/blender/blenkernel/intern/colortools.cc +++ b/source/blender/blenkernel/intern/colortools.cc @@ -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, diff --git a/source/blender/gpu/vulkan/vk_device.hh b/source/blender/gpu/vulkan/vk_device.hh index f865ed9933f..64268f47cc0 100644 --- a/source/blender/gpu/vulkan/vk_device.hh +++ b/source/blender/gpu/vulkan/vk_device.hh @@ -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 diff --git a/source/blender/imbuf/IMB_colormanagement.hh b/source/blender/imbuf/IMB_colormanagement.hh index b0e6fefb660..d20fae94b61 100644 --- a/source/blender/imbuf/IMB_colormanagement.hh +++ b/source/blender/imbuf/IMB_colormanagement.hh @@ -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); /** \} */ diff --git a/source/blender/imbuf/intern/colormanagement.cc b/source/blender/imbuf/intern/colormanagement.cc index b3544ddce76..6aaf98b379a 100644 --- a/source/blender/imbuf/intern/colormanagement.cc +++ b/source/blender/imbuf/intern/colormanagement.cc @@ -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 get_display_buffer_processor( const ColorManagedDisplaySettings &display_settings, const char *look, @@ -807,7 +819,9 @@ static std::shared_ptr 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( diff --git a/source/blender/imbuf/opencolorio/OCIO_config.hh b/source/blender/imbuf/opencolorio/OCIO_config.hh index ac6c4462132..626bb83dd0f 100644 --- a/source/blender/imbuf/opencolorio/OCIO_config.hh +++ b/source/blender/imbuf/opencolorio/OCIO_config.hh @@ -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 { diff --git a/source/blender/imbuf/opencolorio/OCIO_view.hh b/source/blender/imbuf/opencolorio/OCIO_view.hh index e1673fccf29..c322f081a01 100644 --- a/source/blender/imbuf/opencolorio/OCIO_view.hh +++ b/source/blender/imbuf/opencolorio/OCIO_view.hh @@ -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. */ diff --git a/source/blender/imbuf/opencolorio/intern/fallback/fallback_default_view.hh b/source/blender/imbuf/opencolorio/intern/fallback/fallback_default_view.hh index 0baa32ed466..130c418cb7c 100644 --- a/source/blender/imbuf/opencolorio/intern/fallback/fallback_default_view.hh +++ b/source/blender/imbuf/opencolorio/intern/fallback/fallback_default_view.hh @@ -36,6 +36,11 @@ class FallbackDefaultView : public View { return false; } + bool support_emulation() const override + { + return false; + } + Gamut gamut() const override { return Gamut::Rec709; diff --git a/source/blender/imbuf/opencolorio/intern/libocio/libocio_display.cc b/source/blender/imbuf/opencolorio/intern/libocio/libocio_display.cc index 9b46d2826e2..aaa59f544f0 100644 --- a/source/blender/imbuf/opencolorio/intern/libocio/libocio_display.cc +++ b/source/blender/imbuf/opencolorio/intern/libocio/libocio_display.cc @@ -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); diff --git a/source/blender/imbuf/opencolorio/intern/libocio/libocio_view.hh b/source/blender/imbuf/opencolorio/intern/libocio/libocio_view.hh index c71ec05c947..7944fc667d9 100644 --- a/source/blender/imbuf/opencolorio/intern/libocio/libocio_view.hh +++ b/source/blender/imbuf/opencolorio/intern/libocio/libocio_view.hh @@ -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_; diff --git a/source/blender/makesdna/DNA_color_types.h b/source/blender/makesdna/DNA_color_types.h index 8bf3ea1e1a4..529954a6aa3 100644 --- a/source/blender/makesdna/DNA_color_types.h +++ b/source/blender/makesdna/DNA_color_types.h @@ -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), diff --git a/source/blender/makesrna/intern/rna_color.cc b/source/blender/makesrna/intern/rna_color.cc index ac1a194a8c2..e57046cdc27 100644 --- a/source/blender/makesrna/intern/rna_color.cc +++ b/source/blender/makesrna/intern/rna_color.cc @@ -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(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");