mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 10:56:58 +02:00
shader: preserve typed candidates for unfiltered indirect image access
Co-authored-by: fxpw <fxpw@outlook.com>
This commit is contained in:
+147
-18
@@ -206,7 +206,8 @@ bool ReadLinearTestMemory(void *userdata, uint64_t address, std::span<uint32_t>
|
||||
|
||||
std::unique_ptr<Fixture>
|
||||
MakeIndirectImageFixture(bool malformed, uint32_t table_stride = 32, uint32_t table_offset = 0,
|
||||
uint32_t table_shader_offset = 0, bool nested = false) {
|
||||
uint32_t table_shader_offset = 0, bool nested = false,
|
||||
ValueOpcode access = ValueOpcode::ImageSampleRaw, bool finite_key = false) {
|
||||
auto fixture = std::make_unique<Fixture>();
|
||||
std::array<Value, 4> material_words;
|
||||
std::array<Value, 4> heap_words;
|
||||
@@ -215,12 +216,14 @@ MakeIndirectImageFixture(bool malformed, uint32_t table_stride = 32, uint32_t ta
|
||||
heap_words[dword] = fixture->UserData(dword + 4u);
|
||||
}
|
||||
const auto material = fixture->Buffer(material_words, 0x10d8);
|
||||
const auto heap = fixture->Buffer(heap_words, 0x10d8);
|
||||
const auto heap = finite_key ? fixture->Address(heap_words[0], heap_words[1], 0x10d8)
|
||||
: fixture->Buffer(heap_words, 0x10d8);
|
||||
const auto invocation = fixture->Emit(
|
||||
ValueOpcode::GetBuiltin,
|
||||
{Value(static_cast<uint32_t>(StageInputKind::GlobalInvocationId)), Value(0u)});
|
||||
const auto selector = fixture->Emit(ValueOpcode::ReadFirstLane,
|
||||
{invocation, Value(true)});
|
||||
{Value(static_cast<uint32_t>(finite_key ? StageInputKind::WorkgroupId
|
||||
: StageInputKind::GlobalInvocationId)), Value(0u)});
|
||||
const auto selector = finite_key ? invocation
|
||||
: fixture->Emit(ValueOpcode::ReadFirstLane, {invocation, Value(true)});
|
||||
Value member;
|
||||
if (nested) {
|
||||
std::array<Value, 4> root_words;
|
||||
@@ -240,24 +243,27 @@ MakeIndirectImageFixture(bool malformed, uint32_t table_stride = 32, uint32_t ta
|
||||
MemoryInfo material_scalar;
|
||||
material_scalar.kind = ResourceKind::ScalarBuffer;
|
||||
material_scalar.offset = 4u;
|
||||
const auto key =
|
||||
fixture->Emit(ValueOpcode::ReadConstBuffer, {material, member},
|
||||
fixture->AddMemory(material_scalar, 0x10d8));
|
||||
auto key = fixture->Emit(ValueOpcode::ReadConstBuffer, {material, member},
|
||||
fixture->AddMemory(material_scalar, 0x10d8));
|
||||
if (finite_key) key = fixture->Emit(ValueOpcode::BitFieldUExtract,
|
||||
{key, Value(16u), Value(5u)});
|
||||
auto heap_offset = fixture->Emit(ValueOpcode::IMul32, {key, Value(table_stride)});
|
||||
if (table_shader_offset != 0u)
|
||||
heap_offset = fixture->Emit(ValueOpcode::IAdd32, {heap_offset, Value(table_shader_offset)});
|
||||
std::array<Value, 8> image_words;
|
||||
MemoryInfo heap_scalar;
|
||||
heap_scalar.kind = ResourceKind::ScalarBuffer;
|
||||
heap_scalar.kind = finite_key ? ResourceKind::ScalarAddress : ResourceKind::ScalarBuffer;
|
||||
for (uint32_t dword = 0; dword < image_words.size(); dword++) {
|
||||
auto component = heap_scalar;
|
||||
component.offset = table_offset + dword * sizeof(uint32_t);
|
||||
if (malformed && dword == image_words.size() - 1u) {
|
||||
component.offset += sizeof(uint32_t);
|
||||
}
|
||||
image_words[dword] =
|
||||
fixture->Emit(ValueOpcode::ReadConstBuffer, {heap, heap_offset},
|
||||
fixture->AddMemory(component, 0x10d8));
|
||||
const auto flags = fixture->AddMemory(component, 0x10d8);
|
||||
image_words[dword] = finite_key
|
||||
? fixture->Emit(ValueOpcode::LoadAddressU32,
|
||||
{heap, heap_offset, Value(0u), Value(true)}, flags)
|
||||
: fixture->Emit(ValueOpcode::ReadConstBuffer, {heap, heap_offset}, flags);
|
||||
}
|
||||
if (nested) {
|
||||
const auto field = fixture->Emit(ValueOpcode::BitwiseAnd32, {image_words[4], Value(7u)});
|
||||
@@ -269,12 +275,27 @@ MakeIndirectImageFixture(bool malformed, uint32_t table_stride = 32, uint32_t ta
|
||||
MemoryInfo sample;
|
||||
sample.kind = ResourceKind::Image;
|
||||
sample.image_dimension = Decoder::ImageDimension::Dim2D;
|
||||
const auto sampled = fixture->Emit(ValueOpcode::ImageSampleRaw,
|
||||
{image, sampler, fixture->ImageAddress(), Value(true)},
|
||||
fixture->AddMemory(sample, 0x10f0));
|
||||
const auto sampled_x =
|
||||
fixture->Emit(ValueOpcode::CompositeExtractU32x4, {sampled, Value(0u)});
|
||||
fixture->Emit(ValueOpcode::ReferenceU32, {sampled_x});
|
||||
const auto address = fixture->ImageAddress();
|
||||
const auto flags = fixture->AddMemory(sample, 0x10f0);
|
||||
Value result;
|
||||
if (access == ValueOpcode::ImageWrite) {
|
||||
const auto texel = fixture->Emit(ValueOpcode::CompositeConstructU32x4,
|
||||
{Value(1u), Value(2u), Value(3u), Value(4u)});
|
||||
fixture->Emit(access, {image, address, texel, Value(true)}, flags);
|
||||
} else if (access == ValueOpcode::ImageRead) {
|
||||
result = fixture->Emit(access, {image, address, Value(true)}, flags);
|
||||
} else if (access == ValueOpcode::ImageQueryDimensions) {
|
||||
result = fixture->Emit(access, {image, address}, flags);
|
||||
} else if (access == ValueOpcode::ImageAtomicIAdd32) {
|
||||
result = fixture->Emit(access, {image, address, Value(1u), Value(true)}, flags);
|
||||
} else {
|
||||
result = fixture->Emit(access, {image, sampler, address, Value(true)}, flags);
|
||||
}
|
||||
if (!result.IsEmpty()) {
|
||||
if (result.GetType() == Type::U32x4)
|
||||
result = fixture->Emit(ValueOpcode::CompositeExtractU32x4, {result, Value(0u)});
|
||||
fixture->Emit(ValueOpcode::ReferenceU32, {result});
|
||||
}
|
||||
return fixture;
|
||||
}
|
||||
|
||||
@@ -441,6 +462,113 @@ void TestBoundedImageViewEligibility() {
|
||||
"explicitly selected incompatible descriptor was silently normalized to null");
|
||||
}
|
||||
|
||||
void TestExactUnfilteredImageCandidates() {
|
||||
using F = Libs::Graphics::Prospero::BufferFormat;
|
||||
using T = Libs::Graphics::Prospero::ImageType;
|
||||
constexpr std::array<uint32_t, 8> placeholder{
|
||||
0x50091c00u, 0x03800000u, 0u, 0x80000000u, 0u, 0x00700000u, 0u, 0u};
|
||||
for (const auto access : {ValueOpcode::ImageRead, ValueOpcode::ImageWrite,
|
||||
ValueOpcode::ImageSampleRaw, ValueOpcode::ImageQueryDimensions,
|
||||
ValueOpcode::ImageAtomicIAdd32}) {
|
||||
auto fixture = MakeIndirectImageFixture(false, 32u, 0u, 0u, false, access, true);
|
||||
fixture->PlanAndTrack();
|
||||
auto plan = ExtractResourcePlan(fixture->program);
|
||||
const bool unfiltered = access == ValueOpcode::ImageRead || access == ValueOpcode::ImageWrite;
|
||||
Check(plan.info.images[0].unfiltered_access_only == unfiltered,
|
||||
"image access classification admitted a sample, query or atomic");
|
||||
const auto &source = *plan.descriptor_sources[plan.info.images[0].source].indirect_descriptor;
|
||||
Check(source.key_count.Resolve() == Value(32u),
|
||||
"five-bit image selector did not preserve its exact 32-row domain");
|
||||
LinearTestMemory memory;
|
||||
memory.fail_address = 0x3000u; // Selector payload stays on the GPU.
|
||||
auto first = placeholder;
|
||||
first[0] += 1u;
|
||||
first[3] = Libs::Graphics::DstSel(4, 5, 6, 7) | (uint32_t(T::kColor2D) << 28u);
|
||||
auto second = placeholder;
|
||||
if (access == ValueOpcode::ImageAtomicIAdd32) {
|
||||
first[1] = second[1] = uint32_t(F::k32UInt) << 20u;
|
||||
second[3] |= Libs::Graphics::DstSel(4, 5, 6, 7);
|
||||
}
|
||||
const auto fill = [&] {
|
||||
for (uint32_t row = 0; row < 32u; ++row) {
|
||||
const auto &words = row == 31u ? second : first;
|
||||
std::copy(words.begin(), words.end(), memory.words.begin() + 0x1000u / 4u + row * 8u);
|
||||
}
|
||||
};
|
||||
fill();
|
||||
std::array<uint32_t, 8> data{0x3000u, 4u << 16u, 32u, 0u,
|
||||
0x2000u, 0u, 0u, 0u};
|
||||
const SrtRuntime runtime{.user_data = data, .userdata = &memory,
|
||||
.read_specialization_memory = ReadLinearTestMemory};
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) == unfiltered,
|
||||
"mixed 1D/2D candidates violated the all-uses access guard");
|
||||
if (!unfiltered) continue;
|
||||
Check(snapshot.images.size() == 2u && snapshot.images[1].dwords == placeholder &&
|
||||
specialization.images[0].dimension == Decoder::ImageDimension::Dim2D &&
|
||||
specialization.images[1].dimension == Decoder::ImageDimension::Dim1D,
|
||||
"a real 1D zero-swizzle descriptor was filtered, nulled or retyped");
|
||||
if (access == ValueOpcode::ImageWrite)
|
||||
Check(specialization.images[1].shader_swizzle == 0u,
|
||||
"storage placeholder lost its descriptor swizzle");
|
||||
const auto operation = std::ranges::find_if(*fixture->block, [&](const Inst &inst) {
|
||||
return inst.GetOpcode() == access;
|
||||
});
|
||||
const auto key = operation->Arg(0).ResolveInstruction()->Arg(0);
|
||||
ApplyResourceSpecialization(fixture->program, specialization);
|
||||
const auto &images = fixture->program.info.images;
|
||||
const auto &selected = fixture->program.memory_info[operation->Flags<MemoryFlags>().index];
|
||||
Check(images.size() == 2u && selected.resource == 0u &&
|
||||
selected.image_dimension == Decoder::ImageDimension::Dim2D &&
|
||||
images[0].indirect_resources == std::vector<uint32_t>({0u, 1u}) &&
|
||||
images[1].indirect_root == 0u &&
|
||||
images[1].dimension == Decoder::ImageDimension::Dim1D &&
|
||||
images[1].unfiltered_access_only &&
|
||||
operation->Arg(0).ResolveInstruction()->Arg(0) == key &&
|
||||
(access != ValueOpcode::ImageWrite || images[1].shader_swizzle == 0u),
|
||||
"applying mixed candidates lost GPU selection, typed child or storage swizzle");
|
||||
second[0] += 2u;
|
||||
fill();
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
snapshot.images[1].dwords[0] == second[0],
|
||||
"mixed-dimension materialization reused a stale descriptor");
|
||||
plan.info.images[0].dimension = Decoder::ImageDimension::Dim1D;
|
||||
Check(!MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
"encoded 1D payload was treated as having a second coordinate");
|
||||
plan.info.images[0].dimension = Decoder::ImageDimension::Dim2D;
|
||||
second[1] = uint32_t(F::k11_11_10UInt) << 20u;
|
||||
fill();
|
||||
Check(!MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
"mixed-dimension acceptance discarded conversion compatibility");
|
||||
if (access == ValueOpcode::ImageRead) {
|
||||
first[1] = second[1];
|
||||
fill();
|
||||
Check(!MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
"sampled converted candidates accepted different shader swizzles");
|
||||
first[1] = placeholder[1];
|
||||
}
|
||||
second[1] = placeholder[1];
|
||||
second[3] |= 1u << 16u;
|
||||
fill();
|
||||
plan.info.images[0].mip_mode = ImageMipMode::Dynamic;
|
||||
Check(!MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
"mixed-dimension acceptance discarded mip-count compatibility");
|
||||
}
|
||||
// The same image may be merged from several uses; one query keeps the guard strict.
|
||||
auto merged = MakeIndirectImageFixture(false, 32u, 0u, 0u, false, ValueOpcode::ImageRead, true);
|
||||
const auto handle = std::ranges::find_if(*merged->block, [](const Inst &inst) {
|
||||
return inst.GetOpcode() == ValueOpcode::GetImageResource;
|
||||
});
|
||||
merged->Emit(ValueOpcode::ImageQueryDimensions, {Value(&*handle), merged->ImageAddress()},
|
||||
merged->AddMemory({.kind = ResourceKind::Image,
|
||||
.image_dimension = Decoder::ImageDimension::Dim2D}, 0x1100));
|
||||
merged->PlanAndTrack();
|
||||
Check(merged->program.info.images.size() == 1u &&
|
||||
!merged->program.info.images[0].unfiltered_access_only,
|
||||
"a later merged query left the unfiltered-use proof intact");
|
||||
}
|
||||
|
||||
void TestWaterfallImageTable() {
|
||||
namespace CFG = Libs::Graphics::ShaderRecompiler::CFG;
|
||||
const auto make = [](bool equality, bool scalar) {
|
||||
@@ -6757,6 +6885,7 @@ int main() {
|
||||
Run("invariant indirect images", [] { TestInvariantIndirectImageMaterialization(true); });
|
||||
Run("unbounded material image keys", [] { TestInvariantIndirectImageMaterialization(false); });
|
||||
Run("bounded image view eligibility", TestBoundedImageViewEligibility);
|
||||
Run("exact unfiltered image candidates", TestExactUnfilteredImageCandidates);
|
||||
Run("waterfall image table", TestWaterfallImageTable);
|
||||
Run("nested pointer image table", TestNestedPointerImageTable);
|
||||
Run("guarded direct image table", TestGuardedDirectImageTable);
|
||||
|
||||
@@ -16083,11 +16083,11 @@ public:
|
||||
};
|
||||
for (const auto &image : compiled.program.info.images) {
|
||||
bool supported =
|
||||
image.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1D ||
|
||||
image.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D ||
|
||||
image.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray;
|
||||
if (image.resource_class == ShaderRecompiler::IR::ImageResourceClass::Sampled) {
|
||||
supported = supported ||
|
||||
image.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1D ||
|
||||
image.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1DArray;
|
||||
}
|
||||
Require(test.name, "dispatch", supported,
|
||||
@@ -38429,11 +38429,11 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) {
|
||||
resource == 4u ? vk::ImageViewType::e2DArray : vk::ImageViewType::e2D, layers));
|
||||
}
|
||||
const auto native_sampler = vulkan.CreateSampler(test.name);
|
||||
enum class Access { Sample, Gather, GatherHomogeneous, Fetch };
|
||||
enum class Access { Sample, Gather, GatherHomogeneous, Fetch, Fetch1D };
|
||||
auto *access = &sample;
|
||||
for (const auto mode : {Access::Sample, Access::Gather,
|
||||
Access::GatherHomogeneous, Access::Fetch}) {
|
||||
const bool fetch = mode == Access::Fetch;
|
||||
Access::GatherHomogeneous, Access::Fetch, Access::Fetch1D}) {
|
||||
const bool fetch = mode == Access::Fetch || mode == Access::Fetch1D;
|
||||
const bool gather = mode == Access::Gather || mode == Access::GatherHomogeneous;
|
||||
const bool homogeneous = mode == Access::GatherHomogeneous;
|
||||
if (mode == Access::Gather) {
|
||||
@@ -38472,6 +38472,7 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) {
|
||||
position, ValueOpcode::ImageRead,
|
||||
{Value(&image), Value(&address), Value(true)}, memory_flag_bits);
|
||||
access->ReplaceUsesWith(Value(&read), false);
|
||||
access = &read;
|
||||
sampler.Invalidate();
|
||||
compiled.program.info.samplers.clear();
|
||||
compiled.program.info.sampled_pairs.clear();
|
||||
@@ -38483,6 +38484,27 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) {
|
||||
address.SetArg(component, Value(0u));
|
||||
// Native DIM=2DArray places LOD after the layer, even for a 2D candidate.
|
||||
address.SetArg(3, Value(1u));
|
||||
if (mode == Access::Fetch1D) {
|
||||
test.name = "IndirectImageFetch1D2D";
|
||||
compiled.program.memory_info[0].image_dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
||||
compiled.program.memory_info[0].image_address_components = 3;
|
||||
address.SetArg(2, Value(1u));
|
||||
address.SetArg(3, Value(0u));
|
||||
for (const u32 resource : {3u, 4u}) {
|
||||
const bool one_d = resource == 3u;
|
||||
compiled.program.info.images[resource].dimension = one_d
|
||||
? ShaderRecompiler::Decoder::ImageDimension::Dim1D
|
||||
: ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
||||
vulkan.DestroyImage(&textures[resource]);
|
||||
textures[resource] = vulkan.CreateImageMips(test.name, 2, 1, vk::Format::eR32G32B32A32Sfloat,
|
||||
vk::ImageUsageFlagBits::eSampled,
|
||||
{std::vector<u32>(8u, std::bit_cast<u32>(float(resource))),
|
||||
std::vector<u32>(4u, std::bit_cast<u32>(float(resource + 10u)))},
|
||||
4u, vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||
one_d ? vk::ImageType::e1D : vk::ImageType::e2D,
|
||||
one_d ? vk::ImageViewType::e1D : vk::ImageViewType::e2D, 1u);
|
||||
}
|
||||
}
|
||||
}
|
||||
compiled.program.shader_info_complete = false;
|
||||
CollectShaderInfo(compiled.program, {.compute = &compute});
|
||||
@@ -38568,7 +38590,7 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) {
|
||||
}
|
||||
Require(test.name, "mixed numeric image result", raw_join &&
|
||||
tools.Disassemble(mixed_spirv, &text) &&
|
||||
CountText(text, image_opcode) == 4u * halves &&
|
||||
CountText(text, image_opcode) == (mode == Access::Fetch1D ? 3u : 4u) * halves &&
|
||||
CountText(text, "OpConvertUToF") == coordinate_conversions,
|
||||
"mixed image classes lost raw result bits or expanded per image");
|
||||
typed.numeric_class = Prospero::TextureNumericClass::Float;
|
||||
@@ -38585,7 +38607,6 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) {
|
||||
|
||||
// Reuse the GPU key map, including the separated root slot and unmapped suffix,
|
||||
// for storage writes. Every lane has a distinct texel address.
|
||||
test.name = "IndirectImageStoreHomogeneous";
|
||||
store.Invalidate();
|
||||
for (auto &inst : *block)
|
||||
if (inst.GetOpcode() == ValueOpcode::ReferenceU32) inst.Invalidate();
|
||||
@@ -38596,6 +38617,7 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) {
|
||||
store_memory.image_address_components = 2;
|
||||
store_memory.image_has_mip = false;
|
||||
address.SetArg(0, Value(&key));
|
||||
address.SetArg(2, Value(0u));
|
||||
address.SetArg(3, Value(0u));
|
||||
for (auto &resource : compiled.program.info.images) {
|
||||
resource.resource_class = ImageResourceClass::Storage;
|
||||
@@ -38613,45 +38635,61 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) {
|
||||
block->AppendNewInst(ValueOpcode::ImageWrite,
|
||||
{Value(&image), Value(&address), Value(&texel), Value(&write_exec)}, memory_flag_bits);
|
||||
EliminateDeadCode(compiled.program.blocks);
|
||||
compiled.program.shader_info_complete = false;
|
||||
CollectShaderInfo(compiled.program, {.compute = &compute});
|
||||
for (const u32 wave_size : {32u, 64u}) {
|
||||
compute.wave_size = wave_size;
|
||||
compute.threads_num[0] = wave_size;
|
||||
compiled.program.wave_size = wave_size;
|
||||
compiled.spirv = ShaderRecompiler::Spirv::EmitProgram(compiled.program, {.compute = &compute});
|
||||
compiled.packed_user_data.resize(compiled.program.bindings.ShaderDataDwords());
|
||||
ValidateSpirv(test.name, compiled.spirv);
|
||||
const auto halves = wave_size > compute.host_subgroup_size ? 2u : 1u;
|
||||
Require(test.name, "one native array write per wave half",
|
||||
tools.Disassemble(compiled.spirv, &text) && CountText(text, "OpImageWrite") == halves &&
|
||||
text.find("OpSwitch") == std::string::npos &&
|
||||
text.find("StorageImageArrayNonUniformIndexing") != std::string::npos,
|
||||
"homogeneous storage candidates lost their native array selection");
|
||||
std::vector<std::vector<u32>> expected_images;
|
||||
for (u32 resource = 0; resource < compiled.program.info.images.size(); ++resource) {
|
||||
expected_images.emplace_back((wave_size + 2u) * 4u, 0x41000000u + resource);
|
||||
textures.push_back(vulkan.CreateImage2D(test.name, wave_size + 2u, 1,
|
||||
vk::Format::eR32G32B32A32Sfloat, vk::ImageUsageFlagBits::eStorage,
|
||||
expected_images.back(), 4u, vk::ImageLayout::eGeneral));
|
||||
for (const bool mixed : {false, true}) {
|
||||
test.name = mixed ? "IndirectImageStoreMixed" : "IndirectImageStoreHomogeneous";
|
||||
compiled.program.info.images[3].dimension = mixed
|
||||
? ShaderRecompiler::Decoder::ImageDimension::Dim1D
|
||||
: ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
||||
compiled.program.info.images[5].shader_swizzle = mixed ? DstSel(5, 4, 6, 7)
|
||||
: ShaderImageIdentitySwizzle;
|
||||
compiled.program.info.images[6].shader_swizzle = mixed ? 0u : ShaderImageIdentitySwizzle;
|
||||
compiled.program.shader_info_complete = false;
|
||||
CollectShaderInfo(compiled.program, {.compute = &compute});
|
||||
for (const u32 wave_size : {32u, 64u}) {
|
||||
compute.wave_size = wave_size;
|
||||
compute.threads_num[0] = wave_size;
|
||||
compiled.program.wave_size = wave_size;
|
||||
compiled.spirv = ShaderRecompiler::Spirv::EmitProgram(compiled.program, {.compute = &compute});
|
||||
compiled.packed_user_data.resize(compiled.program.bindings.ShaderDataDwords());
|
||||
ValidateSpirv(test.name, compiled.spirv);
|
||||
const auto halves = wave_size > compute.host_subgroup_size ? 2u : 1u;
|
||||
Require(test.name, "native array writes per dimension and swizzle run",
|
||||
tools.Disassemble(compiled.spirv, &text) &&
|
||||
CountText(text, "OpImageWrite") == (mixed ? 5u : 1u) * halves &&
|
||||
(mixed || text.find("OpSwitch") == std::string::npos) &&
|
||||
text.find("StorageImageArrayNonUniformIndexing") != std::string::npos,
|
||||
"storage candidates lost their typed native array runs");
|
||||
std::vector<std::vector<u32>> expected_images;
|
||||
for (u32 resource = 0; resource < compiled.program.info.images.size(); ++resource) {
|
||||
expected_images.emplace_back((wave_size + 2u) * 4u, 0x41000000u + resource);
|
||||
const bool one_d = compiled.program.info.images[resource].dimension ==
|
||||
ShaderRecompiler::Decoder::ImageDimension::Dim1D;
|
||||
textures.push_back(vulkan.CreateImageMips(test.name, wave_size + 2u, 1,
|
||||
vk::Format::eR32G32B32A32Sfloat, vk::ImageUsageFlagBits::eStorage,
|
||||
{expected_images.back()}, 4u, vk::ImageLayout::eGeneral,
|
||||
one_d ? vk::ImageType::e1D : vk::ImageType::e2D,
|
||||
one_d ? vk::ImageViewType::e1D : vk::ImageViewType::e2D, 1u));
|
||||
}
|
||||
test.initial = {0xdeadbeefu};
|
||||
for (u32 lane = 0; lane < wave_size; ++lane) {
|
||||
const auto resource = root.indirect_resources[lane < ordinals.size() ? ordinals[lane] : 0u];
|
||||
if (lane == 2u) continue;
|
||||
std::array written{0x3f800000u + lane, 0x40000000u, 0x40400000u, 0x40800000u};
|
||||
if (mixed && resource == 5u) std::swap(written[0], written[1]);
|
||||
if (mixed && resource == 6u) written.fill(0u);
|
||||
std::copy(written.begin(), written.end(), expected_images[resource].begin() + lane * 4u);
|
||||
}
|
||||
auto buffer = vulkan.CreateStorageBuffer(test.name, test.initial, test.initial.size());
|
||||
vulkan.Dispatch(test, compiled, buffer, nullptr, nullptr, nullptr, nullptr, nullptr, textures);
|
||||
for (u32 resource = 0; resource < textures.size(); ++resource) {
|
||||
CompareWords(test, "selected writes, inactive lane and neighboring texels",
|
||||
expected_images[resource], vulkan.ReadImage(test.name, &textures[resource]));
|
||||
vulkan.DestroyImage(&textures[resource]);
|
||||
}
|
||||
textures.clear();
|
||||
vulkan.DestroyBuffer(&buffer);
|
||||
std::printf("[compute] %s wave=%u ok\n", test.name, wave_size);
|
||||
}
|
||||
test.initial = {0xdeadbeefu};
|
||||
for (u32 lane = 0; lane < wave_size; ++lane) {
|
||||
const auto resource = root.indirect_resources[lane < ordinals.size() ? ordinals[lane] : 0u];
|
||||
if (lane == 2u) continue;
|
||||
const std::array written{0x3f800000u + lane, 0x40000000u, 0x40400000u, 0x40800000u};
|
||||
std::copy(written.begin(), written.end(), expected_images[resource].begin() + lane * 4u);
|
||||
}
|
||||
auto buffer = vulkan.CreateStorageBuffer(test.name, test.initial, test.initial.size());
|
||||
vulkan.Dispatch(test, compiled, buffer, nullptr, nullptr, nullptr, nullptr, nullptr, textures);
|
||||
for (u32 resource = 0; resource < textures.size(); ++resource) {
|
||||
CompareWords(test, "selected writes, inactive lane and neighboring texels",
|
||||
expected_images[resource], vulkan.ReadImage(test.name, &textures[resource]));
|
||||
vulkan.DestroyImage(&textures[resource]);
|
||||
}
|
||||
textures.clear();
|
||||
vulkan.DestroyBuffer(&buffer);
|
||||
std::printf("[compute] %s wave=%u ok\n", test.name, wave_size);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user