From d32a7881f07c66eeddc5cfc63c1119dceb2e3418 Mon Sep 17 00:00:00 2001 From: Gjon Gojcevic Date: Wed, 7 Oct 2026 21:28:06 -0400 Subject: [PATCH 1/3] feat(agc): apply the view's channel selects to a color compare Comparison sampling of a color texture required the view's X channel to select red. A title runs image_sample_c_lz on a 1x1 R8 unorm placeholder whose dst_sel is constant 0 on all four channels. On hardware the compare result is placed in every channel and each returned channel then applies its own dst_sel: 0 gives 0, 1 gives 1, R/G/B/A give the compare result. Return each dmask register from its own select, and throw only for the reserved selects 2 and 3. --- .../tests/execution/EmulatedColorCompare.cpp | 50 ++++++++++++++++++- .../Optimization/src/ResourceMaterializer.cpp | 5 +- .../SpirvBackend/src/SpirvImageEmitter.cpp | 17 ++++++- 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/EmulatedColorCompare.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/EmulatedColorCompare.cpp index 3e4ca1aac..5d0fe2926 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/EmulatedColorCompare.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/EmulatedColorCompare.cpp @@ -47,7 +47,8 @@ constexpr std::array Red{0, 64, 128, 255}; alignas(256) std::array Input{}; alignas(256) std::array CubeInput{}; -alignas(256) std::array Output{}; +alignas(256) std::array Output{}; +std::uint32_t Swizzle = 0xfacu; alignas(256) std::array Extra{}; alignas(4096) std::array Texels{}; @@ -71,6 +72,14 @@ alignas(256) constexpr std::array OffsetCode{ 0x7e0c0308, 0xf0f80108, 0x00820901, 0xbf8c3f70, 0x34160082, 0xe0701000, 0x8001090b, 0xbf810000, }; +alignas(256) constexpr std::array QuadCode{ + 0x1614008c, 0xe03c1000, 0x8000010a, 0x7e0802f3, 0x7e0a02f3, 0x7e0c02f3, 0x7e0e02f3, 0xbf8c3f70, 0xf0bc0f08, 0x00820401, 0xbf8c3f70, 0x34160084, 0xe0781000, 0x8001040b, 0xbf810000, 0xbf810000, 0xbf810000, +}; + +alignas(256) constexpr std::array PairCode{ + 0x1614008c, 0xe03c1000, 0x8000010a, 0x7e0802f3, 0x7e0a02f3, 0x7e0c02f3, 0x7e0e02f3, 0xbf8c3f70, 0xf0bc0608, 0x00820401, 0xbf8c3f70, 0x34160084, 0xe0781000, 0x8001040b, 0xbf810000, 0xbf810000, 0xbf810000, +}; + struct Sampler { std::uint32_t clamp; std::uint32_t filter; @@ -91,7 +100,7 @@ std::array TextureDescriptor(std::uint32_t format, bool cube = static_cast(address >> 8u), static_cast((address >> 40u) & 0xffu) | (format << 20u) | (((Side - 1u) & 3u) << 30u), ((Side - 1u) >> 2u) | ((Side - 1u) << 14u), - 0xfacu | ((cube ? TypeCube : Type2D) << 28u), + Swizzle | ((cube ? TypeCube : Type2D) << 28u), cube ? 5u : 0u, 0u, 0u, 0u, }; } @@ -240,6 +249,32 @@ void Run(AgcDriver::VulkanDevice& device, const Sampler& sampler, const char* na } } +constexpr std::uint32_t Select(std::uint32_t x, std::uint32_t y, std::uint32_t z, std::uint32_t w) { + return x | (y << 3u) | (z << 6u) | (w << 9u); +} + +void RunChannels(AgcDriver::VulkanDevice& device, std::span code, std::array channels, std::uint32_t registers, std::uint32_t firstChannel, const char* name) { + const Sampler sampler{ClampEdge, FilterBilinear}; + Swizzle = Select(channels[0], channels[1], channels[2], channels[3]); + Output.fill(-2.0f); + const auto result = Compile(device, Format8888UNorm, sampler, code); + Swizzle = 0xfacu; + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const float compared = Expected(tid, sampler, false); + for (std::uint32_t reg = 0; reg < 4u; ++reg) { + float expected = -1.0f; + if (reg < registers) { + const auto select = channels[firstChannel + reg]; + expected = select == 0u ? 0.0f : select == 1u ? 1.0f : compared; + } + const float actual = Output[tid * 4u + reg]; + Require(std::fabs(actual - expected) <= 1e-4f, std::string(name) + ": thread " + std::to_string(tid) + " register " + std::to_string(reg) + " returned " + std::to_string(actual) + ", expected " + std::to_string(expected)); + } + } +} + void RunCube(AgcDriver::VulkanDevice& device, const Sampler& sampler, const char* name) { FillCubeTexels(); for (std::uint32_t tid = 0; tid < Threads; ++tid) { @@ -313,6 +348,17 @@ int main() { Run(*device, {ClampWrap, FilterBilinear}, "cached bilinear comparison with offsets", true, true); } Reject(*device, Format32Float, {ClampEdge, FilterPoint}, "native comparison with a nonconstant texel offset requires", OffsetCode, false); + RunChannels(*device, QuadCode, {4, 5, 6, 7}, 4, 0, "view R,G,B,A"); + RunChannels(*device, QuadCode, {0, 0, 0, 0}, 4, 0, "view 0,0,0,0"); + RunChannels(*device, QuadCode, {1, 1, 1, 1}, 4, 0, "view 1,1,1,1"); + RunChannels(*device, QuadCode, {0, 4, 1, 5}, 4, 0, "view 0,R,1,G"); + RunChannels(*device, QuadCode, {4, 0, 0, 1}, 4, 0, "view R,0,0,1"); + RunChannels(*device, QuadCode, {5, 5, 5, 5}, 4, 0, "view G,G,G,G"); + RunChannels(*device, QuadCode, {7, 4, 4, 4}, 4, 0, "view A,R,R,R"); + RunChannels(*device, PairCode, {0, 4, 4, 4}, 2, 1, "view 0,R,R,R, dmask 6"); + Swizzle = Select(4, 2, 4, 4); + Reject(*device, Format8888UNorm, {ClampEdge, FilterPoint}, "every view channel is a constant or a color channel"); + Swizzle = 0xfacu; Reject(*device, Format8888UInt, {ClampEdge, FilterPoint}, "unsupported format"); Reject(*device, Format8888UNorm, {ClampMirror, FilterPoint}, "wrap, clamp-to-edge or clamp-to-border"); Reject(*device, Format8888UNorm, {ClampHalfBorder, FilterPoint}, "wrap, clamp-to-edge or clamp-to-border"); diff --git a/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp b/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp index 6b9f1ec19..a843527d6 100644 --- a/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp +++ b/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp @@ -528,7 +528,10 @@ std::uint32_t emulatedCompareState(const ShaderInfo& info, const ResourceSnapsho const auto reference = colorCompareReference(format); const auto type = rawImageType(descriptor); if (type != ImageType::Color2D && type != ImageType::Color2DArray && type != ImageType::Cube) throw std::runtime_error("comparison sampling of a color texture is implemented only for 2D, 2D array and cube views"); - if ((descriptorImageSwizzle(descriptor) & 0x7u) != 4u) throw std::runtime_error("comparison sampling of a color texture is implemented only when the view's X channel is red"); + for (std::uint32_t channel = 0u; channel < 4u; ++channel) { + const auto select = (descriptorImageSwizzle(descriptor) >> (channel * 3u)) & 0x7u; + if (select == 2u || select == 3u) throw std::runtime_error("comparison sampling of a color texture is implemented only when every view channel is a constant or a color channel"); + } std::optional samplerState; for (const auto& pair : info.sampledPairs) { if (pair.image != index) continue; diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp index a2c4943a8..6a7b63d5b 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp @@ -1375,7 +1375,22 @@ void EmitEmulatedCompareSample(SpirvValueEmitContext& ctx, const ImageEmitAccess const auto bottom = ext(f32, GLSLstd450FMix, {compareTexel(x0, y1), compareTexel(x1, y1), weightU}); return ext(f32, GLSLstd450FMix, {top, bottom, weightV}); }); - ctx.Define(access.inst, TableResult(ctx, access, ResultVector(ctx, access, result, setup.numericClass, true, false))); + const auto channelResult = [&](std::uint32_t channel) { + const auto selector = RuntimeImageSwizzle(state, mem.resource, channel); + return Select(state, f32, equal(selector, 0u), zero, Select(state, f32, equal(selector, 1u), one, result)); + }; + const auto dmask = EffectiveDmask(mem); + const auto channels = static_cast(std::popcount(dmask)); + if (mem.dataBits == 16u) { + if (channels != 1u) ctx.Fail(access.inst, "compares against a color texture into several packed 16-bit channels, which is not implemented"); + ctx.Define(access.inst, TableResult(ctx, access, ResultVector(ctx, access, channelResult(DmaskComponent(dmask, 0u)), setup.numericClass, true, false))); + return; + } + std::uint32_t component[4] = {}; + for (std::uint32_t index = 0; index < 4u; ++index) component[index] = index < channels ? F32BitsToU32(ctx, channelResult(DmaskComponent(dmask, index))) : ConstantU32(state, 0u); + const auto vector = state.module.AllocateId(); + state.module.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 4), vector, component[0], component[1], component[2], component[3]); + ctx.Define(access.inst, TableResult(ctx, access, vector)); } void EmitSampleOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, const SampleSetup& setup) { From 6ac5cad133bb0f5b207e2ae36e48264ed479957f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 11 Oct 2026 11:01:32 +0200 Subject: [PATCH 2/3] fix(agc): compare the red channel of a swizzled view --- .../libSceAgcDriver/tests/execution/EmulatedColorCompare.cpp | 4 +++- .../recompiler/Optimization/src/ResourceMaterializer.cpp | 5 +++++ .../shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp | 3 ++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/EmulatedColorCompare.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/EmulatedColorCompare.cpp index 5d0fe2926..4b45f5dc4 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/EmulatedColorCompare.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/EmulatedColorCompare.cpp @@ -353,11 +353,13 @@ int main() { RunChannels(*device, QuadCode, {1, 1, 1, 1}, 4, 0, "view 1,1,1,1"); RunChannels(*device, QuadCode, {0, 4, 1, 5}, 4, 0, "view 0,R,1,G"); RunChannels(*device, QuadCode, {4, 0, 0, 1}, 4, 0, "view R,0,0,1"); - RunChannels(*device, QuadCode, {5, 5, 5, 5}, 4, 0, "view G,G,G,G"); + RunChannels(*device, QuadCode, {5, 4, 5, 6}, 4, 0, "view G,R,B,A"); RunChannels(*device, QuadCode, {7, 4, 4, 4}, 4, 0, "view A,R,R,R"); RunChannels(*device, PairCode, {0, 4, 4, 4}, 2, 1, "view 0,R,R,R, dmask 6"); Swizzle = Select(4, 2, 4, 4); Reject(*device, Format8888UNorm, {ClampEdge, FilterPoint}, "every view channel is a constant or a color channel"); + Swizzle = Select(5, 5, 5, 5); + Reject(*device, Format8888UNorm, {ClampEdge, FilterPoint}, "a view channel selects red"); Swizzle = 0xfacu; Reject(*device, Format8888UInt, {ClampEdge, FilterPoint}, "unsupported format"); Reject(*device, Format8888UNorm, {ClampMirror, FilterPoint}, "wrap, clamp-to-edge or clamp-to-border"); diff --git a/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp b/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp index a843527d6..1241e4e29 100644 --- a/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp +++ b/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp @@ -528,10 +528,15 @@ std::uint32_t emulatedCompareState(const ShaderInfo& info, const ResourceSnapsho const auto reference = colorCompareReference(format); const auto type = rawImageType(descriptor); if (type != ImageType::Color2D && type != ImageType::Color2DArray && type != ImageType::Cube) throw std::runtime_error("comparison sampling of a color texture is implemented only for 2D, 2D array and cube views"); + bool redChannel = false; + bool compareChannel = false; for (std::uint32_t channel = 0u; channel < 4u; ++channel) { const auto select = (descriptorImageSwizzle(descriptor) >> (channel * 3u)) & 0x7u; if (select == 2u || select == 3u) throw std::runtime_error("comparison sampling of a color texture is implemented only when every view channel is a constant or a color channel"); + redChannel = redChannel || select == 4u; + compareChannel = compareChannel || select >= 4u; } + if (compareChannel && !redChannel) throw std::runtime_error("comparison sampling of a color texture is implemented only when a view channel selects red"); std::optional samplerState; for (const auto& pair : info.sampledPairs) { if (pair.image != index) continue; diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp index 6a7b63d5b..8abbd9b56 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp @@ -1334,7 +1334,8 @@ void EmitEmulatedCompareSample(SpirvValueEmitContext& ctx, const ImageEmitAccess else state.module.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 2), coord, ux, uy); const auto texel = state.module.AllocateId(); state.module.AddFunction(spv::OpImageFetch, ImageVectorType(state, IrTextureNumericClass::Float, 4), texel, descriptor, coord, spv::ImageOperandsLodMask, ConstantU32(state, 0u)); - auto red = extract(f32, texel, 0u); + auto red = extract(f32, texel, 3u); + for (std::uint32_t channel = 3u; channel-- > 0u;) red = Select(state, f32, equal(RuntimeImageSwizzle(state, mem.resource, channel), 4u), extract(f32, texel, channel), red); red = Select(state, f32, equal(modeX, EmulatedCompare::AddressBorder), Select(state, f32, inside(x, width), red, borderRed), red); red = Select(state, f32, equal(modeY, EmulatedCompare::AddressBorder), Select(state, f32, inside(y, height), red, borderRed), red); constexpr std::array operations{spv::OpFOrdLessThan, spv::OpFOrdEqual, spv::OpFOrdLessThanEqual, spv::OpFOrdGreaterThan, spv::OpFOrdNotEqual, spv::OpFOrdGreaterThanEqual}; From a0e03ab48d2f1e6036f77df70e92fa586befabb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 11 Oct 2026 11:10:33 +0200 Subject: [PATCH 3/3] fix(agc): write a color compare's channels to their own registers --- .../SpirvBackend/src/SpirvImageEmitter.cpp | 19 ++++++------------- .../MemoryInstructions/ImageInstructions.cpp | 10 +--------- 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp index 8abbd9b56..e23138734 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp @@ -331,7 +331,7 @@ std::uint32_t ResultVector(SpirvValueEmitContext& ctx, const ImageEmitAccess& ac std::uint32_t component[4] = {}; for (std::uint32_t index = 0; index < 4u; index++) { if (dref) { - component[index] = index == 0u ? F32BitsToU32(ctx, value) : ConstantU32(state, 0); + component[index] = F32BitsToU32(ctx, value); continue; } const auto scalar = state.module.AllocateId(); @@ -1380,18 +1380,11 @@ void EmitEmulatedCompareSample(SpirvValueEmitContext& ctx, const ImageEmitAccess const auto selector = RuntimeImageSwizzle(state, mem.resource, channel); return Select(state, f32, equal(selector, 0u), zero, Select(state, f32, equal(selector, 1u), one, result)); }; - const auto dmask = EffectiveDmask(mem); - const auto channels = static_cast(std::popcount(dmask)); - if (mem.dataBits == 16u) { - if (channels != 1u) ctx.Fail(access.inst, "compares against a color texture into several packed 16-bit channels, which is not implemented"); - ctx.Define(access.inst, TableResult(ctx, access, ResultVector(ctx, access, channelResult(DmaskComponent(dmask, 0u)), setup.numericClass, true, false))); - return; - } - std::uint32_t component[4] = {}; - for (std::uint32_t index = 0; index < 4u; ++index) component[index] = index < channels ? F32BitsToU32(ctx, channelResult(DmaskComponent(dmask, index))) : ConstantU32(state, 0u); - const auto vector = state.module.AllocateId(); - state.module.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 4), vector, component[0], component[1], component[2], component[3]); - ctx.Define(access.inst, TableResult(ctx, access, vector)); + std::uint32_t channels[4] = {}; + for (std::uint32_t channel = 0; channel < 4u; ++channel) channels[channel] = channelResult(channel); + const auto value = state.module.AllocateId(); + state.module.AddFunction(spv::OpCompositeConstruct, TypeF32Vector(state, 4), value, channels[0], channels[1], channels[2], channels[3]); + ctx.Define(access.inst, TableResult(ctx, access, ResultVector(ctx, access, value, IrTextureNumericClass::Float, false, false))); } void EmitSampleOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, const SampleSetup& setup) { diff --git a/core/shader/recompiler/Translation/src/MemoryInstructions/ImageInstructions.cpp b/core/shader/recompiler/Translation/src/MemoryInstructions/ImageInstructions.cpp index 4b5b9a0fa..f29c3029b 100644 --- a/core/shader/recompiler/Translation/src/MemoryInstructions/ImageInstructions.cpp +++ b/core/shader/recompiler/Translation/src/MemoryInstructions/ImageInstructions.cpp @@ -204,15 +204,7 @@ bool TranslationContext::imageSample(const RdnaInstruction& inst) { IrValue* sampler = getSamplerResource(memory); IrValue* address = makeImageAddress(inst, inst.source0); IrValue& result = ir.Emit(IrOpcode::ImageSampleRaw, IrOpcodeType(IrOpcode::ImageSampleRaw), {resource, sampler, address}, addMemoryInfo(memory, inst.programCounter)); - const bool dref = (memory.imageSampleFlags & RdnaImageSampleFlagCompare) != 0u; - if (dref && memory.dataBits != 16u) { - IrValue& component = ir.CompositeExtract(result, 0u); - for (std::uint32_t index = 0; index < memory.dataDwords; ++index) { - writeOperand(offsetOperand(inst.destination, index), &component); - } - } else { - writeImageComponents(inst.destination, &result, memory, 4u); - } + writeImageComponents(inst.destination, &result, memory, 4u); return true; }