texture_cache: Implement color to multisampled depth blit pass (#3103)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / windows-qt (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / macos-qt (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-qt (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / linux-qt-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions

This commit is contained in:
TheTurtle 2025-06-16 14:39:46 +03:00 committed by GitHub
parent d0e2a40cdc
commit c27f45c8c0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 355 additions and 9 deletions

View file

@ -952,6 +952,10 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp
src/video_core/renderer_vulkan/host_passes/fsr_pass.h
src/video_core/renderer_vulkan/host_passes/pp_pass.cpp
src/video_core/renderer_vulkan/host_passes/pp_pass.h
src/video_core/texture_cache/blit_helper.cpp
src/video_core/texture_cache/blit_helper.h
src/video_core/texture_cache/host_compatibility.cpp
src/video_core/texture_cache/host_compatibility.h
src/video_core/texture_cache/image.cpp
src/video_core/texture_cache/image.h
src/video_core/texture_cache/image_info.cpp
@ -965,8 +969,6 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp
src/video_core/texture_cache/tile_manager.cpp
src/video_core/texture_cache/tile_manager.h
src/video_core/texture_cache/types.h
src/video_core/texture_cache/host_compatibility.cpp
src/video_core/texture_cache/host_compatibility.h
src/video_core/page_manager.cpp
src/video_core/page_manager.h
src/video_core/multi_level_page_table.h

View file

@ -11,6 +11,7 @@ set(SHADER_FILES
detilers/micro_32bpp.comp
detilers/micro_64bpp.comp
detilers/micro_8bpp.comp
color_to_ms_depth.frag
fault_buffer_process.comp
fs_tri.vert
fsr.comp

View file

@ -0,0 +1,15 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#version 450 core
#extension GL_EXT_samplerless_texture_functions : require
layout (binding = 0, set = 0) uniform texture2D color;
layout (location = 0) in vec2 uv;
void main()
{
ivec2 coord = ivec2(uv * vec2(textureSize(color, 0).xy));
gl_FragDepth = texelFetch(color, coord, 0)[gl_SampleID];
}

View file

@ -328,6 +328,7 @@ public:
return render_state;
}
/// Returns the current pipeline dynamic state tracking.
DynamicState& GetDynamicState() {
return dynamic_state;
}

View file

@ -0,0 +1,256 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "video_core/renderer_vulkan/vk_instance.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_shader_util.h"
#include "video_core/texture_cache/blit_helper.h"
#include "video_core/texture_cache/image.h"
#include "video_core/host_shaders/color_to_ms_depth_frag.h"
#include "video_core/host_shaders/fs_tri_vert.h"
namespace VideoCore {
static vk::SampleCountFlagBits ToSampleCount(u32 num_samples) {
switch (num_samples) {
case 1:
return vk::SampleCountFlagBits::e1;
case 2:
return vk::SampleCountFlagBits::e2;
case 4:
return vk::SampleCountFlagBits::e4;
case 8:
return vk::SampleCountFlagBits::e8;
case 16:
return vk::SampleCountFlagBits::e16;
default:
UNREACHABLE_MSG("Unknown samples count = {}", num_samples);
}
}
BlitHelper::BlitHelper(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_)
: instance{instance_}, scheduler{scheduler_} {
CreateShaders();
CreatePipelineLayouts();
}
BlitHelper::~BlitHelper() = default;
void BlitHelper::BlitColorToMsDepth(Image& source, Image& dest) {
source.Transit(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eShaderRead, {});
dest.Transit(vk::ImageLayout::eDepthAttachmentOptimal,
vk::AccessFlagBits2::eDepthStencilAttachmentWrite, {});
const vk::ImageViewUsageCreateInfo color_usage_ci{.usage = vk::ImageUsageFlagBits::eSampled};
const vk::ImageViewCreateInfo color_view_ci = {
.pNext = &color_usage_ci,
.image = source.image,
.viewType = vk::ImageViewType::e2D,
.format = source.info.pixel_format,
.subresourceRange{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0U,
.levelCount = 1U,
.baseArrayLayer = 0U,
.layerCount = 1U,
},
};
const auto [color_view_result, color_view] =
instance.GetDevice().createImageView(color_view_ci);
ASSERT_MSG(color_view_result == vk::Result::eSuccess, "Failed to create image view: {}",
vk::to_string(color_view_result));
const vk::ImageViewUsageCreateInfo depth_usage_ci{
.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment};
const vk::ImageViewCreateInfo depth_view_ci = {
.pNext = &depth_usage_ci,
.image = dest.image,
.viewType = vk::ImageViewType::e2D,
.format = dest.info.pixel_format,
.subresourceRange{
.aspectMask = vk::ImageAspectFlagBits::eDepth,
.baseMipLevel = 0U,
.levelCount = 1U,
.baseArrayLayer = 0U,
.layerCount = 1U,
},
};
const auto [depth_view_result, depth_view] =
instance.GetDevice().createImageView(depth_view_ci);
ASSERT_MSG(depth_view_result == vk::Result::eSuccess, "Failed to create image view: {}",
vk::to_string(depth_view_result));
scheduler.DeferOperation([device = instance.GetDevice(), color_view, depth_view] {
device.destroyImageView(color_view);
device.destroyImageView(depth_view);
});
Vulkan::RenderState state{};
state.has_depth = true;
state.width = dest.info.size.width;
state.height = dest.info.size.height;
state.depth_attachment = vk::RenderingAttachmentInfo{
.imageView = depth_view,
.imageLayout = vk::ImageLayout::eDepthAttachmentOptimal,
.loadOp = vk::AttachmentLoadOp::eDontCare,
.storeOp = vk::AttachmentStoreOp::eStore,
.clearValue = vk::ClearValue{.depthStencil = {.depth = 0.f}},
};
scheduler.BeginRendering(state);
const auto cmdbuf = scheduler.CommandBuffer();
const vk::DescriptorImageInfo image_info = {
.sampler = VK_NULL_HANDLE,
.imageView = color_view,
.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal,
};
const vk::WriteDescriptorSet texture_write = {
.dstSet = VK_NULL_HANDLE,
.dstBinding = 0U,
.dstArrayElement = 0U,
.descriptorCount = 1U,
.descriptorType = vk::DescriptorType::eSampledImage,
.pImageInfo = &image_info,
};
cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eGraphics, *single_texture_pl_layout, 0U,
texture_write);
const DepthPipelineKey key{dest.info.num_samples, dest.info.pixel_format};
const vk::Pipeline depth_pipeline = GetDepthToMsPipeline(key);
cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, depth_pipeline);
const vk::Viewport viewport = {
.x = 0,
.y = 0,
.width = float(state.width),
.height = float(state.height),
.minDepth = 0.f,
.maxDepth = 1.f,
};
cmdbuf.setViewport(0, viewport);
const vk::Rect2D scissor = {
.offset = {0, 0},
.extent = {state.width, state.height},
};
cmdbuf.setScissor(0, scissor);
cmdbuf.draw(3, 1, 0, 0);
scheduler.GetDynamicState().Invalidate();
}
vk::Pipeline BlitHelper::GetDepthToMsPipeline(const DepthPipelineKey& key) {
auto it = std::ranges::find(color_to_ms_depth_pl, key, &DepthPipeline::first);
if (it != color_to_ms_depth_pl.end()) {
return *it->second;
}
CreateColorToMSDepthPipeline(key);
return *color_to_ms_depth_pl.back().second;
}
void BlitHelper::CreateShaders() {
fs_tri_vertex = Vulkan::Compile(HostShaders::FS_TRI_VERT, vk::ShaderStageFlagBits::eVertex,
instance.GetDevice());
color_to_ms_depth_frag =
Vulkan::Compile(HostShaders::COLOR_TO_MS_DEPTH_FRAG, vk::ShaderStageFlagBits::eFragment,
instance.GetDevice());
}
void BlitHelper::CreatePipelineLayouts() {
const vk::DescriptorSetLayoutBinding texture_binding = {
.binding = 0,
.descriptorType = vk::DescriptorType::eSampledImage,
.descriptorCount = 1,
.stageFlags = vk::ShaderStageFlagBits::eFragment,
};
const vk::DescriptorSetLayoutCreateInfo desc_layout_ci = {
.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR,
.bindingCount = 1U,
.pBindings = &texture_binding,
};
auto [desc_layout_result, desc_layout] =
instance.GetDevice().createDescriptorSetLayoutUnique(desc_layout_ci);
single_texture_descriptor_set_layout = std::move(desc_layout);
const vk::DescriptorSetLayout set_layout = *single_texture_descriptor_set_layout;
const vk::PipelineLayoutCreateInfo layout_info = {
.setLayoutCount = 1U,
.pSetLayouts = &set_layout,
.pushConstantRangeCount = 0U,
.pPushConstantRanges = nullptr,
};
auto [layout_result, pipeline_layout] =
instance.GetDevice().createPipelineLayoutUnique(layout_info);
ASSERT_MSG(layout_result == vk::Result::eSuccess,
"Failed to create graphics pipeline layout: {}", vk::to_string(layout_result));
Vulkan::SetObjectName(instance.GetDevice(), *pipeline_layout, "Single texture pipeline layout");
single_texture_pl_layout = std::move(pipeline_layout);
}
void BlitHelper::CreateColorToMSDepthPipeline(const DepthPipelineKey& key) {
const vk::PipelineInputAssemblyStateCreateInfo input_assembly = {
.topology = vk::PrimitiveTopology::eTriangleList,
};
const vk::PipelineMultisampleStateCreateInfo multisampling = {
.rasterizationSamples = ToSampleCount(key.num_samples),
};
const vk::PipelineDepthStencilStateCreateInfo depth_state = {
.depthTestEnable = true,
.depthWriteEnable = true,
.depthCompareOp = vk::CompareOp::eAlways,
};
const std::array dynamic_states = {vk::DynamicState::eViewportWithCount,
vk::DynamicState::eScissorWithCount};
const vk::PipelineDynamicStateCreateInfo dynamic_info = {
.dynamicStateCount = static_cast<u32>(dynamic_states.size()),
.pDynamicStates = dynamic_states.data(),
};
std::array<vk::PipelineShaderStageCreateInfo, 2> shader_stages;
shader_stages[0] = {
.stage = vk::ShaderStageFlagBits::eVertex,
.module = fs_tri_vertex,
.pName = "main",
};
shader_stages[1] = {
.stage = vk::ShaderStageFlagBits::eFragment,
.module = color_to_ms_depth_frag,
.pName = "main",
};
const vk::PipelineRenderingCreateInfo pipeline_rendering_ci = {
.colorAttachmentCount = 0U,
.pColorAttachmentFormats = nullptr,
.depthAttachmentFormat = key.depth_format,
.stencilAttachmentFormat = vk::Format::eUndefined,
};
const vk::PipelineColorBlendStateCreateInfo color_blending{};
const vk::PipelineViewportStateCreateInfo viewport_info{};
const vk::PipelineVertexInputStateCreateInfo vertex_input_info{};
const vk::PipelineRasterizationStateCreateInfo raster_state{.lineWidth = 1.f};
const vk::GraphicsPipelineCreateInfo pipeline_info = {
.pNext = &pipeline_rendering_ci,
.stageCount = static_cast<u32>(shader_stages.size()),
.pStages = shader_stages.data(),
.pVertexInputState = &vertex_input_info,
.pInputAssemblyState = &input_assembly,
.pViewportState = &viewport_info,
.pRasterizationState = &raster_state,
.pMultisampleState = &multisampling,
.pDepthStencilState = &depth_state,
.pColorBlendState = &color_blending,
.pDynamicState = &dynamic_info,
.layout = *single_texture_pl_layout,
};
auto [pipeline_result, pipeline] =
instance.GetDevice().createGraphicsPipelineUnique(VK_NULL_HANDLE, pipeline_info);
ASSERT_MSG(pipeline_result == vk::Result::eSuccess, "Failed to create graphics pipeline: {}",
vk::to_string(pipeline_result));
Vulkan::SetObjectName(instance.GetDevice(), *pipeline, "Color to MS Depth {}", key.num_samples);
color_to_ms_depth_pl.emplace_back(key, std::move(pipeline));
}
} // namespace VideoCore

