video_core: Add sRGB to D24S8 depth-stencil conversion support

Implements conversion from sRGB color formats to D24S8 depth-stencil format
in the Vulkan renderer. This change includes:

- New fragment shader convert_abgr8_srgb_to_d24s8.frag that handles proper
  sRGB to linear conversion before depth calculation
- Added shader to CMake build system
- Extended BlitImageHelper with new conversion pipeline and methods
- Updated texture cache to handle sRGB to D24S8 format conversion paths

The conversion properly handles sRGB color space by first converting to
linear space before calculating luminance values for the depth component,
while preserving alpha channel data for the stencil component.
This commit is contained in:
Zephyron 2025-02-01 16:57:49 +10:00
parent 4e8d00f034
commit 8f76ef2579
No known key found for this signature in database
GPG key ID: 2177ADED8AC966AF
5 changed files with 132 additions and 12 deletions

View file

@ -1,4 +1,5 @@
# SPDX-FileCopyrightText: 2018 yuzu Emulator Project # SPDX-FileCopyrightText: 2018 yuzu Emulator Project
# SPDX-FileCopyrightText: 2025 citron Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
set(FIDELITYFX_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/externals/FidelityFX-FSR/ffx-fsr) set(FIDELITYFX_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/externals/FidelityFX-FSR/ffx-fsr)
@ -18,6 +19,7 @@ set(SHADER_FILES
blit_color_float.frag blit_color_float.frag
block_linear_unswizzle_2d.comp block_linear_unswizzle_2d.comp
block_linear_unswizzle_3d.comp block_linear_unswizzle_3d.comp
convert_abgr8_srgb_to_d24s8.frag
convert_abgr8_to_d24s8.frag convert_abgr8_to_d24s8.frag
convert_abgr8_to_d32f.frag convert_abgr8_to_d32f.frag
convert_d32f_to_abgr8.frag convert_d32f_to_abgr8.frag

View file

@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#version 450
#extension GL_ARB_shader_stencil_export : require
layout(binding = 0) uniform sampler2D color_texture;
// Efficient sRGB to linear conversion
float srgbToLinear(float srgb) {
return srgb <= 0.04045 ?
srgb / 12.92 :
pow((srgb + 0.055) / 1.055, 2.4);
}
void main() {
ivec2 coord = ivec2(gl_FragCoord.xy);
vec4 srgbColor = texelFetch(color_texture, coord, 0);
// Convert RGB components to linear space
vec3 linearColor = vec3(
srgbToLinear(srgbColor.r),
srgbToLinear(srgbColor.g),
srgbToLinear(srgbColor.b)
);
// Calculate luminance using standard coefficients
float luminance = dot(linearColor, vec3(0.2126, 0.7152, 0.0722));
// Convert to 24-bit depth value
uint depth_val = uint(luminance * float(0xFFFFFF));
// Extract 8-bit stencil from alpha
uint stencil_val = uint(srgbColor.a * 255.0);
// Pack values efficiently
uint depth_stencil = (stencil_val << 24) | (depth_val & 0x00FFFFFF);
gl_FragDepth = float(depth_val) / float(0xFFFFFF);
gl_FragStencilRefARB = int(stencil_val);
}

View file

@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm> #include <algorithm>
@ -28,6 +29,7 @@
#include "video_core/surface.h" #include "video_core/surface.h"
#include "video_core/vulkan_common/vulkan_device.h" #include "video_core/vulkan_common/vulkan_device.h"
#include "video_core/vulkan_common/vulkan_wrapper.h" #include "video_core/vulkan_common/vulkan_wrapper.h"
#include "video_core/host_shaders/convert_abgr8_srgb_to_d24s8_frag_spv.h"
namespace Vulkan { namespace Vulkan {
@ -439,6 +441,7 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_,
convert_d32f_to_abgr8_frag(BuildShader(device, CONVERT_D32F_TO_ABGR8_FRAG_SPV)), convert_d32f_to_abgr8_frag(BuildShader(device, CONVERT_D32F_TO_ABGR8_FRAG_SPV)),
convert_d24s8_to_abgr8_frag(BuildShader(device, CONVERT_D24S8_TO_ABGR8_FRAG_SPV)), convert_d24s8_to_abgr8_frag(BuildShader(device, CONVERT_D24S8_TO_ABGR8_FRAG_SPV)),
convert_s8d24_to_abgr8_frag(BuildShader(device, CONVERT_S8D24_TO_ABGR8_FRAG_SPV)), convert_s8d24_to_abgr8_frag(BuildShader(device, CONVERT_S8D24_TO_ABGR8_FRAG_SPV)),
convert_abgr8_srgb_to_d24s8_frag(BuildShader(device, CONVERT_ABGR8_SRGB_TO_D24S8_FRAG_SPV)),
linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_LINEAR>)), linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_LINEAR>)),
nearest_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_NEAREST>)) {} nearest_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_NEAREST>)) {}
@ -589,6 +592,14 @@ void BlitImageHelper::ConvertS8D24ToABGR8(const Framebuffer* dst_framebuffer,
ConvertDepthStencil(*convert_s8d24_to_abgr8_pipeline, dst_framebuffer, src_image_view); ConvertDepthStencil(*convert_s8d24_to_abgr8_pipeline, dst_framebuffer, src_image_view);
} }
void BlitImageHelper::ConvertABGR8SRGBToD24S8(const Framebuffer* dst_framebuffer,
const ImageView& src_image_view) {
ConvertPipelineDepthTargetEx(convert_abgr8_srgb_to_d24s8_pipeline,
dst_framebuffer->RenderPass(),
convert_abgr8_srgb_to_d24s8_frag);
Convert(*convert_abgr8_srgb_to_d24s8_pipeline, dst_framebuffer, src_image_view);
}
void BlitImageHelper::ClearColor(const Framebuffer* dst_framebuffer, u8 color_mask, void BlitImageHelper::ClearColor(const Framebuffer* dst_framebuffer, u8 color_mask,
const std::array<f32, 4>& clear_color, const std::array<f32, 4>& clear_color,
const Region2D& dst_region) { const Region2D& dst_region) {
@ -919,13 +930,11 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearStencilPipeline(
return *clear_stencil_pipelines.back(); return *clear_stencil_pipelines.back();
} }
void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass, void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass) {
bool is_target_depth) {
if (pipeline) { if (pipeline) {
return; return;
} }
VkShaderModule frag_shader = VkShaderModule frag_shader = *convert_float_to_depth_frag;
is_target_depth ? *convert_float_to_depth_frag : *convert_depth_to_float_frag;
const std::array stages = MakeStages(*full_screen_vert, frag_shader); const std::array stages = MakeStages(*full_screen_vert, frag_shader);
pipeline = device.GetLogical().CreateGraphicsPipeline({ pipeline = device.GetLogical().CreateGraphicsPipeline({
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
@ -939,9 +948,8 @@ void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass rende
.pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
.pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
.pDepthStencilState = is_target_depth ? &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO : nullptr, .pDepthStencilState = &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
.pColorBlendState = is_target_depth ? &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO,
: &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO,
.pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO,
.layout = *one_texture_pipeline_layout, .layout = *one_texture_pipeline_layout,
.renderPass = renderpass, .renderPass = renderpass,
@ -951,12 +959,33 @@ void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass rende
}); });
} }
void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass) {
ConvertPipeline(pipeline, renderpass, false);
}
void BlitImageHelper::ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass) { void BlitImageHelper::ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass) {
ConvertPipeline(pipeline, renderpass, true); if (pipeline) {
return;
}
VkShaderModule frag_shader = *convert_depth_to_float_frag;
const std::array stages = MakeStages(*full_screen_vert, frag_shader);
pipeline = device.GetLogical().CreateGraphicsPipeline({
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.stageCount = static_cast<u32>(stages.size()),
.pStages = stages.data(),
.pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
.pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
.pTessellationState = nullptr,
.pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
.pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
.pDepthStencilState = &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
.pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO,
.pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO,
.layout = *one_texture_pipeline_layout,
.renderPass = renderpass,
.subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = 0,
});
} }
void BlitImageHelper::ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass renderpass, void BlitImageHelper::ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass renderpass,
@ -999,4 +1028,36 @@ void BlitImageHelper::ConvertPipelineDepthTargetEx(vk::Pipeline& pipeline, VkRen
ConvertPipelineEx(pipeline, renderpass, module, true, true); ConvertPipelineEx(pipeline, renderpass, module, true, true);
} }
void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass,
bool is_target_depth) {
if (pipeline) {
return;
}
VkShaderModule frag_shader =
is_target_depth ? *convert_float_to_depth_frag : *convert_depth_to_float_frag;
const std::array stages = MakeStages(*full_screen_vert, frag_shader);
pipeline = device.GetLogical().CreateGraphicsPipeline({
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.stageCount = static_cast<u32>(stages.size()),
.pStages = stages.data(),
.pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
.pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
.pTessellationState = nullptr,
.pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
.pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
.pDepthStencilState = is_target_depth ? &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO : nullptr,
.pColorBlendState = is_target_depth ? &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO
: &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO,
.pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO,
.layout = *one_texture_pipeline_layout,
.renderPass = renderpass,
.subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = 0,
});
}
} // namespace Vulkan } // namespace Vulkan

