Skip to content

Commit 3d641ef

Browse files
committed
VIDEO/VULKAN: Fix black GPU screenshots when HDR is enabled
When 'Screenshot: Use GPU' was enabled with the Vulkan driver and HDR active, the resulting PNG was entirely black on both scRGB (FP16) and HDR10 (RGB10A2 PQ) swapchains. Two compounding issues in the HDR readback path: 1. gfx/drivers/vulkan.c hardcoded hdr_mode = 0 on the hdr_to_sdr pipeline call. In vulkan_run_hdr_pipeline() that falls into the legacy HDR10 branch (InverseTonemap = 1, HDR10 = 1). With an scRGB backbuffer the shader then ran HDR10ToLinear() — ST.2084 decode — on values that were never PQ-encoded, collapsing output to ~0. Pass the actual encoding instead: 2 for scRGB, 1 for HDR10. Also add an explicit COLOR_ATTACHMENT_WRITE -> TRANSFER_READ memory barrier between the tonemap render pass and vkCmdCopyImageToBuffer. The readback render pass has dependencyCount = 0, so its implicit finalLayout transition does not carry a memory-availability guarantee on its own. 2. gfx/drivers/vulkan_shaders/hdr_tonemap.frag called Tonemap(linear) with one argument while the helper in hdr_common.glsl takes three (hdr_linear, max_nits, paper_white_nits). The checked-in hdr_tonemap.frag.inc was therefore stale SPIR-V from an older single-argument signature, so the HDR10 readback path was also broken independently of the scRGB issue. Rewrite the shader to branch on HDRMode: - Mode 1 (HDR10 PQ): HDR10ToLinear -> Tonemap() back to SDR using paper_white_nits for both arguments (matching the forward pass). - Mode 2 (scRGB): divide by (BrightnessNits / 80) to undo the scRGB nit scaling so SDR paper-white maps back to 1.0. Apply the sRGB OETF before writing in both cases — the readback target is B8G8R8A8_UNORM, not _SRGB, so without encoding the saved PNG would be too dark in any standard sRGB viewer. Regenerate hdr_tonemap.frag.inc from the new source. Fixes #14147
1 parent 9e20d7e commit 3d641ef

3 files changed

Lines changed: 520 additions & 363 deletions

File tree

