perf(shader): build a shader's front end once on first use

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Adrià Franch 2026-10-03 18:44:58 +02:00
parent d3ec1f15fb
commit 81961ca91b
3 changed files with 77 additions and 7 deletions

View File

@ -1,6 +1,7 @@
#ifndef CORE_SHADER_RECOMPILIER_INTERMEDIATEREPRESENTATION_INCLUDE_INTERMEDIATEREPRESENTATION_IRMETADATA_RESOURCEPLAN_HPP
#define CORE_SHADER_RECOMPILIER_INTERMEDIATEREPRESENTATION_INCLUDE_INTERMEDIATEREPRESENTATION_IRMETADATA_RESOURCEPLAN_HPP
#include "IntermediateRepresentation/IrBlock.hpp"
#include "IntermediateRepresentation/IrValue.hpp"
#include "IntermediateRepresentation/IrMetadata/ControlFlowInfo.hpp"
#include "IntermediateRepresentation/IrMetadata/DescriptorBinding.hpp"
@ -55,6 +56,7 @@ struct IrResourcePlan {
std::uint32_t userDataBase = 0;
std::uint32_t userDataCount = 64;
std::vector<std::unique_ptr<IrValue>> valueStorage;
std::vector<std::unique_ptr<IrBlock>> blockStorage;
std::vector<MemoryInfo> memoryInfo;
std::vector<DescriptorSource> descriptorSources;
std::vector<ResourceBlock> controlFlow;

View File

@ -16,6 +16,7 @@
#include <mutex>
#include <optional>
#include <span>
#include <unordered_map>
#include <stdexcept>
#include <string>
#include <utility>
@ -794,6 +795,68 @@ void ResourceMaterializer::Apply(IrProgram& program, const ResourceSpecializatio
resources.memoryInfo = std::move(memoryInfo);
}
namespace {
void ownPlanValues(IrResourcePlan& plan) {
std::vector<IrValue**> roots;
for (auto& source : plan.descriptorSources) {
for (auto& dword : source.dwords) roots.push_back(&dword);
}
for (auto& read : plan.srtReads) roots.push_back(&read.value);
for (auto& block : plan.controlFlow) roots.push_back(&block.condition);
for (auto& value : plan.uniformFill.values) roots.push_back(&value);
std::unordered_map<const IrValue*, IrValue*> clones;
std::vector<const IrValue*> order;
std::vector<const IrValue*> pending;
for (const auto* root : roots) {
if (*root != nullptr) pending.push_back(*root);
}
while (!pending.empty()) {
const auto* value = pending.back();
pending.pop_back();
if (!clones.emplace(value, nullptr).second) continue;
order.push_back(value);
for (const auto* argument : value->Arguments()) {
if (argument != nullptr) pending.push_back(argument);
}
}
for (const auto* value : order) {
auto clone = std::make_unique<IrValue>(value->Opcode(), value->Type(), value->Id());
clone->SetFlags(value->Flags<std::uint64_t>());
if (value->HasImmediate()) clone->SetImmediateU64(value->ImmediateU64());
clone->SetRegister(value->Register());
clones[value] = clone.get();
plan.valueStorage.push_back(std::move(clone));
}
std::unordered_map<const IrBlock*, IrBlock*> blocks;
const auto blockFor = [&](const IrBlock* block) {
auto& clone = blocks[block];
if (clone == nullptr) {
plan.blockStorage.push_back(std::make_unique<IrBlock>(block->Id()));
clone = plan.blockStorage.back().get();
}
return clone;
};
for (const auto* value : order) {
auto* clone = clones.at(value);
for (std::size_t index = 0; index < value->ArgumentCount(); index++) {
const auto* argument = value->Argument(index);
auto* mapped = argument == nullptr ? nullptr : clones.at(argument);
if (value->IsPhi()) {
clone->AddPhiOperand(blockFor(value->PhiBlock(index)), mapped);
} else {
clone->AddArgument(mapped);
}
}
}
for (auto* root : roots) {
if (*root != nullptr) *root = clones.at(*root);
}
}
}
IrResourcePlan ResourceMaterializer::ExtractPlan(const IrProgram& program) const {
const IrResourcePlan& source = program.Resources();
if (!source.resourceTrackingComplete || !source.srtPlanComplete) {
@ -814,6 +877,7 @@ IrResourcePlan ResourceMaterializer::ExtractPlan(const IrProgram& program) const
plan.resourceTrackingComplete = source.resourceTrackingComplete;
plan.info = source.info;
plan.uniformFill = source.uniformFill;
ownPlanValues(plan);
const auto addSource = [&plan](std::uint32_t index) {
if (index >= plan.descriptorSources.size()) {
throw std::runtime_error("ResourceMaterializer::ExtractPlan resource references an unknown descriptor source");

View File

@ -188,6 +188,7 @@ struct SourceEntry {
// rethrown: the front end ran every pass before failing, ~13 ms per dispatch of a shader the
// title issues every frame (0x1048947300 at the intro video). APS5_NO_FAILURE_MEMO=1 rebuilds.
std::exception_ptr planFailure;
std::unique_ptr<IrProgram> program;
std::vector<std::shared_ptr<const CompiledVariant>> variants;
// The result memo, most recently used first, at most ResultMemoEntries (under mutex).
std::list<ResultMemoEntry> memo;
@ -197,15 +198,14 @@ struct SourceEntry {
namespace {
struct ResourceProgram {
explicit ResourceProgram(const RecompileRequest& request) : program(PrepareResourceProgram(request)), plan(ResourceMaterializer{}.ExtractPlan(program)) {}
explicit ResourceProgram(const RecompileRequest& request) : program(std::make_unique<IrProgram>(PrepareResourceProgram(request))), plan(std::make_shared<const IrResourcePlan>(ResourceMaterializer{}.ExtractPlan(*program))) {}
IrProgram program;
IrResourcePlan plan;
std::unique_ptr<IrProgram> program;
std::shared_ptr<const IrResourcePlan> plan;
};
std::shared_ptr<const IrResourcePlan> makeResourcePlan(const RecompileRequest& request) {
const auto resource = std::make_shared<ResourceProgram>(request);
return std::shared_ptr<const IrResourcePlan>(resource, &resource->plan);
return ResourceProgram(request).plan;
}
struct SourceKeyHash {
@ -263,7 +263,9 @@ std::shared_ptr<SourceEntry> getSource(const RecompileRequest& request) {
static const bool memoFailures = std::getenv("APS5_NO_FAILURE_MEMO") == nullptr;
if (memoFailures && source->planFailure) std::rethrow_exception(source->planFailure);
try {
source->plan = makeResourcePlan(request);
ResourceProgram resource(request);
source->plan = std::move(resource.plan);
source->program = std::move(resource.program);
} catch (...) {
if (memoFailures) source->planFailure = std::current_exception();
throw;
@ -389,10 +391,12 @@ std::shared_ptr<const CompiledVariant> findOrCompileVariant(SourceEntry& source,
}
}
if (variant == nullptr) {
auto program = PrepareResourceProgram(request);
auto program = source.program != nullptr ? std::move(*source.program) : PrepareResourceProgram(request);
source.program.reset();
variant = std::make_shared<const CompiledVariant>(compileVariant(request, std::move(program), snapshot, specialization));
if (disk) ShaderDiskCache::Store(std::move(diskKey), variant);
}
source.program.reset();
source.variants.push_back(variant);
return variant;
}