feat(shader): implement enable cache field in recompile request

This commit is contained in:
boykopovar 2026-09-24 20:59:34 +03:00
parent e98d2163f4
commit bf43995e8e
11 changed files with 365 additions and 27 deletions

View File

@ -355,7 +355,7 @@ private:
request.context.memory = captured;
timing.Mark("request_memory");
const auto compiled = ShaderRecompiler::Recompile(request);
timing.Mark("shader_compile");
timing.Mark(compiled.cacheHit ? "shader_cache_hit" : "shader_compile");
std::vector<Graphics::GuestMemorySnapshot> snapshots;
for (const auto& region : captured) snapshots.push_back({region.guestAddress, region.bytes});
timing.Mark("snapshots");
@ -481,7 +481,7 @@ private:
request.context.memory = memory;
shaderTiming.Mark("request_memory");
results.push_back(ShaderRecompiler::Recompile(request));
shaderTiming.Mark("compile");
shaderTiming.Mark(results.back().cacheHit ? "cache_hit" : "compile");
const auto& result = results.back();
if (!drawParameters.indexed && i == 0) {
const auto offsetValue = [&](std::int32_t sgpr) {

View File

@ -46,9 +46,8 @@ bool ShaderMemory::read(void* context, std::uint64_t address, std::uint32_t* val
}
void ShaderMemory::Capture(const ShaderRecompiler::RecompileRequest& request) {
auto program = ShaderRecompiler::PrepareResourceProgram(request);
const auto plan = ShaderRecompiler::GetResourcePlan(request);
constexpr ShaderRecompiler::ResourceMaterializer materializer;
const auto plan = materializer.ExtractPlan(program);
ShaderRecompiler::SrtRuntime runtime;
runtime.userData = request.context.userData;
runtime.shaderBase = request.shader.codeAddress;
@ -57,7 +56,7 @@ void ShaderMemory::Capture(const ShaderRecompiler::RecompileRequest& request) {
runtime.readSpecializationMemory = &read;
ShaderRecompiler::ResourceSnapshot snapshot;
ShaderRecompiler::ResourceSpecialization specialization;
materializer.Materialize(plan, runtime, snapshot, specialization);
materializer.Materialize(*plan, runtime, snapshot, specialization);
}
std::vector<ShaderRecompiler::MemoryRegion> ShaderMemory::Regions() const {

View File

@ -7,6 +7,7 @@
#endif
#include <array>
#include <iostream>
#include <future>
#include <stdexcept>
#include <string>
@ -115,6 +116,38 @@ int main() {
request.context.memory = regions;
const auto first = Recompile(request);
require(!first.spirv.empty(), "empty compiled shader");
require(!first.cacheHit, "first shader compilation unexpectedly hit the cache");
const auto plan = GetResourcePlan(request);
require(plan == GetResourcePlan(request), "resource plan was rebuilt");
const auto cached = Recompile(request);
require(cached.cacheHit, "unchanged shader did not hit the cache");
verifyResult(first, cached);
auto relocated = request;
relocated.shader.codeAddress += 0x1000;
require(Recompile(relocated).cacheHit, "shader relocation caused recompilation");
auto changedTarget = request;
changedTarget.target.subgroupSize = 32;
require(GetResourcePlan(changedTarget) != plan, "different target reused the source entry");
std::vector<std::uint32_t> changedCode(code.begin(), code.end());
changedCode.insert(changedCode.begin(), 0xbf800000u);
auto changedSource = request;
changedSource.shader.code = changedCode;
require(GetResourcePlan(changedSource) != plan, "changed code reused the source entry");
auto uncached = request;
uncached.useCache = false;
require(GetResourcePlan(uncached) != plan, "disabled cache reused the resource plan");
const auto fresh = Recompile(uncached);
require(!fresh.cacheHit, "disabled cache reused the compiled variant");
verifyResult(first, fresh);
require(!RequestSerializer{}.Deserialize(RequestSerializer{}.Serialize(uncached)).request.useCache, "cache policy was lost in serialization");
auto changedLayout = request;
changedLayout.layout.pushConstantSizeBytes = 64;
require(!Recompile(changedLayout).cacheHit, "binding layout change reused an incompatible variant");
require(Recompile(changedLayout).cacheHit, "new binding layout variant was not cached");
require(Recompile(request).cacheHit, "compiling a new variant evicted the original");
auto missingMemory = request;
missingMemory.context.memory = {};
expectFailure([&] { static_cast<void>(Recompile(missingMemory)); }, "SrtWalker::EvaluateRuntimeSources", "cache hit bypassed resource validation");
#if ANYPS5_ENABLE_SPIRV_TOOLS
auto invalidSpirv = first.spirv;
invalidSpirv[0] = 0;
@ -122,6 +155,29 @@ int main() {
expectFailure([&] { static_cast<void>(ValidateAndOptimizeSpirv(first.spirv, 0x00400000u, 0x00010600u)); }, "unsupported Vulkan/SPIR-V target", "incompatible target accepted");
expectFailure([&] { static_cast<void>(ValidateAndOptimizeSpirv(first.spirv, 0x00405000u, 0x00010600u)); }, "unsupported Vulkan target", "unknown Vulkan target accepted");
#endif
payload = 0x40000000u;
AgcDriver::ShaderMemory updatedMemory({});
updatedMemory.Capture(request);
const auto updatedRegions = updatedMemory.Regions();
auto updated = request;
updated.context.memory = updatedRegions;
const auto updatedCached = Recompile(updated);
require(updatedCached.cacheHit, "dynamic shader data caused recompilation");
updated.useCache = false;
verifyResult(updatedCached, Recompile(updated));
bool changedData = updatedCached.pushConstants != first.pushConstants;
for (std::size_t i = 0; i < first.bindings.size(); ++i) changedData = changedData || updatedCached.bindings.at(i).guestDescriptor != first.bindings[i].guestDescriptor;
require(changedData, "cache hit retained stale shader data");
auto concurrent = request;
concurrent.layout.pushConstantSizeBytes = 60;
std::array<std::future<RecompileResult>, 4> concurrentResults;
for (auto& future : concurrentResults) future = std::async(std::launch::async, [concurrent] { return Recompile(concurrent); });
std::uint32_t compilations = 0;
for (auto& future : concurrentResults) {
const auto result = future.get();
if (!result.cacheHit) ++compilations;
}
require(compilations == 1, "concurrent requests compiled the same variant repeatedly");
const auto serialized = RequestSerializer{}.Serialize(request);
table = 0;
payload = 0xdeadbeefu;

View File

@ -0,0 +1,145 @@
#ifndef CORE_SHADER_RECOMPILER_CACHEKEY_HPP
#define CORE_SHADER_RECOMPILER_CACHEKEY_HPP
#include "Recompiler.hpp"
#include <stdexcept>
#include <type_traits>
namespace ShaderRecompiler {
class RecompileCacheKey {
public:
static void Build(const RecompileRequest& request, std::vector<std::uint64_t>& key) {
key.clear();
append(key, request.shader.stage);
append(key, request.shader.code);
append(key, request.context.waveSize);
append(key, request.context.userDataBaseRegister);
append(key, request.context.userData.size());
append(key, request.context.compute);
append(key, request.context.pixel);
append(key, request.context.vertex);
append(key, request.target);
}
private:
template<typename TValue>
static void append(std::vector<std::uint64_t>& key, TValue value) requires (std::is_integral_v<TValue> || std::is_enum_v<TValue>) {
key.push_back(static_cast<std::uint64_t>(value));
}
template<typename TValue, std::size_t TSize>
static void append(std::vector<std::uint64_t>& key, const std::array<TValue, TSize>& values) {
for (const auto value : values) append(key, value);
}
template<typename TValue>
static void append(std::vector<std::uint64_t>& key, std::span<TValue> values) {
append(key, values.size());
for (const auto value : values) append(key, value);
}
template<typename TValue>
static void append(std::vector<std::uint64_t>& key, const std::optional<TValue>& value) {
append(key, value.has_value());
if (value) append(key, *value);
}
static void append(std::vector<std::uint64_t>& key, std::string_view value) {
append(key, value.size());
for (const unsigned char byte : value) append(key, byte);
}
static void append(std::vector<std::uint64_t>& key, const ShaderComputeStageInfo& value) {
append(key, value.numThreads);
append(key, value.ldsSizeDwords);
append(key, value.groupIdEnable);
append(key, value.tgSizeEnable);
append(key, value.threadIdComponentCount);
}
static void append(std::vector<std::uint64_t>& key, const ShaderPixelStageInfo& value) {
append(key, value.interpolatorCount);
if (value.interpolatorCount > value.interpolatorSettings.size()) throw std::runtime_error("Shader cache: invalid interpolator count");
for (std::uint32_t i = 0; i < value.interpolatorCount; ++i) append(key, value.interpolatorSettings[i]);
append(key, value.wave32);
append(key, value.perspectiveCenterVgpr);
append(key, value.hasPerspectiveCenterVgpr);
append(key, value.posX);
append(key, value.posY);
append(key, value.posZ);
append(key, value.posW);
append(key, value.frontFace);
append(key, value.ancillary);
append(key, value.sampleShading);
append(key, value.noPerspective);
append(key, value.pixelKillEnable);
append(key, value.depthExportEnable);
append(key, value.sampleMaskExportEnable);
append(key, value.earlyZ);
append(key, value.executeOnNoop);
append(key, value.targetOutputMode);
append(key, value.targetExportMapping);
}
static void append(std::vector<std::uint64_t>& key, const ShaderVertexResourceDestination& value) {
append(key, value.registerStart);
append(key, value.registersNum);
append(key, value.attrId);
append(key, value.fetchIndex);
}
static void append(std::vector<std::uint64_t>& key, const ShaderVertexStageInfo& value) {
append(key, value.resourcesNum);
append(key, value.fetchAttribReg);
append(key, value.fetchBufferReg);
append(key, value.fetchEmbedded);
if (value.resourcesNum > value.resources.size()) throw std::runtime_error("Shader cache: invalid vertex resource count");
for (std::uint32_t i = 0; i < value.resourcesNum; ++i) {
append(key, value.resources[i].fields[1] & 0xffff0000u);
append(key, value.resources[i].fields[3]);
append(key, value.resourcesDst[i]);
}
}
static void append(std::vector<std::uint64_t>& key, const MeshTargetLimits& value) {
append(key, value.maxWorkgroupSize);
append(key, value.maxWorkgroupInvocations);
append(key, value.maxSharedMemoryBytes);
append(key, value.maxOutputVertices);
append(key, value.maxOutputPrimitives);
append(key, value.maxOutputComponents);
append(key, value.maxOutputMemoryBytes);
append(key, value.outputPerVertexGranularity);
append(key, value.outputPerPrimitiveGranularity);
}
static void append(std::vector<std::uint64_t>& key, const TessellationTargetLimits& value) {
append(key, value.maxPatchSize);
append(key, value.maxControlPerVertexInputComponents);
append(key, value.maxControlPerVertexOutputComponents);
append(key, value.maxControlPerPatchOutputComponents);
append(key, value.maxControlTotalOutputComponents);
append(key, value.maxEvaluationInputComponents);
append(key, value.maxEvaluationOutputComponents);
}
static void append(std::vector<std::uint64_t>& key, const SpirvTarget& value) {
append(key, value.vulkanVersion);
append(key, value.spirvVersion);
append(key, value.subgroupSize);
append(key, value.bdaAbiVersion);
append(key, value.supportedCapabilities);
append(key, value.supportedExtensions);
append(key, value.fragmentShaderBarycentricEnabled);
append(key, value.maxWorkgroupSize);
append(key, value.maxWorkgroupInvocations);
append(key, value.maxWorkgroupSharedMemoryBytes);
append(key, value.mesh);
append(key, value.tessellation);
}
};
}
#endif

View File

@ -660,7 +660,7 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const
std::string buffer;
Writer writer(buffer);
writer.WriteU32(0x41505335u);
writer.WriteU32(1u);
writer.WriteU32(2u);
writeShaderBinary(writer, request.shader);
writeGuestContext(writer, request.context);
writeSpirvTarget(writer, request.target);
@ -669,13 +669,16 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const
if (request.graphics.has_value()) {
writeGraphicsCompileContext(writer, *request.graphics);
}
writer.WriteBool(request.useCache);
return base64Encode(buffer);
}
DeserializedRequest RequestSerializer::Deserialize(std::string_view text) const {
const std::string decoded = base64Decode(text);
Reader reader(decoded);
if (reader.ReadU32() != 0x41505335u || reader.ReadU32() != 1u) throw std::runtime_error("unsupported recompile request serialization version");
if (reader.ReadU32() != 0x41505335u) throw std::runtime_error("invalid recompile request signature");
const auto version = reader.ReadU32();
if (version != 1u && version != 2u) 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);
@ -685,6 +688,7 @@ DeserializedRequest RequestSerializer::Deserialize(std::string_view text) const
result.graphicsStorage = std::make_unique<DeserializedGraphicsCompileContext>();
result.request.graphics = readGraphicsCompileContext(reader, *result.graphicsStorage);
}
if (version == 2u) result.request.useCache = reader.ReadBool();
return result;
}

View File

@ -10,6 +10,7 @@ namespace ShaderRecompiler {
class DescriptorBindingBuilder {
public:
void Populate(BindingAllocationResult& allocation, const IrProgram& program, const ResourceSnapshot& snapshot) const;
void Populate(BindingAllocationResult& allocation, const ShaderInfo& info, IrShaderStage stage, std::uint32_t userDataBase, const ResourceSnapshot& snapshot) const;
};
}

View File

@ -7,6 +7,7 @@
namespace ShaderRecompiler {
[[nodiscard]] IrProgram PrepareResourceProgram(const RecompileRequest& request);
[[nodiscard]] std::shared_ptr<const IrResourcePlan> GetResourcePlan(const RecompileRequest& request);
}

View File

@ -163,14 +163,14 @@ std::vector<std::uint32_t> GuestSamplersDescriptor(const std::vector<std::uint32
return result;
}
std::vector<std::uint32_t> ShaderDataDwordsFor(const IrBindingLayout& layout, const IrResourcePlan& resources, const ResourceSnapshot& snapshot) {
std::vector<std::uint32_t> ShaderDataDwordsFor(const IrBindingLayout& layout, std::uint32_t userDataBase, const ResourceSnapshot& snapshot) {
std::vector<std::uint32_t> result(layout.ShaderDataDwords(), 0u);
for (std::size_t i = 0; i < layout.userDataRegisters.size(); i++) {
const std::uint32_t reg = layout.userDataRegisters[i];
if (reg < resources.userDataBase || reg - resources.userDataBase >= snapshot.userData.size()) {
if (reg < userDataBase || reg - userDataBase >= snapshot.userData.size()) {
fail("DescriptorBindingBuilder::Populate user-data register is out of range");
}
result[i] = snapshot.userData[reg - resources.userDataBase];
result[i] = snapshot.userData[reg - userDataBase];
}
return result;
}
@ -178,9 +178,12 @@ std::vector<std::uint32_t> ShaderDataDwordsFor(const IrBindingLayout& layout, co
}
void DescriptorBindingBuilder::Populate(BindingAllocationResult& allocation, const IrProgram& program, const ResourceSnapshot& snapshot) const {
Populate(allocation, program.Info(), program.Resources().stage, program.Resources().userDataBase, snapshot);
}
void DescriptorBindingBuilder::Populate(BindingAllocationResult& allocation, const ShaderInfo& info, IrShaderStage stage, std::uint32_t userDataBase, const ResourceSnapshot& snapshot) const {
const IrBindingLayout& layout = allocation.layout;
const IrShaderStage stage = program.Resources().stage;
const std::vector<std::uint32_t> shaderData = ShaderDataDwordsFor(layout, program.Resources(), snapshot);
const std::vector<std::uint32_t> shaderData = ShaderDataDwordsFor(layout, userDataBase, snapshot);
std::vector<DescriptorBinding> bindings;
bindings.reserve(layout.descriptors.size());
@ -199,12 +202,12 @@ void DescriptorBindingBuilder::Populate(BindingAllocationResult& allocation, con
break;
case DescriptorRole::GuestImages:
physical.guestDescriptor = GuestImagesDescriptor(logical.resources, snapshot);
physical.imageShape = ImageShapeFor(program.Info().images, logical.resources);
physical.imageShape = ImageShapeFor(info.images, logical.resources);
break;
case DescriptorRole::GuestSamplers:
physical.guestDescriptor = GuestSamplersDescriptor(logical.resources, snapshot);
for (std::size_t element = 0; element < logical.resources.size(); ++element) {
const auto& sampler = program.Info().samplers.at(logical.resources[element]);
const auto& sampler = info.samplers.at(logical.resources[element]);
physical.samplerDepthCompare.push_back(sampler.depthCompare);
if (sampler.forcePointFiltering) {
auto& filter = physical.guestDescriptor.at(element * 4u + 2u);

View File

@ -1,4 +1,8 @@
#include "Recompiler.hpp"
#include "CacheKey.hpp"
#include <mutex>
#include <shared_mutex>
#include <unordered_map>
#include "ControlFlow/include/ControlFlow/GraphBuilder.hpp"
#include "ControlFlow/include/ControlFlow/Structurizer.hpp"
#include "RdnaDecoder/include/RdnaDecoder/RdnaInstructionDecoder.hpp"
@ -121,21 +125,76 @@ IrProgram PrepareResourceProgram(const RecompileRequest& request) {
namespace {
RecompileResult RecompileImpl(const RecompileRequest& request) {
struct CompiledVariant {
ResourceSpecialization specialization;
BindingLayout layout;
CompiledShaderInfo info;
BindingAllocationResult bindings;
RecompileResult result;
};
auto program = PrepareResourceProgram(request);
struct ResourceProgram {
explicit ResourceProgram(const RecompileRequest& request) : program(PrepareResourceProgram(request)), plan(ResourceMaterializer{}.ExtractPlan(program)) {}
IrProgram program;
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);
}
struct SourceEntry {
std::mutex mutex;
std::shared_ptr<const IrResourcePlan> plan;
std::vector<std::shared_ptr<const CompiledVariant>> variants;
};
struct SourceKeyHash {
std::size_t operator()(const std::vector<std::uint64_t>& key) const {
std::size_t hash = 0;
for (const auto value : key) {
hash ^= static_cast<std::size_t>(value) + static_cast<std::size_t>(0x9e3779b97f4a7c15ull) + (hash << 6u) + (hash >> 2u);
if constexpr (sizeof(std::size_t) < sizeof(value)) hash ^= static_cast<std::size_t>(value >> 32u);
}
return hash;
}
};
std::shared_ptr<SourceEntry> getSource(const RecompileRequest& request) {
static std::shared_mutex mutex;
static std::unordered_map<std::vector<std::uint64_t>, std::shared_ptr<SourceEntry>, SourceKeyHash> sources;
thread_local std::vector<std::uint64_t> key;
RecompileCacheKey::Build(request, key);
std::shared_ptr<SourceEntry> source;
{
std::shared_lock lock(mutex);
const auto found = sources.find(key);
if (found != sources.end()) source = found->second;
}
if (source == nullptr) {
std::unique_lock lock(mutex);
const auto found = sources.find(key);
if (found != sources.end()) source = found->second;
else {
source = std::make_shared<SourceEntry>();
sources.emplace(key, source);
}
}
{
std::lock_guard lock(source->mutex);
if (source->plan == nullptr) {
source->plan = makeResourcePlan(request);
}
}
return source;
}
CompiledVariant compileVariant(const RecompileRequest& request, IrProgram program, const ResourceSnapshot& resourceSnapshot, const ResourceSpecialization& resourceSpecialization) {
const auto inputInfo = BuildShaderStageInputInfo(toShaderStageKind(request.shader.stage), request.context);
constexpr DeadCodeEliminator deadCodeEliminator;
constexpr ResourceMaterializer resourceMaterializer;
const auto resourcePlan = resourceMaterializer.ExtractPlan(program);
RequestMemoryView requestMemoryView(request.context.memory);
const auto srtRuntime = requestMemoryView.MakeRuntime(request.context.userData, request.shader.codeAddress);
ResourceSnapshot resourceSnapshot;
ResourceSpecialization resourceSpecialization;
resourceMaterializer.Materialize(resourcePlan, srtRuntime, resourceSnapshot, resourceSpecialization);
resourceMaterializer.Apply(program, resourceSpecialization);
deadCodeEliminator.RemoveIdentities(program);
@ -167,8 +226,6 @@ RecompileResult RecompileImpl(const RecompileRequest& request) {
#endif
result.bdaAbiVersion = program.Info().usesDma ? request.target.bdaAbiVersion : 0u;
result.bindings = bindings.bindings;
result.pushConstants = bindings.pushConstants;
result.vertexOffsetSgpr = program.Info().vertexOffsetSgpr;
result.instanceOffsetSgpr = program.Info().instanceOffsetSgpr;
for (const auto& output : program.Info().outputs) {
@ -184,9 +241,78 @@ RecompileResult RecompileImpl(const RecompileRequest& request) {
}
}
result.bindings.clear();
result.pushConstants.clear();
for (auto& attribute : result.vertexAttributes) attribute.resource = {};
bindings.bindings.clear();
bindings.pushConstants.clear();
return {resourceSpecialization, request.layout, std::move(program).TakeCompiledInfo(), std::move(bindings), std::move(result)};
}
RecompileResult materializeResult(const CompiledVariant& variant, const RecompileRequest& request, const ResourceSnapshot& snapshot) {
auto result = variant.result;
BindingAllocationResult bindings;
bindings.layout = variant.bindings.layout;
bindings.pushConstantOffsetBytes = variant.bindings.pushConstantOffsetBytes;
bindings.pushConstantSizeBytes = variant.bindings.pushConstantSizeBytes;
DescriptorBindingBuilder{}.Populate(bindings, variant.info.info, variant.info.stage, variant.info.userDataBase, snapshot);
result.bindings = std::move(bindings.bindings);
result.pushConstants = std::move(bindings.pushConstants);
for (auto& attribute : result.vertexAttributes) {
if (!request.context.vertex || attribute.location >= request.context.vertex->resourcesNum) throw std::runtime_error("Shader cache: invalid vertex attribute metadata");
attribute.resource = request.context.vertex->resources[attribute.location];
}
return result;
}
bool sameLayout(const BindingLayout& left, const BindingLayout& right) {
return left.descriptorSet == right.descriptorSet && left.firstBinding == right.firstBinding && left.pushConstantOffsetBytes == right.pushConstantOffsetBytes && left.pushConstantSizeBytes == right.pushConstantSizeBytes;
}
RecompileResult RecompileImpl(const RecompileRequest& request) {
static_cast<void>(BuildShaderStageInputInfo(toShaderStageKind(request.shader.stage), request.context));
RequestMemoryView memory(request.context.memory);
const auto runtime = memory.MakeRuntime(request.context.userData, request.shader.codeAddress);
ResourceSnapshot snapshot;
ResourceSpecialization specialization;
constexpr ResourceMaterializer materializer;
if (!request.useCache) {
auto program = PrepareResourceProgram(request);
const auto plan = materializer.ExtractPlan(program);
materializer.Materialize(plan, runtime, snapshot, specialization);
const auto variant = compileVariant(request, std::move(program), snapshot, specialization);
return materializeResult(variant, request, snapshot);
}
const auto source = getSource(request);
materializer.Materialize(*source->plan, runtime, snapshot, specialization);
std::shared_ptr<const CompiledVariant> variant;
bool cacheHit = false;
{
std::lock_guard lock(source->mutex);
for (const auto& candidate : source->variants) {
if (sameLayout(candidate->layout, request.layout) && candidate->specialization == specialization) {
variant = candidate;
cacheHit = true;
break;
}
}
if (variant == nullptr) {
auto program = PrepareResourceProgram(request);
variant = std::make_shared<CompiledVariant>(compileVariant(request, std::move(program), snapshot, specialization));
source->variants.push_back(variant);
}
}
auto result = materializeResult(*variant, request, snapshot);
result.cacheHit = cacheHit;
return result;
}
}
std::shared_ptr<const IrResourcePlan> GetResourcePlan(const RecompileRequest& request) {
static_cast<void>(BuildShaderStageInputInfo(toShaderStageKind(request.shader.stage), request.context));
if (request.useCache) return getSource(request)->plan;
return makeResourcePlan(request);
}
RecompileResult Recompile(const RecompileRequest& request) {

View File

@ -198,6 +198,7 @@ struct RecompileRequest {
SpirvTarget target;
BindingLayout layout;
std::optional<GraphicsCompileContext> graphics;
bool useCache = true;
};
enum class DescriptorKind {
@ -265,6 +266,7 @@ struct RecompileResult {
std::int32_t instanceOffsetSgpr = -1;
std::vector<std::uint32_t> parameterExports;
std::vector<FragmentParameter> fragmentParameters;
bool cacheHit = false;
};
[[nodiscard]] RecompileResult Recompile(const RecompileRequest& request);

View File

@ -132,6 +132,7 @@ ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const Gues
throw std::runtime_error("ShaderInputInfoBuilder: GuestContext.vertex is not set");
}
const auto& vertex = *context.vertex;
if (vertex.resourcesNum > vertex.resources.size()) throw std::runtime_error("ShaderInputInfoBuilder: invalid vertex resource count");
vertexStorage = ShaderVertexInputInfo{};
vertexStorage.logicalStage = _toIrShaderStage(stage);
vertexStorage.fetchEmbedded = vertex.fetchEmbedded;