From 23a091100d9cddc68a3c2160acff9ed8f4b951fd Mon Sep 17 00:00:00 2001 From: PixelyIon Date: Fri, 6 May 2022 14:39:36 +0530 Subject: [PATCH] Implement `ReadCbufValue` + `ReadTextureType` Implements `GraphicsEnvironment::ReadCbufValue` & `GraphicsEnvironment::ReadTextureType` with a framework of heterogeneous lookups for caching and callbacks for querying constant buffer or TIC values with validation checks for successive draws to ensure unique IR is generated. --- .../gpu/interconnect/graphics_context.h | 63 ++++++-- .../cpp/skyline/gpu/interconnect/types/tic.h | 6 +- .../main/cpp/skyline/gpu/shader_manager.cpp | 140 +++++++++++++++--- app/src/main/cpp/skyline/gpu/shader_manager.h | 93 +++++++++++- 4 files changed, 261 insertions(+), 41 deletions(-) diff --git a/app/src/main/cpp/skyline/gpu/interconnect/graphics_context.h b/app/src/main/cpp/skyline/gpu/interconnect/graphics_context.h index 66055b6e..d725b86e 100644 --- a/app/src/main/cpp/skyline/gpu/interconnect/graphics_context.h +++ b/app/src/main/cpp/skyline/gpu/interconnect/graphics_context.h @@ -872,6 +872,17 @@ namespace skyline::gpu::interconnect { DescriptorSetWrites descriptorSetWrites; //!< The writes to the descriptor set that need to be done prior to executing a pipeline }; + /** + * @brief A packed handle supplied by the guest containing the index into the TIC and TSC tables + */ + union BindlessTextureHandle { + u32 raw; + struct { + u32 textureIndex : 20; + u32 samplerIndex : 12; + }; + }; + /** * @brief Updates `runtimeInfo` while automatically triggering a recompilation for a stage if the value has been updated * @param member A member of `runtimeInfo` passed by reference which will be checked and set @@ -915,6 +926,30 @@ namespace skyline::gpu::interconnect { auto &pipelineStage{pipelineStages[shader.ToPipelineStage()]}; if (shader.enabled) { // We only want to include the shader if it is enabled on the guest + + auto readConstantBuffer{[&executor = executor, &constantBuffers = pipelineStage.constantBuffers](u32 index, u32 offset) { + return constantBuffers[index].Read(executor, offset); + }}; + auto getTextureType{[this](u32 handle) { + using TicType = TextureImageControl::TextureType; + using ShaderType = ShaderCompiler::TextureType; + switch (GetTextureImageControl(BindlessTextureHandle{handle}.textureIndex).textureType) { + case TicType::e1D: return ShaderType::Color1D; + case TicType::e1DArray: return ShaderType::ColorArray1D; + case TicType::e1DBuffer: return ShaderType::Buffer; + + case TicType::e2D: return ShaderType::Color2D; + case TicType::e2DArray: return ShaderType::ColorArray2D; + + case TicType::e3D: return ShaderType::Color3D; + + case TicType::eCube: return ShaderType::ColorCube; + case TicType::eCubeArray: return ShaderType::ColorArrayCube; + } + }}; + if (!shader.invalidated && shader.program) + shader.invalidated |= shader.program->VerifyState(readConstantBuffer, getTextureType); + if (shader.invalidated) { // If a shader is invalidated, we need to reparse the program (given that it has changed) @@ -956,7 +991,7 @@ namespace skyline::gpu::interconnect { return std::nullopt; }); - shader.program = gpu.shader.ParseGraphicsShader(shader.stage, shader.bytecode, shader.offset, bindlessTextureConstantBufferIndex); + shader.program = gpu.shader.ParseGraphicsShader(shader.stage, shader.bytecode, shader.offset, bindlessTextureConstantBufferIndex, readConstantBuffer, getTextureType); if (shader.stage != ShaderCompiler::Stage::VertexA && shader.stage != ShaderCompiler::Stage::VertexB) { pipelineStage.program = shader.program; @@ -1140,13 +1175,7 @@ namespace skyline::gpu::interconnect { }); auto &constantBuffer{pipelineStage.constantBuffers[texture.cbuf_index]}; - union TextureHandle { - u32 raw; - struct { - u32 textureIndex : 20; - u32 samplerIndex : 12; - }; - } handle{constantBuffer.Read(executor, texture.cbuf_offset)}; + BindlessTextureHandle handle{constantBuffer.Read(executor, texture.cbuf_offset)}; auto sampler{GetSampler(handle.samplerIndex)}; auto textureView{GetPoolTextureView(handle.textureIndex)}; @@ -1994,7 +2023,13 @@ namespace skyline::gpu::interconnect { public: void SetBindlessTextureConstantBufferIndex(u32 index) { - bindlessTextureConstantBufferIndex = index; + if (bindlessTextureConstantBufferIndex != index) { + bindlessTextureConstantBufferIndex = index; + for (auto &shader : shaders) { + shader.invalidated = true; + shader.shouldCheckSame = false; + } + } } void SetTexturePoolIovaHigh(u32 high) { @@ -2154,7 +2189,7 @@ namespace skyline::gpu::interconnect { }; } - std::shared_ptr GetPoolTextureView(u32 index) { + const TextureImageControl &GetTextureImageControl(u32 index) { if (!texturePool.imageControls.valid()) { auto mappings{channelCtx.asCtx->gmmu.TranslateRange(texturePool.iova, texturePool.maximumIndex * sizeof(TextureImageControl))}; if (mappings.size() != 1) @@ -2162,7 +2197,11 @@ namespace skyline::gpu::interconnect { texturePool.imageControls = mappings.front().cast(); } - TextureImageControl &textureControl{texturePool.imageControls[index]}; + return texturePool.imageControls[index]; + } + + std::shared_ptr GetPoolTextureView(u32 index) { + auto &textureControl{GetTextureImageControl(index)}; auto textureIt{texturePool.textures.insert({textureControl, {}})}; auto &poolTexture{textureIt.first->second}; if (textureIt.second) { @@ -2213,7 +2252,7 @@ namespace skyline::gpu::interconnect { guest.dimensions.depth = depth; break; - case TicType::eCubemap: + case TicType::eCube: guest.type = TexType::eCube; guest.layerCount = CubeFaceCount; break; diff --git a/app/src/main/cpp/skyline/gpu/interconnect/types/tic.h b/app/src/main/cpp/skyline/gpu/interconnect/types/tic.h index 1c7c21bc..d158ff5d 100644 --- a/app/src/main/cpp/skyline/gpu/interconnect/types/tic.h +++ b/app/src/main/cpp/skyline/gpu/interconnect/types/tic.h @@ -133,7 +133,7 @@ namespace skyline::gpu::interconnect { e1D = 0, e2D = 1, e3D = 2, - eCubemap = 3, + eCube = 3, e1DArray = 4, e2DArray = 5, e1DBuffer = 6, @@ -331,11 +331,11 @@ namespace skyline::gpu::interconnect { bool operator==(const TextureImageControl &) const = default; - u64 Iova() { + u64 Iova() const { return (static_cast(addressHigh) << 32) | addressLow; } - u32 BaseLayer() { + u32 BaseLayer() const { return static_cast(viewLayerBase_0_2 | (viewLayerBase_3_7 << 3) | (viewLayerBase_8_10 << 8)); } }; diff --git a/app/src/main/cpp/skyline/gpu/shader_manager.cpp b/app/src/main/cpp/skyline/gpu/shader_manager.cpp index d8c59132..1c9943c7 100644 --- a/app/src/main/cpp/skyline/gpu/shader_manager.cpp +++ b/app/src/main/cpp/skyline/gpu/shader_manager.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/) +#include #include #include #include @@ -24,7 +25,7 @@ namespace Shader::Log { } namespace skyline::gpu { - ShaderManager::ShaderManager(const DeviceState &state, GPU &gpu) : gpu(gpu) { + ShaderManager::ShaderManager(const DeviceState &state, GPU &gpu) : gpu{gpu} { auto &traits{gpu.traits}; hostTranslateInfo = Shader::HostTranslateInfo{ .support_float16 = traits.supportsFloat16, @@ -87,9 +88,14 @@ namespace skyline::gpu { span binary; u32 baseOffset; u32 textureBufferIndex; + ShaderManager::ConstantBufferRead constantBufferRead; + ShaderManager::GetTextureType getTextureType; public: - GraphicsEnvironment(Shader::Stage pStage, span pBinary, u32 baseOffset, u32 textureBufferIndex) : binary(pBinary), baseOffset(baseOffset), textureBufferIndex(textureBufferIndex) { + std::vector constantBufferWords; + std::vector textureTypes; + + GraphicsEnvironment(Shader::Stage pStage, span pBinary, u32 baseOffset, u32 textureBufferIndex, ShaderManager::ConstantBufferRead constantBufferRead, ShaderManager::GetTextureType getTextureType) : binary{pBinary}, baseOffset{baseOffset}, textureBufferIndex{textureBufferIndex}, constantBufferRead{std::move(constantBufferRead)}, getTextureType{std::move(getTextureType)} { stage = pStage; sph = *reinterpret_cast(binary.data()); start_address = baseOffset; @@ -102,12 +108,16 @@ namespace skyline::gpu { return *reinterpret_cast(binary.data() + address); } - [[nodiscard]] u32 ReadCbufValue(u32 cbuf_index, u32 cbuf_offset) final { - throw exception("Not implemented"); + [[nodiscard]] u32 ReadCbufValue(u32 index, u32 offset) final { + auto value{constantBufferRead(index, offset)}; + constantBufferWords.emplace_back(index, offset, value); + return value; } - [[nodiscard]] Shader::TextureType ReadTextureType(u32 raw_handle) final { - throw exception("Not implemented"); + [[nodiscard]] Shader::TextureType ReadTextureType(u32 handle) final { + auto type{getTextureType(handle)}; + textureTypes.emplace_back(handle, type); + return type; } [[nodiscard]] u32 TextureBoundBuffer() const final { @@ -141,11 +151,11 @@ namespace skyline::gpu { throw exception("Not implemented"); } - [[nodiscard]] u32 ReadCbufValue(u32 cbuf_index, u32 cbuf_offset) final { + [[nodiscard]] u32 ReadCbufValue(u32 index, u32 offset) final { throw exception("Not implemented"); } - [[nodiscard]] Shader::TextureType ReadTextureType(u32 raw_handle) final { + [[nodiscard]] Shader::TextureType ReadTextureType(u32 handle) final { throw exception("Not implemented"); } @@ -166,18 +176,95 @@ namespace skyline::gpu { } }; - ShaderManager::DualVertexShaderProgram::DualVertexShaderProgram(Shader::IR::Program ir, std::shared_ptr vertexA, std::shared_ptr vertexB) : ShaderProgram{std::move(ir)}, vertexA(std::move(vertexA)), vertexB(std::move(vertexB)) {} + constexpr ShaderManager::ConstantBufferWord::ConstantBufferWord(u32 index, u32 offset, u32 value) : index(index), offset(offset), value(value) {} - std::shared_ptr ShaderManager::ParseGraphicsShader(Shader::Stage stage, span binary, u32 baseOffset, u32 bindlessTextureConstantBufferIndex) { - auto &program{programCache[binary]}; - if (program) - return program; + constexpr ShaderManager::CachedTextureType::CachedTextureType(u32 handle, Shader::TextureType type) : handle(handle), type(type) {} - program = std::make_shared(); - GraphicsEnvironment environment{stage, binary, baseOffset, bindlessTextureConstantBufferIndex}; + ShaderManager::ShaderProgram::ShaderProgram(Shader::IR::Program &&program) : program{std::move(program)} {} + + bool ShaderManager::SingleShaderProgram::VerifyState(const ConstantBufferRead &constantBufferRead, const GetTextureType &getTextureType) const { + return ranges::all_of(constantBufferWords, [&](const ConstantBufferWord &word) { + return constantBufferRead(word.index, word.offset) == word.value; + }) && + ranges::all_of(textureTypes, [&](const CachedTextureType &type) { + return getTextureType(type.handle) == type.type; + }); + } + + ShaderManager::GuestShaderKey::GuestShaderKey(Shader::Stage stage, span bytecode, u32 textureConstantBufferIndex, span constantBufferWords, span textureTypes) : stage{stage}, bytecode{bytecode.begin(), bytecode.end()}, textureConstantBufferIndex{textureConstantBufferIndex}, constantBufferWords{constantBufferWords}, textureTypes{textureTypes} {} + + ShaderManager::GuestShaderLookup::GuestShaderLookup(Shader::Stage stage, span bytecode, u32 textureConstantBufferIndex, ConstantBufferRead constantBufferRead, GetTextureType getTextureType) : stage(stage), textureConstantBufferIndex(textureConstantBufferIndex), bytecode(bytecode), constantBufferRead(std::move(constantBufferRead)), getTextureType(std::move(getTextureType)) {} + + #define HASH(member) boost::hash_combine(hash, key.member) + + size_t ShaderManager::ShaderProgramHash::operator()(const GuestShaderKey &key) const noexcept { + size_t hash{}; + + HASH(stage); + hash = XXH64(key.bytecode.data(), key.bytecode.size(), hash); + HASH(textureConstantBufferIndex); + + return hash; + } + + size_t ShaderManager::ShaderProgramHash::operator()(const GuestShaderLookup &key) const noexcept { + size_t hash{}; + + HASH(stage); + hash = XXH64(key.bytecode.data(), key.bytecode.size(), hash); + HASH(textureConstantBufferIndex); + + return hash; + } + + #undef HASH + + bool ShaderManager::ShaderProgramEqual::operator()(const GuestShaderKey &lhs, const GuestShaderLookup &rhs) const noexcept { + return lhs.stage == rhs.stage && + ranges::equal(lhs.bytecode, rhs.bytecode) && + lhs.textureConstantBufferIndex == rhs.textureConstantBufferIndex && + ranges::all_of(lhs.constantBufferWords, [&constantBufferRead = rhs.constantBufferRead](const ConstantBufferWord &word) { + return constantBufferRead(word.index, word.offset) == word.value; + }) && + ranges::all_of(lhs.textureTypes, [&getTextureType = rhs.getTextureType](const CachedTextureType &type) { + return getTextureType(type.handle) == type.type; + }); + } + + bool ShaderManager::ShaderProgramEqual::operator()(const GuestShaderKey &lhs, const GuestShaderKey &rhs) const noexcept { + return lhs.stage == rhs.stage && + ranges::equal(lhs.bytecode, rhs.bytecode) && + lhs.textureConstantBufferIndex == rhs.textureConstantBufferIndex && + ranges::equal(lhs.constantBufferWords, rhs.constantBufferWords) && + ranges::equal(lhs.textureTypes, rhs.textureTypes); + } + + ShaderManager::DualVertexShaderProgram::DualVertexShaderProgram(Shader::IR::Program ir, std::shared_ptr vertexA, std::shared_ptr vertexB) : ShaderProgram{std::move(ir)}, vertexA{std::move(vertexA)}, vertexB{std::move(vertexB)} {} + + bool ShaderManager::DualVertexShaderProgram::VerifyState(const ConstantBufferRead &constantBufferRead, const GetTextureType& getTextureType) const { + return vertexA->VerifyState(constantBufferRead, getTextureType) && vertexB->VerifyState(constantBufferRead, getTextureType); + } + + std::shared_ptr ShaderManager::ParseGraphicsShader(Shader::Stage stage, span binary, u32 baseOffset, u32 textureConstantBufferIndex, const ConstantBufferRead &constantBufferRead, const GetTextureType &getTextureType) { + std::unique_lock lock{programMutex}; + + auto it{programCache.find(GuestShaderLookup{stage, binary, textureConstantBufferIndex, constantBufferRead, getTextureType})}; + if (it != programCache.end()) + return it->second; + + lock.unlock(); + + auto program{std::make_shared()}; + GraphicsEnvironment environment{stage, binary, baseOffset, textureConstantBufferIndex, constantBufferRead, getTextureType}; Shader::Maxwell::Flow::CFG cfg(environment, program->flowBlockPool, Shader::Maxwell::Location{static_cast(baseOffset + sizeof(Shader::ProgramHeader))}); program->program = Shader::Maxwell::TranslateProgram(program->instructionPool, program->blockPool, environment, cfg, hostTranslateInfo); - return program; + program->constantBufferWords = std::move(environment.constantBufferWords); + program->textureTypes = std::move(environment.textureTypes); + + lock.lock(); + + auto programIt{programCache.try_emplace(GuestShaderKey{stage, binary, textureConstantBufferIndex, program->constantBufferWords, program->textureTypes}, std::move(program))}; + return programIt.first->second; } constexpr size_t ShaderManager::DualVertexProgramsHash::operator()(const std::pair, std::shared_ptr> &p) const { @@ -187,14 +274,20 @@ namespace skyline::gpu { return hash; } - std::shared_ptr ShaderManager::CombineVertexShaders(const std::shared_ptr &vertexA, const std::shared_ptr &vertexB, span vertexBBinary) { + std::shared_ptr ShaderManager::CombineVertexShaders(const std::shared_ptr &vertexA, const std::shared_ptr &vertexB, span vertexBBinary) { + std::unique_lock lock{programMutex}; auto &program{dualProgramCache[DualVertexPrograms{vertexA, vertexB}]}; if (program) return program; + lock.unlock(); + VertexBEnvironment vertexBEnvironment{vertexBBinary}; - program = std::make_shared(Shader::Maxwell::MergeDualVertexPrograms(vertexA->program, vertexB->program, vertexBEnvironment), vertexA, vertexB); - return program; + auto mergedProgram{std::make_shared(Shader::Maxwell::MergeDualVertexPrograms(vertexA->program, vertexB->program, vertexBEnvironment), vertexA, vertexB)}; + + lock.lock(); + + return program = std::move(mergedProgram); } bool ShaderManager::ShaderModuleState::operator==(const ShaderModuleState &other) const { @@ -226,7 +319,7 @@ namespace skyline::gpu { return true; } - constexpr size_t ShaderManager::ShaderModuleStateHash::operator()(const ShaderManager::ShaderModuleState &state) const { + constexpr size_t ShaderManager::ShaderModuleStateHash::operator()(const ShaderModuleState &state) const { size_t hash{}; boost::hash_combine(hash, state.program); @@ -255,9 +348,10 @@ namespace skyline::gpu { return hash; } - ShaderManager::ShaderModule::ShaderModule(const vk::raii::Device &device, const vk::ShaderModuleCreateInfo &createInfo, Shader::Backend::Bindings bindings) : vkModule(device, createInfo), bindings(bindings) {} + ShaderManager::ShaderModule::ShaderModule(const vk::raii::Device &device, const vk::ShaderModuleCreateInfo &createInfo, Shader::Backend::Bindings bindings) : vkModule{device, createInfo}, bindings{bindings} {} vk::ShaderModule ShaderManager::CompileShader(Shader::RuntimeInfo &runtimeInfo, const std::shared_ptr &program, Shader::Backend::Bindings &bindings) { + std::unique_lock lock{moduleMutex}; ShaderModuleState shaderModuleState{program, bindings, runtimeInfo}; auto it{shaderModuleCache.find(shaderModuleState)}; if (it != shaderModuleCache.end()) { @@ -266,6 +360,8 @@ namespace skyline::gpu { return *entry.vkModule; } + lock.unlock(); + // Note: EmitSPIRV will change bindings so we explicitly have pre/post emit bindings auto spirv{Shader::Backend::SPIRV::EmitSPIRV(profile, runtimeInfo, program->program, bindings)}; @@ -274,6 +370,8 @@ namespace skyline::gpu { .codeSize = spirv.size() * sizeof(u32), }; + lock.lock(); + auto shaderModule{shaderModuleCache.try_emplace(shaderModuleState, gpu.vkDevice, createInfo, bindings)}; return *(shaderModule.first->second.vkModule); } diff --git a/app/src/main/cpp/skyline/gpu/shader_manager.h b/app/src/main/cpp/skyline/gpu/shader_manager.h index 1da7e593..0017edc2 100644 --- a/app/src/main/cpp/skyline/gpu/shader_manager.h +++ b/app/src/main/cpp/skyline/gpu/shader_manager.h @@ -24,28 +24,106 @@ namespace skyline::gpu { GPU &gpu; Shader::HostTranslateInfo hostTranslateInfo; Shader::Profile profile; + std::mutex programMutex; //!< Synchronizes accesses to the program caches + std::mutex moduleMutex; //!< Synchronizes accesses to the module cache public: + using ConstantBufferRead = std::function; //!< A function which reads a constant buffer at the specified offset and returns the value + + /** + * @brief A single u32 word from a constant buffer with the offset it was read from, utilized to ensure constant buffer state is consistent + */ + struct ConstantBufferWord { + u32 index; //!< The index of the constant buffer + u32 offset; //!< The offset of the constant buffer word + u32 value; //!< The contents of the word + + constexpr ConstantBufferWord(u32 index, u32 offset, u32 value); + + constexpr bool operator==(const ConstantBufferWord &other) const = default; + }; + + using GetTextureType = std::function; //!< A function which determines the type of a texture from its handle by checking the corresponding TIC + + struct CachedTextureType { + u32 handle; + Shader::TextureType type; + + constexpr CachedTextureType(u32 handle, Shader::TextureType type); + + constexpr bool operator==(const CachedTextureType &other) const = default; + }; + struct ShaderProgram { Shader::IR::Program program; + + ShaderProgram() = default; + + ShaderProgram(Shader::IR::Program &&program); + + /** + * @return If the current state match the expected values, if they don't match then the shader program might be inaccurate for the current behavior + */ + virtual bool VerifyState(const ConstantBufferRead &constantBufferRead, const GetTextureType &getTextureType) const = 0; }; private: - struct SingleShaderProgram : ShaderProgram { + struct SingleShaderProgram final : ShaderProgram { Shader::ObjectPool flowBlockPool; Shader::ObjectPool instructionPool; Shader::ObjectPool blockPool; + std::vector constantBufferWords; + std::vector textureTypes; + SingleShaderProgram() = default; SingleShaderProgram(const SingleShaderProgram &) = delete; SingleShaderProgram &operator=(const SingleShaderProgram &) = delete; + + bool VerifyState(const ConstantBufferRead &constantBufferRead, const GetTextureType &getTextureType) const final; }; - std::unordered_map, std::shared_ptr, SpanHash, SpanEqual> programCache; //!< A map from Maxwell bytecode to the corresponding shader program + struct GuestShaderKey { + Shader::Stage stage; + std::vector bytecode; + u32 textureConstantBufferIndex; + span constantBufferWords; + span textureTypes; - struct DualVertexShaderProgram : ShaderProgram { + GuestShaderKey(Shader::Stage stage, span bytecode, u32 textureConstantBufferIndex, span constantBufferWords, span textureTypes); + }; + + struct GuestShaderLookup { + Shader::Stage stage; + span bytecode; + u32 textureConstantBufferIndex; + ConstantBufferRead constantBufferRead; + GetTextureType getTextureType; + + GuestShaderLookup(Shader::Stage stage, span bytecode, u32 textureConstantBufferIndex, ConstantBufferRead constantBufferRead, GetTextureType getTextureType); + }; + + struct ShaderProgramHash { + using is_transparent = std::true_type; + + size_t operator()(const GuestShaderKey &key) const noexcept; + + size_t operator()(const GuestShaderLookup &key) const noexcept; + }; + + struct ShaderProgramEqual { + using is_transparent = std::true_type; + + bool operator()(const GuestShaderKey &lhs, const GuestShaderLookup &rhs) const noexcept; + + bool operator()(const GuestShaderKey &lhs, const GuestShaderKey &rhs) const noexcept; + }; + + std::unordered_map, ShaderProgramHash, ShaderProgramEqual> programCache; //!< A map from Maxwell bytecode to the corresponding shader program + + struct DualVertexShaderProgram final : ShaderProgram { std::shared_ptr vertexA; std::shared_ptr vertexB; @@ -54,6 +132,8 @@ namespace skyline::gpu { DualVertexShaderProgram(const DualVertexShaderProgram &) = delete; DualVertexShaderProgram &operator=(const DualVertexShaderProgram &) = delete; + + bool VerifyState(const ConstantBufferRead &constantBufferRead, const GetTextureType &getTextureType) const final; }; using DualVertexPrograms = std::pair, std::shared_ptr>; @@ -83,7 +163,7 @@ namespace skyline::gpu { vk::raii::ShaderModule vkModule; Shader::Backend::Bindings bindings; //!< The bindings after the shader has been compiled - ShaderModule(const vk::raii::Device& device, const vk::ShaderModuleCreateInfo& createInfo, Shader::Backend::Bindings bindings); + ShaderModule(const vk::raii::Device &device, const vk::ShaderModuleCreateInfo &createInfo, Shader::Backend::Bindings bindings); }; std::unordered_map shaderModuleCache; //!< A map from shader module state to the corresponding Vulkan shader module @@ -91,7 +171,10 @@ namespace skyline::gpu { public: ShaderManager(const DeviceState &state, GPU &gpu); - std::shared_ptr ParseGraphicsShader(Shader::Stage stage, span binary, u32 baseOffset, u32 bindlessTextureConstantBufferIndex); + /** + * @return A shader program that corresponds to all the supplied state including the current state of the constant buffers + */ + std::shared_ptr ParseGraphicsShader(Shader::Stage stage, span binary, u32 baseOffset, u32 textureConstantBufferIndex, const ConstantBufferRead &constantBufferRead, const GetTextureType &getTextureType); /** * @brief Combines the VertexA and VertexB shader programs into a single program