diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp index 284c10d1..d2a19cbd 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp @@ -269,20 +269,13 @@ uint32_t ResultVector(ValueEmitContext& ctx, uint32_t value, result, packed[0], packed[1], packed[2], packed[3]); return result; } - uint32_t component[4] {}; - for (uint32_t index = 0; index < 4u; index++) { - if (dref) { - 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); - } + if (!dref) + return value_class == Prospero::TextureNumericClass::Uint + ? value : Unary(ctx.state, spv::OpBitcast, TypeU32Vector(ctx.state, 4), value); 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, - component[0], component[1], component[2], component[3]); + FloatBits(ctx, value), zero, zero, zero); return result; } @@ -610,6 +603,45 @@ uint32_t StoreTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t d 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(Prospero::TextureNumericClass::Sint) + 1u> result {}; + const auto add = [&](uint32_t image, uint32_t sampler) { + auto& entry = result[static_cast(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(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(images[candidate].numeric_class)].id == 0u) + ctx.Fail(inst, "has no sampler variant for an indirect image candidate"); + } + return result; +} + template uint32_t EmitImageAccess(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t result_type, const Emit& emit) { @@ -735,15 +767,14 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) { return; } if (op == IR::ValueOpcode::ImageRead) { - const auto numeric_class = image.numeric_class; const auto condition = ctx.Arg(inst, 2); ctx.Define( inst, EmitValueOrDefaultIfCondition( state, condition, TypeU32Vector(state, 4), ConstantU32CompositeZero(state, 4), [&]() { - const auto color = EmitImageAccess( - ctx, inst, ImageVectorType(state, numeric_class, 4), + return EmitImageAccess( + ctx, inst, TypeU32Vector(state, 4), [&](uint32_t resource, uint32_t array_index) { const auto& candidate = state.program.info.images[resource]; const auto dimension = candidate.dimension; @@ -762,10 +793,9 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) { descriptor, coord, spv::ImageOperandsLodMask, 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; } @@ -820,9 +850,6 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) { ctx.Fail(inst, "has an unsupported 1D-array gather"); 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; if (dref) { component_or_dref = layout.dref != NoImageComponent @@ -831,9 +858,13 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) { } else { 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& 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(candidate.numeric_class)]; const auto dimension = candidate.dimension; const auto coord = CoordF32( 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); } 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 std::array operands {operand_mask, offset}; state.builder.AddFunction( @@ -856,15 +888,15 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) { std::span(operands).first(operand_mask != 0u ? 2u : 0u)); 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( state, GatherMip(ctx, inst, mem, *address, layout, candidate.mip_count), candidate.mip_count, result_type, EmitMip) : 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, ResultVector(ctx, UnpackImageGather(ctx, mem, sample), - result_numeric_class, false, mem, true)); + ctx.Define(inst, EmitImageAccess(ctx, inst, TypeU32Vector(state, 4), EmitGather)); return; } const bool explicit_lod = HasFlag(mem, Decoder::ImageSampleFlagDerivative) || @@ -877,10 +909,8 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) { } else if (dref) { opcode = spv::OpImageSampleDrefImplicitLod; } - uint32_t result_type = ImageVectorType(state, numeric_class, 4); uint32_t dref_value = 0; if (dref) { - result_type = TypeF32(state); dref_value = ZeroF32(state); if (layout.dref != NoImageComponent) { 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); 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(samplers.size()), - TypeU32(state), [&](uint32_t ordinal) { return ConstantU32(state, samplers[ordinal]); }); - } if (layout.offset != NoImageComponent && !offset_value.IsImmediate()) { 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& 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(candidate.numeric_class)]; auto sample_operands = operands; auto sample_operand_count = operand_count; if (layout.offset != NoImageComponent) { @@ -934,19 +957,16 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) { CoordF32(ctx, mem, *address, layout.coord, ImageDimensionInfoFor(candidate.dimension).coordinate_components, candidate.cube); - const auto sampled = MakeSampledImage(state, resource, sampler_id, 0u, array_index, - sampler_index != 0u); + const auto sampled = MakeSampledImage(state, resource, sampler.id, 0u, array_index, + sampler.dynamic); const auto sample = state.builder.AllocateId(); state.builder.AddFunction(opcode, result_type, sample, sampled, coord, std::span(&dref_value, dref ? 1u : 0u), std::span(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); - if (!dref) { - result = UnpackImageTexel(ctx, mem, result); - } - ctx.Define(inst, ResultVector(ctx, result, numeric_class, dref, mem)); + ctx.Define(inst, EmitImageAccess(ctx, inst, TypeU32Vector(state, 4), EmitSample)); return; } if (image_info.access == IR::ImageAccess::Atomic) { diff --git a/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp b/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp index 92baf279..3534d34b 100644 --- a/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp @@ -106,16 +106,12 @@ Prospero::BufferFormat ImageConversionFormat(Prospero::BufferFormat format) { : Prospero::BufferFormat::kInvalid; } -enum class SamplerClass : uint8_t { Float, Integer, PointInteger }; +enum class SamplerClass : uint8_t { Float, Integer }; SamplerClass ClassifySampler(const ImageResource& image) { - if (image.numeric_class == Prospero::TextureNumericClass::Sint || - (image.numeric_class == Prospero::TextureNumericClass::Uint && - image.conversion_format != Prospero::BufferFormat::kInvalid)) { - return SamplerClass::PointInteger; - } - return image.numeric_class == Prospero::TextureNumericClass::Uint ? SamplerClass::Integer - : SamplerClass::Float; + return image.numeric_class == Prospero::TextureNumericClass::Uint || + image.numeric_class == Prospero::TextureNumericClass::Sint + ? SamplerClass::Integer : SamplerClass::Float; } bool DescriptorIsCube(const DescriptorValue& descriptor) { @@ -469,7 +465,7 @@ struct SamplerPlan { uint32_t source; SamplerClass type; }; - std::array, ShaderInfo::MaxSamplers> mapping; + std::array, ShaderInfo::MaxSamplers> mapping; std::array bindings; 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 && image.cube == image_class.cube; - if (image.numeric_class != image_class.numeric_class || - (!same_coordinates && !(is_2d(image.dimension) && is_2d(image_class.dimension))) || + if ((!same_coordinates && !(is_2d(image.dimension) && is_2d(image_class.dimension))) || image.mip_count != image_class.mip_count || image.conversion_format != image_class.conversion_format || image.shader_swizzle != image_class.shader_swizzle) { @@ -1334,9 +1329,20 @@ void ApplyResourceSpecialization(Program& program, const ResourceSpecialization& } SamplerPlan sampler_plan; - EXIT_IF(!BuildSamplerPlan(program.info, sampler_plan)); auto& samplers = program.info.samplers; 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(images[pair.image].numeric_class); + for (const auto candidate: images[pair.image].indirect_resources) { + const auto bit = 1u << static_cast(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(); samplers.reserve(sampler_plan.sampler_count); 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[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; } for (uint32_t index = 0; index < sampler_plan.sampler_count; index++) { diff --git a/tests/ResourceTrackingTests.cpp b/tests/ResourceTrackingTests.cpp index 3062026c..93dc8222 100644 --- a/tests/ResourceTrackingTests.cpp +++ b/tests/ResourceTrackingTests.cpp @@ -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 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( + 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() { namespace CFG = Libs::Graphics::ShaderRecompiler::CFG; enum class Variant { @@ -4086,6 +4132,7 @@ int main() { Run("draw-uniform scalar image", TestUniformScalarBufferImage); Run("scalar buffer selector domain", TestScalarBufferSelectorDomain); Run("descending image range", TestDescendingImageRange); + Run("mixed indirect image samplers", TestMixedIndirectImageSamplers); Run("SRT runtime", TestSrtFlatteningAndRuntimeMemoization); Run("dynamic SRT", TestDynamicSrtReadRemainsExplicit); Run("writable descriptor phi", TestWritableDescriptorPhi); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 4adf61a3..62e3887c 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -34419,7 +34419,7 @@ TestCase ImageLoadR32SintUsesSignedSampledImage() { test.user_data = MakeSampledTextureData(Prospero::BufferFormat::k32SInt); test.has_user_data = true; test.required_spirv = {"image_17", "OpTypeImage %int", "OpImageFetch %v4int", - "OpBitcast %uint"}; + "OpBitcast %v4uint"}; return test; } @@ -36113,49 +36113,102 @@ 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 }; + constexpr u32 uint_texel = 0x80000106u; + auto uint_texture = vulkan.CreateImageMips( + test.name, 2, 2, vk::Format::eR32G32B32A32Uint, vk::ImageUsageFlagBits::eSampled, + {std::vector(16u, uint_texel), std::vector(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; + bool uint_bound = false; for (const auto mode : {Access::Sample, Access::Gather, - Access::GatherHomogeneous, Access::Fetch}) { - const bool fetch = mode == Access::Fetch; - const bool gather = mode == Access::Gather || mode == Access::GatherHomogeneous; + Access::GatherHomogeneous, Access::MixedSample, + Access::MixedGather, Access::MixedFetch, Access::Fetch}) { + 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; - if (mode == Access::Gather) { - test.name = "IndirectImageGatherContiguousRuns"; + test.name = mixed ? (fetch ? "IndirectImageMixedFetch" : gather ? "IndirectImageMixedGather" + : "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) { return &inst == access; }); - auto &read = *block->PrependNewInst( - position, ValueOpcode::ImageGatherRaw, - {Value(&image), Value(&sampler), Value(&address), Value(0u), Value(0u), - Value(0u), Value(0u), Value(true)}, memory_flag_bits); + const auto read_at = fetch ? block->PrependNewInst(position, opcode, + {Value(&image), Value(&address), Value(true)}, memory_flag_bits) + : gather ? block->PrependNewInst(position, opcode, + {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 = &read; + } + if (gather) { compiled.program.info.images[4].cube = false; auto &gather_memory = compiled.program.memory_info[0]; gather_memory.dmask = 1; gather_memory.image_sample_flags = ShaderRecompiler::Decoder::ImageSampleFlagLevelZero | - ShaderRecompiler::Decoder::ImageSampleFlagOffset; - // GATHER4_LZ_O places its packed signed offset before the coordinates. - address.SetArg(0, Value(0x3fu)); - address.SetArg(1, Value(std::bit_cast(0.75f))); - address.SetArg(2, Value(std::bit_cast(0.25f))); + (mixed ? 0u : ShaderRecompiler::Decoder::ImageSampleFlagOffset); + // Nonmixed GATHER4_LZ_O places its packed signed offset before the coordinates. + address.SetArg(0, Value(mixed ? std::bit_cast(1.5f) : 0x3fu)); + address.SetArg(1, Value(std::bit_cast(mixed ? 1.5f : 0.75f))); + address.SetArg(2, Value(mixed ? 0u : std::bit_cast(0.25f))); 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(1.5f) : 0u)); } auto &selected_resources = compiled.program.info.images[0].indirect_resources; selected_resources = homogeneous ? std::vector{0u, 2u, 3u, 5u, 6u} : root.indirect_resources; - if (homogeneous) test.name = "IndirectImageGatherHomogeneous"; compiled.resources.flattened_srt[6] = homogeneous ? 4u : ordinals[5]; + compiled.resources.flattened_srt[7] = mixed ? 5u : ordinals[6]; 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(); compiled.program.info.samplers.clear(); compiled.program.info.sampled_pairs.clear(); @@ -36183,7 +36236,7 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) { const auto image_opcode = fetch ? "OpImageFetch" : gather ? "OpImageGather" : "OpImageSampleExplicitLod"; 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"); if (homogeneous) { 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", std::ranges::count_if(image_operands, [&](u32 operand) { 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"); } test.initial.assign(wave_size, 0xdeadbeefu); test.expected.assign(wave_size, 0u); for (u32 lane = 0; lane < ordinals.size(); ++lane) { const auto resource = selected_resources[compiled.resources.flattened_srt[1u + lane]]; - test.expected[lane] = std::bit_cast(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(float(resource + (fetch && resource != 0u ? 10u : 0u))); } auto buffer = vulkan.CreateStorageBuffer(test.name, test.initial, test.initial.size()); 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(native_samplers) : std::span{}); const auto actual = vulkan.ReadBuffer(test.name, buffer, test.expected.size()); vulkan.DestroyBuffer(&buffer); CompareWords(test, "root, native slots, array runs and mapping bounds", test.expected, actual); } 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.DestroyImage(&uint_texture); for (auto &texture : textures) vulkan.DestroyImage(&texture); } @@ -40533,18 +40592,16 @@ void CheckImageSamplerSpecialization() { ShaderRecompiler::IR::ApplyResourceSpecialization( mixed_sampler_program, mixed_sampler_specialization); 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].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[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[1].sampler == 2u && - mixed_sampler_program.info.sampled_pairs[2].sampler == 2u && + mixed_sampler_program.info.sampled_pairs[1].sampler == 1u && + mixed_sampler_program.info.sampled_pairs[2].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 && std::ranges::all_of(mixed_sampler_program.info.samplers, [](const auto& sampler) { diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 04e42dd2..04d21f38 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -14257,6 +14257,7 @@ void TestBoundedScalarMaterialImageKeys() { uint64_t unavailable = 0; bool clean = true; bool mixed = false; + bool mixed_dimension = false; bool same_key = false; uint32_t keys = 0; uint32_t images = 0; @@ -14288,7 +14289,8 @@ void TestBoundedScalarMaterialImageKeys() { words[1] = static_cast(integer && !(m.mixed && key == 5573u) ? Prospero::BufferFormat::k32UInt : Prospero::BufferFormat::k32Float) << 20u; words[3] = DstSel(4, 5, 6, 7) | - (static_cast(Prospero::ImageType::kColor3D) << 28u); + (static_cast(m.mixed_dimension && key == 5573u + ? Prospero::ImageType::kColor2D : Prospero::ImageType::kColor3D) << 28u); return true; } return false; @@ -14335,8 +14337,15 @@ void TestBoundedScalarMaterialImageKeys() { Check(!MaterializeResources(plan, runtime, snapshot, specialization) && memory.keys == 0, "scalar material byte range escaped V# bounds"); 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), - "selected material fields bypassed image compatibility"); + "selected material fields bypassed image coordinate compatibility"); memory = {}; Check(MaterializeResources(plan, runtime, snapshot, specialization), "native scalar material image selection did not recover after rejected candidates");