View file

@ -0,0 +1,55 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <tsl/robin_map.h>
#include "common/types.h"
#include "video_core/renderer_vulkan/vk_common.h"
namespace Vulkan {
class Instance;
class Scheduler;
} // namespace Vulkan
namespace VideoCore {
class Image;
class ImageView;
class BlitHelper {
static constexpr size_t MaxMsPipelines = 6;
public:
explicit BlitHelper(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler);
~BlitHelper();
void BlitColorToMsDepth(Image& source, Image& dest);
private:
void CreateShaders();
void CreatePipelineLayouts();
struct DepthPipelineKey {
u32 num_samples;
vk::Format depth_format;
auto operator<=>(const DepthPipelineKey&) const noexcept = default;
};
vk::Pipeline GetDepthToMsPipeline(const DepthPipelineKey& key);
void CreateColorToMSDepthPipeline(const DepthPipelineKey& key);
private:
const Vulkan::Instance& instance;
Vulkan::Scheduler& scheduler;
vk::UniqueDescriptorSetLayout single_texture_descriptor_set_layout;
vk::UniquePipelineLayout single_texture_pl_layout;
vk::ShaderModule fs_tri_vertex;
vk::ShaderModule color_to_ms_depth_frag;
using DepthPipeline = std::pair<DepthPipelineKey, vk::UniquePipeline>;
std::vector<DepthPipeline> color_to_ms_depth_pl{};
};
} // namespace VideoCore

