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 <brecht@blender.org> Pull Request: https://projects.blender.org/blender/blender/pulls/144717
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
15
source/blender/gpu/vulkan/shaders/vk_backbuffer_blit_info.hh
Normal file
15
source/blender/gpu/vulkan/shaders/vk_backbuffer_blit_info.hh
Normal file
@@ -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()
|
||||
@@ -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;
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -237,6 +237,8 @@ class VKDevice : public NonCopyable {
|
||||
std::string glsl_comp_patch_;
|
||||
Vector<VKThreadData *> 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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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. */
|
||||
|
||||
Reference in New Issue
Block a user