mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-24 21:06:55 +02:00
graphics: keep bounded image heaps resident in a bindless descriptor set
This commit is contained in:
@@ -436,6 +436,128 @@ void TestInvariantIndirectImageMaterialization(bool nested) {
|
||||
"malformed image table tracking was not transactional");
|
||||
}
|
||||
|
||||
void TestResidentImageHeap() {
|
||||
struct FakeImageHeap final : ImageHeapSource {
|
||||
bool accept = true;
|
||||
std::vector<DescriptorValue> kinds;
|
||||
std::vector<ImageHeapRequest> requests;
|
||||
bool Acquire(const ImageHeapRequest &request, ImageHeapView &view) override {
|
||||
requests.push_back(request);
|
||||
if (!accept) return false;
|
||||
view = {.slots = 0x123456789abcull, .slot_count = 36u, .kinds = kinds};
|
||||
return true;
|
||||
}
|
||||
};
|
||||
auto fixture = MakeIndirectImageFixture(false, 48u, 0u, 16u);
|
||||
fixture->PlanAndTrack();
|
||||
const auto plan = ExtractResourcePlan(fixture->program);
|
||||
std::array<uint32_t, 8> descriptor{};
|
||||
descriptor[0] = 0x20u;
|
||||
descriptor[1] = static_cast<uint32_t>(Libs::Graphics::Prospero::BufferFormat::k32_32_32_32Float) << 20u;
|
||||
descriptor[2] = 3u | (3u << 14u);
|
||||
descriptor[3] = Libs::Graphics::DstSel(4, 5, 6, 7) |
|
||||
(static_cast<uint32_t>(Libs::Graphics::Prospero::ImageType::kColor2D) << 28u);
|
||||
auto cube = descriptor;
|
||||
cube[3] = Libs::Graphics::DstSel(4, 5, 6, 7) |
|
||||
(static_cast<uint32_t>(Libs::Graphics::Prospero::ImageType::kCube) << 28u);
|
||||
std::array<uint32_t, 16> data{0x3000u, 4u << 16u, 0x100000u, 0u,
|
||||
0x2000u, 48u << 16u, 3u, 0u};
|
||||
std::copy_n(data.begin(), 4u, data.begin() + 12u);
|
||||
LinearTestMemory memory;
|
||||
memory.fail_address = 0x3000u;
|
||||
memory.watched_address = 0x2000u;
|
||||
std::copy(descriptor.begin(), descriptor.end(), memory.words.begin() + 0x1010u / 4u);
|
||||
FakeImageHeap heap;
|
||||
heap.kinds = {{descriptor, 8u}, {cube, 8u}};
|
||||
SrtRuntime runtime{.user_data = data, .userdata = &memory,
|
||||
.read_specialization_memory = ReadLinearTestMemory, .image_heap = &heap};
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
heap.requests.size() == 1u && heap.requests[0].base == 0x2000u &&
|
||||
heap.requests[0].size == 144u && heap.requests[0].step == 16u &&
|
||||
heap.requests[0].first == 0u && heap.requests[0].count == 9u &&
|
||||
heap.requests[0].root == &plan.info.images[0] &&
|
||||
memory.watched_reads == 0u && snapshot.images.size() == 3u,
|
||||
"resident image heap was materialized entry by entry");
|
||||
const auto &root = specialization.images[0];
|
||||
const auto mapping = root.indirect_mapping_offset;
|
||||
Check(root.heap && root.indirect_root == 0u && root.indirect_search_iterations == 0u &&
|
||||
snapshot.flattened_srt.size() == mapping + 3u &&
|
||||
snapshot.flattened_srt[mapping] == 36u &&
|
||||
snapshot.flattened_srt[mapping + 1u] == 0x56789abcu &&
|
||||
snapshot.flattened_srt[mapping + 2u] == 0x1234u &&
|
||||
specialization.images[1].heap && specialization.images[1].indirect_root == 0u &&
|
||||
!specialization.images[1].cube && specialization.images[2].heap &&
|
||||
specialization.images[2].cube && snapshot.images[2].dwords == cube &&
|
||||
std::ranges::find(snapshot.specialization_reads,
|
||||
std::pair<uint64_t, uint64_t>{0x2000u, 144u}) !=
|
||||
snapshot.specialization_reads.end(),
|
||||
"resident image heap lost its slot mapping, typed kinds or read dependency");
|
||||
auto &program = fixture->program;
|
||||
ApplyResourceSpecialization(program, specialization);
|
||||
ShaderComputeInputInfo compute{};
|
||||
CollectShaderInfo(program, {.compute = &compute});
|
||||
Check(program.info.uses_image_heap && program.info.images.size() == 3u &&
|
||||
program.info.images[0].indirect_resources == std::vector<uint32_t>({0u, 1u, 2u}) &&
|
||||
std::ranges::all_of(program.info.images, &ImageResource::heap),
|
||||
"applied heap kinds lost their root or heap flag");
|
||||
heap.kinds.clear();
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
snapshot.images.size() == 1u && !specialization.images[0].heap &&
|
||||
specialization.images[0].indirect_root == ImageResource::NoIndirectImage,
|
||||
"a resident heap without kinds did not read as one null image");
|
||||
heap.accept = false;
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
heap.requests.size() == 3u && memory.watched_reads == 1u &&
|
||||
snapshot.images.size() == 2u && !specialization.images[0].heap &&
|
||||
snapshot.images[1].dwords == descriptor,
|
||||
"a declined heap was not materialized entry by entry");
|
||||
}
|
||||
|
||||
void TestImageHeapKinds() {
|
||||
using Format = Libs::Graphics::Prospero::BufferFormat;
|
||||
using Type = Libs::Graphics::Prospero::ImageType;
|
||||
ImageResource root;
|
||||
root.resource_class = ImageResourceClass::Sampled;
|
||||
root.dimension = Decoder::ImageDimension::Dim2DArray;
|
||||
root.source = 3u;
|
||||
const auto descriptor = [](Format format, Type type) {
|
||||
DescriptorValue value{.dword_count = 8u};
|
||||
value.dwords[0] = 0x20u;
|
||||
value.dwords[1] = static_cast<uint32_t>(format) << 20u;
|
||||
value.dwords[2] = 3u | (3u << 14u);
|
||||
value.dwords[3] = Libs::Graphics::DstSel(4, 5, 6, 7) | (static_cast<uint32_t>(type) << 28u);
|
||||
return value;
|
||||
};
|
||||
const auto kind_of = [&](Format format, Type type) {
|
||||
auto value = descriptor(format, type);
|
||||
return ImageHeapKind(root, value);
|
||||
};
|
||||
const auto float_kind = kind_of(Format::k32_32_32_32Float, Type::kColor2DArray);
|
||||
const auto uint_kind = kind_of(Format::k32UInt, Type::kColor2DArray);
|
||||
const auto cube_kind = kind_of(Format::k32_32_32_32Float, Type::kCube);
|
||||
auto volume = descriptor(Format::k32_32_32_32Float, Type::kColor3D);
|
||||
Check(float_kind != 0u && uint_kind != 0u && cube_kind != 0u && float_kind != uint_kind &&
|
||||
float_kind != cube_kind && kind_of(Format::kInvalid, Type::kColor2DArray) == 0u &&
|
||||
ImageHeapKind(root, volume) == 0u &&
|
||||
std::ranges::all_of(volume.dwords, [](uint32_t word) { return word == 0u; }),
|
||||
"heap kinds did not separate typed entries or null mismatched ones");
|
||||
const auto resource = ImageHeapResource(root, uint_kind);
|
||||
const auto cube_resource = ImageHeapResource(root, cube_kind);
|
||||
auto other = root;
|
||||
other.source = 9u;
|
||||
Check(resource.numeric_class == Libs::Graphics::Prospero::TextureNumericClass::Uint &&
|
||||
!resource.cube && cube_resource.cube &&
|
||||
cube_resource.numeric_class == Libs::Graphics::Prospero::TextureNumericClass::Float &&
|
||||
resource.resource_class == ImageResourceClass::Sampled &&
|
||||
ImageHeapResource(root, 0u) == ImageHeapResource(other, 0u),
|
||||
"heap kind resources lost their type or kept root identity");
|
||||
root.depth_compare = true;
|
||||
Check(kind_of(Format::k32UInt, Type::kColor2DArray) == 0u,
|
||||
"depth comparison accepted an integer heap entry");
|
||||
}
|
||||
|
||||
void TestBoundedImageViewEligibility() {
|
||||
using Type = Libs::Graphics::Prospero::ImageType;
|
||||
auto fixture = MakeIndirectImageFixture(false, 48u, 0u, 16u);
|
||||
@@ -8796,6 +8918,8 @@ int main() {
|
||||
Run("dynamic storage mips", TestDynamicStorageMipTracking);
|
||||
Run("invariant indirect images", [] { TestInvariantIndirectImageMaterialization(true); });
|
||||
Run("unbounded material image keys", [] { TestInvariantIndirectImageMaterialization(false); });
|
||||
Run("resident image heap", TestResidentImageHeap);
|
||||
Run("image heap kinds", TestImageHeapKinds);
|
||||
Run("bounded image view eligibility", TestBoundedImageViewEligibility);
|
||||
Run("exact unfiltered image candidates", TestExactUnfilteredImageCandidates);
|
||||
Run("waterfall image table", TestWaterfallImageTable);
|
||||
|
||||
@@ -228,6 +228,18 @@ struct TileManagerTestAccess {
|
||||
}
|
||||
};
|
||||
|
||||
struct ImageHeapTestAccess {
|
||||
static size_t ElementCount(const ImageHeap &heap) { return heap.m_elements.size(); }
|
||||
static size_t MemoryCount(const ImageHeap &heap) { return heap.m_memories.size(); }
|
||||
static const auto &Views(const ImageHeap &heap) { return heap.m_memories.at(0)->views; }
|
||||
static const auto &Element(const ImageHeap &heap, uint32_t entry) {
|
||||
return heap.m_elements[entry & ((1u << ShaderRecompiler::IR::ImageHeapKindShift) - 1u)];
|
||||
}
|
||||
static ImageId ImageOf(const ImageHeap &heap, uint32_t entry) {
|
||||
return Element(heap, entry).binding.image_id;
|
||||
}
|
||||
};
|
||||
|
||||
struct TextureCacheTestAccess {
|
||||
static_assert(std::same_as<decltype(TextureCache::m_slot_images),
|
||||
Common::SlotVector<Image>>);
|
||||
@@ -5150,6 +5162,234 @@ public:
|
||||
std::printf("[host] %-32s ok\n", name);
|
||||
}
|
||||
|
||||
void CheckResidentImageHeap() {
|
||||
constexpr const char *name = "ResidentImageHeap";
|
||||
constexpr uintptr_t base = 0x0000000206800000ull;
|
||||
constexpr uint64_t allocation_size = 0x200000;
|
||||
constexpr uint64_t table = base + 0x100000;
|
||||
constexpr uint64_t idle_table = base + 0x180000;
|
||||
constexpr uint64_t table_size = 0x10000;
|
||||
namespace IR = ShaderRecompiler::IR;
|
||||
using Format = Prospero::BufferFormat;
|
||||
EnsureRuntimeContext();
|
||||
int64_t direct_offset = -1;
|
||||
Require(name, "direct allocation",
|
||||
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
||||
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(),
|
||||
allocation_size, allocation_size, 0, &direct_offset) == 0,
|
||||
"image heap allocation failed");
|
||||
void *mapped = reinterpret_cast<void *>(base);
|
||||
Require(name, "direct mapping",
|
||||
Libs::LibKernel::Memory::KernelMapDirectMemory(
|
||||
&mapped, allocation_size, 0x3, 0x10, direct_offset,
|
||||
allocation_size) == 0 && mapped == reinterpret_cast<void *>(base),
|
||||
"image heap mapping failed");
|
||||
std::memset(mapped, 0, allocation_size);
|
||||
// A 4x4 linear 2D texture per index.
|
||||
const auto descriptor = [](uint32_t texture, Format format) {
|
||||
const uint64_t address = base + 0x10000u + texture * 0x1000u;
|
||||
std::array<uint32_t, 8> words{};
|
||||
words[0] = static_cast<uint32_t>(address >> 8u);
|
||||
words[1] = static_cast<uint32_t>(address >> 40u) | (static_cast<uint32_t>(format) << 20u);
|
||||
words[2] = 3u | (3u << 14u);
|
||||
words[3] = DstSel(4, 5, 6, 7) |
|
||||
(static_cast<uint32_t>(Prospero::ImageType::kColor2D) << 28u);
|
||||
return words;
|
||||
};
|
||||
const auto put = [&](uint64_t probe, const std::array<uint32_t, 8> &words) {
|
||||
std::memcpy(reinterpret_cast<void *>(table + probe * 32u), words.data(), sizeof(words));
|
||||
};
|
||||
put(0, descriptor(0, Format::k8_8_8_8UNorm));
|
||||
put(1, descriptor(0, Format::k8_8_8_8UNorm));
|
||||
put(5, descriptor(1, Format::k8_8_8_8UNorm));
|
||||
{
|
||||
RenderContext context(m_runtime_context);
|
||||
auto &scheduler = context.GetCommandScheduler();
|
||||
HW::Context registers{};
|
||||
HW::UserConfig user_config{};
|
||||
HW::Shader shaders{};
|
||||
scheduler.Begin(registers, user_config, shaders);
|
||||
context.MapMemory(base, allocation_size);
|
||||
auto &heap = context.GetImageHeap();
|
||||
auto &executor = context.GetRenderExecutor();
|
||||
auto &textures = context.GetTextureCache();
|
||||
auto &device = context.GetGraphics().device;
|
||||
const vk::DescriptorSetLayoutCreateInfo empty_info{};
|
||||
vk::DescriptorSetLayout empty = nullptr;
|
||||
vk::PipelineLayout layout = nullptr;
|
||||
Require(name, "set 0 layout",
|
||||
device.createDescriptorSetLayout(&empty_info, nullptr, &empty) ==
|
||||
vk::Result::eSuccess,
|
||||
"could not create the set 0 layout");
|
||||
const std::array set_layouts{empty, context.GetGraphics().image_heap_layout};
|
||||
vk::PipelineLayoutCreateInfo layout_info{};
|
||||
layout_info.setLayoutCount = 2;
|
||||
layout_info.pSetLayouts = set_layouts.data();
|
||||
Require(name, "pipeline layout",
|
||||
device.createPipelineLayout(&layout_info, nullptr, &layout) == vk::Result::eSuccess,
|
||||
"could not create a pipeline layout with the heap set");
|
||||
|
||||
IR::ImageResource sampled{};
|
||||
sampled.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
||||
sampled.unfiltered_access_only = false;
|
||||
auto unfiltered = sampled;
|
||||
unfiltered.unfiltered_access_only = true;
|
||||
IR::ImageHeapView view{};
|
||||
const auto acquire = [&](const IR::ImageResource &root, uint64_t address = table,
|
||||
uint64_t count = table_size / 32u) {
|
||||
return heap.Acquire({address, table_size, 32u, 0u, count, &root}, view);
|
||||
};
|
||||
const auto draw = [&] {
|
||||
heap.Rebind(executor);
|
||||
heap.Commit(scheduler.Current(), vk::PipelineBindPoint::eCompute, layout);
|
||||
scheduler.Finish();
|
||||
heap.BeginDraw();
|
||||
};
|
||||
const auto write = [&](uint64_t probe, const std::array<uint32_t, 8> &words) {
|
||||
Libs::LibKernel::Memory::WriteBacking(table + probe * 32u, words.data(), sizeof(words));
|
||||
Require(name, "table write", context.InvalidateMemory(table + probe * 32u, sizeof(words)),
|
||||
"the table was not mapped");
|
||||
};
|
||||
|
||||
Require(name, "first view",
|
||||
acquire(sampled) && view.kinds.size() == 1u && view.slot_count == table_size / 4u,
|
||||
"a 64-KiB table was not kept resident");
|
||||
const auto &views = ImageHeapTestAccess::Views(heap);
|
||||
const auto &filtered = *views[0];
|
||||
Require(name, "slot words",
|
||||
filtered.slots[0] != 0u && filtered.slots[8] == filtered.slots[0] &&
|
||||
filtered.slots[40] != 0u && filtered.slots[40] != filtered.slots[0] &&
|
||||
filtered.slots[1] == 0u && filtered.slots.back() == 0u &&
|
||||
ImageHeapTestAccess::ElementCount(heap) == 3u &&
|
||||
ImageHeapTestAccess::Element(heap, 0u).binding.layout ==
|
||||
vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||
"probes did not share equal descriptors, left a non-null gap or wrote the null "
|
||||
"element without a sampled layout");
|
||||
Require(name, "second view",
|
||||
acquire(unfiltered) && views.size() == 2u &&
|
||||
ImageHeapTestAccess::ElementCount(heap) == 5u,
|
||||
"a second root profile did not get its own view");
|
||||
const auto &idle = *views[1];
|
||||
draw();
|
||||
const auto resident = ImageHeapTestAccess::ImageOf(heap, filtered.slots[0]);
|
||||
Require(name, "resident images",
|
||||
resident && textures.GetImage(resident).heap_refs.load() == 2u,
|
||||
"resolved elements did not pin their image");
|
||||
|
||||
// Only acquired views take table changes; the idle one must not allocate.
|
||||
const auto idle_slot = idle.slots[40];
|
||||
for (uint32_t texture = 2; texture < 4u; ++texture) {
|
||||
write(5, descriptor(texture, Format::k8_8_8_8UNorm));
|
||||
Require(name, "changed table", acquire(sampled), "a changed table was declined");
|
||||
draw();
|
||||
}
|
||||
Require(name, "idle view",
|
||||
idle.slots[40] == idle_slot && ImageHeapTestAccess::ElementCount(heap) <= 6u,
|
||||
"an idle view took table changes or leaked released elements");
|
||||
Require(name, "reactivated view", acquire(unfiltered), "the idle view was declined");
|
||||
draw();
|
||||
Require(name, "deferred probes",
|
||||
ImageHeapTestAccess::ImageOf(heap, idle.slots[40]) ==
|
||||
ImageHeapTestAccess::ImageOf(heap, filtered.slots[40]) &&
|
||||
ImageHeapTestAccess::ElementCount(heap) <= 7u,
|
||||
"a reactivated view missed the changes made while it was idle");
|
||||
|
||||
// All programs of one draw see the table and kinds of its first acquire.
|
||||
Require(name, "draw kinds", acquire(sampled), "the view was declined");
|
||||
write(7, descriptor(12, Format::k8_8_8_8UInt));
|
||||
Require(name, "stable kinds",
|
||||
acquire(sampled) && view.kinds.size() == 1u && filtered.slots[56] == 0u,
|
||||
"a later program of a draw saw a kind the first one lacked");
|
||||
draw();
|
||||
Require(name, "next draw kinds",
|
||||
acquire(sampled) && view.kinds.size() == 2u &&
|
||||
filtered.slots[56] >> IR::ImageHeapKindShift == 1u,
|
||||
"the next draw did not take the new kind");
|
||||
draw();
|
||||
|
||||
Require(name, "bounded view",
|
||||
acquire(sampled, table, 4u) && views.size() == 3u && views[2]->slots[0] != 0u &&
|
||||
views[2]->slots[40] == 0u,
|
||||
"a view probed descriptors past the guest table bound");
|
||||
draw();
|
||||
|
||||
// Only leaving the read state invalidates resident images.
|
||||
auto &image = textures.GetImage(resident);
|
||||
const auto epoch = Libs::Graphics::Image::heap_epoch.load();
|
||||
for (uint32_t pass = 0; pass < 2u; ++pass) {
|
||||
image.Transit(vk::ImageLayout::eGeneral, vk::AccessFlagBits2::eShaderWrite, {},
|
||||
scheduler.Current().Handle());
|
||||
}
|
||||
Require(name, "write epoch", Libs::Graphics::Image::heap_epoch.load() == epoch + 1u,
|
||||
"repeated writes after leaving the read state bumped the heap epoch again");
|
||||
Require(name, "read again", acquire(sampled), "the view was declined");
|
||||
draw();
|
||||
Require(name, "read layout",
|
||||
image.backing.state.layout == vk::ImageLayout::eShaderReadOnlyOptimal &&
|
||||
Libs::Graphics::Image::heap_epoch.load() == epoch + 1u,
|
||||
"commit did not return a resident image to its read layout");
|
||||
|
||||
// An image freed during a draw's discovery lives until the tick ends, and the draw's
|
||||
// slots still select it.
|
||||
write(9, descriptor(13, Format::k8_8_8_8UNorm));
|
||||
Require(name, "freed image", acquire(sampled), "the view was declined");
|
||||
heap.Rebind(executor);
|
||||
const auto freed = ImageHeapTestAccess::ImageOf(heap, filtered.slots[72]);
|
||||
textures.UnmapMemory(base + 0x10000u + 13u * 0x1000u, 0x1000u);
|
||||
heap.Commit(scheduler.Current(), vk::PipelineBindPoint::eCompute, layout);
|
||||
Require(name, "freed image layout",
|
||||
!textures.GetImage(freed).registered &&
|
||||
textures.GetImage(freed).backing.state.layout ==
|
||||
vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||
"commit skipped the read transition of a freed but sampled image");
|
||||
scheduler.Finish();
|
||||
heap.BeginDraw();
|
||||
|
||||
// A draw skipped after its programs acquired views still leaves the next draw a fresh
|
||||
// table.
|
||||
Require(name, "skipped draw", acquire(sampled), "the view was declined");
|
||||
heap.BeginDraw();
|
||||
write(11, descriptor(16, Format::k8_8_8_8UNorm));
|
||||
Require(name, "draw after a skipped one", acquire(sampled) && filtered.slots[88] != 0u,
|
||||
"a draw sampled the table of a skipped draw");
|
||||
// The skipped draw's view stays acquired. Another view refreshing the table must not let
|
||||
// a stale element take a newer kind from it.
|
||||
heap.BeginDraw();
|
||||
write(5, descriptor(15, Format::k8_8_8_8SInt));
|
||||
Require(name, "other view", acquire(unfiltered), "the view was declined");
|
||||
textures.UnmapMemory(base + 0x10000u + 3u * 0x1000u, 0x1000u);
|
||||
draw();
|
||||
Require(name, "stale rescan",
|
||||
filtered.kinds.size() == 2u &&
|
||||
ImageHeapTestAccess::Element(heap, filtered.slots[40]).value.dwords ==
|
||||
descriptor(3, Format::k8_8_8_8UNorm),
|
||||
"a stale element's rescan took a kind its view's programs lack");
|
||||
|
||||
heap.UnmapMemory(table, table_size);
|
||||
Require(name, "unmapped table",
|
||||
ImageHeapTestAccess::MemoryCount(heap) == 0u &&
|
||||
textures.GetImage(resident).heap_refs.load() == 0u,
|
||||
"unmapping the table kept its images pinned");
|
||||
// The idle table stays watched; destroying the heap must unwatch it before the page
|
||||
// manager fails on live watches.
|
||||
Require(name, "idle table",
|
||||
acquire(sampled, idle_table) && view.kinds.empty() &&
|
||||
ImageHeapTestAccess::MemoryCount(heap) == 1u,
|
||||
"an all-null table was not kept resident");
|
||||
draw();
|
||||
context.UnmapMemory(base, idle_table - base);
|
||||
scheduler.Finish();
|
||||
device.destroyPipelineLayout(layout, nullptr);
|
||||
device.destroyDescriptorSetLayout(empty, nullptr);
|
||||
}
|
||||
Require(name, "unmap", Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0,
|
||||
"image heap unmap failed");
|
||||
Require(name, "release", Libs::LibKernel::Memory::KernelReleaseDirectMemory(
|
||||
direct_offset, allocation_size) == 0,
|
||||
"image heap release failed");
|
||||
std::printf("[host] %-32s ok\n", name);
|
||||
}
|
||||
|
||||
void CheckComputeMetaClearClassification() {
|
||||
constexpr const char *name = "ComputeMetaClearClassification";
|
||||
constexpr uint64_t read_only_meta = 0x0000000204201f00ull;
|
||||
@@ -41289,6 +41529,41 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) {
|
||||
Require(name, "alternating image emitted size", spirv.size() <= 8192u,
|
||||
"alternating descriptors exceeded the 32 KiB SPIR-V regression budget");
|
||||
|
||||
// A resident heap selects its typed kind and element from a device slot word.
|
||||
auto heap_root = root;
|
||||
heap_root.heap = true;
|
||||
heap_root.indirect_search_iterations = 0;
|
||||
heap_root.indirect_resources = {0u, 1u, 2u};
|
||||
auto heap_kind = candidate;
|
||||
heap_kind.heap = true;
|
||||
auto heap_uint = heap_kind;
|
||||
heap_uint.numeric_class = Prospero::TextureNumericClass::Uint;
|
||||
for (const bool mixed : {true, false}) {
|
||||
program.info.images = {heap_root, heap_kind};
|
||||
if (mixed) program.info.images.push_back(heap_uint);
|
||||
program.info.images[0].indirect_resources.resize(program.info.images.size());
|
||||
// ApplyResourceSpecialization adds the integer sampler variant of a Uint kind.
|
||||
program.info.samplers.resize(1);
|
||||
if (mixed) {
|
||||
program.info.samplers.push_back(program.info.samplers[0]);
|
||||
program.info.samplers[1].integer_border = true;
|
||||
}
|
||||
program.shader_info_complete = false;
|
||||
CollectShaderInfo(program, {.compute = &compute});
|
||||
spirv = ShaderRecompiler::Spirv::EmitProgram(program, {.compute = &compute});
|
||||
ValidateSpirv("ResidentImageHeap", spirv);
|
||||
Require(name, "image heap disassembly", tools.Disassemble(spirv, &text),
|
||||
"failed to disassemble the image heap shader");
|
||||
Require(name, "image heap selection",
|
||||
program.info.uses_image_heap && CountText(text, "OpConvertUToPtr") == 1 &&
|
||||
CountText(text, "UMin") == 1 &&
|
||||
CountText(text, "OpSwitch") == (mixed ? 1 : 0) &&
|
||||
CountText(text, "OpImageSampleExplicitLod") == (mixed ? 2 : 1) &&
|
||||
CountText(text, "DescriptorSet 1") == (mixed ? 2 : 1) &&
|
||||
text.find("PhysicalStorageBuffer64") != std::string::npos,
|
||||
"image heap kinds did not sample the slot-selected heap element");
|
||||
}
|
||||
|
||||
program.memory_info[0].image_dimension =
|
||||
ShaderRecompiler::Decoder::ImageDimension::Dim2DArray;
|
||||
program.memory_info[0].image_address_components = 4;
|
||||
@@ -44054,7 +44329,7 @@ void CheckComputeLdsLimit(VulkanHarness &vulkan) {
|
||||
regs.cs_regs.num_thread_y = 1;
|
||||
regs.cs_regs.num_thread_z = 1;
|
||||
regs.cs_regs.wave_size = 32;
|
||||
PipelineCache cache(graphics);
|
||||
PipelineCache cache(graphics, nullptr);
|
||||
ShaderProgram previous;
|
||||
uint16_t previous_units = 0;
|
||||
for (const auto units : {static_cast<uint16_t>(limit_units - 1u), limit_units,
|
||||
@@ -49350,7 +49625,7 @@ void CheckPm4IndirectControlFlow(RenderContext &renderer) {
|
||||
std::printf("[host] %-32s ok\n", name);
|
||||
}
|
||||
|
||||
// waitUntilSafeForRendering stalls only its own command processor (SDK), and a display buffer
|
||||
// waitUntilSafeForRendering stalls only its own command processor, and a display buffer
|
||||
// stays busy while its flip is pending or while it is on screen.
|
||||
void CheckWaitUntilSafeForRendering() {
|
||||
constexpr const char *name = "WaitUntilSafeForRendering";
|
||||
@@ -50653,6 +50928,7 @@ int main(int argc, char **argv) {
|
||||
CheckNestedPointerImageTable(vulkan);
|
||||
CheckGuardedRecordImageTable(vulkan);
|
||||
CheckIndirectImageKeySwitch(vulkan);
|
||||
vulkan.CheckResidentImageHeap();
|
||||
CheckFiniteFirstLaneImageChoice(vulkan);
|
||||
CheckFiniteInlineSamplerPhi(vulkan);
|
||||
RunCase(&vulkan, ImageCubeGradientsPreserveDerivatives());
|
||||
@@ -50831,6 +51107,7 @@ int main(int argc, char **argv) {
|
||||
CheckNestedPointerImageTable(vulkan);
|
||||
CheckGuardedRecordImageTable(vulkan);
|
||||
CheckIndirectImageKeySwitch(vulkan);
|
||||
vulkan.CheckResidentImageHeap();
|
||||
CheckDynamicSampleOffsets(vulkan, 32u);
|
||||
CheckDynamicSampleOffsets(vulkan, 64u);
|
||||
CheckFiniteFirstLaneImageChoice(vulkan);
|
||||
|
||||
Reference in New Issue
Block a user