View file

@ -47,6 +47,7 @@ struct ImageInfo {
VAddr cmask_addr;
VAddr fmask_addr;
VAddr htile_addr;
u32 htile_clear_mask{u32(-1)};
} meta_info{};
struct {

View file

@ -22,7 +22,7 @@ static constexpr u64 NumFramesBeforeRemoval = 32;
TextureCache::TextureCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_,
BufferCache& buffer_cache_, PageManager& tracker_)
: instance{instance_}, scheduler{scheduler_}, buffer_cache{buffer_cache_}, tracker{tracker_},
tile_manager{instance, scheduler} {
blit_helper{instance, scheduler}, tile_manager{instance, scheduler} {
// Create basic null image at fixed image ID.
const auto null_id = GetNullImage(vk::Format::eR8G8B8A8Unorm);
ASSERT(null_id.index == NULL_IMAGE_ID.index);
@ -177,10 +177,20 @@ ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested_info, Bindi
auto& new_image = slot_images[new_image_id];
new_image.usage = cache_image.usage;
new_image.flags &= ~ImageFlagBits::Dirty;
// When creating a depth buffer through overlap resolution don't clear it on first use.
new_image.info.meta_info.htile_clear_mask = 0;
if (cache_image.info.num_samples == 1 && new_info.num_samples == 1) {
// Perform depth<->color copy using the intermediate copy buffer.
const auto& copy_buffer = buffer_cache.GetUtilityBuffer(MemoryUsage::DeviceLocal);
new_image.CopyImageWithBuffer(cache_image, copy_buffer.Handle(), 0);
} else if (cache_image.info.num_samples == 1 && new_info.IsDepthStencil() &&
new_info.num_samples > 1) {
// Perform a rendering pass to transfer the channels of source as samples in dest.
blit_helper.BlitColorToMsDepth(cache_image, new_image);
} else {
LOG_WARNING(Render_Vulkan, "Unimplemented depth overlap copy");
}
// Free the cache image.
FreeImage(cache_image_id);
@ -202,7 +212,8 @@ std::tuple<ImageId, int, int> TextureCache::ResolveOverlap(const ImageInfo& imag
if (image_info.guest_address == tex_cache_image.info.guest_address) { // Equal address
if (image_info.BlockDim() != tex_cache_image.info.BlockDim() ||
image_info.num_bits != tex_cache_image.info.num_bits) {
image_info.num_bits * image_info.num_samples !=
tex_cache_image.info.num_bits * tex_cache_image.info.num_samples) {
// Very likely this kind of overlap is caused by allocation from a pool.
if (safe_to_delete) {
FreeImage(cache_image_id);
@ -470,8 +481,10 @@ ImageView& TextureCache::FindDepthTarget(BaseDesc& desc) {
// Register meta data for this depth buffer
if (!(image.flags & ImageFlagBits::MetaRegistered)) {
if (desc.info.meta_info.htile_addr) {
surface_metas.emplace(desc.info.meta_info.htile_addr,
MetaDataInfo{.type = MetaDataInfo::Type::HTile});
surface_metas.emplace(
desc.info.meta_info.htile_addr,
MetaDataInfo{.type = MetaDataInfo::Type::HTile,
.clear_mask = image.info.meta_info.htile_clear_mask});
image.info.meta_info.htile_addr = desc.info.meta_info.htile_addr;
image.flags |= ImageFlagBits::MetaRegistered;
}

View file

@ -9,6 +9,7 @@
#include "common/slot_vector.h"
#include "video_core/amdgpu/resource.h"
#include "video_core/multi_level_page_table.h"
#include "video_core/texture_cache/blit_helper.h"
#include "video_core/texture_cache/image.h"
#include "video_core/texture_cache/image_view.h"
#include "video_core/texture_cache/sampler.h"
@ -286,6 +287,7 @@ private:
Vulkan::Scheduler& scheduler;
BufferCache& buffer_cache;
PageManager& tracker;
BlitHelper blit_helper;
TileManager tile_manager;
Common::SlotVector<Image> slot_images;
Common::SlotVector<ImageView> slot_image_views;