Merge pull request #1139 from alfalfa-star/fix/agc-inert-pixel-stage

fix(agc): run draws whose pixel stage cannot run without its program
This commit is contained in:
Adrià 2026-10-08 10:20:09 +02:00 committed by GitHub
commit e705d430d6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 35 additions and 8 deletions

View File

@ -15,8 +15,9 @@ std::shared_ptr<DrawDecode> Driver::decodeDraw(const QueueState& queue, const Su
require((high & ~0xffu) == 0, "reserved graphics program address bits are set");
return (static_cast<std::uint64_t>(readRegister(queue.shader, base)) << 8u) | (static_cast<std::uint64_t>(high) << 40u);
};
const bool pixelSkipped = Graphics::PixelProgramSkipped(queue);
const auto prepare = [&](std::uint64_t address, std::uint8_t type, Stage stage, std::uint32_t rsrc2, std::uint32_t userDataBase) {
const bool nullPixel = address == 0 && stage == Stage::Fragment;
const bool nullPixel = stage == Stage::Fragment && (address == 0 || pixelSkipped);
if (nullPixel) address = NullPixelProgramAddress();
auto it = submission.shaders->upper_bound(address);
require(it != submission.shaders->begin(), "graphics program does not belong to a registered shader");
@ -89,7 +90,7 @@ std::shared_ptr<DrawDecode> Driver::decodeDraw(const QueueState& queue, const Su
} else {
append(0xc8, 2, Stage::Vertex, 0x8b, 0x8c, Role::Main);
}
const bool nullPixel = Graphics::PixelProgramUnset(queue);
const bool nullPixel = pixelSkipped;
if (nullPixel) {
const auto rejection = Graphics::NullPixelProgramRejection(queue);
require(rejection.empty(), rejection.c_str());

View File

@ -114,7 +114,7 @@ std::optional<ColorMetadataPass> DecodeColorMetadataPass(const QueueState& queue
// this precheck covers, evaluated without exceptions before the draw is decoded; empty when they
// pass (DecodeState still checks everything). A register a rule needs that is absent is no verdict.
std::string DrawRejection(const QueueState& queue, bool indexed);
bool PixelProgramUnset(const QueueState& queue);
bool PixelProgramSkipped(const QueueState& queue);
std::string NullPixelProgramRejection(const QueueState& queue);
// The recording facade of the draw decoders (design_cpu_final M8, step 8a): every register read

View File

@ -65,6 +65,7 @@ constexpr std::uint32_t DepthControlMask = ~0x007007f0u;
// EXEC_ON_HIER_FAIL / EXEC_ON_NOOP / EXEC_IF_OVERLAPPED (bits 9, 10, 17) only force the pixel shader
// to run, which it always does here.
constexpr std::uint32_t ShaderControlMask = ~(0x0000f870u | 0x00020600u);
constexpr std::uint32_t PixelStageRunsMask = 0x00020747u;
constexpr std::uint32_t AlphaToCoverageMask = ~0x0001ff00u;
constexpr std::uint32_t ScanModeMask = ~2u;
constexpr std::uint32_t ScanControlMask = ~0x06023fffu;
@ -806,7 +807,7 @@ std::string DrawRejection(const QueueState& queue, bool indexed) {
std::uint32_t targetMask = 0, shaderMask = 0;
if (value(cx, 0x8e, targetMask) && value(cx, 0x8f, shaderMask) && value(cx, 0x202, word) && !colorControlSupported(word, (targetMask & shaderMask) != 0)) return colorControlMessage(word);
if (auto reason = nonzero(cx, 0x1c4, zFormatSupported(zFormat) ? 0u : ~0u, "depth or sample-mask export"); !reason.empty()) return reason;
if (PixelProgramUnset(queue)) return NullPixelProgramRejection(queue);
if (PixelProgramSkipped(queue)) return NullPixelProgramRejection(queue);
for (const auto offset : {0x1b3u, 0x1b4u, 0x1c5u}) {
if (find(cx, offset) != cx.end()) continue;
char text[64];
@ -816,10 +817,17 @@ std::string DrawRejection(const QueueState& queue, bool indexed) {
return {};
}
bool PixelProgramUnset(const QueueState& queue) {
bool PixelProgramSkipped(const QueueState& queue) {
const auto low = find(queue.shader, 0x008, RegisterBank::Shader);
const auto high = find(queue.shader, 0x009, RegisterBank::Shader);
return low != queue.shader.end() && high != queue.shader.end() && low->second == 0 && high->second == 0;
if (low != queue.shader.end() && high != queue.shader.end() && low->second == 0 && high->second == 0) return true;
const auto& cx = queue.context;
const auto targetMask = find(cx, 0x8e);
const auto shaderMask = find(cx, 0x8f);
const auto zFormat = find(cx, 0x1c4);
const auto shaderControl = find(cx, 0x203);
if (targetMask == cx.end() || shaderMask == cx.end() || zFormat == cx.end() || shaderControl == cx.end()) return false;
return (targetMask->second & shaderMask->second) == 0 && zFormat->second == 0 && (shaderControl->second & PixelStageRunsMask) == 0;
}
std::string NullPixelProgramRejection(const QueueState& queue) {

View File

@ -207,11 +207,11 @@ void stateTests() {
queue.context.erase(0x1c5);
queue.shader[0x008] = 0x100;
queue.shader[0x009] = 0;
Require(!AgcDriver::Graphics::PixelProgramUnset(queue), "a pixel program address was read as unset");
Require(!AgcDriver::Graphics::PixelProgramSkipped(queue), "a pixel program address was read as unset");
Require(AgcDriver::Graphics::DrawRejection(queue, true).find("missing register at DWORD 0x1b3") != std::string::npos, "a real pixel program without SPI_PS_INPUT_ENA was accepted");
expectFailure([&] { AgcDriver::Graphics::DecodePixelStageInfo(queue.context, std::array<std::uint8_t, 8>{0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u}); }, "missing register");
queue.shader[0x008] = 0;
Require(AgcDriver::Graphics::PixelProgramUnset(queue), "a zero pixel program address was not read as unset");
Require(AgcDriver::Graphics::PixelProgramSkipped(queue), "a zero pixel program address was not read as unset");
Require(AgcDriver::Graphics::DrawRejection(queue, true).find("writes color") != std::string::npos, "a draw without a pixel program that writes color was accepted");
queue.context[0x8e] = 0;
Require(AgcDriver::Graphics::DrawRejection(queue, true).empty(), "a depth-only draw without a pixel program was rejected");
@ -220,6 +220,24 @@ void stateTests() {
const auto unset = AgcDriver::Graphics::DecodePixelStageInfo(queue.context, AgcDriver::Graphics::ExportMappings(state), true);
Require(unset.inputAddr == ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PerspectiveCenter), "unset pixel inputs of the null program did not read as PERSP_CENTER_ENA");
for (const auto mode : unset.targetOutputMode) Require(mode == 0, "an unset SPI_SHADER_COL_FORMAT exported a color");
queue.shader[0x008] = 0x100;
queue.context[0x1c4] = 0;
queue.context[0x203] = 0;
Require(AgcDriver::Graphics::PixelProgramSkipped(queue), "a pixel program that writes nothing and cannot run was not skipped");
Require(AgcDriver::Graphics::DrawRejection(queue, true).empty(), "a depth-only draw whose pixel program cannot run was rejected");
for (const auto [control, what] : {std::pair{0x400u, "EXEC_ON_NOOP"}, std::pair{0x40u, "KILL_ENABLE"}}) {
queue.context[0x203] = control;
Require(!AgcDriver::Graphics::PixelProgramSkipped(queue), std::string("a pixel program with ") + what + " was skipped");
Require(AgcDriver::Graphics::DrawRejection(queue, true).find("missing register at DWORD 0x1b3") != std::string::npos, std::string("a pixel program with ") + what + " ran without SPI_PS_INPUT_ENA");
}
queue.context[0x203] = 1;
Require(!AgcDriver::Graphics::PixelProgramSkipped(queue), "a pixel program with Z_EXPORT_ENABLE was skipped");
queue.context[0x203] = 0;
queue.context[0x1c4] = 1;
Require(!AgcDriver::Graphics::PixelProgramSkipped(queue), "a pixel program exporting depth was skipped");
queue.context[0x1c4] = 0;
queue.context[0x8e] = 0xf;
Require(!AgcDriver::Graphics::PixelProgramSkipped(queue), "a pixel program writing color was skipped");
}
VkFormatFeatureFlags srgb8Features = 0;