feat(agc): decode GS_FAST_LAUNCH geometry with one point per subgroup
VGT_SHADER_STAGES_EN.GS_FAST_LAUNCH = 1 (mesh-style NGG, as RADV programs it on gfx10.3) was refused. With a point list and GE_CNTL groups of one point, it launches one subgroup per input point and the first thread reads that point's index in the ES vertex id VGPR, which the mesh prologue already provides. The subgroup's primitive limit comes from GE_NGG_SUBGRP_CNTL.PRIM_AMP_FACTOR, and the GS and ES VGPR counts are not required to be full. Fast launch 2, other group sizes or inputs, and indexed fast-launch draws still throw. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
3b94f923a5
commit
7370aaf6d0
|
|
@ -1037,6 +1037,7 @@ DrawInputs prepareDrawInputs(const Context& context, const State& state, const P
|
|||
const auto& mesh = *state.stages.mesh;
|
||||
const auto inputSize = mesh.inputPrimitive == 1 ? 1u : mesh.inputPrimitive == 2 ? 2u : 3u;
|
||||
Require(draw.indexCount >= inputSize && mesh.primitivesPerGroup != 0, "mesh draw contains no complete primitive");
|
||||
Require(((state.stages.registerValue >> 19u) & 3u) == 0 || !draw.indexed, "indexed fast-launch draws are unsupported");
|
||||
if (args == nullptr) {
|
||||
const auto step = mesh.inputPrimitive == 5 || mesh.inputPrimitive == 6 ? 1u : inputSize;
|
||||
const auto primitives = (draw.indexCount - inputSize) / step + 1u;
|
||||
|
|
|
|||
|
|
@ -634,10 +634,12 @@ ShaderStages DecodeShaderStages(const QueueState& queue, bool vertexWorkgroup) {
|
|||
Require(result.minTessellationLevel >= 0.0f && result.minTessellationLevel <= 63.0f && result.minTessellationLevel <= result.maxTessellationLevel && result.maxTessellationLevel >= 1.0f, "tessellation level clamps need 0 <= VGT_HOS_MIN_TESS_LEVEL <= 63, VGT_HOS_MAX_TESS_LEVEL >= 1 and the minimum at most the maximum");
|
||||
result.tessellation = tess;
|
||||
} else {
|
||||
validate((value & ~0x0047ec30u) == 0, "unsupported geometry routing, fast launch or wave-ID state");
|
||||
const bool fastLaunch = ((value >> 19u) & 3u) == 1u;
|
||||
validate((value & ~(fastLaunch ? 0x004fec30u : 0x0047ec30u)) == 0, "unsupported geometry routing, fast launch or wave-ID state");
|
||||
const auto group = read(queue.userConfig, 0x25b, RegisterBank::UserConfig);
|
||||
const auto vertices = (group >> 9u) & 0x1ffu;
|
||||
const auto primitives = group & 0x1ffu;
|
||||
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");
|
||||
|
|
@ -648,11 +650,14 @@ ShaderStages DecodeShaderStages(const QueueState& queue, bool vertexWorkgroup) {
|
|||
const auto groupPrimitives = std::min({primitives, (vertices - inputSize) / inputStep + 1u, outputLimit});
|
||||
validate(groupPrimitives != 0, "geometry subgroup contains no primitives");
|
||||
const auto resources = read(queue.shader, 0x8b, RegisterBank::Shader);
|
||||
validate(vertexWorkgroup || (((read(queue.shader, 0x8a, RegisterBank::Shader) >> 29u) & 3u) == 3 && ((resources >> 16u) & 3u) == 3), "unsupported geometry VGPR allocation");
|
||||
validate(vertexWorkgroup || fastLaunch || (((read(queue.shader, 0x8a, RegisterBank::Shader) >> 29u) & 3u) == 3 && ((resources >> 16u) & 3u) == 3), "unsupported geometry VGPR allocation");
|
||||
const auto esgsItemSize = read(queue.context, 0x2ab);
|
||||
validate(esgsItemSize != 0 && esgsItemSize * vertices <= 0xffffu, "invalid VGT_ESGS_RING_ITEMSIZE");
|
||||
const auto threads = std::max({(groupPrimitives - 1u) * inputStep + inputSize, primitives, maxVertices, primitives * (verticesPerPrimitive - 2u)});
|
||||
result.mesh = ShaderRecompiler::MeshConfiguration{primitive, groupPrimitives, (groupPrimitives - 1u) * inputStep + inputSize, maxVertices, primitives * (verticesPerPrimitive - 2u), ((threads + result.vertexWaveSize - 1u) / result.vertexWaveSize) * result.vertexWaveSize, ((resources >> 19u) & 0xffu) * 128u, 0, esgsItemSize};
|
||||
const auto amplification = fastLaunch ? read(queue.context, 0x2d3) & 0x1ffu : verticesPerPrimitive - 2u;
|
||||
validate(amplification != 0, "fast launch subgroup amplifies to no primitives");
|
||||
const auto maxPrimitives = 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};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -573,6 +573,34 @@ void ShaderStageTests() {
|
|||
fan.userConfig[0x242] = 3;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(fan); }, "unsupported geometry input or output assembly");
|
||||
}
|
||||
{
|
||||
auto fast = makeState();
|
||||
fast.userConfig[0x242] = 1;
|
||||
fast.context[0x2d5] = 0x482030;
|
||||
fast.userConfig[0x25b] = 0x201;
|
||||
fast.context[0x1ff] = 32;
|
||||
fast.context[0x2ce] = 32;
|
||||
fast.context[0x2d3] = 64;
|
||||
fast.context[0x29b] = 2;
|
||||
fast.context[0x2ab] = 4;
|
||||
fast.shader[0x8a] = 0x022c0245;
|
||||
fast.shader[0x8b] = 0x9002a;
|
||||
const auto state = AgcDriver::Graphics::DecodeState(fast);
|
||||
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[0x2d5] = 0x502030;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(fast); }, "unsupported geometry routing, fast launch or wave-ID state");
|
||||
fast.context[0x2d5] = 0x482030;
|
||||
fast.userConfig[0x25b] = 0x402;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(fast); }, "fast launch other than one point per subgroup");
|
||||
fast.userConfig[0x25b] = 0x201;
|
||||
fast.userConfig[0x242] = 4;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(fast); }, "fast launch other than one point per subgroup");
|
||||
fast.userConfig[0x242] = 1;
|
||||
fast.context[0x2d3] = 0;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(fast); }, "amplifies to no primitives");
|
||||
}
|
||||
queue.context[0x2ab] = 0;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "invalid VGT_ESGS_RING_ITEMSIZE");
|
||||
queue.context[0x2ab] = 4;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <new>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
|
@ -46,6 +47,13 @@ alignas(256) constexpr auto Wave32GeometryCode = [] {
|
|||
return code;
|
||||
}();
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 25> FastLaunchCode{
|
||||
0xd7650000, 0x000100c1, 0x7e000505, 0x93018300, 0x4a020001, 0xe0382000, 0x80020201, 0xe0382010,
|
||||
0x80020601, 0xb07c1003, 0xbf900009, 0xbf8c3f70, 0x7d880083, 0xbefe036a, 0xf80008cf, 0x05040302,
|
||||
0xf800020f, 0x09080706, 0x7d840080, 0xbefe036a, 0x7e1402ff, 0x00200400, 0xf8000941, 0x0000000a,
|
||||
0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 7> PixelCode{
|
||||
0xc8020002, 0xc8060102, 0xc80a0202, 0xc80e0302, 0xf800180f, 0x03020100, 0xbf810000,
|
||||
};
|
||||
|
|
@ -132,6 +140,8 @@ struct MeshDraw {
|
|||
bool restart = false;
|
||||
std::uint32_t waveSize = 64u;
|
||||
VkCullModeFlags cull = VK_CULL_MODE_NONE;
|
||||
std::span<const std::uint32_t> code = {};
|
||||
std::uint32_t stagesRegister = 0x20u;
|
||||
};
|
||||
|
||||
bool ReadsNumWorkgroups(const std::vector<std::uint32_t>& words) {
|
||||
|
|
@ -160,7 +170,7 @@ void DrawMesh(AgcDriver::VulkanDevice& device, const MeshDraw& setup) {
|
|||
const auto index = AgcDriver::Graphics::MeshIndexBufferDescriptor(setup.draw);
|
||||
std::copy(index.begin(), index.end(), userData.begin() + ShaderRecompiler::MeshIndexBufferUserWord);
|
||||
std::copy(setup.vertexBuffer.begin(), setup.vertexBuffer.end(), userData.begin() + 8);
|
||||
const auto& code = setup.waveSize == 32u ? Wave32GeometryCode : GeometryCode;
|
||||
const std::span<const std::uint32_t> code = !setup.code.empty() ? setup.code : setup.waveSize == 32u ? std::span<const std::uint32_t>(Wave32GeometryCode) : std::span<const std::uint32_t>(GeometryCode);
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> geometryMemory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(std::span(code))}}};
|
||||
ShaderRecompiler::RecompileRequest geometry{
|
||||
{ShaderStage::Mesh, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
|
||||
|
|
@ -194,7 +204,7 @@ void DrawMesh(AgcDriver::VulkanDevice& device, const MeshDraw& setup) {
|
|||
}};
|
||||
|
||||
AgcDriver::Graphics::State state{};
|
||||
state.stages = {AgcDriver::Graphics::ShaderPath::Geometry, 0x20u, setup.waveSize, 64, setup.mesh, std::nullopt};
|
||||
state.stages = {AgcDriver::Graphics::ShaderPath::Geometry, setup.stagesRegister, setup.waveSize, 64, setup.mesh, std::nullopt};
|
||||
state.color = {reinterpret_cast<std::uintptr_t>(Pixels.data()), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, Pixels.size(), 0xe4u};
|
||||
state.colors = {state.color};
|
||||
state.hasColorTarget = true;
|
||||
|
|
@ -215,6 +225,7 @@ void DrawMesh(AgcDriver::VulkanDevice& device, const MeshDraw& setup) {
|
|||
|
||||
constexpr ShaderRecompiler::MeshConfiguration SmallSubgroup{4u, 4u, 12u, 12u, 4u, 64u, 1024u, 0u, 4u};
|
||||
constexpr ShaderRecompiler::MeshConfiguration WideSubgroup{4u, 32u, 96u, 96u, 32u, 128u, 2048u, 0u, 4u};
|
||||
constexpr ShaderRecompiler::MeshConfiguration FastLaunchSubgroup{1u, 1u, 1u, 3u, 1u, 32u, 128u, 0u, 4u};
|
||||
|
||||
std::size_t FanPixel(float angle, float radius) {
|
||||
const auto x = static_cast<std::uint32_t>((radius * std::cos(angle) + 1.0f) * 0.5f * Width);
|
||||
|
|
@ -344,6 +355,24 @@ int main() {
|
|||
CheckTriangles("non-indexed wave32 triangle list");
|
||||
}
|
||||
|
||||
ClearPixels();
|
||||
DrawMesh(device, {FastLaunchSubgroup, {0, Triangles, 0, 1, 0, false}, VertexBufferDescriptor(Ordered.data(), static_cast<std::uint32_t>(Ordered.size())), ShaderRecompiler::MeshDrawPushOffsetBytes, false, 32u, VK_CULL_MODE_NONE, FastLaunchCode, 0x482030u});
|
||||
CheckTriangles("fast-launch subgroups");
|
||||
ClearPixels();
|
||||
DrawMesh(device, {FastLaunchSubgroup, {0, Triangles / 2u, 0, 1, 0, false, Triangles / 4u}, VertexBufferDescriptor(Ordered.data(), static_cast<std::uint32_t>(Ordered.size())), ShaderRecompiler::MeshDrawPushOffsetBytes, false, 32u, VK_CULL_MODE_NONE, FastLaunchCode, 0x482030u});
|
||||
for (std::uint32_t triangle = 0; triangle < Triangles; ++triangle) {
|
||||
const bool drawn = triangle >= Triangles / 4u && triangle < Triangles / 4u + Triangles / 2u;
|
||||
Require(PixelIs(CellPixel(triangle), drawn ? TriangleColor(triangle) : Background), "fast-launch subgroups from a start vertex: triangle " + std::to_string(triangle) + " pixel " + PixelText(CellPixel(triangle)));
|
||||
}
|
||||
bool fastLaunchIndexedRefused = false;
|
||||
try {
|
||||
DrawMesh(device, {FastLaunchSubgroup, {reinterpret_cast<std::uintptr_t>(Indices.data()), Triangles, 2, 1, 0, true}, VertexBufferDescriptor(Ordered.data(), static_cast<std::uint32_t>(Ordered.size())), ShaderRecompiler::MeshDrawPushOffsetBytes, false, 32u, VK_CULL_MODE_NONE, FastLaunchCode, 0x482030u});
|
||||
} catch (const std::runtime_error& error) {
|
||||
fastLaunchIndexedRefused = std::string(error.what()).find("indexed fast-launch draws are unsupported") != std::string::npos;
|
||||
if (!fastLaunchIndexedRefused) throw;
|
||||
}
|
||||
Require(fastLaunchIndexedRefused, "an indexed fast-launch draw was drawn");
|
||||
|
||||
constexpr std::size_t recordBlockBytes = 65536;
|
||||
auto* record = static_cast<std::uint32_t*>(::operator new(recordBlockBytes, std::align_val_t{65536}));
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue