Vulkan: Tweaks to improve HDR display on Windows

* Improve accuracy of warning when HDR display is not supported, taking into
  account HDR mode on/off on Windows.
* When HDR mode is disabled on Windows, don't create a HDR swapchain. This
  saves memory, and avoids a color difference on NVIDIA. That's because NVIDIA
  is the only GPU we've tested that allows a HDR swapchain when HDR mode is
  off, and we don't currently know the expected transforms for that case.
* Recreate swapchain when HDR mode on/off switch is detected.
* Update HDR info when window gains focus.

Note this means there is no wide gamut when Windows HDR is off, but it was
already not working. For that we may need to add support for something like
10bit VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT, or whatever is commonly
available outside of HDR mode.

Pull Request: https://projects.blender.org/blender/blender/pulls/144959
This commit is contained in:
Brecht Van Lommel
2025-08-28 11:03:24 +02:00
committed by Brecht Van Lommel
parent f77881a795
commit 20a19c7aa4
16 changed files with 103 additions and 30 deletions

View File

@@ -228,6 +228,13 @@ extern GHOST_TSuccess GHOST_DisposeWindow(GHOST_SystemHandle systemhandle,
*/
extern bool GHOST_ValidWindow(GHOST_SystemHandle systemhandle, GHOST_WindowHandle windowhandle);
/*
**
* Returns high dynamic range color information about this window.
* \return HDR info.
*/
extern GHOST_WindowHDRInfo GHOST_WindowGetHDRInfo(GHOST_WindowHandle windowhandle);
/**
* Get the Window under the cursor. Although coordinates of the mouse are supplied, platform-
* specific implementations are free to ignore these and query the mouse location themselves, due

View File

@@ -379,6 +379,12 @@ class GHOST_IWindow {
*/
virtual uint16_t getDPIHint() = 0;
/**
* Returns high dynamic range color information about this window.
* \return HDR info.
* */
virtual GHOST_WindowHDRInfo getHDRInfo() = 0;
#ifdef WITH_INPUT_IME
/**
* Enable IME attached to the given window, i.e. allows user-input

View File

@@ -217,6 +217,12 @@ bool GHOST_ValidWindow(GHOST_SystemHandle systemhandle, GHOST_WindowHandle windo
return system->validWindow(window);
}
GHOST_WindowHDRInfo GHOST_WindowGetHDRInfo(GHOST_WindowHandle windowhandle)
{
GHOST_IWindow *window = (GHOST_IWindow *)windowhandle;
return window->getHDRInfo();
}
GHOST_WindowHandle GHOST_GetWindowUnderCursor(GHOST_SystemHandle systemhandle,
int32_t x,
int32_t y)

View File

@@ -598,7 +598,8 @@ GHOST_ContextVK::GHOST_ContextVK(const GHOST_ContextParams &context_params,
surface_(VK_NULL_HANDLE),
swapchain_(VK_NULL_HANDLE),
frame_data_(GHOST_FRAMES_IN_FLIGHT),
render_frame_(0)
render_frame_(0),
use_hdr_swapchain_(false)
{
}
@@ -648,24 +649,31 @@ GHOST_TSuccess GHOST_ContextVK::swapBuffers()
vkWaitForFences(device, 1, &submission_frame_data.submission_fence, true, UINT64_MAX);
}
submission_frame_data.discard_pile.destroy(device);
bool use_hdr_swapchain = true;
#ifdef WITH_GHOST_WAYLAND
/* Wayland doesn't provide a WSI with windowing capabilities, therefore cannot detect whether the
* swap-chain needs to be recreated. But as a side effect we can recreate the swap-chain before
* presenting. */
if (wayland_window_info_) {
const bool recreate_swapchain = ((wayland_window_info_->size[0] !=
std::max(render_extent_.width, render_extent_min_.width)) ||
(wayland_window_info_->size[1] !=
std::max(render_extent_.height, render_extent_min_.height)));
use_hdr_swapchain = wayland_window_info_->is_color_managed;
if (recreate_swapchain) {
/* Swap-chain is out of date. Recreate swap-chain. */
recreateSwapchain(use_hdr_swapchain);
}
const bool use_hdr_swapchain = hdr_info_ && hdr_info_->hdr_enabled;
if (use_hdr_swapchain != use_hdr_swapchain_) {
/* Re-create swapchain if HDR mode was toggled in the system settings. */
recreateSwapchain(use_hdr_swapchain);
}
else {
#ifdef WITH_GHOST_WAYLAND
/* Wayland doesn't provide a WSI with windowing capabilities, therefore cannot detect whether
* the swap-chain needs to be recreated. But as a side effect we can recreate the swap-chain
* before presenting. */
if (wayland_window_info_) {
const bool recreate_swapchain =
((wayland_window_info_->size[0] !=
std::max(render_extent_.width, render_extent_min_.width)) ||
(wayland_window_info_->size[1] !=
std::max(render_extent_.height, render_extent_min_.height)));
if (recreate_swapchain) {
/* Swap-chain is out of date. Recreate swap-chain. */
recreateSwapchain(use_hdr_swapchain);
}
}
#endif
}
/* There is no valid swapchain as the previous window was minimized. User can have maximized the
* window so we need to check if the swapchain can be created. */
if (swapchain_ == VK_NULL_HANDLE) {
@@ -1014,6 +1022,7 @@ GHOST_TSuccess GHOST_ContextVK::recreateSwapchain(bool use_hdr_swapchain)
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface_, &capabilities);
}
use_hdr_swapchain_ = use_hdr_swapchain;
render_extent_ = capabilities.currentExtent;
render_extent_min_ = capabilities.minImageExtent;
if (render_extent_.width == UINT32_MAX) {

View File

@@ -56,7 +56,6 @@ enum GHOST_TVulkanPlatformType {
struct GHOST_ContextVK_WindowInfo {
int size[2];
bool is_color_managed;
};
struct GHOST_FrameDiscard {
@@ -245,6 +244,7 @@ class GHOST_ContextVK : public GHOST_Context {
VkExtent2D render_extent_;
VkExtent2D render_extent_min_;
VkSurfaceFormatKHR surface_format_;
bool use_hdr_swapchain_;
std::function<void(const GHOST_VulkanSwapChainData *)> swap_buffers_pre_callback_;
std::function<void(void)> swap_buffers_post_callback_;

View File

@@ -2361,6 +2361,7 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, uint msg, WPARAM wParam,
* another window-management function. */
case WM_SETFOCUS: {
/* The WM_SETFOCUS message is sent to a window after it has gained the keyboard focus. */
window->updateHDRInfo();
break;
}
/* ============

View File

@@ -250,6 +250,12 @@ class GHOST_Window : public GHOST_IWindow {
return 96;
}
/** \copydoc #GHOST_IWindow::getHDRInfo */
GHOST_WindowHDRInfo getHDRInfo() override
{
return hdr_info_;
}
#ifdef WITH_INPUT_IME
void beginIME(
int32_t /*x*/, int32_t /*y*/, int32_t /*w*/, int32_t /*h*/, bool /*completed*/) override
@@ -358,6 +364,8 @@ class GHOST_Window : public GHOST_IWindow {
/* OSX only, retina screens */
float native_pixel_size_;
GHOST_WindowHDRInfo hdr_info_ = GHOST_WINDOW_HDR_INFO_NONE;
private:
GHOST_Context *context_;
};

View File

@@ -401,6 +401,10 @@ GHOST_WindowCocoa::GHOST_WindowCocoa(GHOST_SystemCocoa *systemCocoa,
CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(name);
metal_layer_.colorspace = colorspace;
CGColorSpaceRelease(colorspace);
/* For Blender to know if this window supports HDR. */
hdr_info_.hdr_enabled = true;
hdr_info_.sdr_white_level = 1.0f;
}
metal_view_ = [[CocoaMetalView alloc] initWithSystemCocoa:systemCocoa
@@ -898,7 +902,7 @@ GHOST_Context *GHOST_WindowCocoa::newDrawingContext(GHOST_TDrawingContextType ty
#ifdef WITH_VULKAN_BACKEND
case GHOST_kDrawingContextTypeVulkan: {
GHOST_Context *context = new GHOST_ContextVK(
want_context_params_, metal_layer_, 1, 2, true, preferred_device_);
want_context_params_, metal_layer_, 1, 2, true, preferred_device_, &hdr_info_);
if (context->initializeDrawingContext()) {
return context;
}

View File

@@ -1991,7 +1991,12 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system,
window_->backend.vulkan_window_info = new GHOST_ContextVK_WindowInfo;
window_->backend.vulkan_window_info->size[0] = window_->frame.size[0];
window_->backend.vulkan_window_info->size[1] = window_->frame.size[1];
window_->backend.vulkan_window_info->is_color_managed = true;
/* There is no HDR on/off settings as on Windows, so from the Window side
* consider it always enabled. But may still get disabled if Vulkan has no
* appropriate surface format. */
hdr_info_.hdr_enabled = true;
hdr_info_.sdr_white_level = 1.0f;
}
#endif
@@ -2472,7 +2477,8 @@ GHOST_Context *GHOST_WindowWayland::newDrawingContext(GHOST_TDrawingContextType
window_->backend.vulkan_window_info,
1,
2,
preferred_device_);
preferred_device_,
&hdr_info_);
if (context->initializeDrawingContext()) {
return context;
}

View File

@@ -1326,7 +1326,10 @@ void GHOST_WindowWin32::updateHDRInfo()
color_info.header.id = path.targetInfo.id;
if (::DisplayConfigGetDeviceInfo(&color_info.header) == ERROR_SUCCESS) {
info.hdr_enabled = color_info.advancedColorSupported && color_info.advancedColorEnabled;
/* This particular combination indicates HDR mode is enabled. This is undocumented but
* used by WinRT. When wideColorEnforced is true we are in SDR mode with advanced color. */
info.hdr_enabled = color_info.advancedColorSupported && color_info.advancedColorEnabled &&
!color_info.wideColorEnforced;
}
if (info.hdr_enabled) {

View File

@@ -415,8 +415,6 @@ class GHOST_WindowWin32 : public GHOST_Window {
GHOST_DirectManipulationHelper *direct_manipulation_helper_;
GHOST_WindowHDRInfo hdr_info_ = GHOST_WINDOW_HDR_INFO_NONE;
#ifdef WITH_INPUT_IME
/** Handle input method editors event */
GHOST_ImeWin32 ime_input_;

View File

@@ -1191,7 +1191,8 @@ GHOST_Context *GHOST_WindowX11::newDrawingContext(GHOST_TDrawingContextType type
nullptr,
1,
2,
preferred_device_);
preferred_device_,
&hdr_info_);
if (context->initializeDrawingContext()) {
return context;
}

View File

@@ -76,12 +76,10 @@ class RENDER_PT_color_management(RenderButtonsPanel, Panel):
col.prop(view, "view_transform")
col.prop(view, "look")
if view.is_hdr:
import gpu
if not gpu.capabilities.hdr_support_get():
row = col.split(factor=0.4)
row.label()
row.label(text="HDR display not supported", icon="INFO")
if view.is_hdr and not context.window.support_hdr_color:
row = col.split(factor=0.4)
row.label()
row.label(text="HDR display not supported", icon="INFO")
col = flow.column()
col.prop(view, "exposure")

View File

@@ -1049,6 +1049,12 @@ static void rna_Window_view_layer_set(PointerRNA *ptr, PointerRNA value, ReportL
WM_window_set_active_view_layer(win, view_layer);
}
static bool rna_Window_support_hdr_color_get(PointerRNA *ptr)
{
wmWindow *win = static_cast<wmWindow *>(ptr->data);
return WM_window_support_hdr_color(win);
}
static bool rna_Window_modal_handler_skip(CollectionPropertyIterator * /*iter*/, void *data)
{
const wmEventHandler_Op *handler = (wmEventHandler_Op *)data;
@@ -2782,6 +2788,14 @@ static void rna_def_window(BlenderRNA *brna)
RNA_def_property_struct_type(prop, "Stereo3dDisplay");
RNA_def_property_ui_text(prop, "Stereo 3D Display", "Settings for stereo 3D display");
prop = RNA_def_property(srna, "support_hdr_color", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(prop,
"Support HDR Color",
"The window has a HDR graphics buffer that wide gamut and high dynamic "
"range colors can be written to, in extended sRGB color space.");
RNA_def_property_boolean_funcs(prop, "rna_Window_support_hdr_color_get", nullptr);
prop = RNA_def_property(srna, "modal_operators", PROP_COLLECTION, PROP_NONE);
RNA_def_property_struct_type(prop, "Operator");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);

View File

@@ -316,6 +316,11 @@ bool WM_window_is_main_top_level(const wmWindow *win);
bool WM_window_is_fullscreen(const wmWindow *win);
bool WM_window_is_maximized(const wmWindow *win);
/*
* Support for wide gamut and HDR colors.
*/
bool WM_window_support_hdr_color(const wmWindow *win);
/**
* Some editor data may need to be synced with scene data (3D View camera and layers).
* This function ensures data is synced for editors

View File

@@ -83,6 +83,7 @@
#include "UI_interface_layout.hh"
#include "BLF_api.hh"
#include "GPU_capabilities.hh"
#include "GPU_context.hh"
#include "GPU_framebuffer.hh"
#include "GPU_init_exit.hh"
@@ -2895,6 +2896,12 @@ bool WM_window_is_main_top_level(const wmWindow *win)
return true;
}
bool WM_window_support_hdr_color(const wmWindow *win)
{
return GPU_hdr_support() && win->ghostwin &&
GHOST_WindowGetHDRInfo(static_cast<GHOST_WindowHandle>(win->ghostwin)).hdr_enabled;
}
/** \} */
/* -------------------------------------------------------------------- */