Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::uint8_t>(swap == 2 ? 0x1bu : swap == 3 ? 0x93u : 0xe4u);
const auto reversed = static_cast<std::uint8_t>(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};
Expand Down Expand Up @@ -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<float, 3> requiredBlendConstants{};
std::uint32_t requiredBlendMask = 0;
for (const auto& color : result.colors) {
const auto slot = color.slot;
const auto blend = read(cx, 0x1e0 + slot);
Expand Down Expand Up @@ -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");
Expand All @@ -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");
Expand Down
46 changes: 43 additions & 3 deletions core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::uintptr_t>(sliceMemory.data());
queue.context[0x318] = static_cast<std::uint32_t>(address >> 8u);
queue.context[0x390] = static_cast<std::uint32_t>(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<std::uint32_t>(float(i + 1) / 8.0f);
state = DecodeState(queue);
Require(state.blendConstants == std::array<float, 4>{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<float, 4>{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<std::uint32_t>(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");
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -4118,6 +4157,7 @@ int main() {
}
RunGuestLeaseWaitTests();
stateTests();
hdrComponentSwapTests();
depthMaintenanceTests();
hardwareScreenOffsetTests();
srgb8TargetTests();
Expand Down
1 change: 1 addition & 0 deletions docs/dev/TechnicalDebt.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading