Color Management: Wide gamut display
* Change meaning of display device to be the device that we are trying to emulate, not the monitor configuration. * The display transform is now: * User specified view and display transform * Clamp to 0 to limit colors to gamut * Inverse untonemapped display transform * Convert to extended sRGB * When using the display space in the color management API, it now needs to be specified if it's for drawing (with emulation) or file output or color inspection (without). Like HDR, this only works on macOS and Linux + Wayland + Vulkan currently. Support for Windows/Vulkan is under development in #144717. Ref #144911 Pull Request: https://projects.blender.org/blender/blender/pulls/144565
This commit is contained in:
@@ -851,7 +851,8 @@ void UI_tooltip_color_field_add(uiTooltipData &data,
|
||||
IMB_colormanagement_srgb_to_scene_linear_v3(scene_linear_color, scene_linear_color);
|
||||
}
|
||||
else {
|
||||
IMB_colormanagement_scene_linear_to_display_v3(display_color, display);
|
||||
IMB_colormanagement_scene_linear_to_display_v3(
|
||||
display_color, display, DISPLAY_SPACE_COLOR_INSPECTION);
|
||||
IMB_colormanagement_scene_linear_to_srgb_v3(srgb_color, srgb_color);
|
||||
}
|
||||
|
||||
|
||||
@@ -242,14 +242,12 @@ void ED_image_draw_info(Scene *scene,
|
||||
rgba[3] = linearcol[3];
|
||||
}
|
||||
|
||||
if (use_default_view) {
|
||||
IMB_colormanagement_pixel_to_display_space_v4(
|
||||
rgba, rgba, nullptr, &scene->display_settings);
|
||||
}
|
||||
else {
|
||||
IMB_colormanagement_pixel_to_display_space_v4(
|
||||
rgba, rgba, &scene->view_settings, &scene->display_settings);
|
||||
}
|
||||
IMB_colormanagement_pixel_to_display_space_v4(rgba,
|
||||
rgba,
|
||||
(use_default_view) ? nullptr :
|
||||
&scene->view_settings,
|
||||
&scene->display_settings,
|
||||
DISPLAY_SPACE_COLOR_INSPECTION);
|
||||
|
||||
SNPRINTF_UTF8(str, " | Display R:%-.4f G:%-.4f B:%-.4f", rgba[0], rgba[1], rgba[2]);
|
||||
BLF_position(blf_mono_font, dx, dy, 0);
|
||||
@@ -284,14 +282,11 @@ void ED_image_draw_info(Scene *scene,
|
||||
}
|
||||
|
||||
if (color_manage) {
|
||||
if (use_default_view) {
|
||||
IMB_colormanagement_pixel_to_display_space_v4(
|
||||
finalcol, col, nullptr, &scene->display_settings);
|
||||
}
|
||||
else {
|
||||
IMB_colormanagement_pixel_to_display_space_v4(
|
||||
finalcol, col, &scene->view_settings, &scene->display_settings);
|
||||
}
|
||||
IMB_colormanagement_pixel_to_display_space_v4(finalcol,
|
||||
col,
|
||||
(use_default_view) ? nullptr :
|
||||
&scene->view_settings,
|
||||
&scene->display_settings);
|
||||
}
|
||||
else {
|
||||
copy_v4_v4(finalcol, col);
|
||||
|
||||
@@ -77,6 +77,7 @@ struct GPUViewport {
|
||||
/* Color management. */
|
||||
ColorManagedViewSettings view_settings;
|
||||
ColorManagedDisplaySettings display_settings;
|
||||
bool use_hdr;
|
||||
CurveMapping *orig_curve_mapping;
|
||||
float dither;
|
||||
/* TODO(@fclem): the UV-image display use the viewport but do not set any view transform for the
|
||||
@@ -287,6 +288,8 @@ void GPU_viewport_colorspace_set(GPUViewport *viewport,
|
||||
BKE_color_managed_display_settings_copy(&viewport->display_settings, display_settings);
|
||||
viewport->dither = dither;
|
||||
viewport->do_color_management = true;
|
||||
viewport->use_hdr = GPU_hdr_support() &&
|
||||
((viewport->view_settings.flag & COLORMANAGE_VIEW_USE_HDR) != 0);
|
||||
}
|
||||
|
||||
void GPU_viewport_force_hdr(GPUViewport *viewport)
|
||||
@@ -455,8 +458,7 @@ static void gpu_viewport_draw_colormanaged(GPUViewport *viewport,
|
||||
blender::gpu::Texture *color_overlay = viewport->color_overlay_tx[view];
|
||||
|
||||
bool use_ocio = false;
|
||||
bool use_hdr = GPU_hdr_support() &&
|
||||
((viewport->view_settings.flag & COLORMANAGE_VIEW_USE_HDR) != 0);
|
||||
bool use_hdr = viewport->use_hdr;
|
||||
|
||||
if (viewport->force_hdr_output) {
|
||||
use_hdr = true;
|
||||
|
||||
@@ -32,14 +32,10 @@ void main()
|
||||
float4 overlay_col = texture(overlays_texture, texCoord_interp.xy);
|
||||
|
||||
if (overlay) {
|
||||
if (!use_hdr) {
|
||||
/* If we're not using an extended color space, clamp the color 0..1. */
|
||||
fragColor = clamp(fragColor, 0.0f, 1.0f);
|
||||
}
|
||||
else {
|
||||
/* When using extended color-space, interpolate towards clamped color to improve display of
|
||||
if (use_hdr) {
|
||||
/* When using HDR, interpolate towards clamped color to improve display of
|
||||
* alpha-blended overlays. */
|
||||
fragColor = mix(max(fragColor, 0.0f), clamp(fragColor, 0.0f, 1.0f), overlay_col.a);
|
||||
fragColor = mix(fragColor, clamp(fragColor, 0.0f, 1.0f), overlay_col.a);
|
||||
}
|
||||
fragColor *= 1.0f - overlay_col.a;
|
||||
fragColor += overlay_col;
|
||||
|
||||
@@ -31,6 +31,16 @@ class Display;
|
||||
using ColorSpace = blender::ocio::ColorSpace;
|
||||
using ColorManagedDisplay = blender::ocio::Display;
|
||||
|
||||
enum ColorManagedDisplaySpace {
|
||||
/* Convert to display space for drawing. This will included emulation of the
|
||||
* chosen display for an extended sRGB buffer. */
|
||||
DISPLAY_SPACE_DRAW,
|
||||
/* Convert to display space for file output. */
|
||||
DISPLAY_SPACE_FILE_OUTPUT,
|
||||
/* Convert to display space for inspecting color values as text in the UI. */
|
||||
DISPLAY_SPACE_COLOR_INSPECTION,
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Generic Functions
|
||||
* \{ */
|
||||
@@ -238,25 +248,31 @@ BLI_INLINE void IMB_colormanagement_srgb_to_scene_linear_v3(float scene_linear[3
|
||||
* used by performance-critical areas such as color-related widgets where we want to reduce
|
||||
* amount of per-widget allocations.
|
||||
*/
|
||||
void IMB_colormanagement_scene_linear_to_display_v3(float pixel[3],
|
||||
const ColorManagedDisplay *display);
|
||||
void IMB_colormanagement_scene_linear_to_display_v3(
|
||||
float pixel[3],
|
||||
const ColorManagedDisplay *display,
|
||||
const ColorManagedDisplaySpace display_space = DISPLAY_SPACE_DRAW);
|
||||
/**
|
||||
* Same as #IMB_colormanagement_scene_linear_to_display_v3,
|
||||
* but converts color in opposite direction.
|
||||
*/
|
||||
void IMB_colormanagement_display_to_scene_linear_v3(float pixel[3],
|
||||
const ColorManagedDisplay *display);
|
||||
void IMB_colormanagement_display_to_scene_linear_v3(
|
||||
float pixel[3],
|
||||
const ColorManagedDisplay *display,
|
||||
const ColorManagedDisplaySpace display_space = DISPLAY_SPACE_DRAW);
|
||||
|
||||
void IMB_colormanagement_pixel_to_display_space_v4(
|
||||
float result[4],
|
||||
const float pixel[4],
|
||||
const ColorManagedViewSettings *view_settings,
|
||||
const ColorManagedDisplaySettings *display_settings);
|
||||
const ColorManagedDisplaySettings *display_settings,
|
||||
const ColorManagedDisplaySpace display_space = DISPLAY_SPACE_DRAW);
|
||||
|
||||
void IMB_colormanagement_imbuf_make_display_space(
|
||||
ImBuf *ibuf,
|
||||
const ColorManagedViewSettings *view_settings,
|
||||
const ColorManagedDisplaySettings *display_settings);
|
||||
const ColorManagedDisplaySettings *display_settings,
|
||||
const ColorManagedDisplaySpace display_space = DISPLAY_SPACE_DRAW);
|
||||
|
||||
/**
|
||||
* Prepare image buffer to be saved on disk, applying color management if needed
|
||||
@@ -428,12 +444,14 @@ void IMB_partial_display_buffer_update_delayed(
|
||||
|
||||
ColormanageProcessor *IMB_colormanagement_display_processor_new(
|
||||
const ColorManagedViewSettings *view_settings,
|
||||
const ColorManagedDisplaySettings *display_settings);
|
||||
const ColorManagedDisplaySettings *display_settings,
|
||||
const ColorManagedDisplaySpace display_space = DISPLAY_SPACE_DRAW);
|
||||
|
||||
ColormanageProcessor *IMB_colormanagement_display_processor_for_imbuf(
|
||||
const ImBuf *ibuf,
|
||||
const ColorManagedViewSettings *view_settings,
|
||||
const ColorManagedDisplaySettings *display_settings);
|
||||
const ColorManagedDisplaySettings *display_settings,
|
||||
const ColorManagedDisplaySpace display_space = DISPLAY_SPACE_DRAW);
|
||||
|
||||
ColormanageProcessor *IMB_colormanagement_colorspace_processor_new(const char *from_colorspace,
|
||||
const char *to_colorspace);
|
||||
|
||||
@@ -748,20 +748,21 @@ static const ColorSpace *get_untonemapped_display_colorspace(
|
||||
}
|
||||
|
||||
static std::shared_ptr<const ocio::CPUProcessor> get_display_buffer_processor(
|
||||
const ColorManagedDisplaySettings &display_settings,
|
||||
const char *look,
|
||||
const char *view_transform,
|
||||
const char *display,
|
||||
const float exposure,
|
||||
const float gamma,
|
||||
const float temperature,
|
||||
const float tint,
|
||||
const bool use_white_balance,
|
||||
const char *from_colorspace)
|
||||
const char *from_colorspace,
|
||||
const ColorManagedDisplaySpace target)
|
||||
{
|
||||
ocio::DisplayParameters display_parameters;
|
||||
display_parameters.from_colorspace = from_colorspace;
|
||||
display_parameters.view = view_transform;
|
||||
display_parameters.display = display;
|
||||
display_parameters.display = display_settings.display_device;
|
||||
display_parameters.look = (colormanage_use_look(look, view_transform)) ? look : "";
|
||||
display_parameters.scale = (exposure == 0.0f) ? 1.0f : powf(2.0f, exposure);
|
||||
display_parameters.exponent = (gamma == 1.0f) ? 1.0f : 1.0f / max_ff(FLT_EPSILON, gamma);
|
||||
@@ -769,6 +770,10 @@ static std::shared_ptr<const ocio::CPUProcessor> get_display_buffer_processor(
|
||||
display_parameters.tint = tint;
|
||||
display_parameters.use_white_balance = use_white_balance;
|
||||
display_parameters.inverse = false;
|
||||
display_parameters.use_hdr = GPU_hdr_support() && target == DISPLAY_SPACE_DRAW &&
|
||||
IMB_colormanagement_display_is_hdr(&display_settings,
|
||||
view_transform);
|
||||
display_parameters.use_display_emulation = target == DISPLAY_SPACE_DRAW;
|
||||
|
||||
return g_config->get_display_cpu_processor(display_parameters);
|
||||
}
|
||||
@@ -964,7 +969,7 @@ static void colormanage_check_colorspace_settings(
|
||||
|
||||
if (!colorspace) {
|
||||
CLOG_WARN(&LOG,
|
||||
"%s colorspace \"%s\" not found, will use default instead.\n",
|
||||
"%s colorspace \"%s\" not found, will use default instead.",
|
||||
what,
|
||||
colorspace_settings->name);
|
||||
|
||||
@@ -1632,7 +1637,8 @@ static bool is_colorspace_same_as_display(const ColorSpace *colorspace,
|
||||
ColormanageProcessor *IMB_colormanagement_display_processor_for_imbuf(
|
||||
const ImBuf *ibuf,
|
||||
const ColorManagedViewSettings *view_settings,
|
||||
const ColorManagedDisplaySettings *display_settings)
|
||||
const ColorManagedDisplaySettings *display_settings,
|
||||
const ColorManagedDisplaySpace display_space)
|
||||
{
|
||||
bool skip_transform = false;
|
||||
if (ibuf->float_buffer.data == nullptr && ibuf->byte_buffer.colorspace) {
|
||||
@@ -1647,7 +1653,7 @@ ColormanageProcessor *IMB_colormanagement_display_processor_for_imbuf(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return IMB_colormanagement_display_processor_new(view_settings, display_settings);
|
||||
return IMB_colormanagement_display_processor_new(view_settings, display_settings, display_space);
|
||||
}
|
||||
|
||||
static void colormanage_display_buffer_process_ex(
|
||||
@@ -1655,10 +1661,11 @@ static void colormanage_display_buffer_process_ex(
|
||||
float *display_buffer,
|
||||
uchar *display_buffer_byte,
|
||||
const ColorManagedViewSettings *view_settings,
|
||||
const ColorManagedDisplaySettings *display_settings)
|
||||
const ColorManagedDisplaySettings *display_settings,
|
||||
const ColorManagedDisplaySpace display_space)
|
||||
{
|
||||
ColormanageProcessor *cm_processor = IMB_colormanagement_display_processor_for_imbuf(
|
||||
ibuf, view_settings, display_settings);
|
||||
ibuf, view_settings, display_settings, display_space);
|
||||
display_buffer_apply_threaded(ibuf,
|
||||
ibuf->float_buffer.data,
|
||||
ibuf->byte_buffer.data,
|
||||
@@ -1674,10 +1681,11 @@ static void colormanage_display_buffer_process_ex(
|
||||
static void colormanage_display_buffer_process(ImBuf *ibuf,
|
||||
uchar *display_buffer,
|
||||
const ColorManagedViewSettings *view_settings,
|
||||
const ColorManagedDisplaySettings *display_settings)
|
||||
const ColorManagedDisplaySettings *display_settings,
|
||||
const ColorManagedDisplaySpace display_space)
|
||||
{
|
||||
colormanage_display_buffer_process_ex(
|
||||
ibuf, nullptr, display_buffer, view_settings, display_settings);
|
||||
ibuf, nullptr, display_buffer, view_settings, display_settings, display_space);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
@@ -2234,18 +2242,22 @@ void IMB_colormanagement_color_picking_to_scene_linear_v3(float scene_linear[3],
|
||||
}
|
||||
|
||||
void IMB_colormanagement_scene_linear_to_display_v3(float pixel[3],
|
||||
const ColorManagedDisplay *display)
|
||||
const ColorManagedDisplay *display,
|
||||
const ColorManagedDisplaySpace display_space)
|
||||
{
|
||||
const ocio::CPUProcessor *processor = display->get_from_scene_linear_cpu_processor();
|
||||
const ocio::CPUProcessor *processor = display->get_from_scene_linear_cpu_processor(
|
||||
display_space == DISPLAY_SPACE_DRAW);
|
||||
if (processor != nullptr) {
|
||||
processor->apply_rgb(pixel);
|
||||
}
|
||||
}
|
||||
|
||||
void IMB_colormanagement_display_to_scene_linear_v3(float pixel[3],
|
||||
const ColorManagedDisplay *display)
|
||||
const ColorManagedDisplay *display,
|
||||
const ColorManagedDisplaySpace display_space)
|
||||
{
|
||||
const ocio::CPUProcessor *processor = display->get_to_scene_linear_cpu_processor();
|
||||
const ocio::CPUProcessor *processor = display->get_to_scene_linear_cpu_processor(
|
||||
display_space == DISPLAY_SPACE_DRAW);
|
||||
if (processor != nullptr) {
|
||||
processor->apply_rgb(pixel);
|
||||
}
|
||||
@@ -2255,13 +2267,15 @@ void IMB_colormanagement_pixel_to_display_space_v4(
|
||||
float result[4],
|
||||
const float pixel[4],
|
||||
const ColorManagedViewSettings *view_settings,
|
||||
const ColorManagedDisplaySettings *display_settings)
|
||||
const ColorManagedDisplaySettings *display_settings,
|
||||
const ColorManagedDisplaySpace display_space)
|
||||
{
|
||||
ColormanageProcessor *cm_processor;
|
||||
|
||||
copy_v4_v4(result, pixel);
|
||||
|
||||
cm_processor = IMB_colormanagement_display_processor_new(view_settings, display_settings);
|
||||
cm_processor = IMB_colormanagement_display_processor_new(
|
||||
view_settings, display_settings, display_space);
|
||||
IMB_colormanagement_processor_apply_v4(cm_processor, result);
|
||||
IMB_colormanagement_processor_free(cm_processor);
|
||||
}
|
||||
@@ -2270,22 +2284,29 @@ static void colormanagement_imbuf_make_display_space(
|
||||
ImBuf *ibuf,
|
||||
const ColorManagedViewSettings *view_settings,
|
||||
const ColorManagedDisplaySettings *display_settings,
|
||||
const ColorManagedDisplaySpace display_space,
|
||||
bool make_byte)
|
||||
{
|
||||
if (!ibuf->byte_buffer.data && make_byte) {
|
||||
IMB_alloc_byte_pixels(ibuf);
|
||||
}
|
||||
|
||||
colormanage_display_buffer_process_ex(
|
||||
ibuf, ibuf->float_buffer.data, ibuf->byte_buffer.data, view_settings, display_settings);
|
||||
colormanage_display_buffer_process_ex(ibuf,
|
||||
ibuf->float_buffer.data,
|
||||
ibuf->byte_buffer.data,
|
||||
view_settings,
|
||||
display_settings,
|
||||
display_space);
|
||||
}
|
||||
|
||||
void IMB_colormanagement_imbuf_make_display_space(
|
||||
ImBuf *ibuf,
|
||||
const ColorManagedViewSettings *view_settings,
|
||||
const ColorManagedDisplaySettings *display_settings)
|
||||
const ColorManagedDisplaySettings *display_settings,
|
||||
const ColorManagedDisplaySpace display_space)
|
||||
{
|
||||
colormanagement_imbuf_make_display_space(ibuf, view_settings, display_settings, false);
|
||||
colormanagement_imbuf_make_display_space(
|
||||
ibuf, view_settings, display_settings, display_space, false);
|
||||
}
|
||||
|
||||
static ImBuf *imbuf_ensure_editable(ImBuf *ibuf, ImBuf *colormanaged_ibuf, bool allocate_result)
|
||||
@@ -2368,6 +2389,7 @@ ImBuf *IMB_colormanagement_imbuf_for_write(ImBuf *ibuf,
|
||||
colormanagement_imbuf_make_display_space(colormanaged_ibuf,
|
||||
&image_format->view_settings,
|
||||
&image_format->display_settings,
|
||||
DISPLAY_SPACE_FILE_OUTPUT,
|
||||
byte_output);
|
||||
|
||||
if (colormanaged_ibuf->float_buffer.data) {
|
||||
@@ -2554,7 +2576,7 @@ uchar *IMB_display_buffer_acquire(ImBuf *ibuf,
|
||||
DISPLAY_BUFFER_CHANNELS * size_t(ibuf->x) * size_t(ibuf->y), "imbuf display buffer");
|
||||
|
||||
colormanage_display_buffer_process(
|
||||
ibuf, display_buffer, applied_view_settings, display_settings);
|
||||
ibuf, display_buffer, applied_view_settings, display_settings, DISPLAY_SPACE_DRAW);
|
||||
|
||||
colormanage_cache_put(
|
||||
ibuf, &cache_view_settings, &cache_display_settings, display_buffer, cache_handle);
|
||||
@@ -3325,12 +3347,13 @@ void IMB_partial_display_buffer_update_delayed(ImBuf *ibuf, int xmin, int ymin,
|
||||
|
||||
ColormanageProcessor *IMB_colormanagement_display_processor_new(
|
||||
const ColorManagedViewSettings *view_settings,
|
||||
const ColorManagedDisplaySettings *display_settings)
|
||||
const ColorManagedDisplaySettings *display_settings,
|
||||
const ColorManagedDisplaySpace display_space)
|
||||
{
|
||||
ColormanageProcessor *cm_processor;
|
||||
ColorManagedViewSettings untonemapped_view_settings;
|
||||
const ColorManagedViewSettings *applied_view_settings;
|
||||
const ColorSpace *display_space;
|
||||
const ColorSpace *display_colorspace;
|
||||
|
||||
cm_processor = MEM_new<ColormanageProcessor>("colormanagement processor");
|
||||
|
||||
@@ -3343,21 +3366,22 @@ ColormanageProcessor *IMB_colormanagement_display_processor_new(
|
||||
applied_view_settings = &untonemapped_view_settings;
|
||||
}
|
||||
|
||||
display_space = get_display_colorspace(applied_view_settings, display_settings);
|
||||
if (display_space) {
|
||||
cm_processor->is_data_result = display_space->is_data();
|
||||
display_colorspace = get_display_colorspace(applied_view_settings, display_settings);
|
||||
if (display_colorspace) {
|
||||
cm_processor->is_data_result = display_colorspace->is_data();
|
||||
}
|
||||
|
||||
const bool use_white_balance = applied_view_settings->flag & COLORMANAGE_VIEW_USE_WHITE_BALANCE;
|
||||
cm_processor->cpu_processor = get_display_buffer_processor(applied_view_settings->look,
|
||||
cm_processor->cpu_processor = get_display_buffer_processor(*display_settings,
|
||||
applied_view_settings->look,
|
||||
applied_view_settings->view_transform,
|
||||
display_settings->display_device,
|
||||
applied_view_settings->exposure,
|
||||
applied_view_settings->gamma,
|
||||
applied_view_settings->temperature,
|
||||
applied_view_settings->tint,
|
||||
use_white_balance,
|
||||
global_role_scene_linear);
|
||||
global_role_scene_linear,
|
||||
display_space);
|
||||
|
||||
if (applied_view_settings->flag & COLORMANAGE_VIEW_USE_CURVES) {
|
||||
cm_processor->curve_mapping = BKE_curvemapping_copy(applied_view_settings->curve_mapping);
|
||||
@@ -3615,6 +3639,7 @@ bool IMB_colormanagement_setup_glsl_draw_from_space(
|
||||
display_parameters.do_overlay_merge = do_overlay_merge;
|
||||
display_parameters.use_hdr = GPU_hdr_support() &&
|
||||
(applied_view_settings->flag & COLORMANAGE_VIEW_USE_HDR) != 0;
|
||||
display_parameters.use_display_emulation = true;
|
||||
|
||||
/* 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(
|
||||
|
||||
@@ -30,6 +30,11 @@ struct DisplayParameters {
|
||||
float temperature = 6500.0f;
|
||||
float tint = 10.0f;
|
||||
bool use_white_balance = false;
|
||||
/* Allow HDR colors (above 1.0) in the result. */
|
||||
bool use_hdr = false;
|
||||
/* Rather than outputting colors for the specified display, output extended
|
||||
* sRGB colors emulating the specified display. */
|
||||
bool use_display_emulation = false;
|
||||
/* Invert the entire transform. */
|
||||
bool inverse = false;
|
||||
};
|
||||
|
||||
@@ -55,9 +55,14 @@ class Display {
|
||||
/**
|
||||
* Quick access to processors that convert color space from the display to scene linear and vice
|
||||
* versa. The call is allowed to be caching from the color space implementation perspective.
|
||||
*
|
||||
* With #use_display_emulation, rather than converting to the display space, this converts to
|
||||
* extended sRGB emulating the display space.
|
||||
*/
|
||||
const virtual CPUProcessor *get_to_scene_linear_cpu_processor() const = 0;
|
||||
const virtual CPUProcessor *get_from_scene_linear_cpu_processor() const = 0;
|
||||
const virtual CPUProcessor *get_to_scene_linear_cpu_processor(
|
||||
bool use_display_emulation) const = 0;
|
||||
const virtual CPUProcessor *get_from_scene_linear_cpu_processor(
|
||||
bool use_display_emulation) const = 0;
|
||||
|
||||
/**
|
||||
* Determine if the display supports HDR.
|
||||
|
||||
@@ -55,6 +55,9 @@ struct GPUDisplayParameters {
|
||||
bool do_overlay_merge = false;
|
||||
/* Allow HDR colors (above 1.0) in the result. */
|
||||
bool use_hdr = false;
|
||||
/* Rather than outputting colors for the specified display, output extended
|
||||
* sRGB colors emulating the specified display. */
|
||||
bool use_display_emulation = false;
|
||||
};
|
||||
|
||||
class GPUShaderBinder {
|
||||
|
||||
@@ -60,13 +60,15 @@ class FallbackDefaultDisplay : public Display {
|
||||
return &default_view_;
|
||||
}
|
||||
|
||||
const CPUProcessor *get_to_scene_linear_cpu_processor() const override
|
||||
const CPUProcessor *get_to_scene_linear_cpu_processor(
|
||||
bool /*use_display_emulation*/) const override
|
||||
{
|
||||
static FallbackSRGBToLinearRGBCPUProcessor processor;
|
||||
return &processor;
|
||||
}
|
||||
|
||||
const CPUProcessor *get_from_scene_linear_cpu_processor() const override
|
||||
const CPUProcessor *get_from_scene_linear_cpu_processor(
|
||||
bool /*use_display_emulation*/) const override
|
||||
{
|
||||
static FallbackLinearRGBToSRGBCPUProcessor processor;
|
||||
return &processor;
|
||||
|
||||
@@ -133,7 +133,9 @@ bool GPUDisplayShader::matches(const GPUDisplayParameters &display_parameters) c
|
||||
const bool use_curve_mapping = (display_parameters.curve_mapping != nullptr);
|
||||
return (this->from_colorspace == display_parameters.from_colorspace &&
|
||||
this->view == display_parameters.view && this->display == display_parameters.display &&
|
||||
this->look == display_parameters.look && this->use_curve_mapping == use_curve_mapping);
|
||||
this->look == display_parameters.look && this->use_curve_mapping == use_curve_mapping &&
|
||||
this->use_hdr == display_parameters.use_hdr &&
|
||||
this->use_display_emulation == display_parameters.use_display_emulation);
|
||||
}
|
||||
|
||||
bool GPUDisplayShader::initialize_common()
|
||||
@@ -394,6 +396,8 @@ bool GPUShaderBinder::display_bind(const GPUDisplayParameters &display_parameter
|
||||
display_shader->display = display_parameters.display;
|
||||
display_shader->look = display_parameters.look;
|
||||
display_shader->use_curve_mapping = (display_parameters.curve_mapping != nullptr);
|
||||
display_shader->use_hdr = display_parameters.use_hdr;
|
||||
display_shader->use_display_emulation = display_parameters.use_display_emulation;
|
||||
display_shader->is_valid = false;
|
||||
|
||||
if (display_parameters.curve_mapping) {
|
||||
|
||||
@@ -123,12 +123,14 @@ class GPUCurveMappping : NonCopyable, NonMovable {
|
||||
|
||||
class GPUDisplayShader : NonCopyable, NonMovable {
|
||||
public:
|
||||
/* Cache variables. */
|
||||
/* Cached display parameters. */
|
||||
std::string from_colorspace;
|
||||
std::string view;
|
||||
std::string display;
|
||||
std::string look;
|
||||
bool use_curve_mapping = false;
|
||||
bool use_hdr = false;
|
||||
bool use_display_emulation = false;
|
||||
|
||||
/* The shader is valid and can be bound.
|
||||
* Note that the cache might contain invalid shaders to prevent Blender from attempting to keep
|
||||
|
||||
@@ -143,51 +143,52 @@ const View *LibOCIODisplay::get_view_by_index(const int index) const
|
||||
return &views_[index];
|
||||
}
|
||||
|
||||
const CPUProcessor *LibOCIODisplay::get_to_scene_linear_cpu_processor() const
|
||||
std::unique_ptr<LibOCIOCPUProcessor> LibOCIODisplay::create_scene_linear_cpu_processor(
|
||||
const bool use_display_emulation, const bool inverse) const
|
||||
{
|
||||
return to_scene_linear_cpu_processor_.get([&]() -> std::unique_ptr<CPUProcessor> {
|
||||
DisplayParameters display_parameters;
|
||||
display_parameters.from_colorspace = OCIO_NAMESPACE::ROLE_SCENE_LINEAR;
|
||||
display_parameters.view = get_default_view()->name();
|
||||
display_parameters.display = name_;
|
||||
display_parameters.inverse = true;
|
||||
OCIO_NAMESPACE::ConstProcessorRcPtr ocio_processor = create_ocio_display_processor(
|
||||
*config_, display_parameters);
|
||||
if (!ocio_processor) {
|
||||
return nullptr;
|
||||
}
|
||||
const View *view = get_untonemapped_view();
|
||||
if (view == nullptr) {
|
||||
view = get_default_view();
|
||||
}
|
||||
|
||||
OCIO_NAMESPACE::ConstCPUProcessorRcPtr ocio_cpu_processor =
|
||||
ocio_processor->getDefaultCPUProcessor();
|
||||
if (!ocio_cpu_processor) {
|
||||
return nullptr;
|
||||
}
|
||||
DisplayParameters display_parameters;
|
||||
display_parameters.from_colorspace = OCIO_NAMESPACE::ROLE_SCENE_LINEAR;
|
||||
display_parameters.view = view->name();
|
||||
display_parameters.display = name_;
|
||||
display_parameters.inverse = inverse;
|
||||
display_parameters.use_display_emulation = use_display_emulation;
|
||||
OCIO_NAMESPACE::ConstProcessorRcPtr ocio_processor = create_ocio_display_processor(
|
||||
*config_, display_parameters);
|
||||
if (!ocio_processor) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::make_unique<LibOCIOCPUProcessor>(ocio_cpu_processor);
|
||||
});
|
||||
OCIO_NAMESPACE::ConstCPUProcessorRcPtr ocio_cpu_processor =
|
||||
ocio_processor->getDefaultCPUProcessor();
|
||||
if (!ocio_cpu_processor) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::make_unique<LibOCIOCPUProcessor>(ocio_cpu_processor);
|
||||
}
|
||||
|
||||
const CPUProcessor *LibOCIODisplay::get_from_scene_linear_cpu_processor() const
|
||||
const CPUProcessor *LibOCIODisplay::get_to_scene_linear_cpu_processor(
|
||||
const bool use_display_emulation) const
|
||||
{
|
||||
return from_scene_linear_cpu_processor_.get([&]() -> std::unique_ptr<CPUProcessor> {
|
||||
DisplayParameters display_parameters;
|
||||
display_parameters.from_colorspace = OCIO_NAMESPACE::ROLE_SCENE_LINEAR;
|
||||
display_parameters.view = get_default_view()->name();
|
||||
display_parameters.display = name_;
|
||||
OCIO_NAMESPACE::ConstProcessorRcPtr ocio_processor = create_ocio_display_processor(
|
||||
*config_, display_parameters);
|
||||
if (!ocio_processor) {
|
||||
return nullptr;
|
||||
}
|
||||
const CPUProcessorCache &cache = (use_display_emulation) ?
|
||||
to_scene_linear_emulation_cpu_processor_ :
|
||||
to_scene_linear_cpu_processor_;
|
||||
return cache.get([&] { return create_scene_linear_cpu_processor(use_display_emulation, true); });
|
||||
}
|
||||
|
||||
OCIO_NAMESPACE::ConstCPUProcessorRcPtr ocio_cpu_processor =
|
||||
ocio_processor->getDefaultCPUProcessor();
|
||||
if (!ocio_cpu_processor) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::make_unique<LibOCIOCPUProcessor>(ocio_cpu_processor);
|
||||
});
|
||||
const CPUProcessor *LibOCIODisplay::get_from_scene_linear_cpu_processor(
|
||||
const bool use_display_emulation) const
|
||||
{
|
||||
const CPUProcessorCache &cache = (use_display_emulation) ?
|
||||
from_scene_linear_emulation_cpu_processor_ :
|
||||
from_scene_linear_cpu_processor_;
|
||||
return cache.get(
|
||||
[&] { return create_scene_linear_cpu_processor(use_display_emulation, false); });
|
||||
}
|
||||
|
||||
} // namespace blender::ocio
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
namespace blender::ocio {
|
||||
|
||||
class LibOCIOConfig;
|
||||
class LibOCIOCPUProcessor;
|
||||
|
||||
class LibOCIODisplay : public Display {
|
||||
/* Store by pointer to allow move semantic.
|
||||
@@ -33,7 +34,9 @@ class LibOCIODisplay : public Display {
|
||||
bool is_hdr_ = false;
|
||||
|
||||
CPUProcessorCache to_scene_linear_cpu_processor_;
|
||||
CPUProcessorCache to_scene_linear_emulation_cpu_processor_;
|
||||
CPUProcessorCache from_scene_linear_cpu_processor_;
|
||||
CPUProcessorCache from_scene_linear_emulation_cpu_processor_;
|
||||
|
||||
public:
|
||||
LibOCIODisplay(int index, const LibOCIOConfig &config);
|
||||
@@ -63,8 +66,9 @@ class LibOCIODisplay : public Display {
|
||||
int get_num_views() const override;
|
||||
const View *get_view_by_index(int index) const override;
|
||||
|
||||
const CPUProcessor *get_to_scene_linear_cpu_processor() const override;
|
||||
const CPUProcessor *get_from_scene_linear_cpu_processor() const override;
|
||||
const CPUProcessor *get_to_scene_linear_cpu_processor(bool use_display_emulation) const override;
|
||||
const CPUProcessor *get_from_scene_linear_cpu_processor(
|
||||
bool use_display_emulation) const override;
|
||||
|
||||
bool is_hdr() const override
|
||||
{
|
||||
@@ -72,6 +76,10 @@ class LibOCIODisplay : public Display {
|
||||
}
|
||||
|
||||
MEM_CXX_CLASS_ALLOC_FUNCS("LibOCIOConfig");
|
||||
|
||||
protected:
|
||||
std::unique_ptr<LibOCIOCPUProcessor> create_scene_linear_cpu_processor(
|
||||
const bool use_display_emulation, const bool inverse) const;
|
||||
};
|
||||
|
||||
} // namespace blender::ocio
|
||||
|
||||
@@ -12,11 +12,99 @@
|
||||
|
||||
# include "error_handling.hh"
|
||||
# include "libocio_config.hh"
|
||||
# include "libocio_display.hh"
|
||||
|
||||
# include "../white_point.hh"
|
||||
|
||||
# include "CLG_log.h"
|
||||
|
||||
static CLG_LogRef LOG = {"color_management"};
|
||||
|
||||
namespace blender::ocio {
|
||||
|
||||
static void display_as_extended_srgb(const LibOCIOConfig &config,
|
||||
OCIO_NAMESPACE::GroupTransformRcPtr &group,
|
||||
StringRefNull display_name,
|
||||
StringRefNull view_name)
|
||||
{
|
||||
/* Emulate the user specified display on an extended sRGB display:
|
||||
* - Apply the view and display transform
|
||||
* - Clamp colors to be within gamut
|
||||
* - Apply the inverse untonemapped display transform
|
||||
* - Convert to extended sRGB
|
||||
*/
|
||||
|
||||
/* TODO: This is quite inefficient, and may be possible to optimize.
|
||||
*
|
||||
* Ideally we want OpenColor to cancel out the last part of the forward display transform
|
||||
* and inverse untonemapped display transform. By itself this seems to generally work for
|
||||
* the Blender and ACES 2.0 configs.
|
||||
*
|
||||
* However the clamping prevents this. For common display color spaces it should be possible
|
||||
* to clamp the gamut and HDR in a linear space. For example for PQ, we'd need to clamp
|
||||
* to Rec.2020 gamut and (10000 nits max / 100 nits reference white) = 100.0. Probably this
|
||||
* can be done with a matrix transform, clamp and inverse matrix transform. Where hopefully
|
||||
* the matrix transforms can be merged with a preceding or following one. */
|
||||
|
||||
const LibOCIODisplay *display = static_cast<const LibOCIODisplay *>(
|
||||
config.get_display_by_name(display_name));
|
||||
const LibOCIOView *view = (display) ? static_cast<const LibOCIOView *>(
|
||||
display->get_view_by_name(view_name)) :
|
||||
nullptr;
|
||||
|
||||
/* Clamp to gamut, so that we don't display values outside of it. One exception
|
||||
* is extended sRGB, where we need to preserve them for correct results and assume
|
||||
* the view transform already clamped them. */
|
||||
if (!(view && view->is_extended())) {
|
||||
auto clamp = OCIO_NAMESPACE::RangeTransform::Create();
|
||||
clamp->setStyle(OCIO_NAMESPACE::RANGE_CLAMP);
|
||||
clamp->setMinInValue(0.0);
|
||||
clamp->setMinOutValue(0.0);
|
||||
clamp->setMaxInValue(1.0);
|
||||
clamp->setMaxOutValue(1.0);
|
||||
group->appendTransform(clamp);
|
||||
}
|
||||
|
||||
/* Nothing further to do if already using sRGB. */
|
||||
if (view && view->is_srgb()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Find the linear Rec.709 colorspace needed for the extended sRGB transform. */
|
||||
const OCIO_NAMESPACE::ConstConfigRcPtr &ocio_config = config.get_ocio_config();
|
||||
const char *lin_rec709_srgb = ocio_config->getCanonicalName("lin_rec709_srgb");
|
||||
if (lin_rec709_srgb == nullptr || lin_rec709_srgb[0] == '\0') {
|
||||
CLOG_INFO(&LOG, "Failed to find lin_rec709_srgb colorspace, display may be incorrect");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Find the display and its untonemapped view. */
|
||||
const View *untonemapped_view = display->get_untonemapped_view();
|
||||
if (untonemapped_view == nullptr) {
|
||||
CLOG_INFO(&LOG,
|
||||
"Failed to find untonemapped view for \"%s\", display may be incorrect",
|
||||
display->name().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
/* Convert to extended sRGB. */
|
||||
const ColorSpace *display_colorspace = config.get_display_view_color_space(
|
||||
display->name().c_str(), untonemapped_view->name().c_str());
|
||||
|
||||
auto to_rec709 = OCIO_NAMESPACE::ColorSpaceTransform::Create();
|
||||
to_rec709->setSrc(display_colorspace->name().c_str());
|
||||
to_rec709->setDst(lin_rec709_srgb);
|
||||
group->appendTransform(to_rec709);
|
||||
|
||||
/* sRGB transfer function, mirrored for negative as specified by scRGB and extended sRGB. */
|
||||
auto to_extended_srgb = OCIO_NAMESPACE::ExponentWithLinearTransform::Create();
|
||||
to_extended_srgb->setGamma({2.4, 2.4, 2.4, 1.0});
|
||||
to_extended_srgb->setOffset({0.055, 0.055, 0.055, 0.0});
|
||||
to_extended_srgb->setDirection(OCIO_NAMESPACE::TRANSFORM_DIR_INVERSE);
|
||||
to_extended_srgb->setNegativeStyle(OCIO_NAMESPACE::NEGATIVE_MIRROR);
|
||||
group->appendTransform(to_extended_srgb);
|
||||
}
|
||||
|
||||
OCIO_NAMESPACE::ConstProcessorRcPtr create_ocio_display_processor(
|
||||
const LibOCIOConfig &config, const DisplayParameters &display_parameters)
|
||||
{
|
||||
@@ -102,6 +190,10 @@ OCIO_NAMESPACE::ConstProcessorRcPtr create_ocio_display_processor(
|
||||
group->appendTransform(et);
|
||||
}
|
||||
|
||||
if (display_parameters.use_display_emulation) {
|
||||
display_as_extended_srgb(config, group, display_parameters.display, display_parameters.view);
|
||||
}
|
||||
|
||||
if (display_parameters.inverse) {
|
||||
group->setDirection(TRANSFORM_DIR_INVERSE);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@ static ConstProcessorRcPtr create_to_display_processor(
|
||||
display_parameters.view = display_shader.view;
|
||||
display_parameters.display = display_shader.display;
|
||||
display_parameters.look = display_shader.look;
|
||||
display_parameters.use_hdr = display_shader.use_hdr;
|
||||
display_parameters.use_display_emulation = display_shader.use_display_emulation;
|
||||
return create_ocio_display_processor(config, display_parameters);
|
||||
}
|
||||
|
||||
|
||||
@@ -221,23 +221,20 @@ float4 OCIO_ProcessColor(float4 col, float4 col_overlay)
|
||||
* w.r.t. display chromaticity and radiometry. We separate the color-management process into two
|
||||
* steps to be able to merge UI using alpha blending in the correct color space. */
|
||||
if (parameters.do_overlay_merge) {
|
||||
col.rgb = pow(col.rgb, float3(parameters.exponent * 2.2));
|
||||
/* This sign/abs is used to preserve negative values for extended sRGB. */
|
||||
col.rgb = sign(col.rgb) * pow(abs(col.rgb), float3(parameters.exponent * 2.2));
|
||||
|
||||
if (!parameters.use_hdr) {
|
||||
/* If we're not using an extended color space, clamp the color 0..1. */
|
||||
col = clamp(col, 0.0, 1.0);
|
||||
}
|
||||
else {
|
||||
if (parameters.use_hdr) {
|
||||
/* When using extended color-space, interpolate towards clamped color to improve display of
|
||||
* alpha-blended overlays. */
|
||||
col = mix(max(col, 0.0), clamp(col, 0.0, 1.0), col_overlay.a);
|
||||
col = mix(col, clamp(col, 0.0, 1.0), col_overlay.a);
|
||||
}
|
||||
col *= 1.0 - col_overlay.a;
|
||||
col += col_overlay; /* Assumed unassociated alpha. */
|
||||
col.rgb = pow(col.rgb, float3(1.0 / 2.2));
|
||||
col.rgb = sign(col.rgb) * pow(abs(col.rgb), float3(1.0 / 2.2));
|
||||
}
|
||||
else {
|
||||
col.rgb = pow(col.rgb, float3(parameters.exponent));
|
||||
col.rgb = sign(col.rgb) * pow(abs(col.rgb), float3(parameters.exponent));
|
||||
}
|
||||
|
||||
if (parameters.dither > 0.0) {
|
||||
|
||||
@@ -1327,7 +1327,12 @@ static void rna_def_colormanage(BlenderRNA *brna)
|
||||
"rna_ColorManagedDisplaySettings_display_device_get",
|
||||
"rna_ColorManagedDisplaySettings_display_device_set",
|
||||
"rna_ColorManagedDisplaySettings_display_device_itemf");
|
||||
RNA_def_property_ui_text(prop, "Display Device", "Display device name");
|
||||
RNA_def_property_ui_text(
|
||||
prop,
|
||||
"Display Device",
|
||||
"Display device name. For viewing, this is the display that will be emulated by limiting of "
|
||||
"gamut and HDR colors. For image and video output, this is the display space used for "
|
||||
"writing.");
|
||||
RNA_def_property_update(
|
||||
prop, NC_WINDOW, "rna_ColorManagedDisplaySettings_display_device_update");
|
||||
|
||||
|
||||
@@ -65,6 +65,8 @@
|
||||
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "IMB_colormanagement.hh"
|
||||
|
||||
#ifdef WITH_OPENSUBDIV
|
||||
# include "BKE_subsurf.hh"
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user