diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp index 06d954d3b..5fa283462 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp @@ -37,6 +37,9 @@ void DecodeGraphicsPrograms(DrawDecode& decoded, const QueueState& queue, const struct PreparedGraphicsStage { std::shared_ptr snapshot; PreparedShaders::Entry entry; + ShaderRecompiler::ShaderStage stage = ShaderRecompiler::ShaderStage::Vertex; + std::uint32_t pushOffset = 0; + std::shared_ptr registration; }; std::vector PrepareGraphicsStages(const DrawDecode& decoded, const ShaderRecompiler::SpirvTarget& target); diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp index fe9ea5117..2027874af 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp @@ -299,6 +299,7 @@ public: }; IndirectDrawSupport DrawIndirectSupport() const; void EnqueueDraw(const Graphics::State& graphics, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots = {}); + bool PrebuildGraphicsLibraries(const Graphics::ShaderStages& stages, std::span shaders); private: VulkanDevice(const PresentationWindow* window, bool compileOnly); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp index 580d119af..ed72dcd0d 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -30,7 +31,8 @@ namespace AgcDriver::DriverDetail { std::shared_ptr PrepareShaderWithDiagnostics(const ShaderRecompiler::RecompileRequest& request, ShaderRecompiler::ShaderPreparationContext* preparation) { try { - return ShaderRecompiler::PrepareShader(request, preparation); + auto handle = ShaderRecompiler::PrepareShader(request, preparation); + return handle; } catch (...) { try { if (std::getenv("APS5_DUMP_SHADERS") != nullptr) static_cast(Driver::dumpRequest(request.shader.codeAddress, request)); @@ -860,7 +862,14 @@ std::vector PrepareGraphicsStages(const DrawDecode& decod } const auto bytes = handle->artifact->bindings.pushConstantSizeBytes; require(bytes <= room, "prepared graphics stages exceed the push constant block"); - prepared.push_back({program.snapshot, {program.codeOffset, handle}}); + std::shared_ptr registration; + if (target.registrationModules) { + try { + registration = ShaderRecompiler::RegistrationResult(request, *handle); + } catch (const std::exception&) { + } + } + prepared.push_back({program.snapshot, {program.codeOffset, handle}, program.binary.stage, offset, std::move(registration)}); pushOffset += bytes; } transaction.Commit(); @@ -930,6 +939,23 @@ void Driver::ResolveGraphicsStagesAbi(std::span stages, std if (stage.snapshot->type == 1) continue; ResolvePreparedGraphics(*stage.snapshot, {}, primitiveType == state.userConfig.end() ? 0u : primitiveType->second, localDevice->Target()); } + if (localDevice->Target().registrationModules && !prepared.empty() && std::ranges::all_of(prepared, [](const auto& stage) { return stage.registration != nullptr && (stage.stage == ShaderRecompiler::ShaderStage::Vertex || stage.stage == ShaderRecompiler::ShaderStage::Fragment); }) && prepared.back().stage == ShaderRecompiler::ShaderStage::Fragment) { + std::vector> results; + std::vector> stages; + for (const auto& stage : prepared) { + results.push_back(stage.registration); + stages.emplace_back(stage.stage, stage.pushOffset); + } + SubmitRegistrationPreparation([localDevice, results = std::move(results), stages = std::move(stages), shaderStages = decoded.state.stages] { + std::vector shaders; + for (std::size_t index = 0; index < results.size(); ++index) shaders.push_back({stages[index].first, results[index].get(), stages[index].second}); + try { + static_cast(localDevice->PrebuildGraphicsLibraries(shaderStages, shaders)); + } catch (const std::exception& error) { + APS5_LOG_OUT("graphics library prebuild skipped: %s", error.what()); + } + }); + } PreparedShaderState::GraphicsAbi abi{key, registry, {}}; for (const auto& stage : prepared) abi.stages.push_back(stage.snapshot); auto& graphicsAbis = transaction.Edit(*owner).graphicsAbis; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index 858e855dc..78d96fe75 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -3058,6 +3058,25 @@ void VulkanDevice::ColorMetadataPass(const Graphics::ColorMetadataPass& pass) { Graphics::RunColorMetadataPass(graphicsContext(), pass); } +bool VulkanDevice::PrebuildGraphicsLibraries(const Graphics::ShaderStages& stages, std::span shaders) { + PerformanceTimer timing("Vulkan.GraphicsPipelinePrebuild"); + const auto context = graphicsContext(); + if (!context.graphicsPipelineLibrary || context.descriptorCache == nullptr) return false; + Graphics::State state{}; + state.stages = stages; + state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + state.color.format = VK_FORMAT_R8G8B8A8_UNORM; + state.color.exportIndex = 0; + state.colors.push_back(state.color); + state.hasColorTarget = true; + state.blend.colorWriteMask = 15; + state.blends.push_back(state.blend); + state.renderExtent = {1, 1}; + const auto layouts = Graphics::DescribeSetLayouts(context, shaders); + return Graphics::Pipeline::Prebuild(context, state, Graphics::VertexInputLayout{}, layouts.View(), shaders); +} + void VulkanDevice::Draw(const Graphics::State& graphics, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots, std::shared_ptr* recipe) { for (const auto& shader : shaders) { require(shader.program != nullptr, "missing compiled shader"); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp index ee0f2d094..f3a48a189 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp @@ -43,6 +43,7 @@ public: Pipeline(const Context& context, const State& state, const VertexInputLayout& vertexInput, const ShaderResources& resources, std::span shaders, VkImageLayout attachmentLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); Pipeline(const Context& context, const State& state, const VertexInputLayout& vertexInput, const PipelineSetLayouts& resources, std::span shaders, VkImageLayout attachmentLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); ~Pipeline(); + static bool Prebuild(const Context& context, const State& state, const VertexInputLayout& vertexInput, const PipelineSetLayouts& resources, std::span shaders); Pipeline(const Pipeline&) = delete; Pipeline& operator=(const Pipeline&) = delete; VkPipelineLayout Layout() const; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/PipelineLibrary.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/PipelineLibrary.hpp index 4ffb5e3bc..5541197b2 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/PipelineLibrary.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/PipelineLibrary.hpp @@ -33,7 +33,7 @@ struct OptimizedPipeline { void Release() noexcept; }; -VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPipelineCreateInfo& info, const VkPipelineRenderingCreateInfoKHR& rendering, const VkPipelineLayoutCreateInfo& layout, const PipelineLibraryKeys& keys, std::shared_ptr* optimized = nullptr, std::span modules = {}); +VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPipelineCreateInfo& info, const VkPipelineRenderingCreateInfoKHR& rendering, const VkPipelineLayoutCreateInfo& layout, const PipelineLibraryKeys& keys, std::shared_ptr* optimized = nullptr, std::span modules = {}, bool partsOnly = false); void WaitForOptimizedPipelines(VkDevice device); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp index aaff3c414..911f1a10a 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp @@ -152,6 +152,9 @@ bool ShadersWriteMemory(std::span shaders) { namespace { +thread_local bool prebuilding = false; +thread_local bool prebuilt = false; + VkShaderStageFlags pushStagesOf(const Context& context) { return PreRasterizationPushStages(context.meshShader, context.tessellationShader, context.geometryShader); } @@ -469,7 +472,18 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput pipelineInfo.renderPass = renderPass; if (dynamicRendering) pipelineInfo.pNext = &rendering; timing.Mark("modules_and_state"); - if (libraries) pipeline = LinkPipelineFromLibraries(context, pipelineInfo, rendering, layoutInfo, keys, &optimized, _modules); + if (prebuilding && !libraries) { + for (const auto module : _modules) context.Function("vkDestroyShaderModule")(context.device, module, nullptr); + _modules.clear(); + return; + } + if (libraries) pipeline = LinkPipelineFromLibraries(context, pipelineInfo, rendering, layoutInfo, keys, &optimized, _modules, prebuilding); + if (prebuilding) { + for (const auto module : _modules) context.Function("vkDestroyShaderModule")(context.device, module, nullptr); + _modules.clear(); + prebuilt = true; + return; + } else Check(context.Function("vkCreateGraphicsPipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &pipeline), "vkCreateGraphicsPipelines"); if (alternateAlpha) { blend.pAttachments = secondChannels.data(); @@ -489,6 +503,15 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput } } +bool Pipeline::Prebuild(const Context& context, const State& state, const VertexInputLayout& vertexInput, const PipelineSetLayouts& resources, std::span shaders) { + struct Scope { + Scope() { prebuilding = true; prebuilt = false; } + ~Scope() { prebuilding = false; } + } scope; + const Pipeline pipeline(context, state, vertexInput, resources, shaders); + return prebuilt; +} + Pipeline::~Pipeline() { release(); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp index df499bb21..a6bbdb1cb 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp @@ -102,10 +102,10 @@ std::span PipelineLibraryDynamicStates() { return dynamicStates; } -VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPipelineCreateInfo& info, const VkPipelineRenderingCreateInfoKHR& rendering, const VkPipelineLayoutCreateInfo& layout, const PipelineLibraryKeys& keys, std::shared_ptr* optimized, std::span modules) { +VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPipelineCreateInfo& info, const VkPipelineRenderingCreateInfoKHR& rendering, const VkPipelineLayoutCreateInfo& layout, const PipelineLibraryKeys& keys, std::shared_ptr* optimized, std::span modules, bool partsOnly) { Require(info.renderPass == VK_NULL_HANDLE && info.pDynamicState != nullptr, "pipeline library parts need dynamic rendering and the library dynamic states"); auto& store = Libraries(); - std::lock_guard lock(store.mutex); + std::unique_lock lock(store.mutex); auto& device = store.devices[context.device]; if (device.destroyPipeline == nullptr) { device.destroyPipeline = context.Function("vkDestroyPipeline"); @@ -193,7 +193,15 @@ VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPip break; } VkPipeline handle = VK_NULL_HANDLE; - Check(context.Function("vkCreateGraphicsPipelines")(context.device, context.pipelineCache, 1, &create, nullptr, &handle), "vkCreateGraphicsPipelines library"); + if (partsOnly) lock.unlock(); + const auto created = context.Function("vkCreateGraphicsPipelines")(context.device, context.pipelineCache, 1, &create, nullptr, &handle); + if (partsOnly) lock.lock(); + Check(created, "vkCreateGraphicsPipelines library"); + if (const auto raced = cache.find(key); raced != cache.end()) { + device.destroyPipeline(context.device, handle, nullptr); + libraries.push_back(raced->second); + continue; + } cache.emplace(key, handle); if (optimize) { for (std::uint32_t stage = 0; stage < create.stageCount; ++stage) { @@ -207,6 +215,7 @@ VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPip ++device.counters.built[part]; libraries.push_back(handle); } + if (partsOnly) return VK_NULL_HANDLE; PerformanceTimer timing("Vulkan.GraphicsPipelineLink"); VkPipelineLibraryCreateInfoKHR linked{VK_STRUCTURE_TYPE_PIPELINE_LIBRARY_CREATE_INFO_KHR}; linked.libraryCount = static_cast(libraries.size()); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedGraphics.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedGraphics.cpp index 5d456afdf..7b4970657 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedGraphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedGraphics.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAgcDriver/Graphics/include/PipelineLibrary.hpp" #include "VulkanTestDevice.hpp" #include "prx/libSceAgcDriver/Execution/include/ShaderPreparation.hpp" #include "SceShaders.hpp" @@ -130,6 +131,22 @@ void Check(AgcDriver::VulkanDevice& device, AgcDriver::Graphics::ShaderPath path } const auto stages = PrepareGraphicsStages(prepared, target); Require(stages.size() == (tessellation ? 4u : 2u), "graphics preparation compiled the wrong stages"); + if (!mesh && !tessellation && target.registrationModules) { + const auto prebuild = [&](const std::vector& group) { + Require(group.front().registration != nullptr && group.back().registration != nullptr, "registration modules: a prepared stage has no registration result"); + const std::array shaders{{{group.front().stage, group.front().registration.get(), group.front().pushOffset}, {group.back().stage, group.back().registration.get(), group.back().pushOffset}}}; + const auto before = AgcDriver::Graphics::PipelineLibraryCountersOf(device.Device()); + Require(device.PrebuildGraphicsLibraries(prepared.state.stages, shaders), "registration modules: the shader libraries were not prebuilt"); + const auto after = AgcDriver::Graphics::PipelineLibraryCountersOf(device.Device()); + return std::pair{after.built[1] - before.built[1] + after.built[2] - before.built[2], after.linked - before.linked}; + }; + const auto [first, firstLinks] = prebuild(stages); + Require(first == 2u && firstLinks == 0u, "registration modules: the prebuild did not build exactly the two shader libraries without linking"); + const auto again = PrepareGraphicsStages(prepared, target); + Require(again.front().registration != stages.front().registration, "registration modules: the second preparation reused the first result object"); + const auto [second, secondLinks] = prebuild(again); + Require(second == 0u && secondLinks == 0u, "registration modules: another materialization of the same stages did not find the prebuilt libraries"); + } for (const auto& stage : stages) stage.snapshot->prepared->entries.push_back(stage.entry); const auto& pixelArtifact = GetPreparedArtifact(*stages.back().entry.handle); Require(!pixelArtifact.fragmentParameters.empty() && pixelArtifact.fragmentParameters.front().sourceLocation == 3u, "prepared fragment lost interpolant mapping"); diff --git a/core/shader/recompiler/CMakeLists.txt b/core/shader/recompiler/CMakeLists.txt index 88aafc8fe..6d3d68e2d 100644 --- a/core/shader/recompiler/CMakeLists.txt +++ b/core/shader/recompiler/CMakeLists.txt @@ -236,6 +236,7 @@ function(add_shader_recompiler target runtimeTarget) MatchesPreparedShader GetPreparedArtifact MaterializeShader + RegistrationResult ResolveSource ResourceMaterializer::SpecializationNanoseconds RuntimeAbi::RequireVersion diff --git a/core/shader/recompiler/Optimization/include/Optimization/ResourceProgram.hpp b/core/shader/recompiler/Optimization/include/Optimization/ResourceProgram.hpp index f23d1b8f6..909f3cea1 100644 --- a/core/shader/recompiler/Optimization/include/Optimization/ResourceProgram.hpp +++ b/core/shader/recompiler/Optimization/include/Optimization/ResourceProgram.hpp @@ -70,6 +70,7 @@ private: void BuildPreparedShaderKey(const RecompileRequest& request, std::vector& key); [[nodiscard]] bool MatchesPreparedShader(const RecompileRequest& request, const SourceHandle& handle, std::span key); [[nodiscard]] const CompiledShaderArtifact& GetPreparedArtifact(const SourceHandle& handle); +[[nodiscard]] std::shared_ptr RegistrationResult(const RecompileRequest& request, const SourceHandle& handle); [[nodiscard]] std::shared_ptr MaterializeShader(const RecompileRequest& request, const ResourceCapture& capture, const SourceHandle& handle); [[nodiscard]] std::shared_ptr ResolveSource(const RecompileRequest& request); [[nodiscard]] std::shared_ptr CaptureResources(const RecompileRequest& request, const SrtRuntime& runtime, const SourceHandle& handle); diff --git a/core/shader/recompiler/Recompiler.cpp b/core/shader/recompiler/Recompiler.cpp index f7d2ff5b2..2b7f9cf59 100644 --- a/core/shader/recompiler/Recompiler.cpp +++ b/core/shader/recompiler/Recompiler.cpp @@ -1216,6 +1216,18 @@ const CompiledShaderArtifact& GetPreparedArtifact(const SourceHandle& handle) { return handle.artifact->artifact; } +std::shared_ptr RegistrationResult(const RecompileRequest& request, const SourceHandle& handle) { + if (!request.target.registrationModules) throw std::runtime_error("ShaderRecompiler: registration results need registration modules"); + if (handle.artifact == nullptr || !MatchesPreparedShader(request, handle)) throw std::runtime_error("ShaderRecompiler: prepared artifact does not match the static ABI"); + const auto& info = handle.artifact->info.info; + ResourceSnapshot snapshot; + snapshot.buffers.assign(info.buffers.size(), {{}, 4u}); + snapshot.images.assign(info.images.size(), {{}, 8u}); + snapshot.samplers.assign(info.samplers.size(), {{}, 4u}); + snapshot.userData.assign(RuntimeAbi::UserDataCapacity, 0u); + return std::make_shared(materializeResult(*handle.artifact, request, snapshot)); +} + std::shared_ptr MaterializeShader(const RecompileRequest& request, const ResourceCapture& capture, const SourceHandle& handle) { if (!MatchesPreparedShader(request, handle)) throw std::runtime_error("ShaderRecompiler: prepared artifact does not match the static ABI"); if (capture.source != handle.source || capture.plan != handle.source->plan) throw std::runtime_error("ShaderRecompiler: resource capture belongs to another prepared shader");