Image binding and texture cache interface refactor (1/2) (#1481)

* video_core: texture_cache: interface refactor and better overlap handling

* resources binding moved into vk_rasterizer

* remove `virtual` flag leftover
This commit is contained in:
psucien 2024-11-24 17:07:51 +01:00 committed by GitHub
parent 16e1d679dc
commit 3d95ad0e3a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 911 additions and 679 deletions

View file

@ -61,6 +61,15 @@ bool ImageInfo::IsDepthStencil() const {
}
}
bool ImageInfo::HasStencil() const {
if (pixel_format == vk::Format::eD32SfloatS8Uint ||
pixel_format == vk::Format::eD24UnormS8Uint ||
pixel_format == vk::Format::eD16UnormS8Uint) {
return true;
}
return false;
}
static vk::ImageUsageFlags ImageUsageFlags(const ImageInfo& info) {
vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eTransferSrc |
vk::ImageUsageFlagBits::eTransferDst |
@ -143,14 +152,17 @@ Image::Image(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_,
// the texture cache should re-create the resource with the usage requested
vk::ImageCreateFlags flags{vk::ImageCreateFlagBits::eMutableFormat |
vk::ImageCreateFlagBits::eExtendedUsage};
if (info.props.is_cube || (info.type == vk::ImageType::e2D && info.resources.layers >= 6)) {
const bool can_be_cube = (info.type == vk::ImageType::e2D) &&
(info.resources.layers % 6 == 0) &&
(info.size.width == info.size.height);
if (info.props.is_cube || can_be_cube) {
flags |= vk::ImageCreateFlagBits::eCubeCompatible;
} else if (info.props.is_volume) {
flags |= vk::ImageCreateFlagBits::e2DArrayCompatible;
}
usage = ImageUsageFlags(info);
format_features = FormatFeatureFlags(usage);
usage_flags = ImageUsageFlags(info);
format_features = FormatFeatureFlags(usage_flags);
switch (info.pixel_format) {
case vk::Format::eD16Unorm:
@ -170,7 +182,7 @@ Image::Image(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_,
constexpr auto tiling = vk::ImageTiling::eOptimal;
const auto supported_format = instance->GetSupportedFormat(info.pixel_format, format_features);
const auto properties = instance->GetPhysicalDevice().getImageFormatProperties(
supported_format, info.type, tiling, usage, flags);
supported_format, info.type, tiling, usage_flags, flags);
const auto supported_samples = properties.result == vk::Result::eSuccess
? properties.value.sampleCounts
: vk::SampleCountFlagBits::e1;
@ -188,7 +200,7 @@ Image::Image(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_,
.arrayLayers = static_cast<u32>(info.resources.layers),
.samples = LiverpoolToVK::NumSamples(info.num_samples, supported_samples),
.tiling = tiling,
.usage = usage,
.usage = usage_flags,
.initialLayout = vk::ImageLayout::eUndefined,
};

View file

@ -30,8 +30,6 @@ enum ImageFlagBits : u32 {
Registered = 1 << 6, ///< True when the image is registered
Picked = 1 << 7, ///< Temporary flag to mark the image as picked
MetaRegistered = 1 << 8, ///< True when metadata for this surface is known and registered
Bound = 1 << 9, ///< True when the image is bound to a descriptor set
NeedsRebind = 1 << 10, ///< True when the image needs to be rebound
};
DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits)
@ -113,7 +111,15 @@ struct Image {
std::vector<ImageViewId> image_view_ids;
// Resource state tracking
vk::ImageUsageFlags usage;
struct {
u32 texture : 1;
u32 storage : 1;
u32 render_target : 1;
u32 depth_target : 1;
u32 stencil : 1;
u32 vo_surface : 1;
} usage{};
vk::ImageUsageFlags usage_flags;
vk::FormatFeatureFlags2 format_features;
struct State {
vk::Flags<vk::PipelineStageFlagBits2> pl_stage = vk::PipelineStageFlagBits2::eAllCommands;
@ -124,6 +130,22 @@ struct Image {
std::vector<State> subresource_states{};
boost::container::small_vector<u64, 14> mip_hashes{};
u64 tick_accessed_last{0};
struct {
union {
struct {
u32 is_bound : 1; // the image is bound to a descriptor set
u32 is_target : 1; // the image is bound as color/depth target
u32 needs_rebind : 1; // the image needs to be rebound
u32 force_general : 1; // the image needs to be used in general layout
};
u32 raw{};
};
void Reset() {
raw = 0u;
}
} binding{};
};
} // namespace VideoCore

View file

@ -245,7 +245,6 @@ ImageInfo::ImageInfo(const Libraries::VideoOut::BufferAttributeGroup& group,
size.width = attrib.width;
size.height = attrib.height;
pitch = attrib.tiling_mode == TilingMode::Linear ? size.width : (size.width + 127) & (~127);
usage.vo_buffer = true;
num_bits = attrib.pixel_format != VideoOutFormat::A16R16G16B16Float ? 32 : 64;
ASSERT(num_bits == 32);
@ -277,7 +276,6 @@ ImageInfo::ImageInfo(const AmdGpu::Liverpool::ColorBuffer& buffer,
resources.layers = buffer.NumSlices();
meta_info.cmask_addr = buffer.info.fast_clear ? buffer.CmaskAddress() : 0;
meta_info.fmask_addr = buffer.info.compression ? buffer.FmaskAddress() : 0;
usage.render_target = true;
guest_address = buffer.Address();
const auto color_slice_sz = buffer.GetColorSliceSize();
@ -299,9 +297,6 @@ ImageInfo::ImageInfo(const AmdGpu::Liverpool::DepthBuffer& buffer, u32 num_slice
pitch = buffer.Pitch();
resources.layers = num_slices;
meta_info.htile_addr = buffer.z_info.tile_surface_en ? htile_address : 0;
usage.depth_target = true;
usage.stencil =
buffer.stencil_info.format != AmdGpu::Liverpool::DepthBuffer::StencilFormat::Invalid;
guest_address = buffer.Address();
const auto depth_slice_sz = buffer.GetDepthSliceSize();
@ -330,7 +325,6 @@ ImageInfo::ImageInfo(const AmdGpu::Image& image, const Shader::ImageResource& de
resources.layers = image.NumLayers(desc.is_array);
num_samples = image.NumSamples();
num_bits = NumBits(image.GetDataFmt());
usage.texture = true;
guest_address = image.Address();
@ -392,7 +386,6 @@ void ImageInfo::UpdateSize() {
}
}
mip_info.size *= mip_d;
mip_info.offset = guest_size_bytes;
mips_layout.emplace_back(mip_info);
guest_size_bytes += mip_info.size;
@ -400,79 +393,87 @@ void ImageInfo::UpdateSize() {
guest_size_bytes *= resources.layers;
}
bool ImageInfo::IsMipOf(const ImageInfo& info) const {
int ImageInfo::IsMipOf(const ImageInfo& info) const {
if (!IsCompatible(info)) {
return false;
return -1;
}
if (IsTilingCompatible(info.tiling_idx, tiling_idx)) {
return -1;
}
// Currently we expect only on level to be copied.
if (resources.levels != 1) {
return false;
return -1;
}
const int mip = info.resources.levels - resources.levels;
if (mip < 1) {
return false;
if (info.mips_layout.empty()) {
UNREACHABLE();
}
// Find mip
auto mip = -1;
for (auto m = 0; m < info.mips_layout.size(); ++m) {
if (guest_address == (info.guest_address + info.mips_layout[m].offset)) {
mip = m;
break;
}
}
if (mip < 0) {
return -1;
}
ASSERT(mip != 0);
const auto mip_w = std::max(info.size.width >> mip, 1u);
const auto mip_h = std::max(info.size.height >> mip, 1u);
if ((size.width != mip_w) || (size.height != mip_h)) {
return false;
return -1;
}
const auto mip_d = std::max(info.size.depth >> mip, 1u);
if (info.type == vk::ImageType::e3D && type == vk::ImageType::e2D) {
// In case of 2D array to 3D copy, make sure we have proper number of layers.
if (resources.layers != mip_d) {
return false;
return -1;
}
} else {
if (type != info.type) {
return false;
return -1;
}
}
// Check if the mip has correct size.
if (info.mips_layout.size() <= mip || info.mips_layout[mip].size != guest_size_bytes) {
return false;
}
// Ensure that address matches too.
if ((info.guest_address + info.mips_layout[mip].offset) != guest_address) {
return false;
}
return true;
return mip;
}
bool ImageInfo::IsSliceOf(const ImageInfo& info) const {
int ImageInfo::IsSliceOf(const ImageInfo& info) const {
if (!IsCompatible(info)) {
return false;
return -1;
}
// Array slices should be of the same type.
if (type != info.type) {
return false;
return -1;
}
// 2D dimensions of both images should be the same.
if ((size.width != info.size.width) || (size.height != info.size.height)) {
return false;
return -1;
}
// Check for size alignment.
const bool slice_size = info.guest_size_bytes / info.resources.layers;
if (guest_size_bytes % slice_size != 0) {
return false;
return -1;
}
// Ensure that address is aligned too.
if (((info.guest_address - guest_address) % guest_size_bytes) != 0) {
return false;
const auto addr_diff = guest_address - info.guest_address;
if ((addr_diff % guest_size_bytes) != 0) {
return -1;
}
return true;
return addr_diff / guest_size_bytes;
}
} // namespace VideoCore

View file

@ -28,14 +28,28 @@ struct ImageInfo {
bool IsBlockCoded() const;
bool IsPacked() const;
bool IsDepthStencil() const;
bool HasStencil() const;
bool IsMipOf(const ImageInfo& info) const;
bool IsSliceOf(const ImageInfo& info) const;
int IsMipOf(const ImageInfo& info) const;
int IsSliceOf(const ImageInfo& info) const;
/// Verifies if images are compatible for subresource merging.
bool IsCompatible(const ImageInfo& info) const {
return (pixel_format == info.pixel_format && tiling_idx == info.tiling_idx &&
num_samples == info.num_samples && num_bits == info.num_bits);
return (pixel_format == info.pixel_format && num_samples == info.num_samples &&
num_bits == info.num_bits);
}
bool IsTilingCompatible(u32 lhs, u32 rhs) const {
if (lhs == rhs) {
return true;
}
if (lhs == 0x0e && rhs == 0x0d) {
return true;
}
if (lhs == 0x0d && rhs == 0x0e) {
return true;
}
return false;
}
void UpdateSize();
@ -46,15 +60,6 @@ struct ImageInfo {
VAddr htile_addr;
} meta_info{};
struct {
u32 texture : 1;
u32 storage : 1;
u32 render_target : 1;
u32 depth_target : 1;
u32 stencil : 1;
u32 vo_buffer : 1;
} usage{}; // Usage data tracked during image lifetime
struct {
u32 is_cube : 1;
u32 is_volume : 1;
@ -81,6 +86,9 @@ struct ImageInfo {
VAddr guest_address{0};
u32 guest_size_bytes{0};
u32 tiling_idx{0}; // TODO: merge with existing!
VAddr stencil_addr{0};
u32 stencil_size{0};
};
} // namespace VideoCore

View file

@ -149,7 +149,7 @@ ImageViewInfo::ImageViewInfo(const AmdGpu::Liverpool::DepthBuffer& depth_buffer,
ImageView::ImageView(const Vulkan::Instance& instance, const ImageViewInfo& info_, Image& image,
ImageId image_id_)
: image_id{image_id_}, info{info_} {
vk::ImageViewUsageCreateInfo usage_ci{.usage = image.usage};
vk::ImageViewUsageCreateInfo usage_ci{.usage = image.usage_flags};
if (!info.is_storage) {
usage_ci.usage &= ~vk::ImageUsageFlagBits::eStorage;
}

View file

@ -77,84 +77,149 @@ void TextureCache::UnmapMemory(VAddr cpu_addr, size_t size) {
}
}
ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested_info, ImageId cache_image_id) {
const auto& cache_info = slot_images[cache_image_id].info;
ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested_info, BindingType binding,
ImageId cache_image_id) {
const auto& cache_image = slot_images[cache_image_id];
const bool was_bound_as_texture =
!cache_info.usage.depth_target && (cache_info.usage.texture || cache_info.usage.storage);
if (requested_info.usage.depth_target && was_bound_as_texture) {
auto new_image_id = slot_images.insert(instance, scheduler, requested_info);
if (!cache_image.info.IsDepthStencil() && !requested_info.IsDepthStencil()) {
return {};
}
const bool stencil_match = requested_info.HasStencil() == cache_image.info.HasStencil();
const bool bpp_match = requested_info.num_bits == cache_image.info.num_bits;
// If an image in the cache has less slices we need to expand it
bool recreate = cache_image.info.resources < requested_info.resources;
switch (binding) {
case BindingType::Texture:
// The guest requires a depth sampled texture, but cache can offer only Rxf. Need to
// recreate the image.
recreate |= requested_info.IsDepthStencil() && !cache_image.info.IsDepthStencil();
break;
case BindingType::Storage:
// If the guest is going to use previously created depth as storage, the image needs to be
// recreated. (TODO: Probably a case with linear rgba8 aliasing is legit)
recreate |= cache_image.info.IsDepthStencil();
break;
case BindingType::RenderTarget:
// Render target can have only Rxf format. If the cache contains only Dx[S8] we need to
// re-create the image.
ASSERT(!requested_info.IsDepthStencil());
recreate |= cache_image.info.IsDepthStencil();
break;
case BindingType::DepthTarget:
// The guest has requested previously allocated texture to be bound as a depth target.
// In this case we need to convert Rx float to a Dx[S8] as requested
recreate |= !cache_image.info.IsDepthStencil();
// The guest is trying to bind a depth target and cache has it. Need to be sure that aspects
// and bpp match
recreate |= cache_image.info.IsDepthStencil() && !(stencil_match && bpp_match);
break;
default:
break;
}
if (recreate) {
auto new_info{requested_info};
new_info.resources = std::max(requested_info.resources, cache_image.info.resources);
new_info.UpdateSize();
const auto new_image_id = slot_images.insert(instance, scheduler, new_info);
RegisterImage(new_image_id);
// Inherit image usage
auto& new_image = GetImage(new_image_id);
new_image.usage = cache_image.usage;
// TODO: perform a depth copy here
FreeImage(cache_image_id);
return new_image_id;
}
const bool should_bind_as_texture =
!requested_info.usage.depth_target &&
(requested_info.usage.texture || requested_info.usage.storage);
if (cache_info.usage.depth_target && should_bind_as_texture) {
if (cache_info.resources == requested_info.resources) {
return cache_image_id;
} else {
// UNREACHABLE();
}
}
return {};
// Will be handled by view
return cache_image_id;
}
ImageId TextureCache::ResolveOverlap(const ImageInfo& image_info, ImageId cache_image_id,
ImageId merged_image_id) {
std::tuple<ImageId, int, int> TextureCache::ResolveOverlap(const ImageInfo& image_info,
BindingType binding,
ImageId cache_image_id,
ImageId merged_image_id) {
auto& tex_cache_image = slot_images[cache_image_id];
// We can assume it is safe to delete the image if it wasn't accessed in some number of frames.
const bool safe_to_delete =
scheduler.CurrentTick() - tex_cache_image.tick_accessed_last > NumFramesBeforeRemoval;
if (image_info.guest_address == tex_cache_image.info.guest_address) { // Equal address
if (image_info.size != tex_cache_image.info.size) {
// Very likely this kind of overlap is caused by allocation from a pool. We can assume
// it is safe to delete the image if it wasn't accessed in some amount of frames.
if (scheduler.CurrentTick() - tex_cache_image.tick_accessed_last >
NumFramesBeforeRemoval) {
// Very likely this kind of overlap is caused by allocation from a pool.
if (safe_to_delete) {
FreeImage(cache_image_id);
}
return merged_image_id;
return {merged_image_id, -1, -1};
}
if (auto depth_image_id = ResolveDepthOverlap(image_info, cache_image_id)) {
return depth_image_id;
if (const auto depth_image_id = ResolveDepthOverlap(image_info, binding, cache_image_id)) {
return {depth_image_id, -1, -1};
}
if (image_info.pixel_format != tex_cache_image.info.pixel_format ||
image_info.guest_size_bytes <= tex_cache_image.info.guest_size_bytes) {
auto result_id = merged_image_id ? merged_image_id : cache_image_id;
const auto& result_image = slot_images[result_id];
return IsVulkanFormatCompatible(image_info.pixel_format, result_image.info.pixel_format)
? result_id
: ImageId{};
return {
IsVulkanFormatCompatible(image_info.pixel_format, result_image.info.pixel_format)
? result_id
: ImageId{},
-1, -1};
}
ImageId new_image_id{};
if (image_info.type == tex_cache_image.info.type) {
ASSERT(image_info.resources > tex_cache_image.info.resources);
new_image_id = ExpandImage(image_info, cache_image_id);
} else {
UNREACHABLE();
}
return new_image_id;
return {new_image_id, -1, -1};
}
// Right overlap, the image requested is a possible subresource of the image from cache.
if (image_info.guest_address > tex_cache_image.info.guest_address) {
// Should be handled by view. No additional actions needed.
if (auto mip = image_info.IsMipOf(tex_cache_image.info); mip >= 0) {
return {cache_image_id, mip, -1};
}
if (auto slice = image_info.IsSliceOf(tex_cache_image.info); slice >= 0) {
return {cache_image_id, -1, slice};
}
// TODO: slice and mip
if (safe_to_delete) {
FreeImage(cache_image_id);
}
return {{}, -1, -1};
} else {
// Left overlap, the image from cache is a possible subresource of the image requested
if (!merged_image_id) {
// We need to have a larger, already allocated image to copy this one into
return {};
return {{}, -1, -1};
}
if (tex_cache_image.info.IsMipOf(image_info)) {
if (auto mip = tex_cache_image.info.IsMipOf(image_info); mip >= 0) {
if (tex_cache_image.binding.is_target) {
// We have a larger image created and a separate one, representing a subres of it,
// bound as render target. In this case we need to rebind render target.
tex_cache_image.binding.needs_rebind = 1u;
GetImage(merged_image_id).binding.is_target = 1u;
FreeImage(cache_image_id);
return {merged_image_id, -1, -1};
}
tex_cache_image.Transit(vk::ImageLayout::eTransferSrcOptimal,
vk::AccessFlagBits2::eTransferRead, {});
@ -162,13 +227,13 @@ ImageId TextureCache::ResolveOverlap(const ImageInfo& image_info, ImageId cache_
ASSERT(num_mips_to_copy == 1);
auto& merged_image = slot_images[merged_image_id];
merged_image.CopyMip(tex_cache_image, image_info.resources.levels - 1);
merged_image.CopyMip(tex_cache_image, mip);
FreeImage(cache_image_id);
}
}
return merged_image_id;
return {merged_image_id, -1, -1};
}
ImageId TextureCache::ExpandImage(const ImageInfo& info, ImageId image_id) {
@ -181,8 +246,8 @@ ImageId TextureCache::ExpandImage(const ImageInfo& info, ImageId image_id) {
src_image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {});
new_image.CopyImage(src_image);
if (True(src_image.flags & ImageFlagBits::Bound)) {
src_image.flags |= ImageFlagBits::NeedsRebind;
if (src_image.binding.is_bound || src_image.binding.is_target) {
src_image.binding.needs_rebind = 1u;
}
FreeImage(image_id);
@ -192,9 +257,11 @@ ImageId TextureCache::ExpandImage(const ImageInfo& info, ImageId image_id) {
return new_image_id;
}
ImageId TextureCache::FindImage(const ImageInfo& info, FindFlags flags) {
ImageId TextureCache::FindImage(BaseDesc& desc, FindFlags flags) {
const auto& info = desc.info;
if (info.guest_address == 0) [[unlikely]] {
return NULL_IMAGE_VIEW_ID;
return NULL_IMAGE_ID;
}
std::scoped_lock lock{mutex};
@ -231,10 +298,16 @@ ImageId TextureCache::FindImage(const ImageInfo& info, FindFlags flags) {
}
// Try to resolve overlaps (if any)
int view_mip{-1};
int view_slice{-1};
if (!image_id) {
for (const auto& cache_id : image_ids) {
view_mip = -1;
view_slice = -1;
const auto& merged_info = image_id ? slot_images[image_id].info : info;
image_id = ResolveOverlap(merged_info, cache_id, image_id);
std::tie(image_id, view_mip, view_slice) =
ResolveOverlap(merged_info, desc.type, cache_id, image_id);
}
}
@ -254,6 +327,10 @@ ImageId TextureCache::FindImage(const ImageInfo& info, FindFlags flags) {
RegisterImage(image_id);
}
if (view_mip > 0) {
desc.view_info.range.base.level = view_mip;
}
Image& image = slot_images[image_id];
image.tick_accessed_last = scheduler.CurrentTick();
@ -275,100 +352,58 @@ ImageView& TextureCache::RegisterImageView(ImageId image_id, const ImageViewInfo
ImageView& TextureCache::FindTexture(ImageId image_id, const ImageViewInfo& view_info) {
Image& image = slot_images[image_id];
UpdateImage(image_id);
auto& usage = image.info.usage;
if (view_info.is_storage) {
image.Transit(vk::ImageLayout::eGeneral,
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eShaderWrite,
view_info.range);
usage.storage = true;
} else {
const auto new_layout = image.info.IsDepthStencil()
? vk::ImageLayout::eDepthStencilReadOnlyOptimal
: vk::ImageLayout::eShaderReadOnlyOptimal;
image.Transit(new_layout, vk::AccessFlagBits2::eShaderRead, view_info.range);
usage.texture = true;
}
return RegisterImageView(image_id, view_info);
}
ImageView& TextureCache::FindRenderTarget(const ImageInfo& image_info,
const ImageViewInfo& view_info) {
const ImageId image_id = FindImage(image_info);
ImageView& TextureCache::FindRenderTarget(BaseDesc& desc) {
const ImageId image_id = FindImage(desc);
Image& image = slot_images[image_id];
image.flags |= ImageFlagBits::GpuModified;
image.usage.render_target = 1u;
UpdateImage(image_id);
image.Transit(vk::ImageLayout::eColorAttachmentOptimal,
vk::AccessFlagBits2::eColorAttachmentWrite |
vk::AccessFlagBits2::eColorAttachmentRead,
view_info.range);
// Register meta data for this color buffer
if (!(image.flags & ImageFlagBits::MetaRegistered)) {
if (image_info.meta_info.cmask_addr) {
if (desc.info.meta_info.cmask_addr) {
surface_metas.emplace(
image_info.meta_info.cmask_addr,
desc.info.meta_info.cmask_addr,
MetaDataInfo{.type = MetaDataInfo::Type::CMask, .is_cleared = true});
image.info.meta_info.cmask_addr = image_info.meta_info.cmask_addr;
image.info.meta_info.cmask_addr = desc.info.meta_info.cmask_addr;
image.flags |= ImageFlagBits::MetaRegistered;
}
if (image_info.meta_info.fmask_addr) {
if (desc.info.meta_info.fmask_addr) {
surface_metas.emplace(
image_info.meta_info.fmask_addr,
desc.info.meta_info.fmask_addr,
MetaDataInfo{.type = MetaDataInfo::Type::FMask, .is_cleared = true});
image.info.meta_info.fmask_addr = image_info.meta_info.fmask_addr;
image.info.meta_info.fmask_addr = desc.info.meta_info.fmask_addr;
image.flags |= ImageFlagBits::MetaRegistered;
}
}
// Update tracked image usage
image.info.usage.render_target = true;
return RegisterImageView(image_id, view_info);
return RegisterImageView(image_id, desc.view_info);
}
ImageView& TextureCache::FindDepthTarget(const ImageInfo& image_info,
const ImageViewInfo& view_info) {
const ImageId image_id = FindImage(image_info);
ImageView& TextureCache::FindDepthTarget(BaseDesc& desc) {
const ImageId image_id = FindImage(desc);
Image& image = slot_images[image_id];
image.flags |= ImageFlagBits::GpuModified;
image.flags &= ~ImageFlagBits::Dirty;
image.aspect_mask = vk::ImageAspectFlagBits::eDepth;
const bool has_stencil = image_info.usage.stencil;
if (has_stencil) {
image.aspect_mask |= vk::ImageAspectFlagBits::eStencil;
}
const auto new_layout = view_info.is_storage
? has_stencil ? vk::ImageLayout::eDepthStencilAttachmentOptimal
: vk::ImageLayout::eDepthAttachmentOptimal
: has_stencil ? vk::ImageLayout::eDepthStencilReadOnlyOptimal
: vk::ImageLayout::eDepthReadOnlyOptimal;
image.Transit(new_layout,
vk::AccessFlagBits2::eDepthStencilAttachmentWrite |
vk::AccessFlagBits2::eDepthStencilAttachmentRead,
view_info.range);
image.usage.depth_target = 1u;
image.usage.stencil = image.info.HasStencil();
// Register meta data for this depth buffer
if (!(image.flags & ImageFlagBits::MetaRegistered)) {
if (image_info.meta_info.htile_addr) {
if (desc.info.meta_info.htile_addr) {
surface_metas.emplace(
image_info.meta_info.htile_addr,
desc.info.meta_info.htile_addr,
MetaDataInfo{.type = MetaDataInfo::Type::HTile, .is_cleared = true});
image.info.meta_info.htile_addr = image_info.meta_info.htile_addr;
image.info.meta_info.htile_addr = desc.info.meta_info.htile_addr;
image.flags |= ImageFlagBits::MetaRegistered;
}
}
// Update tracked image usage
image.info.usage.depth_target = true;
image.info.usage.stencil = has_stencil;
return RegisterImageView(image_id, view_info);
return RegisterImageView(image_id, desc.view_info);
}
void TextureCache::RefreshImage(Image& image, Vulkan::Scheduler* custom_scheduler /*= nullptr*/) {
@ -472,7 +507,7 @@ void TextureCache::RegisterImage(ImageId image_id) {
void TextureCache::UnregisterImage(ImageId image_id) {
Image& image = slot_images[image_id];
ASSERT_MSG(True(image.flags & ImageFlagBits::Registered),
"Trying to unregister an already registered image");
"Trying to unregister an already unregistered image");
image.flags &= ~ImageFlagBits::Registered;
ForEachPage(image.cpu_addr, image.info.guest_size_bytes, [this, image_id](u64 page) {
const auto page_it = page_table.find(page);

View file

@ -43,8 +43,55 @@ class TextureCache {
using PageTable = MultiLevelPageTable<Traits>;
public:
explicit TextureCache(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler,
BufferCache& buffer_cache, PageManager& tracker);
enum class BindingType : u32 {
Texture,
Storage,
RenderTarget,
DepthTarget,
VideoOut,
};
struct BaseDesc {
ImageInfo info;
ImageViewInfo view_info;
BindingType type{BindingType::Texture};
BaseDesc() = default;
BaseDesc(BindingType type_, ImageInfo info_, ImageViewInfo view_info_) noexcept
: info{std::move(info_)}, view_info{std::move(view_info_)}, type{type_} {}
};
struct TextureDesc : public BaseDesc {
TextureDesc() = default;
TextureDesc(const AmdGpu::Image& image, const Shader::ImageResource& desc)
: BaseDesc{desc.is_storage ? BindingType::Storage : BindingType::Texture,
ImageInfo{image, desc}, ImageViewInfo{image, desc}} {}
};
struct RenderTargetDesc : public BaseDesc {
RenderTargetDesc(const AmdGpu::Liverpool::ColorBuffer& buffer,
const AmdGpu::Liverpool::CbDbExtent& hint = {})
: BaseDesc{BindingType::RenderTarget, ImageInfo{buffer, hint}, ImageViewInfo{buffer}} {}
};
struct DepthTargetDesc : public BaseDesc {
DepthTargetDesc(const AmdGpu::Liverpool::DepthBuffer& buffer,
const AmdGpu::Liverpool::DepthView& view,
const AmdGpu::Liverpool::DepthControl& ctl, VAddr htile_address,
const AmdGpu::Liverpool::CbDbExtent& hint = {})
: BaseDesc{BindingType::DepthTarget,
ImageInfo{buffer, view.NumSlices(), htile_address, hint},
ImageViewInfo{buffer, view, ctl}} {}
};
struct VideoOutDesc : public BaseDesc {
VideoOutDesc(const Libraries::VideoOut::BufferAttributeGroup& group, VAddr cpu_address)
: BaseDesc{BindingType::VideoOut, ImageInfo{group, cpu_address}, ImageViewInfo{}} {}
};
public:
TextureCache(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler,
BufferCache& buffer_cache, PageManager& tracker);
~TextureCache();
/// Invalidates any image in the logical page range.
@ -57,18 +104,16 @@ public:
void UnmapMemory(VAddr cpu_addr, size_t size);
/// Retrieves the image handle of the image with the provided attributes.
[[nodiscard]] ImageId FindImage(const ImageInfo& info, FindFlags flags = {});
[[nodiscard]] ImageId FindImage(BaseDesc& desc, FindFlags flags = {});
/// Retrieves an image view with the properties of the specified image id.
[[nodiscard]] ImageView& FindTexture(ImageId image_id, const ImageViewInfo& view_info);
/// Retrieves the render target with specified properties
[[nodiscard]] ImageView& FindRenderTarget(const ImageInfo& image_info,
const ImageViewInfo& view_info);
[[nodiscard]] ImageView& FindRenderTarget(BaseDesc& desc);
/// Retrieves the depth target with specified properties
[[nodiscard]] ImageView& FindDepthTarget(const ImageInfo& image_info,
const ImageViewInfo& view_info);
[[nodiscard]] ImageView& FindDepthTarget(BaseDesc& desc);
/// Updates image contents if it was modified by CPU.
void UpdateImage(ImageId image_id, Vulkan::Scheduler* custom_scheduler = nullptr) {
@ -77,11 +122,13 @@ public:
RefreshImage(image, custom_scheduler);
}
[[nodiscard]] ImageId ResolveOverlap(const ImageInfo& info, ImageId cache_img_id,
ImageId merged_image_id);
[[nodiscard]] std::tuple<ImageId, int, int> ResolveOverlap(const ImageInfo& info,
BindingType binding,
ImageId cache_img_id,
ImageId merged_image_id);
/// Resolves depth overlap and either re-creates the image or returns existing one
[[nodiscard]] ImageId ResolveDepthOverlap(const ImageInfo& requested_info,
[[nodiscard]] ImageId ResolveDepthOverlap(const ImageInfo& requested_info, BindingType binding,
ImageId cache_img_id);
[[nodiscard]] ImageId ExpandImage(const ImageInfo& info, ImageId image_id);