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;
}