gfx/drivers/vulkan.c

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6901,6 +6901,15 @@ static bool vulkan_frame(void *data, const void *frame,
69016901
struct vk_image* readback_source = backbuffer;
69026902
if((vk->context->flags & VK_CTX_FLAG_HDR_ENABLE))
69036903
{
6904+
/* Select the inverse conversion the shader should apply.
6905+
* The backbuffer is in one of two HDR encodings:
6906+
* - scRGB (FP16 linear, 1.0 = 80 nits) -> pass mode 2
6907+
* - HDR10 (RGB10A2 PQ, BT.2020) -> pass mode 1
6908+
* The readback target is B8G8R8A8_UNORM, so the shader
6909+
* converts back to SDR and writes sRGB-encoded bytes.
6910+
* Modes here mirror the forward pipeline naming. */
6911+
unsigned readback_hdr_mode =
6912+
(vk->context->flags & VK_CTX_FLAG_HDR_SCRGB) ? 2 : 1;
69046913
{
69056914
/* Prepare backbuffer for reading */
69066915
backbuffer_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
@@ -6910,7 +6919,26 @@ static bool vulkan_frame(void *data, const void *frame,
69106919
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
69116920
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
69126921
}
6913-
vulkan_run_hdr_pipeline(vk->pipelines.hdr_to_sdr, vk->readback_render_pass, readback_source, &vk->readback_image, vk, &vk->hdr.ubo, 0, false);
6922+
vulkan_run_hdr_pipeline(vk->pipelines.hdr_to_sdr, vk->readback_render_pass, readback_source, &vk->readback_image, vk, &vk->hdr.ubo, readback_hdr_mode, false);
6923+
6924+
/* The readback render pass declares its finalLayout as
6925+
* TRANSFER_SRC_OPTIMAL, but with no subpass dependency it
6926+
* does not provide a memory availability guarantee against
6927+
* the subsequent vkCmdCopyImageToBuffer in vulkan_readback().
6928+
* Insert an explicit barrier so color writes from the
6929+
* tonemap pass are visible to the transfer read. */
6930+
{
6931+
VkMemoryBarrier mem_barrier;
6932+
mem_barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
6933+
mem_barrier.pNext = NULL;
6934+
mem_barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
6935+
mem_barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
6936+
vkCmdPipelineBarrier(vk->cmd,
6937+
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
6938+
VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
6939+
1, &mem_barrier, 0, NULL, 0, NULL);
6940+
}
6941+
69146942
readback_source = &vk->readback_image;
69156943
}
69166944
#endif /* VULKAN_HDR_SWAPCHAIN */

gfx/drivers/vulkan_shaders/hdr_tonemap.frag

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,54 @@ layout(set = 0, binding = 2) uniform highp sampler2D Source;
77

88
#include "hdr_common.glsl"
99

10+
/* sRGB electrical-optical transfer function (linear -> sRGB encoded bytes).
11+
* Needed because the readback render target is B8G8R8A8_UNORM (linear),
12+
* so we must apply the sRGB OETF ourselves for the saved PNG to look
13+
* correct in any standard sRGB viewer. */
14+
vec3 LinearToSRGB(vec3 c)
15+
{
16+
vec3 clamped = clamp(c, vec3(0.0), vec3(1.0));
17+
vec3 lo = clamped * 12.92;
18+
vec3 hi = 1.055 * pow(clamped, vec3(1.0 / 2.4)) - 0.055;
19+
bvec3 cutoff = lessThanEqual(clamped, vec3(0.0031308));
20+
return mix(hi, lo, vec3(cutoff));
21+
}
22+
23+
/* Mode tag passed from vulkan_run_hdr_pipeline() for the readback pipeline:
24+
* 1 = backbuffer is HDR10 PQ (RGB10A2, BT.2020, ST.2084 encoded)
25+
* 2 = backbuffer is scRGB (FP16, BT.709 linear, 1.0 == 80 nits)
26+
* Any other value falls through to a passthrough path (e.g. if the
27+
* backbuffer is already SDR). */
28+
1029
void main()
1130
{
1231
vec4 source = texture(Source, vTexCoord);
13-
vec3 hdr = source.rgb;
14-
vec3 linear = hdr;
15-
if (global.HDR10 > 0.0f)
32+
vec3 sdr_linear;
33+
34+
if (global.HDRMode == 1u)
35+
{
36+
/* HDR10 PQ: decode PQ -> linear BT.709, then inverse-of-inverse-tonemap
37+
* back down to SDR linear using the same paper_white the forward pass used. */
38+
vec3 hdr_linear = HDR10ToLinear(source.rgb);
39+
sdr_linear = Tonemap(hdr_linear,
40+
global.BrightnessNits,
41+
global.BrightnessNits);
42+
}
43+
else if (global.HDRMode == 2u)
1644
{
17-
/* Backbuffer is in HDR10, need to convert to linear */
18-
linear = HDR10ToLinear(hdr);
45+
/* scRGB: already linear BT.709, but scaled such that 1.0 = 80 nits
46+
* and the SDR UI sits at paper_white_nits. Undo that scaling so SDR
47+
* paper white maps back to 1.0. scRGB can carry negative / >1 values
48+
* for out-of-gamut / super-white content; LinearToSRGB will clamp. */
49+
sdr_linear = source.rgb * (kscRGBWhiteNits / global.BrightnessNits);
1950
}
20-
vec3 sdr = hdr;
21-
if (global.InverseTonemap > 0.0f)
51+
else
2252
{
23-
/* Backbuffer is inverse tonemapped, need to tonemap back */
24-
sdr = Tonemap(linear);
53+
/* Passthrough: backbuffer is already SDR linear (shouldn't happen on
54+
* the HDR readback path, but keeps the shader well-defined). */
55+
sdr_linear = source.rgb;
2556
}
26-
FragColor = vec4(sdr, source.a);
57+
58+
/* B8G8R8A8_UNORM target — apply sRGB OETF so the PNG looks right. */
59+
FragColor = vec4(LinearToSRGB(sdr_linear), 1.0);
2760
}

0 commit comments

Comments
 (0)