shader: emit native texel offsets using sample execution context

Co-authored-by: netwarex <mail@pety.me>
This commit is contained in:
nmzik
2026-10-11 18:32:06 +02:00
parent df0412afb5
commit 984cfcebd1
6 changed files with 136 additions and 63 deletions
@@ -323,25 +323,25 @@ uint32_t QueryDimensions(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
return vector; return vector;
} }
uint32_t PackedOffset(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::Inst& address, uint32_t PackedOffset(ValueEmitContext& ctx, IR::Value value, ImageDimension dimension,
const ImageSampleLayout& layout, ImageDimension dimension) { uint32_t& mask) {
const auto components = ImageDimensionInfoFor(dimension).spatial_components; const auto components = ImageDimensionInfoFor(dimension).spatial_components;
const auto zero = ConstantI32(ctx.state, 0); uint32_t values[3] {};
if (layout.offset == NoImageComponent || mem.image_address_components <= layout.offset) { if (value.IsImmediate()) {
if (components == 1u) return zero; // RDNA2 packs signed six-bit texel offsets into the low bits of each byte.
const auto result = ctx.state.builder.AllocateId(); for (uint32_t index = 0; index < components; index++) {
if (components == 3u) { const auto bits = (value.U32() >> (index * 8u)) & 0x3fu;
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeI32Vector(ctx.state, 3), values[index] = ConstantI32(ctx.state, static_cast<int32_t>(bits ^ 0x20u) - 0x20);
result, zero, zero, zero);
} else {
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeI32Vector(ctx.state, 2),
result, zero, zero);
} }
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), mask |= spv::ImageOperandsOffsetMask;
AddressU32(ctx, mem, address, layout.offset)); const auto packed = Unary(ctx.state, spv::OpBitcast, TypeI32(ctx.state), ctx.Def(value));
uint32_t values[3] = {zero, zero, zero};
for (uint32_t index = 0; index < components; index++) { for (uint32_t index = 0; index < components; index++) {
values[index] = ctx.state.builder.AllocateId(); values[index] = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(spv::OpBitFieldSExtract, TypeI32(ctx.state), values[index], 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]; if (components == 1u) return values[0];
const auto result = ctx.state.builder.AllocateId(); const auto result = ctx.state.builder.AllocateId();
if (components == 3u) { ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeI32Vector(ctx.state, components),
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeI32Vector(ctx.state, 3), result, std::span(values).first(components));
result, values[0], values[1], values[2]);
} else {
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeI32Vector(ctx.state, 2),
result, values[0], values[1]);
}
return result; return result;
} }
@@ -794,6 +789,12 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto layout = Layout(mem); const auto layout = Layout(mem);
const auto numeric_class = image.numeric_class; const auto numeric_class = image.numeric_class;
const bool dref = HasFlag(mem, Decoder::ImageSampleFlagCompare); 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 != if (dref && state.program.info.images[mem.resource].conversion_format !=
Prospero::BufferFormat::kInvalid) { Prospero::BufferFormat::kInvalid) {
ctx.Fail(inst, "uses depth comparison with a converted image"); 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; operand_mask = spv::ImageOperandsConstOffsetsMask;
offset = HorizontalOffsets(state, dimension); offset = HorizontalOffsets(state, dimension);
} else if (layout.offset != NoImageComponent) { } else if (layout.offset != NoImageComponent) {
operand_mask = spv::ImageOperandsOffsetMask; offset = PackedOffset(ctx, offset_value, dimension, operand_mask);
offset = PackedOffset(ctx, mem, *address, layout, dimension);
} }
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);
@@ -886,7 +886,7 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
dref_value = AddressF32(ctx, mem, *address, layout.dref); dref_value = AddressF32(ctx, mem, *address, layout.dref);
} }
} }
std::array<uint32_t, 3> operands {}; std::array<uint32_t, 4> operands {};
size_t operand_count = 0; size_t operand_count = 0;
if (HasFlag(mem, Decoder::ImageSampleFlagDerivative)) { if (HasFlag(mem, Decoder::ImageSampleFlagDerivative)) {
operands[0] = spv::ImageOperandsGradMask; operands[0] = spv::ImageOperandsGradMask;
@@ -916,9 +916,20 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
state, ctx.Def(handle->Arg(0)), static_cast<uint32_t>(samplers.size()), state, ctx.Def(handle->Arg(0)), static_cast<uint32_t>(samplers.size()),
TypeU32(state), [&](uint32_t ordinal) { return ConstantU32(state, samplers[ordinal]); }); 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 sampler_id = LoadSamplerDescriptor(state, mem.sampler, sampler_index);
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];
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 = const auto coord =
CoordF32(ctx, mem, *address, layout.coord, CoordF32(ctx, mem, *address, layout.coord,
ImageDimensionInfoFor(candidate.dimension).coordinate_components, ImageDimensionInfoFor(candidate.dimension).coordinate_components,
@@ -928,7 +939,7 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
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>(operands).first(operand_count)); std::span<const uint32_t>(sample_operands).first(sample_operand_count));
return sample; return sample;
}; };
auto result = EmitImageAccess(ctx, inst, result_type, EmitSample); auto result = EmitImageAccess(ctx, inst, result_type, EmitSample);
@@ -687,7 +687,8 @@ void Translator::IMAGE_SAMPLE(const Decoder::Instruction& inst) {
const auto resource = GetImageResource(memory); const auto resource = GetImageResource(memory);
const auto sampler = GetSamplerResource(memory); const auto sampler = GetSamplerResource(memory);
const auto address = MakeImageAddress(inst, MemorySourceAt(inst, 0)); 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)); AddMemoryInfo(memory, inst.pc));
const bool dref = (memory.image_sample_flags & Decoder::ImageSampleFlagCompare) != 0u; const bool dref = (memory.image_sample_flags & Decoder::ImageSampleFlagCompare) != 0u;
if (dref && memory.data_bits != 16u) { 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(1) : IR::Value(0u),
has_lod ? resource.Instruction()->Arg(3) : 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(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)); AddMemoryInfo(memory, inst.pc));
for (uint32_t index = 0; index < memory.data_dwords; index++) { for (uint32_t index = 0; index < memory.data_dwords; index++) {
WriteOperand(OffsetOperand(inst.dst, index), WriteOperand(OffsetOperand(inst.dst, index),
@@ -317,8 +317,8 @@ VALUE_OPCODE(BvhIntersect, U32x4, U32x4, ImageAddress, U1)
VALUE_OPCODE(ImageQueryLod, U32x4, ImageResource, SamplerResource, ImageAddress) VALUE_OPCODE(ImageQueryLod, U32x4, ImageResource, SamplerResource, ImageAddress)
VALUE_OPCODE(ImageRead, U32x4, ImageResource, ImageAddress, U1) VALUE_OPCODE(ImageRead, U32x4, ImageResource, ImageAddress, U1)
VALUE_OPCODE(ImageWrite, Void, ImageResource, ImageAddress, U32x4, U1) VALUE_OPCODE(ImageWrite, Void, ImageResource, ImageAddress, U32x4, U1)
VALUE_OPCODE(ImageSampleRaw, U32x4, ImageResource, SamplerResource, ImageAddress) VALUE_OPCODE(ImageSampleRaw, U32x4, ImageResource, SamplerResource, ImageAddress, U1)
VALUE_OPCODE(ImageGatherRaw, U32x4, ImageResource, SamplerResource, ImageAddress, U32, U32, U32, U32) VALUE_OPCODE(ImageGatherRaw, U32x4, ImageResource, SamplerResource, ImageAddress, U32, U32, U32, U32, U1)
VALUE_OPCODE(ImageAtomicCompareSwap32, U32, ImageResource, ImageAddress, U32, U32, U1) VALUE_OPCODE(ImageAtomicCompareSwap32, U32, ImageResource, ImageAddress, U32, U32, U1)
VALUE_OPCODE(ImageAtomicSwap32, U32, ImageResource, ImageAddress, U32, U1) VALUE_OPCODE(ImageAtomicSwap32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(ImageAtomicSwap64, U64, ImageResource, ImageAddress, U64, U1) VALUE_OPCODE(ImageAtomicSwap64, U64, ImageResource, ImageAddress, U64, U1)
+22 -19
View File
@@ -270,7 +270,7 @@ MakeIndirectImageFixture(bool malformed, uint32_t table_stride = 32, uint32_t ta
sample.kind = ResourceKind::Image; sample.kind = ResourceKind::Image;
sample.image_dimension = Decoder::ImageDimension::Dim2D; sample.image_dimension = Decoder::ImageDimension::Dim2D;
const auto sampled = fixture->Emit(ValueOpcode::ImageSampleRaw, const auto sampled = fixture->Emit(ValueOpcode::ImageSampleRaw,
{image, sampler, fixture->ImageAddress()}, {image, sampler, fixture->ImageAddress(), Value(true)},
fixture->AddMemory(sample, 0x10f0)); fixture->AddMemory(sample, 0x10f0));
const auto sampled_x = const auto sampled_x =
fixture->Emit(ValueOpcode::CompositeExtractU32x4, {sampled, Value(0u)}); fixture->Emit(ValueOpcode::CompositeExtractU32x4, {sampled, Value(0u)});
@@ -486,7 +486,8 @@ void TestWaterfallImageTable() {
memory.kind = ResourceKind::Image; memory.kind = ResourceKind::Image;
memory.image_dimension = Decoder::ImageDimension::Dim2D; memory.image_dimension = Decoder::ImageDimension::Dim2D;
const auto value = fixture->Emit(ValueOpcode::ImageSampleRaw, 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::ReferenceU32,
{fixture->Emit(ValueOpcode::CompositeExtractU32x4, {value, Value(0u)}, 0, block)}, 0, block); {fixture->Emit(ValueOpcode::CompositeExtractU32x4, {value, Value(0u)}, 0, block)}, 0, block);
}; };
@@ -641,7 +642,7 @@ void TestGuardedDirectImageTable() {
MemoryInfo memory; MemoryInfo memory;
memory.kind = ResourceKind::Image; memory.kind = ResourceKind::Image;
memory.image_dimension = Decoder::ImageDimension::Dim2D; 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.AddMemory(memory, 0x128));
fixture.PlanAndTrack(); fixture.PlanAndTrack();
const auto source = fixture.program.info.images[0].source; const auto source = fixture.program.info.images[0].source;
@@ -989,7 +990,7 @@ void TestBoundedComputeImageLoop() {
sample.kind = ResourceKind::Image; sample.kind = ResourceKind::Image;
sample.image_dimension = Decoder::ImageDimension::Dim2D; sample.image_dimension = Decoder::ImageDimension::Dim2D;
fixture.Emit(ValueOpcode::ImageSampleRaw, fixture.Emit(ValueOpcode::ImageSampleRaw,
{image, sampler, fixture.ImageAddress()}, {image, sampler, fixture.ImageAddress(), Value(true)},
fixture.AddMemory(sample, 0x29c)); fixture.AddMemory(sample, 0x29c));
fixture.PlanAndTrack(); fixture.PlanAndTrack();
const auto source = fixture.program.info.images[0].source; const auto source = fixture.program.info.images[0].source;
@@ -1325,7 +1326,9 @@ void TestUniformizedMaterialImageKeys() {
MemoryInfo image_memory; MemoryInfo image_memory;
image_memory.kind = ResourceKind::Image; image_memory.kind = ResourceKind::Image;
image_memory.image_dimension = Decoder::ImageDimension::Dim2D; 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); fixture.AddMemory(image_memory, 0x1aecu), sample);
if (first_lane) { if (first_lane) {
const auto component = fixture.Emit( const auto component = fixture.Emit(
@@ -1477,7 +1480,7 @@ void TestImageDescriptorFields() {
memory.kind = ResourceKind::Image; memory.kind = ResourceKind::Image;
memory.image_dimension = Decoder::ImageDimension::Dim2D; memory.image_dimension = Decoder::ImageDimension::Dim2D;
memory.image_r128 = r128; 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.AddMemory(memory, 0x80));
fixture.PlanAndTrack(); fixture.PlanAndTrack();
auto plan = ExtractResourcePlan(fixture.program); auto plan = ExtractResourcePlan(fixture.program);
@@ -1571,7 +1574,7 @@ void TestUniformScalarBufferImage() {
sample.kind = ResourceKind::Image; sample.kind = ResourceKind::Image;
sample.image_dimension = Decoder::ImageDimension::Dim2D; sample.image_dimension = Decoder::ImageDimension::Dim2D;
fixture.Emit(ValueOpcode::ImageSampleRaw, fixture.Emit(ValueOpcode::ImageSampleRaw,
{image, sampler, fixture.ImageAddress()}, fixture.AddMemory(sample, 0x228)); {image, sampler, fixture.ImageAddress(), Value(true)}, fixture.AddMemory(sample, 0x228));
fixture.PlanAndTrack(); fixture.PlanAndTrack();
const auto plan = ExtractResourcePlan(fixture.program); const auto plan = ExtractResourcePlan(fixture.program);
@@ -1866,7 +1869,7 @@ void TestImagesSamplersAndAliases() {
memory.kind = ResourceKind::Image; memory.kind = ResourceKind::Image;
memory.image_dimension = Decoder::ImageDimension::Dim2D; memory.image_dimension = Decoder::ImageDimension::Dim2D;
memory.image_sample_flags = sample_flags; 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)); fixture.AddMemory(memory, pc));
return std::pair{image, sampler}; return std::pair{image, sampler};
}; };
@@ -1938,14 +1941,14 @@ void TestSampleAdjustSamplerScratch() {
memory.image_dimension = Decoder::ImageDimension::Dim2D; memory.image_dimension = Decoder::ImageDimension::Dim2D;
memory.image_sample_flags = Decoder::ImageSampleFlagAdjust; memory.image_sample_flags = Decoder::ImageSampleFlagAdjust;
fixture.Emit(ValueOpcode::ImageSampleRaw, fixture.Emit(ValueOpcode::ImageSampleRaw,
{image, sampler, fixture.ImageAddress()}, {image, sampler, fixture.ImageAddress(), Value(true)},
fixture.AddMemory(memory, 0x1ec)); fixture.AddMemory(memory, 0x1ec));
// PPSA24156's zero border payload leaves the reserved scratch shift as // PPSA24156's zero border payload leaves the reserved scratch shift as
// the entire DWORD after the frontend folds its OR with zero. // the entire DWORD after the frontend folds its OR with zero.
const auto shift_sampler = fixture.Sampler( const auto shift_sampler = fixture.Sampler(
{Value(0x36u), Value(0xfff000u), Value(0x02500000u), scratch}, 0x200); {Value(0x36u), Value(0xfff000u), Value(0x02500000u), scratch}, 0x200);
fixture.Emit(ValueOpcode::ImageSampleRaw, fixture.Emit(ValueOpcode::ImageSampleRaw,
{image, shift_sampler, fixture.ImageAddress()}, {image, shift_sampler, fixture.ImageAddress(), Value(true)},
fixture.AddMemory(memory, 0x200)); fixture.AddMemory(memory, 0x200));
fixture.PlanAndTrack(); fixture.PlanAndTrack();
auto plan = ExtractResourcePlan(fixture.program); auto plan = ExtractResourcePlan(fixture.program);
@@ -1994,7 +1997,7 @@ void TestSampleAdjustSamplerScratch() {
rejected_memory.image_dimension = Decoder::ImageDimension::Dim2D; rejected_memory.image_dimension = Decoder::ImageDimension::Dim2D;
rejected_memory.image_sample_flags = flags; rejected_memory.image_sample_flags = flags;
rejected.Emit(ValueOpcode::ImageSampleRaw, rejected.Emit(ValueOpcode::ImageSampleRaw,
{rejected_image, rejected_sampler, rejected.ImageAddress()}, {rejected_image, rejected_sampler, rejected.ImageAddress(), Value(true)},
rejected.AddMemory(rejected_memory, 0x200)); rejected.AddMemory(rejected_memory, 0x200));
CheckFatal([&] { rejected.PlanAndTrack(); }, CheckFatal([&] { rejected.PlanAndTrack(); },
@@ -2112,7 +2115,7 @@ void TestGatherLodSamplerValidation() {
fixture.Emit(ValueOpcode::ImageGatherRaw, fixture.Emit(ValueOpcode::ImageGatherRaw,
{fixture.Image(image_words), fixture.Sampler(sampler_words), {fixture.Image(image_words), fixture.Sampler(sampler_words),
fixture.ImageAddress(), image_words[1], image_words[3], 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.AddMemory(memory, i * 4));
} }
fixture.PlanAndTrack(); fixture.PlanAndTrack();
@@ -2514,7 +2517,7 @@ ResourcePlan ConditionalSamplerPlan(bool diamond, bool reverse, bool reverse_phi
sample.kind = ResourceKind::Image; sample.kind = ResourceKind::Image;
sample.image_dimension = Decoder::ImageDimension::Dim2D; sample.image_dimension = Decoder::ImageDimension::Dim2D;
const auto result = const auto result =
fixture.Emit(ValueOpcode::ImageSampleRaw, {image, sampler, address}, fixture.Emit(ValueOpcode::ImageSampleRaw, {image, sampler, address, Value(true)},
fixture.AddMemory(sample, 0x4d4)); fixture.AddMemory(sample, 0x4d4));
fixture.Emit(ValueOpcode::ReferenceU32, fixture.Emit(ValueOpcode::ReferenceU32,
{fixture.Emit(ValueOpcode::CompositeExtractU32x4, {fixture.Emit(ValueOpcode::CompositeExtractU32x4,
@@ -2619,7 +2622,7 @@ void TestFiniteImagePhiCycle() {
MemoryInfo memory; MemoryInfo memory;
memory.kind = ResourceKind::Image; memory.kind = ResourceKind::Image;
memory.image_dimension = Decoder::ImageDimension::Dim2D; 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.AddMemory(memory, 4));
fixture.PlanAndTrack(); fixture.PlanAndTrack();
auto plan = ExtractResourcePlan(fixture.program); auto plan = ExtractResourcePlan(fixture.program);
@@ -2685,12 +2688,12 @@ void TestFiniteInlineSampler() {
memory.kind = ResourceKind::Image; memory.kind = ResourceKind::Image;
memory.image_dimension = Decoder::ImageDimension::Dim2D; memory.image_dimension = Decoder::ImageDimension::Dim2D;
if (format == F::k32Float) 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.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)); fixture.AddMemory(memory, 0x1dd0u));
if (format == F::k32Float) 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.AddMemory(memory, 0x1dd8u));
} }
fixture.PlanAndTrack(); fixture.PlanAndTrack();
@@ -2764,7 +2767,7 @@ void TestFiniteInlineSampler() {
memory.kind = ResourceKind::Image; memory.kind = ResourceKind::Image;
memory.image_dimension = Decoder::ImageDimension::Dim2D; memory.image_dimension = Decoder::ImageDimension::Dim2D;
memory.image_sample_flags = flags; 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)); adjusted.AddMemory(memory, 0x1dd0u));
} }
CheckFatal([&] { adjusted.PlanAndTrack(); }, "not a valid runtime value", CheckFatal([&] { adjusted.PlanAndTrack(); }, "not a valid runtime value",
@@ -2817,7 +2820,7 @@ void TestFiniteImageBitScanSentinel() {
MemoryInfo memory; MemoryInfo memory;
memory.kind = ResourceKind::Image; memory.kind = ResourceKind::Image;
memory.image_dimension = Decoder::ImageDimension::Dim2D; 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.AddMemory(memory, 4));
if (!nonzero) { if (!nonzero) {
CheckFatal([&] { fixture.PlanAndTrack(); }, "not a valid runtime value", CheckFatal([&] { fixture.PlanAndTrack(); }, "not a valid runtime value",
+70 -12
View File
@@ -34960,12 +34960,11 @@ TestCase ImageSampleOpcodeAliasUsesNormalCoords() {
return test; return test;
} }
TestCase ImageSampleA16OffsetKeepsTexelOffset32BitOnGpu() { template <u32 wave_size> TestCase ImageSampleA16OffsetKeepsTexelOffset32BitOnGpu() {
using O = ShaderOpcode; using O = ShaderOpcode;
std::vector<u32> code; std::vector<u32> code;
AppendVMovU32(&code, 20, AppendVMovU32(&code, 20, 1); // +1 X remains a full dword with A16 coordinates.
1); // Non-constant +1 X offset is not a SPIR-V ConstOffset.
AppendVMovLiteral(&code, 21, 0x36003900u); // x=0.625, y=0.375 packed as f16. AppendVMovLiteral(&code, 21, 0x36003900u); // x=0.625, y=0.375 packed as f16.
AppendVMovU32(&code, 22, 0); AppendVMovU32(&code, 22, 0);
code.push_back(EncodeMimg0(0x30, 0xf)); code.push_back(EncodeMimg0(0x30, 0xf));
@@ -34976,18 +34975,63 @@ TestCase ImageSampleA16OffsetKeepsTexelOffset32BitOnGpu() {
AppendEnd(&code); AppendEnd(&code);
auto image = MakeRgbaImage(4, 4); 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); 0x40800000u);
TestCase test; TestCase test;
test.name = "ImageSampleA16OffsetKeepsTexelOffset32BitOnGpu"; test.name = wave_size == 32 ? "ImageSampleA16OffsetKeepsTexelOffset32BitOnGpu32"
: "ImageSampleA16OffsetKeepsTexelOffset32BitOnGpu64";
test.code = code; test.code = code;
test.compute_info.wave_size = wave_size;
test.has_compute_info = true;
test.expected = {0x3f800000u, 0x40000000u, 0x40400000u, 0x40800000u}; test.expected = {0x3f800000u, 0x40000000u, 0x40400000u, 0x40800000u};
test.opcodes = {O::V_MOV_B32, O::IMAGE_SAMPLE, O::BUFFER_STORE_DWORD, test.opcodes = {O::V_MOV_B32, O::IMAGE_SAMPLE, O::BUFFER_STORE_DWORD,
O::S_ENDPGM}; O::S_ENDPGM};
test.sampled_image_rgba = image; test.sampled_image_rgba = image;
test.required_spirv = {"UnpackHalf2x16"}; test.required_spirv = {"UnpackHalf2x16", "Lod|ConstOffset"};
test.forbidden_spirv = {"OpBitFieldSExtract"}; test.forbidden_spirv = {"OpBitFieldSExtract", "OpImageQuerySizeLod", "OpFDiv"};
return test;
}
template <u32 wave_size> 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<u32>(gradient));
}
}
// All instructions address the center of texel (2, 1) before applying the offset.
AppendVMovLiteral(&test.code, coordinate, std::bit_cast<u32>(0.625f));
AppendVMovLiteral(&test.code, coordinate + 1, std::bit_cast<u32>(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<u32>(16.f), 0, 0, 0);
constexpr std::array<std::array<u32, 2>, 4> neighbors{{{3, 1}, {1, 1}, {2, 0}, {2, 2}}};
for (u32 sample = 0; sample < neighbors.size(); ++sample) {
const auto value = std::bit_cast<u32>(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; return test;
} }
@@ -35058,7 +35102,8 @@ TestCase ImageGatherCompareOpcodes() {
O::V_MOV_B32, O::IMAGE_GATHER4_C, O::IMAGE_GATHER4_C_LZ, 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::IMAGE_GATHER4_C_O, O::IMAGE_GATHER4_C_LZ_O, O::BUFFER_STORE_DWORD,
O::S_ENDPGM}; O::S_ENDPGM};
test.required_spirv = {"OpImageDrefGather", "OpBitFieldSExtract"}; test.required_spirv = {"OpImageDrefGather", "ConstOffset"};
test.forbidden_spirv = {"OpBitFieldSExtract"};
test.compile_only = true; test.compile_only = true;
return test; return test;
} }
@@ -35610,7 +35655,7 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) {
std::memcpy(&memory_flag_bits, &memory_flags, sizeof(memory_flags)); std::memcpy(&memory_flag_bits, &memory_flags, sizeof(memory_flags));
auto &sample = block->AppendNewInst( auto &sample = block->AppendNewInst(
ValueOpcode::ImageSampleRaw, 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, auto &sample_x = block->AppendNewInst(ValueOpcode::CompositeExtractU32x4,
{Value(&sample), Value(0u)}); {Value(&sample), Value(0u)});
block->AppendNewInst(ValueOpcode::ReferenceU32, {Value(&sample_x)}); block->AppendNewInst(ValueOpcode::ReferenceU32, {Value(&sample_x)});
@@ -35836,7 +35881,7 @@ void CheckIndirectImageKeySwitch(VulkanHarness &vulkan) {
auto &read = *block->PrependNewInst( auto &read = *block->PrependNewInst(
position, ValueOpcode::ImageGatherRaw, position, ValueOpcode::ImageGatherRaw,
{Value(&image), Value(&sampler), Value(&address), Value(0u), Value(0u), {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->ReplaceUsesWith(Value(&read), false);
access = &read; access = &read;
compiled.program.info.images[4].cube = false; compiled.program.info.images[4].cube = false;
@@ -37772,7 +37817,10 @@ std::vector<TestCase> MakeCases() {
AddCase(ImageD16GatherPacksHalfPairs); AddCase(ImageD16GatherPacksHalfPairs);
AddCase(ImageSampleA16SamplerCoordsOnGpu); AddCase(ImageSampleA16SamplerCoordsOnGpu);
AddCase(ImageSampleOpcodeAliasUsesNormalCoords); AddCase(ImageSampleOpcodeAliasUsesNormalCoords);
AddCase(ImageSampleA16OffsetKeepsTexelOffset32BitOnGpu); AddCase(ImageSampleA16OffsetKeepsTexelOffset32BitOnGpu<32>);
AddCase(ImageSampleA16OffsetKeepsTexelOffset32BitOnGpu<64>);
AddCase(ImageSampleOffsetsReadFourNeighbors<32>);
AddCase(ImageSampleOffsetsReadFourNeighbors<64>);
AddCase(ImageSampleA16CompareBiasRdna2AddressOrder); AddCase(ImageSampleA16CompareBiasRdna2AddressOrder);
AddCase(ImageGatherCompareOpcodes); AddCase(ImageGatherCompareOpcodes);
AddCase(ImageStoreVariants); AddCase(ImageStoreVariants);
@@ -40183,7 +40231,7 @@ void CheckImageSamplerSpecialization() {
sizeof(signed_memory_flags)); sizeof(signed_memory_flags));
mixed_sampler_block->AppendNewInst( mixed_sampler_block->AppendNewInst(
ValueOpcode::ImageSampleRaw, 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); signed_memory_flag_bits);
ShaderRecompiler::IR::DescriptorValue native_image_descriptor{}; ShaderRecompiler::IR::DescriptorValue native_image_descriptor{};
@@ -43440,6 +43488,16 @@ int main(int argc, char **argv) {
RunCase(&vulkan, FlatVirtualAddressRebasesGuestAllocation()); RunCase(&vulkan, FlatVirtualAddressRebasesGuestAllocation());
return 0; 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) { if (argc == 2 && std::strcmp(argv[1], "--image-gather-lod-only") == 0) {
VulkanHarness vulkan; VulkanHarness vulkan;
RunCase(&vulkan, ImageGatherExplicitLod()); RunCase(&vulkan, ImageGatherExplicitLod());
+1 -1
View File
@@ -1316,7 +1316,7 @@ void TestShaderFeatureCollection() {
{Value(0u), Value(0u), Value(0u)}); {Value(0u), Value(0u), Value(0u)});
block->AppendNewInst(ValueOpcode::ImageGatherRaw, 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(0u)}); Value(0u), Value(0u), Value(0u), Value(true)});
auto &shared = block->AppendNewInst(ValueOpcode::LoadSharedU32, auto &shared = block->AppendNewInst(ValueOpcode::LoadSharedU32,
{Value(0u), Value(true)}); {Value(0u), Value(true)});
shared.SetFlags(MemoryFlags{.index = 0}); shared.SetFlags(MemoryFlags{.index = 0});