mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-18 23:07:07 -05:00
Merge pull request #5884 from AnthonyClemens/fix/agc-geometry-line-strip-output
feat(agc): draw line-strip output of NGG geometry programs as mesh lines
This commit is contained in:
18 files changed
+140
-34
No files matched your search
@@ -22,7 +22,7 @@ void Driver::lookupDraw(const Submission& submission, const std::shared_ptr<Vulk
|
||||
mix(graphics.stages.mesh.has_value());
|
||||
if (graphics.stages.mesh) {
|
||||
const auto& mesh = *graphics.stages.mesh;
|
||||
for (const auto value : {mesh.inputPrimitive, mesh.primitivesPerGroup, mesh.verticesPerGroup, mesh.maxVertices, mesh.maxPrimitives, mesh.threadsPerGroup, mesh.ldsSizeDwords, mesh.provokingVertex, mesh.esgsItemSize}) mix(value);
|
||||
for (const auto value : {mesh.inputPrimitive, mesh.primitivesPerGroup, mesh.verticesPerGroup, mesh.maxVertices, mesh.maxPrimitives, mesh.threadsPerGroup, mesh.ldsSizeDwords, mesh.provokingVertex, mesh.esgsItemSize, mesh.outputPrimitiveSize}) mix(value);
|
||||
}
|
||||
mix(graphics.stages.tessellation.has_value());
|
||||
if (graphics.stages.tessellation) {
|
||||
|
||||
@@ -235,7 +235,7 @@ bool Driver::sameDecode(const DrawDecode& a, const DrawDecode& b) {
|
||||
const auto sameMesh = [](const std::optional<ShaderRecompiler::MeshConfiguration>& x, const std::optional<ShaderRecompiler::MeshConfiguration>& y) {
|
||||
if (x.has_value() != y.has_value()) return false;
|
||||
if (!x) return true;
|
||||
return x->inputPrimitive == y->inputPrimitive && x->primitivesPerGroup == y->primitivesPerGroup && x->verticesPerGroup == y->verticesPerGroup && x->maxVertices == y->maxVertices && x->maxPrimitives == y->maxPrimitives && x->threadsPerGroup == y->threadsPerGroup && x->ldsSizeDwords == y->ldsSizeDwords && x->provokingVertex == y->provokingVertex && x->esgsItemSize == y->esgsItemSize;
|
||||
return x->inputPrimitive == y->inputPrimitive && x->primitivesPerGroup == y->primitivesPerGroup && x->verticesPerGroup == y->verticesPerGroup && x->maxVertices == y->maxVertices && x->maxPrimitives == y->maxPrimitives && x->threadsPerGroup == y->threadsPerGroup && x->ldsSizeDwords == y->ldsSizeDwords && x->provokingVertex == y->provokingVertex && x->esgsItemSize == y->esgsItemSize && x->outputPrimitiveSize == y->outputPrimitiveSize;
|
||||
};
|
||||
const auto sameTess = [](const std::optional<ShaderRecompiler::TessellationConfiguration>& x, const std::optional<ShaderRecompiler::TessellationConfiguration>& y) {
|
||||
if (x.has_value() != y.has_value()) return false;
|
||||
|
||||
@@ -658,6 +658,7 @@ bool ValidationKey(const Context& context, std::span<const CompiledShader> shade
|
||||
add(mesh.threadsPerGroup);
|
||||
add(mesh.ldsSizeDwords);
|
||||
add(mesh.provokingVertex);
|
||||
add(mesh.outputPrimitiveSize);
|
||||
}
|
||||
add(state.stages.tessellation.has_value());
|
||||
if (state.stages.tessellation) {
|
||||
|
||||
@@ -884,6 +884,7 @@ void pipelineKey(std::vector<std::byte>& key, const Context& context, const Stat
|
||||
append(key, mesh.threadsPerGroup);
|
||||
append(key, mesh.ldsSizeDwords);
|
||||
append(key, mesh.provokingVertex);
|
||||
append(key, mesh.outputPrimitiveSize);
|
||||
}
|
||||
append(key, state.stages.tessellation.has_value());
|
||||
if (state.stages.tessellation) {
|
||||
|
||||
@@ -47,7 +47,7 @@ struct Module {
|
||||
bool position = false;
|
||||
std::uint32_t clipDistances = 0;
|
||||
std::uint32_t cullDistances = 0;
|
||||
bool primitiveIndices = false;
|
||||
std::uint32_t primitiveIndices = 0;
|
||||
bool fragmentBarycentric = false;
|
||||
bool layerOutput = false;
|
||||
std::map<std::uint32_t, std::vector<std::uint32_t>> modes;
|
||||
@@ -134,8 +134,9 @@ struct Module {
|
||||
Require(input && ((value == spv::BuiltInPosition && signature == "f32x4") || (value == spv::BuiltInTessCoord && stage == Stage::TessellationEvaluation && signature == "f32x3") || ((value == spv::BuiltInInvocationId || value == spv::BuiltInPrimitiveId || value == spv::BuiltInPatchVertices) && (signature == "i32" || signature == "u32"))), "unsupported tessellation built-in");
|
||||
return;
|
||||
}
|
||||
if (!input && value == spv::BuiltInPrimitiveTriangleIndicesEXT) primitiveIndices = true;
|
||||
Require((input && ((value == spv::BuiltInWorkgroupId || value == spv::BuiltInLocalInvocationId || value == spv::BuiltInGlobalInvocationId || value == spv::BuiltInNumWorkgroups) && signature == "u32x3")) || (input && value == spv::BuiltInLocalInvocationIndex && signature == "u32") || (!input && value == spv::BuiltInPrimitiveTriangleIndicesEXT && signature == "u32x3") || (!input && value == spv::BuiltInCullPrimitiveEXT && signature == "bool") || (!input && value == spv::BuiltInLayer && signature == "u32"), "unsupported mesh built-in");
|
||||
if (!input && value == spv::BuiltInPrimitiveLineIndicesEXT) primitiveIndices = 2u;
|
||||
if (!input && value == spv::BuiltInPrimitiveTriangleIndicesEXT) primitiveIndices = 3u;
|
||||
Require((input && ((value == spv::BuiltInWorkgroupId || value == spv::BuiltInLocalInvocationId || value == spv::BuiltInGlobalInvocationId || value == spv::BuiltInNumWorkgroups) && signature == "u32x3")) || (input && value == spv::BuiltInLocalInvocationIndex && signature == "u32") || (!input && value == spv::BuiltInPrimitiveLineIndicesEXT && signature == "u32x2") || (!input && value == spv::BuiltInPrimitiveTriangleIndicesEXT && signature == "u32x3") || (!input && value == spv::BuiltInCullPrimitiveEXT && signature == "bool") || (!input && value == spv::BuiltInLayer && signature == "u32"), "unsupported mesh built-in");
|
||||
return;
|
||||
}
|
||||
const auto signature = Signature(type);
|
||||
@@ -437,7 +438,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
|
||||
mode(spv::ExecutionModeLocalSize, {paired ? config.threadsPerGroup / 2u : config.threadsPerGroup, 1, 1});
|
||||
mode(spv::ExecutionModeOutputVertices, {config.maxVertices});
|
||||
mode(spv::ExecutionModeOutputPrimitivesEXT, {config.maxPrimitives});
|
||||
mode(spv::ExecutionModeOutputTrianglesEXT, {});
|
||||
mode(config.outputPrimitiveSize == 2u ? spv::ExecutionModeOutputLinesEXT : spv::ExecutionModeOutputTrianglesEXT, {});
|
||||
Require(module.modes.size() == 4, "unsupported mesh execution mode");
|
||||
} else if (control) {
|
||||
Require(state.rectList || state.stages.tessellation.has_value(), "tessellation configuration is missing");
|
||||
@@ -486,7 +487,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
|
||||
const auto length = module.constants.find(outer[3]);
|
||||
Require(length != module.constants.end() && length->second != 0, "invalid per-vertex interface array length");
|
||||
if (mesh) {
|
||||
const bool primitive = decoration.perPrimitive || (decoration.builtin && (*decoration.builtin == spv::BuiltInPrimitiveTriangleIndicesEXT || *decoration.builtin == spv::BuiltInCullPrimitiveEXT));
|
||||
const bool primitive = decoration.perPrimitive || (decoration.builtin && (*decoration.builtin == spv::BuiltInPrimitiveLineIndicesEXT || *decoration.builtin == spv::BuiltInPrimitiveTriangleIndicesEXT || *decoration.builtin == spv::BuiltInCullPrimitiveEXT));
|
||||
Require(length->second == (primitive ? state.stages.mesh->maxPrimitives : state.stages.mesh->maxVertices), "mesh interface array disagrees with output limits");
|
||||
} else if (geometry) {
|
||||
Require(length->second == 3u, "geometry interface array must hold the three vertices of a triangle");
|
||||
@@ -594,7 +595,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
|
||||
Require(descriptors.size() == shader.bindings.size(), "recompiler binding metadata contains undeclared resources");
|
||||
Require(push == (!shader.pushConstants.empty() || mesh || (control && !rectListControl)), "recompiler push constant metadata disagrees with SPIR-V");
|
||||
for (const auto id : module.interface) Require(module.variables.contains(id), "entry point interface contains an unknown variable");
|
||||
if (mesh) Require(module.primitiveIndices, "mesh shader does not export primitive indices");
|
||||
if (mesh) Require(module.primitiveIndices == state.stages.mesh->outputPrimitiveSize, "mesh shader does not export primitive indices");
|
||||
if (stage == Stage::Vertex || mesh || evaluation || geometry) Require(module.position, "vertex shader does not export position");
|
||||
return module;
|
||||
}
|
||||
|
||||
@@ -876,7 +876,10 @@ ShaderStages DecodeShaderStages(const QueueState& queue, bool vertexWorkgroup) {
|
||||
validate(!fastLaunch || (!vertexWorkgroup && primitive == 1 && vertices == 1 && primitives == 1), "fast launch other than one point per subgroup is unsupported");
|
||||
const auto maxVertices = read(queue.context, 0x1ff);
|
||||
const auto verticesPerPrimitive = vertexWorkgroup ? 3u : read(queue.context, 0x2ce);
|
||||
validate((primitive == 1 || primitive == 2 || primitive == 4 || primitive == 5 || primitive == 6) && read(queue.context, 0x29b) == 2 && verticesPerPrimitive >= 3, "unsupported geometry input or output assembly");
|
||||
const auto outputType = read(queue.context, 0x29b);
|
||||
validate(outputType == 2 || (outputType == 1 && !vertexWorkgroup), "VGT_GS_OUT_PRIM_TYPE other than a triangle strip, or a line strip from a geometry program, is unsupported");
|
||||
const auto outputSize = outputType == 1 ? 2u : 3u;
|
||||
validate((primitive == 1 || primitive == 2 || primitive == 4 || primitive == 5 || primitive == 6) && verticesPerPrimitive >= outputSize, "unsupported geometry input or output assembly");
|
||||
const auto inputSize = primitive == 1 ? 1u : primitive == 2 ? 2u : 3u;
|
||||
validate(vertices >= inputSize && maxVertices != 0 && (!vertexWorkgroup || maxVertices >= inputSize) && maxVertices <= 256 && verticesPerPrimitive <= 256, "invalid geometry subgroup output");
|
||||
const auto inputStep = primitive == 5 || primitive == 6 ? 1u : inputSize;
|
||||
@@ -886,11 +889,11 @@ ShaderStages DecodeShaderStages(const QueueState& queue, bool vertexWorkgroup) {
|
||||
const auto resources = read(queue.shader, 0x8b, RegisterBank::Shader);
|
||||
const auto esgsItemSize = read(queue.context, 0x2ab);
|
||||
validate(esgsItemSize != 0 && esgsItemSize * vertices <= 0xffffu, "invalid VGT_ESGS_RING_ITEMSIZE");
|
||||
const auto amplification = fastLaunch ? read(queue.context, 0x2d3) & 0x1ffu : verticesPerPrimitive - 2u;
|
||||
const auto amplification = fastLaunch ? read(queue.context, 0x2d3) & 0x1ffu : verticesPerPrimitive - outputSize + 1u;
|
||||
validate(amplification != 0, "fast launch subgroup amplifies to no primitives");
|
||||
const auto maxPrimitives = primitives * amplification;
|
||||
const auto maxPrimitives = outputSize == 2u && !fastLaunch ? std::max(primitives * amplification, maxVertices) : primitives * amplification;
|
||||
const auto threads = std::max({(groupPrimitives - 1u) * inputStep + inputSize, primitives, maxVertices, maxPrimitives});
|
||||
result.mesh = ShaderRecompiler::MeshConfiguration{primitive, groupPrimitives, (groupPrimitives - 1u) * inputStep + inputSize, maxVertices, maxPrimitives, ((threads + result.vertexWaveSize - 1u) / result.vertexWaveSize) * result.vertexWaveSize, ((resources >> 19u) & 0xffu) * 128u, 0, esgsItemSize};
|
||||
result.mesh = ShaderRecompiler::MeshConfiguration{primitive, groupPrimitives, (groupPrimitives - 1u) * inputStep + inputSize, maxVertices, maxPrimitives, ((threads + result.vertexWaveSize - 1u) / result.vertexWaveSize) * result.vertexWaveSize, ((resources >> 19u) & 0xffu) * 128u, 0, esgsItemSize, outputSize};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -1060,7 +1063,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 || result.topology == VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
|
||||
const bool pointsOrLines = result.topology == VK_PRIMITIVE_TOPOLOGY_POINT_LIST || result.topology == VK_PRIMITIVE_TOPOLOGY_LINE_LIST || result.topology == VK_PRIMITIVE_TOPOLOGY_LINE_STRIP || (result.stages.mesh.has_value() && result.stages.mesh->outputPrimitiveSize == 2u);
|
||||
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"));
|
||||
@@ -1326,6 +1329,7 @@ std::array<std::uint16_t, 4> QuadSampleMasks(const State& state) {
|
||||
|
||||
ShaderRecompiler::RasterPrimitive RasterPrimitiveOf(const State& state) {
|
||||
if (state.rectList) return ShaderRecompiler::RasterPrimitive::Rectangle;
|
||||
if (state.stages.mesh.has_value() && state.stages.mesh->outputPrimitiveSize == 2u) return ShaderRecompiler::RasterPrimitive::Line;
|
||||
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;
|
||||
|
||||
@@ -819,6 +819,10 @@ void ShaderStageTests() {
|
||||
Require(state.stages.path == AgcDriver::Graphics::ShaderPath::Geometry && state.stages.vertexWaveSize == 32 && state.stages.mesh, "a fast-launch draw did not decode as wave32 geometry");
|
||||
const auto& mesh = *state.stages.mesh;
|
||||
Require(mesh.inputPrimitive == 1 && mesh.primitivesPerGroup == 1 && mesh.verticesPerGroup == 1 && mesh.maxVertices == 32 && mesh.maxPrimitives == 64 && mesh.threadsPerGroup == 64 && mesh.ldsSizeDwords == 128, "a fast-launch subgroup did not decode as one point amplified by PRIM_AMP_FACTOR");
|
||||
fast.context[0x29b] = 1;
|
||||
const auto fastLines = AgcDriver::Graphics::DecodeState(fast);
|
||||
Require(fastLines.stages.mesh->outputPrimitiveSize == 2 && fastLines.stages.mesh->maxPrimitives == 64 && AgcDriver::Graphics::RasterPrimitiveOf(fastLines) == ShaderRecompiler::RasterPrimitive::Line, "a fast-launch line strip did not take its line count from PRIM_AMP_FACTOR");
|
||||
fast.context[0x29b] = 2;
|
||||
fast.context[0x2d5] = 0x502030;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(fast); }, "unsupported geometry routing, fast launch or wave-ID state");
|
||||
fast.context[0x2d5] = 0x482030;
|
||||
@@ -831,6 +835,40 @@ void ShaderStageTests() {
|
||||
fast.context[0x2d3] = 0;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(fast); }, "amplifies to no primitives");
|
||||
}
|
||||
{
|
||||
auto lines = makeState();
|
||||
lines.userConfig[0x242] = 1;
|
||||
lines.context[0x2d5] = 0x2030;
|
||||
lines.userConfig[0x25b] = 0x6633;
|
||||
lines.context[0x1ff] = 255;
|
||||
lines.context[0x2ce] = 5;
|
||||
lines.context[0x29b] = 1;
|
||||
lines.context[0x2ab] = 4;
|
||||
lines.shader[0x8a] = 3u << 29u;
|
||||
lines.shader[0x8b] = 3u << 16u;
|
||||
auto state = AgcDriver::Graphics::DecodeState(lines);
|
||||
Require(state.stages.path == AgcDriver::Graphics::ShaderPath::Geometry && state.stages.mesh && AgcDriver::Graphics::RasterPrimitiveOf(state) == ShaderRecompiler::RasterPrimitive::Line, "a geometry program that outputs line strips did not decode as geometry rasterized as lines");
|
||||
auto mesh = *state.stages.mesh;
|
||||
Require(mesh.inputPrimitive == 1 && mesh.primitivesPerGroup == 51 && mesh.verticesPerGroup == 51 && mesh.maxVertices == 255 && mesh.maxPrimitives == 255 && mesh.threadsPerGroup == 256 && mesh.outputPrimitiveSize == 2, "a line-strip subgroup did not decode as 51 points with a line slot per output vertex");
|
||||
lines.context[0x29b] = 2;
|
||||
state = AgcDriver::Graphics::DecodeState(lines);
|
||||
mesh = *state.stages.mesh;
|
||||
Require(mesh.maxPrimitives == 153 && mesh.threadsPerGroup == 256 && mesh.outputPrimitiveSize == 3 && AgcDriver::Graphics::RasterPrimitiveOf(state) == ShaderRecompiler::RasterPrimitive::Triangle, "the triangle-strip subgroup of the same draw changed");
|
||||
lines.context[0x29b] = 1;
|
||||
lines.context[0x2ce] = 1;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(lines); }, "unsupported geometry input or output assembly");
|
||||
lines.context[0x2ce] = 5;
|
||||
for (const auto output : {0u, 3u, 0x80000001u, 0x101u}) {
|
||||
lines.context[0x29b] = output;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(lines); }, "VGT_GS_OUT_PRIM_TYPE other than a triangle strip, or a line strip from a geometry program, is unsupported");
|
||||
}
|
||||
lines.userConfig[0x242] = 4;
|
||||
lines.context[0x205] = 0x2a40;
|
||||
lines.context[0x29b] = 2;
|
||||
Require(AgcDriver::Graphics::DecodeState(lines).stages.mesh->primitivesPerGroup == 17, "triangle input with a triangle-strip output and a triangle polygon offset did not decode");
|
||||
lines.context[0x29b] = 1;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(lines); }, "polygon mode, depth bias or nonstandard rasterization");
|
||||
}
|
||||
queue.context[0x2ab] = 0;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "invalid VGT_ESGS_RING_ITEMSIZE");
|
||||
queue.context[0x2ab] = 4;
|
||||
@@ -950,6 +988,9 @@ void NggVertexWorkgroupTests() {
|
||||
queue.context[0x2d5] = stages;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue, true); }, "requires PRIMGEN_EN without GS_EN, PRIMGEN_PASSTHRU_EN or tessellation");
|
||||
}
|
||||
queue.context[0x2d5] = 0x12010;
|
||||
queue.context[0x29b] = 1;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue, true); }, "VGT_GS_OUT_PRIM_TYPE other than a triangle strip, or a line strip from a geometry program, is unsupported");
|
||||
}
|
||||
|
||||
constexpr std::array<std::uint8_t, 8> IdentityExports{0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u};
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <new>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
@@ -41,6 +42,13 @@ alignas(256) constexpr std::array<std::uint32_t, 25> FastLaunchCode{
|
||||
0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 27> LineStripCode{
|
||||
0xd7650000, 0x000100c1, 0x7e000505, 0x93018300, 0x4a020001, 0xe0382000, 0x80020201, 0xe0382010,
|
||||
0x80020601, 0xb07c2003, 0xbf900009, 0xbf8c3f70, 0x7d880083, 0xbefe036a, 0xf80008cf, 0x05040302,
|
||||
0xf800020f, 0x09080706, 0x7d880082, 0xbefe036a, 0x161400ff, 0x00000401, 0x4a1414ff, 0x00000400,
|
||||
0xf8000941, 0x0000000a, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 7> PixelCode{
|
||||
0xc8020002, 0xc8060102, 0xc80a0202, 0xc80e0302, 0xf800180f, 0x03020100, 0xbf810000,
|
||||
};
|
||||
@@ -85,6 +93,13 @@ alignas(256) std::array<std::uint16_t, 3 * Triangles> Indices{};
|
||||
alignas(256) std::array<Vertex, 3 * Triangles> Ordered{};
|
||||
alignas(256) std::array<Vertex, 2 * Columns + 2> Strip{};
|
||||
|
||||
constexpr std::uint32_t LineStrips = 4;
|
||||
alignas(256) std::array<Vertex, 3 * LineStrips> LineStripVertices{};
|
||||
|
||||
std::uint32_t LineStripRow(std::uint32_t strip) {
|
||||
return 16u + 28u * strip;
|
||||
}
|
||||
|
||||
constexpr std::uint32_t FanRim = 8;
|
||||
constexpr float FanStep = 6.28318530718f / FanRim;
|
||||
alignas(256) std::array<Vertex, FanRim + 1> Fan{};
|
||||
@@ -279,6 +294,17 @@ int main() {
|
||||
const float y = i % 2u == 0u ? -1.0f : 0.0f;
|
||||
Strip[i] = {{x, y, 0.5f, 1.0f}, stripColor};
|
||||
}
|
||||
for (std::uint32_t strip = 0; strip < LineStrips; ++strip) {
|
||||
const auto color = TriangleColor(strip);
|
||||
const std::array<float, 4> rgba{color[0] / 255.0f, color[1] / 255.0f, color[2] / 255.0f, 1.0f};
|
||||
const float top = 1.0f - (static_cast<float>(LineStripRow(strip)) + 0.5f) / (Height / 2.0f);
|
||||
const float bottom = top - 20.0f / (Height / 2.0f);
|
||||
const float left = 24.5f / (Width / 2.0f) - 1.0f;
|
||||
const float right = 168.5f / (Width / 2.0f) - 1.0f;
|
||||
LineStripVertices[3 * strip] = {{left, top, 0.5f, 1.0f}, rgba};
|
||||
LineStripVertices[3 * strip + 1] = {{right, top, 0.5f, 1.0f}, rgba};
|
||||
LineStripVertices[3 * strip + 2] = {{right, bottom, 0.5f, 1.0f}, rgba};
|
||||
}
|
||||
Fan[0] = {{0.0f, 0.0f, 0.5f, 1.0f}, {1.0f, 1.0f, 1.0f, 1.0f}};
|
||||
for (std::uint32_t k = 1; k <= FanRim; ++k) {
|
||||
const auto color = TriangleColor(k);
|
||||
@@ -360,6 +386,24 @@ int main() {
|
||||
}
|
||||
Require(fastLaunchIndexedRefused, "an indexed fast-launch draw was drawn");
|
||||
|
||||
ClearPixels();
|
||||
auto lineSubgroup = FastLaunchSubgroup;
|
||||
lineSubgroup.maxPrimitives = 2u;
|
||||
lineSubgroup.outputPrimitiveSize = 2u;
|
||||
DrawMesh(device, {lineSubgroup, {0, LineStrips, 0, 1, 0, false}, VertexBufferDescriptor(LineStripVertices.data(), static_cast<std::uint32_t>(LineStripVertices.size())), ShaderRecompiler::MeshDrawPushOffsetBytes, false, 32u, VK_CULL_MODE_NONE, LineStripCode, 0x482030u});
|
||||
const auto linePixel = [](std::uint32_t x, std::uint32_t y) { return (static_cast<std::size_t>(y) * Width + x) * 4u; };
|
||||
for (std::uint32_t strip = 0; strip < LineStrips; ++strip) {
|
||||
const auto row = LineStripRow(strip);
|
||||
const auto color = TriangleColor(strip);
|
||||
const auto where = "line strip " + std::to_string(strip) + ": ";
|
||||
for (std::uint32_t x = 32; x <= 160; x += 8) Require(PixelIs(linePixel(x, row), color), where + "horizontal line pixel (" + std::to_string(x) + ", " + std::to_string(row) + ") is " + PixelText(linePixel(x, row)));
|
||||
for (std::uint32_t y = row + 4u; y <= row + 16u; y += 4) Require(PixelIs(linePixel(168, y), color), where + "vertical line pixel (168, " + std::to_string(y) + ") is " + PixelText(linePixel(168, y)));
|
||||
for (const auto& [x, y, what] : {std::tuple{96u, row - 3u, "above the horizontal line"}, std::tuple{96u, row + 3u, "below the horizontal line"}, std::tuple{150u, row + 5u, "inside the triangle of the strip's three vertices"}, std::tuple{96u, row + 10u, "on the edge closing the strip"}, std::tuple{96u, row + 24u, "between this strip and the next"}, std::tuple{8u, row, "left of the strip"}, std::tuple{184u, row + 10u, "right of the strip"}}) {
|
||||
Require(PixelIs(linePixel(x, y), Background), where + "pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") " + what + " is " + PixelText(linePixel(x, y)));
|
||||
}
|
||||
}
|
||||
Require(PixelIs(0, Background) && PixelIs(Pixels.size() - 4u, Background), "line strips: the corners changed");
|
||||
|
||||
constexpr std::size_t recordBlockBytes = 65536;
|
||||
auto* record = static_cast<std::uint32_t*>(::operator new(recordBlockBytes, std::align_val_t{65536}));
|
||||
{
|
||||
|
||||
@@ -2683,6 +2683,14 @@ void verifyMeshConfiguration() {
|
||||
other.graphics = GraphicsCompileContext{0u, {}, otherMesh, std::nullopt, {}};
|
||||
RecompileCacheKey::Build(other, key);
|
||||
require(key != first && RecompileCacheKey::ContextHash(request) != RecompileCacheKey::ContextHash(other), "the cache keys ignore the mesh configuration");
|
||||
require(mesh.outputPrimitiveSize == 3u && replay.request.graphics->mesh->outputPrimitiveSize == 3u, "a mesh configuration does not output triangles by default");
|
||||
otherMesh = mesh;
|
||||
otherMesh.outputPrimitiveSize = 2u;
|
||||
other.graphics = GraphicsCompileContext{0u, {}, otherMesh, std::nullopt, {}};
|
||||
const auto lineReplay = RequestSerializer{}.Deserialize(RequestSerializer{}.Serialize(other));
|
||||
require(lineReplay.request.graphics.has_value() && lineReplay.request.graphics->mesh.has_value() && lineReplay.request.graphics->mesh->outputPrimitiveSize == 2u, "the mesh output primitive size was lost in serialization");
|
||||
RecompileCacheKey::Build(other, key);
|
||||
require(key != first && RecompileCacheKey::ContextHash(request) != RecompileCacheKey::ContextHash(other), "the cache keys ignore the mesh output primitive size");
|
||||
RecompileRequest hull{};
|
||||
hull.shader = {ShaderStage::TessellationControl, 0x10000u, code, 0, {}};
|
||||
hull.context.waveSize = 64;
|
||||
@@ -3059,7 +3067,7 @@ void verifyPixelRequestSerialization() {
|
||||
minimal.context.pixel->layerExported = true;
|
||||
minimal.context.pixel->ditheredAlphaToCoverage = true;
|
||||
const auto encoded = serializer.Serialize(minimal);
|
||||
require(requestPrefix(encoded, 8u) == "NVNQQR4AAAA=", "new requests did not use serialization version 30");
|
||||
require(requestPrefix(encoded, 8u) == "NVNQQR8AAAA=", "new requests did not use serialization version 31");
|
||||
const std::array<std::uint32_t, 2> fallbacks{0x40u, 0x1ac4u};
|
||||
auto withFallbacks = minimal;
|
||||
withFallbacks.context.linkedCallFallbacks = fallbacks;
|
||||
@@ -3177,7 +3185,7 @@ void verifyPixelRequestSerialization() {
|
||||
for (std::size_t bytes = 0; bytes < 45u; ++bytes) {
|
||||
expectFailure([&] { static_cast<void>(serializer.Deserialize(requestPrefix(encoded, mappingOffset + bytes))); }, "truncated data", "a truncated pixel mapping, packing, dual-source, alpha-to-coverage, stencil reference export flag, primitive class, layer export, dither or front-face flag, quad pixel mask or quad sample mask was accepted");
|
||||
}
|
||||
for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQR8AAAA="}) {
|
||||
for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQSAAAAA="}) {
|
||||
expectFailure([&] { static_cast<void>(serializer.Deserialize(unsupported)); }, "serialization version", "an unsupported request version was accepted");
|
||||
}
|
||||
static constexpr std::string_view version18Request{
|
||||
|
||||
@@ -104,7 +104,7 @@ private:
|
||||
const auto* mesh = request.graphics && request.graphics->mesh ? &*request.graphics->mesh : nullptr;
|
||||
append(key, mesh != nullptr);
|
||||
if (mesh == nullptr) return;
|
||||
for (const auto value : {mesh->inputPrimitive, mesh->primitivesPerGroup, mesh->verticesPerGroup, mesh->maxVertices, mesh->maxPrimitives, mesh->threadsPerGroup, mesh->ldsSizeDwords, mesh->provokingVertex, mesh->esgsItemSize}) append(key, value);
|
||||
for (const auto value : {mesh->inputPrimitive, mesh->primitivesPerGroup, mesh->verticesPerGroup, mesh->maxVertices, mesh->maxPrimitives, mesh->threadsPerGroup, mesh->ldsSizeDwords, mesh->provokingVertex, mesh->esgsItemSize, mesh->outputPrimitiveSize}) append(key, value);
|
||||
}
|
||||
|
||||
static void appendTessellation(std::vector<std::uint64_t>& key, const RecompileRequest& request) {
|
||||
|
||||
@@ -518,6 +518,7 @@ void writeMeshConfiguration(Writer& writer, const MeshConfiguration& configurati
|
||||
writer.WriteU32(configuration.ldsSizeDwords);
|
||||
writer.WriteU32(configuration.provokingVertex);
|
||||
writer.WriteU32(configuration.esgsItemSize);
|
||||
writer.WriteU32(configuration.outputPrimitiveSize);
|
||||
}
|
||||
|
||||
MeshConfiguration readMeshConfiguration(Reader& reader, std::uint32_t version) {
|
||||
@@ -531,6 +532,7 @@ MeshConfiguration readMeshConfiguration(Reader& reader, std::uint32_t version) {
|
||||
configuration.ldsSizeDwords = reader.ReadU32();
|
||||
configuration.provokingVertex = reader.ReadU32();
|
||||
configuration.esgsItemSize = version >= 4u ? reader.ReadU32() : 4u;
|
||||
if (version >= 31u) configuration.outputPrimitiveSize = reader.ReadU32();
|
||||
return configuration;
|
||||
}
|
||||
|
||||
@@ -776,7 +778,7 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const
|
||||
std::string buffer;
|
||||
Writer writer(buffer);
|
||||
writer.WriteU32(0x41505335u);
|
||||
writer.WriteU32(30u);
|
||||
writer.WriteU32(31u);
|
||||
writeShaderBinary(writer, request.shader);
|
||||
writeGuestContext(writer, request.context);
|
||||
writeSpirvTarget(writer, request.target);
|
||||
@@ -809,7 +811,7 @@ DeserializedRequest RequestSerializer::Deserialize(std::string_view text) const
|
||||
Reader reader(decoded);
|
||||
if (reader.ReadU32() != 0x41505335u) throw std::runtime_error("invalid recompile request signature");
|
||||
const auto version = reader.ReadU32();
|
||||
if (version < 1u || version > 30u) throw std::runtime_error("unsupported recompile request serialization version");
|
||||
if (version < 1u || version > 31u) throw std::runtime_error("unsupported recompile request serialization version");
|
||||
DeserializedRequest result{};
|
||||
result.request.shader = readShaderBinary(reader, result.shaderCode, result.shaderHeader);
|
||||
result.request.context = readGuestContext(reader, result, version);
|
||||
|
||||
@@ -92,6 +92,7 @@ struct ShaderMeshInputInfo: ShaderWorkgroupInputInfo {
|
||||
std::uint32_t maxPrimitives = 0;
|
||||
std::uint32_t provokingVertex = 0;
|
||||
std::uint32_t esgsItemSize = 0;
|
||||
std::uint32_t outputPrimitiveSize = 3;
|
||||
|
||||
[[nodiscard]] std::uint32_t InputPrimitiveSize() const {
|
||||
return inputPrimitive == 1u ? 1u : inputPrimitive == 2u ? 2u : 3u;
|
||||
|
||||
@@ -303,6 +303,7 @@ struct MeshConfiguration {
|
||||
std::uint32_t ldsSizeDwords;
|
||||
std::uint32_t provokingVertex;
|
||||
std::uint32_t esgsItemSize = 0;
|
||||
std::uint32_t outputPrimitiveSize = 3;
|
||||
};
|
||||
|
||||
struct TessellationConfiguration {
|
||||
|
||||
@@ -240,7 +240,7 @@ void DefineModule(SpirvEmitterState& state) {
|
||||
state.meshGuestFunc = state.module.AllocateId();
|
||||
state.module.EmitCapability(spv::CapabilityMeshShadingEXT);
|
||||
state.module.EmitExtension("SPV_EXT_mesh_shader");
|
||||
state.module.AddExecutionMode(state.mainFunc, spv::ExecutionModeOutputTrianglesEXT);
|
||||
state.module.AddExecutionMode(state.mainFunc, mesh.outputPrimitiveSize == 2u ? spv::ExecutionModeOutputLinesEXT : spv::ExecutionModeOutputTrianglesEXT);
|
||||
state.module.AddExecutionMode(state.mainFunc, spv::ExecutionModeOutputVertices, mesh.maxVertices);
|
||||
state.module.AddExecutionMode(state.mainFunc, spv::ExecutionModeOutputPrimitivesEXT, mesh.maxPrimitives);
|
||||
}
|
||||
|
||||
@@ -767,11 +767,11 @@ void DefineMeshOutputs(SpirvEmitterState& state) {
|
||||
}
|
||||
state.meshAllocation = MeshArray(state, spv::StorageClassWorkgroup, TypeU32(state), 2u);
|
||||
state.meshPrimitiveData = MeshArray(state, spv::StorageClassPrivate, TypeU32(state), state.laneCount);
|
||||
state.meshPrimitives = MeshArray(state, spv::StorageClassOutput, TypeU32Vector(state, 3u), mesh.maxPrimitives);
|
||||
state.meshPrimitives = MeshArray(state, spv::StorageClassOutput, TypeU32Vector(state, mesh.outputPrimitiveSize), mesh.maxPrimitives);
|
||||
state.meshCull = MeshArray(state, spv::StorageClassOutput, TypeBool(state), mesh.maxPrimitives);
|
||||
state.interfaceVariables.push_back(state.meshPrimitives);
|
||||
state.interfaceVariables.push_back(state.meshCull);
|
||||
state.module.AddAnnotation(spv::OpDecorate, state.meshPrimitives, spv::DecorationBuiltIn, spv::BuiltInPrimitiveTriangleIndicesEXT);
|
||||
state.module.AddAnnotation(spv::OpDecorate, state.meshPrimitives, spv::DecorationBuiltIn, mesh.outputPrimitiveSize == 2u ? spv::BuiltInPrimitiveLineIndicesEXT : spv::BuiltInPrimitiveTriangleIndicesEXT);
|
||||
state.module.AddAnnotation(spv::OpDecorate, state.meshCull, spv::DecorationBuiltIn, spv::BuiltInCullPrimitiveEXT);
|
||||
state.module.AddAnnotation(spv::OpDecorate, state.meshCull, spv::DecorationPerPrimitiveEXT);
|
||||
if (state.numWorkgroupsVariable == 0u) {
|
||||
@@ -837,16 +837,16 @@ void EmitMeshEntryPoint(SpirvEmitterState& state) {
|
||||
EmitIfCondition(state, isPrimitive, [&]() {
|
||||
const auto packed = MeshLoad(state, state.meshPrimitiveData, spv::StorageClassPrivate, TypeU32(state), ConstantU32(state, half));
|
||||
std::array<std::uint32_t, 3> vertex {};
|
||||
for (std::uint32_t component = 0; component < 3u; component++) {
|
||||
for (std::uint32_t component = 0; component < mesh.outputPrimitiveSize; component++) {
|
||||
const auto field = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpBitFieldUExtract, TypeU32(state), field, packed, ConstantU32(state, component * 10u), ConstantU32(state, 9u));
|
||||
vertex.at(component) = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpExtInst, TypeU32(state), vertex.at(component), GlslStd450(state), GLSLstd450UMin, field, lastVertex);
|
||||
}
|
||||
const auto triangle = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 3u), triangle, vertex[0], vertex[1], vertex[2]);
|
||||
const auto trianglePointer = MeshElement(state, state.meshPrimitives, spv::StorageClassOutput, TypeU32Vector(state, 3u), index);
|
||||
state.module.AddFunction(spv::OpStore, trianglePointer, triangle);
|
||||
const auto indices = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, mesh.outputPrimitiveSize), indices, std::span<const std::uint32_t>(vertex.data(), mesh.outputPrimitiveSize));
|
||||
const auto indicesPointer = MeshElement(state, state.meshPrimitives, spv::StorageClassOutput, TypeU32Vector(state, mesh.outputPrimitiveSize), index);
|
||||
state.module.AddFunction(spv::OpStore, indicesPointer, indices);
|
||||
const auto nullBit = EmitBinaryU32(state, spv::OpBitwiseAnd, packed, ConstantU32(state, 0x80000000u));
|
||||
const auto culled = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpINotEqual, TypeBool(state), culled, nullBit, ConstantU32(state, 0u));
|
||||
|
||||
@@ -217,7 +217,8 @@ ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const Gues
|
||||
target.maxPrimitives = mesh->maxPrimitives;
|
||||
target.provokingVertex = mesh->provokingVertex;
|
||||
target.esgsItemSize = mesh->esgsItemSize;
|
||||
if (target.threadsNum[0] == 0u || target.maxVertices == 0u || target.maxPrimitives == 0u || target.provokingVertex > 2u) throw std::runtime_error("ShaderInputInfoBuilder: invalid mesh configuration");
|
||||
target.outputPrimitiveSize = mesh->outputPrimitiveSize;
|
||||
if (target.threadsNum[0] == 0u || target.maxVertices == 0u || target.maxPrimitives == 0u || (target.outputPrimitiveSize != 2u && target.outputPrimitiveSize != 3u) || target.provokingVertex >= target.outputPrimitiveSize) throw std::runtime_error("ShaderInputInfoBuilder: invalid mesh configuration");
|
||||
}
|
||||
ShaderStageInputInfo result;
|
||||
result.vertex = &vertexStorage;
|
||||
|
||||
@@ -206,7 +206,7 @@ int main() {
|
||||
Require(request.graphics->draw.indexAddress == 0xabcdef3400u && request.graphics->draw.indexCount == 57u && request.graphics->draw.indexElementBytes == 4u && request.graphics->draw.instanceCount == 9u);
|
||||
Require(request.target.float16DenormPreserve == preserve);
|
||||
auto upgraded = bytes;
|
||||
upgraded[4] = 30u;
|
||||
upgraded[4] = 31u;
|
||||
if (fixture.pixel) upgraded.insert(upgraded.begin() + 258u, {0u, 0u, 0u});
|
||||
if (fixture.pixel) InsertPixelDefaultsAfterFrontFace(upgraded);
|
||||
if (fixture.tessellation) upgraded.insert(upgraded.end() - 35u, 5u, 0u);
|
||||
@@ -245,7 +245,7 @@ int main() {
|
||||
layerRejected = std::string_view(error.what()) == "RequestSerializer: truncated data";
|
||||
}
|
||||
Require(layerRejected);
|
||||
layered[4] = 30u;
|
||||
layered[4] = 31u;
|
||||
layered.insert(layered.begin() + 259u, {0u, 0u});
|
||||
InsertPixelDefaultsAfterFrontFace(layered);
|
||||
if (fixture.tessellation) layered.insert(layered.end() - 35u, 5u, 0u);
|
||||
@@ -270,7 +270,7 @@ int main() {
|
||||
Require(replay.request.context.pixel->rasterPrimitive == ShaderRecompiler::RasterPrimitive::Rectangle);
|
||||
Require(replay.request.context.vertex->paClVsOutCntl == 0x00050a05u);
|
||||
Require(replay.request.target.float16DenormPreserve == preserve);
|
||||
bytes[4] = 30u;
|
||||
bytes[4] = 31u;
|
||||
bytes.insert(bytes.begin() + 259u, {0u, 0u});
|
||||
InsertPixelDefaultsAfterFrontFace(bytes);
|
||||
bytes.insert(bytes.end(), sizeof(std::uint64_t), 0u);
|
||||
@@ -394,7 +394,7 @@ int main() {
|
||||
Require(request.graphics->draw.indexAddress == 0xabcdef3400u && request.graphics->draw.indexCount == 57u && request.graphics->draw.indexElementBytes == 4u && request.graphics->draw.instanceCount == 9u);
|
||||
Require(request.target.float16DenormPreserve == preserve);
|
||||
auto upgraded = bytes;
|
||||
upgraded[4] = 30u;
|
||||
upgraded[4] = 31u;
|
||||
if (fixture.pixel) upgraded.insert(upgraded.begin() + 259u, {0u, 0u});
|
||||
if (fixture.pixel) InsertPixelDefaultsAfterFrontFace(upgraded);
|
||||
upgraded.insert(upgraded.end(), sizeof(std::uint64_t), 0u);
|
||||
@@ -407,7 +407,7 @@ int main() {
|
||||
const auto replay25 = serializer.Deserialize(EncodeBase64(main25));
|
||||
Require(!fixture.pixel || (replay25.request.context.pixel->layerExported == layer && replay25.request.context.pixel->ditheredAlphaToCoverage == dither && replay25.request.context.pixel->reversedBlend == 0u));
|
||||
Require(replay25.request.target.float16DenormPreserve == preserve && (!fixture.tessellation || replay25.request.graphics->tessellation->primitiveGroupSize == 37u));
|
||||
main25[4] = 30u;
|
||||
main25[4] = 31u;
|
||||
if (fixture.pixel) {
|
||||
main25.insert(main25.begin() + 260u, 0u);
|
||||
InsertPixelDefaultsAfterFrontFace(main25);
|
||||
@@ -426,9 +426,9 @@ int main() {
|
||||
const auto replay = serializer.Deserialize(encoded);
|
||||
Require(replay.request.target.float16DenormPreserve == preserve);
|
||||
auto bytes = DecodeBase64(encoded);
|
||||
Require(bytes.size() >= 8u && bytes[4] == 30u && bytes[5] == 0u && bytes[6] == 0u && bytes[7] == 0u);
|
||||
Require(bytes.size() >= 8u && bytes[4] == 31u && bytes[5] == 0u && bytes[6] == 0u && bytes[7] == 0u);
|
||||
auto future = bytes;
|
||||
future[4] = 31u;
|
||||
future[4] = 32u;
|
||||
bool futureRejected = false;
|
||||
try {
|
||||
static_cast<void>(serializer.Deserialize(EncodeBase64(future)));
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
- [Indices far past every vertex fetch](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver) - when a draw's highest index is at least 64Ki vertices past the largest per-vertex record count, every index past that count is moved to it in a copy of the indices, so the zero-padded vertex copies are not sized to the original index (PPSA04159 draws a list of `0xffffffff` with primitive restart off, which asked for 160 GiB). Each attribute fetched by vertex index reads zeros there as at the original index (out-of-bounds select 0 or 1); a draw with another out-of-bounds select keeps its indices. The vertex index the shader sees changes for the moved indices, which a shader that reads it directly would notice
|
||||
- [Depth clear draws](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - a draw with `DB_RENDER_CONTROL.DEPTH_CLEAR_ENABLE` is taken to write `DB_DEPTH_CLEAR` to every pixel it covers whatever the depth test, depth bounds and depth bias say: it is drawn with an always-passing depth write and the viewport depth range pinned to the clear value. The draw is rasterized as submitted, so its coverage is the draw's own. Whether the DB still applies the depth test or HiZ to such a draw is not measured
|
||||
- [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.
|
||||
- [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.
|
||||
|
||||
Reference in new issue
Block a user