diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 68f487b48..7aa1f37fd 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -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; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index 8a07f6bc8..4426f8c1c 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -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; } diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 29a7dc8b2..1682c8bc1 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -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; diff --git a/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp b/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp index 29891d598..47db4246d 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -46,6 +47,13 @@ alignas(256) constexpr auto Wave32GeometryCode = [] { return code; }(); +alignas(256) constexpr std::array 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 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 code = {}; + std::uint32_t stagesRegister = 0x20u; }; bool ReadsNumWorkgroups(const std::vector& 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 code = !setup.code.empty() ? setup.code : setup.waveSize == 32u ? std::span(Wave32GeometryCode) : std::span(GeometryCode); const std::array geometryMemory{{{reinterpret_cast(code.data()), std::as_bytes(std::span(code))}}}; ShaderRecompiler::RecompileRequest geometry{ {ShaderStage::Mesh, reinterpret_cast(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(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((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(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(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(Indices.data()), Triangles, 2, 1, 0, true}, VertexBufferDescriptor(Ordered.data(), static_cast(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(::operator new(recordBlockBytes, std::align_val_t{65536})); {