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.
This commit is contained in:
PixelyIon 2022-05-06 14:39:36 +05:30
parent 765c3f4e1f
commit 23a091100d
4 changed files with 261 additions and 41 deletions

View File

@ -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<u32>(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<u32>(executor, texture.cbuf_offset)};
BindlessTextureHandle handle{constantBuffer.Read<u32>(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) {
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<TextureView> 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>();
}
TextureImageControl &textureControl{texturePool.imageControls[index]};
return texturePool.imageControls[index];
}
std::shared_ptr<TextureView> 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;

View File

@ -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<u64>(addressHigh) << 32) | addressLow;
}
u32 BaseLayer() {
u32 BaseLayer() const {
return static_cast<u32>(viewLayerBase_0_2 | (viewLayerBase_3_7 << 3) | (viewLayerBase_8_10 << 8));
}
};

View File

@ -1,6 +1,7 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <range/v3/algorithm.hpp>
#include <boost/functional/hash.hpp>
#include <gpu.h>
#include <shader_compiler/common/settings.h>
@ -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<u8> binary;
u32 baseOffset;
u32 textureBufferIndex;
ShaderManager::ConstantBufferRead constantBufferRead;
ShaderManager::GetTextureType getTextureType;
public:
GraphicsEnvironment(Shader::Stage pStage, span<u8> pBinary, u32 baseOffset, u32 textureBufferIndex) : binary(pBinary), baseOffset(baseOffset), textureBufferIndex(textureBufferIndex) {
std::vector<ShaderManager::ConstantBufferWord> constantBufferWords;
std::vector<ShaderManager::CachedTextureType> textureTypes;
GraphicsEnvironment(Shader::Stage pStage, span<u8> 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<Shader::ProgramHeader *>(binary.data());
start_address = baseOffset;
@ -102,12 +108,16 @@ namespace skyline::gpu {
return *reinterpret_cast<u64 *>(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<ShaderProgram> vertexA, std::shared_ptr<ShaderProgram> 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::ShaderProgram> ShaderManager::ParseGraphicsShader(Shader::Stage stage, span<u8> 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<SingleShaderProgram>();
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<u8> bytecode, u32 textureConstantBufferIndex, span<ConstantBufferWord> constantBufferWords, span<CachedTextureType> textureTypes) : stage{stage}, bytecode{bytecode.begin(), bytecode.end()}, textureConstantBufferIndex{textureConstantBufferIndex}, constantBufferWords{constantBufferWords}, textureTypes{textureTypes} {}
ShaderManager::GuestShaderLookup::GuestShaderLookup(Shader::Stage stage, span<u8> 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<ShaderProgram> vertexA, std::shared_ptr<ShaderProgram> 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::ShaderProgram> ShaderManager::ParseGraphicsShader(Shader::Stage stage, span<u8> 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<SingleShaderProgram>()};
GraphicsEnvironment environment{stage, binary, baseOffset, textureConstantBufferIndex, constantBufferRead, getTextureType};
Shader::Maxwell::Flow::CFG cfg(environment, program->flowBlockPool, Shader::Maxwell::Location{static_cast<u32>(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<ShaderProgram>, std::shared_ptr<ShaderProgram>> &p) const {
@ -187,14 +274,20 @@ namespace skyline::gpu {
return hash;
}
std::shared_ptr<ShaderManager::ShaderProgram> ShaderManager::CombineVertexShaders(const std::shared_ptr<ShaderManager::ShaderProgram> &vertexA, const std::shared_ptr<ShaderManager::ShaderProgram> &vertexB, span<u8> vertexBBinary) {
std::shared_ptr<ShaderManager::ShaderProgram> ShaderManager::CombineVertexShaders(const std::shared_ptr<ShaderProgram> &vertexA, const std::shared_ptr<ShaderProgram> &vertexB, span<u8> 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<DualVertexShaderProgram>(Shader::Maxwell::MergeDualVertexPrograms(vertexA->program, vertexB->program, vertexBEnvironment), vertexA, vertexB);
return program;
auto mergedProgram{std::make_shared<DualVertexShaderProgram>(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<ShaderProgram> &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);
}

View File

@ -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<u32(u32 index, u32 offset)>; //!< 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<Shader::TextureType(u32 handle)>; //!< 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<Shader::Maxwell::Flow::Block> flowBlockPool;
Shader::ObjectPool<Shader::IR::Inst> instructionPool;
Shader::ObjectPool<Shader::IR::Block> blockPool;
std::vector<ConstantBufferWord> constantBufferWords;
std::vector<CachedTextureType> 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<span<u8>, std::shared_ptr<SingleShaderProgram>, SpanHash<u8>, SpanEqual<u8>> programCache; //!< A map from Maxwell bytecode to the corresponding shader program
struct GuestShaderKey {
Shader::Stage stage;
std::vector<u8> bytecode;
u32 textureConstantBufferIndex;
span<ConstantBufferWord> constantBufferWords;
span<CachedTextureType> textureTypes;
struct DualVertexShaderProgram : ShaderProgram {
GuestShaderKey(Shader::Stage stage, span<u8> bytecode, u32 textureConstantBufferIndex, span<ConstantBufferWord> constantBufferWords, span<CachedTextureType> textureTypes);
};
struct GuestShaderLookup {
Shader::Stage stage;
span<u8> bytecode;
u32 textureConstantBufferIndex;
ConstantBufferRead constantBufferRead;
GetTextureType getTextureType;
GuestShaderLookup(Shader::Stage stage, span<u8> 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<GuestShaderKey, std::shared_ptr<SingleShaderProgram>, ShaderProgramHash, ShaderProgramEqual> programCache; //!< A map from Maxwell bytecode to the corresponding shader program
struct DualVertexShaderProgram final : ShaderProgram {
std::shared_ptr<ShaderProgram> vertexA;
std::shared_ptr<ShaderProgram> 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<ShaderProgram>, std::shared_ptr<ShaderProgram>>;
@ -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<ShaderModuleState, ShaderModule, ShaderModuleStateHash> 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<ShaderManager::ShaderProgram> ParseGraphicsShader(Shader::Stage stage, span<u8> 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<ShaderManager::ShaderProgram> ParseGraphicsShader(Shader::Stage stage, span<u8> binary, u32 baseOffset, u32 textureConstantBufferIndex, const ConstantBufferRead &constantBufferRead, const GetTextureType &getTextureType);
/**
* @brief Combines the VertexA and VertexB shader programs into a single program