diff --git a/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp b/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp index 63a4e802f..615f12d82 100644 --- a/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp @@ -434,13 +434,18 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p selector->stride != sizeof(masks) || selector->offset != 0u || material.Type() != 0u || material.Stride() != sizeof(masks) || material.SwizzleEnabled() || material.AddTid() || material.OutOfBounds() != 0u || - (material.Base48() & 3u) != 0u || - material.GetSize() / sizeof(uint32_t) > MaxIndirectDescriptorProbes) return false; + (material.Base48() & 3u) != 0u) return false; if (material.Format() != Prospero::BufferFormat::kInvalid) { - keys.resize(material.GetSize() / sizeof(uint32_t)); - if (!keys.empty() && !ReadScalarTable(material.Base48(), material.GetSize(), 0u, - runtime, keys)) return false; - for (size_t word = 0; word < keys.size(); ++word) masks[word % masks.size()] |= keys[word]; + const auto words = material.GetSize() / sizeof(uint32_t); + // Bitmap storage can grow with resolution while its candidate domain stays + // at 128 keys. Bound scratch memory, preserving each coherent read's capture. + for (uint64_t first_word = 0; first_word < words; first_word += keys.size()) { + keys.resize(std::min(words - first_word, MaxIndirectDescriptorProbes)); + if (!ReadScalarTable(material.Base48(), material.GetSize(), first_word * 4u, + runtime, keys)) return false; + for (size_t word = 0; word < keys.size(); ++word) masks[word % masks.size()] |= keys[word]; + if (std::ranges::all_of(masks, [](uint32_t mask) { return mask == UINT32_MAX; })) break; + } keys.clear(); for (uint32_t word = 0; word < masks.size(); ++word) { auto mask = masks[word]; diff --git a/tests/ResourceTrackingTests.cpp b/tests/ResourceTrackingTests.cpp index 8d7c5daea..600278856 100644 --- a/tests/ResourceTrackingTests.cpp +++ b/tests/ResourceTrackingTests.cpp @@ -1653,8 +1653,9 @@ void TestDescendingImageRange() { for (uint32_t record = 1u; record <= 130u; ++record) memory.words[record * 8u] = 0x100u + record; constexpr uint32_t bitmap_address = 0x4000u; + constexpr uint32_t display_records = 32400u; const auto bitmap_word = (bitmap_address - memory.base) / 4u; - memory.words.resize(bitmap_word + MaxIndirectDescriptorProbes + 4u, 0u); + memory.words.resize(bitmap_word + display_records * 4u, 0u); std::array bitmap_data {0x1000u, 0u, 0u, UINT32_MAX, bitmap_address, 16u << 16u, 3u, 0x5204u}; const SrtRuntime bitmap_runtime {.user_data = bitmap_data, .userdata = &memory, @@ -1668,14 +1669,17 @@ void TestDescendingImageRange() { std::ranges::sort(keys); Check(MaterializeResources(bitmap_plan, bitmap_runtime, snapshot, specialization) && memory.watched_reads == uint32_t(bitmap_data[6] != 0u) && - (bitmap_data[6] == 0u || memory.watched_dwords == bitmap_data[6] * 4u) && + (bitmap_data[6] == 0u || memory.watched_dwords == + std::min(bitmap_data[6] * 4u, MaxIndirectDescriptorProbes)) && memory.descriptor_reads == keys.size() && snapshot.images.size() == std::max(keys.size(), 1u), - "bitmap selector did not read one bounded snapshot or retained unreachable descriptors"); - Check(bitmap_data[6] == 0u || std::ranges::find(snapshot.specialization_reads, - std::pair{uint64_t{bitmap_address}, uint64_t{bitmap_data[6]} * 16u}) != - snapshot.specialization_reads.end(), - "bitmap snapshot was omitted from same-dispatch write-alias protection"); + "bitmap selector lost bounded reads or retained unreachable descriptors"); + for (uint32_t first = 0; first < bitmap_data[6] * 4u; first += MaxIndirectDescriptorProbes) + Check(std::ranges::find(snapshot.specialization_reads, + std::pair{uint64_t{bitmap_address} + first * 4u, + uint64_t{std::min(bitmap_data[6] * 4u - first, MaxIndirectDescriptorProbes)} * 4u}) != + snapshot.specialization_reads.end(), + "bitmap chunk was omitted from same-dispatch write-alias protection"); if (keys.empty()) { Check(std::ranges::all_of(snapshot.images[0].dwords, [](uint32_t value) { return value == 0u; }) && specialization.images[0].indirect_root == ImageResource::NoIndirectImage, @@ -1725,14 +1729,13 @@ void TestDescendingImageRange() { memory.fail_address = UINT64_MAX; const auto valid_data = bitmap_data; - for (const auto [word, value] : std::array, 7>{{ + for (const auto [word, value] : std::array, 6>{{ {4u, bitmap_address + 1u}, {5u, 8u << 16u}, {5u, (16u << 16u) | (1u << 31u)}, {7u, 0x5204u | (1u << 23u)}, {7u, 0x5204u | (1u << 28u)}, - {7u, 0x5204u | (1u << 30u)}, - {6u, MaxIndirectDescriptorProbes / 4u + 1u}}}) { + {7u, 0x5204u | (1u << 30u)}}}) { bitmap_data[word] = value; Check(!MaterializeResources(bitmap_plan, bitmap_runtime, snapshot, specialization), - "bitmap selector accepted an unproved buffer layout or exceeded its read bound"); + "bitmap selector accepted an unproved buffer layout"); bitmap_data = valid_data; } bitmap_data[7] &= ~(0x7fu << 12u); // FORMAT=INVALID makes even an unformatted load unbound. @@ -1751,6 +1754,22 @@ void TestDescendingImageRange() { memory.words[bitmap_word + MaxIndirectDescriptorProbes - 1u] = 1u << 31u; memory.words[bitmap_word] = 1u; check_bitmap({0u, 127u}, 0u); + bitmap_data[6] = display_records; + memory.words[bitmap_word + display_records * 4u - 1u] = 1u << 5u; + check_bitmap({0u, 101u, 127u}, 0u); + memory.fail_address = bitmap_address + MaxIndirectDescriptorProbes * 4u; + Check(!MaterializeResources(bitmap_plan, bitmap_runtime, snapshot, specialization), + "bitmap selector retained a partial union after a later chunk failed"); + memory.fail_address = UINT64_MAX; + check_bitmap({0u, 101u, 127u}, 0u); + std::fill_n(memory.words.begin() + bitmap_word, 4u, UINT32_MAX); + memory.fail_address = bitmap_address + MaxIndirectDescriptorProbes * 4u; + Check(MaterializeResources(bitmap_plan, bitmap_runtime, snapshot, specialization) && + snapshot.images.size() == 128u && + std::ranges::none_of(snapshot.specialization_reads, [&](const auto &read) { + return read.first >= memory.fail_address; + }), + "a complete bitmap domain read redundant chunks or lost candidate keys"); } void TestUniformizedMaterialImageKeys() {