diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp index 419c6600..284c10d1 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp @@ -323,25 +323,25 @@ uint32_t QueryDimensions(ValueEmitContext& ctx, const IR::MemoryInfo& mem, return vector; } -uint32_t PackedOffset(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::Inst& address, - const ImageSampleLayout& layout, ImageDimension dimension) { +uint32_t PackedOffset(ValueEmitContext& ctx, IR::Value value, ImageDimension dimension, + uint32_t& mask) { const auto components = ImageDimensionInfoFor(dimension).spatial_components; - const auto zero = ConstantI32(ctx.state, 0); - if (layout.offset == NoImageComponent || mem.image_address_components <= layout.offset) { - if (components == 1u) return zero; - const auto result = ctx.state.builder.AllocateId(); - if (components == 3u) { - ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeI32Vector(ctx.state, 3), - result, zero, zero, zero); - } else { - ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeI32Vector(ctx.state, 2), - result, zero, zero); + uint32_t values[3] {}; + if (value.IsImmediate()) { + // RDNA2 packs signed six-bit texel offsets into the low bits of each byte. + for (uint32_t index = 0; index < components; index++) { + const auto bits = (value.U32() >> (index * 8u)) & 0x3fu; + values[index] = ConstantI32(ctx.state, static_cast(bits ^ 0x20u) - 0x20); } - return result; + mask |= spv::ImageOperandsConstOffsetMask; + return components == 1u + ? values[0] + : ctx.state.builder.Constant(spv::OpConstantComposite, + TypeI32Vector(ctx.state, components), + std::span(values).first(components)); } - const auto packed = Unary(ctx.state, spv::OpBitcast, TypeI32(ctx.state), - AddressU32(ctx, mem, address, layout.offset)); - uint32_t values[3] = {zero, zero, zero}; + mask |= spv::ImageOperandsOffsetMask; + const auto packed = Unary(ctx.state, spv::OpBitcast, TypeI32(ctx.state), ctx.Def(value)); for (uint32_t index = 0; index < components; index++) { values[index] = ctx.state.builder.AllocateId(); ctx.state.builder.AddFunction(spv::OpBitFieldSExtract, TypeI32(ctx.state), values[index], @@ -350,13 +350,8 @@ uint32_t PackedOffset(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR } if (components == 1u) return values[0]; const auto result = ctx.state.builder.AllocateId(); - if (components == 3u) { - ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeI32Vector(ctx.state, 3), - result, values[0], values[1], values[2]); - } else { - ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeI32Vector(ctx.state, 2), - result, values[0], values[1]); - } + ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeI32Vector(ctx.state, components), + result, std::span(values).first(components)); return result; } @@ -794,6 +789,12 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) { const auto layout = Layout(mem); const auto numeric_class = image.numeric_class; const bool dref = HasFlag(mem, Decoder::ImageSampleFlagCompare); + IR::Value offset_value; + if (layout.offset != NoImageComponent) { + const auto value = address->Arg(layout.offset).Resolve(); + offset_value = IR::ResolveActiveU32(value, inst.Arg(inst.NumArgs() - 1u).Resolve()); + if (offset_value.IsEmpty()) offset_value = value; + } if (dref && state.program.info.images[mem.resource].conversion_format != Prospero::BufferFormat::kInvalid) { ctx.Fail(inst, "uses depth comparison with a converted image"); @@ -843,8 +844,7 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) { operand_mask = spv::ImageOperandsConstOffsetsMask; offset = HorizontalOffsets(state, dimension); } else if (layout.offset != NoImageComponent) { - operand_mask = spv::ImageOperandsOffsetMask; - offset = PackedOffset(ctx, mem, *address, layout, dimension); + 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); @@ -886,7 +886,7 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) { dref_value = AddressF32(ctx, mem, *address, layout.dref); } } - std::array operands {}; + std::array operands {}; size_t operand_count = 0; if (HasFlag(mem, Decoder::ImageSampleFlagDerivative)) { operands[0] = spv::ImageOperandsGradMask; @@ -916,9 +916,20 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) { 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 EmitSample = [&](uint32_t resource, uint32_t array_index) { const auto& candidate = state.program.info.images[resource]; + auto sample_operands = operands; + auto sample_operand_count = operand_count; + if (layout.offset != NoImageComponent) { + sample_operand_count = std::max(size_t{1}, sample_operand_count); + sample_operands[sample_operand_count++] = PackedOffset( + ctx, offset_value, candidate.dimension, sample_operands[0]); + } const auto coord = CoordF32(ctx, mem, *address, layout.coord, ImageDimensionInfoFor(candidate.dimension).coordinate_components, @@ -928,7 +939,7 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) { const auto sample = state.builder.AllocateId(); state.builder.AddFunction(opcode, result_type, sample, sampled, coord, std::span(&dref_value, dref ? 1u : 0u), - std::span(operands).first(operand_count)); + std::span(sample_operands).first(sample_operand_count)); return sample; }; auto result = EmitImageAccess(ctx, inst, result_type, EmitSample); diff --git a/src/graphics/shader/recompiler/frontend/translate/Memory.cpp b/src/graphics/shader/recompiler/frontend/translate/Memory.cpp index a2f3dda7..32eb3421 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Memory.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Memory.cpp @@ -687,7 +687,8 @@ void Translator::IMAGE_SAMPLE(const Decoder::Instruction& inst) { const auto resource = GetImageResource(memory); const auto sampler = GetSamplerResource(memory); const auto address = MakeImageAddress(inst, MemorySourceAt(inst, 0)); - const auto result = ir.Emit(IR::ValueOpcode::ImageSampleRaw, {resource, sampler, address}, + const auto result = ir.Emit(IR::ValueOpcode::ImageSampleRaw, + {resource, sampler, address, ir.GetExec()}, AddMemoryInfo(memory, inst.pc)); const bool dref = (memory.image_sample_flags & Decoder::ImageSampleFlagCompare) != 0u; if (dref && memory.data_bits != 16u) { @@ -714,7 +715,7 @@ void Translator::IMAGE_GATHER(const Decoder::Instruction& inst) { has_lod ? resource.Instruction()->Arg(1) : IR::Value(0u), has_lod ? resource.Instruction()->Arg(3) : IR::Value(0u), has_lod ? sampler.Instruction()->Arg(1) : IR::Value(0u), - has_lod ? sampler.Instruction()->Arg(2) : IR::Value(0u)}, + has_lod ? sampler.Instruction()->Arg(2) : IR::Value(0u), ir.GetExec()}, AddMemoryInfo(memory, inst.pc)); for (uint32_t index = 0; index < memory.data_dwords; index++) { WriteOperand(OffsetOperand(inst.dst, index), diff --git a/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc b/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc index ed2a0dc0..30b86651 100644 --- a/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc +++ b/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc @@ -317,8 +317,8 @@ VALUE_OPCODE(BvhIntersect, U32x4, U32x4, ImageAddress, U1) VALUE_OPCODE(ImageQueryLod, U32x4, ImageResource, SamplerResource, ImageAddress) VALUE_OPCODE(ImageRead, U32x4, ImageResource, ImageAddress, U1) VALUE_OPCODE(ImageWrite, Void, ImageResource, ImageAddress, U32x4, U1) -VALUE_OPCODE(ImageSampleRaw, U32x4, ImageResource, SamplerResource, ImageAddress) -VALUE_OPCODE(ImageGatherRaw, U32x4, ImageResource, SamplerResource, ImageAddress, U32, U32, U32, U32) +VALUE_OPCODE(ImageSampleRaw, U32x4, ImageResource, SamplerResource, ImageAddress, U1) +VALUE_OPCODE(ImageGatherRaw, U32x4, ImageResource, SamplerResource, ImageAddress, U32, U32, U32, U32, U1) VALUE_OPCODE(ImageAtomicCompareSwap32, U32, ImageResource, ImageAddress, U32, U32, U1) VALUE_OPCODE(ImageAtomicSwap32, U32, ImageResource, ImageAddress, U32, U1) VALUE_OPCODE(ImageAtomicSwap64, U64, ImageResource, ImageAddress, U64, U1) diff --git a/tests/ResourceTrackingTests.cpp b/tests/ResourceTrackingTests.cpp index cd13266a..bb66e88f 100644 --- a/tests/ResourceTrackingTests.cpp +++ b/tests/ResourceTrackingTests.cpp @@ -270,7 +270,7 @@ MakeIndirectImageFixture(bool malformed, uint32_t table_stride = 32, uint32_t ta sample.kind = ResourceKind::Image; sample.image_dimension = Decoder::ImageDimension::Dim2D; const auto sampled = fixture->Emit(ValueOpcode::ImageSampleRaw, - {image, sampler, fixture->ImageAddress()}, + {image, sampler, fixture->ImageAddress(), Value(true)}, fixture->AddMemory(sample, 0x10f0)); const auto sampled_x = fixture->Emit(ValueOpcode::CompositeExtractU32x4, {sampled, Value(0u)}); @@ -486,7 +486,8 @@ void TestWaterfallImageTable() { memory.kind = ResourceKind::Image; memory.image_dimension = Decoder::ImageDimension::Dim2D; const auto value = fixture->Emit(ValueOpcode::ImageSampleRaw, - {image, sampler, fixture->ImageAddress()}, fixture->AddMemory(memory, 0x92cu), block); + {image, sampler, fixture->ImageAddress(), block == entry ? active : guard}, + fixture->AddMemory(memory, 0x92cu), block); fixture->Emit(ValueOpcode::ReferenceU32, {fixture->Emit(ValueOpcode::CompositeExtractU32x4, {value, Value(0u)}, 0, block)}, 0, block); }; @@ -641,7 +642,7 @@ void TestGuardedDirectImageTable() { MemoryInfo memory; memory.kind = ResourceKind::Image; memory.image_dimension = Decoder::ImageDimension::Dim2D; - fixture.Emit(ValueOpcode::ImageSampleRaw, {image, sampler, fixture.ImageAddress()}, + fixture.Emit(ValueOpcode::ImageSampleRaw, {image, sampler, fixture.ImageAddress(), Value(true)}, fixture.AddMemory(memory, 0x128)); fixture.PlanAndTrack(); const auto source = fixture.program.info.images[0].source; @@ -989,7 +990,7 @@ void TestBoundedComputeImageLoop() { sample.kind = ResourceKind::Image; sample.image_dimension = Decoder::ImageDimension::Dim2D; fixture.Emit(ValueOpcode::ImageSampleRaw, - {image, sampler, fixture.ImageAddress()}, + {image, sampler, fixture.ImageAddress(), Value(true)}, fixture.AddMemory(sample, 0x29c)); fixture.PlanAndTrack(); const auto source = fixture.program.info.images[0].source; @@ -1325,7 +1326,9 @@ void TestUniformizedMaterialImageKeys() { MemoryInfo image_memory; image_memory.kind = ResourceKind::Image; image_memory.image_dimension = Decoder::ImageDimension::Dim2D; - const auto sampled = fixture.Emit(ValueOpcode::ImageSampleRaw, {image, sampler, address}, + const auto sampled = fixture.Emit(ValueOpcode::ImageSampleRaw, + {image, sampler, address, + variant == Variant::EmptyFirstEntry ? material_guard : sample_guard}, fixture.AddMemory(image_memory, 0x1aecu), sample); if (first_lane) { const auto component = fixture.Emit( @@ -1477,7 +1480,7 @@ void TestImageDescriptorFields() { memory.kind = ResourceKind::Image; memory.image_dimension = Decoder::ImageDimension::Dim2D; memory.image_r128 = r128; - fixture.Emit(ValueOpcode::ImageSampleRaw, {image, sampler, fixture.ImageAddress()}, + fixture.Emit(ValueOpcode::ImageSampleRaw, {image, sampler, fixture.ImageAddress(), Value(true)}, fixture.AddMemory(memory, 0x80)); fixture.PlanAndTrack(); auto plan = ExtractResourcePlan(fixture.program); @@ -1571,7 +1574,7 @@ void TestUniformScalarBufferImage() { sample.kind = ResourceKind::Image; sample.image_dimension = Decoder::ImageDimension::Dim2D; fixture.Emit(ValueOpcode::ImageSampleRaw, - {image, sampler, fixture.ImageAddress()}, fixture.AddMemory(sample, 0x228)); + {image, sampler, fixture.ImageAddress(), Value(true)}, fixture.AddMemory(sample, 0x228)); fixture.PlanAndTrack(); const auto plan = ExtractResourcePlan(fixture.program); @@ -1866,7 +1869,7 @@ void TestImagesSamplersAndAliases() { memory.kind = ResourceKind::Image; memory.image_dimension = Decoder::ImageDimension::Dim2D; memory.image_sample_flags = sample_flags; - fixture.Emit(ValueOpcode::ImageSampleRaw, {image, sampler, image_address}, + fixture.Emit(ValueOpcode::ImageSampleRaw, {image, sampler, image_address, Value(true)}, fixture.AddMemory(memory, pc)); return std::pair{image, sampler}; }; @@ -1938,14 +1941,14 @@ void TestSampleAdjustSamplerScratch() { memory.image_dimension = Decoder::ImageDimension::Dim2D; memory.image_sample_flags = Decoder::ImageSampleFlagAdjust; fixture.Emit(ValueOpcode::ImageSampleRaw, - {image, sampler, fixture.ImageAddress()}, + {image, sampler, fixture.ImageAddress(), Value(true)}, fixture.AddMemory(memory, 0x1ec)); // PPSA24156's zero border payload leaves the reserved scratch shift as // the entire DWORD after the frontend folds its OR with zero. const auto shift_sampler = fixture.Sampler( {Value(0x36u), Value(0xfff000u), Value(0x02500000u), scratch}, 0x200); fixture.Emit(ValueOpcode::ImageSampleRaw, - {image, shift_sampler, fixture.ImageAddress()}, + {image, shift_sampler, fixture.ImageAddress(), Value(true)}, fixture.AddMemory(memory, 0x200)); fixture.PlanAndTrack(); auto plan = ExtractResourcePlan(fixture.program); @@ -1994,7 +1997,7 @@ void TestSampleAdjustSamplerScratch() { rejected_memory.image_dimension = Decoder::ImageDimension::Dim2D; rejected_memory.image_sample_flags = flags; rejected.Emit(ValueOpcode::ImageSampleRaw, - {rejected_image, rejected_sampler, rejected.ImageAddress()}, + {rejected_image, rejected_sampler, rejected.ImageAddress(), Value(true)}, rejected.AddMemory(rejected_memory, 0x200)); CheckFatal([&] { rejected.PlanAndTrack(); }, @@ -2112,7 +2115,7 @@ void TestGatherLodSamplerValidation() { fixture.Emit(ValueOpcode::ImageGatherRaw, {fixture.Image(image_words), fixture.Sampler(sampler_words), fixture.ImageAddress(), image_words[1], image_words[3], - sampler_words[1], sampler_words[2]}, + sampler_words[1], sampler_words[2], Value(true)}, fixture.AddMemory(memory, i * 4)); } fixture.PlanAndTrack(); @@ -2514,7 +2517,7 @@ ResourcePlan ConditionalSamplerPlan(bool diamond, bool reverse, bool reverse_phi sample.kind = ResourceKind::Image; sample.image_dimension = Decoder::ImageDimension::Dim2D; const auto result = - fixture.Emit(ValueOpcode::ImageSampleRaw, {image, sampler, address}, + fixture.Emit(ValueOpcode::ImageSampleRaw, {image, sampler, address, Value(true)}, fixture.AddMemory(sample, 0x4d4)); fixture.Emit(ValueOpcode::ReferenceU32, {fixture.Emit(ValueOpcode::CompositeExtractU32x4, @@ -2619,7 +2622,7 @@ void TestFiniteImagePhiCycle() { MemoryInfo memory; memory.kind = ResourceKind::Image; memory.image_dimension = Decoder::ImageDimension::Dim2D; - fixture.Emit(ValueOpcode::ImageSampleRaw, {image, sampler, fixture.ImageAddress()}, + fixture.Emit(ValueOpcode::ImageSampleRaw, {image, sampler, fixture.ImageAddress(), Value(true)}, fixture.AddMemory(memory, 4)); fixture.PlanAndTrack(); auto plan = ExtractResourcePlan(fixture.program); @@ -2685,12 +2688,12 @@ void TestFiniteInlineSampler() { memory.kind = ResourceKind::Image; memory.image_dimension = Decoder::ImageDimension::Dim2D; if (format == F::k32Float) - fixture.Emit(ValueOpcode::ImageSampleRaw, {image, previous, fixture.ImageAddress()}, + fixture.Emit(ValueOpcode::ImageSampleRaw, {image, previous, fixture.ImageAddress(), Value(true)}, fixture.AddMemory(memory, 0x440u)); - fixture.Emit(ValueOpcode::ImageSampleRaw, {image, sampler, fixture.ImageAddress()}, + fixture.Emit(ValueOpcode::ImageSampleRaw, {image, sampler, fixture.ImageAddress(), Value(true)}, fixture.AddMemory(memory, 0x1dd0u)); if (format == F::k32Float) - fixture.Emit(ValueOpcode::ImageSampleRaw, {image, direct, fixture.ImageAddress()}, + fixture.Emit(ValueOpcode::ImageSampleRaw, {image, direct, fixture.ImageAddress(), Value(true)}, fixture.AddMemory(memory, 0x1dd8u)); } fixture.PlanAndTrack(); @@ -2764,7 +2767,7 @@ void TestFiniteInlineSampler() { memory.kind = ResourceKind::Image; memory.image_dimension = Decoder::ImageDimension::Dim2D; memory.image_sample_flags = flags; - adjusted.Emit(ValueOpcode::ImageSampleRaw, {image, shared, adjusted.ImageAddress()}, + adjusted.Emit(ValueOpcode::ImageSampleRaw, {image, shared, adjusted.ImageAddress(), Value(true)}, adjusted.AddMemory(memory, 0x1dd0u)); } CheckFatal([&] { adjusted.PlanAndTrack(); }, "not a valid runtime value", @@ -2817,7 +2820,7 @@ void TestFiniteImageBitScanSentinel() { MemoryInfo memory; memory.kind = ResourceKind::Image; memory.image_dimension = Decoder::ImageDimension::Dim2D; - fixture.Emit(ValueOpcode::ImageSampleRaw, {image, sampler, fixture.ImageAddress()}, + fixture.Emit(ValueOpcode::ImageSampleRaw, {image, sampler, fixture.ImageAddress(), Value(true)}, fixture.AddMemory(memory, 4)); if (!nonzero) { CheckFatal([&] { fixture.PlanAndTrack(); }, "not a valid runtime value", diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 34c58d40..14653903 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -34960,12 +34960,11 @@ TestCase ImageSampleOpcodeAliasUsesNormalCoords() { return test; } -TestCase ImageSampleA16OffsetKeepsTexelOffset32BitOnGpu() { +template TestCase ImageSampleA16OffsetKeepsTexelOffset32BitOnGpu() { using O = ShaderOpcode; std::vector code; - AppendVMovU32(&code, 20, - 1); // Non-constant +1 X offset is not a SPIR-V ConstOffset. + AppendVMovU32(&code, 20, 1); // +1 X remains a full dword with A16 coordinates. AppendVMovLiteral(&code, 21, 0x36003900u); // x=0.625, y=0.375 packed as f16. AppendVMovU32(&code, 22, 0); code.push_back(EncodeMimg0(0x30, 0xf)); @@ -34976,18 +34975,63 @@ TestCase ImageSampleA16OffsetKeepsTexelOffset32BitOnGpu() { AppendEnd(&code); auto image = MakeRgbaImage(4, 4); - SetRgbaPixel(&image, 4, 2, 1, 0x3f800000u, 0x40000000u, 0x40400000u, + SetRgbaPixel(&image, 4, 2, 1, 0x3f000000u, 0, 0, 0); + SetRgbaPixel(&image, 4, 3, 1, 0x3f800000u, 0x40000000u, 0x40400000u, 0x40800000u); TestCase test; - test.name = "ImageSampleA16OffsetKeepsTexelOffset32BitOnGpu"; + test.name = wave_size == 32 ? "ImageSampleA16OffsetKeepsTexelOffset32BitOnGpu32" + : "ImageSampleA16OffsetKeepsTexelOffset32BitOnGpu64"; test.code = code; + test.compute_info.wave_size = wave_size; + test.has_compute_info = true; test.expected = {0x3f800000u, 0x40000000u, 0x40400000u, 0x40800000u}; test.opcodes = {O::V_MOV_B32, O::IMAGE_SAMPLE, O::BUFFER_STORE_DWORD, O::S_ENDPGM}; test.sampled_image_rgba = image; - test.required_spirv = {"UnpackHalf2x16"}; - test.forbidden_spirv = {"OpBitFieldSExtract"}; + test.required_spirv = {"UnpackHalf2x16", "Lod|ConstOffset"}; + test.forbidden_spirv = {"OpBitFieldSExtract", "OpImageQuerySizeLod", "OpFDiv"}; + return test; +} + +template TestCase ImageSampleOffsetsReadFourNeighbors() { + using O = ShaderOpcode; + TestCase test; + test.name = wave_size == 32 ? "ImageSampleOffsetsReadFourNeighbors32" + : "ImageSampleOffsetsReadFourNeighbors64"; + test.compute_info.wave_size = wave_size; + test.has_compute_info = true; + constexpr std::array offsets{0x01u, 0x3fu, 0x3f00u, 0x0100u}; + constexpr std::array opcodes{0x37u, 0x37u, 0x34u, 0x32u}; + for (u32 sample = 0; sample < offsets.size(); ++sample) { + AppendVMovLiteral(&test.code, 20, offsets[sample]); + u32 coordinate = 21; + if (opcodes[sample] == 0x32u) { + for (float gradient : {0.25f, 0.f, 0.f, 0.25f}) { + AppendVMovLiteral(&test.code, coordinate++, std::bit_cast(gradient)); + } + } + // All instructions address the center of texel (2, 1) before applying the offset. + AppendVMovLiteral(&test.code, coordinate, std::bit_cast(0.625f)); + AppendVMovLiteral(&test.code, coordinate + 1, std::bit_cast(0.375f)); + if (opcodes[sample] == 0x34u) AppendVMovU32(&test.code, coordinate + 2, 0); + test.code.push_back(EncodeMimg0(opcodes[sample], 0x1)); + test.code.push_back(EncodeMimg1(sample, 20)); + AppendStoreVgpr(&test.code, sample, sample); + } + AppendEnd(&test.code); + test.sampled_image_rgba = MakeRgbaImage(4, 4); + SetRgbaPixel(&test.sampled_image_rgba, 4, 2, 1, std::bit_cast(16.f), 0, 0, 0); + constexpr std::array, 4> neighbors{{{3, 1}, {1, 1}, {2, 0}, {2, 2}}}; + for (u32 sample = 0; sample < neighbors.size(); ++sample) { + const auto value = std::bit_cast(float(sample + 1)); + SetRgbaPixel(&test.sampled_image_rgba, 4, neighbors[sample][0], neighbors[sample][1], + value, 0, 0, 0); + test.expected.push_back(value); + } + test.opcodes = {O::V_MOV_B32, O::IMAGE_SAMPLE, O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.required_spirv = {"Lod|ConstOffset", "Grad|ConstOffset"}; + test.forbidden_spirv = {"OpImageQuerySizeLod", "OpFDiv", "OpBitFieldSExtract", "OpSwitch"}; return test; } @@ -35058,7 +35102,8 @@ TestCase ImageGatherCompareOpcodes() { O::V_MOV_B32, O::IMAGE_GATHER4_C, O::IMAGE_GATHER4_C_LZ, O::IMAGE_GATHER4_C_O, O::IMAGE_GATHER4_C_LZ_O, O::BUFFER_STORE_DWORD, O::S_ENDPGM}; - test.required_spirv = {"OpImageDrefGather", "OpBitFieldSExtract"}; + test.required_spirv = {"OpImageDrefGather", "ConstOffset"}; + test.forbidden_spirv = {"OpBitFieldSExtract"}; test.compile_only = true; return test; } @@ -35610,7 +35655,7 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) { std::memcpy(&memory_flag_bits, &memory_flags, sizeof(memory_flags)); auto &sample = block->AppendNewInst( ValueOpcode::ImageSampleRaw, - {Value(&image), Value(&sampler), Value(&address)}, memory_flag_bits); + {Value(&image), Value(&sampler), Value(&address), Value(true)}, memory_flag_bits); auto &sample_x = block->AppendNewInst(ValueOpcode::CompositeExtractU32x4, {Value(&sample), Value(0u)}); block->AppendNewInst(ValueOpcode::ReferenceU32, {Value(&sample_x)}); @@ -35836,7 +35881,7 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) { auto &read = *block->PrependNewInst( position, ValueOpcode::ImageGatherRaw, {Value(&image), Value(&sampler), Value(&address), Value(0u), Value(0u), - Value(0u), Value(0u)}, memory_flag_bits); + Value(0u), Value(0u), Value(true)}, memory_flag_bits); access->ReplaceUsesWith(Value(&read), false); access = &read; compiled.program.info.images[4].cube = false; @@ -37772,7 +37817,10 @@ std::vector MakeCases() { AddCase(ImageD16GatherPacksHalfPairs); AddCase(ImageSampleA16SamplerCoordsOnGpu); AddCase(ImageSampleOpcodeAliasUsesNormalCoords); - AddCase(ImageSampleA16OffsetKeepsTexelOffset32BitOnGpu); + AddCase(ImageSampleA16OffsetKeepsTexelOffset32BitOnGpu<32>); + AddCase(ImageSampleA16OffsetKeepsTexelOffset32BitOnGpu<64>); + AddCase(ImageSampleOffsetsReadFourNeighbors<32>); + AddCase(ImageSampleOffsetsReadFourNeighbors<64>); AddCase(ImageSampleA16CompareBiasRdna2AddressOrder); AddCase(ImageGatherCompareOpcodes); AddCase(ImageStoreVariants); @@ -40183,7 +40231,7 @@ void CheckImageSamplerSpecialization() { sizeof(signed_memory_flags)); mixed_sampler_block->AppendNewInst( ValueOpcode::ImageSampleRaw, - {Value(&image_handle), Value(&sampler_handle), Value(&image_address)}, + {Value(&image_handle), Value(&sampler_handle), Value(&image_address), Value(true)}, signed_memory_flag_bits); ShaderRecompiler::IR::DescriptorValue native_image_descriptor{}; @@ -43440,6 +43488,16 @@ int main(int argc, char **argv) { RunCase(&vulkan, FlatVirtualAddressRebasesGuestAllocation()); return 0; } + if (argc == 2 && std::strcmp(argv[1], "--image-sample-offset-only") == 0) { + VulkanHarness vulkan; + RunCase(&vulkan, ImageSampleOffsetsReadFourNeighbors<32>()); + RunCase(&vulkan, ImageSampleOffsetsReadFourNeighbors<64>()); + RunCase(&vulkan, ImageSampleA16OffsetKeepsTexelOffset32BitOnGpu<32>()); + RunCase(&vulkan, ImageSampleA16OffsetKeepsTexelOffset32BitOnGpu<64>()); + RunCase(&vulkan, ImageSampleAndGather()); + RunCase(nullptr, ImageGatherCompareOpcodes()); + return 0; + } if (argc == 2 && std::strcmp(argv[1], "--image-gather-lod-only") == 0) { VulkanHarness vulkan; RunCase(&vulkan, ImageGatherExplicitLod()); diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index b5f708ad..ed5b8aac 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -1316,7 +1316,7 @@ void TestShaderFeatureCollection() { {Value(0u), Value(0u), Value(0u)}); block->AppendNewInst(ValueOpcode::ImageGatherRaw, {Value(0u), Value(0u), Value(0u), Value(0u), - Value(0u), Value(0u), Value(0u)}); + Value(0u), Value(0u), Value(0u), Value(true)}); auto &shared = block->AppendNewInst(ValueOpcode::LoadSharedU32, {Value(0u), Value(true)}); shared.SetFlags(MemoryFlags{.index = 0});