View file

@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
@ -67,6 +68,8 @@ public:
void ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer, const ImageView& src_image_view); void ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
void ConvertABGR8SRGBToD24S8(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
void ConvertABGR8ToD32F(const Framebuffer* dst_framebuffer, const ImageView& src_image_view); void ConvertABGR8ToD32F(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
void ConvertD32FToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view); void ConvertD32FToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
@ -136,6 +139,7 @@ private:
vk::ShaderModule convert_d32f_to_abgr8_frag; vk::ShaderModule convert_d32f_to_abgr8_frag;
vk::ShaderModule convert_d24s8_to_abgr8_frag; vk::ShaderModule convert_d24s8_to_abgr8_frag;
vk::ShaderModule convert_s8d24_to_abgr8_frag; vk::ShaderModule convert_s8d24_to_abgr8_frag;
vk::ShaderModule convert_abgr8_srgb_to_d24s8_frag;
vk::Sampler linear_sampler; vk::Sampler linear_sampler;
vk::Sampler nearest_sampler; vk::Sampler nearest_sampler;
@ -156,6 +160,7 @@ private:
vk::Pipeline convert_d32f_to_abgr8_pipeline; vk::Pipeline convert_d32f_to_abgr8_pipeline;
vk::Pipeline convert_d24s8_to_abgr8_pipeline; vk::Pipeline convert_d24s8_to_abgr8_pipeline;
vk::Pipeline convert_s8d24_to_abgr8_pipeline; vk::Pipeline convert_s8d24_to_abgr8_pipeline;
vk::Pipeline convert_abgr8_srgb_to_d24s8_pipeline;
}; };
} // namespace Vulkan } // namespace Vulkan

View file

@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm> #include <algorithm>
@ -1247,6 +1248,16 @@ void TextureCacheRuntime::ConvertImage(Framebuffer* dst, ImageView& dst_view, Im
return blit_image_helper.ConvertR32ToD32(dst, src_view); return blit_image_helper.ConvertR32ToD32(dst, src_view);
} }
break; break;
case PixelFormat::D24_UNORM_S8_UINT:
if (src_view.format == PixelFormat::A8B8G8R8_UNORM ||
src_view.format == PixelFormat::B8G8R8A8_UNORM) {
return blit_image_helper.ConvertABGR8ToD24S8(dst, src_view);
}
if (src_view.format == PixelFormat::A8B8G8R8_SRGB ||
src_view.format == PixelFormat::B8G8R8A8_SRGB) {
return blit_image_helper.ConvertABGR8SRGBToD24S8(dst, src_view);
}
break;
default: default:
break; break;
} }