Metadata support (#223)

* texture_cache: more image usage flags

* texture_cache: metadata registration

* renderer_vulkan: initial CMask support

* renderer_vulkan: skip redundant FCE and FMask decompression passes

* renderer_vulkan: redundant VO surface registration removed

* renderer_vulkan: initial HTile support

* renderer_vulkan: added support for MSAA attachments

* renderer_vulkan: skip unnecessary metadata updates
This commit is contained in:
psucien 2024-06-29 15:49:59 +02:00 committed by GitHub
parent 059f54838a
commit 2cbbcbd371
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 336 additions and 47 deletions

View file

@ -91,7 +91,7 @@ static vk::ImageUsageFlags ImageUsageFlags(const ImageInfo& info) {
usage |= vk::ImageUsageFlagBits::eColorAttachment;
}
}
if (info.is_tiled || info.is_storage) {
if (info.is_tiled || info.usage.storage) {
usage |= vk::ImageUsageFlagBits::eStorage;
}
return usage;
@ -149,10 +149,12 @@ ImageInfo::ImageInfo(const AmdGpu::Liverpool::ColorBuffer& buffer,
size.depth = 1;
pitch = size.width;
guest_size_bytes = buffer.GetSizeAligned();
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;
}
ImageInfo::ImageInfo(const AmdGpu::Liverpool::DepthBuffer& buffer,
ImageInfo::ImageInfo(const AmdGpu::Liverpool::DepthBuffer& buffer, VAddr htile_address,
const AmdGpu::Liverpool::CbDbExtent& hint) noexcept {
is_tiled = false;
pixel_format = LiverpoolToVK::DepthFormat(buffer.z_info.format, buffer.stencil_info.format);
@ -163,6 +165,7 @@ ImageInfo::ImageInfo(const AmdGpu::Liverpool::DepthBuffer& buffer,
size.depth = 1;
pitch = size.width;
guest_size_bytes = buffer.GetSizeAligned();
meta_info.htile_addr = buffer.z_info.tile_surface_en ? htile_address : 0;
usage.depth_target = true;
}
@ -178,6 +181,7 @@ ImageInfo::ImageInfo(const AmdGpu::Image& image) noexcept {
resources.levels = image.NumLevels();
resources.layers = image.NumLayers();
guest_size_bytes = image.GetSizeAligned();
usage.texture = true;
}
UniqueImage::UniqueImage(vk::Device device_, VmaAllocator allocator_)
@ -248,6 +252,7 @@ Image::Image(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_,
},
.mipLevels = static_cast<u32>(info.resources.levels),
.arrayLayers = static_cast<u32>(info.resources.layers),
.samples = LiverpoolToVK::NumSamples(info.num_samples),
.tiling = vk::ImageTiling::eOptimal,
.usage = usage,
.initialLayout = vk::ImageLayout::eUndefined,

View file

@ -25,11 +25,12 @@ VK_DEFINE_HANDLE(VmaAllocator)
namespace VideoCore {
enum ImageFlagBits : u32 {
CpuModified = 1 << 2, ///< Contents have been modified from the CPU
GpuModified = 1 << 3, ///< Contents have been modified from the GPU
Tracked = 1 << 4, ///< Writes and reads are being hooked from the CPU
Registered = 1 << 6, ///< True when the image is registered
Picked = 1 << 7, ///< Temporary flag to mark the image as picked
CpuModified = 1 << 2, ///< Contents have been modified from the CPU
GpuModified = 1 << 3, ///< Contents have been modified from the GPU
Tracked = 1 << 4, ///< Writes and reads are being hooked from the CPU
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
};
DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits)
@ -38,7 +39,7 @@ struct ImageInfo {
explicit ImageInfo(const Libraries::VideoOut::BufferAttributeGroup& group) noexcept;
explicit ImageInfo(const AmdGpu::Liverpool::ColorBuffer& buffer,
const AmdGpu::Liverpool::CbDbExtent& hint = {}) noexcept;
explicit ImageInfo(const AmdGpu::Liverpool::DepthBuffer& buffer,
explicit ImageInfo(const AmdGpu::Liverpool::DepthBuffer& buffer, VAddr htile_address,
const AmdGpu::Liverpool::CbDbExtent& hint = {}) noexcept;
explicit ImageInfo(const AmdGpu::Image& image) noexcept;
@ -49,16 +50,21 @@ struct ImageInfo {
bool IsPacked() const;
bool IsDepthStencil() const;
struct {
VAddr cmask_addr;
VAddr fmask_addr;
VAddr htile_addr;
} meta_info{};
struct {
u32 texture : 1;
u32 storage : 1;
u32 render_target : 1;
u32 depth_target : 1;
u32 vo_buffer : 1;
} usage; // Usage data tracked during image lifetime
} usage{}; // Usage data tracked during image lifetime
bool is_tiled = false;
bool is_storage = false;
vk::Format pixel_format = vk::Format::eUndefined;
vk::ImageType type = vk::ImageType::e1D;
SubresourceExtent resources;

View file

@ -131,6 +131,8 @@ Image& TextureCache::FindImage(const ImageInfo& info, VAddr cpu_address, bool re
image_id = image_ids[0];
}
RegisterMeta(info, image_id);
Image& image = slot_images[image_id];
if (True(image.flags & ImageFlagBits::CpuModified) &&
(!image_ids.empty() || refresh_on_create)) {
@ -150,7 +152,7 @@ ImageView& TextureCache::RegisterImageView(Image& image, const ImageViewInfo& vi
// impossible to use. However, during view creation, if an image isn't used as storage we can
// temporary remove its storage bit.
std::optional<vk::ImageUsageFlags> usage_override;
if (!image.info.is_storage) {
if (!image.info.usage.storage) {
usage_override = image.usage & ~vk::ImageUsageFlagBits::eStorage;
}
@ -161,12 +163,15 @@ ImageView& TextureCache::RegisterImageView(Image& image, const ImageViewInfo& vi
}
ImageView& TextureCache::FindImageView(const AmdGpu::Image& desc, bool is_storage) {
Image& image = FindImage(ImageInfo{desc}, desc.Address());
const ImageInfo info{desc};
Image& image = FindImage(info, desc.Address());
if (is_storage) {
image.Transit(vk::ImageLayout::eGeneral, vk::AccessFlagBits::eShaderWrite);
image.info.usage.storage = true;
} else {
image.Transit(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits::eShaderRead);
image.info.usage.texture = true;
}
const ImageViewInfo view_info{desc, is_storage};
@ -183,13 +188,16 @@ ImageView& TextureCache::RenderTarget(const AmdGpu::Liverpool::ColorBuffer& buff
vk::AccessFlagBits::eColorAttachmentWrite |
vk::AccessFlagBits::eColorAttachmentRead);
image.info.usage.render_target = true;
ImageViewInfo view_info{buffer, !!image.info.usage.vo_buffer};
return RegisterImageView(image, view_info);
}
ImageView& TextureCache::DepthTarget(const AmdGpu::Liverpool::DepthBuffer& buffer,
VAddr htile_address,
const AmdGpu::Liverpool::CbDbExtent& hint) {
const ImageInfo info{buffer, hint};
const ImageInfo info{buffer, htile_address, hint};
auto& image = FindImage(info, buffer.Address(), false);
image.flags &= ~ImageFlagBits::CpuModified;
@ -197,6 +205,8 @@ ImageView& TextureCache::DepthTarget(const AmdGpu::Liverpool::DepthBuffer& buffe
vk::AccessFlagBits::eDepthStencilAttachmentWrite |
vk::AccessFlagBits::eDepthStencilAttachmentRead);
image.info.usage.depth_target = true;
ImageViewInfo view_info;
view_info.format = info.pixel_format;
return RegisterImageView(image, view_info);
@ -276,6 +286,47 @@ void TextureCache::RegisterImage(ImageId image_id) {
[this, image_id](u64 page) { page_table[page].push_back(image_id); });
}
void TextureCache::RegisterMeta(const ImageInfo& info, ImageId image_id) {
Image& image = slot_images[image_id];
if (image.flags & ImageFlagBits::MetaRegistered) {
return;
}
bool registered = true;
// Current resource tracking implementation allows us to detect usage of meta only in the last
// moment, so we likely will miss its first clear. To avoid this and make first frame, where
// the meta is encountered, looks correct we set its state to "cleared" at registrations time.
if (info.usage.render_target) {
if (info.meta_info.cmask_addr) {
surface_metas.emplace(
info.meta_info.cmask_addr,
MetaDataInfo{.type = MetaDataInfo::Type::CMask, .is_cleared = true});
image.info.meta_info.cmask_addr = info.meta_info.cmask_addr;
}
if (info.meta_info.fmask_addr) {
surface_metas.emplace(
info.meta_info.fmask_addr,
MetaDataInfo{.type = MetaDataInfo::Type::FMask, .is_cleared = true});
image.info.meta_info.fmask_addr = info.meta_info.fmask_addr;
}
} else if (info.usage.depth_target) {
if (info.meta_info.htile_addr) {
surface_metas.emplace(
info.meta_info.htile_addr,
MetaDataInfo{.type = MetaDataInfo::Type::HTile, .is_cleared = true});
image.info.meta_info.htile_addr = info.meta_info.htile_addr;
}
} else {
registered = false;
}
if (registered) {
image.flags |= ImageFlagBits::MetaRegistered;
}
}
void TextureCache::UnregisterImage(ImageId image_id) {
Image& image = slot_images[image_id];
ASSERT_MSG(True(image.flags & ImageFlagBits::Registered),

View file

@ -29,6 +29,17 @@ class TextureCache {
static constexpr u64 PageBits = 20;
static constexpr u64 PageMask = (1ULL << PageBits) - 1;
struct MetaDataInfo {
enum class Type {
CMask,
FMask,
HTile,
};
Type type;
bool is_cleared;
};
public:
explicit TextureCache(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler);
~TextureCache();
@ -47,6 +58,7 @@ public:
[[nodiscard]] ImageView& RenderTarget(const AmdGpu::Liverpool::ColorBuffer& buffer,
const AmdGpu::Liverpool::CbDbExtent& hint);
[[nodiscard]] ImageView& DepthTarget(const AmdGpu::Liverpool::DepthBuffer& buffer,
VAddr htile_address,
const AmdGpu::Liverpool::CbDbExtent& hint);
/// Reuploads image contents.
@ -60,6 +72,27 @@ public:
return slot_images[id];
}
bool IsMeta(VAddr address) const {
return surface_metas.contains(address);
}
bool IsMetaCleared(VAddr address) const {
const auto& it = surface_metas.find(address);
if (it != surface_metas.end()) {
return it.value().is_cleared;
}
return false;
}
bool TouchMeta(VAddr address, bool is_clear) {
auto it = surface_metas.find(address);
if (it != surface_metas.end()) {
it.value().is_cleared = is_clear;
return true;
}
return false;
}
private:
ImageView& RegisterImageView(Image& image, const ImageViewInfo& view_info);
@ -123,6 +156,9 @@ private:
/// Register image in the page table
void RegisterImage(ImageId image);
/// Register metadata surfaces attached to the image
void RegisterMeta(const ImageInfo& info, ImageId image);
/// Unregister image from the page table
void UnregisterImage(ImageId image);
@ -145,6 +181,7 @@ private:
tsl::robin_map<u64, Sampler> samplers;
tsl::robin_pg_map<u64, std::vector<ImageId>> page_table;
boost::icl::interval_map<VAddr, s32> cached_pages;
tsl::robin_map<VAddr, MetaDataInfo> surface_metas;
std::mutex mutex;
#ifdef _WIN64
void* veh_handle{};