diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp index d3b182c7a..0bc1a7c21 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp @@ -246,6 +246,31 @@ inline std::optional HighestDrawIndex(std::span return highest; } +inline std::vector MeshRestartTable(std::span indices, std::uint32_t indexSize) { + Require(indexSize == 2 || indexSize == 4, "unsupported index size"); + const auto restartIndex = indexSize == 2 ? 0xffffu : 0xffffffffu; + std::vector starts(indices.size() / indexSize); + std::uint32_t stripStart = 0; + bool restarted = false; + for (std::size_t position = 0; position < starts.size(); ++position) { + std::uint32_t index = 0; + if (indexSize == 2) { + std::uint16_t value = 0; + std::memcpy(&value, indices.data() + position * 2u, sizeof(value)); + index = value; + } else { + std::memcpy(&index, indices.data() + position * 4u, sizeof(index)); + } + if (index == restartIndex) { + restarted = true; + stripStart = static_cast(position) + 1u; + } + starts[position] = stripStart; + } + if (!restarted) starts.clear(); + return starts; +} + inline std::vector SoloZeroPaddedFetchIndices(const std::vector& fetches, const std::vector& fetchValid) { Require(fetchValid.size() == fetches.size(), "fetch validity does not match the fetch count"); std::vector solo; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 4d238de0c..edca5809a 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -961,6 +961,7 @@ struct DrawInputs { std::set fragmentOutputs; VkPipelineStageFlags shaderStages = 0; std::uint32_t meshGroups = 0; + std::vector restartTable; }; std::shared_ptr zeroVertexBuffer(const Context& context, const ShaderRecompiler::VertexAttribute& attribute) { @@ -1062,6 +1063,10 @@ DrawInputs prepareDrawInputs(const Context& context, const State& state, const P Require(*highest <= context.limits.maxDrawIndexedIndexValue, "index exceeds the device's indexed draw limit"); Require(!fanGeometry || !state.primitiveRestart || *highest != (draw.indexSize == 2 ? 0xffffu : 0xffffffffu), "primitive restart in a triangle fan geometry draw is unsupported"); inputs.maxIndex = *highest; + if (skipRestart && state.stages.mesh && state.stages.mesh->inputPrimitive == 6) { + const auto indexSpan = import != nullptr ? std::span(reinterpret_cast(draw.indexAddress), static_cast(indexBytes)) : copy.buffer->Bytes().first(static_cast(indexBytes)); + inputs.restartTable = MeshRestartTable(indexSpan, draw.indexSize); + } if (import != nullptr) { inputs.indexHandle = import->buffer; inputs.indexOffset = draw.indexAddress - import->base; @@ -1363,10 +1368,30 @@ bool recordIndirectArguments(const Context& context, VkCommandBuffer commands, R return true; } -std::shared_ptr recordMeshArguments(const Context& context, VkCommandBuffer commands, Recorder* recorder, bool recorded, const State& state, const Pm4::DrawParameters& draw, const IndirectRecord& indirect, const std::function& countBarrier) { +std::shared_ptr meshArgumentBuffer(const Context& context, std::span restartTable) { + const auto tableBytes = restartTable.empty() ? 0u : sizeof(std::uint32_t) * (restartTable.size() + 1u); + auto arguments = std::make_shared(context, ShaderRecompiler::MeshArgumentBytes + tableBytes, VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); + if (!restartTable.empty()) { + Require(restartTable.size() <= std::numeric_limits::max(), "mesh restart table is too large"); + const auto length = static_cast(restartTable.size()); + auto* bytes = arguments->Bytes().data() + ShaderRecompiler::MeshArgumentBytes; + std::memcpy(bytes, &length, sizeof(length)); + std::memcpy(bytes + sizeof(length), restartTable.data(), restartTable.size_bytes()); + } + return arguments; +} + +std::shared_ptr directMeshArguments(const Context& context, const Pm4::DrawParameters& draw, std::span restartTable) { + auto arguments = meshArgumentBuffer(context, restartTable); + const MeshArguments resolved{0u, 0u, 0u, draw.indexCount, 0u}; + std::memcpy(arguments->Bytes().data(), &resolved, sizeof(resolved)); + return arguments; +} + +std::shared_ptr recordMeshArguments(const Context& context, VkCommandBuffer commands, Recorder* recorder, bool recorded, const State& state, const Pm4::DrawParameters& draw, const IndirectRecord& indirect, const std::function& countBarrier, std::span restartTable) { const auto* args = indirect.args; const auto rules = MeshArgumentRulesFor(context, *state.stages.mesh, draw.indexCount); - auto arguments = std::make_shared(context, ShaderRecompiler::MeshArgumentBytes, VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); + auto arguments = meshArgumentBuffer(context, restartTable); if (indirect.path == IndirectDrawPath::Gpu && recorder != nullptr && indirect.argumentImport->address != 0) { const std::array words{rules.indexCount, rules.inputSize, rules.step, rules.primitivesPerGroup, rules.maxGroups, rules.maxInstances, rules.maxTotal}; if (recorder->RecordMeshArguments(commands, indirect.argumentImport->address + (args->arguments - indirect.argumentImport->base), arguments->DeviceAddress(), words)) { @@ -1481,7 +1506,7 @@ struct RecordedDraw { VkShaderStageFlags pushStages = 0; }; -void pushDrawConstants(const Pipeline& pipeline, VkCommandBuffer commands, const State& state, const Pm4::DrawParameters& draw, std::span shaders, const ShaderResources& resources, const std::array* bytes, VkShaderStageFlags stages, VkDeviceAddress meshArguments = 0) { +void pushDrawConstants(const Pipeline& pipeline, VkCommandBuffer commands, const State& state, const Pm4::DrawParameters& draw, std::span shaders, const ShaderResources& resources, const std::array* bytes, VkShaderStageFlags stages, VkDeviceAddress meshArguments = 0, bool restartTable = false) { auto block = bytes != nullptr ? *bytes : AssemblePushConstants(shaders); if (bytes == nullptr) { resources.PatchPushConstants(block); @@ -1493,7 +1518,7 @@ void pushDrawConstants(const Pipeline& pipeline, VkCommandBuffer commands, const } const auto firstVertex = draw.indirect ? draw.indirect->vertexConstant : draw.firstVertex; const auto firstInstance = draw.indirect ? draw.indirect->instanceConstant : draw.firstInstance; - const std::array words{draw.indexCount, firstVertex, firstInstance, draw.indexed ? draw.indexSize : 0u, static_cast(meshArguments), static_cast(meshArguments >> 32u)}; + const std::array words{draw.indexCount, firstVertex, firstInstance, draw.indexed ? draw.indexSize | (restartTable ? ShaderRecompiler::MeshIndexRestartTable : 0u) : 0u, static_cast(meshArguments), static_cast(meshArguments >> 32u)}; static_assert(ShaderRecompiler::MeshDrawPushOffsetBytes + ShaderRecompiler::MeshDrawPushBytes == PipelinePushSlotBytes); std::memcpy(block.data() + ShaderRecompiler::MeshDrawPushOffsetBytes, words.data(), sizeof(words)); pipeline.PushConstants(commands, stages | VK_SHADER_STAGE_MESH_BIT_EXT, block); @@ -1651,6 +1676,7 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam VkBuffer argumentBuffer = VK_NULL_HANDLE; VkDeviceSize argumentOffset = 0; bool rewritten = false; + if (!meshIndirect && !inputs.restartTable.empty()) meshArguments = directMeshArguments(context, draw, inputs.restartTable); if (continued) { record.pipeline->Continue(commands, state); } else { @@ -1665,7 +1691,7 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam } APS5_LOG_CHARS_OUT_DEBUG("Upload barrier recorded"); if (meshIndirect) { - meshArguments = recordMeshArguments(context, commands, recorder, true, state, draw, *record.indirect, countBarrier); + meshArguments = recordMeshArguments(context, commands, recorder, true, state, draw, *record.indirect, countBarrier, inputs.restartTable); argumentBuffer = meshArguments->Handle(); } else if (gpuIndirect) { rewritten = recordIndirectArguments(context, commands, recorder, true, *record.indirect, scratch, argumentBuffer, argumentOffset, countBarrier); @@ -1683,7 +1709,7 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam resources.Bind(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, record.pipeline->Layout()); } APS5_LOG_CHARS_OUT_DEBUG("Resources bound"); - pushDrawConstants(*record.pipeline, commands, state, draw, shaders, resources, record.pushBytes, record.pushStages, meshArguments != nullptr ? meshArguments->DeviceAddress() : 0); + pushDrawConstants(*record.pipeline, commands, state, draw, shaders, resources, record.pushBytes, record.pushStages, meshArguments != nullptr ? meshArguments->DeviceAddress() : 0, !inputs.restartTable.empty()); APS5_LOG_CHARS_OUT_DEBUG("Push constants recorded"); recordDrawCommands(context, commands, state, draw, inputs, record.indirect, argumentBuffer, argumentOffset); if (record.pipeline->SplitsFaces()) { @@ -2117,8 +2143,10 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& APS5_LOG_CHARS_OUT_DEBUG("Upload barrier recorded"); std::shared_ptr meshArguments; if (state.stages.mesh && args != nullptr) { - meshArguments = recordMeshArguments(context, commands, recorder, recorded, state, draw, indirect, countBarrier); + meshArguments = recordMeshArguments(context, commands, recorder, recorded, state, draw, indirect, countBarrier, inputs.restartTable); argumentBuffer = meshArguments->Handle(); + } else if (!inputs.restartTable.empty()) { + meshArguments = directMeshArguments(context, draw, inputs.restartTable); } else if (gpuIndirect) { rewritten = recordIndirectArguments(context, commands, recorder, recorded, indirect, scratch, argumentBuffer, argumentOffset, countBarrier); } @@ -2153,7 +2181,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& APS5_LOG_CHARS_OUT_DEBUG("Pipeline Begin OK"); resources->Bind(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->Layout()); APS5_LOG_CHARS_OUT_DEBUG("Resources bound"); - pushDrawConstants(*pipeline, commands, state, draw, shaders, *resources, nullptr, 0, meshArguments != nullptr ? meshArguments->DeviceAddress() : 0); + pushDrawConstants(*pipeline, commands, state, draw, shaders, *resources, nullptr, 0, meshArguments != nullptr ? meshArguments->DeviceAddress() : 0, !inputs.restartTable.empty()); APS5_LOG_CHARS_OUT_DEBUG("Push constants recorded"); recordDrawCommands(context, commands, state, draw, inputs, args != nullptr ? &indirect : nullptr, argumentBuffer, argumentOffset); if (pipeline->SplitsFaces()) { diff --git a/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp b/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp index 9cc2ceaa9..c92bd7fad 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp @@ -95,6 +95,9 @@ alignas(256) std::array Fan{}; alignas(256) std::array FanScrambled{}; alignas(256) std::array FanIndices{}; alignas(256) std::array FanRestart{}; +alignas(256) std::array Quads{}; +alignas(256) std::array QuadStrips{}; +alignas(256) std::array QuadStrips32{}; std::array VertexBufferDescriptor(const void* vertices, std::uint32_t count) { const auto address = reinterpret_cast(vertices); @@ -127,6 +130,7 @@ struct MeshDraw { std::uint32_t geometryPushBytes = ShaderRecompiler::MeshDrawPushOffsetBytes; bool restart = false; std::uint32_t waveSize = 64u; + VkCullModeFlags cull = VK_CULL_MODE_NONE; }; void DrawMesh(AgcDriver::VulkanDevice& device, const MeshDraw& setup) { @@ -178,7 +182,7 @@ void DrawMesh(AgcDriver::VulkanDevice& device, const MeshDraw& setup) { state.viewport = {0, static_cast(Height), static_cast(Width), -static_cast(Height), 0, 1}; state.negativeOneToOne = false; state.scissor = {{0, 0}, {Width, Height}}; - state.cullMode = VK_CULL_MODE_NONE; + state.cullMode = setup.cull; state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; state.blend.colorWriteMask = 15; state.blends = {state.blend}; @@ -205,6 +209,24 @@ void CheckFan(const char* what) { Require(PixelIs(0, Background) && PixelIs(Pixels.size() - 4u, Background), std::string(what) + ": the corners changed"); } +std::size_t QuadPixel(std::uint32_t quad, float across) { + const auto x = static_cast((static_cast(quad) + across * 0.8f) * Width / Columns); + return (static_cast(Height * 3u / 4u) * Width + x) * 4u; +} + +void CheckQuads(const char* what, std::uint32_t first, std::uint32_t count) { + for (std::uint32_t quad = 0; quad < Columns; ++quad) { + const bool drawn = quad >= first && quad < first + count; + for (const float across : {0.25f, 0.75f}) { + const auto offset = QuadPixel(quad, across); + Require(PixelIs(offset, drawn ? TriangleColor(quad) : Background), std::string(what) + ": quad " + std::to_string(quad) + (across < 0.5f ? " left" : " right") + " half pixel " + PixelText(offset)); + } + const auto gap = QuadPixel(quad, 1.12f); + Require(PixelIs(gap, Background), std::string(what) + ": a triangle crossed the restart after quad " + std::to_string(quad) + ", pixel " + PixelText(gap)); + } + Require(PixelIs((static_cast(Height / 4u) * Width + Width / 2u) * 4u, Background), std::string(what) + ": the upper half changed"); +} + void CheckTriangles(const char* what) { for (std::uint32_t triangle = 0; triangle < Triangles; ++triangle) { const auto offset = CellPixel(triangle); @@ -249,6 +271,22 @@ int main() { FanRestart[k] = FanIndices[k]; } FanRestart[FanRim] = 0xffffu; + for (std::uint32_t quad = 0; quad < Columns; ++quad) { + const auto color = TriangleColor(quad); + const std::array rgba{color[0] / 255.0f, color[1] / 255.0f, color[2] / 255.0f, 1.0f}; + const float left = -1.0f + 2.0f * static_cast(quad) / Columns; + const float right = left + 1.6f / Columns; + Quads[4 * quad] = {{left, -1.0f, 0.5f, 1.0f}, rgba}; + Quads[4 * quad + 1] = {{left, 0.0f, 0.5f, 1.0f}, rgba}; + Quads[4 * quad + 2] = {{right, -1.0f, 0.5f, 1.0f}, rgba}; + Quads[4 * quad + 3] = {{right, 0.0f, 0.5f, 1.0f}, rgba}; + for (std::uint32_t k = 0; k < 4; ++k) { + QuadStrips[5 * quad + k] = static_cast(4 * quad + k); + QuadStrips32[5 * quad + k] = 4 * quad + k; + } + QuadStrips[5 * quad + 4] = 0xffffu; + QuadStrips32[5 * quad + 4] = 0xffffffffu; + } const auto testDevice = OpenVulkanTestDevice(); if (!testDevice) return VulkanTestSkipped; @@ -332,6 +370,21 @@ int main() { } Require(PixelIs((static_cast(Height / 4u) * Width + Width / 2u) * 4u, Background), "triangle strip: the upper half changed"); + const auto quadBuffer = VertexBufferDescriptor(Quads.data(), static_cast(Quads.size())); + ClearPixels(); + DrawMesh(device, {strip, {reinterpret_cast(QuadStrips.data()), static_cast(QuadStrips.size()), 2, 1, 0, true}, quadBuffer, ShaderRecompiler::MeshDrawPushOffsetBytes, true, 64u, VK_CULL_MODE_FRONT_BIT}); + CheckQuads("16-bit triangle strips with restarts", 0, Columns); + ClearPixels(); + DrawMesh(device, {strip, {reinterpret_cast(QuadStrips32.data()), static_cast(QuadStrips32.size()), 4, 1, 0, true}, quadBuffer, ShaderRecompiler::MeshDrawPushOffsetBytes, true, 64u, VK_CULL_MODE_FRONT_BIT}); + CheckQuads("32-bit triangle strips with restarts", 0, Columns); + const std::array quadWords{15u, 1u, 5u, 0u, 0u}; + std::copy(quadWords.begin(), quadWords.end(), record); + AgcDriver::Pm4::DrawParameters quadDraw{reinterpret_cast(QuadStrips.data()), static_cast(QuadStrips.size()), 2, 1, 0, true}; + quadDraw.indirect = AgcDriver::Pm4::DrawParameters::IndirectDraw{reinterpret_cast(record), 0x25u, 20u, 20u, 1u, false, 0u, 0x280u, 0x280u, 0x280u, false, 0u}; + ClearPixels(); + DrawMesh(device, {strip, quadDraw, quadBuffer, ShaderRecompiler::MeshDrawPushOffsetBytes, true, 64u, VK_CULL_MODE_FRONT_BIT}); + CheckQuads("indirect triangle strips with restarts", 1, 3); + const ShaderRecompiler::MeshConfiguration fan{5u, 3u, 5u, 9u, 3u, 64u, 1024u, 0u, 4u}; ClearPixels(); DrawMesh(device, {fan, {0, static_cast(Fan.size()), 0, 1, 0, false}, VertexBufferDescriptor(Fan.data(), static_cast(Fan.size()))}); diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp index f08b2da82..e580b1118 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp @@ -79,6 +79,7 @@ enum class IrOpcode : std::uint16_t { RealtimeClock, MeshDrawParameter, MeshArgument, + MeshRestartStart, MeshAllocate, TessellationBase, GetTessellationAttribute, diff --git a/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp b/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp index ec6ddd79e..7abf9208a 100644 --- a/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp +++ b/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp @@ -120,6 +120,7 @@ constexpr std::array(IrOpcode::Count)> Meta makeMeta("RealtimeClock", U64), makeMeta("MeshDrawParameter", U32, U32), makeMeta("MeshArgument", U32, U32), + makeMeta("MeshRestartStart", U32, U32, U1), makeMeta("MeshAllocate", Void, U32), makeMeta("TessellationBase", U32, U32), makeMeta("GetTessellationAttribute", U32, U32, U32, U1), diff --git a/core/shader/recompiler/Recompiler.hpp b/core/shader/recompiler/Recompiler.hpp index e22a50162..ab6def974 100644 --- a/core/shader/recompiler/Recompiler.hpp +++ b/core/shader/recompiler/Recompiler.hpp @@ -278,6 +278,9 @@ inline constexpr std::uint32_t MeshArgumentAddressDword = 4; inline constexpr std::uint32_t MeshArgumentIndexCountDword = 3; inline constexpr std::uint32_t MeshArgumentFirstIndexDword = 4; inline constexpr std::uint32_t MeshArgumentBytes = 20; +inline constexpr std::uint32_t MeshIndexRestartTable = 0x100; +inline constexpr std::uint32_t MeshRestartLengthDword = MeshArgumentBytes / 4; +inline constexpr std::uint32_t MeshRestartTableDword = MeshRestartLengthDword + 1; inline constexpr std::uint32_t MeshIndexBufferUserWord = 4; inline constexpr std::uint32_t WorkgroupMemoryDescriptorSet = 1; diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp index 8e275b607..3513c1a8f 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp @@ -62,6 +62,7 @@ void EmitVoid(SpirvValueEmitContext& context); void EmitBarrier(SpirvEmitterState& state); std::uint32_t EmitMeshDrawParameter(SpirvValueEmitContext& ctx, const IrValue& inst); std::uint32_t EmitMeshArgument(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitMeshRestartStart(SpirvValueEmitContext& ctx, const IrValue& inst); std::uint32_t EmitGetTessellationAttribute(SpirvValueEmitContext& ctx, const IrValue& inst); void EmitSetTessellationAttribute(SpirvValueEmitContext& ctx, const IrValue& inst); std::uint32_t EmitGetUserData(SpirvEmitterState& state, ScalarReg reg); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp index d4e8eaa77..e851f59c3 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp @@ -294,6 +294,7 @@ void EmitDirectInstruction(SpirvValueEmitContext& ctx, const IrValue& inst) { case IrOpcode::RealtimeClock: return Invoke(EmitRealtimeClock, ctx, inst); case IrOpcode::MeshDrawParameter: return Invoke(EmitMeshDrawParameter, ctx, inst); case IrOpcode::MeshArgument: return Invoke(EmitMeshArgument, ctx, inst); + case IrOpcode::MeshRestartStart: return Invoke(EmitMeshRestartStart, ctx, inst); case IrOpcode::MeshAllocate: return Invoke(EmitMeshAllocate, ctx, inst); case IrOpcode::TessellationBase: return Invoke(EmitTessellationBase, ctx, inst); case IrOpcode::GetTessellationAttribute: return Invoke(EmitGetTessellationAttribute, ctx, inst); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp index baf755e18..07f3ca948 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp @@ -1053,12 +1053,9 @@ std::uint32_t EmitMeshDrawParameter(SpirvValueEmitContext& ctx, const IrValue& i return result; } -std::uint32_t EmitMeshArgument(SpirvValueEmitContext& ctx, const IrValue& inst) { - auto& state = ctx.state; - const auto index = inst.Argument(0)->ImmediateU32(); - if (state.program.Resources().stage != IrShaderStage::Mesh || index >= MeshArgumentBytes / 4u) { - ctx.Fail(inst, "invalid mesh argument"); - } +namespace { + +std::uint32_t MeshArgumentAddress(SpirvEmitterState& state) { const auto push = [&](std::uint32_t dword) { const auto pointer = state.module.AllocateId(); state.module.AddFunction(spv::OpAccessChain, TypePushConstantElementPointer(state), pointer, state.pushConstantVariable, ConstantU32(state, 0u), ConstantU32(state, MeshDrawPushOffsetBytes / 4u + dword)); @@ -1068,7 +1065,42 @@ std::uint32_t EmitMeshArgument(SpirvValueEmitContext& ctx, const IrValue& inst) }; const auto low = push(MeshArgumentAddressDword); const auto high = push(MeshArgumentAddressDword + 1u); - const auto address = Binary(state, spv::OpBitwiseOr, TypeScalarU64(state), low, Binary(state, spv::OpShiftLeftLogical, TypeScalarU64(state), high, ConstantU32(state, 32u))); + return Binary(state, spv::OpBitwiseOr, TypeScalarU64(state), low, Binary(state, spv::OpShiftLeftLogical, TypeScalarU64(state), high, ConstantU32(state, 32u))); +} + +std::uint32_t LoadMeshArgumentWord(SpirvEmitterState& state, std::uint32_t address, std::uint32_t dword) { + const auto element = Binary(state, spv::OpIAdd, TypeScalarU64(state), address, Binary(state, spv::OpShiftLeftLogical, TypeScalarU64(state), Unary(state, spv::OpUConvert, TypeScalarU64(state), dword), ConstantU32(state, 2u))); + const auto pointer = state.module.AllocateId(); + state.module.AddFunction(spv::OpConvertUToPtr, TypePointer(state, spv::StorageClassPhysicalStorageBuffer, TypeU32(state)), pointer, element); + const auto loaded = state.module.AllocateId(); + state.module.AddFunction(spv::OpLoad, TypeU32(state), loaded, pointer, spv::MemoryAccessAlignedMask, 4u); + return loaded; +} + +} + +std::uint32_t EmitMeshRestartStart(SpirvValueEmitContext& ctx, const IrValue& inst) { + auto& state = ctx.state; + if (state.program.Resources().stage != IrShaderStage::Mesh) { + ctx.Fail(inst, "mesh restart table read outside a mesh shader"); + } + return EmitValueOrZeroIfCondition(state, ctx.Arg(inst, 1), [&]() { + const auto address = MeshArgumentAddress(state); + const auto length = LoadMeshArgumentWord(state, address, ConstantU32(state, MeshRestartLengthDword)); + const auto last = Binary(state, spv::OpISub, TypeU32(state), length, ConstantU32(state, 1u)); + const auto position = state.module.AllocateId(); + state.module.AddFunction(spv::OpExtInst, TypeU32(state), position, GlslStd450(state), GLSLstd450UMin, ctx.Arg(inst, 0), last); + return LoadMeshArgumentWord(state, address, Binary(state, spv::OpIAdd, TypeU32(state), position, ConstantU32(state, MeshRestartTableDword))); + }); +} + +std::uint32_t EmitMeshArgument(SpirvValueEmitContext& ctx, const IrValue& inst) { + auto& state = ctx.state; + const auto index = inst.Argument(0)->ImmediateU32(); + if (state.program.Resources().stage != IrShaderStage::Mesh || index >= MeshArgumentBytes / 4u) { + ctx.Fail(inst, "invalid mesh argument"); + } + const auto address = MeshArgumentAddress(state); const auto present = Binary(state, spv::OpINotEqual, TypeBool(state), address, BdaConstant(state, 0u)); const auto before = state.currentLabel; const auto loadLabel = state.module.AllocateId(); diff --git a/core/shader/recompiler/Translation/src/InstructionTranslator.cpp b/core/shader/recompiler/Translation/src/InstructionTranslator.cpp index b764b46af..f6b44497a 100644 --- a/core/shader/recompiler/Translation/src/InstructionTranslator.cpp +++ b/core/shader/recompiler/Translation/src/InstructionTranslator.cpp @@ -262,12 +262,20 @@ void emitEntryPrologue(IrProgram& program, IrBlock& entryBlock, const TranslateO IrValue& primitiveCount = minimum(subtractSaturate(primitives, waveBase), u32(options.waveSize)); IrValue& waveInfo = entryIr.BitwiseOr(entryIr.ShiftLeftLogical(wave, u32(24u)), u32((totalThreads / options.waveSize) << 28u)); entryIr.SetScalarReg(static_cast(3), entryIr.BitwiseOr(waveInfo, entryIr.BitwiseOr(entryIr.ShiftLeftLogical(primitiveCount, u32(8u)), vertexCount))); - IrValue& parity = mesh.inputPrimitive == kTriStripPrimitiveType ? entryIr.BitwiseAnd(entryIr.IAdd(firstPrimitive, local), u32(1u)) : u32(0u); + const bool strip = mesh.inputPrimitive == kTriStripPrimitiveType; + IrValue& indexWord = draw(3u); + IrValue& stripPosition = entryIr.IAdd(entryIr.IAdd(firstPrimitive, local), firstIndex); + IrValue& restartTable = entryIr.INotEqual(entryIr.BitwiseAnd(indexWord, u32(MeshIndexRestartTable)), u32(0u)); + IrValue& stripStart = strip ? entryIr.Emit(IrOpcode::MeshRestartStart, IrOpcodeType(IrOpcode::MeshRestartStart), {&entryIr.IAdd(stripPosition, u32(2u)), &restartTable}) : u32(0u); + IrValue& parity = strip ? entryIr.BitwiseAnd(entryIr.ISub(stripPosition, entryIr.Emit(IrOpcode::UMax32, IrOpcodeType(IrOpcode::UMax32), {&stripStart, &firstIndex})), u32(1u)) : u32(0u); IrValue& vertex = entryIr.IMul(local, step); IrValue& item = u32(mesh.esgsItemSize); - IrValue& first = fan ? entryIr.IMul(entryIr.IAdd(vertex, u32(1u)), item) : entryIr.IMul(entryIr.IAdd(vertex, parity), item); - IrValue& second = fan ? entryIr.IMul(entryIr.IAdd(vertex, u32(2u)), item) : size >= 2u ? entryIr.IMul(entryIr.ISub(entryIr.IAdd(vertex, u32(1u)), parity), item) : u32(0u); - IrValue& third = fan ? u32(0u) : size == 3u ? entryIr.IMul(entryIr.IAdd(vertex, u32(2u)), item) : u32(0u); + const auto unlessRestarted = [&](IrValue& offset) -> IrValue& { + return strip ? entryIr.Select(entryIr.ULessThan(stripPosition, stripStart), entryIr.IMul(vertex, item), offset) : offset; + }; + IrValue& first = fan ? entryIr.IMul(entryIr.IAdd(vertex, u32(1u)), item) : unlessRestarted(entryIr.IMul(entryIr.IAdd(vertex, parity), item)); + IrValue& second = fan ? entryIr.IMul(entryIr.IAdd(vertex, u32(2u)), item) : size >= 2u ? unlessRestarted(entryIr.IMul(entryIr.ISub(entryIr.IAdd(vertex, u32(1u)), parity), item)) : u32(0u); + IrValue& third = fan ? u32(0u) : size == 3u ? unlessRestarted(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))); entryIr.SetVectorReg(static_cast(2), entryIr.IAdd(firstPrimitive, local)); @@ -277,7 +285,7 @@ void emitEntryPrologue(IrProgram& program, IrBlock& entryBlock, const TranslateO throw std::runtime_error("mesh shader translation requires the merged program's eight hidden user words"); } IrValue& inputVertex = fan ? entryIr.Select(entryIr.IEqual(local, u32(0u)), u32(0u), entryIr.IAdd(firstVertex, local)) : entryIr.IAdd(firstVertex, local); - IrValue& indexBytes = draw(3u); + IrValue& indexBytes = entryIr.BitwiseAnd(indexWord, u32(0xffu)); IrValue& indexed = entryIr.INotEqual(indexBytes, u32(0u)); IrValue& byteOffset = entryIr.IMul(entryIr.IAdd(inputVertex, firstIndex), indexBytes); IrValue& indexResource = entryIr.Emit(IrOpcode::GetBufferResource, IrOpcodeType(IrOpcode::GetBufferResource), {&entryIr.GetUserData(static_cast(4)), &entryIr.GetUserData(static_cast(5)), &entryIr.GetUserData(static_cast(6)), &entryIr.GetUserData(static_cast(7))}); @@ -288,7 +296,9 @@ void emitEntryPrologue(IrProgram& program, IrBlock& entryBlock, const TranslateO for (std::uint32_t reg = 4u; reg < 8u; reg++) { entryIr.SetScalarReg(static_cast(reg), u32(0u)); } - entryIr.SetVectorReg(static_cast(5), entryIr.IAdd(draw(1u), entryIr.Select(indexed, index, inputVertex))); + IrValue& restartIndex = entryIr.Select(entryIr.IEqual(indexBytes, u32(2u)), u32(0xffffu), u32(0xffffffffu)); + IrValue& fetchedIndex = entryIr.Select(entryIr.LogicalAnd(restartTable, entryIr.IEqual(index, restartIndex)), u32(0u), index); + entryIr.SetVectorReg(static_cast(5), entryIr.IAdd(draw(1u), entryIr.Select(indexed, fetchedIndex, inputVertex))); entryIr.SetVectorReg(static_cast(6), u32(0u)); entryIr.SetVectorReg(static_cast(7), u32(0u)); entryIr.SetVectorReg(static_cast(8), entryIr.IAdd(draw(2u), builtin(StageInputKind::WorkgroupId, 1u)));