Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ddb6680
perf(recompiler): read buffer V# words in the shader when modules are…
adriadam10 Oct 10, 2026
765ea6a
perf(recompiler): keep descriptor heaps uncompacted and read image mo…
adriadam10 Oct 11, 2026
baf3437
perf(recompiler): read depth-compare state and export mappings at run…
adriadam10 Oct 11, 2026
4d0b5c7
perf(agc): give the fragment stage its own descriptor set when module…
adriadam10 Oct 11, 2026
d7e0f27
perf(agc): describe pipeline set layouts from the compiled shaders alone
adriadam10 Oct 11, 2026
7f64d14
perf(agc): build the shader pipeline libraries when graphics stages a…
adriadam10 Oct 11, 2026
e9def90
perf(agc): prebuild each stage's library alone and keep two descripto…
adriadam10 Oct 11, 2026
6755672
perf(recompiler): read vertex attribute component selectors at run ti…
adriadam10 Oct 11, 2026
9eb42ca
fix(agc): destroy pipeline library shader modules once the library is…
adriadam10 Oct 11, 2026
a326fa8
fix(recompiler): give registration results a flattened SRT placeholder
adriadam10 Oct 11, 2026
361c7e2
perf(agc): keep oversized shaders specialized and prebuild libraries …
adriadam10 Oct 11, 2026
acc0a04
docs: describe shader modules and pipeline libraries built at registr…
adriadam10 Oct 11, 2026
d809e6d
fix(agc): close the library prebuild pool only if it was created
adriadam10 Oct 12, 2026
672e43c
perf(recompiler): keep compute shaders specialized; only prebuilt ver…
adriadam10 Oct 12, 2026
68164ae
docs: note that compute shaders keep the specialized form
adriadam10 Oct 12, 2026
60151b6
perf(recompiler): keep the format word specialized for buffers access…
adriadam10 Oct 12, 2026
56d5a49
perf(recompiler): read the formatted buffer helper's V# words as spec…
adriadam10 Oct 12, 2026
f9b5928
perf(recompiler): keep the buffer base misalignment specialized in re…
adriadam10 Oct 12, 2026
b1ee794
test(agc): give the formatted buffer vertex case clip distances and t…
adriadam10 Oct 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions core/libs/prx/libSceAgcDriver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1010,6 +1010,8 @@ target_include_directories(agc_driver_provoking_vertex_tests PRIVATE ${agcTestSh

target_include_directories(agc_driver_prepared_exports_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)

target_include_directories(agc_driver_buffer_descriptor_runtime_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)

target_include_directories(agc_driver_prepared_publication_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)

target_include_directories(agc_driver_prepared_graphics_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ std::shared_ptr<const ShaderSnapshot> RegisteredHeader(const ShaderRegistry& reg
std::shared_ptr<const ShaderSnapshot> RegisteredProgram(const ShaderRegistry& registry, std::uint64_t address, std::initializer_list<std::uint8_t> types);
void PublishRegisteredShader(std::shared_ptr<ShaderRegistry>& registry, const std::shared_ptr<const ShaderSnapshot>& snapshot);
void SubmitRegistrationPreparation(std::function<void()> task);
void SubmitLibraryPrebuild(std::function<void()> task);
void CloseRegistrationPreparation();

void ResolvePreparedGraphics(const ShaderSnapshot& front, const std::shared_ptr<const ShaderSnapshot>& fragment, std::uint32_t primitiveType, const ShaderRecompiler::SpirvTarget& target);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ class VulkanDevice {
};
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <deque>
#include <functional>
#include <list>
#include <atomic>
#include <pthread.h>
#include <stdexcept>
#include <thread>
Expand All @@ -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));
Expand Down Expand Up @@ -571,6 +573,7 @@ struct RegisteredPreparation {
std::array<ShaderRecompiler::MemoryRegion, 2> memory{};
std::optional<ShaderRecompiler::RecompileRequest> request;
std::vector<std::uint64_t> abiKey;
std::shared_ptr<VulkanDevice> prebuildDevice;
};

std::unique_ptr<RegisteredPreparation> PlanRegistered(const ShaderSnapshot& snapshot, const VulkanDevice& device, const QueueState& state, bool registration, bool* deferred = nullptr) {
Expand Down Expand Up @@ -669,6 +672,18 @@ std::unique_ptr<RegisteredPreparation> PlanRegistered(const ShaderSnapshot& snap
return plan;
}

void PrebuildStageLibrary(const std::shared_ptr<VulkanDevice>& device, const Graphics::ShaderStages& stages, ShaderRecompiler::ShaderStage stage, std::shared_ptr<const ShaderRecompiler::RecompileResult> result, std::uint32_t pushOffset) {
if (result == nullptr || (stage != ShaderRecompiler::ShaderStage::Vertex && stage != ShaderRecompiler::ShaderStage::Fragment)) return;
SubmitLibraryPrebuild([device, stages, stage, result = std::move(result), pushOffset] {
const std::array<Graphics::CompiledShader, 1> shaders{{{stage, result.get(), pushOffset}}};
try {
static_cast<void>(device->PrebuildGraphicsLibraries(stages, shaders));
} catch (const std::exception& error) {
APS5_LOG_OUT("shader library prebuild skipped: %s", error.what());
}
});
}

std::vector<PreparedShaders::Entry> PrepareRegistered(RegisteredPreparation& plan) {
auto& request = *plan.request;
std::vector<PreparedShaders::Entry> entries;
Expand All @@ -678,6 +693,13 @@ std::vector<PreparedShaders::Entry> PrepareRegistered(RegisteredPreparation& pla
entries.push_back({plan.codeOffset, PrepareShaderWithDiagnostics(request, &preparation)});
};
append();
if (plan.prebuildDevice != nullptr && request.target.registrationModules && request.shader.stage == ShaderRecompiler::ShaderStage::Fragment) {
try {
PrebuildStageLibrary(plan.prebuildDevice, {Graphics::ShaderPath::Vertex, 0u, 32u, request.context.waveSize, {}, {}}, request.shader.stage, ShaderRecompiler::RegistrationResult(request, *entries.front().handle), request.layout.pushConstantOffsetBytes);
} catch (const std::exception& error) {
APS5_LOG_OUT("shader library prebuild skipped: %s", error.what());
}
}
if (request.context.compute) {
request.context.compute->partialThreads = {1, 1, 1};
append();
Expand All @@ -696,10 +718,32 @@ std::vector<PreparedShaders::Entry> PrepareRegistered(const ShaderSnapshot& snap
class RegistrationPreparePool {
public:
static RegistrationPreparePool& Instance() {
static auto* pool = new RegistrationPreparePool();
static auto* pool = new RegistrationPreparePool(std::max(2u, std::thread::hardware_concurrency() / 2u));
return *pool;
}

static RegistrationPreparePool& Prebuild() {
static auto* pool = [] {
const char* text = std::getenv("APS5_PREBUILD_THREADS");
auto* created = new RegistrationPreparePool(text != nullptr ? std::max(1u, static_cast<unsigned>(std::strtoul(text, nullptr, 10))) : 1u);
PrebuildSlot().store(created);
return created;
}();
return *pool;
}

static RegistrationPreparePool* ExistingPrebuild() {
return PrebuildSlot().load();
}

private:
static std::atomic<RegistrationPreparePool*>& PrebuildSlot() {
static std::atomic<RegistrationPreparePool*> slot{nullptr};
return slot;
}

public:

void Submit(std::function<void()> task) {
{
std::unique_lock lock(mutex);
Expand All @@ -720,8 +764,7 @@ class RegistrationPreparePool {
}

private:
RegistrationPreparePool() {
const auto count = std::max(2u, std::thread::hardware_concurrency() / 2u);
explicit RegistrationPreparePool(unsigned count) {
pthread_attr_t attributes;
pthread_attr_init(&attributes);
pthread_attr_setstacksize(&attributes, 64u << 20u);
Expand Down Expand Up @@ -802,8 +845,13 @@ void SubmitRegistrationPreparation(std::function<void()> task) {
RegistrationPreparePool::Instance().Submit(std::move(task));
}

void SubmitLibraryPrebuild(std::function<void()> task) {
RegistrationPreparePool::Prebuild().Submit(std::move(task));
}

void CloseRegistrationPreparation() {
RegistrationPreparePool::Instance().Close();
if (auto* prebuild = RegistrationPreparePool::ExistingPrebuild()) prebuild->Close();
}

std::vector<PreparedGraphicsStage> PrepareGraphicsStages(const DrawDecode& decoded, const ShaderRecompiler::SpirvTarget& target) {
Expand Down Expand Up @@ -860,7 +908,15 @@ 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& error) {
APS5_LOG_OUT("shader library prebuild skipped: %s", error.what());
}
}
prepared.push_back({program.snapshot, {program.codeOffset, handle}, program.binary.stage, offset, std::move(registration)});
pushOffset += bytes;
}
transaction.Commit();
Expand Down Expand Up @@ -930,6 +986,11 @@ 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 (decoded.state.stages.path == Graphics::ShaderPath::Vertex) {
for (const auto& stage : prepared) {
if (stage.registration != nullptr) PrebuildStageLibrary(localDevice, decoded.state.stages, stage.stage, stage.registration, stage.pushOffset);
}
}
PreparedShaderState::GraphicsAbi abi{key, registry, {}};
for (const auto& stage : prepared) abi.stages.push_back(stage.snapshot);
auto& graphicsAbis = transaction.Edit(*owner).graphicsAbis;
Expand Down Expand Up @@ -970,14 +1031,20 @@ void Driver::ResolveShaderAbi(const Shader* shader, std::span<const ShaderRegist
struct RegisteredAbiKeyStorage {};
auto& key = HostThreadLocal<std::vector<std::uint64_t>, RegisteredAbiKeyStorage>();
BuildRegisteredAbiKey(state, *localDevice, key);
const auto prepare = [&] {
const auto plan = PlanRegistered(*snapshot, *localDevice, state, false);
if (plan == nullptr) return std::vector<PreparedShaders::Entry>{};
plan->prebuildDevice = localDevice;
return PrepareRegistered(*plan);
};
const bool committedAbi = CommittedAbi(*snapshot, key);
auto entries = committedAbi ? std::vector<PreparedShaders::Entry>{} : PrepareRegistered(*snapshot, *localDevice, state, false);
auto entries = committedAbi ? std::vector<PreparedShaders::Entry>{} : prepare();
const auto& registeredAbis = transaction.Read(*snapshot).registeredAbis;
if (std::ranges::find(registeredAbis, key) != registeredAbis.end()) {
transaction.Commit();
return;
}
if (committedAbi) entries = PrepareRegistered(*snapshot, *localDevice, state, false);
if (committedAbi) entries = prepare();
auto& prepared = transaction.Edit(*snapshot);
for (auto& entry : entries) {
const auto duplicate = std::ranges::any_of(prepared.entries, [&](const auto& existing) { return existing.handle->artifact == entry.handle->artifact; });
Expand Down Expand Up @@ -1164,6 +1231,7 @@ void Driver::RegisterShader(const Shader* shader) {
std::shared_ptr<RegisteredPreparation> preparation;
if (AsyncRegistrationPrepare()) {
preparation = PlanRegistered(snapshot, *localDevice, registered, true, &snapshot.prepared->deferred);
if (preparation != nullptr) preparation->prebuildDevice = localDevice;
if (preparation != nullptr && (snapshot.type == 0 || snapshot.type == 1)) BuildRegisteredAbiKey(registered, *localDevice, preparation->abiKey);
snapshot.prepared->pending = preparation != nullptr;
} else {
Expand Down
39 changes: 39 additions & 0 deletions core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2926,6 +2926,7 @@ ShaderRecompiler::SpirvTarget VulkanDevice::buildTarget() const {
target.narrowSubgroupClock = state->narrowSubgroupClock;
target.float64DenormPreserve = state->float64DenormPreserve;
target.fixedPushSlots = state->graphicsPipelineLibrary;
target.registrationModules = state->graphicsPipelineLibrary;
target.srgbDecodeFormats = state->srgbDecodeFormats;
if (state->meshShader) {
const auto& mesh = state->meshLimits;
Expand Down Expand Up @@ -3057,6 +3058,44 @@ 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};
std::vector<Graphics::CompiledShader> group(shaders.begin(), shaders.end());
if (group.size() == 1u) {
const bool vertex = group.front().stage == ShaderRecompiler::ShaderStage::Fragment;
static const auto stub = [](bool vertexStub) {
auto result = std::make_shared<ShaderRecompiler::RecompileResult>();
const std::uint32_t model = vertexStub ? 0u : 4u;
std::vector<std::uint32_t> words{0x07230203u, 0x00010000u, 0u, 6u, 0u, 0x00020011u, 1u, 0x0003000eu, 0u, 1u, 0x0005000fu, model, 4u, 0x6e69616du, 0u};
if (!vertexStub) words.insert(words.end(), {0x00030010u, 4u, 7u});
words.insert(words.end(), {0x00020013u, 2u, 0x00030021u, 3u, 2u, 0x00050036u, 2u, 4u, 0u, 3u, 0x000200f8u, 5u, 0x000100fdu, 0x00010038u});
result->spirv = std::move(words);
result->variantId = vertexStub ? ~std::uint64_t{0} - 1u : ~std::uint64_t{0} - 2u;
result->runtimeAbiVersion = ShaderRecompiler::RuntimeAbi::Version;
return result;
};
static const auto vertexStub = stub(true);
static const auto fragmentStub = stub(false);
if (vertex) group.insert(group.begin(), {ShaderRecompiler::ShaderStage::Vertex, vertexStub.get(), 0u});
else group.push_back({ShaderRecompiler::ShaderStage::Fragment, fragmentStub.get(), Graphics::PipelinePushSlotBytes});
}
const auto layouts = Graphics::DescribeSetLayouts(context, group);
return Graphics::Pipeline::Prebuild(context, state, Graphics::VertexInputLayout{}, layouts.View(), group);
}

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");
Expand Down
11 changes: 11 additions & 0 deletions core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ class Pipeline {
// before, during and after the pass (GENERAL for resident targets, which then need no
// transitions).
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;
Expand Down Expand Up @@ -98,6 +100,15 @@ class Pipeline {
// only evicted once no recorded draw holds them. Debug aid: APS5_NO_PIPELINE_CACHE=1 builds one per
// draw as before.
std::shared_ptr<Pipeline> CachedPipeline(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);
std::shared_ptr<Pipeline> CachedPipeline(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);
struct DescribedSetLayouts {
PipelineSetLayouts layouts;
std::vector<std::uint32_t> mainKey;
std::vector<std::uint32_t> fragmentKey;
PipelineSetLayouts View() const { return {layouts.main, layouts.fragment, layouts.split, mainKey, fragmentKey}; }
};
DescribedSetLayouts DescribeSetLayouts(const Context& context, std::span<const CompiledShader> shaders);
bool SplitsSets(const Context& context, std::span<const CompiledShader> shaders);
// Destroys the cached pipelines of a device; to be called before the device goes away. Without it,
// entries of a gone device are recognised by their buffer pool (made and reset with the device, so
// it tells device instances apart when the loader reuses a VkDevice handle) and forgotten unused.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ struct PipelineLibraryKeys {
std::vector<std::byte> fragmentOutput;
std::vector<std::byte> renderPass;
std::vector<std::byte> layout;
std::vector<std::byte> preRasterizationLayout;
std::vector<std::byte> fragmentLayout;
};

std::span<const VkDynamicState> PipelineLibraryDynamicStates();
Expand All @@ -31,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, bool partsOnly = false);

void WaitForOptimizedPipelines(VkDevice device);

Expand Down
Loading
Loading