diff --git a/src/graphics/guest_gpu/tile.cpp b/src/graphics/guest_gpu/tile.cpp index 7043ca40c..9094292a4 100644 --- a/src/graphics/guest_gpu/tile.cpp +++ b/src/graphics/guest_gpu/tile.cpp @@ -259,6 +259,17 @@ static bool GetMipTailLayout(const TileBlockLayout& block, MipTailLayout& out) { 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) { if (const auto bytes = Prospero::NumBytesPerElement(format); std::has_single_bit(bytes)) { out = {bytes, 1, 1}; @@ -336,23 +347,14 @@ bool TileGetTiledTextureLayout(const TileSurfaceDescription& description, TileSu TileSurfaceLayout result {}; result.description = description; result.texture = texture; - result.first_tail_level = description.levels; const uint32_t block_slices = volume ? ShiftCeil(description.depth, std::countr_zero(block.block_depth)) : description.layers; MipTailLayout tail {}; - const bool has_tail = GetMipTailLayout(block, tail); - if (has_tail && description.levels > 1) { - for (uint32_t level = 0; level < description.levels; ++level) { - 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; - } - } - } + result.first_tail_level = GetMipTailLayout(block, tail) + ? FirstTailLevel(tail, width0, height0, description.levels) + : description.levels; for (uint32_t level = 0; level < result.first_tail_level; ++level) { auto& mip = result.mips[level]; @@ -1213,44 +1215,52 @@ bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch, 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(width) + block_width - 1u) / block_width; - const uint64_t blocks_y = (static_cast(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(blocks * slices * 4096u); - total_size.align = 4096; - return true; -} - bool TileGetDccSize(uint32_t width, uint32_t height, uint32_t slices, 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 = {}; - if (levels != 1 || num_fragments_log2 != 0 || - !std::has_single_bit(bytes_per_element) || bytes_per_element > 16 || - (tile != Prospero::TileMode::kRenderTarget && tile != Prospero::TileMode::kDepth)) { + TileBlockLayout block {}; + MipTailLayout tail {}; + 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; } - // Gen5 DCC uses 4 KiB blocks, with one byte per 256 bytes of color data. - // Metadata stays thin for volume surfaces, so every depth slice has its own block raster. + // Gen5 DCC uses 4 KiB blocks, with one byte per 256 bytes of color data. Each slice stores + // 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); - return TileGetColorMetadataSize(width, height, slices, 1u << ((coverage_bits + 1u) / 2u), - 1u << (coverage_bits / 2u), total_size); + uint64_t slice_size = 0; + 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(size), + .offset = static_cast(slice_size)}; + } + slice_size += size; + } + if (slice_size == 0 || slice_size > UINT32_MAX / slices) { + return false; + } + total_size = {static_cast(slice_size * slices), 4096}; + return true; } bool TileGetCmaskSize(uint32_t width, uint32_t height, uint32_t slices, uint32_t levels, TileSizeAlign& total_size) { total_size = {}; // 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(blocks * slices * 4096u), 4096}; + return true; } void TileGetTextureSize(Prospero::BufferFormat format, uint32_t width, uint32_t height, diff --git a/src/graphics/guest_gpu/tile.h b/src/graphics/guest_gpu/tile.h index 15148e44c..f85f6d92c 100644 --- a/src/graphics/guest_gpu/tile.h +++ b/src/graphics/guest_gpu/tile.h @@ -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, uint32_t bytes_per_element, TileSizeAlign& total_size, 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, 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, TileSizeAlign& total_size); void TileGetTextureSize(Prospero::BufferFormat format, uint32_t width, uint32_t height, diff --git a/src/graphics/host_gpu/renderer/cache/textureCache.cpp b/src/graphics/host_gpu/renderer/cache/textureCache.cpp index 3c496a56f..c87c68559 100644 --- a/src/graphics/host_gpu/renderer/cache/textureCache.cpp +++ b/src/graphics/host_gpu/renderer/cache/textureCache.cpp @@ -1043,34 +1043,51 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc, desc.info.metadata.kind != ImageMetadataKind::Cmask) { 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}; auto& image = m_slot_images[id]; image.info.metadata = desc.info.metadata; // Native color metadata must not retain a reused HTile clear flag. m_surface_metas.erase(range.address); - if (range.size == 0 || desc.info.resources.levels != 1) { + if (range.size == 0 || view.level_count != 1) { return; } } 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; if (!range.Valid() || range.address % MetadataBlockSize != 0 || layers == 0 || range.size % layers != 0 || (range.size / layers) % MetadataBlockSize != 0) { 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 auto first = volume_texture ? 0u : metadata_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 bool volume_texture = desc.info.IsVolume() && view.type == vk::ImageViewType::e3D; + 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 >= layers || count > layers - first) { EXIT("TextureCache: color view exceeds its native metadata slices\n"); } - const auto slice_size = range.size / layers; - const GuestRange viewed {range.address + slice_size * first, slice_size * count}; + const auto slice_size = range.size / layers; + // 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(slice_size)}; + if (desc.info.metadata.kind == ImageMetadataKind::Dcc && desc.info.resources.levels > 1) { + std::array 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)) { std::array clears {}; 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. - 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; } { @@ -1107,9 +1128,9 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc, const auto [buffer, offset] = m_buffer_cache.ObtainBuffer(viewed.address, viewed.size, expand); const auto predicates = m_tiler.CheckColorClear( - {buffer->Handle(), offset, viewed.size}, count, keys, expand); - if (expand) { - m_buffer_cache.MarkColorMetadataChecked(viewed.address, viewed.size, keys); + {buffer->Handle(), offset, viewed.size}, slice_size, count, keys, expand); + for (uint32_t slice = 0; slice < count && expand; slice++) { + m_buffer_cache.MarkColorMetadataChecked(run(slice), mip.size, keys); } const auto command = m_scheduler.Current().Handle(); 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); } 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; if (!LibKernel::Memory::TryReadBacking(address, &code, sizeof(code))) { 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)) { continue; } - std::vector bytes(slice_size); + std::vector bytes(mip.size); if (!LibKernel::Memory::TryReadBacking(address, bytes.data(), bytes.size())) { 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. if (desc.type != BindingType::VideoOut) { 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()); } } diff --git a/src/graphics/host_gpu/renderer/image/tiler.cpp b/src/graphics/host_gpu/renderer/image/tiler.cpp index 06e74c7a7..6a3da29da 100644 --- a/src/graphics/host_gpu/renderer/image/tiler.cpp +++ b/src/graphics/host_gpu/renderer/image/tiler.cpp @@ -630,19 +630,20 @@ TileManager::Result TileManager::TransformColor(Result input, ColorTransform tra return result; } -vk::Buffer TileManager::CheckColorClear(Result metadata, uint32_t slices, uint32_t keys, - bool expand) { +vk::Buffer TileManager::CheckColorClear(Result metadata, uint64_t stride, uint32_t slices, + uint32_t keys, bool expand) { struct Check { uint32_t metadata_base; uint32_t words; + uint32_t stride; uint32_t keys; uint32_t expand; }; static_assert(sizeof(Check) <= sizeof(Conversion)); const uint32_t max_slices = m_graphics.GetPhysicalDeviceProperties().limits.maxFramebufferLayers; - EXIT_IF(slices == 0 || slices > max_slices || metadata.size % (4ull * slices) != 0 || - metadata.size / 4 > UINT32_MAX); + EXIT_IF(slices == 0 || slices > max_slices || metadata.size % 4 != 0 || stride % 4 != 0 || + metadata.size / 4 > UINT32_MAX || stride * (slices - 1) >= metadata.size); if (m_color_clear == nullptr) { m_color_clear = CreatePipeline(GPU_TILER_COLOR_CLEAR_SPV, nullptr); m_clear_predicates = std::make_unique( @@ -673,8 +674,9 @@ vk::Buffer TileManager::CheckColorClear(Result metadata, uint32_t slices, uint32 command.bindPipeline(vk::PipelineBindPoint::eCompute, m_color_clear); command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, m_pipeline_layout, 0, static_cast(writes.size()), writes.data()); - const Check push {source.base, static_cast(metadata.size / 4 / slices), keys, - expand}; + const Check push {source.base, + static_cast((metadata.size - stride * (slices - 1)) / 4), + static_cast(stride / 4), keys, expand}; command.pushConstants(m_pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0, sizeof(push), &push); command.dispatch(slices, 1, 1); diff --git a/src/graphics/host_gpu/renderer/image/tiler.h b/src/graphics/host_gpu/renderer/image/tiler.h index 95babc39e..9d2a3bda9 100644 --- a/src/graphics/host_gpu/renderer/image/tiler.h +++ b/src/graphics/host_gpu/renderer/image/tiler.h @@ -56,11 +56,11 @@ public: 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. static constexpr uint32_t ColorClearSlots = 8; - // Returns the buffer whose dword slice * ColorClearSlots + (key >> 5) is set when every byte - // of that metadata slice equals a key whose bit `keys` selects; a hit expands to 0xff when - // `expand` is set. - [[nodiscard]] vk::Buffer CheckColorClear(Result metadata, uint32_t slices, uint32_t keys, - bool expand); + // metadata spans `slices` equal runs `stride` bytes apart. Returns the buffer whose dword + // slice * ColorClearSlots + (key >> 5) is set when every byte of that run equals a key whose + // bit `keys` selects; a hit expands to 0xff when `expand` is set. + [[nodiscard]] vk::Buffer CheckColorClear(Result metadata, uint64_t stride, uint32_t slices, + uint32_t keys, bool expand); private: friend struct TileManagerTestAccess; diff --git a/src/graphics/host_gpu/shaders/gpu_tiler_color_clear.comp b/src/graphics/host_gpu/shaders/gpu_tiler_color_clear.comp index 396da68e2..dcf861677 100644 --- a/src/graphics/host_gpu/shaders/gpu_tiler_color_clear.comp +++ b/src/graphics/host_gpu/shaders/gpu_tiler_color_clear.comp @@ -9,6 +9,7 @@ layout(set = 0, binding = 1, std430) writeonly buffer Predicates { uint data[]; layout(push_constant, std430) uniform Check { uint metadata_base; uint words; + uint stride; uint keys; uint expand; } params; @@ -20,7 +21,7 @@ shared uint mismatch; void main() { 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. if (gl_LocalInvocationIndex == 0u) { key = metadata.data[base] & 0xffu; diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index c432b8043..2578645b4 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -12712,6 +12712,15 @@ public: DecodeColorDwordFill(vk::Format::eR16G16B16A16Uint, 0x22221111u, clear) && clear.uint32[2] == 0x1111u && clear.uint32[3] == 0x2222u, "a DWORD fill did not repeat across the 64-bit pixel"); + std::array 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 uint64_t allocation_size = 0x1000000; constexpr uint64_t allocation_alignment = 0x10000; @@ -12727,6 +12736,7 @@ public: bool eliminate_fast_clear = false; uint32_t samples_log2 = 0; bool goat = false; + bool dcc_mips = false; }; constexpr std::array cases{ FillCase{0x40404040u, {0, 0x3c000000u}}, @@ -12767,6 +12777,8 @@ public: FillCase{.fill = 0, .texel = {0, 0x3c000000u}, .cmask = true, .eliminate_fast_clear = true, .samples_log2 = 2}, FillCase{.fill = 0x40404040u, .texel = {0, 0x3c000000u}, .eliminate_fast_clear = true}, + FillCase{.fill = 0x40404040u, .texel = {0, 0x3c000000u}, .reuse_mips = true, + .dcc_mips = true}, }; EnsureRuntimeContext(); // Astro's generic metadata fill, through S_ENDPGM; trailing debug data is omitted. @@ -12832,13 +12844,19 @@ public: const auto bytes_per_pixel = TextureGetRenderTargetFormat( layout, type, Prospero::ChannelOrder::kStandard).bytes_per_element; 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{}; - (void)TileGetDccSize(512, 256, selected_layer + 1, bytes_per_pixel, 1, - Prospero::TileMode::kRenderTarget, dcc_size, fill_case.samples_log2); - const uint64_t dcc_address = base + (fill_case.samples_log2 != 0 ? 0x800000 : - fill_case.eliminate_fast_clear ? 0x200000 : - fill_case.reuse_mips ? 0x160000 : 0x100000); + (void)TileGetDccSize(512, 256, selected_layer + 1, bytes_per_pixel, + fill_case.dcc_mips ? 2 : 1, Prospero::TileMode::kRenderTarget, dcc_size, + fill_case.samples_log2); + uint64_t dcc_offset = fill_case.reuse_mips ? 0x300000 : 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 std::vector expected( 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}); registers.SetColorAttrib(0, {.num_samples = 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, {.tile_mode = Prospero::TileMode::kRenderTarget, .dimension = 1, @@ -12909,7 +12928,7 @@ public: clear.color.float32 = std::array{1.0f, 0.0f, 1.0f, 0.0f}; TextureCacheTestAccess::ClearImage( 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) { vk::ClearValue stale{}; @@ -12963,7 +12982,10 @@ public: Require(name, "expanded metadata readback", LibKernel::Memory::TryReadBacking(dcc_address, bytes.data(), bytes.size()), "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", std::all_of(bytes.begin(), selected, [&](uint8_t v) { return v == static_cast(fill_case.fill); }) && @@ -13055,12 +13077,12 @@ public: const auto &image = texture_cache.GetImage(color.image_id); Require(name, "single-mip clear reuses mip chain", 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, "the single-mip target did not retain its cached image and view scope"); Require(name, "single-mip clear preserves higher mip", ReadCachedTexel(name, context, color.image_id, {255, 127, 0}, - {1, 1, 1}, 0, 1) == + {1, 1, 1}, selected_layer, 1) == std::vector{0x00003c00u, 0x00003c00u}, "clearing mip zero changed its higher mip"); } @@ -13269,6 +13291,58 @@ public: Require(name, "native color clear after HTile reuse", read_texel() == expected, "the former HTile entry swallowed the native color metadata fill"); 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 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(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); resources.UnmapMemory(base, allocation_size); scheduler.Finish();