diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 09793e98c..3989edc63 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -46,7 +46,7 @@ add_custom_command( VERBATIM ) -add_library(libSceAgcDriver SHARED EXCLUDE_FROM_ALL +set(agcDriverSources ${CMAKE_CURRENT_SOURCE_DIR}/Unimplemented.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/DisplayBuffer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/Driver.cpp @@ -95,6 +95,10 @@ add_library(libSceAgcDriver SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Submit/src/Dcb.cpp ) +add_library(libSceAgcDriver SHARED EXCLUDE_FROM_ALL + ${agcDriverSources} +) + target_include_directories(libSceAgcDriver PRIVATE ${LIBS_INCLUDE_DIR} ${agcTextureDetileGeneratedRoot} @@ -303,6 +307,20 @@ target_link_libraries(agc_driver_bda_device_tests PRIVATE SDL2-static libc shade set_target_properties(agc_driver_bda_device_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_driver_bda_device_tests) +add_executable(agc_driver_mesh_tests EXCLUDE_FROM_ALL + tests/Mesh.cpp + ${agcDriverSources} +) +target_include_directories(agc_driver_mesh_tests PRIVATE + ${LIBS_INCLUDE_DIR} + ${agcTextureDetileGeneratedRoot} + ${CMAKE_SOURCE_DIR}/3rdparty/Vulkan-Headers/include + ${CMAKE_SOURCE_DIR}/3rdparty/SPIRV-Headers/include +) +target_link_libraries(agc_driver_mesh_tests PRIVATE shader_recompiler_agc_driver SDL2-static libc libkernel) +set_target_properties(agc_driver_mesh_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_driver_mesh_tests) + if(BUILD_TESTING) foreach(agcTest IN ITEMS agc_driver_tests agc_shader_memory_tests agc_structurizer_tests agc_dpp_write_tests agc_shader_disk_cache_tests agc_driver_flip_tests agc_driver_pm4_tests) string(REGEX REPLACE "_tests$" "" agcTestName "${agcTest}") diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp index e1c0f4904..65f93ed4d 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp @@ -1426,7 +1426,7 @@ private: const auto sameMesh = [](const std::optional& 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; + 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; }; const auto sameTess = [](const std::optional& x, const std::optional& y) { if (x.has_value() != y.has_value()) return false; @@ -4203,6 +4203,15 @@ private: const auto& graphics = decode->state; const auto& pixel = decode->pixel; std::vector programs = decode->programs; + const auto setMeshIndexBuffer = [&](const Pm4::DrawParameters& parameters) { + if (!graphics.stages.mesh) return; + auto& words = programs.front().userData; + require(programs.front().firstUserSgpr == 0 && words.size() >= ShaderRecompiler::MeshIndexBufferUserWord + 4, "mesh program lacks the hidden user words"); + const auto descriptor = Graphics::MeshIndexBufferDescriptor(parameters, programs.front().binary.codeAddress); + std::copy(descriptor.begin(), descriptor.end(), words.begin() + ShaderRecompiler::MeshIndexBufferUserWord); + }; + if (!drawParameters.indirect) setMeshIndexBuffer(drawParameters); + else if (graphics.stages.mesh) setMeshIndexBuffer(Pm4::DrawParameters{drawParameters.indexAddress, std::max(drawParameters.indexCount, 1u), drawParameters.indexSize, 1, 0, drawParameters.indexed}); const std::vector& roles = decode->roles; phase(DrawRowDecode); // The user word an SH register named by an indirect draw packet lands in: the program whose @@ -4297,7 +4306,7 @@ private: 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}) mix(value); + for (const auto value : {mesh.inputPrimitive, mesh.primitivesPerGroup, mesh.verticesPerGroup, mesh.maxVertices, mesh.maxPrimitives, mesh.threadsPerGroup, mesh.ldsSizeDwords, mesh.provokingVertex, mesh.esgsItemSize}) mix(value); } mix(graphics.stages.tessellation.has_value()); if (graphics.stages.tessellation) { @@ -4483,7 +4492,7 @@ private: program.binary, {waveSize, program.firstUserSgpr, program.userData, std::nullopt, program.binary.stage == Stage::Fragment ? std::optional(pixel) : std::nullopt, vertexInfos[i], memory}, localDevice->Target(), - {0, 0, pushOffset, Graphics::PipelinePushConstantBytes - pushOffset}, + {0, 0, pushOffset, (graphics.stages.mesh ? ShaderRecompiler::MeshDrawPushOffsetBytes : Graphics::PipelinePushConstantBytes) - pushOffset}, ShaderRecompiler::GraphicsCompileContext{program.firstUserSgpr, linked, graphics.stages.mesh, graphics.stages.tessellation, {drawParameters.indexAddress, drawParameters.indexCount, drawParameters.indexSize, drawParameters.instanceCount}} }; const auto waitedBefore = TraceCapSync() || profile ? Graphics::Recorder::ThreadWaitedMs() : 0.0; @@ -4810,6 +4819,10 @@ private: } else { direct.firstVertex = indirect.indxOffset; } + if (graphics.stages.mesh) { + setMeshIndexBuffer(direct); + patched.insert(0); + } for (const auto programIndex : patched) { auto& result = results[resultIndex[programIndex]]; const auto pushBytes = result.pushConstants.size(); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp index 63724bec1..531d44eee 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp @@ -14,6 +14,8 @@ namespace AgcDriver::Graphics { // APS5_NO_DRAW_RECIPE=1). void Draw(const Context& context, const State& state, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots = {}, std::shared_ptr* recipe = nullptr); +std::array MeshIndexBufferDescriptor(const Pm4::DrawParameters& draw, std::uint64_t unreadAddress); + // Why DrawWithRecipe did not record from the recipe (the caller then runs Draw): the draw is not // recordable (no recorder, APS5_SYNC_DRAWS, APS5_DUMP_TARGETS), a resident target is gone from the // storage cache, the template is gone, the pipeline is gone from the pipeline store, or the diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Shaders.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Shaders.hpp index 00d19fc87..307dbde3c 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Shaders.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Shaders.hpp @@ -49,7 +49,7 @@ inline VkShaderStageFlags PushConstantStages(std::span sha VkShaderStageFlags result = 0; for (const auto& shader : shaders) { Require(shader.program != nullptr, "missing compiled shader"); - if (!shader.program->pushConstants.empty()) result |= VulkanStage(shader.stage); + if (!shader.program->pushConstants.empty() || shader.stage == ShaderRecompiler::ShaderStage::Mesh) result |= VulkanStage(shader.stage); } return result; } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp index dffd822f2..5c17647f1 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp @@ -122,16 +122,15 @@ struct DrawKeyRange { std::uint32_t first; std::uint32_t count; }; -inline constexpr std::array DrawKeyRegisters{{ +inline constexpr std::array DrawKeyRegisters{{ {RegisterBank::Context, 0x000, 1}, {RegisterBank::Context, 0x002, 1}, {RegisterBank::Context, 0x007, 1}, {RegisterBank::Context, 0x00a, 4}, {RegisterBank::Context, 0x010, 6}, {RegisterBank::Context, 0x01a, 4}, {RegisterBank::Context, 0x080, 4}, {RegisterBank::Context, 0x08c, 4}, {RegisterBank::Context, 0x090, 2}, {RegisterBank::Context, 0x094, 2}, {RegisterBank::Context, 0x0b4, 2}, {RegisterBank::Context, 0x105, 4}, {RegisterBank::Context, 0x10b, 3}, {RegisterBank::Context, 0x10f, 6}, // SPI_PS_INPUT_CNTL_0..31, SPI_PS_INPUT_ENA/ADDR, SPI_PS_IN_CONTROL, SPI_SHADER_POS/Z/COL_FORMAT, // CB_BLEND0..7_CONTROL, GE_MAX_OUTPUT_PER_SUBGROUP. {RegisterBank::Context, 0x191, 32}, {RegisterBank::Context, 0x1b3, 2}, {RegisterBank::Context, 0x1b6, 1}, {RegisterBank::Context, 0x1c3, 3}, {RegisterBank::Context, 0x1e0, 8}, {RegisterBank::Context, 0x1ff, 1}, // DB_DEPTH_CONTROL .. PA_CL_VS_OUT_CNTL, PA_SC_MODE_CNTL_0/1, VGT_GS_MODE, VGT_GS_VERT_ITEMSIZE, - // VGT_SHADER_STAGES_EN/GS_ONCHIP, VGT_TF_PARAM/DB_ALPHA_TO_MASK, PA_SC_AA_CONFIG and // PA_SU_VTX_CNTL, the sample masks, PA_SC_CONSERVATIVE_RASTERIZATION_CNTL. - {RegisterBank::Context, 0x200, 8}, {RegisterBank::Context, 0x292, 2}, {RegisterBank::Context, 0x29b, 1}, {RegisterBank::Context, 0x2ce, 1}, {RegisterBank::Context, 0x2d5, 2}, {RegisterBank::Context, 0x2db, 2}, {RegisterBank::Context, 0x2f8, 2}, {RegisterBank::Context, 0x30e, 2}, {RegisterBank::Context, 0x313, 1}, + {RegisterBank::Context, 0x200, 8}, {RegisterBank::Context, 0x292, 2}, {RegisterBank::Context, 0x29b, 1}, {RegisterBank::Context, 0x2ab, 1}, {RegisterBank::Context, 0x2ce, 1}, {RegisterBank::Context, 0x2d5, 2}, {RegisterBank::Context, 0x2db, 2}, {RegisterBank::Context, 0x2f8, 2}, {RegisterBank::Context, 0x30e, 2}, {RegisterBank::Context, 0x313, 1}, // CB_COLOR0..7_BASE .. DCC_BASE (15 words a slot), CB_COLOR0..7_BASE_EXT, DCC_BASE_EXT, ATTRIB2, ATTRIB3. {RegisterBank::Context, 0x318, 0x78}, {RegisterBank::Context, 0x390, 8}, {RegisterBank::Context, 0x3a8, 0x18}, // The pixel program address, RSRC2 and user words; the geometry-back user pointer and program diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 8b46b0812..88b37d143 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -503,8 +503,10 @@ void CheckBufferAliases(std::span shaders, const ColorTarg const auto address = descriptor.Base48(); const auto size = descriptor.GetSize(); if (size == 0 || address == 0) continue; + const auto element = offset / 4; + const bool written = element >= binding.bufferWritten.size() || binding.bufferWritten[element]; Require(!overlap(address, size, target.address, target.bytes), "shader buffer aliases the render target"); - Require(!overlap(address, size, indexAddress, indexBytes), "writable shader buffer aliases the index buffer"); + Require(!written || !overlap(address, size, indexAddress, indexBytes), "writable shader buffer aliases the index buffer"); } } } @@ -512,6 +514,14 @@ void CheckBufferAliases(std::span shaders, const ColorTarg } +std::array MeshIndexBufferDescriptor(const Pm4::DrawParameters& draw, std::uint64_t unreadAddress) { + const auto address = draw.indexed ? draw.indexAddress : unreadAddress; + const auto bytes = draw.indexed ? (static_cast(draw.indexCount) * draw.indexSize + 3u) & ~std::uint64_t{3} : 4u; + Require(address != 0 && bytes != 0 && bytes <= 0xffffffffu && (address >> 48u) == 0, "invalid mesh index buffer range"); + constexpr std::uint32_t RawWord3 = 0x31016facu; + return {static_cast(address), static_cast(address >> 32u) & 0xffffu, static_cast(bytes), RawWord3}; +} + std::shared_ptr>> DrawCopiedWriters() { // Never destroyed: a completion action of a batch still in flight at static teardown may erase from it. static auto* const writers = new std::shared_ptr>>(std::make_shared>>()); @@ -655,7 +665,6 @@ DrawInputs prepareDrawInputs(const Context& context, const State& state, const P APS5_LOG_CHARS_OUT_DEBUG("ValidateShaders OK"); APS5_LOG_OUT_DEBUG("PipelineStages=0x%x", static_cast(inputs.shaderStages)); if (state.stages.mesh) { - Require(draw.firstVertex == 0 && draw.firstInstance == 0, "mesh draw offsets are unsupported"); APS5_LOG_CHARS_OUT_DEBUG("Mesh path"); Require(context.meshShader, "device does not support mesh shaders"); const auto& mesh = *state.stages.mesh; @@ -995,6 +1004,22 @@ 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) { + auto block = bytes != nullptr ? *bytes : AssemblePushConstants(shaders); + if (bytes == nullptr) { + resources.PatchPushConstants(block); + stages = PushConstantStages(shaders); + } + if (!state.stages.mesh) { + pipeline.PushConstants(commands, stages, block); + return; + } + const std::array words{draw.indexCount, draw.firstVertex, draw.firstInstance, draw.indexed ? draw.indexSize : 0u, 0u, 0u}; + static_assert(ShaderRecompiler::MeshDrawPushOffsetBytes + ShaderRecompiler::MeshDrawPushBytes == PipelinePushConstantBytes); + std::memcpy(block.data() + ShaderRecompiler::MeshDrawPushOffsetBytes, words.data(), sizeof(words)); + pipeline.PushConstants(commands, stages | VK_SHADER_STAGE_MESH_BIT_EXT, block); +} + // The record of a draw whose targets are all resident (`lean`: recorded into the device's open // batch, rendering in the general layout): one memory barrier before the pass (earlier writes to // the attachments and the draw's inputs visible to it) and one after it, recorded by the recorder @@ -1156,12 +1181,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"); - if (record.pushBytes != nullptr) record.pipeline->PushConstants(commands, record.pushStages, *record.pushBytes); - else { - auto bytes = AssemblePushConstants(shaders); - resources.PatchPushConstants(bytes); - record.pipeline->PushConstants(commands, PushConstantStages(shaders), bytes); - } + pushDrawConstants(*record.pipeline, commands, state, draw, shaders, resources, record.pushBytes, record.pushStages); APS5_LOG_CHARS_OUT_DEBUG("Push constants recorded"); recordDrawCommands(context, commands, state, draw, inputs, record.indirect, argumentBuffer, argumentOffset); if (args != nullptr) CountIndirectDraw(record.indirect->path, record.indirect->readMs, rewritten); @@ -1580,9 +1600,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"); - auto pushBytes = AssemblePushConstants(shaders); - resources->PatchPushConstants(pushBytes); - pipeline->PushConstants(commands, PushConstantStages(shaders), pushBytes); + pushDrawConstants(*pipeline, commands, state, draw, shaders, *resources, nullptr, 0); APS5_LOG_CHARS_OUT_DEBUG("Push constants recorded"); recordDrawCommands(context, commands, state, draw, inputs, args != nullptr ? &indirect : nullptr, argumentBuffer, argumentOffset); if (args != nullptr) CountIndirectDraw(indirect.path, indirect.readMs, rewritten); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp index 8481a4e0e..36772cf21 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp @@ -57,7 +57,8 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput if (state.stages.mesh) { Require(context.meshShader, "device does not support VK_EXT_mesh_shader"); const auto& mesh = *state.stages.mesh; - Require(mesh.threadsPerGroup <= context.meshLimits.maxMeshWorkGroupInvocations && mesh.threadsPerGroup <= context.meshLimits.maxMeshWorkGroupSize[0], "mesh workgroup exceeds device limits"); + const auto invocations = state.stages.vertexWaveSize == 64u && context.subgroup.subgroupSize == 32u ? mesh.threadsPerGroup / 2u : mesh.threadsPerGroup; + Require(invocations <= context.meshLimits.maxMeshWorkGroupInvocations && invocations <= context.meshLimits.maxMeshWorkGroupSize[0], "mesh workgroup exceeds device limits"); Require(mesh.maxVertices <= context.meshLimits.maxMeshOutputVertices && mesh.maxPrimitives <= context.meshLimits.maxMeshOutputPrimitives && static_cast(mesh.ldsSizeDwords) * 4 <= context.meshLimits.maxMeshSharedMemorySize, "mesh output or LDS exceeds device limits"); } const auto pushStages = PushConstantStages(shaders); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index 95e471b7a..2fd5ee157 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -2034,10 +2034,8 @@ std::size_t ShaderResources::addGuestBuffer(std::span words Require(target == nullptr || !overlap(address, size, target->address, target->bytes), "shader buffer aliases the render target"); // APS5_ALL_BUFFERS_WRITTEN=1: every element is noted as written, as before bufferWritten existed. static const bool allWritten = std::getenv("APS5_ALL_BUFFERS_WRITTEN") != nullptr; + Require(!written || !overlap(address, size, indexAddress, indexBytes), "writable shader buffer aliases the index buffer"); written = written || allWritten; - // Unconditional (also for read-only elements): Draw.cpp's CheckBufferAliases repeats this check - // on every resource-cache hit, and a draw must fail its hit exactly when it fails its build. - Require(!overlap(address, size, indexAddress, indexBytes), "writable shader buffer aliases the index buffer"); if (written) guestMemory.AddWritable(address, size, atomic); else { guestMemory.AddReadable(address, size); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp index 502b00981..76b3f374d 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp @@ -349,7 +349,9 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi if (mesh) { Require(state.stages.mesh.has_value(), "mesh configuration is missing"); const auto& config = *state.stages.mesh; - mode(spv::ExecutionModeLocalSize, {config.threadsPerGroup, 1, 1}); + const auto local = module.modes.find(spv::ExecutionModeLocalSize); + const bool paired = state.stages.vertexWaveSize == 64u && subgroup.subgroupSize == 32u && local != module.modes.end() && local->second == std::vector{config.threadsPerGroup / 2u, 1, 1}; + mode(spv::ExecutionModeLocalSize, {paired ? config.threadsPerGroup / 2u : config.threadsPerGroup, 1, 1}); mode(spv::ExecutionModeOutputVertices, {config.maxVertices}); mode(spv::ExecutionModeOutputPrimitivesEXT, {config.maxPrimitives}); mode(spv::ExecutionModeOutputTrianglesEXT, {}); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index 0e7e3e92f..cf340973a 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -363,7 +363,10 @@ ShaderStages DecodeShaderStages(const QueueState& queue) { 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"); - result.mesh = ShaderRecompiler::MeshConfiguration{primitive, groupPrimitives, (groupPrimitives - 1u) * inputStep + inputSize, maxVertices, primitives * (verticesPerPrimitive - 2u), ((maxVertices + result.vertexWaveSize - 1u) / result.vertexWaveSize) * result.vertexWaveSize, ((resources >> 19u) & 0xffu) * 128u, 0}; + 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}; } return result; } diff --git a/core/libs/prx/libSceAgcDriver/PostCompile.cmake b/core/libs/prx/libSceAgcDriver/PostCompile.cmake index b48b71293..3cabf5ccd 100644 --- a/core/libs/prx/libSceAgcDriver/PostCompile.cmake +++ b/core/libs/prx/libSceAgcDriver/PostCompile.cmake @@ -10,19 +10,19 @@ add_custom_command( VERBATIM ) -foreach(agcTarget IN ITEMS libSceAgcDriver agc_driver_visual_test agc_driver_graphics_tests agc_driver_bda_device_tests) +foreach(agcTarget IN ITEMS libSceAgcDriver agc_driver_visual_test agc_driver_graphics_tests agc_driver_bda_device_tests agc_driver_mesh_tests) if(TARGET ${agcTarget}) target_sources(${agcTarget} PRIVATE Graphics/src/BufferPool.cpp) endif() endforeach() -foreach(agcTarget IN ITEMS libSceAgcDriver agc_driver_visual_test agc_driver_graphics_tests) +foreach(agcTarget IN ITEMS libSceAgcDriver agc_driver_visual_test agc_driver_graphics_tests agc_driver_mesh_tests) if(TARGET ${agcTarget}) target_sources(${agcTarget} PRIVATE Graphics/src/TextureDetilerDescriptors.cpp Graphics/src/TextureCache.cpp) endif() endforeach() -foreach(agcTarget IN ITEMS libSceAgcDriver agc_driver_visual_test agc_driver_graphics_tests agc_driver_bda_device_tests) +foreach(agcTarget IN ITEMS libSceAgcDriver agc_driver_visual_test agc_driver_graphics_tests agc_driver_bda_device_tests agc_driver_mesh_tests) if(TARGET ${agcTarget}) target_sources(${agcTarget} PRIVATE Graphics/src/GpuColorTransfer.cpp ${agcColorTransferHeader}) endif() diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 77359edc7..30d18bddb 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -188,10 +188,15 @@ void ShaderStageTests() { queue.context[0x1ff] = 64; queue.context[0x2ce] = 3; queue.context[0x29b] = 2; + queue.context[0x2ab] = 4; queue.shader[0x8a] = 3u << 29u; queue.shader[0x8b] = 3u << 16u; 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"); + queue.context[0x2ab] = 0; + expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "invalid VGT_ESGS_RING_ITEMSIZE"); + queue.context[0x2ab] = 4; queue.userConfig[0x25b] = 0; expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "invalid geometry subgroup"); queue = makeState(); diff --git a/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp b/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp new file mode 100644 index 000000000..129df2015 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp @@ -0,0 +1,233 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "Recompiler.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Width = 192; +constexpr std::uint32_t Height = 128; +constexpr std::array Background{16, 24, 40, 255}; +alignas(256) std::array Pixels{}; + +alignas(256) constexpr std::array GeometryCode{ + 0x8f6a9003, 0x94fe6ac1, 0xbf88000b, 0xd7650006, 0x000100c1, 0xd7660006, 0x00020cc1, 0x93ebff03, + 0x00040018, 0xd7460006, 0x04190c6b, 0x340c0c82, 0xd8340000, 0x00000506, 0xbf8cc07f, 0xbefe04c1, + 0xbf8a0000, 0x938dff02, 0x00090016, 0x9382ff03, 0x00040018, 0x938cff03, 0x00080008, 0xd7650009, + 0x000100c1, 0xd7660009, 0x000212c1, 0xd746000a, 0x04250c02, 0x7da8120c, 0xbf88002e, 0x361600ff, + 0x0000ffff, 0x2c180090, 0x361a02ff, 0x0000ffff, 0xd8d80000, 0x0b00000b, 0xd8d80000, 0x0c00000c, + 0xd8d80000, 0x0d00000d, 0xbf8cc07f, 0xe0382000, 0x8002100b, 0xe0382010, 0x8002140b, 0xe0382000, + 0x8002180c, 0xe0382010, 0x80021c0c, 0xe0382000, 0x8002200d, 0xe0382010, 0x8002240d, 0x161c1483, + 0x161e14ff, 0x00000060, 0xbf8c3f70, 0xdb7c0400, 0x0000100f, 0xdb7c0410, 0x0000140f, 0xdb7c0420, + 0x0000180f, 0xdb7c0430, 0x00001c0f, 0xdb7c0440, 0x0000200f, 0xdb7c0450, 0x0000240f, 0x4a501c81, + 0x4a521c82, 0x3450508a, 0x34525294, 0xd772002a, 0x04a6510e, 0xbf8cc07f, 0xbefe04c1, 0xbf8a0000, + 0x930e830d, 0xbf078002, 0xbf850003, 0x8f0f8c0d, 0x887c0f0e, 0xbf900009, 0x7da8140d, 0xbf880002, + 0xf8000941, 0x0000002a, 0xbefe04c1, 0x7da8140e, 0xbf88000a, 0x34561485, 0xdbfc0400, 0x2c00002b, + 0xdbfc0410, 0x3000002b, 0xbf8cc07f, 0xf80008cf, 0x2f2e2d2c, 0xf800020f, 0x33323130, 0xbf810000, +}; + +alignas(256) constexpr std::array PixelCode{ + 0xc8020002, 0xc8060102, 0xc80a0202, 0xc80e0302, 0xf800180f, 0x03020100, 0xbf810000, +}; + +struct Vertex { + std::array position; + std::array color; +}; + +constexpr std::uint32_t Columns = 8; +constexpr std::uint32_t Rows = 5; +constexpr std::uint32_t Triangles = Columns * Rows; + +std::array TriangleColor(std::uint32_t triangle) { + return {static_cast(40u + 29u * triangle), static_cast(200u + 53u * triangle), static_cast(triangle % 2u == 0u ? 60u : 200u), 255}; +} + +std::array TriangleVertices(std::uint32_t triangle) { + const float cellWidth = 2.0f / Columns; + const float cellHeight = 2.0f / Rows; + const float x = -1.0f + cellWidth * static_cast(triangle % Columns); + const float y = -1.0f + cellHeight * static_cast(triangle / Columns); + const auto color = TriangleColor(triangle); + const std::array rgba{color[0] / 255.0f, color[1] / 255.0f, color[2] / 255.0f, 1.0f}; + return {{ + {{x + 0.1f * cellWidth, y + 0.1f * cellHeight, 0.5f, 1.0f}, rgba}, + {{x + 0.9f * cellWidth, y + 0.1f * cellHeight, 0.5f, 1.0f}, rgba}, + {{x + 0.5f * cellWidth, y + 0.9f * cellHeight, 0.5f, 1.0f}, rgba}, + }}; +} + +std::size_t CellPixel(std::uint32_t triangle) { + const auto column = triangle % Columns; + const auto row = triangle / Columns; + const auto x = (column * Width + Width / 2u) / Columns; + const auto y = Height - 1u - (row * Height + (Height * 4u) / 10u) / Rows; + return (static_cast(y) * Width + x) * 4u; +} + +alignas(256) std::array Scrambled{}; +alignas(256) std::array Indices{}; +alignas(256) std::array Ordered{}; +alignas(256) std::array Strip{}; + +std::array VertexBufferDescriptor(const void* vertices, std::uint32_t count) { + const auto address = reinterpret_cast(vertices); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (32u << 16u), count, 0x01016facu}; +} + +void ClearPixels() { + for (std::size_t i = 0; i < Pixels.size(); i += 4) { + for (std::size_t c = 0; c < 4; ++c) Pixels[i + c] = std::byte{Background[c]}; + } +} + +bool PixelIs(std::size_t offset, const std::array& color) { + for (std::size_t c = 0; c < 4; ++c) { + if (std::to_integer(Pixels[offset + c]) != color[c]) return false; + } + return true; +} + +std::string PixelText(std::size_t offset) { + char text[64]; + std::snprintf(text, sizeof(text), "(%u, %u, %u, %u)", std::to_integer(Pixels[offset]), std::to_integer(Pixels[offset + 1]), std::to_integer(Pixels[offset + 2]), std::to_integer(Pixels[offset + 3])); + return text; +} + +struct MeshDraw { + ShaderRecompiler::MeshConfiguration mesh; + AgcDriver::Pm4::DrawParameters draw; + std::array vertexBuffer; +}; + +void DrawMesh(AgcDriver::VulkanDevice& device, const MeshDraw& setup) { + const auto target = device.Target(); + std::vector userData(12, 0u); + const auto index = AgcDriver::Graphics::MeshIndexBufferDescriptor(setup.draw, reinterpret_cast(GeometryCode.data())); + std::copy(index.begin(), index.end(), userData.begin() + ShaderRecompiler::MeshIndexBufferUserWord); + std::copy(setup.vertexBuffer.begin(), setup.vertexBuffer.end(), userData.begin() + 8); + const std::array geometryMemory{{{reinterpret_cast(GeometryCode.data()), std::as_bytes(std::span(GeometryCode))}}}; + ShaderRecompiler::RecompileRequest geometry{ + {ShaderStage::Mesh, reinterpret_cast(GeometryCode.data()), GeometryCode, 0, {}}, + {64, 0, userData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, geometryMemory}, + target, + {0, 0, 0, ShaderRecompiler::MeshDrawPushOffsetBytes}, + ShaderRecompiler::GraphicsCompileContext{0, {}, setup.mesh, std::nullopt, {setup.draw.indexAddress, setup.draw.indexCount, setup.draw.indexSize, setup.draw.instanceCount}} + }; + const auto meshResult = ShaderRecompiler::Recompile(geometry); + const auto meshPush = static_cast(meshResult.pushConstants.size()); + + ShaderRecompiler::ShaderPixelStageInfo pixel{}; + pixel.interpolatorCount = 1; + pixel.interpolatorSettings[0] = 0x400u; + pixel.targetOutputMode[0] = 9; + pixel.targetExportMapping.fill(0xe4u); + const std::array pixelMemory{{{reinterpret_cast(PixelCode.data()), std::as_bytes(std::span(PixelCode))}}}; + ShaderRecompiler::RecompileRequest fragment{ + {ShaderStage::Fragment, reinterpret_cast(PixelCode.data()), PixelCode, 0, {}}, + {64, 0, {}, std::nullopt, pixel, std::nullopt, pixelMemory}, + target, + {0, 0, meshPush, ShaderRecompiler::MeshDrawPushOffsetBytes - meshPush}, + std::nullopt + }; + const auto pixelResult = ShaderRecompiler::Recompile(fragment); + const std::array shaders{{ + {ShaderStage::Mesh, &meshResult, 0}, + {ShaderStage::Fragment, &pixelResult, meshPush} + }}; + + AgcDriver::Graphics::State state{}; + state.stages = {AgcDriver::Graphics::ShaderPath::Geometry, 0x20u, 64, 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; + state.renderExtent = {Width, Height}; + state.topology = setup.mesh.inputPrimitive == 6u ? VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP : VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + 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.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + state.blend.colorWriteMask = 15; + state.blends = {state.blend}; + state.blendConstants = {}; + device.Draw(state, setup.draw, shaders); + device.WaitIdle(); +} + +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}; + +void CheckTriangles(const char* what) { + for (std::uint32_t triangle = 0; triangle < Triangles; ++triangle) { + const auto offset = CellPixel(triangle); + Require(PixelIs(offset, TriangleColor(triangle)), std::string(what) + ": triangle " + std::to_string(triangle) + " was not rendered in its color, pixel " + PixelText(offset)); + } + Require(PixelIs(0, Background) && PixelIs(Pixels.size() - 4u, Background), std::string(what) + ": the corners changed"); +} + +} + +int main() { + try { + setenv("APS5_NO_SHADER_DISK_CACHE", "1", 1); + for (std::uint32_t triangle = 0; triangle < Triangles; ++triangle) { + const auto vertices = TriangleVertices(triangle); + for (std::uint32_t k = 0; k < 3; ++k) { + Ordered[3 * triangle + k] = vertices[k]; + const auto slot = 3 * (Triangles - 1 - triangle) + (k + 1) % 3; + Scrambled[slot] = vertices[k]; + Indices[3 * triangle + k] = static_cast(slot); + } + } + const std::array stripColor{128.0f / 255.0f, 1.0f, 64.0f / 255.0f, 1.0f}; + for (std::uint32_t i = 0; i < Strip.size(); ++i) { + const float x = -1.0f + 2.0f * static_cast(i / 2u) / Columns; + const float y = i % 2u == 0u ? -1.0f : 0.0f; + Strip[i] = {{x, y, 0.5f, 1.0f}, stripColor}; + } + + AgcDriver::VulkanDevice device; + const auto target = device.Target(); + if (!target.mesh.has_value()) { + std::puts("Mesh tests skipped: the device has no VK_EXT_mesh_shader"); + return 0; + } + + for (const auto& [name, subgroup] : {std::pair{"one-wave subgroups", SmallSubgroup}, std::pair{"two-wave subgroups", WideSubgroup}}) { + ClearPixels(); + DrawMesh(device, {subgroup, {reinterpret_cast(Indices.data()), static_cast(Indices.size()), 2, 1, 0, true}, VertexBufferDescriptor(Scrambled.data(), static_cast(Scrambled.size()))}); + CheckTriangles((std::string("indexed triangle list, ") + name).c_str()); + + ClearPixels(); + DrawMesh(device, {subgroup, {0, static_cast(Ordered.size()), 0, 1, 0, false}, VertexBufferDescriptor(Ordered.data(), static_cast(Ordered.size()))}); + CheckTriangles((std::string("non-indexed triangle list, ") + name).c_str()); + } + + ClearPixels(); + const ShaderRecompiler::MeshConfiguration strip{6u, 3u, 5u, 9u, 3u, 64u, 1024u, 0u, 4u}; + DrawMesh(device, {strip, {0, static_cast(Strip.size()), 0, 1, 0, false}, VertexBufferDescriptor(Strip.data(), static_cast(Strip.size()))}); + const std::array stripPixel{128, 255, 64, 255}; + for (std::uint32_t y = Height / 2u + 2u; y < Height - 2u; y += 5u) { + for (std::uint32_t x = 2u; x < Width - 2u; x += 5u) { + const auto offset = (static_cast(y) * Width + x) * 4u; + Require(PixelIs(offset, stripPixel), "triangle strip: pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") is " + PixelText(offset)); + } + } + Require(PixelIs((static_cast(Height / 4u) * Width + Width / 2u) * 4u, Background), "triangle strip: the upper half changed"); + + std::puts("Mesh tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index 5067f59d9..720621c6a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -3,10 +3,12 @@ #include "Optimization/RequestMemoryView.hpp" #include "Optimization/ResourceMaterializer.hpp" #include "Optimization/ResourceProgram.hpp" +#include "Optimization/ShaderStageInputInfo.hpp" #include "Optimization/SrtWalker/SrtFlatSlotClasses.hpp" #if ANYPS5_ENABLE_SPIRV_TOOLS #include "SpirvBackend/SpirvOptimizer.hpp" #endif +#include "CacheKey.hpp" #include #include #include @@ -405,6 +407,39 @@ void verifyProgramCounterRelativeData() { require(moved.cacheHit, "program counter data: relocating the shader recompiled it"); require(dataBase(moved) == codeAddress + 0x1000u + 56u, "program counter data: the relocated shader bound the old address"); } +void verifyMeshConfiguration() { + using namespace ShaderRecompiler; + ShaderMeshInputInfo list; + list.inputPrimitive = 4u; + require(list.InputPrimitiveSize() == 3u && list.InputPrimitiveStep() == 3u && list.InputVertexCount(21u) == 63u && list.InputPrimitiveCount(63u) == 21u && list.InputPrimitiveCount(2u) == 0u && list.InputVertexCount(0u) == 0u, "triangle list subgroup sizes changed"); + ShaderMeshInputInfo strip; + strip.inputPrimitive = 6u; + require(strip.InputPrimitiveSize() == 3u && strip.InputPrimitiveStep() == 1u && strip.InputVertexCount(21u) == 23u && strip.InputPrimitiveCount(23u) == 21u, "triangle strip subgroup sizes changed"); + ShaderMeshInputInfo lines; + lines.inputPrimitive = 2u; + ShaderMeshInputInfo points; + points.inputPrimitive = 1u; + require(lines.InputPrimitiveSize() == 2u && lines.InputPrimitiveStep() == 2u && points.InputPrimitiveSize() == 1u && points.InputVertexCount(5u) == 5u, "line or point subgroup sizes changed"); + + static constexpr std::array code{0xbf810000u}; + RecompileRequest request{}; + request.shader = {ShaderStage::Mesh, 0x10000u, code, 0, {}}; + request.context.waveSize = 64; + const MeshConfiguration mesh{4u, 21u, 63u, 64u, 21u, 64u, 256u, 0u, 12u}; + request.graphics = GraphicsCompileContext{0u, {}, mesh, std::nullopt, {}}; + const auto replay = RequestSerializer{}.Deserialize(RequestSerializer{}.Serialize(request)); + require(replay.request.graphics.has_value() && replay.request.graphics->mesh.has_value() && replay.request.graphics->mesh->esgsItemSize == 12u && replay.request.graphics->mesh->primitivesPerGroup == 21u, "mesh configuration was lost in serialization"); + std::vector key; + RecompileCacheKey::Build(request, key); + const auto first = key; + auto other = request; + auto otherMesh = mesh; + otherMesh.esgsItemSize = 16u; + 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"); +} + } int main() { @@ -414,6 +449,7 @@ int main() { verifyPureFlatSlots(); verifyBindlessTable(); verifyProgramCounterRelativeData(); + verifyMeshConfiguration(); #if ANYPS5_ENABLE_SPIRV_TOOLS const std::vector minimalSpirv{ 0x07230203u, 0x00010000u, 0u, 5u, 0u, diff --git a/core/libs/prx/libSceAgcDriver/tools/ShaderReplay.cpp b/core/libs/prx/libSceAgcDriver/tools/ShaderReplay.cpp index dd73918e3..de5b97b7a 100644 --- a/core/libs/prx/libSceAgcDriver/tools/ShaderReplay.cpp +++ b/core/libs/prx/libSceAgcDriver/tools/ShaderReplay.cpp @@ -48,6 +48,14 @@ bool Replay(const char* path) { std::printf(" compute: threads %ux%ux%u, lds %u dwords, group ids %d%d%d, tg size %d, thread id components %u\n", compute.numThreads[0], compute.numThreads[1], compute.numThreads[2], compute.ldsSizeDwords, compute.groupIdEnable[0], compute.groupIdEnable[1], compute.groupIdEnable[2], compute.tgSizeEnable, compute.threadIdComponentCount); if (compute.PartialGroups()) std::printf(" partial groups: dispatch of %ux%ux%u threads\n", compute.partialThreads[0], compute.partialThreads[1], compute.partialThreads[2]); } + if (request.request.graphics.has_value()) { + const auto& graphics = *request.request.graphics; + std::printf(" graphics: %zu linked programs, draw %u indices of %u bytes at 0x%llx, %u instances\n", graphics.linkedPrograms.size(), graphics.draw.indexCount, graphics.draw.indexElementBytes, static_cast(graphics.draw.indexAddress), graphics.draw.instanceCount); + if (graphics.mesh.has_value()) { + const auto& mesh = *graphics.mesh; + std::printf(" mesh: input primitive %u, %u primitives / %u vertices per group, max %u vertices / %u primitives, %u threads, lds %u dwords, provoking %u, ESGS item %u\n", mesh.inputPrimitive, mesh.primitivesPerGroup, mesh.verticesPerGroup, mesh.maxVertices, mesh.maxPrimitives, mesh.threadsPerGroup, mesh.ldsSizeDwords, mesh.provokingVertex, mesh.esgsItemSize); + } + } if (g_memory) { // --mem: the captured inputs. User data words are printed; each memory region is written to // mem__.bin next to the request for inspection with other tools. diff --git a/core/shader/recompiler/CacheKey.hpp b/core/shader/recompiler/CacheKey.hpp index bf86cb817..fc574d67c 100644 --- a/core/shader/recompiler/CacheKey.hpp +++ b/core/shader/recompiler/CacheKey.hpp @@ -30,6 +30,7 @@ public: append(key, request.context.compute); append(key, request.context.pixel); append(key, request.context.vertex); + appendMesh(key, request); append(key, request.target); append(key, DebugProbeActive()); } @@ -48,6 +49,7 @@ public: append(key, request.context.compute); append(key, request.context.pixel); append(key, request.context.vertex); + appendMesh(key, request); std::uint64_t hash = 0xcbf29ce484222325ull; for (const auto value : key) { hash ^= value; @@ -70,6 +72,14 @@ public: } private: + static void appendMesh(std::vector& key, const RecompileRequest& request) { + if (request.shader.stage != ShaderStage::Mesh) return; + 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); + } + template static void append(std::vector& key, TValue value) requires (std::is_integral_v || std::is_enum_v) { key.push_back(static_cast(value)); diff --git a/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp b/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp index 0588b5da7..2a4ee6295 100644 --- a/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp +++ b/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp @@ -426,9 +426,10 @@ void writeMeshConfiguration(Writer& writer, const MeshConfiguration& configurati writer.WriteU32(configuration.threadsPerGroup); writer.WriteU32(configuration.ldsSizeDwords); writer.WriteU32(configuration.provokingVertex); + writer.WriteU32(configuration.esgsItemSize); } -MeshConfiguration readMeshConfiguration(Reader& reader) { +MeshConfiguration readMeshConfiguration(Reader& reader, std::uint32_t version) { MeshConfiguration configuration{}; configuration.inputPrimitive = reader.ReadU32(); configuration.primitivesPerGroup = reader.ReadU32(); @@ -438,6 +439,7 @@ MeshConfiguration readMeshConfiguration(Reader& reader) { configuration.threadsPerGroup = reader.ReadU32(); configuration.ldsSizeDwords = reader.ReadU32(); configuration.provokingVertex = reader.ReadU32(); + configuration.esgsItemSize = version >= 4u ? reader.ReadU32() : 4u; return configuration; } @@ -615,7 +617,7 @@ void writeGraphicsCompileContext(Writer& writer, const GraphicsCompileContext& g writer.WriteU32(graphics.draw.instanceCount); } -GraphicsCompileContext readGraphicsCompileContext(Reader& reader, DeserializedGraphicsCompileContext& storage) { +GraphicsCompileContext readGraphicsCompileContext(Reader& reader, DeserializedGraphicsCompileContext& storage, std::uint32_t version) { GraphicsCompileContext graphics{}; graphics.firstUserSgpr = reader.ReadU32(); const auto programCount = reader.ReadU64(); @@ -639,7 +641,7 @@ GraphicsCompileContext readGraphicsCompileContext(Reader& reader, DeserializedGr } graphics.linkedPrograms = storage.linkedPrograms; if (reader.ReadBool()) { - storage.mesh = readMeshConfiguration(reader); + storage.mesh = readMeshConfiguration(reader, version); graphics.mesh = storage.mesh; } if (reader.ReadBool()) { @@ -660,7 +662,7 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const std::string buffer; Writer writer(buffer); writer.WriteU32(0x41505335u); - writer.WriteU32(3u); + writer.WriteU32(4u); writeShaderBinary(writer, request.shader); writeGuestContext(writer, request.context); writeSpirvTarget(writer, request.target); @@ -683,7 +685,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 > 3u) throw std::runtime_error("unsupported recompile request serialization version"); + if (version < 1u || version > 4u) 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); @@ -691,7 +693,7 @@ DeserializedRequest RequestSerializer::Deserialize(std::string_view text) const result.request.layout = readBindingLayout(reader); if (reader.ReadBool()) { result.graphicsStorage = std::make_unique(); - result.request.graphics = readGraphicsCompileContext(reader, *result.graphicsStorage); + result.request.graphics = readGraphicsCompileContext(reader, *result.graphicsStorage, version); } if (version >= 2u) result.request.useCache = reader.ReadBool(); if (version >= 3u && result.request.context.compute.has_value()) { diff --git a/core/shader/recompiler/Deserialize.cpp b/core/shader/recompiler/Deserialize.cpp index 377bebb3a..4e2574a14 100644 --- a/core/shader/recompiler/Deserialize.cpp +++ b/core/shader/recompiler/Deserialize.cpp @@ -152,7 +152,7 @@ int main(int argc, char** argv) { std::cout << "Disassembly written to disasm.txt (" << disasm.size() << " bytes)\n"; const ShaderStageKind stageKind = ToShaderStageKind(request.shader.stage); - const auto inputInfo = BuildShaderStageInputInfo(stageKind, request.context, request.target.subgroupSize); + const auto inputInfo = BuildShaderStageInputInfo(stageKind, request.context, request.target.subgroupSize, request.graphics && request.graphics->mesh ? &*request.graphics->mesh : nullptr); GraphBuilder graphBuilder; auto cfg = graphBuilder.Build(decoded); diff --git a/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp b/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp index 44757fdc8..e8915fadd 100644 --- a/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp +++ b/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp @@ -100,18 +100,19 @@ struct ShaderMeshInputInfo: ShaderWorkgroupInputInfo { std::uint32_t maxVertices = 0; std::uint32_t maxPrimitives = 0; std::uint32_t provokingVertex = 0; + std::uint32_t esgsItemSize = 0; [[nodiscard]] std::uint32_t InputPrimitiveSize() const { - throw std::runtime_error("shader input helper not implemented"); + return inputPrimitive == 1u ? 1u : inputPrimitive == 2u ? 2u : 3u; } [[nodiscard]] std::uint32_t InputPrimitiveStep() const { - throw std::runtime_error("shader input helper not implemented"); + return inputPrimitive == 6u ? 1u : InputPrimitiveSize(); } [[nodiscard]] std::uint32_t InputPrimitiveCount(std::uint32_t vertices) const { - throw std::runtime_error("shader input helper not implemented"); + return vertices < InputPrimitiveSize() ? 0u : (vertices - InputPrimitiveSize()) / InputPrimitiveStep() + 1u; } [[nodiscard]] std::uint32_t InputVertexCount(std::uint32_t primitives) const { - throw std::runtime_error("shader input helper not implemented"); + return primitives == 0u ? 0u : (primitives - 1u) * InputPrimitiveStep() + InputPrimitiveSize(); } }; diff --git a/core/shader/recompiler/Recompiler.cpp b/core/shader/recompiler/Recompiler.cpp index 85f4dccdb..3ee626b4f 100644 --- a/core/shader/recompiler/Recompiler.cpp +++ b/core/shader/recompiler/Recompiler.cpp @@ -82,11 +82,16 @@ std::uint32_t HostSubgroupSize(const RecompileRequest& request) { return request.target.subgroupSize; } +ShaderStageInputInfo RequestInputInfo(const RecompileRequest& request) { + const auto* mesh = request.graphics && request.graphics->mesh ? &*request.graphics->mesh : nullptr; + return BuildShaderStageInputInfo(toShaderStageKind(request.shader.stage), request.context, HostSubgroupSize(request), mesh); +} + } IrProgram PrepareResourceProgram(const RecompileRequest& request) { const auto stageKind = toShaderStageKind(request.shader.stage); - const auto inputInfo = BuildShaderStageInputInfo(stageKind, request.context, HostSubgroupSize(request)); + const auto inputInfo = RequestInputInfo(request); constexpr RdnaInstructionDecoder decoder; const auto decoded = decoder.Decode(request.shader.code); @@ -278,7 +283,7 @@ std::uint64_t nextVariantId() { } CompiledVariant compileVariant(const RecompileRequest& request, IrProgram program, const ResourceSnapshot& resourceSnapshot, const ResourceSpecialization& resourceSpecialization) { - const auto inputInfo = BuildShaderStageInputInfo(toShaderStageKind(request.shader.stage), request.context, HostSubgroupSize(request)); + const auto inputInfo = RequestInputInfo(request); constexpr DeadCodeEliminator deadCodeEliminator; constexpr ResourceMaterializer resourceMaterializer; resourceMaterializer.Apply(program, resourceSpecialization); @@ -405,7 +410,7 @@ RecompileResult materializeVariant(SourceEntry& source, const RecompileRequest& } RecompileResult RecompileImpl(const RecompileRequest& request) { - static_cast(BuildShaderStageInputInfo(toShaderStageKind(request.shader.stage), request.context, HostSubgroupSize(request))); + static_cast(RequestInputInfo(request)); RequestMemoryView memory(request.context.memory); const auto runtime = memory.MakeRuntime(request.context.userData, request.shader.codeAddress); ResourceSnapshot snapshot; @@ -587,7 +592,7 @@ auto recompileReporting(const RecompileRequest& request, Impl&& impl) -> decltyp } std::shared_ptr GetResourcePlan(const RecompileRequest& request) { - static_cast(BuildShaderStageInputInfo(toShaderStageKind(request.shader.stage), request.context, HostSubgroupSize(request))); + static_cast(RequestInputInfo(request)); if (request.useCache) return getSource(request)->plan; return makeResourcePlan(request); } @@ -616,7 +621,7 @@ std::shared_ptr CaptureResources(const RecompileRequest& static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; const auto started = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}; // Validates the stage inputs once per request, as GetResourcePlan and Recompile(request) do. - static_cast(BuildShaderStageInputInfo(toShaderStageKind(request.shader.stage), request.context, HostSubgroupSize(request))); + static_cast(RequestInputInfo(request)); auto capture = std::make_shared(); if (request.useCache) { capture->source = getSource(request); @@ -631,7 +636,7 @@ std::shared_ptr CaptureResources(const RecompileRequest& std::shared_ptr ResolveSource(const RecompileRequest& request) { if (!request.useCache) return nullptr; - static_cast(BuildShaderStageInputInfo(toShaderStageKind(request.shader.stage), request.context, HostSubgroupSize(request))); + static_cast(RequestInputInfo(request)); return std::make_shared(SourceHandle{getSource(request)}); } @@ -639,7 +644,7 @@ std::shared_ptr CaptureResources(const RecompileRequest& static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; const auto started = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}; // The whole vertex family (Vertex, Local, TC, TE, Mesh) validates V# fields the memo key does not cover. - if (request.shader.stage != ShaderStage::Compute && request.shader.stage != ShaderStage::Fragment) static_cast(BuildShaderStageInputInfo(toShaderStageKind(request.shader.stage), request.context, HostSubgroupSize(request))); + if (request.shader.stage != ShaderStage::Compute && request.shader.stage != ShaderStage::Fragment) static_cast(RequestInputInfo(request)); auto capture = std::make_shared(); capture->source = handle.source; capture->plan = handle.source->plan; diff --git a/core/shader/recompiler/Recompiler.hpp b/core/shader/recompiler/Recompiler.hpp index e24ea1bcd..588bae6fd 100644 --- a/core/shader/recompiler/Recompiler.hpp +++ b/core/shader/recompiler/Recompiler.hpp @@ -174,6 +174,7 @@ struct MeshConfiguration { std::uint32_t threadsPerGroup; std::uint32_t ldsSizeDwords; std::uint32_t provokingVertex; + std::uint32_t esgsItemSize = 0; }; struct TessellationConfiguration { @@ -184,6 +185,10 @@ struct TessellationConfiguration { std::uint32_t outputTopology; }; +inline constexpr std::uint32_t MeshDrawPushOffsetBytes = 104; +inline constexpr std::uint32_t MeshDrawPushBytes = 24; +inline constexpr std::uint32_t MeshIndexBufferUserWord = 4; + struct GraphicsDrawParameters { std::uint64_t indexAddress; std::uint32_t indexCount; diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp index c42a895e1..3787acf5c 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp @@ -117,7 +117,7 @@ void ValidateBdaTarget(const IrProgram& program, const SpirvTargetOptions& targe for (const auto extension : {"SPV_KHR_physical_storage_buffer", "SPV_KHR_8bit_storage"}) { if (std::find(target.supportedExtensions.begin(), target.supportedExtensions.end(), extension) == target.supportedExtensions.end()) throw std::runtime_error(std::string("BDA requires unsupported extension ") + extension); } - if (program.Resources().stage == IrShaderStage::Mesh || program.Resources().stage == IrShaderStage::TessellationControl) throw std::runtime_error("BDA fault termination requires a barrier-safe mesh or tessellation-control execution protocol"); + if (program.Resources().stage == IrShaderStage::TessellationControl) throw std::runtime_error("BDA fault termination requires a barrier-safe tessellation-control execution protocol"); } bool BdaInvocationsMayStop(const IrProgram& program) { diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp index e9d65296e..a5b44f316 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp @@ -259,7 +259,7 @@ std::vector SpirvEmitter::Emit(const IrProgram& program, const Sh state.waveLdsScope = WaveLdsScope(program, workgroup, state.laneCount); if (const char* guard = std::getenv("APS5_LOOP_GUARD")) state.loopGuardLimit = static_cast(std::strtoul(guard, nullptr, 0)); // Stopped invocations would leave the wave LDS barriers incomplete. - state.bdaStopsInvocations = state.waveLdsScope == 0 && BdaInvocationsMayStop(program); + state.bdaStopsInvocations = state.waveLdsScope == 0 && BdaInvocationsMayStop(program) && program.Resources().stage != IrShaderStage::Mesh; EmitModuleHeader(state, bindings); EmitProgram(state); state.module.EmitEntryPoint(ExecutionModelForStage(state.program.Resources().stage), state.mainFunc, "main", state.interfaceVariables); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp index 110176243..377a084ee 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp @@ -331,7 +331,7 @@ void DefineOutputs(SpirvEmitterState& state) { void DefineDescriptors(SpirvEmitterState& state) { const IrBindingLayout& layout = state.program.Metadata().bindings; const IrShaderStage stage = state.program.Resources().stage; - if (layout.UsesPushData()) { + if (layout.UsesPushData() || stage == IrShaderStage::Mesh) { const auto type = PushConstantBlockType(state); state.pushConstantVariable = state.module.DefineGlobalVariable(TypePointer(state, spv::StorageClassPushConstant, type), spv::StorageClassPushConstant); state.module.AddName(type, "BufferResource"); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp index f82cfba1a..d80eec77c 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp @@ -532,11 +532,31 @@ void DefineMeshOutputs(SpirvEmitterState& state) { void EmitMeshEntryPoint(SpirvEmitterState& state) { state.module.AddFunction(spv::OpFunction, TypeVoid(state), state.mainFunc, spv::FunctionControlMaskNone, TypeFunction(state)); EmitLabel(state, state.module.AllocateId()); + for (std::uint32_t half = 0; half < state.laneCount; half++) { + state.module.AddFunction(spv::OpStore, MeshElement(state, state.meshPrimitiveData, spv::StorageClassPrivate, TypeU32(state), ConstantU32(state, half)), ConstantU32(state, 0x80000000u)); + } + const auto firstInvocation = state.module.AllocateId(); + state.laneHalf = 0; + state.module.AddFunction(spv::OpIEqual, TypeBool(state), firstInvocation, EmitLocalInvocationIndex(state), ConstantU32(state, 0u)); + EmitIfCondition(state, firstInvocation, [&]() { + for (std::uint32_t field = 0; field < 2u; field++) { + state.module.AddFunction(spv::OpStore, MeshElement(state, state.meshAllocation, spv::StorageClassWorkgroup, TypeU32(state), ConstantU32(state, field)), ConstantU32(state, 0u)); + } + }); state.module.AddFunction(spv::OpFunctionCall, TypeVoid(state), state.module.AllocateId(), state.meshGuestFunc); state.module.AddFunction(spv::OpControlBarrier, ConstantU32(state, spv::ScopeWorkgroup), ConstantU32(state, spv::ScopeWorkgroup), ConstantU32(state, spv::MemorySemanticsAcquireReleaseMask | spv::MemorySemanticsWorkgroupMemoryMask)); - const auto vertices = MeshLoad(state, state.meshAllocation, spv::StorageClassWorkgroup, TypeU32(state), ConstantU32(state, 0u)); - const auto primitives = MeshLoad(state, state.meshAllocation, spv::StorageClassWorkgroup, TypeU32(state), ConstantU32(state, 1u)); + const auto& mesh = state.inputInfo.vertex->mesh; + const auto clamp = [&](std::uint32_t value, std::uint32_t limit) { + const auto result = state.module.AllocateId(); + state.module.AddFunction(spv::OpExtInst, TypeU32(state), result, GlslStd450(state), GLSLstd450UMin, value, ConstantU32(state, limit)); + return result; + }; + const auto vertices = clamp(MeshLoad(state, state.meshAllocation, spv::StorageClassWorkgroup, TypeU32(state), ConstantU32(state, 0u)), mesh.maxVertices); + const auto primitives = clamp(MeshLoad(state, state.meshAllocation, spv::StorageClassWorkgroup, TypeU32(state), ConstantU32(state, 1u)), mesh.maxPrimitives); state.module.AddFunction(spv::OpSetMeshOutputsEXT, vertices, primitives); + const auto anyVertex = state.module.AllocateId(); + state.module.AddFunction(spv::OpExtInst, TypeU32(state), anyVertex, GlslStd450(state), GLSLstd450UMax, vertices, ConstantU32(state, 1u)); + const auto lastVertex = EmitBinaryU32(state, spv::OpISub, anyVertex, ConstantU32(state, 1u)); for (std::uint32_t half = 0; half < state.laneCount; half++) { state.laneHalf = half; const auto index = EmitLocalInvocationIndex(state); @@ -559,8 +579,10 @@ void EmitMeshEntryPoint(SpirvEmitterState& state) { const auto packed = MeshLoad(state, state.meshPrimitiveData, spv::StorageClassPrivate, TypeU32(state), ConstantU32(state, half)); std::array vertex {}; for (std::uint32_t component = 0; component < 3u; 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::OpBitFieldUExtract, TypeU32(state), vertex.at(component), packed, ConstantU32(state, component * 10u), ConstantU32(state, 10u)); + 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]); @@ -937,7 +959,7 @@ std::uint32_t EmitMeshDrawParameter(SpirvValueEmitContext& ctx, const IrValue& i ctx.Fail(inst, "invalid mesh draw parameter"); } const auto pointer = state.module.AllocateId(); - state.module.AddFunction(spv::OpAccessChain, TypePushConstantElementPointer(state), pointer, state.pushConstantVariable, ConstantU32(state, 0u), ConstantU32(state, index)); + state.module.AddFunction(spv::OpAccessChain, TypePushConstantElementPointer(state), pointer, state.pushConstantVariable, ConstantU32(state, 0u), ConstantU32(state, MeshDrawPushOffsetBytes / 4u + index)); const auto result = state.module.AllocateId(); state.module.AddFunction(spv::OpLoad, TypeU32(state), result, pointer); return result; diff --git a/core/shader/recompiler/Translation/include/Translation/ShaderInputInfoBuilder.hpp b/core/shader/recompiler/Translation/include/Translation/ShaderInputInfoBuilder.hpp index 1096eb2e7..1f923d34d 100644 --- a/core/shader/recompiler/Translation/include/Translation/ShaderInputInfoBuilder.hpp +++ b/core/shader/recompiler/Translation/include/Translation/ShaderInputInfoBuilder.hpp @@ -9,7 +9,7 @@ namespace ShaderRecompiler { // hostSubgroupSize is the device subgroup width; wave64 workgroups on 32-wide subgroups run each // invocation as two guest lanes so a guest wave stays one host subgroup. -ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const GuestContext& context, std::uint32_t hostSubgroupSize); +ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const GuestContext& context, std::uint32_t hostSubgroupSize, const MeshConfiguration* mesh = nullptr); } diff --git a/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp b/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp index c7a76848b..4159f2b23 100644 --- a/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp +++ b/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp @@ -174,7 +174,12 @@ void TranslationContext::sBarrier() { (void)ir.Emit(IrOpcode::Barrier, IrType::Void, {}); } -void TranslationContext::sSendmsg(const RdnaInstruction&) { +void TranslationContext::sSendmsg(const RdnaInstruction& inst) { + constexpr std::uint32_t GsAllocReq = 9u; + if (program.Resources().stage == IrShaderStage::Mesh && (inst.rawWords[0] & 0xfu) == GsAllocReq) { + (void)ir.Emit(IrOpcode::MeshAllocate, IrType::Void, {&ir.GetM0()}); + return; + } (void)ir.Emit(IrOpcode::Sendmsg, IrType::Void, {}); } diff --git a/core/shader/recompiler/Translation/src/InstructionTranslator.cpp b/core/shader/recompiler/Translation/src/InstructionTranslator.cpp index 73813b189..3f88ce180 100644 --- a/core/shader/recompiler/Translation/src/InstructionTranslator.cpp +++ b/core/shader/recompiler/Translation/src/InstructionTranslator.cpp @@ -233,8 +233,10 @@ void emitEntryPrologue(IrProgram& program, IrBlock& entryBlock, const TranslateO } } else if (options.stage == ShaderStageKind::Mesh) { const auto& mesh = options.inputInfo.vertex->mesh; - if (options.waveSize != 64u || mesh.primitivesPerGroup == 0u || mesh.verticesPerGroup > 64u || totalThreads > 15u * 64u) { - throw std::runtime_error("mesh shader translation configuration is not supported"); + const std::uint32_t size = mesh.InputPrimitiveSize(); + const std::uint32_t stepCount = mesh.InputPrimitiveStep(); + if (options.waveSize != 64u || mesh.primitivesPerGroup == 0u || mesh.verticesPerGroup != mesh.InputVertexCount(mesh.primitivesPerGroup) || mesh.verticesPerGroup > totalThreads || mesh.primitivesPerGroup > totalThreads || totalThreads % 64u != 0u || totalThreads > 15u * 64u || 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) + ")"); } constexpr std::uint32_t kTriStripPrimitiveType = 6u; const auto u32 = [&entryIr](std::uint32_t value) -> IrValue& { @@ -250,36 +252,47 @@ void emitEntryPrologue(IrProgram& program, IrBlock& entryBlock, const TranslateO return entryIr.ISub(lhs, minimum(lhs, rhs)); }; IrValue& local = builtin(StageInputKind::LocalInvocationIndex); - IrValue& primitiveChunk = entryIr.IMul(builtin(StageInputKind::WorkgroupId, 0u), u32(mesh.primitivesPerGroup)); - IrValue& step = u32(mesh.InputPrimitiveStep()); - IrValue& size = u32(mesh.InputPrimitiveSize()); - IrValue& chunk = entryIr.IMul(primitiveChunk, step); - IrValue& vertices = minimum(subtractSaturate(draw(0u), chunk), u32(mesh.verticesPerGroup)); - IrValue& primitives = entryIr.Select(entryIr.ULessThan(vertices, size), u32(0u), entryIr.IAdd(entryIr.Emit(IrOpcode::UDiv32, IrOpcodeType(IrOpcode::UDiv32), {&subtractSaturate(vertices, size), &step}), u32(1u))); + IrValue& firstPrimitive = entryIr.IMul(builtin(StageInputKind::WorkgroupId, 0u), u32(mesh.primitivesPerGroup)); + IrValue& step = u32(stepCount); + IrValue& firstVertex = entryIr.IMul(firstPrimitive, step); + IrValue& vertices = minimum(subtractSaturate(draw(0u), firstVertex), u32(mesh.verticesPerGroup)); + IrValue& primitives = entryIr.Select(entryIr.ULessThan(vertices, u32(size)), u32(0u), entryIr.IAdd(entryIr.Emit(IrOpcode::UDiv32, IrOpcodeType(IrOpcode::UDiv32), {&subtractSaturate(vertices, u32(size)), &step}), u32(1u))); + entryIr.SetScalarReg(static_cast(2), entryIr.BitwiseOr(entryIr.ShiftLeftLogical(vertices, u32(12u)), entryIr.ShiftLeftLogical(primitives, u32(22u)))); IrValue& wave = entryIr.ShiftRightLogical(local, u32(6u)); IrValue& waveBase = entryIr.BitwiseAnd(local, u32(~63u)); IrValue& vertexCount = minimum(subtractSaturate(vertices, waveBase), u32(64u)); IrValue& primitiveCount = minimum(subtractSaturate(primitives, waveBase), u32(64u)); - IrValue& waveInfo = entryIr.BitwiseOr(entryIr.ShiftLeftLogical(wave, u32(24u)), u32(((totalThreads + 63u) / 64u) << 28u)); + IrValue& waveInfo = entryIr.BitwiseOr(entryIr.ShiftLeftLogical(wave, u32(24u)), u32((totalThreads / 64u) << 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(primitiveChunk, local), u32(1u)) : u32(0u); + IrValue& parity = mesh.inputPrimitive == kTriStripPrimitiveType ? entryIr.BitwiseAnd(entryIr.IAdd(firstPrimitive, local), u32(1u)) : u32(0u); IrValue& vertex = entryIr.IMul(local, step); - IrValue& first = entryIr.IAdd(vertex, parity); - IrValue& second = mesh.InputPrimitiveSize() >= 2u ? entryIr.ISub(entryIr.IAdd(vertex, u32(1u)), parity) : u32(0u); - IrValue& third = mesh.InputPrimitiveSize() == 3u ? entryIr.IAdd(vertex, u32(2u)) : u32(0u); - entryIr.SetVectorReg(static_cast(0), entryIr.BitwiseOr(entryIr.ShiftLeftLogical(first, u32(2u)), entryIr.ShiftLeftLogical(second, u32(18u)))); - entryIr.SetVectorReg(static_cast(1), entryIr.ShiftLeftLogical(third, u32(2u))); - IrValue& inputVertex = entryIr.IAdd(chunk, local); + IrValue& item = u32(mesh.esgsItemSize); + IrValue& first = entryIr.IMul(entryIr.IAdd(vertex, parity), item); + IrValue& second = size >= 2u ? entryIr.IMul(entryIr.ISub(entryIr.IAdd(vertex, u32(1u)), parity), item) : u32(0u); + 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))); + entryIr.SetVectorReg(static_cast(2), entryIr.IAdd(firstPrimitive, local)); + entryIr.SetVectorReg(static_cast(3), u32(0u)); + entryIr.SetVectorReg(static_cast(4), u32(0u)); + if (options.userDataBaseRegister != 0u || options.userDataCount < 8u) { + throw std::runtime_error("mesh shader translation requires the merged program's eight hidden user words"); + } + IrValue& inputVertex = entryIr.IAdd(firstVertex, local); IrValue& indexBytes = draw(3u); IrValue& indexed = entryIr.INotEqual(indexBytes, u32(0u)); - IrValue& indexLow = draw(4u); - IrValue& byteOffset = entryIr.IAdd(entryIr.BitwiseAnd(indexLow, u32(3u)), entryIr.IMul(inputVertex, indexBytes)); - IrValue& indexResource = entryIr.Emit(IrOpcode::GetAddressResource, IrOpcodeType(IrOpcode::GetAddressResource), {&entryIr.BitwiseAnd(indexLow, u32(~3u)), &draw(5u)}); + IrValue& byteOffset = entryIr.IMul(inputVertex, 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))}); const std::uint32_t memoryIndex = static_cast(program.Resources().memoryInfo.size()); - program.Resources().memoryInfo.push_back(MemoryInfo{.kind = ResourceKind::Global}); - IrValue& packedIndex = entryIr.Emit(IrOpcode::LoadAddressU32, IrOpcodeType(IrOpcode::LoadAddressU32), {&indexResource, &entryIr.BitwiseAnd(byteOffset, u32(~3u)), &u32(0u), &entryIr.LogicalAnd(indexed, entryIr.ULessThan(local, vertices))}, MemoryFlags{.index = memoryIndex}); + program.Resources().memoryInfo.push_back(MemoryInfo{.kind = ResourceKind::Buffer, .resource = 1u, .offen = true}); + IrValue& packedIndex = entryIr.Emit(IrOpcode::LoadBufferU32, IrOpcodeType(IrOpcode::LoadBufferU32), {&indexResource, &u32(0u), &entryIr.BitwiseAnd(byteOffset, u32(~3u)), &u32(0u), &entryIr.LogicalAnd(indexed, entryIr.ULessThan(local, vertices))}, MemoryFlags{.index = memoryIndex}); IrValue& index = entryIr.Emit(IrOpcode::BitFieldUExtract, IrOpcodeType(IrOpcode::BitFieldUExtract), {&packedIndex, &entryIr.IMul(entryIr.BitwiseAnd(byteOffset, u32(3u)), u32(8u)), &entryIr.IMul(indexBytes, u32(8u))}); + 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))); + 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))); } else if (options.stage == ShaderStageKind::Local) { entryIr.SetScalarReg(static_cast(3), entryIr.Constant(64u)); diff --git a/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp b/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp index d29849d3a..2525f99cd 100644 --- a/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp +++ b/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp @@ -63,7 +63,7 @@ void _detectVertexBuffers(ShaderVertexInputInfo& info) { } -ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const GuestContext& context, std::uint32_t hostSubgroupSize) { +ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const GuestContext& context, std::uint32_t hostSubgroupSize, const MeshConfiguration* mesh) { switch (stage) { case ShaderStageKind::Compute: { if (!context.compute.has_value()) { @@ -154,6 +154,24 @@ ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const Gues vertexStorage.resourcesDst[i].fetchIndex = vertex.resourcesDst[i].fetchIndex; } _detectVertexBuffers(vertexStorage); + if (stage == ShaderStageKind::Mesh) { + if (mesh == nullptr) throw std::runtime_error("ShaderInputInfoBuilder: a mesh-stage program has no mesh configuration"); + auto& target = vertexStorage.mesh; + target.threadsNum[0] = mesh->threadsPerGroup; + target.threadsNum[1] = 1u; + target.threadsNum[2] = 1u; + target.ldsSizeDwords = mesh->ldsSizeDwords; + target.waveSize = context.waveSize; + target.hostSubgroupSize = hostSubgroupSize; + target.inputPrimitive = mesh->inputPrimitive; + target.primitivesPerGroup = mesh->primitivesPerGroup; + target.verticesPerGroup = mesh->verticesPerGroup; + target.maxVertices = mesh->maxVertices; + 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"); + } ShaderStageInputInfo result; result.vertex = &vertexStorage; return result;