From 1e20b4ec50c60d2fd02666fa237188cea076f932 Mon Sep 17 00:00:00 2001 From: Scott Schuster Date: Thu, 8 Oct 2026 08:32:46 -0500 Subject: [PATCH] feat(agc): blend against an opaque destination for CB_COLOR_ATTRIB FORCE_DST_ALPHA_1 FORCE_DST_ALPHA_1 makes blending read a target's destination alpha as 1, as for an RGBX surface; RADV sets it for formats without alpha. The target's blend factors are rewritten to what they become with Ad = 1: DST_ALPHA is ONE and ONE_MINUS_DST_ALPHA is ZERO, SRC_ALPHA_SATURATE is ZERO in the color equation, and DST_COLOR and ONE_MINUS_DST_COLOR are ONE and ZERO in the alpha equation. Other CB_COLOR_ATTRIB bits stay rejected. Co-Authored-By: Claude Opus 5.5 --- .../libSceAgcDriver/Graphics/src/State.cpp | 25 ++++++++++++++++++- .../prx/libSceAgcDriver/tests/Graphics.cpp | 20 +++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index dc0ab6fb9..7d8b047e9 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -304,6 +304,23 @@ VkBlendFactor blendFactor(std::uint32_t value) { } } +VkBlendFactor colorFactorWithOpaqueDestination(VkBlendFactor factor) { + switch (factor) { + case VK_BLEND_FACTOR_DST_ALPHA: return VK_BLEND_FACTOR_ONE; + case VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA: return VK_BLEND_FACTOR_ZERO; + case VK_BLEND_FACTOR_SRC_ALPHA_SATURATE: return VK_BLEND_FACTOR_ZERO; + default: return factor; + } +} + +VkBlendFactor alphaFactorWithOpaqueDestination(VkBlendFactor factor) { + switch (factor) { + case VK_BLEND_FACTOR_DST_ALPHA: case VK_BLEND_FACTOR_DST_COLOR: return VK_BLEND_FACTOR_ONE; + case VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA: case VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR: return VK_BLEND_FACTOR_ZERO; + default: return factor; + } +} + VkBlendOp blendOp(std::uint32_t value) { switch (value) { case 0: return VK_BLEND_OP_ADD; @@ -665,6 +682,12 @@ State DecodeState(const QueueState& queue) { state.dstColorBlendFactor = state.dstAlphaBlendFactor; state.colorBlendOp = state.alphaBlendOp; } + if ((read(cx, 0x31d + slot * 0xfu) & 0x20000u) != 0) { + state.srcColorBlendFactor = colorFactorWithOpaqueDestination(state.srcColorBlendFactor); + state.dstColorBlendFactor = colorFactorWithOpaqueDestination(state.dstColorBlendFactor); + state.srcAlphaBlendFactor = alphaFactorWithOpaqueDestination(state.srcAlphaBlendFactor); + state.dstAlphaBlendFactor = alphaFactorWithOpaqueDestination(state.dstAlphaBlendFactor); + } for (std::uint32_t i = 0; i < 4; ++i) result.blendConstants[i] = readFloat(cx, 0x105 + i); } result.blends[color.exportIndex] = state; @@ -719,7 +742,7 @@ ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot) { const auto slice = view & 0x1fffu; Require(slice == ((view >> 13u) & 0x1fffu), "color views of several array slices are unsupported"); const auto viewMip = (view >> 26u) & 0xfu; - zero(cx, 0x31d + stride, ~0u, "color samples, fragments or destination alpha override"); + zero(cx, 0x31d + stride, ~0x20000u, "color samples, fragments or destination alpha override"); const auto attrib2 = read(cx, 0x3b0 + slot); const auto maxMip = attrib2 >> 28u; Require(viewMip <= maxMip, "color view mip exceeds the surface"); diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index e762ee96f..afd279035 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -572,6 +572,25 @@ void PixelInputLayoutTests() { Require(!readsBuiltin(read, spv::BuiltInFragCoord) && !readsBuiltin(read, spv::BuiltInBaryCoordKHR), "an ADDR-only centroid pair was loaded"); } +void OpaqueDestinationAlphaTests() { + auto queue = makeState(); + for (std::uint32_t constant = 0x105; constant < 0x109; ++constant) queue.context[constant] = 0; + queue.context[0x1e0] = (1u << 30u) | (1u << 29u) | 6u | (7u << 8u) | (8u << 16u) | (10u << 24u); + auto blend = AgcDriver::Graphics::DecodeState(queue).blend; + Require(blend.srcColorBlendFactor == VK_BLEND_FACTOR_DST_ALPHA && blend.dstColorBlendFactor == VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA, "color blend factors changed"); + Require(blend.srcAlphaBlendFactor == VK_BLEND_FACTOR_DST_COLOR && blend.dstAlphaBlendFactor == VK_BLEND_FACTOR_SRC_ALPHA_SATURATE, "alpha blend factors changed"); + queue.context[0x31d] = 0x20000u; + blend = AgcDriver::Graphics::DecodeState(queue).blend; + Require(blend.srcColorBlendFactor == VK_BLEND_FACTOR_ONE && blend.dstColorBlendFactor == VK_BLEND_FACTOR_ZERO, "an opaque destination left a destination-alpha color factor"); + Require(blend.srcAlphaBlendFactor == VK_BLEND_FACTOR_ONE && blend.dstAlphaBlendFactor == VK_BLEND_FACTOR_SRC_ALPHA_SATURATE, "an opaque destination left a destination alpha factor"); + queue.context[0x1e0] = (1u << 30u) | 10u | (9u << 8u); + blend = AgcDriver::Graphics::DecodeState(queue).blend; + Require(blend.srcColorBlendFactor == VK_BLEND_FACTOR_ZERO && blend.dstColorBlendFactor == VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR, "alpha saturation with an opaque destination"); + Require(blend.dstAlphaBlendFactor == VK_BLEND_FACTOR_ZERO, "an opaque destination left a destination-color alpha factor"); + queue.context[0x31d] = 0x40000u; + expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "destination alpha override"); +} + void DisabledColorTests() { auto queue = makeState(); queue.context[0x8e] = 0; @@ -2762,6 +2781,7 @@ int main() { orderedPixelShaderTests(); ConservativeRasterizationTests(); DisabledColorTests(); + OpaqueDestinationAlphaTests(); CompactedExportTests(); ReversedComponentOrderTests(); metadataPassTests();