diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index 2210c33d0..e6f74a0a4 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -444,8 +444,7 @@ DecodedColorFormat DecodeColorFormat(std::uint32_t format, std::uint32_t number, if (number == uint) return {VK_FORMAT_R32G32_UINT, 8}; return fail(); case 12: { - if (swap == 1) return fail(); - const auto reversed = static_cast(swap == 2 ? 0x1bu : swap == 3 ? 0x93u : 0xe4u); + const auto reversed = static_cast(alternate ? 0xc6u : swap == 2 ? 0x1bu : swap == 3 ? 0x93u : 0xe4u); if (number == floating) return {VK_FORMAT_R16G16B16A16_SFLOAT, 8, reversed}; if (number == unorm) return {VK_FORMAT_R16G16B16A16_UNORM, 8, reversed}; if (number == snorm) return {VK_FORMAT_R16G16B16A16_SNORM, 8, reversed}; @@ -720,6 +719,8 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) { if (collapsed) result.scissor.extent = {0, 0}; APS5_LOG_OUT_DEBUG("Scissor offset=(%d,%d) extent=%ux%u", result.scissor.offset.x, result.scissor.offset.y, result.scissor.extent.width, result.scissor.extent.height); result.blends.assign(exportCount, VkPipelineColorBlendAttachmentState{}); + std::array requiredBlendConstants{}; + std::uint32_t requiredBlendMask = 0; for (const auto& color : result.colors) { const auto slot = color.slot; const auto blend = read(cx, 0x1e0 + slot); @@ -755,6 +756,16 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) { state.dstAlphaBlendFactor = alphaFactorWithOpaqueDestination(state.dstAlphaBlendFactor); } for (std::uint32_t i = 0; i < 4; ++i) result.blendConstants[i] = readFloat(cx, 0x105 + i); + const auto constantColor = [](VkBlendFactor factor) { return factor == VK_BLEND_FACTOR_CONSTANT_COLOR || factor == VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR; }; + if ((mapping == 0xe4u || mapping == 0xc6u) && state.colorBlendOp != VK_BLEND_OP_MIN && state.colorBlendOp != VK_BLEND_OP_MAX && (constantColor(state.srcColorBlendFactor) || constantColor(state.dstColorBlendFactor))) { + for (std::uint32_t i = 0; i < 3; ++i) { + if ((state.colorWriteMask & (1u << i)) == 0) continue; + const auto value = result.blendConstants[(mapping >> (2u * i)) & 3u]; + Require((requiredBlendMask & (1u << i)) == 0 || requiredBlendConstants[i] == value, "color targets require conflicting blend constants after component swap"); + requiredBlendConstants[i] = value; + requiredBlendMask |= 1u << i; + } + } if (mapping == 0xecu) { const auto destinationAlpha = [](VkBlendFactor factor) { return factor == VK_BLEND_FACTOR_DST_ALPHA || factor == VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA || factor == VK_BLEND_FACTOR_SRC_ALPHA_SATURATE; }; if (destinationAlpha(state.srcColorBlendFactor) || destinationAlpha(state.dstColorBlendFactor) || destinationAlpha(state.srcAlphaBlendFactor) || destinationAlpha(state.dstAlphaBlendFactor)) throw std::runtime_error("AGC graphics: blending into an 8_8 color target with the alternate component order with a factor of the destination alpha, which the target keeps in its second channel, is not implemented"); @@ -773,6 +784,9 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) { } result.blends[color.exportIndex] = state; } + for (std::uint32_t i = 0; i < 3; ++i) { + if ((requiredBlendMask & (1u << i)) != 0) result.blendConstants[i] = requiredBlendConstants[i]; + } if (!result.colors.empty()) result.blend = result.blends[result.colors.front().exportIndex]; if (result.dualSourceBlend) { Require(exportSlots.size() == 2 && exportSlots[0] == 0 && exportSlots[1] == 1 && exportCount == 1, "dual-source blending needs pixel exports to MRT slots 0 and 1 and color writes to slot 0 alone"); diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 9cd184dfd..2d13bd08e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -90,6 +90,48 @@ void expectFailure(TAction action, std::string_view reason) { throw std::runtime_error("expected graphics rejection: " + std::string(reason)); } +void hdrComponentSwapTests() { + using namespace AgcDriver::Graphics; + for (const auto number : {0u, 1u, 4u, 7u}) { + auto queue = makeState(); + queue.context[0x31c] = (queue.context[0x31c] & ~0x1f7cu) | (12u << 2u) | (number << 8u) | (1u << 11u); + const auto address = reinterpret_cast(sliceMemory.data()); + queue.context[0x318] = static_cast(address >> 8u); + queue.context[0x390] = static_cast(address >> 40u); + queue.context[0x1b3] = queue.context[0x1b4] = 2; + auto state = DecodeState(queue); + const auto expected = number == 0 ? VK_FORMAT_R16G16B16A16_UNORM : number == 1 ? VK_FORMAT_R16G16B16A16_SNORM : number == 4 ? VK_FORMAT_R16G16B16A16_UINT : VK_FORMAT_R16G16B16A16_SFLOAT; + Require(state.color.format == expected && state.color.elementBytes == 8, "alternate 16-bit color format was not decoded"); + Require(state.color.componentMapping == 0xc6u && ExportMappings(state)[0] == 0xc6u, "alternate 16-bit color export did not swap red and blue"); + Require(DecodePixelStageInfo(queue.context, ExportMappings(state)).targetExportMapping[0] == 0xc6u, "pixel export lost the alternate color mapping"); + queue.context[0x8e] = 1; + state = DecodeState(queue); + Require(state.blend.colorWriteMask == VK_COLOR_COMPONENT_B_BIT, "alternate red-only write did not select the third storage component"); + queue.context[0x8e] = 0xf; + queue.context[0x1e0] = (1u << 30u) | 13u; + for (std::uint32_t i = 0; i < 4; ++i) queue.context[0x105 + i] = std::bit_cast(float(i + 1) / 8.0f); + state = DecodeState(queue); + Require(state.blendConstants == std::array{0.375f, 0.25f, 0.125f, 0.5f}, "alternate color blend constants were not swizzled with the export"); + queue.context[0x31c] &= ~(1u << 11u); + state = DecodeState(queue); + Require(state.color.componentMapping == 0xe4u && state.blendConstants == std::array{0.125f, 0.25f, 0.375f, 0.5f}, "standard color mapping changed"); + queue.context[0x31c] |= 2u << 11u; + expectFailure([&] { DecodeState(queue); }, "reversed component order"); + } + auto queue = makeState(); + queue.context[0x31c] = (queue.context[0x31c] & ~0x1f7cu) | (12u << 2u) | (7u << 8u) | (1u << 11u); + for (const auto offset : {0x318u, 0x31bu, 0x31cu, 0x31du}) queue.context[offset + 0xfu] = queue.context.at(offset); + for (const auto offset : {0x390u, 0x3b0u, 0x3b8u}) queue.context[offset + 1u] = queue.context.at(offset); + queue.context[0x31c + 0xfu] &= ~(1u << 11u); + queue.context[0x1c5] = 0x99u; + queue.context[0x8e] = queue.context[0x8f] = 0xffu; + queue.context[0x1e0] = queue.context[0x1e1] = (1u << 30u) | 13u; + for (std::uint32_t i = 0; i < 4; ++i) queue.context[0x105 + i] = std::bit_cast(float(i + 1) / 8.0f); + expectFailure([&] { DecodeState(queue); }, "conflicting blend constants"); + queue.context[0x107] = queue.context[0x105]; + Require(DecodeState(queue).colors.size() == 2, "compatible constants rejected mixed component orders"); +} + void stateTests() { AgcDriver::QueueState initial; Require(initial.context.at(0x200) == 0 && initial.context.at(0x83) == 0xffff, "initial context state is missing"); @@ -833,9 +875,6 @@ void ReversedComponentOrderTests() { const auto wide = AgcDriver::Graphics::DecodeState(queue); Require(wide.colors.size() == 1 && wide.colors[0].format == VK_FORMAT_R16G16B16A16_SFLOAT && wide.colors[0].componentMapping == mapping && wide.blends[0].colorWriteMask == 0xfu, "a 16_16_16_16 float target with a reversed component order did not map exports onto RGBA16"); } - auto queue = makeState(); - queue.context[0x31c] = (queue.context[0x31c] & ~((0x1fu << 2u) | (7u << 8u) | (3u << 11u))) | (12u << 2u) | (7u << 8u) | (1u << 11u); - expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "component swap 1"); } void CompactedExportTests() { @@ -4118,6 +4157,7 @@ int main() { } RunGuestLeaseWaitTests(); stateTests(); + hdrComponentSwapTests(); depthMaintenanceTests(); hardwareScreenOffsetTests(); srgb8TargetTests(); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index ac8f34836..1a85b45d3 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -489,6 +489,7 @@ Throughout the project, every function at every stage either **does exactly what - [8_SRGB color targets](../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver) on a device that cannot attach and blend R8_SRGB render into an RGBA8_SRGB copy of the target, so the sRGB encode, decode and blend rounding are the device's; the console's are not measured. - [8_SRGB color targets](../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) (libSceAgcDriver) on such a device throw unless they are single-mip 2D surfaces with a resident image; linear targets have none. - [COPY_DATA from the GPU clock](../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (libSceAgcDriver, `SRC_SEL` 9) writes the host steady clock in 10 ns ticks when the packet runs, the same counter as the `RELEASE_MEM` timestamp, not the console's GPU clock. The `COPY_DATA` cache policy fields are accepted and have no effect. +- [16-bit RGBA color targets](../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) support the standard and the alternate (`COMP_SWAP` 1) component order. Alternate swaps red and blue in shader exports, write masks and constant-color blend factors, following `ac_translate_colorswap` in [Mesa 26.2.2 ac_formats.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-26.2.2/src/amd/common/ac_formats.c). The reverse orders (`COMP_SWAP` 2 and 3) keep throwing when blending, and simultaneous targets requiring conflicting blend constants throw. Console equivalence has not been measured on hardware. - [UINT16_ABGR color exports](../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - export format 7 is accepted only into an unsigned integer target, and each 16-bit value reaches the target unchanged; a value beyond a narrower target's range (`R8_UINT`) is undefined in Vulkan, and whether the console clamps or truncates it is not confirmed. SINT16_ABGR (8) throws - `--to-intel` guest module trampolines are covered only by a synthetic relinker test; no game title has been verified with them on Linux or Windows. - `--to-rosetta` lowers what Rosetta lacks on top of `--to-intel` ([matcher](../../core/relinker/codegen/src/x86/Amd64OnlyInstructionMatcher.cpp)). Rosetta runs AVX2, FMA, BMI1/2, F16C and MOVBE but reports none of them through CPUID, so code that checks CPUID takes its baseline path. RDSEED becomes RDRAND and CLWB a NOP. RDPID returns processor 0, because Rosetta's TSC_AUX is the constant 0xAAAAAAAB; the same value reaches RDTSCP's ECX, which is not converted. ADCX/ADOX fault under Rosetta and are not converted: compilers do not emit them, and code that checks CPUID sees no ADX. The conversions are covered by the converter tests; that the converted code runs under Rosetta was checked on an M5 Max only, outside this repository's CI.