mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-03 12:57:27 +00:00
fix(agc): rasterize into one-sample targets as one sample when MSAA_ENABLE is clear
On top of #5882, a draw with PA_SC_AA_CONFIG.MSAA_NUM_SAMPLES 2 (4x), MSAA_ENABLE clear, a one-sample color target and no depth target threw "a color target's sample count differs from the rasterizer's", which stops METAL GEAR SOLID: Peace Walker (PPSA18216) at its first presentation (draw C on #5268). Without MSAA_ENABLE every sample is at the pixel center, and a one-sample target keeps the coverage of sample 0, so the draw is now drawn as a one-sample draw. PS_ITER_SAMPLE, ANCILLARY or SAMPLE_COVERAGE pixel inputs and a sample-mask export throw on this path. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
f474fb7768
commit
bb877791ac
3 files changed
+99
No files matched your search
@@ -1134,6 +1134,12 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) {
|
||||
if (slotExport == 7 && ((read(cx, 0x31c + slot * 0xfu) >> 8u) & 7u) != 4u) throw std::runtime_error("AGC graphics: color export format 7 (UINT16_ABGR) into a target that is not unsigned integer is unsupported");
|
||||
color.exportIndex = index;
|
||||
color.uintExport = slotExport == 7;
|
||||
if (color.samples == 1 && result.samples > 1 && result.colors.empty() && !result.depth && (read(cx, 0x292) & 1u) == 0) {
|
||||
const auto sampleInputs = read(cx, 0x1b3) & read(cx, 0x1b4) & (ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::Ancillary) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::SampleCoverage));
|
||||
Require(!result.perSampleShading && sampleInputs == 0 && (read(cx, 0x203) & 0x100u) == 0, "a one-sample color target under a multisampled rasterizer without MSAA_ENABLE is unsupported with PS_ITER_SAMPLE, a sample index or coverage input, or a sample-mask export");
|
||||
result.samples = 1;
|
||||
result.customSampleLocations = false;
|
||||
}
|
||||
if (color.samples > 1 && result.samples == 1 && result.colors.empty() && !result.depth && !result.alphaToCoverage && (read(cx, 0x292) & 1u) == 0) {
|
||||
result.samples = color.samples;
|
||||
result.customSampleLocations = true;
|
||||
|
||||
@@ -655,6 +655,97 @@ void UnwrittenSampleLocationTests() {
|
||||
Require(state.samples == 1 && DrawRejection(queue, false).empty(), "MSAA_ENABLE on a single-sample target with unwritten sample locations was refused");
|
||||
}
|
||||
|
||||
void msaaEnableClearOneSampleTargetTests() {
|
||||
using AgcDriver::Graphics::DecodeState;
|
||||
using AgcDriver::Graphics::DrawRejection;
|
||||
const auto drawC = [](std::uint32_t config = 0x0020c002u) {
|
||||
auto queue = makeState();
|
||||
queue.context[0x2f8] = config;
|
||||
queue.context[0x292] = 2;
|
||||
for (std::uint32_t offset = 0x2fe; offset <= 0x30d; ++offset) queue.context[offset] = (offset - 0x2feu) % 4u == 0 ? 0x622ae6aeu : 0u;
|
||||
queue.context[0x31c] = 0x8028;
|
||||
queue.context[0x31d] = 0;
|
||||
queue.context[0x010] = 0x80000183;
|
||||
queue.context[0x200] = 0x710;
|
||||
queue.context[0x201] = 0x00010002;
|
||||
queue.context[0x1b3] = 2;
|
||||
queue.context[0x1b4] = 2;
|
||||
return queue;
|
||||
};
|
||||
const auto requireOneSample = [](const AgcDriver::QueueState& queue, const std::string& what) {
|
||||
const auto state = DecodeState(queue);
|
||||
Require(state.samples == 1 && state.colors.size() == 1 && state.colors[0].samples == 1 && !state.depth, what + " was not drawn as one sample");
|
||||
Require(!state.customSampleLocations && !state.sampleLocationGrid && !state.perSampleShading && state.sampleLocations == decltype(state.sampleLocations){}, what + " kept the multisampled rasterizer's sample state");
|
||||
const auto rejection = DrawRejection(queue, false);
|
||||
Require(rejection.empty(), "the precheck refused " + what + ": " + rejection);
|
||||
};
|
||||
auto queue = drawC();
|
||||
requireOneSample(queue, "PPSA18216's draw C (a one-sample color target under a 4x PA_SC_AA_CONFIG without MSAA_ENABLE)");
|
||||
auto reference = drawC(0);
|
||||
const auto state = DecodeState(queue);
|
||||
const auto expected = DecodeState(reference);
|
||||
Require(state.samples == expected.samples && state.customSampleLocations == expected.customSampleLocations && state.sampleLocations == expected.sampleLocations && state.perSampleShading == expected.perSampleShading && state.colors[0].format == expected.colors[0].format, "draw C decoded differently from the same draw with a one-sample PA_SC_AA_CONFIG");
|
||||
for (const auto config : {0x00100001u, 0x00306003u}) requireOneSample(drawC(config), "a one-sample color target under a 2x or 8x PA_SC_AA_CONFIG without MSAA_ENABLE");
|
||||
queue = drawC();
|
||||
queue.context[0x302] = 0xae62e62a;
|
||||
requireOneSample(queue, "a one-sample color target under a 4x pattern that varies between pixels");
|
||||
for (const auto input : {ShaderRecompiler::PixelInput::PerspectiveSample, ShaderRecompiler::PixelInput::PerspectiveCentroid, ShaderRecompiler::PixelInput::LinearSample, ShaderRecompiler::PixelInput::LinearCentroid}) {
|
||||
queue = drawC();
|
||||
queue.context[0x1b3] = queue.context[0x1b4] = 2u | ShaderRecompiler::PixelInputBit(input);
|
||||
requireOneSample(queue, "draw C with sample or centroid inputs");
|
||||
}
|
||||
queue = drawC();
|
||||
queue.context[0x1b3] |= ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::Ancillary);
|
||||
requireOneSample(queue, "draw C with an enabled but unaddressed ANCILLARY input");
|
||||
constexpr std::string_view refused = "a one-sample color target under a multisampled rasterizer without MSAA_ENABLE is unsupported with PS_ITER_SAMPLE, a sample index or coverage input, or a sample-mask export";
|
||||
for (const auto input : {ShaderRecompiler::PixelInput::Ancillary, ShaderRecompiler::PixelInput::SampleCoverage}) {
|
||||
queue = drawC();
|
||||
queue.context[0x1b3] = queue.context[0x1b4] = 2u | ShaderRecompiler::PixelInputBit(input);
|
||||
expectFailure([&] { DecodeState(queue); }, refused);
|
||||
}
|
||||
queue = drawC();
|
||||
queue.context[0x293] = 0x10000u;
|
||||
expectFailure([&] { DecodeState(queue); }, refused);
|
||||
queue = drawC();
|
||||
queue.context[0x1c4] = 9;
|
||||
queue.context[0x203] |= 0x100u;
|
||||
expectFailure([&] { DecodeState(queue); }, refused);
|
||||
queue = drawC();
|
||||
queue.context[0x2dc] = 0x0000aa01u;
|
||||
expectFailure([&] { DecodeState(queue); }, "without MSAA_ENABLE are unsupported for points, lines");
|
||||
queue = drawC();
|
||||
queue.userConfig[0x242] = 2;
|
||||
expectFailure([&] { DecodeState(queue); }, "without MSAA_ENABLE are unsupported for points, lines");
|
||||
const auto twoTargets = [&](std::uint32_t first, std::uint32_t second) {
|
||||
auto pair = drawC();
|
||||
for (const auto offset : {0x318u, 0x31bu, 0x31cu}) pair.context[offset + 0xfu] = pair.context.at(offset);
|
||||
for (const auto offset : {0x390u, 0x3b0u, 0x3b8u}) pair.context[offset + 1u] = pair.context.at(offset);
|
||||
pair.context[0x31d] = first;
|
||||
pair.context[0x32c] = second;
|
||||
pair.context[0x1c5] = 0x99u;
|
||||
pair.context[0x8e] = pair.context[0x8f] = 0xffu;
|
||||
pair.context[0x1e1] = 0;
|
||||
return pair;
|
||||
};
|
||||
queue = twoTargets(0, 0);
|
||||
Require(DecodeState(queue).samples == 1 && DecodeState(queue).colors.size() == 2, "two one-sample color targets under a 4x PA_SC_AA_CONFIG without MSAA_ENABLE were not drawn as one sample");
|
||||
for (const auto [first, second] : {std::pair{0u, 0x12000u}, std::pair{0x12000u, 0u}}) {
|
||||
queue = twoTargets(first, second);
|
||||
expectFailure([&] { DecodeState(queue); }, "a color target's sample count differs from the rasterizer's");
|
||||
}
|
||||
queue = drawC();
|
||||
queue.context[0x293] = 0;
|
||||
queue.context[0x0] = 0;
|
||||
queue.context[0x002] = 0;
|
||||
queue.context[0x007] = (3u << 16u) | 63u;
|
||||
queue.context[0x00a] = 0;
|
||||
queue.context[0x00b] = 0;
|
||||
queue.context[0x011] = 0x20000180;
|
||||
for (const auto offset : {0x012u, 0x014u}) queue.context[offset] = 0x100;
|
||||
queue.context[0x200] = 0x16;
|
||||
expectFailure([&] { DecodeState(queue); }, "depth target");
|
||||
}
|
||||
|
||||
void MsaaEnableSingleSampleTests() {
|
||||
using AgcDriver::Graphics::DecodeState;
|
||||
using AgcDriver::Graphics::DrawRejection;
|
||||
@@ -7915,6 +8006,7 @@ int main(int argc, char** argv) {
|
||||
MsaaEnableSingleSampleTests();
|
||||
clipDistanceTests();
|
||||
framebufferLayerTests();
|
||||
msaaEnableClearOneSampleTargetTests();
|
||||
UnwrittenSampleLocationTests();
|
||||
quadPixelMaskTests();
|
||||
ShaderStageTests();
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
- [GS_FAST_LAUNCH geometry](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - `VGT_SHADER_STAGES_EN.GS_FAST_LAUNCH = 1` with a point list and `GE_CNTL` groups of one vertex and one primitive decodes as one point per subgroup, with the primitive limit taken from `GE_NGG_SUBGRP_CNTL.PRIM_AMP_FACTOR` and the GS/ES VGPR counts not required. Follows how RADV drives mesh shaders on gfx10.3 (`ac_nir_lower_ngg.c`, the workgroup index comes from the vertex id of the first thread), not measured on the console. Fast launch 2, other group sizes or input primitives and indexed fast-launch draws still throw. PPSA03671's fast-launch draws decode, but the title stops earlier in a wave64 vertex program, so they have not reached the GPU yet.
|
||||
- [Line-strip geometry output](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - a geometry program with `VGT_GS_OUT_PRIM_TYPE = 1` (line strip) runs as a mesh shader with `OutputLinesEXT`: each primitive export is one line whose two vertex indices are packed like the three of a triangle, as Mesa's NGG lowering exports line strips (`ac_nir_lower_ngg_gs.c`), and each input primitive gets `VGT_GS_MAX_VERT_OUT - 1` lines where a triangle strip gets `- 2` triangles. A subgroup gets at least `GE_MAX_OUTPUT_PER_SUBGROUP` line slots, because Mesa's lowering exports one primitive, possibly null, per emitted vertex; whether the console's compiler does the same is not known, and triangle strips keep `- 2` per input. A fast-launch subgroup takes its line count from `GE_NGG_SUBGRP_CNTL.PRIM_AMP_FACTOR` as for triangles. Line output counts as lines for the polygon offset decode, as on the vertex path. The lines are drawn 1 pixel wide with Vulkan's default line rasterization, like lines on the vertex path: `PA_SU_LINE_CNTL` is not read, and neither its width nor the console's line rules were measured. Points, rectangle lists, per-stream types and line strips from a vertex program on the mesh path still throw.
|
||||
- [Alpha-to-coverage](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - only `DB_ALPHA_TO_MASK = 0xaa01` is accepted: the enable bit with the four quad offsets at 2 and no `OFFSET_ROUND`, the undithered setting [radeonsi](https://gitlab.freedesktop.org/mesa/mesa/-/blob/bffd91947efc1932da99aca69fc540d6d4201b0c/src/gallium/drivers/radeonsi/si_state.c#L371-L381) programs. Draws are single-sampled, so the pixel shader [writes](../../../core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp) `SampleMask` 0 when its MRT0 alpha is below one half or NaN and all ones otherwise, and runs with late fragment tests. That rule was measured on a Radeon 660M (gfx1035, RADV with `RADV_DEBUG=noatocdithering`, which programs the same register) for 0, -0, 0.25, the two floats below one half, one half, the two above, 0.75, 1, 2, -1, both infinities and both NaNs, and is pinned in [AlphaToCoverage.cpp](../../../core/libs/prx/libSceAgcDriver/tests/execution/AlphaToCoverage.cpp); it is not measured on gfx1013. Vulkan's `alphaToCoverageEnable` is not used: the NVIDIA driver 615.78.08 dithers single-sample coverage (an alpha of 0.25 writes half the pixels of an RTX 3050 Laptop target). Other offsets, `OFFSET_ROUND`, a sample-mask export, and an MRT0 that is missing, swizzled, packed or exported without a floating-point alpha throw. A draw without a color target on MRT0 keeps alpha-to-coverage from its MRT0 export, as [radeonsi exports MRT0 as `32_AR` for alpha-to-coverage without a color buffer](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-24.0.0/src/gallium/drivers/radeonsi/si_state_shaders.cpp#L2425-L2431). A draw that enables alpha-to-coverage with no MRT0 export still throws: what the console covers then is not measured. Default dithered `DB_ALPHA_TO_MASK = 0x18701` additionally uses a per-quad sample-mask table [measured on gfx1036](https://github.com/boykopovar/AnyPS5/pull/4451#issuecomment-6124848921) for eight standard samples with FP16_ABGR or 32_ABGR exports; other sample counts, custom locations, EQAA combinations and dithered UNORM16/SNORM16 exports throw. The dithered rule is not measured on gfx1013.
|
||||
- [A one-sample target under a multisampled rasterizer without MSAA_ENABLE](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - `PA_SC_AA_CONFIG.MSAA_NUM_SAMPLES` > 0 with `PA_SC_MODE_CNTL_0.MSAA_ENABLE` clear, one-sample color targets and no depth target is drawn as a one-sample draw. Without the bit every sample is taken to be at the pixel center (see the entry for a multisampled target rasterized without MSAA_ENABLE below), and a one-sample target is taken to keep the coverage of sample 0. That is the rule [`VK_AMD_mixed_attachment_samples`](https://github.com/KhronosGroup/Vulkan-Docs/blob/193ebe195f47ffc5004683d1659e71c725658c4d/chapters/fragops.adoc#L2510-L2516) gives a color target with fewer samples than the rasterizer, but Vulkan reaches it only beside a depth or stencil target with the rasterizer's sample count ([VUID-VkGraphicsPipelineCreateInfo-subpass-01505](https://github.com/KhronosGroup/Vulkan-Docs/blob/193ebe195f47ffc5004683d1659e71c725658c4d/chapters/pipelines.adoc#L3033-L3041)), so here it is an analogy. [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp#L127-L128) also rasterizes with the largest sample count of the bound depth and color targets ([L416-L417](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp#L416-L417), [L477-L480](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp#L477-L480)), and reads `PA_SC_AA_CONFIG` only for a `SAMPLE_COVERAGE` input ([L174-L177](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp#L174-L177)). This is not measured on hardware. `PS_ITER_SAMPLE`, an `ANCILLARY` or `SAMPLE_COVERAGE` pixel input and a sample-mask export throw, because what the console gives them under a multisampled configuration is not known; points, lines and alpha-to-coverage throw as for a multisampled target. METAL GEAR SOLID: Peace Walker (PPSA18216) draws a one-sample target under a 4x configuration this way.
|
||||
- [MSAA_ENABLE on a single-sample target](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - `PA_SC_MODE_CNTL_0.MSAA_ENABLE` with `PA_SC_AA_CONFIG.MSAA_NUM_SAMPLES = 0` is drawn like the same state without the bit when sample 0 of the four `PA_SC_AA_SAMPLE_LOCS_PIXEL_*_0` registers is at the pixel center and the draw rasterizes filled triangles with alpha-to-coverage and conservative rasterization off. [RADV](https://gitlab.freedesktop.org/mesa/mesa/-/blob/745709a000482a4a24c91faef5c5a40bd653a863/src/amd/vulkan/radv_cmd_buffer.c#L13606-13609) programs this state for one-sample custom sample locations on GFX10 and later, where the bit makes the rasterizer use the programmed location of sample 0 ([061bc615](https://gitlab.freedesktop.org/mesa/mesa/-/commit/061bc6151a11597f720535f00e79a5752e24aff6)); that the bit changes nothing else for such a draw is inferred from that and is not measured on hardware. Other locations of sample 0, absent location registers, points, lines, rectangles, alpha-to-coverage and conservative rasterization throw
|
||||
- [A multisampled target rasterized without MSAA_ENABLE](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - `PA_SC_AA_CONFIG.MSAA_NUM_SAMPLES` > 0 with `PA_SC_MODE_CNTL_0.MSAA_ENABLE` clear is drawn with the target's sample count and every sample at the pixel center, as a single-sample scan into a multisampled target already is. On GFX10 and later the bit is what makes the rasterizer use the programmed sample locations ([RADV 061bc615](https://gitlab.freedesktop.org/mesa/mesa/-/commit/061bc6151a11597f720535f00e79a5752e24aff6)), and with multisampling disabled GL rasterizes at the pixel center and gives the fragment full coverage, which radeonsi programs by clearing the bit; that the hardware does the same with `MSAA_NUM_SAMPLES` > 0 is inferred from these and is not measured on hardware. Points, lines, a depth target and alpha-to-coverage throw. SONIC SUPERSTARS (PPSA06888) draws such a 2x target.
|
||||
- [sceAgcDriverGetEqContextId](../../../core/libs/prx/libSceAgcDriver/Eq/src/Query.cpp) (libSceAgcDriver) - returns `ident`: an end-of-pipe interrupt is delivered only to the registration whose id equals the raising queue, so `ident` is that context's id (titles register 0 or 1 for graphics and `0x20 + 8 * pipe + queue` for compute). The console's value is not known; KytyPS5 returns the event's `data`, which it fills with the interrupt's context id, whereas `data` here is the EV_CLEAR trigger count. Other event filters throw
|
||||
|
||||
Reference in new issue
Block a user