texture_cache: apply DCC fast clears of mipmapped color targets

Co-authored-by: Artyom Zabroda <artyomzabroda@gmail.com>
This commit is contained in:
nmzikandArtyom Zabroda committed 2026-10-30 04:05:24 +01:00
1 parent ae67e2db51
commit a5cfdcfd32
7 files changed
+184 -74

No files matched your search

+48 -38
View File
@@ -259,6 +259,17 @@ static bool GetMipTailLayout(const TileBlockLayout& block, MipTailLayout& out) {
return false; return false;
} }
static uint32_t FirstTailLevel(const MipTailLayout& tail, uint32_t width, uint32_t height,
uint32_t levels) {
for (uint32_t level = 0; levels > 1 && level < levels; ++level) {
if (ShiftCeil(width, level) <= tail.width_limit &&
ShiftCeil(height, level) <= tail.height_limit && levels - level <= tail.max_levels) {
return level;
}
}
return levels;
}
bool TileGetTextureElementLayout(Prospero::BufferFormat format, TileTextureElementLayout& out) { bool TileGetTextureElementLayout(Prospero::BufferFormat format, TileTextureElementLayout& out) {
if (const auto bytes = Prospero::NumBytesPerElement(format); std::has_single_bit(bytes)) { if (const auto bytes = Prospero::NumBytesPerElement(format); std::has_single_bit(bytes)) {
out = {bytes, 1, 1}; out = {bytes, 1, 1};
@@ -336,23 +347,14 @@ bool TileGetTiledTextureLayout(const TileSurfaceDescription& description, TileSu
TileSurfaceLayout result {}; TileSurfaceLayout result {};
result.description = description; result.description = description;
result.texture = texture; result.texture = texture;
result.first_tail_level = description.levels;
const uint32_t block_slices = const uint32_t block_slices =
volume ? ShiftCeil(description.depth, std::countr_zero(block.block_depth)) volume ? ShiftCeil(description.depth, std::countr_zero(block.block_depth))
: description.layers; : description.layers;
MipTailLayout tail {}; MipTailLayout tail {};
const bool has_tail = GetMipTailLayout(block, tail); result.first_tail_level = GetMipTailLayout(block, tail)
if (has_tail && description.levels > 1) { ? FirstTailLevel(tail, width0, height0, description.levels)
for (uint32_t level = 0; level < description.levels; ++level) { : description.levels;
if (ShiftCeil(width0, level) <= tail.width_limit &&
ShiftCeil(height0, level) <= tail.height_limit &&
description.levels - level <= tail.max_levels) {
result.first_tail_level = level;
break;
}
}
}
for (uint32_t level = 0; level < result.first_tail_level; ++level) { for (uint32_t level = 0; level < result.first_tail_level; ++level) {
auto& mip = result.mips[level]; auto& mip = result.mips[level];
@@ -1213,44 +1215,52 @@ bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch,
return true; return true;
} }
static bool TileGetColorMetadataSize(uint32_t width, uint32_t height, uint32_t slices,
uint32_t block_width, uint32_t block_height,
TileSizeAlign& total_size) {
if (width == 0 || height == 0 || slices == 0) {
return false;
}
const uint64_t blocks_x = (static_cast<uint64_t>(width) + block_width - 1u) / block_width;
const uint64_t blocks_y = (static_cast<uint64_t>(height) + block_height - 1u) / block_height;
const uint64_t blocks = blocks_x * blocks_y;
if (blocks > UINT32_MAX / 4096u / slices) {
return false;
}
total_size.size = static_cast<uint32_t>(blocks * slices * 4096u);
total_size.align = 4096;
return true;
}
bool TileGetDccSize(uint32_t width, uint32_t height, uint32_t slices, bool TileGetDccSize(uint32_t width, uint32_t height, uint32_t slices,
uint32_t bytes_per_element, uint32_t levels, Prospero::TileMode tile, uint32_t bytes_per_element, uint32_t levels, Prospero::TileMode tile,
TileSizeAlign& total_size, uint32_t num_fragments_log2) { TileSizeAlign& total_size, uint32_t num_fragments_log2, TileSizeOffset* mips) {
total_size = {}; total_size = {};
if (levels != 1 || num_fragments_log2 != 0 || TileBlockLayout block {};
!std::has_single_bit(bytes_per_element) || bytes_per_element > 16 || MipTailLayout tail {};
(tile != Prospero::TileMode::kRenderTarget && tile != Prospero::TileMode::kDepth)) { if (slices == 0 || levels > 16 || num_fragments_log2 != 0 ||
(tile != Prospero::TileMode::kRenderTarget && tile != Prospero::TileMode::kDepth) ||
!TileGetBlockLayout(tile == Prospero::TileMode::kDepth ? TileBlockFamily::Depth64KB
: TileBlockFamily::RenderTarget64KB,
bytes_per_element, block) ||
!GetMipTailLayout(block, tail)) {
return false; return false;
} }
// Gen5 DCC uses 4 KiB blocks, with one byte per 256 bytes of color data. // Gen5 DCC uses 4 KiB blocks, with one byte per 256 bytes of color data. Each slice stores
// Metadata stays thin for volume surfaces, so every depth slice has its own block raster. // its mips backwards through the first tail mip, which fits one block; later tail mips have
// none. Metadata stays thin for volume surfaces, so every depth slice has its own block raster.
const uint32_t coverage_bits = 20u - std::countr_zero(bytes_per_element); const uint32_t coverage_bits = 20u - std::countr_zero(bytes_per_element);
return TileGetColorMetadataSize(width, height, slices, 1u << ((coverage_bits + 1u) / 2u), uint64_t slice_size = 0;
1u << (coverage_bits / 2u), total_size); for (uint32_t level = std::min(FirstTailLevel(tail, width, height, levels) + 1u, levels);
level-- > 0;) {
const uint64_t size = uint64_t {ShiftCeil(width, level + (coverage_bits + 1u) / 2u)} *
ShiftCeil(height, level + coverage_bits / 2u) * 4096u;
if (mips != nullptr) {
mips[level] = {.size = static_cast<uint32_t>(size),
.offset = static_cast<uint32_t>(slice_size)};
}
slice_size += size;
}
if (slice_size == 0 || slice_size > UINT32_MAX / slices) {
return false;
}
total_size = {static_cast<uint32_t>(slice_size * slices), 4096};
return true;
} }
bool TileGetCmaskSize(uint32_t width, uint32_t height, uint32_t slices, uint32_t levels, bool TileGetCmaskSize(uint32_t width, uint32_t height, uint32_t slices, uint32_t levels,
TileSizeAlign& total_size) { TileSizeAlign& total_size) {
total_size = {}; total_size = {};
// Gen5 CMASK stores four bits per 8x8 tile in 4 KiB blocks covering 1024x512 pixels. // Gen5 CMASK stores four bits per 8x8 tile in 4 KiB blocks covering 1024x512 pixels.
return levels == 1 && TileGetColorMetadataSize(width, height, slices, 1024, 512, total_size); const uint64_t blocks = uint64_t {ShiftCeil(width, 10)} * ShiftCeil(height, 9);
if (levels != 1 || blocks == 0 || slices == 0 || blocks > UINT32_MAX / 4096u / slices) {
return false;
}
total_size = {static_cast<uint32_t>(blocks * slices * 4096u), 4096};
return true;
} }
void TileGetTextureSize(Prospero::BufferFormat format, uint32_t width, uint32_t height, void TileGetTextureSize(Prospero::BufferFormat format, uint32_t width, uint32_t height,
+3 -1
View File
@@ -123,9 +123,11 @@ uint32_t TileGetDepthPitch(uint32_t width, uint32_t bytes_per_element,
bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch, bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch,
uint32_t bytes_per_element, TileSizeAlign& total_size, uint32_t bytes_per_element, TileSizeAlign& total_size,
uint32_t num_fragments_log2 = 0); uint32_t num_fragments_log2 = 0);
// mips, when given, receives the size and offset within a slice of each level that has metadata.
bool TileGetDccSize(uint32_t width, uint32_t height, uint32_t slices, bool TileGetDccSize(uint32_t width, uint32_t height, uint32_t slices,
uint32_t bytes_per_element, uint32_t levels, Prospero::TileMode tile, uint32_t bytes_per_element, uint32_t levels, Prospero::TileMode tile,
TileSizeAlign& total_size, uint32_t num_fragments_log2 = 0); TileSizeAlign& total_size, uint32_t num_fragments_log2 = 0,
TileSizeOffset* mips = nullptr);
bool TileGetCmaskSize(uint32_t width, uint32_t height, uint32_t slices, uint32_t levels, bool TileGetCmaskSize(uint32_t width, uint32_t height, uint32_t slices, uint32_t levels,
TileSizeAlign& total_size); TileSizeAlign& total_size);
void TileGetTextureSize(Prospero::BufferFormat format, uint32_t width, uint32_t height, void TileGetTextureSize(Prospero::BufferFormat format, uint32_t width, uint32_t height,
+33 -12
View File
@@ -1044,33 +1044,50 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
return; return;
} }
const auto range = desc.info.metadata.range; const auto range = desc.info.metadata.range;
const auto& view = desc.view_info;
{ {
std::scoped_lock lock {m_lock}; std::scoped_lock lock {m_lock};
auto& image = m_slot_images[id]; auto& image = m_slot_images[id];
image.info.metadata = desc.info.metadata; image.info.metadata = desc.info.metadata;
// Native color metadata must not retain a reused HTile clear flag. // Native color metadata must not retain a reused HTile clear flag.
m_surface_metas.erase(range.address); m_surface_metas.erase(range.address);
if (range.size == 0 || desc.info.resources.levels != 1) { if (range.size == 0 || view.level_count != 1) {
return; return;
} }
} }
const auto layers = desc.info.TransferLayers(); const auto layers = desc.info.TransferLayers();
// These one-mip surfaces use complete 4 KiB color metadata blocks. // Color metadata slices consist of complete 4 KiB blocks.
constexpr uint64_t MetadataBlockSize = 0x1000; constexpr uint64_t MetadataBlockSize = 0x1000;
if (!range.Valid() || range.address % MetadataBlockSize != 0 || layers == 0 || if (!range.Valid() || range.address % MetadataBlockSize != 0 || layers == 0 ||
range.size % layers != 0 || (range.size / layers) % MetadataBlockSize != 0) { range.size % layers != 0 || (range.size / layers) % MetadataBlockSize != 0) {
EXIT("TextureCache: color metadata slices must contain aligned 4 KiB blocks\n"); EXIT("TextureCache: color metadata slices must contain aligned 4 KiB blocks\n");
} }
const auto& view = desc.view_info;
const bool volume_texture = desc.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 ? desc.info.extent.depth : view.layer_count; const auto count =
volume_texture ? std::max(desc.info.extent.depth >> view.base_level, 1u) : view.layer_count;
if (first >= layers || count > layers - first) { if (first >= layers || count > layers - first) {
EXIT("TextureCache: color view exceeds its native metadata slices\n"); EXIT("TextureCache: color view exceeds its native metadata slices\n");
} }
const auto slice_size = range.size / layers; const auto slice_size = range.size / layers;
const GuestRange viewed {range.address + slice_size * first, slice_size * count}; // Each slice holds the viewed mip's metadata in one run; DCC mips past the first tail mip
// have none. FindImage moves base_level only for one-mip descriptors, which keep the slice.
TileSizeOffset mip {.size = static_cast<uint32_t>(slice_size)};
if (desc.info.metadata.kind == ImageMetadataKind::Dcc && desc.info.resources.levels > 1) {
std::array<TileSizeOffset, 16> mips {};
TileSizeAlign total {};
(void)TileGetDccSize(desc.info.extent.width, desc.info.extent.height, layers,
desc.info.bytes_per_block, desc.info.resources.levels,
desc.info.tile_mode, total, 0, mips.data());
mip = mips[view.base_level];
}
if (mip.size == 0) {
return;
}
const GuestRange viewed {range.address + slice_size * first + mip.offset,
slice_size * (count - 1) + mip.size};
const auto run = [&](uint32_t slice) { return viewed.address + slice_size * slice; };
if (m_buffer_cache.IsRegionGpuModified(viewed.address, viewed.size)) { if (m_buffer_cache.IsRegionGpuModified(viewed.address, viewed.size)) {
std::array<vk::ClearAttachment, TileManager::ColorClearSlots> clears {}; std::array<vk::ClearAttachment, TileManager::ColorClearSlots> clears {};
uint32_t keys = 0; uint32_t keys = 0;
@@ -1082,7 +1099,11 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
} }
} }
// No selected key to clear, or none left since the last check. // No selected key to clear, or none left since the last check.
if (m_buffer_cache.IsColorMetadataChecked(viewed.address, viewed.size, keys)) { bool checked = true;
for (uint32_t slice = 0; slice < count && checked; slice++) {
checked = m_buffer_cache.IsColorMetadataChecked(run(slice), mip.size, keys);
}
if (checked) {
return; return;
} }
{ {
@@ -1107,9 +1128,9 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
const auto [buffer, offset] = const auto [buffer, offset] =
m_buffer_cache.ObtainBuffer(viewed.address, viewed.size, expand); m_buffer_cache.ObtainBuffer(viewed.address, viewed.size, expand);
const auto predicates = m_tiler.CheckColorClear( const auto predicates = m_tiler.CheckColorClear(
{buffer->Handle(), offset, viewed.size}, count, keys, expand); {buffer->Handle(), offset, viewed.size}, slice_size, count, keys, expand);
if (expand) { for (uint32_t slice = 0; slice < count && expand; slice++) {
m_buffer_cache.MarkColorMetadataChecked(viewed.address, viewed.size, keys); m_buffer_cache.MarkColorMetadataChecked(run(slice), mip.size, keys);
} }
const auto command = m_scheduler.Current().Handle(); const auto command = m_scheduler.Current().Handle();
const auto area = BeginColorRendering(image, view.format, view.base_level, const auto area = BeginColorRendering(image, view.format, view.base_level,
@@ -1142,7 +1163,7 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
m_buffer_cache.ReadMemory(viewed.address, viewed.size, false); m_buffer_cache.ReadMemory(viewed.address, viewed.size, false);
} }
for (uint32_t slice = 0; slice < count; slice++) { for (uint32_t slice = 0; slice < count; slice++) {
const auto address = range.address + slice_size * (first + slice); const auto address = run(slice);
uint8_t code = 0; uint8_t code = 0;
if (!LibKernel::Memory::TryReadBacking(address, &code, sizeof(code))) { if (!LibKernel::Memory::TryReadBacking(address, &code, sizeof(code))) {
EXIT("TextureCache: failed to read color metadata backing\n"); EXIT("TextureCache: failed to read color metadata backing\n");
@@ -1151,7 +1172,7 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
if (!DecodeColorClear(desc, code, clear.color)) { if (!DecodeColorClear(desc, code, clear.color)) {
continue; continue;
} }
std::vector<uint8_t> bytes(slice_size); std::vector<uint8_t> bytes(mip.size);
if (!LibKernel::Memory::TryReadBacking(address, bytes.data(), bytes.size())) { if (!LibKernel::Memory::TryReadBacking(address, bytes.data(), bytes.size())) {
EXIT("TextureCache: failed to read color metadata slice\n"); EXIT("TextureCache: failed to read color metadata slice\n");
} }
@@ -1175,7 +1196,7 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
// to overlapping image data. Invalidate the buffer before updating its backing. // to overlapping image data. Invalidate the buffer before updating its backing.
if (desc.type != BindingType::VideoOut) { if (desc.type != BindingType::VideoOut) {
std::fill(bytes.begin(), bytes.end(), uint8_t {0xff}); std::fill(bytes.begin(), bytes.end(), uint8_t {0xff});
m_buffer_cache.InvalidateMemory(address, slice_size); m_buffer_cache.InvalidateMemory(address, bytes.size());
LibKernel::Memory::WriteBacking(address, bytes.data(), bytes.size()); LibKernel::Memory::WriteBacking(address, bytes.data(), bytes.size());
} }
} }
@@ -630,19 +630,20 @@ TileManager::Result TileManager::TransformColor(Result input, ColorTransform tra
return result; return result;
} }
vk::Buffer TileManager::CheckColorClear(Result metadata, uint32_t slices, uint32_t keys, vk::Buffer TileManager::CheckColorClear(Result metadata, uint64_t stride, uint32_t slices,
bool expand) { uint32_t keys, bool expand) {
struct Check { struct Check {
uint32_t metadata_base; uint32_t metadata_base;
uint32_t words; uint32_t words;
uint32_t stride;
uint32_t keys; uint32_t keys;
uint32_t expand; uint32_t expand;
}; };
static_assert(sizeof(Check) <= sizeof(Conversion)); static_assert(sizeof(Check) <= sizeof(Conversion));
const uint32_t max_slices = const uint32_t max_slices =
m_graphics.GetPhysicalDeviceProperties().limits.maxFramebufferLayers; m_graphics.GetPhysicalDeviceProperties().limits.maxFramebufferLayers;
EXIT_IF(slices == 0 || slices > max_slices || metadata.size % (4ull * slices) != 0 || EXIT_IF(slices == 0 || slices > max_slices || metadata.size % 4 != 0 || stride % 4 != 0 ||
metadata.size / 4 > UINT32_MAX); metadata.size / 4 > UINT32_MAX || stride * (slices - 1) >= metadata.size);
if (m_color_clear == nullptr) { if (m_color_clear == nullptr) {
m_color_clear = CreatePipeline(GPU_TILER_COLOR_CLEAR_SPV, nullptr); m_color_clear = CreatePipeline(GPU_TILER_COLOR_CLEAR_SPV, nullptr);
m_clear_predicates = std::make_unique<Buffer>( m_clear_predicates = std::make_unique<Buffer>(
@@ -673,8 +674,9 @@ vk::Buffer TileManager::CheckColorClear(Result metadata, uint32_t slices, uint32
command.bindPipeline(vk::PipelineBindPoint::eCompute, m_color_clear); command.bindPipeline(vk::PipelineBindPoint::eCompute, m_color_clear);
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, m_pipeline_layout, 0, command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, m_pipeline_layout, 0,
static_cast<uint32_t>(writes.size()), writes.data()); static_cast<uint32_t>(writes.size()), writes.data());
const Check push {source.base, static_cast<uint32_t>(metadata.size / 4 / slices), keys, const Check push {source.base,
expand}; static_cast<uint32_t>((metadata.size - stride * (slices - 1)) / 4),
static_cast<uint32_t>(stride / 4), keys, expand};
command.pushConstants(m_pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0, sizeof(push), command.pushConstants(m_pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0, sizeof(push),
&push); &push);
command.dispatch(slices, 1, 1); command.dispatch(slices, 1, 1);
+5 -5
View File
@@ -56,11 +56,11 @@ public:
void TransformColor(Result input, Result output, ColorTransform transform, bool to_host); void TransformColor(Result input, Result output, ColorTransform transform, bool to_host);
// Color-clear keys are multiples of 0x20, so key >> 5 selects one of these slots. // Color-clear keys are multiples of 0x20, so key >> 5 selects one of these slots.
static constexpr uint32_t ColorClearSlots = 8; static constexpr uint32_t ColorClearSlots = 8;
// Returns the buffer whose dword slice * ColorClearSlots + (key >> 5) is set when every byte // metadata spans `slices` equal runs `stride` bytes apart. Returns the buffer whose dword
// of that metadata slice equals a key whose bit `keys` selects; a hit expands to 0xff when // slice * ColorClearSlots + (key >> 5) is set when every byte of that run equals a key whose
// `expand` is set. // bit `keys` selects; a hit expands to 0xff when `expand` is set.
[[nodiscard]] vk::Buffer CheckColorClear(Result metadata, uint32_t slices, uint32_t keys, [[nodiscard]] vk::Buffer CheckColorClear(Result metadata, uint64_t stride, uint32_t slices,
bool expand); uint32_t keys, bool expand);
private: private:
friend struct TileManagerTestAccess; friend struct TileManagerTestAccess;
@@ -9,6 +9,7 @@ layout(set = 0, binding = 1, std430) writeonly buffer Predicates { uint data[];
layout(push_constant, std430) uniform Check { layout(push_constant, std430) uniform Check {
uint metadata_base; uint metadata_base;
uint words; uint words;
uint stride;
uint keys; uint keys;
uint expand; uint expand;
} params; } params;
@@ -20,7 +21,7 @@ shared uint mismatch;
void main() { void main() {
const uint slice = gl_WorkGroupID.x; const uint slice = gl_WorkGroupID.x;
const uint base = (params.metadata_base >> 2u) + slice * params.words; const uint base = (params.metadata_base >> 2u) + slice * params.stride;
// Each invocation reads, and may rewrite, only its own words. // Each invocation reads, and may rewrite, only its own words.
if (gl_LocalInvocationIndex == 0u) { if (gl_LocalInvocationIndex == 0u) {
key = metadata.data[base] & 0xffu; key = metadata.data[base] & 0xffu;
+85 -11
View File
@@ -12712,6 +12712,15 @@ public:
DecodeColorDwordFill(vk::Format::eR16G16B16A16Uint, 0x22221111u, clear) && DecodeColorDwordFill(vk::Format::eR16G16B16A16Uint, 0x22221111u, clear) &&
clear.uint32[2] == 0x1111u && clear.uint32[3] == 0x2222u, clear.uint32[2] == 0x1111u && clear.uint32[3] == 0x2222u,
"a DWORD fill did not repeat across the 64-bit pixel"); "a DWORD fill did not repeat across the 64-bit pixel");
std::array<TileSizeOffset, 16> chain{};
TileSizeAlign chain_size{};
Require(name, "DCC mip chain layout",
TileGetDccSize(512, 256, 2, 8, 5, Prospero::TileMode::kRenderTarget, chain_size, 0,
chain.data()) &&
chain_size.size == 0x8000 && chain[0].offset == 0x3000 &&
chain[1].offset == 0x2000 && chain[2].offset == 0x1000 && chain[3].offset == 0 &&
chain[3].size == 0x1000 && chain[4].size == 0,
"a DCC slice did not store its mips backwards after the first tail mip's block");
constexpr uintptr_t base = 0x0000000204100000ull; constexpr uintptr_t base = 0x0000000204100000ull;
constexpr uint64_t allocation_size = 0x1000000; constexpr uint64_t allocation_size = 0x1000000;
constexpr uint64_t allocation_alignment = 0x10000; constexpr uint64_t allocation_alignment = 0x10000;
@@ -12727,6 +12736,7 @@ public:
bool eliminate_fast_clear = false; bool eliminate_fast_clear = false;
uint32_t samples_log2 = 0; uint32_t samples_log2 = 0;
bool goat = false; bool goat = false;
bool dcc_mips = false;
}; };
constexpr std::array cases{ constexpr std::array cases{
FillCase{0x40404040u, {0, 0x3c000000u}}, FillCase{0x40404040u, {0, 0x3c000000u}},
@@ -12767,6 +12777,8 @@ public:
FillCase{.fill = 0, .texel = {0, 0x3c000000u}, .cmask = true, FillCase{.fill = 0, .texel = {0, 0x3c000000u}, .cmask = true,
.eliminate_fast_clear = true, .samples_log2 = 2}, .eliminate_fast_clear = true, .samples_log2 = 2},
FillCase{.fill = 0x40404040u, .texel = {0, 0x3c000000u}, .eliminate_fast_clear = true}, FillCase{.fill = 0x40404040u, .texel = {0, 0x3c000000u}, .eliminate_fast_clear = true},
FillCase{.fill = 0x40404040u, .texel = {0, 0x3c000000u}, .reuse_mips = true,
.dcc_mips = true},
}; };
EnsureRuntimeContext(); EnsureRuntimeContext();
// Astro's generic metadata fill, through S_ENDPGM; trailing debug data is omitted. // Astro's generic metadata fill, through S_ENDPGM; trailing debug data is omitted.
@@ -12832,13 +12844,19 @@ public:
const auto bytes_per_pixel = TextureGetRenderTargetFormat( const auto bytes_per_pixel = TextureGetRenderTargetFormat(
layout, type, Prospero::ChannelOrder::kStandard).bytes_per_element; layout, type, Prospero::ChannelOrder::kStandard).bytes_per_element;
const uint32_t probe_width = bytes_per_pixel == 2 ? 2 : 1; const uint32_t probe_width = bytes_per_pixel == 2 ? 2 : 1;
const uint32_t selected_layer = fill_case.cmask || fill_case.eliminate_fast_clear ? 1 : 0; const uint32_t selected_layer =
fill_case.cmask || fill_case.eliminate_fast_clear || fill_case.dcc_mips ? 1 : 0;
TileSizeAlign dcc_size{}; TileSizeAlign dcc_size{};
(void)TileGetDccSize(512, 256, selected_layer + 1, bytes_per_pixel, 1, (void)TileGetDccSize(512, 256, selected_layer + 1, bytes_per_pixel,
Prospero::TileMode::kRenderTarget, dcc_size, fill_case.samples_log2); fill_case.dcc_mips ? 2 : 1, Prospero::TileMode::kRenderTarget, dcc_size,
const uint64_t dcc_address = base + (fill_case.samples_log2 != 0 ? 0x800000 : fill_case.samples_log2);
fill_case.eliminate_fast_clear ? 0x200000 : uint64_t dcc_offset = fill_case.reuse_mips ? 0x300000 : 0x100000;
fill_case.reuse_mips ? 0x160000 : 0x100000); if (fill_case.samples_log2 != 0) {
dcc_offset = 0x800000;
} else if (fill_case.eliminate_fast_clear) {
dcc_offset = 0x200000;
}
const uint64_t dcc_address = base + dcc_offset;
const uint64_t metadata_size = fill_case.cmask ? 0x2000 : dcc_size.size; const uint64_t metadata_size = fill_case.cmask ? 0x2000 : dcc_size.size;
const std::vector<u32> expected( const std::vector<u32> expected(
fill_case.texel.begin(), fill_case.texel.begin() + std::max(bytes_per_pixel / 4u, 1u)); fill_case.texel.begin(), fill_case.texel.begin() + std::max(bytes_per_pixel / 4u, 1u));
@@ -12862,7 +12880,8 @@ public:
.last_array_slice_index = selected_layer}); .last_array_slice_index = selected_layer});
registers.SetColorAttrib(0, {.num_samples = fill_case.samples_log2, registers.SetColorAttrib(0, {.num_samples = fill_case.samples_log2,
.num_fragments = fill_case.samples_log2}); .num_fragments = fill_case.samples_log2});
registers.SetColorAttrib2(0, {.height = 255, .width = 511}); registers.SetColorAttrib2(
0, {.height = 255, .width = 511, .num_mip_levels = fill_case.dcc_mips ? 1u : 0u});
registers.SetColorAttrib3(0, registers.SetColorAttrib3(0,
{.tile_mode = Prospero::TileMode::kRenderTarget, {.tile_mode = Prospero::TileMode::kRenderTarget,
.dimension = 1, .dimension = 1,
@@ -12909,7 +12928,7 @@ public:
clear.color.float32 = std::array{1.0f, 0.0f, 1.0f, 0.0f}; clear.color.float32 = std::array{1.0f, 0.0f, 1.0f, 0.0f};
TextureCacheTestAccess::ClearImage( TextureCacheTestAccess::ClearImage(
texture_cache, scheduler.Current(), preexisting_id, texture_cache, scheduler.Current(), preexisting_id,
{vk::ImageAspectFlagBits::eColor, 0, 2, 0, 1}, clear); {vk::ImageAspectFlagBits::eColor, 0, 2, 0, selected_layer + 1}, clear);
} }
if (fill_case.eliminate_fast_clear) { if (fill_case.eliminate_fast_clear) {
vk::ClearValue stale{}; vk::ClearValue stale{};
@@ -12963,7 +12982,10 @@ public:
Require(name, "expanded metadata readback", Require(name, "expanded metadata readback",
LibKernel::Memory::TryReadBacking(dcc_address, bytes.data(), bytes.size()), LibKernel::Memory::TryReadBacking(dcc_address, bytes.data(), bytes.size()),
"native color metadata could not be read back"); "native color metadata could not be read back");
const auto selected = bytes.begin() + metadata_size * selected_layer / (selected_layer + 1); // Mip 1's metadata block precedes mip 0's in each slice.
const auto selected = bytes.begin() +
metadata_size * selected_layer / (selected_layer + 1) +
(fill_case.dcc_mips ? 0x1000 : 0);
Require(name, "selected metadata slice expansion", Require(name, "selected metadata slice expansion",
std::all_of(bytes.begin(), selected, std::all_of(bytes.begin(), selected,
[&](uint8_t v) { return v == static_cast<uint8_t>(fill_case.fill); }) && [&](uint8_t v) { return v == static_cast<uint8_t>(fill_case.fill); }) &&
@@ -13055,12 +13077,12 @@ public:
const auto &image = texture_cache.GetImage(color.image_id); const auto &image = texture_cache.GetImage(color.image_id);
Require(name, "single-mip clear reuses mip chain", Require(name, "single-mip clear reuses mip chain",
color.image_id == preexisting_id && image.info.resources.levels == 2 && color.image_id == preexisting_id && image.info.resources.levels == 2 &&
color.desc.info.resources.levels == 1 && color.desc.info.resources.levels == (fill_case.dcc_mips ? 2u : 1u) &&
color.desc.view_info.base_level == 0 && color.desc.view_info.level_count == 1, color.desc.view_info.base_level == 0 && color.desc.view_info.level_count == 1,
"the single-mip target did not retain its cached image and view scope"); "the single-mip target did not retain its cached image and view scope");
Require(name, "single-mip clear preserves higher mip", Require(name, "single-mip clear preserves higher mip",
ReadCachedTexel(name, context, color.image_id, {255, 127, 0}, ReadCachedTexel(name, context, color.image_id, {255, 127, 0},
{1, 1, 1}, 0, 1) == {1, 1, 1}, selected_layer, 1) ==
std::vector<u32>{0x00003c00u, 0x00003c00u}, std::vector<u32>{0x00003c00u, 0x00003c00u},
"clearing mip zero changed its higher mip"); "clearing mip zero changed its higher mip");
} }
@@ -13269,6 +13291,58 @@ public:
Require(name, "native color clear after HTile reuse", read_texel() == expected, Require(name, "native color clear after HTile reuse", read_texel() == expected,
"the former HTile entry swallowed the native color metadata fill"); "the former HTile entry swallowed the native color metadata fill");
check_expanded_metadata(); check_expanded_metadata();
if (fill_case.dcc_mips) {
// A layered view checks and expands each slice's mip-0 run, one slice apart.
registers.SetColorView(0, {.base_array_slice_index = 0, .last_array_slice_index = 1});
RenderExecutorTestAccess::ResetBindings(executor);
RenderExecutorTestAccess::ResolveRenderColorTarget(
executor, scheduler.Current(), color, 0);
fill_metadata(metadata_words);
bind();
context.GetBufferCache().ReadMemory(dcc_address, metadata_size, false);
std::vector<uint8_t> bytes(metadata_size);
bool runs = LibKernel::Memory::TryReadBacking(dcc_address, bytes.data(), bytes.size());
for (uint64_t i = 0; i < bytes.size(); i++) {
runs &= bytes[i] == ((i & 0x1000) != 0 ? 0xff : static_cast<uint8_t>(fill_case.fill));
}
Require(name, "layered mip-0 clear",
runs && read_texel() == expected &&
ReadCachedTexel(name, context, color.image_id, {}, {1, 1, 1}, 0) == expected,
"a layered mip view missed a slice or expanded another mip's metadata");
// A one-mip target at mip 1's address reads its own metadata and clears that mip.
const auto chain_id = color.image_id;
const uint64_t mip_dcc = dcc_address + 0x10000;
registers.SetColorAttrib2(0, {.height = 127, .width = 255});
registers.SetColorView(0, {});
registers.SetColorDccAddr(0, {.addr = mip_dcc});
WriteMetadata(context, mip_dcc, 0x1000, fill_case.fill);
RenderExecutorTestAccess::ResetBindings(executor);
RenderExecutorTestAccess::ResolveRenderColorTarget(
executor, scheduler.Current(), color, 0);
Require(name, "one-mip clear of a cached mip",
color.image_id == chain_id && color.desc.view_info.base_level == 1 &&
ReadCachedTexel(name, context, chain_id, {}, {1, 1, 1}, 0, 1) == expected,
"a one-mip target matched to a cached mip skipped its metadata clear");
// A volume's mip-1 view clears that mip's depth slices from their first tail blocks.
constexpr uint64_t volume_dcc = base + 0x320000;
auto volume = color.desc;
volume.type = BindingType::Texture;
volume.info.data = {base + 0x400000, 0};
volume.info.type = Prospero::ImageType::kColor3D;
volume.info.extent = {128, 64, 4};
volume.info.resources = {2, 1};
volume.info.UpdateSize();
volume.info.metadata.range = {volume_dcc, 0x8000};
volume.view_info.type = vk::ImageViewType::e3D;
volume.view_info.base_level = 1;
volume.view_info.usage = vk::ImageUsageFlagBits::eSampled;
WriteMetadata(context, volume_dcc, 0x8000, fill_case.fill);
const auto volume_id = texture_cache.FindImage(volume);
Require(name, "volume mip clear",
ReadCachedTexel(name, context, volume_id, {0, 0, 1}, {1, 1, 1}, 0, 1) ==
expected,
"a volume mip view missed its depth slices' clear");
}
RenderExecutorTestAccess::ResetBindings(executor); RenderExecutorTestAccess::ResetBindings(executor);
resources.UnmapMemory(base, allocation_size); resources.UnmapMemory(base, allocation_size);
scheduler.Finish(); scheduler.Finish();