Blender V5.0
libocio_display.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2025 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "libocio_display.hh"
6#include "OCIO_display.hh"
7
8#if defined(WITH_OPENCOLORIO)
9
10# include "BLI_index_range.hh"
11
12# include "OCIO_config.hh"
13
14# include "CLG_log.h"
15
16# include "../opencolorio.hh"
17
18# include "error_handling.hh"
19# include "libocio_config.hh"
22
23static CLG_LogRef LOG = {"color_management"};
24
25namespace blender::ocio {
26
27static OCIO_NAMESPACE::ConstColorSpaceRcPtr get_display_view_colorspace(
28 const OCIO_NAMESPACE::ConstConfigRcPtr &ocio_config, const char *display, const char *view)
29{
30 const char *display_colorspace = ocio_config->getDisplayViewColorSpaceName(display, view);
31 if (display_colorspace == nullptr) {
32 return nullptr;
33 }
34
35 /* Shared view transforms can use this special display name to indicate
36 * the display colorspace name is the same as the display name. */
37 if (STREQ(display_colorspace, "<USE_DISPLAY_NAME>")) {
38 return ocio_config->getColorSpace(display);
39 }
40
41 return ocio_config->getColorSpace(display_colorspace);
42}
43
44LibOCIODisplay::LibOCIODisplay(const int index, const LibOCIOConfig &config) : config_(&config)
45{
46 const OCIO_NAMESPACE::ConstConfigRcPtr &ocio_config = config.get_ocio_config();
47
48 this->index = index;
49
50 name_ = ocio_config->getDisplay(index);
51
52 CLOG_TRACE(&LOG, "Add display: %s", name_.c_str());
53
54 /* Initialize views. */
55 const int num_views = ocio_config->getNumViews(name_.c_str());
56 if (num_views < 0) {
57 report_error("Invalid OpenColorIO configuration: negative number of views");
58 return;
59 }
60
61 /* Try to assign a display colorspace to every view even if missing. In particular for
62 * Raw we still want to set the colorspace. */
63 OCIO_NAMESPACE::ConstColorSpaceRcPtr ocio_fallback_display_colorspace;
64 for (const int view_index : IndexRange(num_views)) {
65 const char *view_name = ocio_config->getView(name_.c_str(), view_index);
66 ocio_fallback_display_colorspace = get_display_view_colorspace(
67 ocio_config, name_.c_str(), view_name);
68 if (ocio_fallback_display_colorspace &&
69 ocio_fallback_display_colorspace->getReferenceSpaceType() ==
70 OCIO_NAMESPACE::REFERENCE_SPACE_DISPLAY)
71 {
72 break;
73 }
74 ocio_fallback_display_colorspace.reset();
75 }
76
77 bool support_emulation = config.get_color_space(OCIO_NAMESPACE::ROLE_INTERCHANGE_DISPLAY) !=
78 nullptr;
79
80 views_.reserve(num_views);
81 for (const int view_index : IndexRange(num_views)) {
82 const char *view_name = ocio_config->getView(name_.c_str(), view_index);
83
84 OCIO_NAMESPACE::ConstColorSpaceRcPtr ocio_display_colorspace = get_display_view_colorspace(
85 ocio_config, name_.c_str(), view_name);
86 if (!ocio_display_colorspace) {
87 ocio_display_colorspace = ocio_fallback_display_colorspace;
88 }
89
90 /* There does not exist a description for displays, if there is an associated display
91 * colorspace it's likely to be a useful description. */
92 if (description_.is_empty() && ocio_display_colorspace &&
93 ocio_display_colorspace->getReferenceSpaceType() ==
94 OCIO_NAMESPACE::REFERENCE_SPACE_DISPLAY)
95 {
96 description_ = ocio_display_colorspace->getDescription();
97 }
98
99 const char *view_description = nullptr;
100 const char *view_transform_name = ocio_config->getDisplayViewTransformName(name_.c_str(),
101 view_name);
102 if (view_transform_name) {
103 const OCIO_NAMESPACE::ConstViewTransformRcPtr view_transform = ocio_config->getViewTransform(
104 view_transform_name);
105 if (view_transform) {
106 view_description = view_transform->getDescription();
107 }
108 }
109 if (view_description == nullptr) {
110 view_description = "";
111 }
112
113 /* Detect if view is HDR, through encoding of display colorspace. */
114 bool view_is_hdr = false;
115 if (ocio_display_colorspace) {
116 StringRefNull encoding = ocio_display_colorspace->getEncoding();
117 view_is_hdr = encoding == "hdr-video" || encoding == "edr-video";
118 is_hdr_ |= view_is_hdr;
119 }
120
121 /* Detect if display emulation is supported. */
122 bool view_support_emulation = support_emulation && ocio_display_colorspace &&
123 ocio_display_colorspace->getReferenceSpaceType() ==
124 OCIO_NAMESPACE::REFERENCE_SPACE_DISPLAY;
125
126 /* Detect gamut and transfer function through interop ID. When unknown, things
127 * should still work correctly but may miss optimizations. */
128 Gamut gamut = Gamut::Unknown;
129 TransferFunction transfer_function = TransferFunction::Unknown;
130
131 const LibOCIOColorSpace *display_colorspace =
132 (ocio_display_colorspace) ? static_cast<const LibOCIOColorSpace *>(config.get_color_space(
133 ocio_display_colorspace->getName())) :
134 nullptr;
135 StringRefNull display_interop_id = (display_colorspace) ? display_colorspace->interop_id() :
136 "";
137
138 if (!display_interop_id.is_empty()) {
139 if (display_interop_id.endswith("_rec709_display") ||
140 display_interop_id.endswith("_rec709_scene"))
141 {
142 gamut = Gamut::Rec709;
143 }
144 else if (display_interop_id.endswith("_p3d65_display") ||
145 display_interop_id.endswith("_p3d65_scene"))
146 {
147 gamut = Gamut::P3D65;
148 }
149 else if (display_interop_id.endswith("_rec2020_display") ||
150 display_interop_id.endswith("_rec2020_scene"))
151 {
152 gamut = Gamut::Rec2020;
153 }
154
155 if (display_interop_id.startswith("srgb_")) {
156 transfer_function = TransferFunction::sRGB;
157 }
158 else if (display_interop_id.startswith("srgbe_")) {
159 transfer_function = TransferFunction::ExtendedsRGB;
160 }
161 else if (display_interop_id.startswith("pq_")) {
162 transfer_function = TransferFunction::PQ;
163 }
164 else if (display_interop_id.startswith("hlg_")) {
165 transfer_function = TransferFunction::HLG;
166 }
167 else if (display_interop_id.startswith("g18_")) {
168 transfer_function = TransferFunction::Gamma18;
169 }
170 else if (display_interop_id.startswith("g22_")) {
171 transfer_function = TransferFunction::Gamma22;
172 }
173 else if (display_interop_id.startswith("g24_")) {
174 transfer_function = TransferFunction::Gamma24;
175 }
176 else if (display_interop_id.startswith("g26_")) {
177 transfer_function = TransferFunction::Gamma26;
178 }
179 }
180
182 " Add view: %s (colorspace: %s, %s)",
183 view_name,
184 display_colorspace ? display_colorspace->name().c_str() : "<none>",
185 view_is_hdr ? "HDR" : "SDR");
186
187 views_.append_as(view_index,
188 view_name,
189 view_description,
190 view_is_hdr,
191 view_support_emulation,
192 gamut,
193 transfer_function,
194 display_colorspace);
195 }
196
197 /* Detect untonemppaed view transform. */
198 if (untonemapped_view_ == nullptr) {
199 /* Use Blender config and ACES config naming conventions. */
200 for (const LibOCIOView &view : views_) {
201 if (view.name() == "Un-tone-mapped" || view.name() == "Standard") {
202 untonemapped_view_ = &view;
203 break;
204 }
205 }
206 if (untonemapped_view_ == nullptr) {
207 /* Use config wide default view transform between reference and display spaces.
208 * Note this is not always the same as the default view transform of the display. */
209 const char *default_view_transform = ocio_config->getDefaultViewTransformName();
210 for (const LibOCIOView &view : views_) {
211 if (view.name() == default_view_transform) {
212 untonemapped_view_ = &view;
213 break;
214 }
215 }
216 }
217 }
218
219 /* Hide redundant suffix from ACES config. */
220 if (name_.endswith(" - Display")) {
221 ui_name_ = StringRef(name_).drop_known_suffix(" - Display");
222 }
223}
224
225const View *LibOCIODisplay::get_untonemapped_view() const
226{
227 return untonemapped_view_;
228}
229
230const View *LibOCIODisplay::get_view_by_name(const StringRefNull name) const
231{
232 /* TODO(sergey): Is there faster way to lookup Blender-side view? */
233 for (const LibOCIOView &view : views_) {
234 if (view.name() == name) {
235 return &view;
236 }
237 }
238 return nullptr;
239}
240
241int LibOCIODisplay::get_num_views() const
242{
243 return views_.size();
244}
245
246const View *LibOCIODisplay::get_view_by_index(const int index) const
247{
248 if (index < 0 || index >= views_.size()) {
249 return nullptr;
250 }
251 return &views_[index];
252}
253
254std::unique_ptr<LibOCIOCPUProcessor> LibOCIODisplay::create_scene_linear_cpu_processor(
255 const bool use_display_emulation, const bool inverse) const
256{
257 const View *view = get_untonemapped_view();
258 if (view == nullptr) {
259 view = get_default_view();
260 }
261
262 DisplayParameters display_parameters;
263 display_parameters.from_colorspace = OCIO_NAMESPACE::ROLE_SCENE_LINEAR;
264 display_parameters.view = view->name();
265 display_parameters.display = name_;
266 display_parameters.inverse = inverse;
267 display_parameters.use_display_emulation = use_display_emulation;
268 OCIO_NAMESPACE::ConstProcessorRcPtr ocio_processor = create_ocio_display_processor(
269 *config_, display_parameters);
270 if (!ocio_processor) {
271 return nullptr;
272 }
273
274 OCIO_NAMESPACE::ConstCPUProcessorRcPtr ocio_cpu_processor =
275 ocio_processor->getDefaultCPUProcessor();
276 if (!ocio_cpu_processor) {
277 return nullptr;
278 }
279
280 return std::make_unique<LibOCIOCPUProcessor>(ocio_cpu_processor);
281}
282
283const CPUProcessor *LibOCIODisplay::get_to_scene_linear_cpu_processor(
284 const bool use_display_emulation) const
285{
286 const CPUProcessorCache &cache = (use_display_emulation) ?
287 to_scene_linear_emulation_cpu_processor_ :
288 to_scene_linear_cpu_processor_;
289 return cache.get([&] { return create_scene_linear_cpu_processor(use_display_emulation, true); });
290}
291
292const CPUProcessor *LibOCIODisplay::get_from_scene_linear_cpu_processor(
293 const bool use_display_emulation) const
294{
295 const CPUProcessorCache &cache = (use_display_emulation) ?
296 from_scene_linear_emulation_cpu_processor_ :
297 from_scene_linear_cpu_processor_;
298 return cache.get(
299 [&] { return create_scene_linear_cpu_processor(use_display_emulation, false); });
300}
301
302void LibOCIODisplay::clear_caches()
303{
304 to_scene_linear_cpu_processor_ = CPUProcessorCache();
305 to_scene_linear_emulation_cpu_processor_ = CPUProcessorCache();
306 from_scene_linear_cpu_processor_ = CPUProcessorCache();
307 from_scene_linear_emulation_cpu_processor_ = CPUProcessorCache();
308}
309
310} // namespace blender::ocio
311
312#endif
#define STREQ(a, b)
#define CLOG_TRACE(clg_ref,...)
Definition CLG_log.h:192
static AppView * view
static void View(GHOST_IWindow *window, bool stereo, int eye=0)
constexpr StringRef drop_known_suffix(StringRef suffix) const
MatBase< C, R > inverse(MatBase< C, R >) RET
#define LOG(level)
Definition log.h:97
const char * name