From 4dcbf5c3a0dd8f83a3377918dc5bbaa6aa55d142 Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Sat, 10 Sep 2022 20:33:01 +0100 Subject: [PATCH] Drop the caching aspect of shader manager entirely Caching here was deemed unnecessary since it will be done implicitly by the pipeline cache and creates issues with the legacy attribute conversion pass. It now purely serves as a frontend for Hades. --- .../main/cpp/skyline/gpu/shader_manager.cpp | 209 ++---------------- app/src/main/cpp/skyline/gpu/shader_manager.h | 129 +---------- 2 files changed, 33 insertions(+), 305 deletions(-) diff --git a/app/src/main/cpp/skyline/gpu/shader_manager.cpp b/app/src/main/cpp/skyline/gpu/shader_manager.cpp index 0ab130d9..dae93fa7 100644 --- a/app/src/main/cpp/skyline/gpu/shader_manager.cpp +++ b/app/src/main/cpp/skyline/gpu/shader_manager.cpp @@ -96,7 +96,8 @@ namespace skyline::gpu { 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)} { + GraphicsEnvironment(const std::array &postVtgShaderAttributeSkipMask, 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)} { + gp_passthrough_mask = postVtgShaderAttributeSkipMask; stage = pStage; sph = *reinterpret_cast(binary.data()); start_address = baseOffset; @@ -185,204 +186,42 @@ namespace skyline::gpu { constexpr ShaderManager::CachedTextureType::CachedTextureType(u32 handle, Shader::TextureType type) : handle(handle), type(type) {} - ShaderManager::ShaderProgram::ShaderProgram(Shader::IR::Program &&program) : program{std::move(program)} {} + Shader::IR::Program ShaderManager::ParseGraphicsShader(const std::array &postVtgShaderAttributeSkipMask, Shader::Stage stage, span binary, u32 baseOffset, u32 textureConstantBufferIndex, const ConstantBufferRead &constantBufferRead, const GetTextureType &getTextureType) { + std::scoped_lock lock{poolMutex}; - 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; - }); + GraphicsEnvironment environment{postVtgShaderAttributeSkipMask, stage, binary, baseOffset, textureConstantBufferIndex, constantBufferRead, getTextureType}; + Shader::Maxwell::Flow::CFG cfg{environment, flowBlockPool, Shader::Maxwell::Location{static_cast(baseOffset + sizeof(Shader::ProgramHeader))}}; + return Shader::Maxwell::TranslateProgram(instructionPool, blockPool, environment, cfg, hostTranslateInfo); } - 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} {} + Shader::IR::Program ShaderManager::CombineVertexShaders(Shader::IR::Program &vertexA, Shader::IR::Program &vertexB, span vertexBBinary) { + std::scoped_lock lock{poolMutex}; - 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; + VertexBEnvironment env{vertexBBinary}; + return Shader::Maxwell::MergeDualVertexPrograms(vertexA, vertexB, env); } - size_t ShaderManager::ShaderProgramHash::operator()(const GuestShaderLookup &key) const noexcept { - size_t hash{}; + vk::ShaderModule ShaderManager::CompileShader(Shader::RuntimeInfo &runtimeInfo, Shader::IR::Program &program, Shader::Backend::Bindings &bindings) { + std::scoped_lock lock{poolMutex}; - HASH(stage); - hash = XXH64(key.bytecode.data(), key.bytecode.size(), hash); - HASH(textureConstantBufferIndex); + if (program.info.loads.Legacy() || program.info.stores.Legacy()) + Shader::Maxwell::ConvertLegacyToGeneric(program, runtimeInfo); - 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); - 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 { - size_t hash{}; - boost::hash_combine(hash, p.first); - boost::hash_combine(hash, p.second); - return hash; - } - - 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}; - 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 { - if (program != other.program) - return false; - - if (bindings.unified != other.bindings.unified || bindings.uniform_buffer != other.bindings.uniform_buffer || bindings.storage_buffer != other.bindings.storage_buffer || bindings.texture != other.bindings.texture || bindings.image != other.bindings.image || bindings.texture_scaling_index != other.bindings.texture_scaling_index || bindings.image_scaling_index != other.bindings.image_scaling_index) - return false; - - static_assert(sizeof(Shader::Backend::Bindings) == 0x1C); - - if (!std::equal(runtimeInfo.generic_input_types.begin(), runtimeInfo.generic_input_types.end(), other.runtimeInfo.generic_input_types.begin())) - return false; - - #define NEQ(member) runtimeInfo.member != other.runtimeInfo.member - - if (NEQ(previous_stage_stores.mask) || NEQ(convert_depth_mode) || NEQ(force_early_z) || NEQ(tess_primitive) || NEQ(tess_spacing) || NEQ(tess_clockwise) || NEQ(input_topology) || NEQ(fixed_state_point_size) || NEQ(alpha_test_func) || NEQ(alpha_test_reference) || NEQ(y_negate) || NEQ(glasm_use_storage_buffers)) - return false; - - #undef NEQ - - if (!std::equal(runtimeInfo.xfb_varyings.begin(), runtimeInfo.xfb_varyings.end(), other.runtimeInfo.xfb_varyings.begin(), [](const Shader::TransformFeedbackVarying &a, const Shader::TransformFeedbackVarying &b) { - return a.buffer == b.buffer && a.stride == b.stride && a.offset == b.offset && a.components == b.components; - })) - return false; - - static_assert(sizeof(Shader::RuntimeInfo) == 0x88); - - return true; - } - - constexpr size_t ShaderManager::ShaderModuleStateHash::operator()(const ShaderModuleState &state) const { - size_t hash{}; - - boost::hash_combine(hash, state.program); - - hash = XXH64(&state.bindings, sizeof(Shader::Backend::Bindings), hash); - - #define RIH(member) boost::hash_combine(hash, state.runtimeInfo.member) - - hash = XXH64(state.runtimeInfo.generic_input_types.data(), state.runtimeInfo.generic_input_types.size() * sizeof(Shader::AttributeType), hash); - hash = XXH64(&state.runtimeInfo.previous_stage_stores.mask, sizeof(state.runtimeInfo.previous_stage_stores.mask), hash); - RIH(convert_depth_mode); - RIH(force_early_z); - RIH(tess_primitive); - RIH(tess_spacing); - RIH(tess_clockwise); - RIH(input_topology); - RIH(fixed_state_point_size.value_or(NAN)); - RIH(alpha_test_func.value_or(Shader::CompareFunction::Never)); - RIH(alpha_test_reference); - RIH(glasm_use_storage_buffers); - hash = XXH64(state.runtimeInfo.xfb_varyings.data(), state.runtimeInfo.xfb_varyings.size() * sizeof(Shader::TransformFeedbackVarying), hash); - - static_assert(sizeof(Shader::RuntimeInfo) == 0x88); - #undef RIH - - return hash; - } - - 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()) { - const auto &entry{it->second}; - bindings = entry.bindings; - return *entry.vkModule; - } - - lock.unlock(); - - // Note: EmitSPIRV will change bindings so we explicitly have pre/post emit bindings - if (program->program.info.loads.Legacy() || program->program.info.stores.Legacy()) { - // The legacy conversion pass modifies the underlying program based on runtime state, so without making a copy of the program there may be issues if runtimeInfo changes - Logger::Warn("Shader uses legacy attributes, beware!"); - Shader::Maxwell::ConvertLegacyToGeneric(program->program, runtimeInfo); - } - auto spirv{Shader::Backend::SPIRV::EmitSPIRV(profile, runtimeInfo, program->program, bindings)}; + auto spirv{Shader::Backend::SPIRV::EmitSPIRV(profile, runtimeInfo, program, bindings)}; vk::ShaderModuleCreateInfo createInfo{ .pCode = spirv.data(), .codeSize = spirv.size() * sizeof(u32), }; - lock.lock(); + return (*gpu.vkDevice).createShaderModule(createInfo); + } - auto shaderModule{shaderModuleCache.try_emplace(shaderModuleState, gpu.vkDevice, createInfo, bindings)}; - return *(shaderModule.first->second.vkModule); + void ShaderManager::ResetPools() { + std::scoped_lock lock{poolMutex}; + + instructionPool.ReleaseContents(); + blockPool.ReleaseContents(); + flowBlockPool.ReleaseContents(); } } diff --git a/app/src/main/cpp/skyline/gpu/shader_manager.h b/app/src/main/cpp/skyline/gpu/shader_manager.h index 0017edc2..e7937e4d 100644 --- a/app/src/main/cpp/skyline/gpu/shader_manager.h +++ b/app/src/main/cpp/skyline/gpu/shader_manager.h @@ -24,8 +24,10 @@ 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 + Shader::ObjectPool flowBlockPool; + Shader::ObjectPool instructionPool; + Shader::ObjectPool blockPool; + std::mutex poolMutex; public: using ConstantBufferRead = std::function; //!< A function which reads a constant buffer at the specified offset and returns the value @@ -54,134 +56,21 @@ namespace skyline::gpu { 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 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; - }; - - struct GuestShaderKey { - Shader::Stage stage; - std::vector bytecode; - u32 textureConstantBufferIndex; - span constantBufferWords; - span textureTypes; - - 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; - - DualVertexShaderProgram(Shader::IR::Program program, std::shared_ptr vertexA, std::shared_ptr vertexB); - - 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>; - - struct DualVertexProgramsHash { - constexpr size_t operator()(const std::pair, std::shared_ptr> &p) const; - }; - - std::unordered_map, DualVertexProgramsHash> dualProgramCache; //!< A map from Vertex A and Vertex B shader programs to the corresponding dual vertex shader program - - /** - * @brief All unique state that is required to compile a shader program, this is used as the key for the associative compiled shader program cache - */ - struct ShaderModuleState { - std::shared_ptr program; - Shader::Backend::Bindings bindings; //!< The bindings prior to the shader being compiled - Shader::RuntimeInfo runtimeInfo; - - bool operator==(const ShaderModuleState &) const; - }; - - struct ShaderModuleStateHash { - constexpr size_t operator()(const ShaderModuleState &state) const; - }; - - struct ShaderModule { - 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); - }; - - std::unordered_map shaderModuleCache; //!< A map from shader module state to the corresponding Vulkan shader module - - public: ShaderManager(const DeviceState &state, GPU &gpu); /** * @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); + Shader::IR::Program ParseGraphicsShader(const std::array &postVtgShaderAttributeSkipMask, 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 * @note VertexA/VertexB shader programs must be SingleShaderProgram and not DualVertexShaderProgram */ - std::shared_ptr CombineVertexShaders(const std::shared_ptr &vertexA, const std::shared_ptr &vertexB, span vertexBBinary); + Shader::IR::Program CombineVertexShaders(Shader::IR::Program &vertexA, Shader::IR::Program &vertexB, span vertexBBinary); - vk::ShaderModule CompileShader(Shader::RuntimeInfo &runtimeInfo, const std::shared_ptr &program, Shader::Backend::Bindings &bindings); + vk::ShaderModule CompileShader(Shader::RuntimeInfo &runtimeInfo, Shader::IR::Program &program, Shader::Backend::Bindings &bindings); + + void ResetPools(); }; }