perf(agc): build the shader pipeline libraries when graphics stages are resolved at registration
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
d7e0f27d6b
commit
7f64d14772
|
|
@ -37,6 +37,9 @@ void DecodeGraphicsPrograms(DrawDecode& decoded, const QueueState& queue, const
|
|||
struct PreparedGraphicsStage {
|
||||
std::shared_ptr<const ShaderSnapshot> snapshot;
|
||||
PreparedShaders::Entry entry;
|
||||
ShaderRecompiler::ShaderStage stage = ShaderRecompiler::ShaderStage::Vertex;
|
||||
std::uint32_t pushOffset = 0;
|
||||
std::shared_ptr<const ShaderRecompiler::RecompileResult> registration;
|
||||
};
|
||||
|
||||
std::vector<PreparedGraphicsStage> PrepareGraphicsStages(const DrawDecode& decoded, const ShaderRecompiler::SpirvTarget& target);
|
||||
|
|
|
|||
|
|
@ -299,6 +299,7 @@ public:
|
|||
};
|
||||
IndirectDrawSupport DrawIndirectSupport() const;
|
||||
void EnqueueDraw(const Graphics::State& graphics, const Pm4::DrawParameters& draw, std::span<const Graphics::CompiledShader> shaders, std::span<const Graphics::GuestMemorySnapshot> snapshots = {});
|
||||
bool PrebuildGraphicsLibraries(const Graphics::ShaderStages& stages, std::span<const Graphics::CompiledShader> shaders);
|
||||
|
||||
private:
|
||||
VulkanDevice(const PresentationWindow* window, bool compileOnly);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
#include <deque>
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <atomic>
|
||||
#include <pthread.h>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
|
|
@ -30,7 +31,8 @@ namespace AgcDriver::DriverDetail {
|
|||
|
||||
std::shared_ptr<const ShaderRecompiler::SourceHandle> 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<void>(Driver::dumpRequest(request.shader.codeAddress, request));
|
||||
|
|
@ -860,7 +862,14 @@ std::vector<PreparedGraphicsStage> 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<const ShaderRecompiler::RecompileResult> 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<const Shader* const> 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<std::shared_ptr<const ShaderRecompiler::RecompileResult>> results;
|
||||
std::vector<std::pair<ShaderRecompiler::ShaderStage, std::uint32_t>> 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<Graphics::CompiledShader> 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<void>(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;
|
||||
|
|
|
|||
|
|
@ -3058,6 +3058,25 @@ void VulkanDevice::ColorMetadataPass(const Graphics::ColorMetadataPass& pass) {
|
|||
Graphics::RunColorMetadataPass(graphicsContext(), pass);
|
||||
}
|
||||
|
||||
bool VulkanDevice::PrebuildGraphicsLibraries(const Graphics::ShaderStages& stages, std::span<const Graphics::CompiledShader> 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<const Graphics::CompiledShader> shaders, std::span<const Graphics::GuestMemorySnapshot> snapshots, std::shared_ptr<const DrawRecipe>* recipe) {
|
||||
for (const auto& shader : shaders) {
|
||||
require(shader.program != nullptr, "missing compiled shader");
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ public:
|
|||
Pipeline(const Context& context, const State& state, const VertexInputLayout& vertexInput, const ShaderResources& resources, std::span<const CompiledShader> shaders, VkImageLayout attachmentLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||
Pipeline(const Context& context, const State& state, const VertexInputLayout& vertexInput, const PipelineSetLayouts& resources, std::span<const CompiledShader> 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<const CompiledShader> shaders);
|
||||
Pipeline(const Pipeline&) = delete;
|
||||
Pipeline& operator=(const Pipeline&) = delete;
|
||||
VkPipelineLayout Layout() const;
|
||||
|
|
|
|||
|
|
@ -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<OptimizedPipeline>* optimized = nullptr, std::span<VkShaderModule> modules = {});
|
||||
VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPipelineCreateInfo& info, const VkPipelineRenderingCreateInfoKHR& rendering, const VkPipelineLayoutCreateInfo& layout, const PipelineLibraryKeys& keys, std::shared_ptr<OptimizedPipeline>* optimized = nullptr, std::span<VkShaderModule> modules = {}, bool partsOnly = false);
|
||||
|
||||
void WaitForOptimizedPipelines(VkDevice device);
|
||||
|
||||
|
|
|
|||
|
|
@ -152,6 +152,9 @@ bool ShadersWriteMemory(std::span<const CompiledShader> 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<PFN_vkDestroyShaderModule>("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<PFN_vkDestroyShaderModule>("vkDestroyShaderModule")(context.device, module, nullptr);
|
||||
_modules.clear();
|
||||
prebuilt = true;
|
||||
return;
|
||||
}
|
||||
else Check(context.Function<PFN_vkCreateGraphicsPipelines>("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<const CompiledShader> 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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,10 +102,10 @@ std::span<const VkDynamicState> PipelineLibraryDynamicStates() {
|
|||
return dynamicStates;
|
||||
}
|
||||
|
||||
VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPipelineCreateInfo& info, const VkPipelineRenderingCreateInfoKHR& rendering, const VkPipelineLayoutCreateInfo& layout, const PipelineLibraryKeys& keys, std::shared_ptr<OptimizedPipeline>* optimized, std::span<VkShaderModule> modules) {
|
||||
VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPipelineCreateInfo& info, const VkPipelineRenderingCreateInfoKHR& rendering, const VkPipelineLayoutCreateInfo& layout, const PipelineLibraryKeys& keys, std::shared_ptr<OptimizedPipeline>* optimized, std::span<VkShaderModule> 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<PFN_vkDestroyPipeline>("vkDestroyPipeline");
|
||||
|
|
@ -193,7 +193,15 @@ VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPip
|
|||
break;
|
||||
}
|
||||
VkPipeline handle = VK_NULL_HANDLE;
|
||||
Check(context.Function<PFN_vkCreateGraphicsPipelines>("vkCreateGraphicsPipelines")(context.device, context.pipelineCache, 1, &create, nullptr, &handle), "vkCreateGraphicsPipelines library");
|
||||
if (partsOnly) lock.unlock();
|
||||
const auto created = context.Function<PFN_vkCreateGraphicsPipelines>("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<std::uint32_t>(libraries.size());
|
||||
|
|
|
|||
|
|
@ -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<PreparedGraphicsStage>& group) {
|
||||
Require(group.front().registration != nullptr && group.back().registration != nullptr, "registration modules: a prepared stage has no registration result");
|
||||
const std::array<AgcDriver::Graphics::CompiledShader, 2> 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");
|
||||
|
|
|
|||
|
|
@ -236,6 +236,7 @@ function(add_shader_recompiler target runtimeTarget)
|
|||
MatchesPreparedShader
|
||||
GetPreparedArtifact
|
||||
MaterializeShader
|
||||
RegistrationResult
|
||||
ResolveSource
|
||||
ResourceMaterializer::SpecializationNanoseconds
|
||||
RuntimeAbi::RequireVersion
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ private:
|
|||
void BuildPreparedShaderKey(const RecompileRequest& request, std::vector<std::uint64_t>& key);
|
||||
[[nodiscard]] bool MatchesPreparedShader(const RecompileRequest& request, const SourceHandle& handle, std::span<const std::uint64_t> key);
|
||||
[[nodiscard]] const CompiledShaderArtifact& GetPreparedArtifact(const SourceHandle& handle);
|
||||
[[nodiscard]] std::shared_ptr<const RecompileResult> RegistrationResult(const RecompileRequest& request, const SourceHandle& handle);
|
||||
[[nodiscard]] std::shared_ptr<const RecompileResult> MaterializeShader(const RecompileRequest& request, const ResourceCapture& capture, const SourceHandle& handle);
|
||||
[[nodiscard]] std::shared_ptr<const SourceHandle> ResolveSource(const RecompileRequest& request);
|
||||
[[nodiscard]] std::shared_ptr<const ResourceCapture> CaptureResources(const RecompileRequest& request, const SrtRuntime& runtime, const SourceHandle& handle);
|
||||
|
|
|
|||
|
|
@ -1216,6 +1216,18 @@ const CompiledShaderArtifact& GetPreparedArtifact(const SourceHandle& handle) {
|
|||
return handle.artifact->artifact;
|
||||
}
|
||||
|
||||
std::shared_ptr<const RecompileResult> 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<RecompileResult>(materializeResult(*handle.artifact, request, snapshot));
|
||||
}
|
||||
|
||||
std::shared_ptr<const RecompileResult> 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");
|
||||
|
|
|
|||
Loading…
Reference in New Issue