diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawLookup.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawLookup.cpp index 64ec8646f..9e8088ea0 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawLookup.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawLookup.cpp @@ -22,6 +22,7 @@ void Driver::lookupDraw(const Submission& submission, const std::shared_ptr& x, const std::optional& 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->passthrough == y->passthrough; }; const auto sameTess = [](const std::optional& x, const std::optional& y) { if (x.has_value() != y.has_value()) return false; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 9aca4f99a..1ee746445 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -467,6 +467,8 @@ bool ValidationKey(const Context& context, std::span shade add(mesh.threadsPerGroup); add(mesh.ldsSizeDwords); add(mesh.provokingVertex); + add(mesh.esgsItemSize); + add(mesh.passthrough); } add(state.stages.tessellation.has_value()); if (state.stages.tessellation) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp index 36772cf21..5186111b5 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp @@ -362,6 +362,8 @@ std::vector pipelineKey(const Context& context, const State& state, c append(key, mesh.threadsPerGroup); append(key, mesh.ldsSizeDwords); append(key, mesh.provokingVertex); + append(key, mesh.esgsItemSize); + append(key, mesh.passthrough); } append(key, state.stages.tessellation.has_value()); if (state.stages.tessellation) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index cdeb09c36..1545a74d9 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -341,7 +341,9 @@ ShaderStages DecodeShaderStages(const QueueState& queue) { validate((value & 3u) != 3u && ((value >> 3u) & 3u) != 3u && ((value >> 6u) & 3u) != 3u, "reserved LS_EN, ES_EN or VS_EN encoding"); const auto primitive = read(queue.userConfig, 0x242, RegisterBank::UserConfig); const bool tessellation = primitive == 9; - const bool geometry = (value & 0x20u) != 0; + const bool passthrough = (value & 0x02000000u) != 0; + const bool geometry = (value & 0x20u) != 0 || passthrough; + validate(!passthrough || (value & 0x2000u) != 0, "passthrough routing without PRIMGEN_EN is unsupported"); validate(tessellation == ((value & 4u) != 0), "Patch topology and HS_EN disagree"); validate(!tessellation || !geometry, "combined tessellation and geometry is unsupported by the reference path"); const auto path = tessellation ? ShaderPath::Tessellation : geometry ? ShaderPath::Geometry : ShaderPath::Vertex; @@ -360,17 +362,17 @@ ShaderStages DecodeShaderStages(const QueueState& queue) { validate(tess.domain == 1 && tess.partitioning == 2 && tess.outputTopology == 2, "only triangular, fractional-odd, clockwise tessellation is supported by the reference path"); result.tessellation = tess; } else { - validate((value & ~0x0047ec30u) == 0, "unsupported geometry routing, fast launch or wave-ID state"); + validate((value & ~(passthrough ? 0x02402010u : 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; const auto maxVertices = read(queue.context, 0x1ff); - const auto verticesPerPrimitive = read(queue.context, 0x2ce); - validate((primitive == 1 || primitive == 2 || primitive == 4 || primitive == 6) && read(queue.context, 0x29b) == 2 && verticesPerPrimitive >= 3, "unsupported geometry input or output assembly"); + const auto verticesPerPrimitive = passthrough ? 3u : read(queue.context, 0x2ce); + validate(passthrough ? (primitive == 4 || primitive == 6) : ((primitive == 1 || primitive == 2 || primitive == 4 || primitive == 6) && read(queue.context, 0x29b) == 2 && verticesPerPrimitive >= 3), "unsupported geometry input or output assembly"); const auto inputSize = primitive == 1 ? 1u : primitive == 2 ? 2u : 3u; validate(vertices >= inputSize && maxVertices != 0 && maxVertices <= 256 && verticesPerPrimitive <= 256, "invalid geometry subgroup output"); const auto inputStep = primitive == 6 ? 1u : inputSize; - const auto groupPrimitives = std::min({primitives, (vertices - inputSize) / inputStep + 1u, maxVertices / verticesPerPrimitive}); + const auto groupPrimitives = std::min({primitives, (vertices - inputSize) / inputStep + 1u, passthrough ? (maxVertices >= inputSize ? (maxVertices - inputSize) / inputStep + 1u : 0u) : maxVertices / verticesPerPrimitive}); validate(groupPrimitives != 0, "geometry subgroup contains no primitives"); const auto resources = read(queue.shader, 0x8b, RegisterBank::Shader); validate(((read(queue.shader, 0x8a, RegisterBank::Shader) >> 29u) & 3u) == 3 && ((resources >> 16u) & 3u) == 3, "unsupported geometry VGPR allocation"); @@ -378,6 +380,7 @@ ShaderStages DecodeShaderStages(const QueueState& queue) { 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}; + result.mesh->passthrough = passthrough; } return result; } diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 5984166b3..be55bb57a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -186,7 +186,7 @@ void hardwareScreenOffsetTests() { void ShaderStageTests() { auto queue = makeState(); - for (const auto routing : {0x2000u, 0x2010u, 0x02002000u, 0x02002010u}) { + for (const auto routing : {0x2000u, 0x2010u}) { for (const auto vertexWave32 : {false, true}) { for (const auto fragmentWave32 : {false, true}) { queue.context[0x2d5] = routing | (vertexWave32 ? 0x00400000u : 0u); @@ -210,6 +210,17 @@ void ShaderStageTests() { auto stages = AgcDriver::Graphics::DecodeState(queue).stages; Require(stages.path == AgcDriver::Graphics::ShaderPath::Geometry && stages.mesh && stages.mesh->primitivesPerGroup == 21 && stages.mesh->verticesPerGroup == 63, "geometry assembly changed"); Require(stages.mesh->maxVertices == 64 && stages.mesh->maxPrimitives == 21 && stages.mesh->threadsPerGroup == 64 && stages.mesh->esgsItemSize == 4, "geometry subgroup outputs changed"); + for (const auto routing : {0x02002000u, 0x02002010u, 0x02402000u, 0x02402010u}) { + queue.context[0x2d5] = routing; + queue.context[0x2ce] = 0; + queue.context[0x29b] = 0; + const auto pass = AgcDriver::Graphics::DecodeState(queue).stages; + Require(pass.path == AgcDriver::Graphics::ShaderPath::Geometry && pass.mesh && pass.mesh->passthrough && pass.mesh->verticesPerGroup == 63 && pass.mesh->primitivesPerGroup == 21, "passthrough subgroup assembly changed"); + Require(pass.vertexWaveSize == ((routing & 0x00400000u) ? 32u : 64u), "passthrough wave size changed"); + } + queue.context[0x2d5] = 0x2020; + queue.context[0x2ce] = 3; + queue.context[0x29b] = 2; 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/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index 4f9586ac2..24fec9d49 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -449,6 +449,13 @@ 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"); + otherMesh = mesh; + otherMesh.passthrough = true; + other.graphics = GraphicsCompileContext{0u, {}, otherMesh, std::nullopt, {}}; + RecompileCacheKey::Build(other, key); + require(key != first && RecompileCacheKey::ContextHash(request) != RecompileCacheKey::ContextHash(other), "the cache keys ignore passthrough input layout"); + const auto passReplay = RequestSerializer{}.Deserialize(RequestSerializer{}.Serialize(other)); + require(passReplay.request.graphics->mesh->passthrough && !replay.request.graphics->mesh->passthrough, "passthrough input layout was lost in serialization"); } ShaderRecompiler::ShaderPixelStageInfo twoParameterPixel() { diff --git a/core/shader/recompiler/CacheKey.hpp b/core/shader/recompiler/CacheKey.hpp index aacc33f90..78264ca57 100644 --- a/core/shader/recompiler/CacheKey.hpp +++ b/core/shader/recompiler/CacheKey.hpp @@ -78,6 +78,7 @@ private: const auto* mesh = request.graphics && request.graphics->mesh ? &*request.graphics->mesh : nullptr; append(key, mesh != nullptr); if (mesh == nullptr) return; + append(key, mesh->passthrough); 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); } diff --git a/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp b/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp index f483746f1..4f36e6f1d 100644 --- a/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp +++ b/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp @@ -442,6 +442,7 @@ void writeMeshConfiguration(Writer& writer, const MeshConfiguration& configurati writer.WriteU32(configuration.ldsSizeDwords); writer.WriteU32(configuration.provokingVertex); writer.WriteU32(configuration.esgsItemSize); + writer.WriteBool(configuration.passthrough); } MeshConfiguration readMeshConfiguration(Reader& reader, std::uint32_t version) { @@ -455,6 +456,7 @@ MeshConfiguration readMeshConfiguration(Reader& reader, std::uint32_t version) { configuration.ldsSizeDwords = reader.ReadU32(); configuration.provokingVertex = reader.ReadU32(); configuration.esgsItemSize = version >= 4u ? reader.ReadU32() : 4u; + configuration.passthrough = version >= 5u ? reader.ReadBool() : false; return configuration; } diff --git a/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp b/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp index 39e645996..38bc69cf3 100644 --- a/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp +++ b/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp @@ -102,6 +102,7 @@ struct ShaderMeshInputInfo: ShaderWorkgroupInputInfo { std::uint32_t maxPrimitives = 0; std::uint32_t provokingVertex = 0; std::uint32_t esgsItemSize = 0; + bool passthrough = false; [[nodiscard]] std::uint32_t InputPrimitiveSize() const { return inputPrimitive == 1u ? 1u : inputPrimitive == 2u ? 2u : 3u; diff --git a/core/shader/recompiler/Recompiler.hpp b/core/shader/recompiler/Recompiler.hpp index 6fcb04b64..5d9de25dc 100644 --- a/core/shader/recompiler/Recompiler.hpp +++ b/core/shader/recompiler/Recompiler.hpp @@ -225,6 +225,7 @@ struct MeshConfiguration { std::uint32_t ldsSizeDwords; std::uint32_t provokingVertex; std::uint32_t esgsItemSize = 0; + bool passthrough = false; }; struct TessellationConfiguration { diff --git a/core/shader/recompiler/Translation/src/InstructionTranslator.cpp b/core/shader/recompiler/Translation/src/InstructionTranslator.cpp index 1d63b09a8..7d5d58eb0 100644 --- a/core/shader/recompiler/Translation/src/InstructionTranslator.cpp +++ b/core/shader/recompiler/Translation/src/InstructionTranslator.cpp @@ -238,6 +238,9 @@ void emitEntryPrologue(IrProgram& program, IrBlock& entryBlock, const TranslateO const std::uint32_t size = mesh.InputPrimitiveSize(); const std::uint32_t stepCount = mesh.InputPrimitiveStep(); const std::uint32_t waveSize = options.waveSize; + if (mesh.passthrough && mesh.inputPrimitive != 4u && mesh.inputPrimitive != 6u) { + throw std::runtime_error("passthrough mesh translation requires triangle list or strip topology"); + } if (mesh.primitivesPerGroup == 0u || mesh.verticesPerGroup != mesh.InputVertexCount(mesh.primitivesPerGroup) || mesh.verticesPerGroup > totalThreads || mesh.primitivesPerGroup > totalThreads || totalThreads % waveSize != 0u || totalThreads > 15u * waveSize || mesh.esgsItemSize == 0u || mesh.esgsItemSize * mesh.verticesPerGroup > 0xffffu) { throw std::runtime_error("mesh shader translation configuration is not supported (wave " + std::to_string(options.waveSize) + ", primitives per group " + std::to_string(mesh.primitivesPerGroup) + ", vertices per group " + std::to_string(mesh.verticesPerGroup) + ", threads " + std::to_string(totalThreads) + ", ESGS item size " + std::to_string(mesh.esgsItemSize) + ")"); } @@ -275,6 +278,13 @@ void emitEntryPrologue(IrProgram& program, IrBlock& entryBlock, const TranslateO IrValue& third = size == 3u ? entryIr.IMul(entryIr.IAdd(vertex, u32(2u)), item) : u32(0u); entryIr.SetVectorReg(static_cast(0), entryIr.BitwiseOr(entryIr.BitwiseAnd(first, u32(0xffffu)), entryIr.ShiftLeftLogical(second, u32(16u)))); entryIr.SetVectorReg(static_cast(1), entryIr.BitwiseAnd(third, u32(0xffffu))); + if (mesh.passthrough) { + IrValue& firstIndex = entryIr.IAdd(vertex, parity); + IrValue& secondIndex = entryIr.ISub(entryIr.IAdd(vertex, u32(1u)), parity); + IrValue& thirdIndex = entryIr.IAdd(vertex, u32(2u)); + entryIr.SetVectorReg(static_cast(0), entryIr.BitwiseOr(firstIndex, entryIr.BitwiseOr(entryIr.ShiftLeftLogical(secondIndex, u32(10u)), entryIr.ShiftLeftLogical(thirdIndex, u32(20u))))); + entryIr.SetVectorReg(static_cast(1), u32(0u)); + } entryIr.SetVectorReg(static_cast(2), entryIr.IAdd(firstPrimitive, local)); entryIr.SetVectorReg(static_cast(3), u32(0u)); entryIr.SetVectorReg(static_cast(4), u32(0u)); diff --git a/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp b/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp index 32887b3bc..8bb8d945a 100644 --- a/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp +++ b/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp @@ -184,6 +184,7 @@ ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const Gues target.maxPrimitives = mesh->maxPrimitives; target.provokingVertex = mesh->provokingVertex; target.esgsItemSize = mesh->esgsItemSize; + target.passthrough = mesh->passthrough; if (target.threadsNum[0] == 0u || target.maxVertices == 0u || target.maxPrimitives == 0u || target.provokingVertex > 2u) throw std::runtime_error("ShaderInputInfoBuilder: invalid mesh configuration"); } ShaderStageInputInfo result;