mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-06-10 20:53:15 +00:00
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:
parent
059f54838a
commit
2cbbcbd371
16 changed files with 336 additions and 47 deletions
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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{};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue