Remove extra DCC tracking

This commit is contained in:
nmzik
2026-09-14 16:40:46 +04:00
parent dd408ffd00
commit 437e69ef1b
4 changed files with 247 additions and 224 deletions
+69 -170
View File
@@ -33,23 +33,16 @@ namespace {
constexpr uint64_t NumFramesBeforeRemoval = 32; constexpr uint64_t NumFramesBeforeRemoval = 32;
[[nodiscard]] bool IsDccClearCode(uint32_t fill) { [[nodiscard]] bool DecodeDccClear(const TextureCache::ImageDesc& desc, vk::Format format,
switch (fill) { uint8_t code, vk::ClearColorValue& clear) {
case 0x00000000u: switch (code) {
case 0x20202020u: case 0x00:
case 0x40404040u: case 0x20:
case 0x80808080u: case 0x40:
case 0xc0c0c0c0u: return true; case 0x80:
case 0xc0: break;
default: return false; default: return false;
} }
}
[[nodiscard]] bool DecodeDccClear(const TextureCache::ImageDesc& desc, vk::Format format,
uint32_t fill, vk::ClearColorValue& clear) {
if (!IsDccClearCode(fill)) {
return false;
}
const auto code = static_cast<uint8_t>(fill);
const auto& metadata = desc.info.metadata; const auto& metadata = desc.info.metadata;
if (code == 0x20) { if (code == 0x20) {
// Clear-to-register is a color-buffer operation; the texture pipe cannot decode it. // Clear-to-register is a color-buffer operation; the texture pipe cannot decode it.
@@ -196,9 +189,6 @@ TextureCache::TextureCache(GraphicContext& graphics, CommandScheduler& scheduler
} }
TextureCache::~TextureCache() { TextureCache::~TextureCache() {
for (auto& [address, metadata]: m_surface_metas) {
UntrackMetadata(address, metadata);
}
m_slot_images.ForEach([&](ImageId id, const Image& image) { m_slot_images.ForEach([&](ImageId id, const Image& image) {
if (image.registered) { if (image.registered) {
UnregisterImage(id); UnregisterImage(id);
@@ -1133,139 +1123,71 @@ void TextureCache::InitializeImage(ImageId id) {
} }
} }
void TextureCache::UntrackMetadata(uint64_t address, MetaDataInfo& metadata) { void TextureCache::MaterializeDccClear(ImageId id, const ImageDesc& desc,
if (metadata.tracked) { uint32_t metadata_base_layer) {
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) { if (desc.info.metadata.kind != ImageMetadataKind::Dcc) {
return; return;
} }
auto& image = m_slot_images[id]; const auto range = desc.info.metadata.range;
image.info.metadata = desc.info.metadata; vk::Format format;
const auto entry = m_surface_metas.find(desc.info.metadata.range.address); {
if (entry == m_surface_metas.end() || entry->second.type != MetaDataInfo::Type::Dcc) { std::scoped_lock lock {m_lock};
return; auto& image = m_slot_images[id];
image.info.metadata = desc.info.metadata;
// A native DCC allocation must not retain a reused HTile/CMask/FMask interpretation.
m_surface_metas.erase(range.address);
if (range.size == 0 || desc.info.resources.levels != 1 || image.info.resources.levels != 1) {
return;
}
format = image.backing.format;
} }
auto& metadata = entry->second; const auto layers = desc.info.TransferLayers();
if (metadata.dirty || metadata.clear_mask == 0 || image.info.resources.levels != 1 || // These one-mip surfaces use complete 4 KiB DCC metadata blocks.
metadata.size != desc.info.metadata.range.size) { constexpr uint64_t MetadataBlockSize = 0x1000;
return; if (!range.Valid() || range.address % MetadataBlockSize != 0 || layers == 0 ||
} range.size % layers != 0 || (range.size / layers) % MetadataBlockSize != 0) {
vk::ClearValue clear {}; EXIT("TextureCache: DCC slices must contain aligned 4 KiB blocks\n");
if (!DecodeDccClear(desc, image.backing.format, metadata.fill_value, clear.color)) {
return;
} }
const auto& view = desc.view_info; const auto& view = desc.view_info;
const bool volume_texture = image.info.IsVolume() && view.type == vk::ImageViewType::e3D; const bool volume_texture = desc.info.IsVolume() && view.type == vk::ImageViewType::e3D;
const auto first = volume_texture ? 0u : metadata_base_layer; const auto first = volume_texture ? 0u : metadata_base_layer;
const auto image_first = volume_texture ? 0u : view.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) const auto count = volume_texture ? desc.info.extent.depth : view.layer_count;
: view.layer_count; if (first >= layers || count > layers - first) {
if (first >= 32 || count > 32 - first) { EXIT("TextureCache: DCC view exceeds its native metadata slices\n");
return;
} }
const auto metadata_slice_size = metadata.size / desc.info.TransferLayers(); // Finish native metadata writes before reading backing bytes. This can submit the scheduler,
// The metadata fill covers the complete allocation. Consume each layer only after its // so discovery runs before final draw uploads and never holds the texture lock across it.
// native image contents exist; already materialized layers may have been rendered since. if (m_buffer_cache.IsRegionGpuModified(range.address, range.size)) {
for (uint32_t layer = first; layer < first + count;) { m_buffer_cache.ReadMemory(range.address, range.size, false);
if ((metadata.clear_mask & (1u << layer)) == 0) { }
layer++; const auto slice_size = range.size / layers;
for (uint32_t slice = 0; slice < count; slice++) {
const auto address = range.address + slice_size * (first + slice);
uint8_t code = 0;
if (!LibKernel::Memory::TryReadBacking(address, &code, sizeof(code))) {
EXIT("TextureCache: failed to read DCC metadata backing\n");
}
vk::ClearValue clear {};
if (!DecodeDccClear(desc, format, code, clear.color)) {
continue; continue;
} }
const auto start = layer; std::vector<uint8_t> bytes(slice_size);
uint32_t mask = 0; if (!LibKernel::Memory::TryReadBacking(address, bytes.data(), bytes.size())) {
do { EXIT("TextureCache: failed to read DCC metadata slice\n");
mask |= 1u << layer++; }
} while (layer < first + count && (metadata.clear_mask & (1u << layer)) != 0); if (!std::all_of(bytes.begin(), bytes.end(), [code](uint8_t byte) { return byte == code; })) {
ClearImage(m_scheduler.Current(), id, continue;
{vk::ImageAspectFlagBits::eColor, view.base_level, view.level_count, }
image_first + start - first, {
layer - start}, std::scoped_lock lock {m_lock};
clear); ClearImage(m_scheduler.Current(), id,
// The host now owns expanded color data. Publish uncompressed (0xff) {vk::ImageAspectFlagBits::eColor, view.base_level, view.level_count,
// keys for these slices, so a later partial write cannot reuse stale clear codes. image_first + slice, 1}, clear);
const auto address = desc.info.metadata.range.address + metadata_slice_size * start; }
const auto size = metadata_slice_size * (layer - start); // Native expanded keys own consumption. Existing buffer tracking publishes this CPU
auto [buffer, offset] = m_buffer_cache.ObtainBuffer(address, size, true, false); // write to future GPU readers; FillBuffer can fault and must run outside the texture lock.
buffer->Fill(offset, size, UINT32_MAX); m_buffer_cache.FillBuffer(address, slice_size, UINT32_MAX, false);
metadata.clear_mask &= ~mask;
} }
} }
@@ -1330,7 +1252,6 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
std::scoped_lock lock {m_lock}; std::scoped_lock lock {m_lock};
return GetNullImage(desc); return GetNullImage(desc);
} }
RefreshDccMetadata(desc);
const auto metadata_base_layer = desc.view_info.base_layer; const auto metadata_base_layer = desc.view_info.base_layer;
ImageId result {}; ImageId result {};
@@ -1396,10 +1317,10 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
if (view_layer >= 0) { if (view_layer >= 0) {
desc.view_info.base_layer = static_cast<uint32_t>(view_layer); desc.view_info.base_layer = static_cast<uint32_t>(view_layer);
} }
PrepareDccClear(result, desc, metadata_base_layer);
image.tick_accessed_last = m_scheduler.CurrentTick(); image.tick_accessed_last = m_scheduler.CurrentTick();
TouchImage(image); TouchImage(image);
} }
MaterializeDccClear(result, desc, metadata_base_layer);
return result; return result;
} }
@@ -1515,17 +1436,9 @@ vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) {
RefreshImage(id); RefreshImage(id);
if (desc.info.HasMetadata()) { if (desc.info.HasMetadata()) {
image.info.metadata = desc.info.metadata; image.info.metadata = desc.info.metadata;
auto [metadata, inserted] = m_surface_metas.emplace(desc.info.metadata.range.address,
m_surface_metas.try_emplace(desc.info.metadata.range.address, MetaDataInfo {.type = MetaDataInfo::Type::HTile,
MetaDataInfo {.type = MetaDataInfo::Type::HTile, .clear_mask = image.info.htile_clear_mask});
.clear_mask = image.info.htile_clear_mask});
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};
}
} }
CommitGpuWrite(image); CommitGpuWrite(image);
if (desc.info.HasStencil()) { if (desc.info.HasStencil()) {
@@ -1696,7 +1609,6 @@ void TextureCache::InvalidateMemory(uint64_t address, uint64_t size) {
EXIT("TextureCache: invalid memory-invalidation range\n"); EXIT("TextureCache: invalid memory-invalidation range\n");
} }
std::scoped_lock lock {m_lock}; std::scoped_lock lock {m_lock};
InvalidateDccMetadata(address, size);
InvalidateCpuAliases(address, size); InvalidateCpuAliases(address, size);
} }
@@ -1905,7 +1817,6 @@ void TextureCache::InvalidateMemoryFromGPU(uint64_t address, uint64_t size) {
return; return;
} }
std::scoped_lock lock {m_lock}; std::scoped_lock lock {m_lock};
InvalidateDccMetadata(address, size);
for (const auto id: FindImagesInRegion(address, size, true)) { for (const auto id: FindImagesInRegion(address, size, true)) {
auto& image = m_slot_images[id]; auto& image = m_slot_images[id];
if (image.depth_id || !image.Overlaps(address, size)) { if (image.depth_id || !image.Overlaps(address, size)) {
@@ -1963,28 +1874,19 @@ bool TextureCache::IsMeta(uint64_t address) {
return found != m_surface_metas.end(); return found != m_surface_metas.end();
} }
bool TextureCache::IsMetaCleared(uint64_t address, uint32_t slice, uint32_t* fill_value) { bool TextureCache::IsMetaCleared(uint64_t address, uint32_t slice) {
std::scoped_lock lock {m_lock}; std::scoped_lock lock {m_lock};
const auto found = m_surface_metas.find(address); const auto found = m_surface_metas.find(address);
if (found == m_surface_metas.end() || slice >= 32) { if (found == m_surface_metas.end() || slice >= 32) {
return false; 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; return (found->second.clear_mask & (1u << slice)) != 0;
} }
bool TextureCache::ClearMeta(uint64_t address) { bool TextureCache::ClearMeta(uint64_t address) {
std::scoped_lock lock {m_lock}; std::scoped_lock lock {m_lock};
const auto found = m_surface_metas.find(address); const auto found = m_surface_metas.find(address);
if (found == m_surface_metas.end() || found->second.type == MetaDataInfo::Type::Dcc) { if (found == m_surface_metas.end()) {
// 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; return false;
} }
found->second.clear_mask = UINT32_MAX; found->second.clear_mask = UINT32_MAX;
@@ -2012,10 +1914,7 @@ void TextureCache::UnmapMemory(uint64_t address, uint64_t size) {
std::scoped_lock lock {m_lock}; std::scoped_lock lock {m_lock};
for (auto metadata = m_surface_metas.begin(); metadata != m_surface_metas.end();) { for (auto metadata = m_surface_metas.begin(); metadata != m_surface_metas.end();) {
const auto base = metadata->first; const auto base = metadata->first;
if ((base >= address && base < address + size) || 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); metadata = m_surface_metas.erase(metadata);
} else { } else {
++metadata; ++metadata;
+6 -17
View File
@@ -64,8 +64,7 @@ public:
[[nodiscard]] bool IsRegionGpuModified(uint64_t address, uint64_t size); [[nodiscard]] bool IsRegionGpuModified(uint64_t address, uint64_t size);
[[nodiscard]] bool IsMeta(uint64_t address); [[nodiscard]] bool IsMeta(uint64_t address);
[[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice, [[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice);
uint32_t* fill_value = nullptr);
[[nodiscard]] bool ClearMeta(uint64_t address); [[nodiscard]] bool ClearMeta(uint64_t address);
[[nodiscard]] bool TouchMeta(uint64_t address, uint32_t slice, bool is_clear); [[nodiscard]] bool TouchMeta(uint64_t address, uint32_t slice, bool is_clear);
@@ -79,16 +78,10 @@ private:
struct ImageDownload; struct ImageDownload;
struct MetaDataInfo { struct MetaDataInfo {
enum class Type : uint8_t { CMask, FMask, HTile, Dcc }; enum class Type : uint8_t { CMask, FMask, HTile };
Type type = Type::Dcc; Type type;
uint32_t clear_mask = 0; uint32_t clear_mask = UINT32_MAX;
uint32_t fill_value = 0xffffffffu;
// 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 { struct OverlapResult {
@@ -146,11 +139,8 @@ private:
ImageId cached); ImageId cached);
[[nodiscard]] ImageId ExpandImage(const ImageInfo& info, ImageId source); [[nodiscard]] ImageId ExpandImage(const ImageInfo& info, ImageId source);
void RefreshImage(ImageId id); void RefreshImage(ImageId id);
void RefreshDccMetadata(const ImageDesc& desc); void MaterializeDccClear(ImageId id, const ImageDesc& desc,
void PrepareDccClear(ImageId id, const ImageDesc& desc, uint32_t metadata_base_layer);
uint32_t metadata_base_layer);
void UntrackMetadata(uint64_t address, MetaDataInfo& metadata);
void InvalidateDccMetadata(uint64_t address, uint64_t size);
void InitializeImage(ImageId id); void InitializeImage(ImageId id);
[[nodiscard]] TextureTransfer [[nodiscard]] TextureTransfer
BuildTextureTransfer(const Image& image, BindingType binding, TransferDirection direction) const; BuildTextureTransfer(const Image& image, BindingType binding, TransferDirection direction) const;
@@ -187,7 +177,6 @@ private:
Common::LeastRecentlyUsedCache<ImageId, uint64_t> m_lru_cache; Common::LeastRecentlyUsedCache<ImageId, uint64_t> m_lru_cache;
std::unordered_set<ImageId> m_download_images; std::unordered_set<ImageId> m_download_images;
std::map<uint64_t, MetaDataInfo> m_surface_metas; std::map<uint64_t, MetaDataInfo> m_surface_metas;
uint64_t m_metadata_revision = 0;
uint64_t m_total_used_memory = 0; uint64_t m_total_used_memory = 0;
uint64_t m_trigger_gc_memory = 0; uint64_t m_trigger_gc_memory = 0;
uint64_t m_pressure_gc_memory = 1536ull * 1024 * 1024; uint64_t m_pressure_gc_memory = 1536ull * 1024 * 1024;
@@ -873,6 +873,8 @@ void RenderExecutor::PrepareGraphicsBindings(std::span<PreparedBindings* const>
if (old_image != nullptr) { if (old_image != nullptr) {
old_image->binding = {}; old_image->binding = {};
} }
target.desc.view_info.base_level = target.guest_mip_level;
target.desc.view_info.base_layer = target.guest_array_layer;
target.image_id = cache.FindImage(target.desc); target.image_id = cache.FindImage(target.desc);
BindRenderTarget(target.image_id); BindRenderTarget(target.image_id);
} }
+170 -37
View File
@@ -4521,20 +4521,6 @@ public:
{ {
auto &resources = context; auto &resources = context;
namespace Exception = Common::HostException;
Require(name, "guest fault handler",
Exception::InstallHandler([](const Exception::ExceptionInfo &info) {
if (info.type != Exception::ExceptionType::AccessViolation ||
(info.access_violation_type != Exception::AccessViolationType::Read &&
info.access_violation_type != Exception::AccessViolationType::Write)) {
return false;
}
const auto access =
info.access_violation_type == Exception::AccessViolationType::Write
? PageFaultAccess::Write : PageFaultAccess::Read;
return LibKernel::Memory::HandleGpuFault(access, info.access_violation_vaddr);
}),
"failed to install the production guest-memory fault route");
LibKernel::Memory::InstallGpuResources(&resources); LibKernel::Memory::InstallGpuResources(&resources);
auto &texture_cache = resources.GetTextureCache(); auto &texture_cache = resources.GetTextureCache();
const auto [narrow_download, narrow_download_offset] = const auto [narrow_download, narrow_download_offset] =
@@ -6186,6 +6172,39 @@ public:
[](uint8_t value) { return value == 0x5a; }), [](uint8_t value) { return value == 0x5a; }),
"remapped host layer changed metadata outside the guest alias allocation"); "remapped host layer changed metadata outside the guest alias allocation");
RenderColorInfo retired_alias{};
retired_alias.desc = dcc_alias;
retired_alias.image_id = dcc_array_id;
retired_alias.guest_mip_level = 0;
retired_alias.guest_array_layer = 0;
texture_cache.UnmapMemory(dcc_array.info.data.address, dcc_array.info.data.size);
WriteMetadata(context, dcc_alias.info.metadata.range.address, 0x1000, 0x40404040u);
RenderExecutorTestAccess::PrepareGraphicsBindings(
context.GetRenderExecutor(), std::span<PreparedBindings *const>{},
std::span{&retired_alias, 1u});
RenderDepthInfo alias_depth{};
const auto alias_rendering = RenderExecutorTestAccess::AcquireRenderTargets(
context.GetRenderExecutor(), scheduler.Current(), &retired_alias, 1, alias_depth);
Require(name, "retired DCC alias native coordinates",
retired_alias.image_id != dcc_array_id &&
retired_alias.desc.view_info.base_level == 0 &&
retired_alias.desc.view_info.base_layer == 0 &&
retired_alias.desc.info.resources.layers == 1 &&
alias_rendering.color_attachments[0].image_view != nullptr &&
ReadCachedTexel(name, context, retired_alias.image_id) ==
std::vector<u32>{0, 0x3c000000u},
"rediscovery treated a retired host layer as the native DCC slice");
resources.GetBufferCache().ReadMemory(alias_dcc_address, 0x3000);
Require(name, "retired 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 byte) { return byte == 0xff; }) &&
std::all_of(alias_metadata.begin() + 0x2000, alias_metadata.end(),
[](uint8_t byte) { return byte == 0x5a; }),
"retired alias rediscovery overwrote metadata outside its native slice");
RenderExecutorTestAccess::ResetBindings(context.GetRenderExecutor());
constexpr uint64_t metadata_data_a = 0x12000; constexpr uint64_t metadata_data_a = 0x12000;
constexpr uint64_t metadata_data_b = 0x12100; constexpr uint64_t metadata_data_b = 0x12100;
constexpr uint64_t metadata_a = 0x13000; constexpr uint64_t metadata_a = 0x13000;
@@ -6284,7 +6303,7 @@ public:
reused_color.view_info.usage = vk::ImageUsageFlagBits::eColorAttachment; reused_color.view_info.usage = vk::ImageUsageFlagBits::eColorAttachment;
const auto reused_color_id = texture_cache.FindImage(reused_color); const auto reused_color_id = texture_cache.FindImage(reused_color);
(void)texture_cache.FindRenderTarget(reused_color_id, reused_color); (void)texture_cache.FindRenderTarget(reused_color_id, reused_color);
// Exact repeated CPU stores must re-arm DCC metadata write protection. // Repeated CPU stores must remain visible through ordinary buffer ownership.
for (uint32_t repeat = 0; repeat < 2; repeat++) { for (uint32_t repeat = 0; repeat < 2; repeat++) {
vk::ClearValue painted{}; vk::ClearValue painted{};
painted.color.float32 = std::array{1.0f, 0.0f, 1.0f, 1.0f}; painted.color.float32 = std::array{1.0f, 0.0f, 1.0f, 1.0f};
@@ -6305,14 +6324,12 @@ public:
reused_depth.info.stencil.size); reused_depth.info.stencil.size);
const auto reused_depth_id = texture_cache.FindImage(reused_depth); const auto reused_depth_id = texture_cache.FindImage(reused_depth);
(void)texture_cache.FindDepthTarget(reused_depth_id, reused_depth); (void)texture_cache.FindDepthTarget(reused_depth_id, reused_depth);
uint32_t reused_fill = 0;
Require( Require(
name, "DCC allocation reused as HTile", name, "DCC allocation reused as HTile",
TextureCacheTestAccess::Contains(texture_cache, reused_color_id) && TextureCacheTestAccess::Contains(texture_cache, reused_color_id) &&
texture_cache.GetImage(reused_color_id).IsBufferModified() && texture_cache.GetImage(reused_color_id).IsBufferModified() &&
!texture_cache.GetImage(reused_color_id).IsGpuModified() && !texture_cache.GetImage(reused_color_id).IsGpuModified() &&
!texture_cache.IsMetaCleared(base + 0x28000, 0, &reused_fill) && !texture_cache.IsMetaCleared(base + 0x28000, 0) &&
reused_fill == UINT32_MAX &&
texture_cache.ClearMeta(base + 0x28000) && texture_cache.ClearMeta(base + 0x28000) &&
texture_cache.TouchMeta(base + 0x28000, 0, false), texture_cache.TouchMeta(base + 0x28000, 0, false),
"depth binding retained incompatible DCC clear state"); "depth binding retained incompatible DCC clear state");
@@ -8250,14 +8267,13 @@ public:
allocation_alignment) == 0 && allocation_alignment) == 0 &&
mapped == reinterpret_cast<void *>(base), mapped == reinterpret_cast<void *>(base),
"color-volume fixed mapping failed"); "color-volume fixed mapping failed");
std::memset(mapped, 0, allocation_size); std::memset(mapped, 0x5a, allocation_size);
constexpr uint64_t slice_size = 0x10000; constexpr uint64_t slice_size = 0x10000;
std::memset(static_cast<uint8_t *>(mapped) + 31 * slice_size, 0x5a,
slice_size);
{ {
RenderContext context(m_runtime_context); RenderContext context(m_runtime_context);
context.InitializeGpu(nullptr); context.InitializeGpu(nullptr);
LibKernel::Memory::InstallGpuResources(&context);
auto &scheduler = context.GetCommandScheduler(); auto &scheduler = context.GetCommandScheduler();
HW::Context registers{}; HW::Context registers{};
HW::UserConfig user_config{}; HW::UserConfig user_config{};
@@ -8296,7 +8312,6 @@ public:
const auto attachment = const auto attachment =
texture_cache.FindRenderTarget(color.image_id, color.desc); texture_cache.FindRenderTarget(color.image_id, color.desc);
const auto &image = texture_cache.GetImage(color.image_id); const auto &image = texture_cache.GetImage(color.image_id);
uint32_t dcc_clear_value = 0xffffffffu;
Require( Require(
name, "captured 3D target", name, "captured 3D target",
color.image_id && attachment != nullptr && color.image_id && attachment != nullptr &&
@@ -8308,8 +8323,7 @@ public:
color.desc.info.mip_layout[0].size == 0x10000 && color.desc.info.mip_layout[0].size == 0x10000 &&
color.desc.info.metadata.kind == ImageMetadataKind::Dcc && color.desc.info.metadata.kind == ImageMetadataKind::Dcc &&
color.desc.info.metadata.range.address == dcc_address && color.desc.info.metadata.range.address == dcc_address &&
texture_cache.IsMetaCleared(dcc_address, 7, &dcc_clear_value) && !texture_cache.IsMeta(dcc_address) && !texture_cache.ClearMeta(dcc_address) &&
dcc_clear_value == 0 && !texture_cache.ClearMeta(dcc_address) &&
color.desc.view_info.type == vk::ImageViewType::e2D && color.desc.view_info.type == vk::ImageViewType::e2D &&
color.desc.view_info.layer_count == 1 && color.desc.view_info.layer_count == 1 &&
image.backing.image_type == vk::ImageType::e3D && image.backing.image_type == vk::ImageType::e3D &&
@@ -8319,6 +8333,18 @@ public:
"dimension=2/depth=31 did not create the expected 32x32x32 " "dimension=2/depth=31 did not create the expected 32x32x32 "
"backing and 2D " "backing and 2D "
"attachment slice"); "attachment slice");
constexpr uint64_t metadata_slice_size = metadata_size / 32;
std::vector<uint8_t> metadata_bytes(metadata_size);
resources.GetBufferCache().ReadMemory(dcc_address, metadata_size);
Require(name, "first volume slice expanded metadata",
LibKernel::Memory::TryReadBacking(dcc_address, metadata_bytes.data(),
metadata_bytes.size()) &&
std::all_of(metadata_bytes.begin(),
metadata_bytes.begin() + metadata_slice_size,
[](uint8_t byte) { return byte == 0xff; }) &&
std::all_of(metadata_bytes.begin() + metadata_slice_size,
metadata_bytes.end(), [](uint8_t byte) { return byte == 0; }),
"consuming volume slice 0 changed unrequested native metadata slices");
RenderExecutorTestAccess::ResetBindings(executor); RenderExecutorTestAccess::ResetBindings(executor);
registers.SetColorView( registers.SetColorView(
@@ -8336,9 +8362,21 @@ public:
sliced_rendering.color_attachments[0].image_view != nullptr && sliced_rendering.color_attachments[0].image_view != nullptr &&
sliced_color.desc.view_info.base_layer == 7 && sliced_color.desc.view_info.base_layer == 7 &&
sliced_rendering.num_color_attachments == 1 && sliced_rendering.num_color_attachments == 1 &&
sliced_rendering.num_layers == 1 && sliced_rendering.num_layers == 1,
!texture_cache.IsMetaCleared(dcc_address, 7),
"a nonzero 3D attachment slice was treated as a Vulkan array layer"); "a nonzero 3D attachment slice was treated as a Vulkan array layer");
resources.GetBufferCache().ReadMemory(dcc_address, metadata_size);
Require(name, "mixed volume metadata readback",
LibKernel::Memory::TryReadBacking(dcc_address, metadata_bytes.data(),
metadata_bytes.size()),
"mixed volume metadata is unavailable");
for (uint32_t slice = 0; slice < 32; slice++) {
const uint8_t expected = slice == 0 || slice == 7 ? 0xff : 0;
const auto first = metadata_bytes.begin() + slice * metadata_slice_size;
Require(name, "independent volume metadata consumption",
std::all_of(first, first + metadata_slice_size,
[expected](uint8_t byte) { return byte == expected; }),
"mixed DCC metadata lost a pending slice or consumed an unrequested slice");
}
Require(name, "3D slice clear and preservation", Require(name, "3D slice clear and preservation",
ReadCachedTexel(name, context, color.image_id, {0, 0, 7}) == ReadCachedTexel(name, context, color.image_id, {0, 0, 7}) ==
std::vector<u32>{0} && std::vector<u32>{0} &&
@@ -8394,20 +8432,37 @@ public:
"render-target upload/readback lost the final Z slice"); "render-target upload/readback lost the final Z slice");
DestroyBuffer(&slice_probe); DestroyBuffer(&slice_probe);
dcc_clear_value = 0; vk::ClearValue painted{};
painted.color.float32 = std::array{1.0f, 1.0f, 1.0f, 0.0f};
TextureCacheTestAccess::ClearImage(
texture_cache, scheduler.Current(), color.image_id,
{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}, painted);
WriteMetadata(context, dcc_address, metadata_size, 0xffffffffu); WriteMetadata(context, dcc_address, metadata_size, 0xffffffffu);
color.image_id = texture_cache.FindImage(color.desc); color.image_id = texture_cache.FindImage(color.desc);
(void)texture_cache.FindRenderTarget(color.image_id, color.desc); (void)texture_cache.FindRenderTarget(color.image_id, color.desc);
const bool uncompressed = resources.GetBufferCache().ReadMemory(dcc_address, metadata_size);
!texture_cache.IsMetaCleared(dcc_address, 7, &dcc_clear_value) && Require(name, "expanded volume metadata preserves rendering",
dcc_clear_value == 0xffffffffu; ReadCachedTexel(name, context, color.image_id) ==
std::vector<u32>{0x3fffffffu} &&
LibKernel::Memory::TryReadBacking(dcc_address, metadata_bytes.data(),
metadata_bytes.size()) &&
std::all_of(metadata_bytes.begin(), metadata_bytes.end(),
[](uint8_t byte) { return byte == 0xff; }),
"uncompressed native keys reapplied an old volume clear");
WriteMetadata(context, dcc_address, metadata_size, 0); WriteMetadata(context, dcc_address, metadata_size, 0);
color.image_id = texture_cache.FindImage(color.desc); color.image_id = texture_cache.FindImage(color.desc);
(void)texture_cache.FindRenderTarget(color.image_id, color.desc); (void)texture_cache.FindRenderTarget(color.image_id, color.desc);
Require( resources.GetBufferCache().ReadMemory(dcc_address, metadata_size);
name, "registered DCC code state", Require(name, "native volume re-clear",
uncompressed && texture_cache.IsMetaCleared(dcc_address, 7), ReadCachedTexel(name, context, color.image_id) == std::vector<u32>{0} &&
"DCC fill codes were not classified as clear or uncompressed"); LibKernel::Memory::TryReadBacking(dcc_address, metadata_bytes.data(),
metadata_bytes.size()) &&
std::all_of(metadata_bytes.begin(),
metadata_bytes.begin() + metadata_slice_size,
[](uint8_t byte) { return byte == 0xff; }) &&
std::all_of(metadata_bytes.begin() + metadata_slice_size,
metadata_bytes.end(), [](uint8_t byte) { return byte == 0; }),
"a native metadata overwrite did not clear only its requested volume slice");
for (const uint32_t base_slice : {0u, 7u}) { for (const uint32_t base_slice : {0u, 7u}) {
RenderExecutorTestAccess::ResetBindings(executor); RenderExecutorTestAccess::ResetBindings(executor);
@@ -8432,10 +8487,19 @@ public:
scheduler.Current().BeginRendering(volume_rendering); scheduler.Current().BeginRendering(volume_rendering);
scheduler.Current().EndRendering(); scheduler.Current().EndRendering();
} }
resources.GetBufferCache().ReadMemory(dcc_address, metadata_size);
Require(name, "complete volume expansion",
LibKernel::Memory::TryReadBacking(dcc_address, metadata_bytes.data(),
metadata_bytes.size()) &&
std::all_of(metadata_bytes.begin(), metadata_bytes.end(),
[](uint8_t byte) { return byte == 0xff; }),
"full volume acquisition left an unconsumed native clear slice");
RenderExecutorTestAccess::ResetBindings(executor); RenderExecutorTestAccess::ResetBindings(executor);
resources.UnmapMemory(base, allocation_size); resources.UnmapMemory(base, allocation_size);
scheduler.Finish(); scheduler.Finish();
context.ShutdownGpu();
LibKernel::Memory::InstallGpuResources(nullptr);
} }
Require(name, "unmap direct backing", Require(name, "unmap direct backing",
@@ -8570,6 +8634,7 @@ public:
HW::UserConfig user_config{}; HW::UserConfig user_config{};
HW::Shader shaders{}; HW::Shader shaders{};
context.InitializeGpu(nullptr); context.InitializeGpu(nullptr);
LibKernel::Memory::InstallGpuResources(&context);
context.GetGpu().SendCommandSync([&] { context.GetGpu().SendCommandSync([&] {
auto &scheduler = context.GetCommandScheduler(); auto &scheduler = context.GetCommandScheduler();
registers.SetColorBase(0, {.addr = base}); registers.SetColorBase(0, {.addr = base});
@@ -8627,7 +8692,7 @@ public:
color.desc.info.metadata.kind == ImageMetadataKind::Dcc && color.desc.info.metadata.kind == ImageMetadataKind::Dcc &&
color.desc.info.metadata.range.address == dcc_address && color.desc.info.metadata.range.address == dcc_address &&
rendering.num_color_attachments == 1 && rendering.num_color_attachments == 1 &&
!texture_cache.IsMetaCleared(dcc_address, 0), !texture_cache.IsMeta(dcc_address),
"a DCC fixed clear code was not materialised on an RGBA16F " "a DCC fixed clear code was not materialised on an RGBA16F "
"target"); "target");
Require(name, "GPU float clear value", Require(name, "GPU float clear value",
@@ -8694,11 +8759,55 @@ public:
ReadCachedTexel(name, context, color.image_id) == ReadCachedTexel(name, context, color.image_id) ==
std::vector<u32>(fill_case.texel.begin(), fill_case.texel.end()), std::vector<u32>(fill_case.texel.begin(), fill_case.texel.end()),
"completing the metadata overwrite did not restore its clear"); "completing the metadata overwrite did not restore its clear");
ImageDesc previous_depth{};
previous_depth.type = BindingType::DepthTarget;
previous_depth.info.data = {base + 0x180000, 0x10000};
previous_depth.info.pixel_format = vk::Format::eD32Sfloat;
previous_depth.info.guest_format = Prospero::BufferFormat::k32Float;
previous_depth.info.type = Prospero::ImageType::kColor2D;
previous_depth.info.extent = {128, 128, 1};
previous_depth.info.resources = {1, 1};
previous_depth.info.pitch = 128;
previous_depth.info.bytes_per_block = 4;
previous_depth.info.samples = 1;
previous_depth.info.tile_mode = Prospero::TileMode::kLinear;
previous_depth.info.mip_layout[0] = {0, 0x10000, 128, 128};
previous_depth.info.metadata.kind = ImageMetadataKind::Htile;
previous_depth.info.metadata.range = {dcc_address, metadata_size.size};
previous_depth.info.htile_clear_mask = 0;
previous_depth.view_info.format = vk::Format::eD32Sfloat;
previous_depth.view_info.type = vk::ImageViewType::e2D;
previous_depth.view_info.aspect = vk::ImageAspectFlagBits::eDepth;
previous_depth.view_info.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment;
const auto previous_depth_id = texture_cache.FindImage(previous_depth);
Require(name, "old HTile interpretation",
texture_cache.FindDepthTarget(previous_depth_id, previous_depth) != nullptr &&
texture_cache.IsMeta(dcc_address),
"the old depth target did not register its metadata allocation");
paint();
bind();
Require(name, "HTile allocation reused as expanded DCC",
!texture_cache.IsMeta(dcc_address) && !texture_cache.ClearMeta(dcc_address) &&
ReadCachedTexel(name, context, color.image_id) == painted,
"DCC discovery retained the old HTile classification or erased rendered color");
fill_metadata(metadata_words);
bind();
context.GetBufferCache().ReadMemory(dcc_address, metadata_size.size);
Require(name, "native DCC dispatch after HTile reuse",
ReadCachedTexel(name, context, color.image_id) ==
std::vector<u32>(fill_case.texel.begin(), fill_case.texel.end()) &&
LibKernel::Memory::TryReadBacking(dcc_address, expanded_metadata.data(),
expanded_metadata.size()) &&
std::all_of(expanded_metadata.begin(), expanded_metadata.end(),
[](uint8_t byte) { return byte == 0xff; }),
"the former HTile entry swallowed the native DCC fill dispatch");
RenderExecutorTestAccess::ResetBindings(executor); RenderExecutorTestAccess::ResetBindings(executor);
resources.UnmapMemory(base, allocation_size); resources.UnmapMemory(base, allocation_size);
scheduler.Finish(); scheduler.Finish();
}); });
context.ShutdownGpu(); context.ShutdownGpu();
LibKernel::Memory::InstallGpuResources(nullptr);
} }
Require(name, "unmap direct backing", Require(name, "unmap direct backing",
@@ -8737,6 +8846,7 @@ public:
std::memset(reinterpret_cast<void *>(dcc_address), 0xff, metadata_size); std::memset(reinterpret_cast<void *>(dcc_address), 0xff, metadata_size);
RenderContext context(m_runtime_context); RenderContext context(m_runtime_context);
context.InitializeGpu(nullptr); context.InitializeGpu(nullptr);
LibKernel::Memory::InstallGpuResources(&context);
auto &scheduler = context.GetCommandScheduler(); auto &scheduler = context.GetCommandScheduler();
HW::Context registers{}; HW::Context registers{};
HW::UserConfig user_config{}; HW::UserConfig user_config{};
@@ -8785,9 +8895,16 @@ public:
} }
Require(name, "opaque black before first sample", Require(name, "opaque black before first sample",
ReadCachedTexel(name, context, binding.image_id, {3839, 2159, 0}) == ReadCachedTexel(name, context, binding.image_id, {3839, 2159, 0}) ==
std::vector<u32>{0, 0x3c000000u} && std::vector<u32>{0, 0x3c000000u},
!cache.IsMetaCleared(dcc_address, 0),
"sampling read stale color bytes instead of the DCC opaque black clear"); "sampling read stale color bytes instead of the DCC opaque black clear");
context.GetBufferCache().ReadMemory(dcc_address, metadata_size);
std::vector<uint8_t> expanded_metadata(metadata_size);
Require(name, "sampled clear expands native metadata",
LibKernel::Memory::TryReadBacking(dcc_address, expanded_metadata.data(),
expanded_metadata.size()) &&
std::all_of(expanded_metadata.begin(), expanded_metadata.end(),
[](uint8_t byte) { return byte == 0xff; }),
"the sampled clear retained native clear keys after materialization");
WriteMetadata(context, dcc_address, metadata_size, 0x80808080u); WriteMetadata(context, dcc_address, metadata_size, 0x80808080u);
binding.image_id = cache.FindImage(binding.desc); binding.image_id = cache.FindImage(binding.desc);
(void)cache.FindTexture(binding.image_id, binding.desc); (void)cache.FindTexture(binding.image_id, binding.desc);
@@ -8797,6 +8914,8 @@ public:
"an existing sampled image retained the previous DCC clear"); "an existing sampled image retained the previous DCC clear");
resources.UnmapMemory(base, allocation_size); resources.UnmapMemory(base, allocation_size);
scheduler.Finish(); scheduler.Finish();
context.ShutdownGpu();
LibKernel::Memory::InstallGpuResources(nullptr);
} }
Require(name, "unmap", Require(name, "unmap",
Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0, Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0,
@@ -14243,6 +14362,20 @@ private:
if (m_runtime_context.allocator != nullptr) { if (m_runtime_context.allocator != nullptr) {
return; return;
} }
namespace Exception = Common::HostException;
Require("VulkanHarness", "guest fault handler",
Exception::InstallHandler([](const Exception::ExceptionInfo &info) {
if (info.type != Exception::ExceptionType::AccessViolation ||
(info.access_violation_type != Exception::AccessViolationType::Read &&
info.access_violation_type != Exception::AccessViolationType::Write)) {
return false;
}
const auto access =
info.access_violation_type == Exception::AccessViolationType::Write
? PageFaultAccess::Write : PageFaultAccess::Read;
return LibKernel::Memory::HandleGpuFault(access, info.access_violation_vaddr);
}),
"failed to install the production guest-memory fault route");
m_runtime_context.instance = m_instance; m_runtime_context.instance = m_instance;
m_runtime_context.physical_device = m_physical_device; m_runtime_context.physical_device = m_physical_device;
m_runtime_context.device = m_device; m_runtime_context.device = m_device;