From 0ea1feabd92b08c64c3dd8ec8bc1da542cdc3305 Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Fri, 22 Aug 2025 10:11:55 +0200 Subject: [PATCH] Vulkan: HDR support for Windows This PR adds HDR support for Windows for `VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT` on `VK_FORMAT_R16G16B16A16_SFLOAT` swapchains . For nonlinear surface formats (sRGB and extended sRGB) the back buffer is blit into the swapchain, When VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT is used as surface format a compute shader is used to flip and invert the gamma. SDR white level is updated from a few window event changes, but actually none of them immediately respond to SDR white level changes in the system. That requires using the WinRT API, which we don't do so far. Current limitations: - Intel GPU support - Dual GPU support In the future we may add controls inside Blender for absolute HDR nits, across different platforms. But this makes behavior closer to macOS. See !144565 for details Co-authored-by: Brecht Van Lommel Pull Request: https://projects.blender.org/blender/blender/pulls/144717 --- intern/ghost/GHOST_Types.h | 14 +++ intern/ghost/intern/GHOST_ContextVK.cc | 20 ++++- intern/ghost/intern/GHOST_ContextVK.hh | 6 +- intern/ghost/intern/GHOST_SystemWin32.cc | 9 ++ intern/ghost/intern/GHOST_WindowWin32.cc | 89 ++++++++++++++++++- intern/ghost/intern/GHOST_WindowWin32.hh | 5 ++ source/blender/gpu/CMakeLists.txt | 9 ++ .../gpu/intern/gpu_shader_create_info_list.hh | 4 + source/blender/gpu/shaders/CMakeLists.txt | 8 ++ .../shaders/vk_backbuffer_blit_comp.glsl | 25 ++++++ .../vulkan/shaders/vk_backbuffer_blit_info.hh | 15 ++++ source/blender/gpu/vulkan/vk_context.cc | 59 +++++++----- source/blender/gpu/vulkan/vk_device.cc | 1 + source/blender/gpu/vulkan/vk_device.hh | 10 +++ source/blender/gpu/vulkan/vk_texture.cc | 9 ++ source/blender/gpu/vulkan/vk_texture.hh | 3 + 16 files changed, 259 insertions(+), 27 deletions(-) create mode 100644 source/blender/gpu/vulkan/shaders/vk_backbuffer_blit_comp.glsl create mode 100644 source/blender/gpu/vulkan/shaders/vk_backbuffer_blit_info.hh diff --git a/intern/ghost/GHOST_Types.h b/intern/ghost/GHOST_Types.h index 691e3c9a4c9..0db26735d65 100644 --- a/intern/ghost/GHOST_Types.h +++ b/intern/ghost/GHOST_Types.h @@ -852,6 +852,18 @@ typedef struct { float colored_titlebar_bg_color[3]; } GHOST_WindowDecorationStyleSettings; +typedef struct { + /* Is HDR enabled for this Window? */ + bool hdr_enabled; + /* Scale factor to display SDR content in HDR. */ + float sdr_white_level; +} GHOST_WindowHDRInfo; + +#define GHOST_WINDOW_HDR_INFO_NONE \ + { \ + /*hdr_enabled*/ false, /*sdr_white_level*/ 1.0f, \ + } + #ifdef WITH_VULKAN_BACKEND typedef struct { /** Image handle to the image that will be presented to the user. */ @@ -866,6 +878,8 @@ typedef struct { VkSemaphore present_semaphore; /** Fence to signal after the image has been updated. */ VkFence submission_fence; + /* Factor to scale SDR content to HDR. */ + float sdr_scale; } GHOST_VulkanSwapChainData; typedef enum { diff --git a/intern/ghost/intern/GHOST_ContextVK.cc b/intern/ghost/intern/GHOST_ContextVK.cc index 1636a7660a5..6e893efcfa1 100644 --- a/intern/ghost/intern/GHOST_ContextVK.cc +++ b/intern/ghost/intern/GHOST_ContextVK.cc @@ -574,7 +574,8 @@ GHOST_ContextVK::GHOST_ContextVK(const GHOST_ContextParams &context_params, #endif int contextMajorVersion, int contextMinorVersion, - const GHOST_GPUDevice &preferred_device) + const GHOST_GPUDevice &preferred_device, + const GHOST_WindowHDRInfo *hdr_info) : GHOST_Context(context_params), #ifdef _WIN32 hwnd_(hwnd), @@ -593,6 +594,7 @@ GHOST_ContextVK::GHOST_ContextVK(const GHOST_ContextParams &context_params, context_major_version_(contextMajorVersion), context_minor_version_(contextMinorVersion), preferred_device_(preferred_device), + hdr_info_(hdr_info), surface_(VK_NULL_HANDLE), swapchain_(VK_NULL_HANDLE), frame_data_(GHOST_FRAMES_IN_FLIGHT), @@ -644,7 +646,7 @@ GHOST_TSuccess GHOST_ContextVK::swapBuffers() * has been signaled and waited for. */ vkWaitForFences(device, 1, &submission_frame_data.submission_fence, true, UINT64_MAX); submission_frame_data.discard_pile.destroy(device); - bool use_hdr_swapchain = false; + 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 @@ -720,6 +722,7 @@ GHOST_TSuccess GHOST_ContextVK::swapBuffers() swap_chain_data.submission_fence = submission_frame_data.submission_fence; swap_chain_data.acquire_semaphore = submission_frame_data.acquire_semaphore; swap_chain_data.present_semaphore = swapchain_image.present_semaphore; + swap_chain_data.sdr_scale = (hdr_info_) ? hdr_info_->sdr_white_level : 1.0f; vkResetFences(device, 1, &submission_frame_data.submission_fence); if (swap_buffers_pre_callback_) { @@ -767,6 +770,7 @@ GHOST_TSuccess GHOST_ContextVK::getVulkanSwapChainFormat( r_swap_chain_data->image = VK_NULL_HANDLE; r_swap_chain_data->surface_format = surface_format_; r_swap_chain_data->extent = render_extent_; + r_swap_chain_data->sdr_scale = (hdr_info_) ? hdr_info_->sdr_white_level : 1.0f; return GHOST_kSuccess; } @@ -915,7 +919,7 @@ static bool selectSurfaceFormat(const VkPhysicalDevice physical_device, vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &format_count, formats.data()); array, 4> selection_order = { - make_pair(VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT, VK_FORMAT_R16G16B16A16_SFLOAT), + make_pair(VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT, VK_FORMAT_R16G16B16A16_SFLOAT), make_pair(VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, VK_FORMAT_R16G16B16A16_SFLOAT), make_pair(VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, VK_FORMAT_R8G8B8A8_UNORM), make_pair(VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, VK_FORMAT_B8G8R8A8_UNORM), @@ -1119,7 +1123,7 @@ GHOST_TSuccess GHOST_ContextVK::recreateSwapchain(bool use_hdr_swapchain) create_info.imageColorSpace = surface_format_.colorSpace; create_info.imageExtent = render_extent_; create_info.imageArrayLayers = 1; - create_info.imageUsage = VK_IMAGE_USAGE_TRANSFER_DST_BIT; + create_info.imageUsage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_STORAGE_BIT; create_info.preTransform = capabilities.currentTransform; create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; create_info.presentMode = present_mode; @@ -1226,6 +1230,7 @@ GHOST_TSuccess GHOST_ContextVK::initializeDrawingContext() bool use_hdr_swapchain = false; #ifdef _WIN32 const bool use_window_surface = (hwnd_ != nullptr); + use_hdr_swapchain = true; #elif defined(__APPLE__) const bool use_window_surface = (metal_layer_ != nullptr); #else /* UNIX/Linux */ @@ -1281,6 +1286,13 @@ GHOST_TSuccess GHOST_ContextVK::initializeDrawingContext() VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME); optional_device_extensions.push_back(VK_EXT_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME); } + + const bool use_swapchain_colorspace = contains_extension( + extensions_available, VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME); + if (use_swapchain_colorspace) { + requireExtension( + extensions_available, extensions_enabled, VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME); + } } /* External memory extensions. */ diff --git a/intern/ghost/intern/GHOST_ContextVK.hh b/intern/ghost/intern/GHOST_ContextVK.hh index 40558ffd1ed..2582f23c1cc 100644 --- a/intern/ghost/intern/GHOST_ContextVK.hh +++ b/intern/ghost/intern/GHOST_ContextVK.hh @@ -127,7 +127,8 @@ class GHOST_ContextVK : public GHOST_Context { #endif int contextMajorVersion, int contextMinorVersion, - const GHOST_GPUDevice &preferred_device); + const GHOST_GPUDevice &preferred_device, + const GHOST_WindowHDRInfo *hdr_info_ = nullptr); /** * Destructor. @@ -230,6 +231,9 @@ class GHOST_ContextVK : public GHOST_Context { const int context_minor_version_; const GHOST_GPUDevice preferred_device_; + /* Optional HDR info updated by window. */ + const GHOST_WindowHDRInfo *hdr_info_; + /* For display only. */ VkSurfaceKHR surface_; VkSwapchainKHR swapchain_; diff --git a/intern/ghost/intern/GHOST_SystemWin32.cc b/intern/ghost/intern/GHOST_SystemWin32.cc index 59e39d03efe..2a527915d75 100644 --- a/intern/ghost/intern/GHOST_SystemWin32.cc +++ b/intern/ghost/intern/GHOST_SystemWin32.cc @@ -1434,6 +1434,8 @@ GHOST_Event *GHOST_SystemWin32::processWindowSizeEvent(GHOST_WindowWin32 *window system->dispatchEvents(); return nullptr; } + + window->updateHDRInfo(); return sizeEvent; } @@ -2159,6 +2161,9 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, uint msg, WPARAM wParam, if (LOWORD(wParam) == WA_INACTIVE) { window->lostMouseCapture(); } + else { + window->updateHDRInfo(); + } lResult = ::DefWindowProc(hwnd, msg, wParam, lParam); break; @@ -2176,6 +2181,7 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, uint msg, WPARAM wParam, } case WM_EXITSIZEMOVE: { window->in_live_resize_ = 0; + window->updateHDRInfo(); break; } case WM_PAINT: { @@ -2240,6 +2246,7 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, uint msg, WPARAM wParam, } else { event = processWindowEvent(GHOST_kEventWindowMove, window); + window->updateHDRInfo(); } break; @@ -2275,6 +2282,7 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, uint msg, WPARAM wParam, if (wt) { wt->remapCoordinates(); } + window->updateHDRInfo(); break; } case WM_KILLFOCUS: { @@ -2293,6 +2301,7 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, uint msg, WPARAM wParam, { window->ThemeRefresh(); } + window->updateHDRInfo(); break; } /* ====================== diff --git a/intern/ghost/intern/GHOST_WindowWin32.cc b/intern/ghost/intern/GHOST_WindowWin32.cc index 9a72007d791..1b92e7d0a4c 100644 --- a/intern/ghost/intern/GHOST_WindowWin32.cc +++ b/intern/ghost/intern/GHOST_WindowWin32.cc @@ -218,6 +218,9 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system, /* Initialize Direct Manipulation. */ direct_manipulation_helper_ = GHOST_DirectManipulationHelper::create(h_wnd_, getDPIHint()); + + /* Initialize HDR info. */ + updateHDRInfo(); } void GHOST_WindowWin32::updateDirectManipulation() @@ -619,7 +622,7 @@ GHOST_Context *GHOST_WindowWin32::newDrawingContext(GHOST_TDrawingContextType ty #ifdef WITH_VULKAN_BACKEND case GHOST_kDrawingContextTypeVulkan: { GHOST_Context *context = new GHOST_ContextVK( - want_context_params_, h_wnd_, 1, 2, preferred_device_); + want_context_params_, h_wnd_, 1, 2, preferred_device_, &hdr_info_); if (context->initializeDrawingContext()) { return context; } @@ -1262,3 +1265,87 @@ void GHOST_WindowWin32::unregisterWindowAppUserModelProperties() pstore->Release(); } } + +/* We call this from a few window event changes, but actually none of them immediately + * respond to SDR white level changes in the system. That requires using the WinRT API, + * which we don't do so far. */ +void GHOST_WindowWin32::updateHDRInfo() +{ + /* Get monitor from window. */ + HMONITOR hmonitor = ::MonitorFromWindow(h_wnd_, MONITOR_DEFAULTTONEAREST); + if (!hmonitor) { + return; + } + + MONITORINFOEXW monitor_info = {}; + monitor_info.cbSize = sizeof(MONITORINFOEXW); + if (!::GetMonitorInfoW(hmonitor, &monitor_info)) { + return; + } + + /* Get active display paths and modes. */ + UINT32 path_count = 0; + UINT32 mode_count = 0; + if (::GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &path_count, &mode_count) != + ERROR_SUCCESS) + { + return; + } + + std::vector paths(path_count); + std::vector modes(mode_count); + if (::QueryDisplayConfig( + QDC_ONLY_ACTIVE_PATHS, &path_count, paths.data(), &mode_count, modes.data(), nullptr) != + ERROR_SUCCESS) + { + return; + } + + GHOST_WindowHDRInfo info = GHOST_WINDOW_HDR_INFO_NONE; + + /* Find the display path matching the monitor. */ + for (const DISPLAYCONFIG_PATH_INFO &path : paths) { + DISPLAYCONFIG_SOURCE_DEVICE_NAME device_name = {}; + device_name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME; + device_name.header.size = sizeof(device_name); + device_name.header.adapterId = path.sourceInfo.adapterId; + device_name.header.id = path.sourceInfo.id; + + if (::DisplayConfigGetDeviceInfo(&device_name.header) != ERROR_SUCCESS) { + continue; + } + if (wcscmp(monitor_info.szDevice, device_name.viewGdiDeviceName) != 0) { + continue; + } + + /* Query HDR status. */ + DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO color_info = {}; + color_info.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO; + color_info.header.size = sizeof(color_info); + color_info.header.adapterId = path.targetInfo.adapterId; + color_info.header.id = path.targetInfo.id; + + if (::DisplayConfigGetDeviceInfo(&color_info.header) == ERROR_SUCCESS) { + info.hdr_enabled = color_info.advancedColorSupported && color_info.advancedColorEnabled; + } + + if (info.hdr_enabled) { + /* Query SDR white level. */ + DISPLAYCONFIG_SDR_WHITE_LEVEL white_level = {}; + white_level.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL; + white_level.header.size = sizeof(white_level); + white_level.header.adapterId = path.targetInfo.adapterId; + white_level.header.id = path.targetInfo.id; + + if (::DisplayConfigGetDeviceInfo(&white_level.header) == ERROR_SUCCESS) { + if (white_level.SDRWhiteLevel > 0) { + /* Windows assumes 1.0 = 80 nits, so multipley by that to get the absolute + * value in nits if we need it in the future. */ + info.sdr_white_level = static_cast(white_level.SDRWhiteLevel) / 1000.0f; + } + } + } + + hdr_info_ = info; + } +} diff --git a/intern/ghost/intern/GHOST_WindowWin32.hh b/intern/ghost/intern/GHOST_WindowWin32.hh index 73c3dd3be54..624515fe1ff 100644 --- a/intern/ghost/intern/GHOST_WindowWin32.hh +++ b/intern/ghost/intern/GHOST_WindowWin32.hh @@ -326,6 +326,9 @@ class GHOST_WindowWin32 : public GHOST_Window { GHOST_TTrackpadInfo getTrackpadInfo(); + /* Update HDR info on initialization and window changes. */ + void updateHDRInfo(); + private: /** * \param type: The type of rendering context create. @@ -412,6 +415,8 @@ 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_; diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index ade8f3a911d..5805a826b2d 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -29,6 +29,7 @@ set(INC ../draw/intern ../draw/intern/shaders metal/kernels + vulkan/shaders shaders/infos @@ -674,6 +675,10 @@ set(MSL_SRC shaders/metal/mtl_shader_common.msl ) +set(VULKAN_BACKEND_GLSL_SRC + vulkan/shaders/vk_backbuffer_blit_comp.glsl +) + if(WITH_GTESTS) if(WITH_GPU_BACKEND_TESTS) list(APPEND GLSL_SRC ${GLSL_SRC_TEST}) @@ -689,6 +694,10 @@ if(WITH_METAL_BACKEND) endforeach() endif() +if(WITH_VULKAN_BACKEND) + list(APPEND GLSL_SRC ${VULKAN_BACKEND_GLSL_SRC}) +endif() + set(GLSL_C) foreach(GLSL_FILE ${GLSL_SRC}) glsl_to_c(${GLSL_FILE} GLSL_C) diff --git a/source/blender/gpu/intern/gpu_shader_create_info_list.hh b/source/blender/gpu/intern/gpu_shader_create_info_list.hh index 935d4e9be6d..2d748207da1 100644 --- a/source/blender/gpu/intern/gpu_shader_create_info_list.hh +++ b/source/blender/gpu/intern/gpu_shader_create_info_list.hh @@ -50,6 +50,10 @@ # include "gpu_shader_fullscreen_blit_info.hh" #endif +#ifdef WITH_VULKAN_BACKEND +# include "vk_backbuffer_blit_info.hh" +#endif + /* Compositor. */ #include "compositor_alpha_crop_info.hh" #include "compositor_bilateral_blur_info.hh" diff --git a/source/blender/gpu/shaders/CMakeLists.txt b/source/blender/gpu/shaders/CMakeLists.txt index afc99ea987d..ab7ec16c3bd 100644 --- a/source/blender/gpu/shaders/CMakeLists.txt +++ b/source/blender/gpu/shaders/CMakeLists.txt @@ -83,6 +83,14 @@ set(SRC_GLSL_COMP # gpu_shader_index_2d_array_tris.glsl ) +set(SRC_VULKAN_GLSL_COMP + vk_backbuffer_blit_comp.glsl +) + +if(WITH_VULKAN_BACKEND) + list(APPEND SRC_GLSL_COMP ${SRC_VULKAN_GLSL_COMP}) +endif() + set(SRC_GLSL_LIB common/gpu_shader_print_lib.glsl ) diff --git a/source/blender/gpu/vulkan/shaders/vk_backbuffer_blit_comp.glsl b/source/blender/gpu/vulkan/shaders/vk_backbuffer_blit_comp.glsl new file mode 100644 index 00000000000..c5c589041d8 --- /dev/null +++ b/source/blender/gpu/vulkan/shaders/vk_backbuffer_blit_comp.glsl @@ -0,0 +1,25 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "vk_backbuffer_blit_info.hh" + +COMPUTE_SHADER_CREATE_INFO(vk_backbuffer_blit) + +void main() +{ + ivec2 dst_texel = ivec2(gl_GlobalInvocationID.xy); + ivec2 src_size = ivec2(imageSize(src_img)); + ivec2 src_texel = ivec2(dst_texel.x, src_size.y - dst_texel.y - 1); + vec4 color = imageLoad(src_img, ivec2(src_texel)); + /* + * Convert from extended sRGB non-linear to linear. + * + * Preserves negative wide gamut values with sign/abs. + * Gamma 2.2 is used instead of the sRGB piecewise transfer function, because + * most SDR sRGB displays decode with gamma 2.2, and that's what we are trying + * to match. + */ + color.rgb = sign(color.rgb) * pow(abs(color.rgb), vec3(2.2f)) * sdr_scale; + imageStore(dst_img, ivec2(dst_texel), color); +} diff --git a/source/blender/gpu/vulkan/shaders/vk_backbuffer_blit_info.hh b/source/blender/gpu/vulkan/shaders/vk_backbuffer_blit_info.hh new file mode 100644 index 00000000000..844a0b7495c --- /dev/null +++ b/source/blender/gpu/vulkan/shaders/vk_backbuffer_blit_info.hh @@ -0,0 +1,15 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "gpu_shader_create_info.hh" + +GPU_SHADER_CREATE_INFO(vk_backbuffer_blit) +LOCAL_GROUP_SIZE(16, 16) +IMAGE(0, SFLOAT_16_16_16_16, read, Float2D, src_img) +IMAGE(1, SFLOAT_16_16_16_16, write, Float2D, dst_img) +PUSH_CONSTANT(float, sdr_scale) +COMPUTE_SOURCE("vk_backbuffer_blit_comp.glsl") +ADDITIONAL_INFO(gpu_srgb_to_framebuffer_space) +DO_STATIC_COMPILATION() +GPU_SHADER_CREATE_END() diff --git a/source/blender/gpu/vulkan/vk_context.cc b/source/blender/gpu/vulkan/vk_context.cc index f7b8b6f4bdb..e6abd814e21 100644 --- a/source/blender/gpu/vulkan/vk_context.cc +++ b/source/blender/gpu/vulkan/vk_context.cc @@ -89,7 +89,7 @@ void VKContext::sync_backbuffer(bool cycle_resource_pool) swap_chain_data.extent.height, 1, to_gpu_format(swap_chain_data.surface_format.format), - GPU_TEXTURE_USAGE_ATTACHMENT, + GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_SHADER_READ, nullptr); back_left->attachment_set(GPU_FB_COLOR_ATTACHMENT0, @@ -102,7 +102,7 @@ void VKContext::sync_backbuffer(bool cycle_resource_pool) swap_chain_format_ = swap_chain_data.surface_format; GCaps.hdr_viewport_support = (swap_chain_format_.format == VK_FORMAT_R16G16B16A16_SFLOAT) && ELEM(swap_chain_format_.colorSpace, - VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT, + VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR); } } @@ -372,6 +372,8 @@ void VKContext::swap_buffers_post_callback() void VKContext::swap_buffers_pre_handler(const GHOST_VulkanSwapChainData &swap_chain_data) { const bool do_blit_to_swapchain = swap_chain_data.image != VK_NULL_HANDLE; + const bool use_shader = swap_chain_data.surface_format.colorSpace == + VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT; /* When swapchain is invalid/minimized we only flush the render graph to free GPU resources. */ if (!do_blit_to_swapchain) { @@ -387,28 +389,43 @@ void VKContext::swap_buffers_pre_handler(const GHOST_VulkanSwapChainData &swap_c device.resources.add_image(swap_chain_data.image, 1, "SwapchainImage"); GPU_debug_group_begin("BackBuffer.Blit"); + if (use_shader) { + VKTexture swap_chain_texture("swap_chain_texture"); + swap_chain_texture.init_swapchain(swap_chain_data.image, + to_gpu_format(swap_chain_data.surface_format.format)); + Shader *shader = device.vk_backbuffer_blit_sh_get(); + GPU_shader_bind(shader); + GPU_shader_uniform_1f(shader, "sdr_scale", swap_chain_data.sdr_scale); + VKStateManager &state_manager = state_manager_get(); + state_manager.image_bind(color_attachment, 0); + state_manager.image_bind(&swap_chain_texture, 1); + int2 dispatch_size = math::divide_ceil( + int2(swap_chain_data.extent.width, swap_chain_data.extent.height), int2(16)); + VKBackend::get().compute_dispatch(UNPACK2(dispatch_size), 1); + } + else { + render_graph::VKBlitImageNode::CreateInfo blit_image = {}; + blit_image.src_image = color_attachment->vk_image_handle(); + blit_image.dst_image = swap_chain_data.image; + blit_image.filter = VK_FILTER_LINEAR; - render_graph::VKBlitImageNode::CreateInfo blit_image = {}; - blit_image.src_image = color_attachment->vk_image_handle(); - blit_image.dst_image = swap_chain_data.image; - blit_image.filter = VK_FILTER_LINEAR; + VkImageBlit ®ion = blit_image.region; + region.srcOffsets[0] = {0, 0, 0}; + region.srcOffsets[1] = {color_attachment->width_get(), color_attachment->height_get(), 1}; + region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.srcSubresource.mipLevel = 0; + region.srcSubresource.baseArrayLayer = 0; + region.srcSubresource.layerCount = 1; - VkImageBlit ®ion = blit_image.region; - region.srcOffsets[0] = {0, 0, 0}; - region.srcOffsets[1] = {color_attachment->width_get(), color_attachment->height_get(), 1}; - region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - region.srcSubresource.mipLevel = 0; - region.srcSubresource.baseArrayLayer = 0; - region.srcSubresource.layerCount = 1; + region.dstOffsets[0] = {0, int32_t(swap_chain_data.extent.height), 0}; + region.dstOffsets[1] = {int32_t(swap_chain_data.extent.width), 0, 1}; + region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.dstSubresource.mipLevel = 0; + region.dstSubresource.baseArrayLayer = 0; + region.dstSubresource.layerCount = 1; - region.dstOffsets[0] = {0, int32_t(swap_chain_data.extent.height), 0}; - region.dstOffsets[1] = {int32_t(swap_chain_data.extent.width), 0, 1}; - region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - region.dstSubresource.mipLevel = 0; - region.dstSubresource.baseArrayLayer = 0; - region.dstSubresource.layerCount = 1; - - render_graph.add_node(blit_image); + render_graph.add_node(blit_image); + } render_graph::VKSynchronizationNode::CreateInfo synchronization = {}; synchronization.vk_image = swap_chain_data.image; diff --git a/source/blender/gpu/vulkan/vk_device.cc b/source/blender/gpu/vulkan/vk_device.cc index 3014a1902ab..c0aa2581e36 100644 --- a/source/blender/gpu/vulkan/vk_device.cc +++ b/source/blender/gpu/vulkan/vk_device.cc @@ -75,6 +75,7 @@ void VKDevice::deinit() dummy_buffer.free(); samplers_.free(); + GPU_SHADER_FREE_SAFE(vk_backbuffer_blit_sh_); { while (!thread_data_.is_empty()) { diff --git a/source/blender/gpu/vulkan/vk_device.hh b/source/blender/gpu/vulkan/vk_device.hh index 02ab4cfe8d9..b6be136f722 100644 --- a/source/blender/gpu/vulkan/vk_device.hh +++ b/source/blender/gpu/vulkan/vk_device.hh @@ -237,6 +237,8 @@ class VKDevice : public NonCopyable { std::string glsl_comp_patch_; Vector thread_data_; + Shader *vk_backbuffer_blit_sh_ = nullptr; + public: render_graph::VKResourceStateTracker resources; VKDiscardPool orphaned_data; @@ -479,6 +481,14 @@ class VKDevice : public NonCopyable { /** \} */ + Shader *vk_backbuffer_blit_sh_get() + { + if (vk_backbuffer_blit_sh_ == nullptr) { + vk_backbuffer_blit_sh_ = GPU_shader_create_from_info_name("vk_backbuffer_blit"); + } + return vk_backbuffer_blit_sh_; + } + private: void init_physical_device_properties(); void init_physical_device_memory_properties(); diff --git a/source/blender/gpu/vulkan/vk_texture.cc b/source/blender/gpu/vulkan/vk_texture.cc index 3364a82165d..34fa6764fc5 100644 --- a/source/blender/gpu/vulkan/vk_texture.cc +++ b/source/blender/gpu/vulkan/vk_texture.cc @@ -480,6 +480,15 @@ bool VKTexture::init_internal(gpu::Texture *src, return true; } +void VKTexture::init_swapchain(VkImage vk_image, TextureFormat format) +{ + device_format_ = format_ = format; + format_flag_ = to_format_flag(format); + vk_image_ = vk_image; + type_ = GPU_TEXTURE_2D; + usage_set(GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_SHADER_WRITE); +} + bool VKTexture::is_texture_view() const { return source_texture_ != nullptr; diff --git a/source/blender/gpu/vulkan/vk_texture.hh b/source/blender/gpu/vulkan/vk_texture.hh index ae2a3e10aaa..467866cfc95 100644 --- a/source/blender/gpu/vulkan/vk_texture.hh +++ b/source/blender/gpu/vulkan/vk_texture.hh @@ -30,6 +30,7 @@ ENUM_OPERATORS(VKImageViewFlags, VKImageViewFlags::NO_SWIZZLING) class VKTexture : public Texture { friend class VKDescriptorSetUpdator; + friend class VKContext; /** * Texture format how the texture is stored on the device. @@ -148,6 +149,8 @@ class VKTexture : public Texture { int mip_offset, int layer_offset, bool use_stencil) override; + /* Initialize VKTexture with a swapchain image. */ + void init_swapchain(VkImage vk_image, TextureFormat gpu_format); private: /** Is this texture a view of another texture. */