shader: merge typed image candidates as raw vectors with shared sampler variants

This commit is contained in:
nmzik
2026-10-12 18:54:54 +02:00
parent 49ebbfd627
commit 652154aead
5 changed files with 238 additions and 99 deletions
@@ -269,20 +269,13 @@ uint32_t ResultVector(ValueEmitContext& ctx, uint32_t value,
result, packed[0], packed[1], packed[2], packed[3]); result, packed[0], packed[1], packed[2], packed[3]);
return result; return result;
} }
uint32_t component[4] {}; if (!dref)
for (uint32_t index = 0; index < 4u; index++) { return value_class == Prospero::TextureNumericClass::Uint
if (dref) { ? value : Unary(ctx.state, spv::OpBitcast, TypeU32Vector(ctx.state, 4), value);
component[index] = index == 0u ? FloatBits(ctx, value) : ConstantU32(ctx.state, 0);
continue;
}
const auto scalar = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(
spv::OpCompositeExtract, ImageScalarType(ctx.state, value_class), scalar, value, index);
component[index] = SampledComponentBits(ctx, scalar, value_class);
}
const auto result = ctx.state.builder.AllocateId(); const auto result = ctx.state.builder.AllocateId();
const auto zero = ConstantU32(ctx.state, 0);
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4), result, ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4), result,
component[0], component[1], component[2], component[3]); FloatBits(ctx, value), zero, zero, zero);
return result; return result;
} }
@@ -610,6 +603,45 @@ uint32_t StoreTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t d
return PackImageTexel(ctx, mem, texel); return PackImageTexel(ctx, mem, texel);
} }
struct ImageSampler {
uint32_t id = 0;
bool dynamic = false;
};
auto EmitImageSamplers(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
const auto& mem = ctx.Memory(inst);
const auto& images = state.program.info.images;
const auto& samplers = state.program.info.samplers;
std::array<ImageSampler, static_cast<size_t>(Prospero::TextureNumericClass::Sint) + 1u> result {};
const auto add = [&](uint32_t image, uint32_t sampler) {
auto& entry = result[static_cast<size_t>(images[image].numeric_class)];
if (entry.id != 0u) return;
const auto& choices = samplers.at(sampler).indirect_resources;
uint32_t index = 0;
if (!choices.empty()) {
const auto* handle = inst.Arg(1).ResolveInstruction();
if (handle == nullptr || handle->GetOpcode() != IR::ValueOpcode::GetSamplerResource)
ctx.Fail(inst, "has invalid finite sampler key provenance");
index = EmitIndexSwitch(
state, ctx.Def(handle->Arg(0)), static_cast<uint32_t>(choices.size()),
TypeU32(state), [&](uint32_t ordinal) { return ConstantU32(state, choices[ordinal]); });
}
entry = {LoadSamplerDescriptor(state, sampler, index), index != 0u};
};
add(mem.resource, mem.sampler);
if (images[mem.resource].indirect_root == mem.resource) {
for (const auto& pair: state.program.info.sampled_pairs)
if (images[pair.image].indirect_root == mem.resource &&
samplers[pair.sampler].snapshot_index == samplers[mem.sampler].snapshot_index)
add(pair.image, pair.sampler);
for (const auto candidate: images[mem.resource].indirect_resources)
if (result[static_cast<size_t>(images[candidate].numeric_class)].id == 0u)
ctx.Fail(inst, "has no sampler variant for an indirect image candidate");
}
return result;
}
template <typename Emit> template <typename Emit>
uint32_t EmitImageAccess(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t result_type, uint32_t EmitImageAccess(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t result_type,
const Emit& emit) { const Emit& emit) {
@@ -735,15 +767,14 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
return; return;
} }
if (op == IR::ValueOpcode::ImageRead) { if (op == IR::ValueOpcode::ImageRead) {
const auto numeric_class = image.numeric_class;
const auto condition = ctx.Arg(inst, 2); const auto condition = ctx.Arg(inst, 2);
ctx.Define( ctx.Define(
inst, inst,
EmitValueOrDefaultIfCondition( EmitValueOrDefaultIfCondition(
state, condition, TypeU32Vector(state, 4), ConstantU32CompositeZero(state, 4), state, condition, TypeU32Vector(state, 4), ConstantU32CompositeZero(state, 4),
[&]() { [&]() {
const auto color = EmitImageAccess( return EmitImageAccess(
ctx, inst, ImageVectorType(state, numeric_class, 4), ctx, inst, TypeU32Vector(state, 4),
[&](uint32_t resource, uint32_t array_index) { [&](uint32_t resource, uint32_t array_index) {
const auto& candidate = state.program.info.images[resource]; const auto& candidate = state.program.info.images[resource];
const auto dimension = candidate.dimension; const auto dimension = candidate.dimension;
@@ -762,10 +793,9 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
descriptor, coord, spv::ImageOperandsLodMask, descriptor, coord, spv::ImageOperandsLodMask,
LodU32(ctx, mem, *address, mem.image_dimension)); LodU32(ctx, mem, *address, mem.image_dimension));
} }
return fetched; return ResultVector(ctx, UnpackImageTexel(ctx, mem, fetched),
candidate.numeric_class, false, mem);
}); });
return ResultVector(ctx, UnpackImageTexel(ctx, mem, color), numeric_class,
false, mem);
})); }));
return; return;
} }
@@ -820,9 +850,6 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
ctx.Fail(inst, "has an unsupported 1D-array gather"); ctx.Fail(inst, "has an unsupported 1D-array gather");
return; return;
} }
const auto result_numeric_class = dref ? Prospero::TextureNumericClass::Float
: numeric_class;
const auto result_type = ImageVectorType(state, result_numeric_class, 4);
uint32_t component_or_dref; uint32_t component_or_dref;
if (dref) { if (dref) {
component_or_dref = layout.dref != NoImageComponent component_or_dref = layout.dref != NoImageComponent
@@ -831,9 +858,13 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
} else { } else {
component_or_dref = ConstantU32(state, ImageGatherSource(state, mem)); component_or_dref = ConstantU32(state, ImageGatherSource(state, mem));
} }
const auto sampler_id = LoadSamplerDescriptor(state, mem.sampler); const auto samplers = EmitImageSamplers(ctx, inst);
const auto EmitGather = [&](uint32_t resource, uint32_t array_index) { const auto EmitGather = [&](uint32_t resource, uint32_t array_index) {
const auto& candidate = state.program.info.images[resource]; const auto& candidate = state.program.info.images[resource];
const auto numeric_class = dref ? Prospero::TextureNumericClass::Float
: candidate.numeric_class;
const auto result_type = ImageVectorType(state, numeric_class, 4);
const auto sampler = samplers[static_cast<size_t>(candidate.numeric_class)];
const auto dimension = candidate.dimension; const auto dimension = candidate.dimension;
const auto coord = CoordF32( const auto coord = CoordF32(
ctx, mem, *address, layout.coord, ctx, mem, *address, layout.coord,
@@ -847,7 +878,8 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
offset = PackedOffset(ctx, offset_value, dimension, operand_mask); offset = PackedOffset(ctx, offset_value, dimension, operand_mask);
} }
const auto EmitMip = [&](uint32_t mip) { const auto EmitMip = [&](uint32_t mip) {
const auto sampled = MakeSampledImage(state, resource, sampler_id, mip, array_index); const auto sampled = MakeSampledImage(state, resource, sampler.id, mip, array_index,
sampler.dynamic);
const auto sample = state.builder.AllocateId(); const auto sample = state.builder.AllocateId();
const std::array operands {operand_mask, offset}; const std::array operands {operand_mask, offset};
state.builder.AddFunction( state.builder.AddFunction(
@@ -856,15 +888,15 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
std::span(operands).first(operand_mask != 0u ? 2u : 0u)); std::span(operands).first(operand_mask != 0u ? 2u : 0u));
return sample; return sample;
}; };
return candidate.mip_mode == IR::ImageMipMode::Dynamic && candidate.mip_count > 1u const auto gathered = candidate.mip_mode == IR::ImageMipMode::Dynamic && candidate.mip_count > 1u
? EmitIndexSwitch( ? EmitIndexSwitch(
state, GatherMip(ctx, inst, mem, *address, layout, candidate.mip_count), state, GatherMip(ctx, inst, mem, *address, layout, candidate.mip_count),
candidate.mip_count, result_type, EmitMip) candidate.mip_count, result_type, EmitMip)
: EmitMip(0u); : EmitMip(0u);
return ResultVector(ctx, UnpackImageGather(ctx, mem, gathered), numeric_class,
false, mem, true);
}; };
const auto sample = EmitImageAccess(ctx, inst, result_type, EmitGather); ctx.Define(inst, EmitImageAccess(ctx, inst, TypeU32Vector(state, 4), EmitGather));
ctx.Define(inst, ResultVector(ctx, UnpackImageGather(ctx, mem, sample),
result_numeric_class, false, mem, true));
return; return;
} }
const bool explicit_lod = HasFlag(mem, Decoder::ImageSampleFlagDerivative) || const bool explicit_lod = HasFlag(mem, Decoder::ImageSampleFlagDerivative) ||
@@ -877,10 +909,8 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
} else if (dref) { } else if (dref) {
opcode = spv::OpImageSampleDrefImplicitLod; opcode = spv::OpImageSampleDrefImplicitLod;
} }
uint32_t result_type = ImageVectorType(state, numeric_class, 4);
uint32_t dref_value = 0; uint32_t dref_value = 0;
if (dref) { if (dref) {
result_type = TypeF32(state);
dref_value = ZeroF32(state); dref_value = ZeroF32(state);
if (layout.dref != NoImageComponent) { if (layout.dref != NoImageComponent) {
dref_value = AddressF32(ctx, mem, *address, layout.dref); dref_value = AddressF32(ctx, mem, *address, layout.dref);
@@ -906,23 +936,16 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
operands[1] = AddressF32(ctx, mem, *address, layout.bias); operands[1] = AddressF32(ctx, mem, *address, layout.bias);
operand_count = 2; operand_count = 2;
} }
const auto& samplers = state.program.info.samplers.at(mem.sampler).indirect_resources;
uint32_t sampler_index = 0;
if (!samplers.empty()) {
const auto* handle = inst.Arg(1).ResolveInstruction();
if (handle == nullptr || handle->GetOpcode() != IR::ValueOpcode::GetSamplerResource)
ctx.Fail(inst, "has invalid finite sampler key provenance");
sampler_index = EmitIndexSwitch(
state, ctx.Def(handle->Arg(0)), static_cast<uint32_t>(samplers.size()),
TypeU32(state), [&](uint32_t ordinal) { return ConstantU32(state, samplers[ordinal]); });
}
if (layout.offset != NoImageComponent && if (layout.offset != NoImageComponent &&
!offset_value.IsImmediate()) { !offset_value.IsImmediate()) {
ctx.Fail(inst, "has a nonconstant sample texel offset"); ctx.Fail(inst, "has a nonconstant sample texel offset");
} }
const auto sampler_id = LoadSamplerDescriptor(state, mem.sampler, sampler_index); const auto samplers = EmitImageSamplers(ctx, inst);
const auto EmitSample = [&](uint32_t resource, uint32_t array_index) { const auto EmitSample = [&](uint32_t resource, uint32_t array_index) {
const auto& candidate = state.program.info.images[resource]; const auto& candidate = state.program.info.images[resource];
const auto result_type = dref ? TypeF32(state)
: ImageVectorType(state, candidate.numeric_class, 4);
const auto sampler = samplers[static_cast<size_t>(candidate.numeric_class)];
auto sample_operands = operands; auto sample_operands = operands;
auto sample_operand_count = operand_count; auto sample_operand_count = operand_count;
if (layout.offset != NoImageComponent) { if (layout.offset != NoImageComponent) {
@@ -934,19 +957,16 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
CoordF32(ctx, mem, *address, layout.coord, CoordF32(ctx, mem, *address, layout.coord,
ImageDimensionInfoFor(candidate.dimension).coordinate_components, ImageDimensionInfoFor(candidate.dimension).coordinate_components,
candidate.cube); candidate.cube);
const auto sampled = MakeSampledImage(state, resource, sampler_id, 0u, array_index, const auto sampled = MakeSampledImage(state, resource, sampler.id, 0u, array_index,
sampler_index != 0u); sampler.dynamic);
const auto sample = state.builder.AllocateId(); const auto sample = state.builder.AllocateId();
state.builder.AddFunction(opcode, result_type, sample, sampled, coord, state.builder.AddFunction(opcode, result_type, sample, sampled, coord,
std::span(&dref_value, dref ? 1u : 0u), std::span(&dref_value, dref ? 1u : 0u),
std::span<const uint32_t>(sample_operands).first(sample_operand_count)); std::span<const uint32_t>(sample_operands).first(sample_operand_count));
return sample; return ResultVector(ctx, dref ? sample : UnpackImageTexel(ctx, mem, sample),
candidate.numeric_class, dref, mem);
}; };
auto result = EmitImageAccess(ctx, inst, result_type, EmitSample); ctx.Define(inst, EmitImageAccess(ctx, inst, TypeU32Vector(state, 4), EmitSample));
if (!dref) {
result = UnpackImageTexel(ctx, mem, result);
}
ctx.Define(inst, ResultVector(ctx, result, numeric_class, dref, mem));
return; return;
} }
if (image_info.access == IR::ImageAccess::Atomic) { if (image_info.access == IR::ImageAccess::Atomic) {
@@ -106,16 +106,12 @@ Prospero::BufferFormat ImageConversionFormat(Prospero::BufferFormat format) {
: Prospero::BufferFormat::kInvalid; : Prospero::BufferFormat::kInvalid;
} }
enum class SamplerClass : uint8_t { Float, Integer, PointInteger }; enum class SamplerClass : uint8_t { Float, Integer };
SamplerClass ClassifySampler(const ImageResource& image) { SamplerClass ClassifySampler(const ImageResource& image) {
if (image.numeric_class == Prospero::TextureNumericClass::Sint || return image.numeric_class == Prospero::TextureNumericClass::Uint ||
(image.numeric_class == Prospero::TextureNumericClass::Uint && image.numeric_class == Prospero::TextureNumericClass::Sint
image.conversion_format != Prospero::BufferFormat::kInvalid)) { ? SamplerClass::Integer : SamplerClass::Float;
return SamplerClass::PointInteger;
}
return image.numeric_class == Prospero::TextureNumericClass::Uint ? SamplerClass::Integer
: SamplerClass::Float;
} }
bool DescriptorIsCube(const DescriptorValue& descriptor) { bool DescriptorIsCube(const DescriptorValue& descriptor) {
@@ -469,7 +465,7 @@ struct SamplerPlan {
uint32_t source; uint32_t source;
SamplerClass type; SamplerClass type;
}; };
std::array<std::array<uint32_t, 3>, ShaderInfo::MaxSamplers> mapping; std::array<std::array<uint32_t, 2>, ShaderInfo::MaxSamplers> mapping;
std::array<Binding, ShaderInfo::MaxSamplers> bindings; std::array<Binding, ShaderInfo::MaxSamplers> bindings;
uint32_t sampler_count = 0; uint32_t sampler_count = 0;
}; };
@@ -645,8 +641,7 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
} }
const bool same_coordinates = image.dimension == image_class.dimension && const bool same_coordinates = image.dimension == image_class.dimension &&
image.cube == image_class.cube; image.cube == image_class.cube;
if (image.numeric_class != image_class.numeric_class || if ((!same_coordinates && !(is_2d(image.dimension) && is_2d(image_class.dimension))) ||
(!same_coordinates && !(is_2d(image.dimension) && is_2d(image_class.dimension))) ||
image.mip_count != image_class.mip_count || image.mip_count != image_class.mip_count ||
image.conversion_format != image_class.conversion_format || image.conversion_format != image_class.conversion_format ||
image.shader_swizzle != image_class.shader_swizzle) { image.shader_swizzle != image_class.shader_swizzle) {
@@ -1334,9 +1329,20 @@ void ApplyResourceSpecialization(Program& program, const ResourceSpecialization&
} }
SamplerPlan sampler_plan; SamplerPlan sampler_plan;
EXIT_IF(!BuildSamplerPlan(program.info, sampler_plan));
auto& samplers = program.info.samplers; auto& samplers = program.info.samplers;
auto& sampled_pairs = program.info.sampled_pairs; auto& sampled_pairs = program.info.sampled_pairs;
const auto original_pair_count = sampled_pairs.size();
for (size_t index = 0; index < original_pair_count; ++index) {
const auto pair = sampled_pairs[index];
uint32_t classes = 1u << static_cast<uint32_t>(images[pair.image].numeric_class);
for (const auto candidate: images[pair.image].indirect_resources) {
const auto bit = 1u << static_cast<uint32_t>(images[candidate].numeric_class);
if ((classes & bit) != 0u) continue;
classes |= bit;
sampled_pairs.push_back({candidate, pair.sampler, pair.first_use_pc});
}
}
EXIT_IF(!BuildSamplerPlan(program.info, sampler_plan));
const auto original_sampler_count = samplers.size(); const auto original_sampler_count = samplers.size();
samplers.reserve(sampler_plan.sampler_count); samplers.reserve(sampler_plan.sampler_count);
for (uint32_t index = 0; index < sampler_plan.sampler_count; index++) { for (uint32_t index = 0; index < sampler_plan.sampler_count; index++) {
@@ -1345,7 +1351,7 @@ void ApplyResourceSpecialization(Program& program, const ResourceSpecialization&
samplers.push_back(samplers[binding.source]); samplers.push_back(samplers[binding.source]);
} }
samplers[index].snapshot_index = binding.source; samplers[index].snapshot_index = binding.source;
samplers[index].force_point_filtering = binding.type == SamplerClass::PointInteger; samplers[index].force_point_filtering = binding.type == SamplerClass::Integer;
samplers[index].integer_border = binding.type != SamplerClass::Float; samplers[index].integer_border = binding.type != SamplerClass::Float;
} }
for (uint32_t index = 0; index < sampler_plan.sampler_count; index++) { for (uint32_t index = 0; index < sampler_plan.sampler_count; index++) {
+47
View File
@@ -1210,6 +1210,52 @@ void TestDescendingImageRange() {
} }
} }
void TestMixedIndirectImageSamplers() {
auto fixture = MakeIndirectImageFixture(false, 32u, 0u, 0u);
fixture->PlanAndTrack();
auto& samplers = fixture->program.info.samplers;
samplers.resize(3u, samplers[0]);
samplers[0].indirect_resources = {1u, 2u};
const auto plan = ExtractResourcePlan(fixture->program);
std::array<uint32_t, 8> data {0x3000u, 16u << 16u, 1u, 0u,
0x2000u, 32u << 16u, 3u, 0u};
LinearTestMemory memory;
using F = Libs::Graphics::Prospero::BufferFormat;
for (uint32_t record = 0; record < 3u; ++record) {
const auto offset = (0x1000u + record * 32u) / 4u;
memory.words[offset] = 0x100u + record;
memory.words[offset + 1u] = static_cast<uint32_t>(
record == 0u ? F::k32_32_32_32Float : F::k32_32_32_32UInt) << 20u;
memory.words[offset + 3u] = Libs::Graphics::DstSel(4, 5, 6, 7) |
(uint32_t(Libs::Graphics::Prospero::ImageType::kColor2D) << 28u);
}
const SrtRuntime runtime {.user_data = data, .userdata = &memory,
.read_specialization_memory = ReadLinearTestMemory};
ResourceSnapshot snapshot;
ResourceSpecialization specialization;
Check(MaterializeResources(plan, runtime, snapshot, specialization) && snapshot.images.size() == 4u,
"mixed float/uint image table failed materialization");
ApplyResourceSpecialization(fixture->program, specialization);
const auto& info = fixture->program.info;
Check(info.sampled_pairs.size() == 2u && info.samplers.size() == 6u,
"indirect image candidates duplicated sampler pairs or lost finite class variants");
for (const auto& pair : info.sampled_pairs) {
const auto integer = info.images[pair.image].numeric_class ==
Libs::Graphics::Prospero::TextureNumericClass::Uint;
const auto& sampler = info.samplers[pair.sampler];
Check(sampler.snapshot_index == 0u && sampler.integer_border == integer &&
sampler.force_point_filtering == integer && sampler.indirect_resources.size() == 2u &&
pair.first_use_pc == 0x10f0u,
"mixed image class changed native sampler identity or integer-border semantics");
for (uint32_t ordinal = 0; ordinal < sampler.indirect_resources.size(); ++ordinal) {
const auto& candidate = info.samplers[sampler.indirect_resources[ordinal]];
Check(candidate.snapshot_index == ordinal + 1u &&
candidate.integer_border == integer && candidate.force_point_filtering == integer,
"mixed image class mapped a finite sampler choice to the wrong variant");
}
}
}
void TestUniformizedMaterialImageKeys() { void TestUniformizedMaterialImageKeys() {
namespace CFG = Libs::Graphics::ShaderRecompiler::CFG; namespace CFG = Libs::Graphics::ShaderRecompiler::CFG;
enum class Variant { enum class Variant {
@@ -4086,6 +4132,7 @@ int main() {
Run("draw-uniform scalar image", TestUniformScalarBufferImage); Run("draw-uniform scalar image", TestUniformScalarBufferImage);
Run("scalar buffer selector domain", TestScalarBufferSelectorDomain); Run("scalar buffer selector domain", TestScalarBufferSelectorDomain);
Run("descending image range", TestDescendingImageRange); Run("descending image range", TestDescendingImageRange);
Run("mixed indirect image samplers", TestMixedIndirectImageSamplers);
Run("SRT runtime", TestSrtFlatteningAndRuntimeMemoization); Run("SRT runtime", TestSrtFlatteningAndRuntimeMemoization);
Run("dynamic SRT", TestDynamicSrtReadRemainsExplicit); Run("dynamic SRT", TestDynamicSrtReadRemainsExplicit);
Run("writable descriptor phi", TestWritableDescriptorPhi); Run("writable descriptor phi", TestWritableDescriptorPhi);
+93 -36
View File
@@ -34419,7 +34419,7 @@ TestCase ImageLoadR32SintUsesSignedSampledImage() {
test.user_data = MakeSampledTextureData(Prospero::BufferFormat::k32SInt); test.user_data = MakeSampledTextureData(Prospero::BufferFormat::k32SInt);
test.has_user_data = true; test.has_user_data = true;
test.required_spirv = {"image_17", "OpTypeImage %int", "OpImageFetch %v4int", test.required_spirv = {"image_17", "OpTypeImage %int", "OpImageFetch %v4int",
"OpBitcast %uint"}; "OpBitcast %v4uint"};
return test; return test;
} }
@@ -36113,49 +36113,102 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) {
resource == 4u ? vk::ImageViewType::e2DArray : vk::ImageViewType::e2D, layers)); resource == 4u ? vk::ImageViewType::e2DArray : vk::ImageViewType::e2D, layers));
} }
const auto native_sampler = vulkan.CreateSampler(test.name); const auto native_sampler = vulkan.CreateSampler(test.name);
enum class Access { Sample, Gather, GatherHomogeneous, Fetch }; constexpr u32 uint_texel = 0x80000106u;
auto uint_texture = vulkan.CreateImageMips(
test.name, 2, 2, vk::Format::eR32G32B32A32Uint, vk::ImageUsageFlagBits::eSampled,
{std::vector<u32>(16u, uint_texel), std::vector<u32>(4u, uint_texel + 10u)}, 4u,
vk::ImageLayout::eShaderReadOnlyOptimal, vk::ImageType::e2D, vk::ImageViewType::e2D, 1u);
vk::SamplerCreateInfo integer_sampler_info{};
integer_sampler_info.addressModeU = integer_sampler_info.addressModeV =
integer_sampler_info.addressModeW = vk::SamplerAddressMode::eClampToEdge;
integer_sampler_info.borderColor = vk::BorderColor::eIntOpaqueWhite;
vk::Sampler integer_sampler;
RequireVk(test.name, "integer sampler", vulkan.Device().createSampler(
&integer_sampler_info, nullptr, &integer_sampler), "vkCreateSampler");
integer_sampler_info.addressModeU = integer_sampler_info.addressModeV =
integer_sampler_info.addressModeW = vk::SamplerAddressMode::eClampToBorder;
vk::Sampler integer_border_sampler;
RequireVk(test.name, "integer border sampler", vulkan.Device().createSampler(
&integer_sampler_info, nullptr, &integer_border_sampler), "vkCreateSampler");
const std::array native_samplers{native_sampler, native_sampler, native_sampler,
integer_sampler, integer_sampler, integer_border_sampler};
enum class Access { Sample, Gather, GatherHomogeneous, MixedSample, MixedGather, MixedFetch, Fetch };
auto *access = &sample; auto *access = &sample;
bool uint_bound = false;
for (const auto mode : {Access::Sample, Access::Gather, for (const auto mode : {Access::Sample, Access::Gather,
Access::GatherHomogeneous, Access::Fetch}) { Access::GatherHomogeneous, Access::MixedSample,
const bool fetch = mode == Access::Fetch; Access::MixedGather, Access::MixedFetch, Access::Fetch}) {
const bool gather = mode == Access::Gather || mode == Access::GatherHomogeneous; const bool mixed = mode == Access::MixedSample || mode == Access::MixedGather || mode == Access::MixedFetch;
const bool fetch = mode == Access::Fetch || mode == Access::MixedFetch;
const bool gather = mode == Access::Gather || mode == Access::GatherHomogeneous || mode == Access::MixedGather;
const bool homogeneous = mode == Access::GatherHomogeneous; const bool homogeneous = mode == Access::GatherHomogeneous;
if (mode == Access::Gather) { test.name = mixed ? (fetch ? "IndirectImageMixedFetch" : gather ? "IndirectImageMixedGather"
test.name = "IndirectImageGatherContiguousRuns"; : "IndirectImageMixedSample")
: fetch ? "IndirectImageFetchContiguousRuns"
: homogeneous ? "IndirectImageGatherHomogeneous"
: gather ? "IndirectImageGatherContiguousRuns" : "IndirectImageContiguousRuns";
if (uint_bound != mixed) {
std::swap(textures[6], uint_texture);
compiled.program.info.images[6].numeric_class = mixed ? Prospero::TextureNumericClass::Uint
: Prospero::TextureNumericClass::Float;
uint_bound = mixed;
}
if (mode == Access::MixedSample) {
const auto position = std::ranges::find_if(*block, [&](const Inst &inst) { return &inst == &sampler; });
const auto sampler_key = block->PrependNewInst(position, ValueOpcode::BitwiseAnd32,
{Value(&key), Value(1u)});
sampler.SetArg(0, Value(&*sampler_key));
compiled.program.info.samplers.resize(6);
for (u32 index = 0; index < 6u; ++index) {
auto &resource = compiled.program.info.samplers[index];
resource.snapshot_index = index % 3u;
resource.force_point_filtering = index >= 3u;
resource.integer_border = index >= 3u;
}
compiled.program.info.samplers[0].indirect_resources = {1u, 2u};
compiled.program.info.samplers[3].indirect_resources = {4u, 5u};
compiled.program.info.sampled_pairs.push_back({6u, 3u, 0x10f0u});
}
const auto opcode = fetch ? ValueOpcode::ImageRead : gather ? ValueOpcode::ImageGatherRaw
: ValueOpcode::ImageSampleRaw;
if (access->GetOpcode() != opcode) {
const auto position = std::ranges::find_if(*block, [&](const Inst &inst) { const auto position = std::ranges::find_if(*block, [&](const Inst &inst) {
return &inst == access; return &inst == access;
}); });
auto &read = *block->PrependNewInst( const auto read_at = fetch ? block->PrependNewInst(position, opcode,
position, ValueOpcode::ImageGatherRaw, {Value(&image), Value(&address), Value(true)}, memory_flag_bits)
{Value(&image), Value(&sampler), Value(&address), Value(0u), Value(0u), : gather ? block->PrependNewInst(position, opcode,
Value(0u), Value(0u), Value(true)}, memory_flag_bits); {Value(&image), Value(&sampler), Value(&address), Value(0u), Value(0u),
Value(0u), Value(0u), Value(true)}, memory_flag_bits)
: block->PrependNewInst(position, opcode,
{Value(&image), Value(&sampler), Value(&address), Value(true)}, memory_flag_bits);
auto &read = *read_at;
access->ReplaceUsesWith(Value(&read), false); access->ReplaceUsesWith(Value(&read), false);
access = &read; access = &read;
}
if (gather) {
compiled.program.info.images[4].cube = false; compiled.program.info.images[4].cube = false;
auto &gather_memory = compiled.program.memory_info[0]; auto &gather_memory = compiled.program.memory_info[0];
gather_memory.dmask = 1; gather_memory.dmask = 1;
gather_memory.image_sample_flags = ShaderRecompiler::Decoder::ImageSampleFlagLevelZero | gather_memory.image_sample_flags = ShaderRecompiler::Decoder::ImageSampleFlagLevelZero |
ShaderRecompiler::Decoder::ImageSampleFlagOffset; (mixed ? 0u : ShaderRecompiler::Decoder::ImageSampleFlagOffset);
// GATHER4_LZ_O places its packed signed offset before the coordinates. // Nonmixed GATHER4_LZ_O places its packed signed offset before the coordinates.
address.SetArg(0, Value(0x3fu)); address.SetArg(0, Value(mixed ? std::bit_cast<u32>(1.5f) : 0x3fu));
address.SetArg(1, Value(std::bit_cast<u32>(0.75f))); address.SetArg(1, Value(std::bit_cast<u32>(mixed ? 1.5f : 0.75f)));
address.SetArg(2, Value(std::bit_cast<u32>(0.25f))); address.SetArg(2, Value(mixed ? 0u : std::bit_cast<u32>(0.25f)));
address.SetArg(3, Value(0u)); address.SetArg(3, Value(0u));
} else if (mode == Access::MixedSample) {
compiled.program.memory_info[0].dmask = 0xf;
compiled.program.memory_info[0].image_sample_flags = ShaderRecompiler::Decoder::ImageSampleFlagLod;
for (u32 component = 0; component < 4u; ++component)
address.SetArg(component, Value(component < 2u ? std::bit_cast<u32>(1.5f) : 0u));
} }
auto &selected_resources = compiled.program.info.images[0].indirect_resources; auto &selected_resources = compiled.program.info.images[0].indirect_resources;
selected_resources = homogeneous ? std::vector<u32>{0u, 2u, 3u, 5u, 6u} selected_resources = homogeneous ? std::vector<u32>{0u, 2u, 3u, 5u, 6u}
: root.indirect_resources; : root.indirect_resources;
if (homogeneous) test.name = "IndirectImageGatherHomogeneous";
compiled.resources.flattened_srt[6] = homogeneous ? 4u : ordinals[5]; compiled.resources.flattened_srt[6] = homogeneous ? 4u : ordinals[5];
compiled.resources.flattened_srt[7] = mixed ? 5u : ordinals[6];
if (fetch) { if (fetch) {
test.name = "IndirectImageFetchContiguousRuns";
const auto position = std::ranges::find_if(*block, [&](const Inst &inst) {
return &inst == access;
});
auto &read = *block->PrependNewInst(
position, ValueOpcode::ImageRead,
{Value(&image), Value(&address), Value(true)}, memory_flag_bits);
access->ReplaceUsesWith(Value(&read), false);
sampler.Invalidate(); sampler.Invalidate();
compiled.program.info.samplers.clear(); compiled.program.info.samplers.clear();
compiled.program.info.sampled_pairs.clear(); compiled.program.info.sampled_pairs.clear();
@@ -36183,7 +36236,7 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) {
const auto image_opcode = fetch ? "OpImageFetch" : gather ? "OpImageGather" const auto image_opcode = fetch ? "OpImageFetch" : gather ? "OpImageGather"
: "OpImageSampleExplicitLod"; : "OpImageSampleExplicitLod";
Require(test.name, "native array image runs", tools.Disassemble(compiled.spirv, &text) && Require(test.name, "native array image runs", tools.Disassemble(compiled.spirv, &text) &&
CountText(text, image_opcode) == (homogeneous ? 1u : 3u) * halves, CountText(text, image_opcode) == (homogeneous ? 1u : mixed ? 4u : 3u) * halves,
"mixed runs expanded into per-image accesses"); "mixed runs expanded into per-image accesses");
if (homogeneous) { if (homogeneous) {
Require(test.name, "homogeneous gather array", text.find("OpSwitch") == std::string::npos, Require(test.name, "homogeneous gather array", text.find("OpSwitch") == std::string::npos,
@@ -36205,25 +36258,31 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) {
Require(test.name, "nonuniform image operands", Require(test.name, "nonuniform image operands",
std::ranges::count_if(image_operands, [&](u32 operand) { std::ranges::count_if(image_operands, [&](u32 operand) {
return std::ranges::find(decorated, operand) != decorated.end(); return std::ranges::find(decorated, operand) != decorated.end();
}) == (homogeneous ? 1u : 2u) * halves, }) == (homogeneous ? 1u : mixed ? (fetch ? 1u : 4u) : 2u) * halves,
"a dynamically selected image operand lacks its nonuniform decoration"); "a dynamically selected image operand lacks its nonuniform decoration");
} }
test.initial.assign(wave_size, 0xdeadbeefu); test.initial.assign(wave_size, 0xdeadbeefu);
test.expected.assign(wave_size, 0u); test.expected.assign(wave_size, 0u);
for (u32 lane = 0; lane < ordinals.size(); ++lane) { for (u32 lane = 0; lane < ordinals.size(); ++lane) {
const auto resource = selected_resources[compiled.resources.flattened_srt[1u + lane]]; const auto resource = selected_resources[compiled.resources.flattened_srt[1u + lane]];
test.expected[lane] = std::bit_cast<u32>(float(resource + (fetch && resource != 0u ? 10u : 0u))); test.expected[lane] = mixed && resource == 6u
? (fetch ? uint_texel + 10u : (lane & 1u) ? 1u : uint_texel)
: std::bit_cast<u32>(float(resource + (fetch && resource != 0u ? 10u : 0u)));
} }
auto buffer = vulkan.CreateStorageBuffer(test.name, test.initial, test.initial.size()); auto buffer = vulkan.CreateStorageBuffer(test.name, test.initial, test.initial.size());
vulkan.Dispatch(test, compiled, buffer, nullptr, nullptr, nullptr, nullptr, vulkan.Dispatch(test, compiled, buffer, nullptr, nullptr, nullptr, nullptr,
fetch ? vk::Sampler{} : native_sampler, textures); fetch ? vk::Sampler{} : native_sampler, textures,
mixed && !fetch ? std::span<const vk::Sampler>(native_samplers) : std::span<const vk::Sampler>{});
const auto actual = vulkan.ReadBuffer(test.name, buffer, test.expected.size()); const auto actual = vulkan.ReadBuffer(test.name, buffer, test.expected.size());
vulkan.DestroyBuffer(&buffer); vulkan.DestroyBuffer(&buffer);
CompareWords(test, "root, native slots, array runs and mapping bounds", test.expected, actual); CompareWords(test, "root, native slots, array runs and mapping bounds", test.expected, actual);
} }
std::printf("[compute] %-32s ok\n", test.name); std::printf("[compute] %-32s ok\n", test.name);
} }
vulkan.Device().destroySampler(integer_sampler);
vulkan.Device().destroySampler(integer_border_sampler);
vulkan.Device().destroySampler(native_sampler); vulkan.Device().destroySampler(native_sampler);
vulkan.DestroyImage(&uint_texture);
for (auto &texture : textures) vulkan.DestroyImage(&texture); for (auto &texture : textures) vulkan.DestroyImage(&texture);
} }
@@ -40533,18 +40592,16 @@ void CheckImageSamplerSpecialization() {
ShaderRecompiler::IR::ApplyResourceSpecialization( ShaderRecompiler::IR::ApplyResourceSpecialization(
mixed_sampler_program, mixed_sampler_specialization); mixed_sampler_program, mixed_sampler_specialization);
Require("ImageSamplerSpecialization", "mixed sampler variant", Require("ImageSamplerSpecialization", "mixed sampler variant",
mixed_sampler_program.info.samplers.size() == 3u && mixed_sampler_program.info.samplers.size() == 2u &&
!mixed_sampler_program.info.samplers[0].force_point_filtering && !mixed_sampler_program.info.samplers[0].force_point_filtering &&
!mixed_sampler_program.info.samplers[0].integer_border && !mixed_sampler_program.info.samplers[0].integer_border &&
!mixed_sampler_program.info.samplers[1].force_point_filtering && mixed_sampler_program.info.samplers[1].force_point_filtering &&
mixed_sampler_program.info.samplers[1].integer_border && mixed_sampler_program.info.samplers[1].integer_border &&
mixed_sampler_program.info.samplers[2].force_point_filtering &&
mixed_sampler_program.info.samplers[2].integer_border &&
mixed_sampler_program.info.sampled_pairs[0].sampler == 0u && mixed_sampler_program.info.sampled_pairs[0].sampler == 0u &&
mixed_sampler_program.info.sampled_pairs[1].sampler == 2u && mixed_sampler_program.info.sampled_pairs[1].sampler == 1u &&
mixed_sampler_program.info.sampled_pairs[2].sampler == 2u && mixed_sampler_program.info.sampled_pairs[2].sampler == 1u &&
mixed_sampler_program.info.sampled_pairs[3].sampler == 1u && mixed_sampler_program.info.sampled_pairs[3].sampler == 1u &&
mixed_sampler_program.memory_info[0].sampler == 2u && mixed_sampler_program.memory_info[0].sampler == 1u &&
mixed_sampler_snapshot.samplers.size() == 1u && mixed_sampler_snapshot.samplers.size() == 1u &&
std::ranges::all_of(mixed_sampler_program.info.samplers, std::ranges::all_of(mixed_sampler_program.info.samplers,
[](const auto& sampler) { [](const auto& sampler) {
+11 -2
View File
@@ -14257,6 +14257,7 @@ void TestBoundedScalarMaterialImageKeys() {
uint64_t unavailable = 0; uint64_t unavailable = 0;
bool clean = true; bool clean = true;
bool mixed = false; bool mixed = false;
bool mixed_dimension = false;
bool same_key = false; bool same_key = false;
uint32_t keys = 0; uint32_t keys = 0;
uint32_t images = 0; uint32_t images = 0;
@@ -14288,7 +14289,8 @@ void TestBoundedScalarMaterialImageKeys() {
words[1] = static_cast<uint32_t>(integer && !(m.mixed && key == 5573u) words[1] = static_cast<uint32_t>(integer && !(m.mixed && key == 5573u)
? Prospero::BufferFormat::k32UInt : Prospero::BufferFormat::k32Float) << 20u; ? Prospero::BufferFormat::k32UInt : Prospero::BufferFormat::k32Float) << 20u;
words[3] = DstSel(4, 5, 6, 7) | words[3] = DstSel(4, 5, 6, 7) |
(static_cast<uint32_t>(Prospero::ImageType::kColor3D) << 28u); (static_cast<uint32_t>(m.mixed_dimension && key == 5573u
? Prospero::ImageType::kColor2D : Prospero::ImageType::kColor3D) << 28u);
return true; return true;
} }
return false; return false;
@@ -14335,8 +14337,15 @@ void TestBoundedScalarMaterialImageKeys() {
Check(!MaterializeResources(plan, runtime, snapshot, specialization) && memory.keys == 0, Check(!MaterializeResources(plan, runtime, snapshot, specialization) && memory.keys == 0,
"scalar material byte range escaped V# bounds"); "scalar material byte range escaped V# bounds");
memory = {.mixed = true}; memory = {.mixed = true};
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
snapshot.images.size() == 4u &&
std::ranges::count_if(specialization.images, [](const auto& image) {
return image.numeric_class == Prospero::TextureNumericClass::Uint;
}) == 1,
"selected material fields lost their mixed float/uint image classes");
memory = {.mixed_dimension = true};
Check(!MaterializeResources(plan, runtime, snapshot, specialization), Check(!MaterializeResources(plan, runtime, snapshot, specialization),
"selected material fields bypassed image compatibility"); "selected material fields bypassed image coordinate compatibility");
memory = {}; memory = {};
Check(MaterializeResources(plan, runtime, snapshot, specialization), Check(MaterializeResources(plan, runtime, snapshot, specialization),
"native scalar material image selection did not recover after rejected candidates"); "native scalar material image selection did not recover after rejected candidates");