fix(agc): start the sample location registers at their clear-state value

A draw with PA_SC_AA_CONFIG.MSAA_NUM_SAMPLES > 0 whose context never
wrote PA_SC_AA_SAMPLE_LOCS_PIXEL_* (context 0x2fe-0x30d) was rejected
with "missing register in context bank at DWORD 0x2fe", because the
registers were absent from the initial context (PPSA07411 stops on it
after two frames). The MSAA_ENABLE single-sample path read them the
same way.

The sixteen registers now start at 0, both initially and after
CLEAR_STATE. That is the value in libSceAgc's own register defaults
(sceAgcGetRegisterDefaults2, every public table that holds context
registers, the tables the initial context already follows for
PA_SU_VTX_CNTL and PA_SC_CONSERVATIVE_RASTERIZATION_CNTL), in amdgpu's
GFX10 clear-state table (clearstate_gfx10.h, gfx10_SECT_CONTEXT_def_8,
loaded by gfx_v10_0_cp_gfx_start before CLEAR_STATE) and the GC 10.3
register reset value (gc_10_3_0_default.h). RADV, radeonsi and PAL
always write the locations themselves and do not settle the unwritten
value.

0 puts every sample at the pixel center. That is not the standard
pattern for any sample count, so such a draw decodes as custom
locations at the center, as a single-sample scan into a multisampled
target already does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
fdfawef324 2026-10-13 01:53:11 -04:00
parent 18c438b109
commit 15955c089e
2 changed files with 54 additions and 0 deletions

View File

@ -240,6 +240,7 @@ inline Registers BuildInitialContextRegisters() {
result.emplace(0x31c + 0xfu * i, 0);
}
for (std::uint32_t i = 0; i < 16; ++i) {
result.emplace(0x2fe + i, 0);
result.emplace(0x94 + 2 * i, 0x80000000);
result.emplace(0x95 + 2 * i, 0x40004000);
result.emplace(0xb4 + 2 * i, 0);

View File

@ -452,6 +452,58 @@ void hardwareScreenOffsetTests() {
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "missing register");
}
void UnwrittenSampleLocationTests() {
using AgcDriver::Graphics::DecodeState;
using AgcDriver::Graphics::DrawRejection;
using AgcDriver::Graphics::SampleLocations;
const auto fresh = [](std::uint32_t config, std::uint32_t attributes, std::uint32_t scanMode = 3) {
auto written = makeState();
written.context[0x2f8] = config;
written.context[0x292] = scanMode;
written.context[0x3b8] = 0x0dc00000u;
written.context[0x31d] = attributes;
written.context[0x1b3] = 2;
written.context[0x1b4] = 2;
AgcDriver::QueueState queue;
queue.shader = written.shader;
queue.userConfig = written.userConfig;
written.context.ForEach([&](std::uint32_t offset, std::uint32_t value) { queue.context[offset] = value; });
return queue;
};
AgcDriver::QueueState initial;
for (std::uint32_t offset = 0x2fe; offset <= 0x30d; ++offset) Require(initial.context.contains(offset) && initial.context.at(offset) == 0, "an unwritten PA_SC_AA_SAMPLE_LOCS register did not hold its clear-state value 0");
for (const auto samples : {2u, 4u, 8u}) {
const auto log2 = static_cast<std::uint32_t>(std::countr_zero(samples));
auto queue = fresh((log2 << 20u) | log2, (log2 << 12u) | (log2 << 15u));
const auto state = DecodeState(queue);
Require(state.samples == samples && state.colors.size() == 1 && state.colors[0].samples == samples, "a multisampled draw with unwritten sample locations did not decode its sample count");
Require(state.customSampleLocations, "unwritten sample locations did not decode as the clear-state pattern");
for (std::uint32_t sample = 0; sample < samples; ++sample) Require(state.sampleLocations[sample] == 0, "an unwritten sample location was not at the pixel center");
const auto centered = SampleLocations(state);
for (std::uint32_t sample = 0; sample < samples; ++sample) Require(centered[sample].x == 0.5f && centered[sample].y == 0.5f, "unwritten sample locations did not place every sample at the pixel center");
const auto rejection = DrawRejection(queue, false);
Require(rejection.empty(), "the precheck refused a multisampled draw with unwritten sample locations: " + rejection);
}
auto queue = fresh(0x00200002, 0x12000);
for (std::uint32_t pixel = 0; pixel < 4; ++pixel) queue.context[0x2fe + pixel * 4u] = 0x622ae6ae;
auto state = DecodeState(queue);
Require(state.samples == 4 && !state.customSampleLocations, "explicit standard 4x sample locations changed");
queue = fresh(0x00200002, 0x12000);
for (std::uint32_t pixel = 0; pixel < 4; ++pixel) queue.context[0x2fe + pixel * 4u] = 0x22eea66a;
state = DecodeState(queue);
Require(state.samples == 4 && state.customSampleLocations, "explicit custom 4x sample locations changed");
const auto custom = SampleLocations(state);
const std::array<VkSampleLocationEXT, 4> expected{{{0.125f, 0.875f}, {0.875f, 0.125f}, {0.375f, 0.375f}, {0.625f, 0.625f}}};
for (std::size_t sample = 0; sample < expected.size(); ++sample) Require(custom[sample].x == expected[sample].x && custom[sample].y == expected[sample].y, "explicit custom 4x sample coordinates changed");
queue.ClearContext();
for (std::uint32_t offset = 0x2fe; offset <= 0x30d; ++offset) Require(queue.context.at(offset) == 0, "CLEAR_STATE did not reset PA_SC_AA_SAMPLE_LOCS");
expectFailure([&] { DecodeState(fresh(0x00400004, 0x12000)); }, "more than eight samples");
expectFailure([&] { DecodeState(fresh(0x00100002, 0x12000)); }, "exposing fewer samples");
queue = fresh(0, 0, 3);
state = DecodeState(queue);
Require(state.samples == 1 && DrawRejection(queue, false).empty(), "MSAA_ENABLE on a single-sample target with unwritten sample locations was refused");
}
void MsaaEnableSingleSampleTests() {
using AgcDriver::Graphics::DecodeState;
using AgcDriver::Graphics::DrawRejection;
@ -5627,6 +5679,7 @@ int main(int argc, char** argv) {
MsaaEnableSingleSampleTests();
clipDistanceTests();
framebufferLayerTests();
UnwrittenSampleLocationTests();
ShaderStageTests();
NggVertexWorkgroupTests();
TuningFieldTests();