Fix DCC clears after metadata writes
This commit is contained in:
parent
968cf3c03f
commit
01df42a5f3
|
|
@ -33,12 +33,23 @@ namespace {
|
|||
|
||||
constexpr uint64_t NumFramesBeforeRemoval = 32;
|
||||
|
||||
[[nodiscard]] bool IsDccClearCode(uint32_t fill) {
|
||||
switch (fill) {
|
||||
case 0x00000000u:
|
||||
case 0x20202020u:
|
||||
case 0x40404040u:
|
||||
case 0x80808080u:
|
||||
case 0xc0c0c0c0u: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool DecodeDccClear(const TextureCache::ImageDesc& desc, vk::Format format,
|
||||
uint32_t fill, vk::ClearColorValue& clear) {
|
||||
const auto code = static_cast<uint8_t>(fill);
|
||||
if (fill != static_cast<uint32_t>(code) * 0x01010101u) {
|
||||
if (!IsDccClearCode(fill)) {
|
||||
return false;
|
||||
}
|
||||
const auto code = static_cast<uint8_t>(fill);
|
||||
const auto& metadata = desc.info.metadata;
|
||||
if (code == 0x20) {
|
||||
// Clear-to-register is a color-buffer operation; the texture pipe cannot decode it.
|
||||
|
|
@ -46,9 +57,6 @@ constexpr uint64_t NumFramesBeforeRemoval = 32;
|
|||
metadata.dcc_clear_register_valid &&
|
||||
DecodePackedColorClear(format, metadata.dcc_clear_word, clear);
|
||||
}
|
||||
if (code != 0x00 && code != 0x40 && code != 0x80 && code != 0xc0) {
|
||||
return false;
|
||||
}
|
||||
clear = {};
|
||||
if (code == 0x00) {
|
||||
return true;
|
||||
|
|
@ -188,6 +196,9 @@ TextureCache::TextureCache(GraphicContext& graphics, CommandScheduler& scheduler
|
|||
}
|
||||
|
||||
TextureCache::~TextureCache() {
|
||||
for (auto& [address, metadata]: m_surface_metas) {
|
||||
UntrackMetadata(address, metadata);
|
||||
}
|
||||
m_slot_images.ForEach([&](ImageId id, const Image& image) {
|
||||
if (image.registered) {
|
||||
UnregisterImage(id);
|
||||
|
|
@ -323,10 +334,8 @@ void TextureCache::DeleteImage(ImageId id) {
|
|||
if (image->info.HasMetadata()) {
|
||||
const auto metadata = m_surface_metas.find(image->info.metadata.range.address);
|
||||
if (metadata != m_surface_metas.end() &&
|
||||
((image->info.metadata.kind == ImageMetadataKind::Dcc &&
|
||||
metadata->second.type == MetaDataInfo::Type::Dcc) ||
|
||||
(image->info.metadata.kind == ImageMetadataKind::Htile &&
|
||||
metadata->second.type == MetaDataInfo::Type::HTile))) {
|
||||
image->info.metadata.kind == ImageMetadataKind::Htile &&
|
||||
metadata->second.type == MetaDataInfo::Type::HTile) {
|
||||
// A later binding may have reused this address for another metadata type.
|
||||
m_surface_metas.erase(metadata);
|
||||
}
|
||||
|
|
@ -1124,22 +1133,99 @@ void TextureCache::InitializeImage(ImageId id) {
|
|||
}
|
||||
}
|
||||
|
||||
void TextureCache::PrepareDccClear(ImageId id, const ImageDesc& desc) {
|
||||
void TextureCache::UntrackMetadata(uint64_t address, MetaDataInfo& metadata) {
|
||||
if (metadata.tracked) {
|
||||
m_page_manager.UpdatePageWatchers<false>(address, metadata.size);
|
||||
metadata.tracked = false;
|
||||
}
|
||||
}
|
||||
|
||||
void TextureCache::InvalidateDccMetadata(uint64_t address, uint64_t size) {
|
||||
for (auto& [base, metadata]: m_surface_metas) {
|
||||
if (metadata.type != MetaDataInfo::Type::Dcc ||
|
||||
metadata.size == 0 || base >= address + size || address >= base + metadata.size) {
|
||||
continue;
|
||||
}
|
||||
UntrackMetadata(base, metadata);
|
||||
metadata.dirty = true;
|
||||
metadata.clear_mask = UINT32_MAX;
|
||||
metadata.revision = ++m_metadata_revision;
|
||||
}
|
||||
}
|
||||
|
||||
void TextureCache::RefreshDccMetadata(const ImageDesc& desc) {
|
||||
const auto range = desc.info.metadata.range;
|
||||
if (desc.info.metadata.kind != ImageMetadataKind::Dcc || range.size == 0 ||
|
||||
desc.info.resources.levels != 1) {
|
||||
return;
|
||||
}
|
||||
// DCC allocations use complete 4 KiB metadata blocks. Besides validating
|
||||
// the descriptor, this ensures that a CPU page fault identifies the metadata it modifies.
|
||||
if (!range.Valid() || range.address % TRACKER_PAGE_SIZE != 0 ||
|
||||
range.size % TRACKER_PAGE_SIZE != 0) {
|
||||
EXIT("TextureCache: DCC allocation must contain aligned 4 KiB blocks\n");
|
||||
}
|
||||
for (;;) {
|
||||
uint64_t revision = 0;
|
||||
{
|
||||
std::scoped_lock lock {m_lock};
|
||||
auto [entry, inserted] = m_surface_metas.try_emplace(range.address);
|
||||
auto& metadata = entry->second;
|
||||
if (inserted || metadata.type != MetaDataInfo::Type::Dcc) {
|
||||
UntrackMetadata(range.address, metadata);
|
||||
metadata = {.type = MetaDataInfo::Type::Dcc, .clear_mask = UINT32_MAX};
|
||||
}
|
||||
if (metadata.size != range.size) {
|
||||
UntrackMetadata(range.address, metadata);
|
||||
metadata.size = range.size;
|
||||
metadata.dirty = true;
|
||||
metadata.revision = ++m_metadata_revision;
|
||||
}
|
||||
if (!metadata.dirty) {
|
||||
return;
|
||||
}
|
||||
if (!metadata.tracked) {
|
||||
m_page_manager.UpdatePageWatchers<true>(range.address, range.size);
|
||||
metadata.tracked = true;
|
||||
}
|
||||
revision = metadata.revision;
|
||||
}
|
||||
// Discovery precedes descriptor/vertex uploads. Drain metadata writes before taking
|
||||
// the texture lock; completion can retire buffers and run memory-tracking callbacks.
|
||||
m_buffer_cache.ReadMemory(range.address, range.size, false);
|
||||
std::vector<uint8_t> bytes(range.size);
|
||||
std::scoped_lock lock {m_lock};
|
||||
const auto entry = m_surface_metas.find(range.address);
|
||||
if (entry == m_surface_metas.end() || entry->second.revision != revision) {
|
||||
continue;
|
||||
}
|
||||
if (!LibKernel::Memory::TryReadBacking(range.address, bytes.data(), range.size)) {
|
||||
EXIT("TextureCache: failed to read DCC metadata backing\n");
|
||||
}
|
||||
auto& metadata = entry->second;
|
||||
metadata.fill_value = UINT32_MAX;
|
||||
if (std::all_of(bytes.begin(), bytes.end(), [&](uint8_t byte) { return byte == bytes[0]; })) {
|
||||
metadata.fill_value = static_cast<uint32_t>(bytes[0]) * 0x01010101u;
|
||||
}
|
||||
metadata.dirty = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void TextureCache::PrepareDccClear(ImageId id, const ImageDesc& desc,
|
||||
uint32_t metadata_base_layer) {
|
||||
if (desc.info.metadata.kind != ImageMetadataKind::Dcc) {
|
||||
return;
|
||||
}
|
||||
auto& image = m_slot_images[id];
|
||||
image.info.metadata = desc.info.metadata;
|
||||
auto [entry, inserted] = m_surface_metas.try_emplace(
|
||||
desc.info.metadata.range.address, MetaDataInfo {.type = MetaDataInfo::Type::Dcc});
|
||||
auto& metadata = entry->second;
|
||||
if (!inserted && metadata.type == MetaDataInfo::Type::PendingDcc) {
|
||||
metadata.type = MetaDataInfo::Type::Dcc;
|
||||
} else if (metadata.type != MetaDataInfo::Type::Dcc) {
|
||||
EXIT("TextureCache: image reuses non-DCC metadata\n");
|
||||
const auto entry = m_surface_metas.find(desc.info.metadata.range.address);
|
||||
if (entry == m_surface_metas.end() || entry->second.type != MetaDataInfo::Type::Dcc) {
|
||||
return;
|
||||
}
|
||||
if (metadata.clear_mask == 0 || image.info.resources.levels != 1 ||
|
||||
desc.info.metadata.range.size == 0 || metadata.fill_size < desc.info.metadata.range.size) {
|
||||
auto& metadata = entry->second;
|
||||
if (metadata.dirty || metadata.clear_mask == 0 || image.info.resources.levels != 1 ||
|
||||
metadata.size != desc.info.metadata.range.size) {
|
||||
return;
|
||||
}
|
||||
vk::ClearValue clear {};
|
||||
|
|
@ -1148,12 +1234,14 @@ void TextureCache::PrepareDccClear(ImageId id, const ImageDesc& desc) {
|
|||
}
|
||||
const auto& view = desc.view_info;
|
||||
const bool volume_texture = image.info.IsVolume() && view.type == vk::ImageViewType::e3D;
|
||||
const auto first = volume_texture ? 0u : view.base_layer;
|
||||
const auto count = volume_texture ? std::max(image.info.extent.depth >> view.base_level, 1u)
|
||||
const auto first = volume_texture ? 0u : metadata_base_layer;
|
||||
const auto image_first = volume_texture ? 0u : view.base_layer;
|
||||
const auto count = volume_texture ? std::max(desc.info.extent.depth >> view.base_level, 1u)
|
||||
: view.layer_count;
|
||||
if (first >= 32 || count > 32 - first) {
|
||||
return;
|
||||
}
|
||||
const auto metadata_slice_size = metadata.size / desc.info.TransferLayers();
|
||||
// The metadata fill covers the complete allocation. Consume each layer only after its
|
||||
// native image contents exist; already materialized layers may have been rendered since.
|
||||
for (uint32_t layer = first; layer < first + count;) {
|
||||
|
|
@ -1167,9 +1255,16 @@ void TextureCache::PrepareDccClear(ImageId id, const ImageDesc& desc) {
|
|||
mask |= 1u << layer++;
|
||||
} while (layer < first + count && (metadata.clear_mask & (1u << layer)) != 0);
|
||||
ClearImage(m_scheduler.Current(), id,
|
||||
{vk::ImageAspectFlagBits::eColor, view.base_level, view.level_count, start,
|
||||
{vk::ImageAspectFlagBits::eColor, view.base_level, view.level_count,
|
||||
image_first + start - first,
|
||||
layer - start},
|
||||
clear);
|
||||
// The host now owns expanded color data. Publish uncompressed (0xff)
|
||||
// keys for these slices, so a later partial write cannot reuse stale clear codes.
|
||||
const auto address = desc.info.metadata.range.address + metadata_slice_size * start;
|
||||
const auto size = metadata_slice_size * (layer - start);
|
||||
auto [buffer, offset] = m_buffer_cache.ObtainBuffer(address, size, true, false);
|
||||
buffer->Fill(offset, size, UINT32_MAX);
|
||||
metadata.clear_mask &= ~mask;
|
||||
}
|
||||
}
|
||||
|
|
@ -1235,6 +1330,8 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
|
|||
std::scoped_lock lock {m_lock};
|
||||
return GetNullImage(desc);
|
||||
}
|
||||
RefreshDccMetadata(desc);
|
||||
const auto metadata_base_layer = desc.view_info.base_layer;
|
||||
|
||||
ImageId result {};
|
||||
{
|
||||
|
|
@ -1299,6 +1396,7 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
|
|||
if (view_layer >= 0) {
|
||||
desc.view_info.base_layer = static_cast<uint32_t>(view_layer);
|
||||
}
|
||||
PrepareDccClear(result, desc, metadata_base_layer);
|
||||
image.tick_accessed_last = m_scheduler.CurrentTick();
|
||||
TouchImage(image);
|
||||
}
|
||||
|
|
@ -1365,7 +1463,6 @@ vk::ImageView TextureCache::FindTexture(ImageId id, const ImageDesc& desc) {
|
|||
image.MarkGpuModified();
|
||||
}
|
||||
if (!image.info.data.Empty()) {
|
||||
PrepareDccClear(id, desc);
|
||||
RefreshImage(id);
|
||||
}
|
||||
switch (desc.type) {
|
||||
|
|
@ -1395,7 +1492,6 @@ vk::ImageView TextureCache::FindRenderTarget(ImageId id, const ImageDesc& desc)
|
|||
TouchImage(image);
|
||||
image.MarkGpuModified();
|
||||
image.usage.render_target = true;
|
||||
PrepareDccClear(id, desc);
|
||||
RefreshImage(id);
|
||||
CommitGpuWrite(image);
|
||||
TrackImageDownload(id, image);
|
||||
|
|
@ -1426,6 +1522,7 @@ vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) {
|
|||
if (!inserted && metadata->second.type != MetaDataInfo::Type::HTile) {
|
||||
// PS5 allocations can reuse DCC storage as HTile while the old color image is cached.
|
||||
// The depth binding defines the new type; incompatible fill state cannot carry over.
|
||||
UntrackMetadata(metadata->first, metadata->second);
|
||||
metadata->second = {.type = MetaDataInfo::Type::HTile,
|
||||
.clear_mask = image.info.htile_clear_mask};
|
||||
}
|
||||
|
|
@ -1599,6 +1696,7 @@ void TextureCache::InvalidateMemory(uint64_t address, uint64_t size) {
|
|||
EXIT("TextureCache: invalid memory-invalidation range\n");
|
||||
}
|
||||
std::scoped_lock lock {m_lock};
|
||||
InvalidateDccMetadata(address, size);
|
||||
InvalidateCpuAliases(address, size);
|
||||
}
|
||||
|
||||
|
|
@ -1807,6 +1905,7 @@ void TextureCache::InvalidateMemoryFromGPU(uint64_t address, uint64_t size) {
|
|||
return;
|
||||
}
|
||||
std::scoped_lock lock {m_lock};
|
||||
InvalidateDccMetadata(address, size);
|
||||
for (const auto id: FindImagesInRegion(address, size, true)) {
|
||||
auto& image = m_slot_images[id];
|
||||
if (image.depth_id || !image.Overlaps(address, size)) {
|
||||
|
|
@ -1861,27 +1960,29 @@ void TextureCache::InvalidateCpuAliases(uint64_t address, uint64_t size) {
|
|||
bool TextureCache::IsMeta(uint64_t address) {
|
||||
std::scoped_lock lock {m_lock};
|
||||
const auto found = m_surface_metas.find(address);
|
||||
return found != m_surface_metas.end() && found->second.type != MetaDataInfo::Type::PendingDcc;
|
||||
return found != m_surface_metas.end();
|
||||
}
|
||||
|
||||
bool TextureCache::IsMetaCleared(uint64_t address, uint32_t slice, uint32_t* fill_value) {
|
||||
std::scoped_lock lock {m_lock};
|
||||
const auto found = m_surface_metas.find(address);
|
||||
if (found == m_surface_metas.end() || found->second.type == MetaDataInfo::Type::PendingDcc ||
|
||||
slice >= 32) {
|
||||
if (found == m_surface_metas.end() || slice >= 32) {
|
||||
return false;
|
||||
}
|
||||
if (fill_value != nullptr) {
|
||||
*fill_value = found->second.fill_value;
|
||||
}
|
||||
if (found->second.type == MetaDataInfo::Type::Dcc &&
|
||||
(found->second.dirty || !IsDccClearCode(found->second.fill_value))) {
|
||||
return false;
|
||||
}
|
||||
return (found->second.clear_mask & (1u << slice)) != 0;
|
||||
}
|
||||
|
||||
bool TextureCache::ClearMeta(uint64_t address) {
|
||||
std::scoped_lock lock {m_lock};
|
||||
const auto found = m_surface_metas.find(address);
|
||||
if (found == m_surface_metas.end() || found->second.type == MetaDataInfo::Type::PendingDcc ||
|
||||
found->second.type == MetaDataInfo::Type::Dcc) {
|
||||
if (found == m_surface_metas.end() || found->second.type == MetaDataInfo::Type::Dcc) {
|
||||
// Preserve the broad metadata-clear operation for CMask/FMask/HTile. DCC requires a
|
||||
// validated fill value, so an arbitrary compute write must not clear it.
|
||||
return false;
|
||||
|
|
@ -1890,43 +1991,10 @@ bool TextureCache::ClearMeta(uint64_t address) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void TextureCache::TrackDccFill(uint64_t address, uint64_t size, uint32_t fill_value) {
|
||||
if (!GuestRange {address, size}.Valid()) {
|
||||
EXIT("TextureCache: invalid DCC fill range\n");
|
||||
}
|
||||
// DCC fills use a repeated byte code. Require all four bytes of the detected dword to agree,
|
||||
// and mark only recognized deferred-clear encodings as logically clear.
|
||||
const auto dcc_clear_mask = [fill_value] {
|
||||
const auto code = static_cast<uint8_t>(fill_value);
|
||||
if (fill_value != static_cast<uint32_t>(code) * 0x01010101u) {
|
||||
return 0u;
|
||||
}
|
||||
switch (code) {
|
||||
case 0x00:
|
||||
case 0x20:
|
||||
case 0x40:
|
||||
case 0x80:
|
||||
case 0xc0: return UINT32_MAX;
|
||||
default: return 0u;
|
||||
}
|
||||
}();
|
||||
std::scoped_lock lock {m_lock};
|
||||
// The guest dispatch still writes metadata. An unknown address remains PendingDcc until an
|
||||
// image descriptor confirms its role; never reinterpret CMask/FMask/HTile as DCC.
|
||||
const auto found = m_surface_metas.try_emplace(address).first;
|
||||
if (found->second.type == MetaDataInfo::Type::PendingDcc ||
|
||||
found->second.type == MetaDataInfo::Type::Dcc) {
|
||||
found->second.clear_mask = dcc_clear_mask;
|
||||
found->second.fill_value = fill_value;
|
||||
found->second.fill_size = size;
|
||||
}
|
||||
}
|
||||
|
||||
bool TextureCache::TouchMeta(uint64_t address, uint32_t slice, bool is_clear) {
|
||||
std::scoped_lock lock {m_lock};
|
||||
const auto found = m_surface_metas.find(address);
|
||||
if (found == m_surface_metas.end() || found->second.type == MetaDataInfo::Type::PendingDcc ||
|
||||
slice >= 32) {
|
||||
if (found == m_surface_metas.end() || slice >= 32) {
|
||||
return false;
|
||||
}
|
||||
if (is_clear) {
|
||||
|
|
@ -1942,10 +2010,16 @@ void TextureCache::UnmapMemory(uint64_t address, uint64_t size) {
|
|||
EXIT("TextureCache: invalid unmap range\n");
|
||||
}
|
||||
std::scoped_lock lock {m_lock};
|
||||
const auto end = address + size;
|
||||
for (auto metadata = m_surface_metas.lower_bound(address);
|
||||
metadata != m_surface_metas.end() && metadata->first < end;) {
|
||||
metadata = m_surface_metas.erase(metadata);
|
||||
for (auto metadata = m_surface_metas.begin(); metadata != m_surface_metas.end();) {
|
||||
const auto base = metadata->first;
|
||||
if ((base >= address && base < address + size) ||
|
||||
(metadata->second.size != 0 && base < address + size &&
|
||||
address < base + metadata->second.size)) {
|
||||
UntrackMetadata(base, metadata->second);
|
||||
metadata = m_surface_metas.erase(metadata);
|
||||
} else {
|
||||
++metadata;
|
||||
}
|
||||
}
|
||||
auto images = FindImagesInRegion(address, size, false);
|
||||
for (const auto id: images) {
|
||||
|
|
|
|||
|
|
@ -67,8 +67,6 @@ public:
|
|||
[[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice,
|
||||
uint32_t* fill_value = nullptr);
|
||||
[[nodiscard]] bool ClearMeta(uint64_t address);
|
||||
// Record deferred DCC state while the original guest dispatch writes the metadata.
|
||||
void TrackDccFill(uint64_t address, uint64_t size, uint32_t fill_value);
|
||||
[[nodiscard]] bool TouchMeta(uint64_t address, uint32_t slice, bool is_clear);
|
||||
|
||||
void UnmapMemory(uint64_t address, uint64_t size);
|
||||
|
|
@ -81,17 +79,16 @@ private:
|
|||
struct ImageDownload;
|
||||
|
||||
struct MetaDataInfo {
|
||||
// A guest metadata-fill dispatch may initialize DCC before its render target is bound.
|
||||
// PendingDcc retains that exact fill until an image binding classifies the address,
|
||||
// without exposing an unconfirmed buffer address to the normal metadata heuristics.
|
||||
// Keep all surface metadata in one entry so CMask/FMask can be
|
||||
// registered beside HTile and DCC without introducing parallel tracking paths.
|
||||
enum class Type : uint8_t { PendingDcc, CMask, FMask, HTile, Dcc };
|
||||
enum class Type : uint8_t { CMask, FMask, HTile, Dcc };
|
||||
|
||||
Type type = Type::PendingDcc;
|
||||
Type type = Type::Dcc;
|
||||
uint32_t clear_mask = 0;
|
||||
uint32_t fill_value = 0xffffffffu;
|
||||
uint64_t fill_size = 0;
|
||||
// PS5 DCC bytes and layer consumption belong to the metadata allocation, not an image.
|
||||
uint64_t size = 0;
|
||||
uint64_t revision = 0;
|
||||
bool dirty = true;
|
||||
bool tracked = false;
|
||||
};
|
||||
|
||||
struct OverlapResult {
|
||||
|
|
@ -149,7 +146,11 @@ private:
|
|||
ImageId cached);
|
||||
[[nodiscard]] ImageId ExpandImage(const ImageInfo& info, ImageId source);
|
||||
void RefreshImage(ImageId id);
|
||||
void PrepareDccClear(ImageId id, const ImageDesc& desc);
|
||||
void RefreshDccMetadata(const ImageDesc& desc);
|
||||
void PrepareDccClear(ImageId id, const ImageDesc& desc,
|
||||
uint32_t metadata_base_layer);
|
||||
void UntrackMetadata(uint64_t address, MetaDataInfo& metadata);
|
||||
void InvalidateDccMetadata(uint64_t address, uint64_t size);
|
||||
void InitializeImage(ImageId id);
|
||||
[[nodiscard]] TextureTransfer
|
||||
BuildTextureTransfer(const Image& image, BindingType binding, TransferDirection direction) const;
|
||||
|
|
@ -186,6 +187,7 @@ private:
|
|||
Common::LeastRecentlyUsedCache<ImageId, uint64_t> m_lru_cache;
|
||||
std::unordered_set<ImageId> m_download_images;
|
||||
std::map<uint64_t, MetaDataInfo> m_surface_metas;
|
||||
uint64_t m_metadata_revision = 0;
|
||||
uint64_t m_total_used_memory = 0;
|
||||
uint64_t m_trigger_gc_memory = 0;
|
||||
uint64_t m_pressure_gc_memory = 1536ull * 1024 * 1024;
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ NativeStorageBuffer(RenderContext& context, const PreparedBindings::BufferSource
|
|||
}
|
||||
buffer_offset = static_cast<uint32_t>(adjustment);
|
||||
const vk::DescriptorBufferInfo result {buffer->Handle(), aligned_offset, size + adjustment};
|
||||
if (resource.formatted && resource.written) {
|
||||
if (resource.written) {
|
||||
context.GetTextureCache().InvalidateMemoryFromGPU(address, size);
|
||||
}
|
||||
const char* access = "Read";
|
||||
|
|
|
|||
|
|
@ -183,14 +183,6 @@ bool RenderExecutor::TryConsumeComputeImageClear(const ShaderComputeInputInfo& i
|
|||
return false;
|
||||
}
|
||||
if (!cache.ClearImageFromBuffer(command, descriptor.Base48(), size, packed_clear)) {
|
||||
// Track deferred DCC state while the original dispatch writes the metadata allocation.
|
||||
cache.TrackDccFill(descriptor.Base48(), size, packed_clear);
|
||||
static std::atomic<uint32_t> logged_metadata_clears {0};
|
||||
if (logged_metadata_clears.fetch_add(1, std::memory_order_relaxed) < 32) {
|
||||
LOGF("GraphicsRenderDispatchDirect: metadata fill shader=0x%016" PRIx64
|
||||
" addr=0x%016" PRIx64 " size=0x%016" PRIx64 " value=0x%08" PRIx32 "\n",
|
||||
input.stage.program->shader_hash, descriptor.Base48(), size, packed_clear);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static std::atomic<uint32_t> logged_clears {0};
|
||||
|
|
|
|||
|
|
@ -6133,6 +6133,53 @@ public:
|
|||
"fresh multisample target violated write-only ownership");
|
||||
}
|
||||
|
||||
// A one-layer guest descriptor can alias layer 1 of a cached native array.
|
||||
constexpr uint64_t alias_dcc_address = base + 0x2120000;
|
||||
auto dcc_array = MakeLinearDesc(
|
||||
base + 0x2100000, 0x20000, vk::Format::eR16G16B16A16Sfloat,
|
||||
Prospero::BufferFormat::k16_16_16_16Float, Prospero::ImageType::kColor2D,
|
||||
{128, 64, 1}, 2, 8, 1);
|
||||
dcc_array.type = BindingType::RenderTarget;
|
||||
dcc_array.info.tile_mode = Prospero::TileMode::kRenderTarget;
|
||||
dcc_array.info.pitch = TileGetRenderTargetPitch(128, 8);
|
||||
dcc_array.info.mip_layout[0].pitch = dcc_array.info.pitch;
|
||||
dcc_array.info.metadata.kind = ImageMetadataKind::Dcc;
|
||||
dcc_array.info.metadata.range = {alias_dcc_address, 0x2000};
|
||||
dcc_array.view_info.usage = vk::ImageUsageFlagBits::eColorAttachment;
|
||||
WriteMetadata(context, alias_dcc_address, 0x2000, UINT32_MAX);
|
||||
WriteMetadata(context, alias_dcc_address + 0x2000, 0x1000, 0x5a5a5a5au);
|
||||
const auto dcc_array_id = texture_cache.FindImage(dcc_array);
|
||||
vk::ClearValue array_paint{};
|
||||
array_paint.color.float32 = std::array{1.0f, 0.0f, 1.0f, 1.0f};
|
||||
TextureCacheTestAccess::ClearImage(texture_cache, scheduler.Current(), dcc_array_id,
|
||||
{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 2}, array_paint);
|
||||
auto dcc_alias = dcc_array;
|
||||
dcc_alias.info.data = {dcc_array.info.data.address + 0x10000, 0x10000};
|
||||
dcc_alias.info.resources.layers = 1;
|
||||
dcc_alias.info.mip_layout[0].size = 0x10000;
|
||||
dcc_alias.info.metadata.range = {alias_dcc_address + 0x1000, 0x1000};
|
||||
dcc_alias.view_info.type = vk::ImageViewType::e2D;
|
||||
dcc_alias.view_info.layer_count = 1;
|
||||
WriteMetadata(context, dcc_alias.info.metadata.range.address, 0x1000, 0x40404040u);
|
||||
Require(name, "DCC alias host layer",
|
||||
texture_cache.FindImage(dcc_alias) == dcc_array_id &&
|
||||
dcc_alias.view_info.base_layer == 1 &&
|
||||
ReadCachedTexel(name, context, dcc_array_id) ==
|
||||
std::vector<u32>{0x00003c00u, 0x3c003c00u} &&
|
||||
ReadCachedTexel(name, context, dcc_array_id, {}, {1, 1, 1}, 1) ==
|
||||
std::vector<u32>{0, 0x3c000000u},
|
||||
"guest metadata layer 0 did not clear only remapped native layer 1");
|
||||
resources.GetBufferCache().ReadMemory(alias_dcc_address, 0x3000, false);
|
||||
std::array<uint8_t, 0x3000> alias_metadata{};
|
||||
Require(name, "DCC alias metadata bounds",
|
||||
LibKernel::Memory::TryReadBacking(alias_dcc_address, alias_metadata.data(),
|
||||
alias_metadata.size()) &&
|
||||
std::all_of(alias_metadata.begin(), alias_metadata.begin() + 0x2000,
|
||||
[](uint8_t value) { return value == 0xff; }) &&
|
||||
std::all_of(alias_metadata.begin() + 0x2000, alias_metadata.end(),
|
||||
[](uint8_t value) { return value == 0x5a; }),
|
||||
"remapped host layer changed metadata outside the guest alias allocation");
|
||||
|
||||
constexpr uint64_t metadata_data_a = 0x12000;
|
||||
constexpr uint64_t metadata_data_b = 0x12100;
|
||||
constexpr uint64_t metadata_a = 0x13000;
|
||||
|
|
@ -6156,11 +6203,11 @@ public:
|
|||
texture_cache.FindImage(metadata_depth_a);
|
||||
const auto metadata_depth_b_id =
|
||||
texture_cache.FindImage(metadata_depth_b);
|
||||
texture_cache.TrackDccFill(base + metadata_a, 0x80, 0);
|
||||
WriteMetadata(context, base + metadata_a, 0x80, 0);
|
||||
Require(
|
||||
name, "pending DCC/HTile separation",
|
||||
name, "unbound metadata separation",
|
||||
!texture_cache.IsMeta(base + metadata_a),
|
||||
"an unclassified PS5 DCC fill was exposed as registered metadata");
|
||||
"unbound metadata bytes were exposed as registered HTile");
|
||||
const auto metadata_depth_a_view =
|
||||
texture_cache.FindDepthTarget(metadata_depth_a_id, metadata_depth_a);
|
||||
const auto metadata_depth_b_view =
|
||||
|
|
@ -6193,7 +6240,7 @@ public:
|
|||
texture_cache.IsMetaCleared(base + metadata_b, 0),
|
||||
"image discovery retired aliased metadata or its live depth image");
|
||||
const bool touched_metadata = texture_cache.TouchMeta(base + metadata_a, 0, false);
|
||||
texture_cache.TrackDccFill(base + metadata_a, 0x80, 0);
|
||||
WriteMetadata(context, base + metadata_a, 0x80, 0);
|
||||
Require(
|
||||
name, "registered DCC/HTile separation",
|
||||
touched_metadata && !texture_cache.IsMetaCleared(base + metadata_a, 0),
|
||||
|
|
@ -6214,7 +6261,7 @@ public:
|
|||
|
||||
// R-Type Final 3 reuses an old color allocation inside a new stencil plane,
|
||||
// with the same metadata address changing from DCC to HTile.
|
||||
auto reused_depth = MakeMetadataDepth(0x12300, 0x13200);
|
||||
auto reused_depth = MakeMetadataDepth(0x12300, 0x28000);
|
||||
reused_depth.info.pixel_format = vk::Format::eD32SfloatS8Uint;
|
||||
reused_depth.info.stencil = {base + 0x12200, 0x80};
|
||||
reused_depth.info.htile_clear_mask = 0;
|
||||
|
|
@ -6227,11 +6274,27 @@ public:
|
|||
{1, 1, 1}, 1, 4, 1);
|
||||
reused_color.type = BindingType::RenderTarget;
|
||||
reused_color.info.metadata.kind = ImageMetadataKind::Dcc;
|
||||
reused_color.info.metadata.range = {base + 0x13200, 0x20};
|
||||
reused_color.info.metadata.range = {base + 0x28000, 0x1000};
|
||||
reused_color.view_info.usage = vk::ImageUsageFlagBits::eColorAttachment;
|
||||
const auto reused_color_id = texture_cache.FindImage(reused_color);
|
||||
(void)texture_cache.FindRenderTarget(reused_color_id, reused_color);
|
||||
texture_cache.TrackDccFill(base + 0x13200, 0x80, 0);
|
||||
// Exact repeated CPU stores must re-arm DCC metadata write protection.
|
||||
for (uint32_t repeat = 0; repeat < 2; repeat++) {
|
||||
vk::ClearValue painted{};
|
||||
painted.color.float32 = std::array{1.0f, 0.0f, 1.0f, 1.0f};
|
||||
TextureCacheTestAccess::ClearImage(
|
||||
texture_cache, scheduler.Current(), reused_color_id,
|
||||
{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}, painted);
|
||||
std::memset(memory + 0x28000, 0, 0x1000);
|
||||
Require(name, "CPU metadata rediscovery",
|
||||
texture_cache.FindImage(reused_color) == reused_color_id,
|
||||
"metadata writes replaced the live expanded image");
|
||||
(void)texture_cache.FindRenderTarget(reused_color_id, reused_color);
|
||||
Require(name, "repeated CPU metadata clear",
|
||||
ReadCachedTexel(name, context, reused_color_id) == std::vector<u32>{0},
|
||||
"a same-value CPU metadata write lost its clear after rendering");
|
||||
}
|
||||
WriteMetadata(context, base + 0x28000, 0x1000, 0);
|
||||
texture_cache.InvalidateMemoryFromGPU(reused_depth.info.stencil.address,
|
||||
reused_depth.info.stencil.size);
|
||||
const auto reused_depth_id = texture_cache.FindImage(reused_depth);
|
||||
|
|
@ -6242,24 +6305,24 @@ public:
|
|||
TextureCacheTestAccess::Contains(texture_cache, reused_color_id) &&
|
||||
texture_cache.GetImage(reused_color_id).IsBufferModified() &&
|
||||
!texture_cache.GetImage(reused_color_id).IsGpuModified() &&
|
||||
!texture_cache.IsMetaCleared(base + 0x13200, 0, &reused_fill) &&
|
||||
!texture_cache.IsMetaCleared(base + 0x28000, 0, &reused_fill) &&
|
||||
reused_fill == UINT32_MAX &&
|
||||
texture_cache.ClearMeta(base + 0x13200) &&
|
||||
texture_cache.TouchMeta(base + 0x13200, 0, false),
|
||||
texture_cache.ClearMeta(base + 0x28000) &&
|
||||
texture_cache.TouchMeta(base + 0x28000, 0, false),
|
||||
"depth binding retained incompatible DCC clear state");
|
||||
TextureCacheTestAccess::DeleteImage(texture_cache, reused_color_id);
|
||||
(void)texture_cache.FindDepthTarget(reused_depth_id, reused_depth);
|
||||
Require(
|
||||
name, "reused metadata owner retirement",
|
||||
!TextureCacheTestAccess::Contains(texture_cache, reused_color_id) &&
|
||||
texture_cache.IsMeta(base + 0x13200) &&
|
||||
!texture_cache.IsMetaCleared(base + 0x13200, 0) &&
|
||||
texture_cache.IsMetaCleared(base + 0x13200, 1),
|
||||
texture_cache.IsMeta(base + 0x28000) &&
|
||||
!texture_cache.IsMetaCleared(base + 0x28000, 0) &&
|
||||
texture_cache.IsMetaCleared(base + 0x28000, 1),
|
||||
"retiring the old DCC image erased the live HTile slice state");
|
||||
texture_cache.UnmapMemory(reused_depth.info.data.address,
|
||||
reused_depth.info.data.size);
|
||||
Require(name, "reused metadata final retirement",
|
||||
!texture_cache.IsMeta(base + 0x13200),
|
||||
!texture_cache.IsMeta(base + 0x28000),
|
||||
"retiring the HTile image left its metadata registered");
|
||||
|
||||
constexpr uint64_t partial_unmap_image_offset = 0x2700000;
|
||||
|
|
@ -8188,6 +8251,7 @@ public:
|
|||
|
||||
{
|
||||
RenderContext context(m_runtime_context);
|
||||
context.InitializeGpu(nullptr);
|
||||
auto &scheduler = context.GetCommandScheduler();
|
||||
HW::Context registers{};
|
||||
HW::UserConfig user_config{};
|
||||
|
|
@ -8215,7 +8279,7 @@ public:
|
|||
auto &texture_cache = resources.GetTextureCache();
|
||||
auto &executor = context.GetRenderExecutor();
|
||||
resources.MapMemory(base, allocation_size);
|
||||
texture_cache.TrackDccFill(dcc_address, metadata_size, 0);
|
||||
WriteMetadata(context, dcc_address, metadata_size, 0);
|
||||
Require(name, "deferred DCC clear",
|
||||
!texture_cache.IsMeta(dcc_address),
|
||||
"a pre-registration DCC clear was not deferred");
|
||||
|
|
@ -8325,11 +8389,15 @@ public:
|
|||
DestroyBuffer(&slice_probe);
|
||||
|
||||
dcc_clear_value = 0;
|
||||
texture_cache.TrackDccFill(dcc_address, metadata_size, 0xffffffffu);
|
||||
WriteMetadata(context, dcc_address, metadata_size, 0xffffffffu);
|
||||
color.image_id = texture_cache.FindImage(color.desc);
|
||||
(void)texture_cache.FindRenderTarget(color.image_id, color.desc);
|
||||
const bool uncompressed =
|
||||
!texture_cache.IsMetaCleared(dcc_address, 7, &dcc_clear_value) &&
|
||||
dcc_clear_value == 0xffffffffu;
|
||||
texture_cache.TrackDccFill(dcc_address, metadata_size, 0);
|
||||
WriteMetadata(context, dcc_address, metadata_size, 0);
|
||||
color.image_id = texture_cache.FindImage(color.desc);
|
||||
(void)texture_cache.FindRenderTarget(color.image_id, color.desc);
|
||||
Require(
|
||||
name, "registered DCC code state",
|
||||
uncompressed && texture_cache.IsMetaCleared(dcc_address, 7),
|
||||
|
|
@ -8374,9 +8442,18 @@ public:
|
|||
std::printf("[gpu] %-32s ok\n", name);
|
||||
}
|
||||
|
||||
static void WriteMetadata(RenderContext &context, uint64_t address,
|
||||
uint64_t size, uint32_t value) {
|
||||
Require("DccMetadata", "guest write range", size % sizeof(value) == 0 &&
|
||||
context.InvalidateMemory(address, size),
|
||||
"metadata write is outside the mapped allocation");
|
||||
const std::vector<uint32_t> words(size / sizeof(value), value);
|
||||
LibKernel::Memory::WriteBacking(address, words.data(), size);
|
||||
}
|
||||
|
||||
std::vector<u32> ReadCachedTexel(const char *name, RenderContext &context,
|
||||
ImageId id, vk::Offset3D offset = {},
|
||||
vk::Extent3D extent = {1, 1, 1}) {
|
||||
vk::Extent3D extent = {1, 1, 1}, uint32_t layer = 0) {
|
||||
auto &scheduler = context.GetCommandScheduler();
|
||||
auto &image = context.GetTextureCache().GetImage(id);
|
||||
const auto bytes = image.info.bytes_per_block * extent.width *
|
||||
|
|
@ -8389,7 +8466,7 @@ public:
|
|||
scheduler.Current().Handle());
|
||||
vk::BufferImageCopy copy{};
|
||||
copy.imageSubresource = {image.info.IsDepth() ? vk::ImageAspectFlagBits::eDepth
|
||||
: vk::ImageAspectFlagBits::eColor, 0, 0, 1};
|
||||
: vk::ImageAspectFlagBits::eColor, 0, layer, 1};
|
||||
copy.imageOffset = offset;
|
||||
copy.imageExtent = extent;
|
||||
scheduler.Current().Handle().copyImageToBuffer(
|
||||
|
|
@ -8427,6 +8504,44 @@ public:
|
|||
FillCase{0x80808080u, {0x3c003c00u, 0x00003c00u}},
|
||||
};
|
||||
EnsureRuntimeContext();
|
||||
// Astro's generic metadata fill, through S_ENDPGM; trailing debug data is omitted.
|
||||
static constexpr std::array<uint32_t, 69> native_fill{
|
||||
0xbfa00003u, 0xd7460002u, 0x04010c0au, 0x7da80408u,
|
||||
0xbf88003fu, 0x7e000c09u, 0xbf070980u, 0x858a807eu,
|
||||
0x7e005700u, 0x100000ffu, 0x4f800000u, 0x7e060f00u,
|
||||
0xd5766a00u, 0x02020609u, 0x7d8a0280u, 0x4c020080u,
|
||||
0x02000101u, 0xd56a0001u, 0x00020700u, 0x4c000303u,
|
||||
0x4a020303u, 0x02000101u, 0xd56a0000u, 0x00020500u,
|
||||
0xd5690001u, 0x00020009u, 0x4c060302u, 0x7d860609u,
|
||||
0x7d8c02f9u, 0x06068c02u, 0x87ea6a0cu, 0x50000080u,
|
||||
0xd5286a00u, 0x003200c1u, 0xd5010000u, 0x002a00c1u,
|
||||
0xd5690000u, 0x00020009u, 0x4c000102u, 0x7d0a0080u,
|
||||
0xbe88246au, 0xbf880015u, 0x7d0a0081u, 0xbe8a246au,
|
||||
0xbf88000cu, 0x7d0a0082u, 0xbeea246au, 0xbf880003u,
|
||||
0x7e000207u, 0xe0102000u, 0x80000002u, 0x8afe7e6au,
|
||||
0xbf880003u, 0x7e000206u, 0xe0102000u, 0x80000002u,
|
||||
0xbefe046au, 0x8afe7e0au, 0xbf880003u, 0x7e000205u,
|
||||
0xe0102000u, 0x80000002u, 0xbefe040au, 0x8afe7e08u,
|
||||
0xbf880003u, 0x7e000204u, 0xe0102000u, 0x80000002u,
|
||||
0xbf810000u,
|
||||
};
|
||||
static constexpr auto raw_fill = [] {
|
||||
auto words = native_fill;
|
||||
for (auto &word : words) {
|
||||
if (word == 0xe0102000u) {
|
||||
word = 0xe0702000u; // BUFFER_STORE_DWORD in place of BUFFER_STORE_FORMAT_X.
|
||||
}
|
||||
}
|
||||
return words;
|
||||
}();
|
||||
for (const auto *shader : {&native_fill, &raw_fill}) {
|
||||
ShaderMapUserData(reinterpret_cast<uint64_t>(shader->data()),
|
||||
{.type = Prospero::ShaderBinaryType::kCs,
|
||||
.code_size_bytes = sizeof(native_fill)});
|
||||
}
|
||||
TileSizeAlign metadata_size{};
|
||||
(void)TileGetDccSize(512, 256, 1, 8, 1,
|
||||
Prospero::TileMode::kRenderTarget, metadata_size, 0);
|
||||
|
||||
int64_t direct_offset = -1;
|
||||
Require(name, "direct allocation",
|
||||
|
|
@ -8445,58 +8560,139 @@ public:
|
|||
|
||||
for (const auto &fill_case : cases) {
|
||||
RenderContext context(m_runtime_context);
|
||||
auto &scheduler = context.GetCommandScheduler();
|
||||
HW::Context registers{};
|
||||
HW::UserConfig user_config{};
|
||||
HW::Shader shaders{};
|
||||
registers.SetColorBase(0, {.addr = base});
|
||||
registers.SetColorInfo(
|
||||
0, {.dcc_compression_enable = true,
|
||||
.format = Prospero::ChannelLayout::k16_16_16_16,
|
||||
.channel_type = Prospero::ChannelType::kFloat,
|
||||
.channel_order = Prospero::ChannelOrder::kStandard});
|
||||
registers.SetColorAttrib2(0, {.height = 255, .width = 255});
|
||||
registers.SetColorAttrib3(0,
|
||||
{.tile_mode = Prospero::TileMode::kRenderTarget,
|
||||
.dimension = 1,
|
||||
.metadata_pipe_aligned = true});
|
||||
registers.SetColorDccAddr(0, {.addr = dcc_address});
|
||||
registers.SetColorClearWord0(0, {.word0 = 0});
|
||||
registers.SetColorClearWord1(0, {.word1 = 0});
|
||||
registers.SetRenderTargetMask(0x0f);
|
||||
scheduler.Begin(registers, user_config, shaders);
|
||||
context.InitializeGpu(nullptr);
|
||||
context.GetGpu().SendCommandSync([&] {
|
||||
auto &scheduler = context.GetCommandScheduler();
|
||||
registers.SetColorBase(0, {.addr = base});
|
||||
registers.SetColorInfo(
|
||||
0, {.dcc_compression_enable = true,
|
||||
.format = Prospero::ChannelLayout::k16_16_16_16,
|
||||
.channel_type = Prospero::ChannelType::kFloat,
|
||||
.channel_order = Prospero::ChannelOrder::kStandard});
|
||||
registers.SetColorAttrib2(0, {.height = 255, .width = 511});
|
||||
registers.SetColorAttrib3(0,
|
||||
{.tile_mode = Prospero::TileMode::kRenderTarget,
|
||||
.dimension = 1,
|
||||
.metadata_pipe_aligned = true});
|
||||
registers.SetColorDccAddr(0, {.addr = dcc_address});
|
||||
registers.SetColorClearWord0(0, {.word0 = 0});
|
||||
registers.SetColorClearWord1(0, {.word1 = 0});
|
||||
registers.SetRenderTargetMask(0x0f);
|
||||
scheduler.Begin(registers, user_config, shaders);
|
||||
|
||||
auto &resources = context;
|
||||
auto &texture_cache = resources.GetTextureCache();
|
||||
auto &executor = context.GetRenderExecutor();
|
||||
resources.MapMemory(base, allocation_size);
|
||||
texture_cache.TrackDccFill(dcc_address, 0x1000, fill_case.fill);
|
||||
Require(name, "deferred fixed-code fill",
|
||||
!texture_cache.IsMeta(dcc_address),
|
||||
"a pre-registration DCC fill was not deferred");
|
||||
auto &resources = context;
|
||||
auto &texture_cache = resources.GetTextureCache();
|
||||
auto &executor = context.GetRenderExecutor();
|
||||
resources.MapMemory(base, allocation_size);
|
||||
const auto fill_metadata = [&](uint32_t count, bool raw = false) {
|
||||
const auto *shader = &native_fill;
|
||||
if (raw) {
|
||||
shader = &raw_fill;
|
||||
}
|
||||
shaders.SetCsShader({.data_addr = reinterpret_cast<uint64_t>(shader->data()),
|
||||
.num_thread_x = 64, .num_thread_y = 1, .num_thread_z = 1,
|
||||
.wave_size = 64, .user_sgpr = 10, .tgid_x_en = true});
|
||||
const std::array<uint32_t, 10> user_data{
|
||||
static_cast<uint32_t>(dcc_address),
|
||||
static_cast<uint32_t>(dcc_address >> 32u) | (4u << 16u),
|
||||
count, 0x00014004u, fill_case.fill, 0, 0x200, 0, count, 1};
|
||||
for (uint32_t i = 0; i < user_data.size(); i++) {
|
||||
shaders.SetCsUserSgpr(i, user_data[i], HW::UserSgprType::Unknown);
|
||||
}
|
||||
executor.DispatchDirect(0, scheduler.Current(), (count + 63) / 64, 1, 1, 0x41u);
|
||||
};
|
||||
const auto metadata_words = static_cast<uint32_t>(metadata_size.size / 4);
|
||||
fill_metadata(metadata_words);
|
||||
Require(name, "metadata before descriptor",
|
||||
!texture_cache.IsMeta(dcc_address),
|
||||
"the native fill guessed an unbound metadata allocation's type");
|
||||
|
||||
RenderColorInfo color{};
|
||||
RenderExecutorTestAccess::ResolveRenderColorTarget(
|
||||
executor, scheduler.Current(), color, 0);
|
||||
RenderDepthInfo no_depth{};
|
||||
const auto rendering = RenderExecutorTestAccess::AcquireRenderTargets(
|
||||
executor, scheduler.Current(), &color, 1, no_depth);
|
||||
Require(name, "fixed clear on a float target",
|
||||
color.image_id &&
|
||||
color.desc.info.metadata.kind == ImageMetadataKind::Dcc &&
|
||||
color.desc.info.metadata.range.address == dcc_address &&
|
||||
rendering.num_color_attachments == 1 &&
|
||||
!texture_cache.IsMetaCleared(dcc_address, 0),
|
||||
"a DCC fixed clear code was not materialised on an RGBA16F "
|
||||
"target");
|
||||
Require(name, "GPU float clear value",
|
||||
ReadCachedTexel(name, context, color.image_id) ==
|
||||
std::vector<u32>(fill_case.texel.begin(), fill_case.texel.end()),
|
||||
"RGBA16F native image did not receive the fixed clear value");
|
||||
RenderColorInfo color{};
|
||||
RenderExecutorTestAccess::ResolveRenderColorTarget(
|
||||
executor, scheduler.Current(), color, 0);
|
||||
RenderDepthInfo no_depth{};
|
||||
const auto rendering = RenderExecutorTestAccess::AcquireRenderTargets(
|
||||
executor, scheduler.Current(), &color, 1, no_depth);
|
||||
Require(name, "fixed clear on a float target",
|
||||
color.image_id &&
|
||||
color.desc.info.metadata.kind == ImageMetadataKind::Dcc &&
|
||||
color.desc.info.metadata.range.address == dcc_address &&
|
||||
rendering.num_color_attachments == 1 &&
|
||||
!texture_cache.IsMetaCleared(dcc_address, 0),
|
||||
"a DCC fixed clear code was not materialised on an RGBA16F "
|
||||
"target");
|
||||
Require(name, "GPU float clear value",
|
||||
ReadCachedTexel(name, context, color.image_id) ==
|
||||
std::vector<u32>(fill_case.texel.begin(), fill_case.texel.end()),
|
||||
"RGBA16F native image did not receive the fixed clear value");
|
||||
context.GetBufferCache().ReadMemory(dcc_address, metadata_size.size, false);
|
||||
std::vector<uint8_t> expanded_metadata(metadata_size.size);
|
||||
Require(name, "expanded native metadata",
|
||||
LibKernel::Memory::TryReadBacking(dcc_address, expanded_metadata.data(),
|
||||
expanded_metadata.size()) &&
|
||||
std::all_of(expanded_metadata.begin(), expanded_metadata.end(),
|
||||
[](uint8_t value) { return value == 0xff; }),
|
||||
"materialized color retained stale native DCC clear codes");
|
||||
|
||||
RenderExecutorTestAccess::ResetBindings(executor);
|
||||
resources.UnmapMemory(base, allocation_size);
|
||||
scheduler.Finish();
|
||||
const auto bind = [&] {
|
||||
RenderExecutorTestAccess::ResetBindings(executor);
|
||||
color.image_id = texture_cache.FindImage(color.desc);
|
||||
(void)texture_cache.FindRenderTarget(color.image_id, color.desc);
|
||||
};
|
||||
const auto paint = [&] {
|
||||
vk::ClearValue clear{};
|
||||
clear.color.float32 = std::array{1.0f, 0.0f, 1.0f, 1.0f};
|
||||
TextureCacheTestAccess::ClearImage(
|
||||
texture_cache, scheduler.Current(), color.image_id,
|
||||
{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}, clear);
|
||||
};
|
||||
const std::vector<u32> painted{0x00003c00u, 0x3c003c00u};
|
||||
const auto retains_painted_texel = [&] {
|
||||
const auto texels = ReadCachedTexel(name, context, color.image_id, {}, {512, 256, 1});
|
||||
for (size_t i = 0; i < texels.size(); i += 2) {
|
||||
if (texels[i] == painted[0] && texels[i + 1] == painted[1]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
paint();
|
||||
bind();
|
||||
Require(name, "unchanged metadata preserves rendering",
|
||||
ReadCachedTexel(name, context, color.image_id) == painted,
|
||||
"rebinding an unchanged DCC allocation reapplied an old clear");
|
||||
fill_metadata(metadata_words - 1);
|
||||
bind();
|
||||
Require(name, "partial same-value metadata fill",
|
||||
retains_painted_texel(),
|
||||
"a partial same-value write reused stale clear bytes after rendering");
|
||||
fill_metadata(metadata_words, true);
|
||||
bind();
|
||||
Require(name, "same-value raw native re-clear",
|
||||
ReadCachedTexel(name, context, color.image_id) ==
|
||||
std::vector<u32>(fill_case.texel.begin(), fill_case.texel.end()),
|
||||
"a repeated metadata fill did not clear an already drawn target");
|
||||
paint();
|
||||
WriteMetadata(context, dcc_address, metadata_size.size, UINT32_MAX);
|
||||
fill_metadata(metadata_words - 1);
|
||||
bind();
|
||||
Require(name, "partial native metadata fill",
|
||||
retains_painted_texel(),
|
||||
"a nonuniform metadata allocation cleared the whole target");
|
||||
fill_metadata(metadata_words);
|
||||
bind();
|
||||
Require(name, "complete fill after partial fill",
|
||||
ReadCachedTexel(name, context, color.image_id) ==
|
||||
std::vector<u32>(fill_case.texel.begin(), fill_case.texel.end()),
|
||||
"completing the metadata overwrite did not restore its clear");
|
||||
RenderExecutorTestAccess::ResetBindings(executor);
|
||||
resources.UnmapMemory(base, allocation_size);
|
||||
scheduler.Finish();
|
||||
});
|
||||
context.ShutdownGpu();
|
||||
}
|
||||
|
||||
Require(name, "unmap direct backing",
|
||||
|
|
@ -8532,8 +8728,9 @@ public:
|
|||
"captured scene mapping failed");
|
||||
for (const auto initial_size : {metadata_size - 0x1000, metadata_size}) {
|
||||
std::memset(mapped, 0x5a, color_size);
|
||||
std::memset(reinterpret_cast<void *>(dcc_address), 0x40, metadata_size);
|
||||
std::memset(reinterpret_cast<void *>(dcc_address), 0xff, metadata_size);
|
||||
RenderContext context(m_runtime_context);
|
||||
context.InitializeGpu(nullptr);
|
||||
auto &scheduler = context.GetCommandScheduler();
|
||||
HW::Context registers{};
|
||||
HW::UserConfig user_config{};
|
||||
|
|
@ -8543,7 +8740,7 @@ public:
|
|||
auto &cache = resources.GetTextureCache();
|
||||
auto &executor = context.GetRenderExecutor();
|
||||
resources.MapMemory(base, allocation_size);
|
||||
cache.TrackDccFill(dcc_address, initial_size, 0x40404040u);
|
||||
WriteMetadata(context, dcc_address, initial_size, 0x40404040u);
|
||||
Require(name, "deferred metadata fill",
|
||||
!cache.IsMeta(dcc_address),
|
||||
"unknown metadata was consumed before descriptor discovery");
|
||||
|
|
@ -8576,7 +8773,8 @@ public:
|
|||
ReadCachedTexel(name, context, binding.image_id) ==
|
||||
std::vector<u32>{0x5a5a5a5au, 0x5a5a5a5au},
|
||||
"an incomplete metadata fill cleared the whole scene");
|
||||
cache.TrackDccFill(dcc_address, metadata_size, 0x40404040u);
|
||||
WriteMetadata(context, dcc_address, metadata_size, 0x40404040u);
|
||||
binding.image_id = cache.FindImage(binding.desc);
|
||||
(void)cache.FindTexture(binding.image_id, binding.desc);
|
||||
}
|
||||
Require(name, "opaque black before first sample",
|
||||
|
|
@ -8584,7 +8782,8 @@ public:
|
|||
std::vector<u32>{0, 0x3c000000u} &&
|
||||
!cache.IsMetaCleared(dcc_address, 0),
|
||||
"sampling read stale color bytes instead of the DCC opaque black clear");
|
||||
cache.TrackDccFill(dcc_address, metadata_size, 0x80808080u);
|
||||
WriteMetadata(context, dcc_address, metadata_size, 0x80808080u);
|
||||
binding.image_id = cache.FindImage(binding.desc);
|
||||
(void)cache.FindTexture(binding.image_id, binding.desc);
|
||||
Require(name, "later sampled clear",
|
||||
ReadCachedTexel(name, context, binding.image_id) ==
|
||||
|
|
@ -9201,6 +9400,7 @@ public:
|
|||
HW::Context registers{};
|
||||
HW::UserConfig user_config{};
|
||||
HW::Shader shaders{};
|
||||
context.InitializeGpu(nullptr);
|
||||
scheduler.Begin(registers, user_config, shaders);
|
||||
auto &resources = context;
|
||||
auto &texture_cache = resources.GetTextureCache();
|
||||
|
|
@ -9642,6 +9842,12 @@ public:
|
|||
sint_storage.fields[1] =
|
||||
(sint_storage.fields[1] & ~0xffu) |
|
||||
static_cast<uint32_t>(encoded_sint_address >> 32u);
|
||||
constexpr uint64_t sint_metadata_address = base + 0x160000;
|
||||
const auto encoded_sint_metadata = sint_metadata_address >> 8u;
|
||||
sint_storage.fields[6] = (sint_storage.fields[6] & 0x00ffffffu) |
|
||||
(static_cast<uint32_t>(encoded_sint_metadata & 0xffu) << 24u);
|
||||
sint_storage.fields[7] = static_cast<uint32_t>(encoded_sint_metadata >> 8u);
|
||||
WriteMetadata(context, sint_metadata_address, 0x1000, UINT32_MAX);
|
||||
ShaderRecompiler::IR::DescriptorValue sint_storage_descriptor{};
|
||||
std::copy(std::begin(sint_storage.fields), std::end(sint_storage.fields),
|
||||
sint_storage_descriptor.dwords.begin());
|
||||
|
|
@ -9657,6 +9863,8 @@ public:
|
|||
sint_storage_binding.image_id, sint_storage_binding.desc);
|
||||
Require(name, "PPSA06888 raw R32 SINT storage view",
|
||||
sint_storage_view != nullptr &&
|
||||
sint_storage_binding.desc.info.metadata.range ==
|
||||
GuestRange{sint_metadata_address, 0x1000} &&
|
||||
sint_storage_binding.desc.info.data.size == 0x10000 &&
|
||||
sint_storage_binding.desc.info.pixel_format ==
|
||||
vk::Format::eR32Sint &&
|
||||
|
|
@ -11307,6 +11515,7 @@ public:
|
|||
scheduler.Finish();
|
||||
RenderExecutorTestAccess::DestroyDescriptorPipelines(
|
||||
executor, descriptor_pipelines);
|
||||
context.ShutdownGpu();
|
||||
}
|
||||
|
||||
Require(name, "unmap direct backing",
|
||||
|
|
@ -30134,6 +30343,11 @@ int main(int argc, char **argv) {
|
|||
vulkan.CheckUnifiedTextureCacheFlow();
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--dcc-clear-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
vulkan.CheckRenderExecutorDccFixedClearFloat();
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--compute-meta-clear-only") == 0) {
|
||||
CheckDynamicRenderingState();
|
||||
VulkanHarness vulkan;
|
||||
|
|
|
|||
Loading…
Reference in New Issue