fix(agc): decode primitive type 3 as a line strip

DecodeState mapped points, lists, strips, fans, rect lists and patches
but threw "unsupported primitive type 3" for VGT_PRIMITIVE_TYPE 3, the
line strip (DI_PT_LINESTRIP in Mesa's r600d.h). Record of Lodoss War
(PPSA05347) stops on it after a few minutes.

Vulkan allows primitive restart for line strips without the list
restart extension, and the precheck, the pipeline and the provoking
vertex check already treat the strip class that way; only the decode
lacked the case. The point and line depth bias enable stays rejected on
line strips like on line lists, and a line strip gets the line primitive
class for a pixel shader that reads the ancillary word.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
xXPBSXx 2026-10-13 05:23:21 +02:00
parent c13dc7e6b4
commit 67e7a3029c
2 changed files with 25 additions and 3 deletions

View File

@ -677,6 +677,7 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) {
switch (primitive) {
case 1: Require(result.stages.mesh.has_value(), "point-list vertex rendering requires point-size output support"); result.topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; break;
case 2: result.topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST; break;
case 3: result.topology = VK_PRIMITIVE_TOPOLOGY_LINE_STRIP; break;
case 7:
case 17:
Require(result.stages.path == ShaderPath::Vertex, "rect-list requires vertex routing");
@ -751,7 +752,7 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) {
const auto raster = read(cx, 0x205);
// Bits 5-10 give the front/back polygon type (2 = filled triangles), which POLY_MODE (bit 3) turns on
// explicitly; KEEP_TOGETHER_ENABLE (bit 24) only affects primitive distribution across the chip.
const bool pointsOrLines = result.topology == VK_PRIMITIVE_TOPOLOGY_POINT_LIST || result.topology == VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
const bool pointsOrLines = result.topology == VK_PRIMITIVE_TOPOLOGY_POINT_LIST || result.topology == VK_PRIMITIVE_TOPOLOGY_LINE_LIST || result.topology == VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
const auto lineOffset = (raster & 0x1800u) != 0 && !pointsOrLines ? 0x2000u : 0u;
const auto rasterMode = raster & ~0x7u & ~(1u << 24u) & ~0x1800u & ~lineOffset & ~(1u << 19u) & ~(1u << 16u);
if (rasterMode != 0 && rasterMode != 0x240u && rasterMode != 0x248u) throw std::runtime_error("AGC graphics: " + zeroMessage(0x205, raster, "polygon mode, depth bias or nonstandard rasterization"));
@ -956,7 +957,8 @@ ShaderRecompiler::RasterPrimitive RasterPrimitiveOf(const State& state) {
if (state.stages.mesh.has_value() || state.stages.tessellation.has_value()) return ShaderRecompiler::RasterPrimitive::Triangle;
switch (state.topology) {
case VK_PRIMITIVE_TOPOLOGY_POINT_LIST: return ShaderRecompiler::RasterPrimitive::Point;
case VK_PRIMITIVE_TOPOLOGY_LINE_LIST: return ShaderRecompiler::RasterPrimitive::Line;
case VK_PRIMITIVE_TOPOLOGY_LINE_LIST:
case VK_PRIMITIVE_TOPOLOGY_LINE_STRIP: return ShaderRecompiler::RasterPrimitive::Line;
default: return ShaderRecompiler::RasterPrimitive::Triangle;
}
}

View File

@ -1578,6 +1578,8 @@ void DepthBoundsBiasTests() {
queue.context[0x80] = 0;
queue.userConfig[0x242] = 2;
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "polygon mode, depth bias or nonstandard rasterization");
queue.userConfig[0x242] = 3;
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "polygon mode, depth bias or nonstandard rasterization");
queue.userConfig[0x242] = 4;
queue.context[0x205] = 0x00001a4au;
queue.context[0x2de] = 0x1f0u;
@ -4124,6 +4126,23 @@ void aliasedBufferViewTests() {
}
}
void LineStripTests() {
using namespace AgcDriver::Graphics;
auto queue = makeState();
queue.context[0x1b3] = 2;
queue.context[0x1b4] = 2;
queue.userConfig[0x242] = 3;
auto state = DecodeState(queue);
Require(state.topology == VK_PRIMITIVE_TOPOLOGY_LINE_STRIP && !state.rectList && !state.primitiveRestart, "primitive type 3 did not decode as a line strip");
Require(RasterPrimitiveOf(state) == ShaderRecompiler::RasterPrimitive::Line, "a line strip draw did not get the line primitive class");
queue.userConfig[0x24b] = 1;
queue.context[0x103] = 0xffffffffu;
state = DecodeState(queue);
Require(state.topology == VK_PRIMITIVE_TOPOLOGY_LINE_STRIP && state.primitiveRestart, "primitive restart was lost on a line strip");
const auto rejection = DrawRejection(queue, true);
Require(rejection.empty(), "the draw precheck rejected an indexed line strip with primitive restart: " + rejection);
}
void ProvokingVertexTests() {
using namespace AgcDriver::Graphics;
constexpr std::uint32_t lastVertex = 1u << 19u;
@ -4132,7 +4151,7 @@ void ProvokingVertexTests() {
queue.context[0x1b4] = 2;
const auto first = DecodeState(queue);
Require(first.provokingVertexMode == VK_PROVOKING_VERTEX_MODE_FIRST_VERTEX_EXT, "the default provoking vertex is not first");
for (const auto primitive : {2u, 4u, 5u, 6u}) {
for (const auto primitive : {2u, 3u, 4u, 5u, 6u}) {
queue.userConfig[0x242] = primitive;
for (const auto last : {false, true, false}) {
queue.context[0x205] = 0x240u | (last ? lastVertex : 0u);
@ -5173,6 +5192,7 @@ int main(int argc, char** argv) {
orderedPixelShaderTests();
stencilRefExportTests();
aliasedBufferViewTests();
LineStripTests();
ProvokingVertexTests();
ProvokingVertexRenderPassTests();
ConservativeRasterizationTests();