From e9bcdd06eb26aa2558b3fd11fb872e87b5882a37 Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Sat, 10 Dec 2022 15:43:06 +0000 Subject: [PATCH] Introduce a pipeline cache manager for simple read/write cache accesses All writes are done async into a staging file, which is then merged into the main pipeline cache file at the time of the next launch. Upon encountering file corruption the cache can be trimmed up to the last-known-good entry to avoid any excessive loss of data from just one error. --- app/CMakeLists.txt | 1 + app/src/main/cpp/skyline/gpu.cpp | 5 + app/src/main/cpp/skyline/gpu.h | 11 ++ .../common/pipeline_state_accessor.h | 2 +- .../kepler_compute/pipeline_manager.h | 1 - .../kepler_compute/pipeline_state.cpp | 2 +- .../kepler_compute/pipeline_state.h | 2 - .../graphics_pipeline_state_accessor.cpp | 16 +-- .../graphics_pipeline_state_accessor.h | 6 +- .../maxwell_3d/packed_pipeline_state.h | 1 + .../maxwell_3d/pipeline_manager.cpp | 2 + .../maxwell_3d/pipeline_state.cpp | 2 +- .../interconnect/maxwell_3d/pipeline_state.h | 2 - .../skyline/gpu/pipeline_cache_manager.cpp | 101 ++++++++++++++++++ .../cpp/skyline/gpu/pipeline_cache_manager.h | 44 ++++++++ app/src/main/cpp/skyline/os.cpp | 3 + 16 files changed, 184 insertions(+), 17 deletions(-) create mode 100644 app/src/main/cpp/skyline/gpu/pipeline_cache_manager.cpp create mode 100644 app/src/main/cpp/skyline/gpu/pipeline_cache_manager.h diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index dcba65b5..c345ad5f 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -185,6 +185,7 @@ add_library(skyline SHARED ${source_DIR}/skyline/gpu/megabuffer.cpp ${source_DIR}/skyline/gpu/presentation_engine.cpp ${source_DIR}/skyline/gpu/shader_manager.cpp + ${source_DIR}/skyline/gpu/pipeline_cache_manager.cpp ${source_DIR}/skyline/gpu/cache/graphics_pipeline_cache.cpp ${source_DIR}/skyline/gpu/cache/renderpass_cache.cpp ${source_DIR}/skyline/gpu/cache/framebuffer_cache.cpp diff --git a/app/src/main/cpp/skyline/gpu.cpp b/app/src/main/cpp/skyline/gpu.cpp index 10bb2bbc..95db3e23 100644 --- a/app/src/main/cpp/skyline/gpu.cpp +++ b/app/src/main/cpp/skyline/gpu.cpp @@ -382,4 +382,9 @@ namespace skyline::gpu { graphicsPipelineCache(*this), renderPassCache(*this), framebufferCache(*this) {} + + void GPU::Initialise() { + graphicsPipelineCacheManager.emplace(state, state.os->publicAppFilesPath + "graphics_pipeline_cache/" + state.loader->nacp->GetSaveDataOwnerId()); + graphicsPipelineManager.emplace(*this); + } } diff --git a/app/src/main/cpp/skyline/gpu.h b/app/src/main/cpp/skyline/gpu.h index 7e7b11bc..cf2a3c32 100644 --- a/app/src/main/cpp/skyline/gpu.h +++ b/app/src/main/cpp/skyline/gpu.h @@ -12,10 +12,13 @@ #include "gpu/megabuffer.h" #include "gpu/descriptor_allocator.h" #include "gpu/shader_manager.h" +#include "gpu/pipeline_cache_manager.h" #include "gpu/shaders/helper_shaders.h" #include "gpu/cache/graphics_pipeline_cache.h" #include "gpu/cache/renderpass_cache.h" #include "gpu/cache/framebuffer_cache.h" +#include "gpu/interconnect/maxwell_3d/pipeline_manager.h" +#include "gpu/interconnect/kepler_compute/pipeline_manager.h" namespace skyline::gpu { static constexpr u32 VkApiVersion{VK_API_VERSION_1_1}; //!< The version of core Vulkan that we require @@ -58,7 +61,15 @@ namespace skyline::gpu { cache::FramebufferCache framebufferCache; std::mutex channelLock; + std::optional graphicsPipelineCacheManager; + std::optional graphicsPipelineManager; + interconnect::kepler_compute::PipelineManager computePipelineManager; GPU(const DeviceState &state); + + /** + * @brief Should be called after loader population to initialize the per-title caches + */ + void Initialise(); }; } diff --git a/app/src/main/cpp/skyline/gpu/interconnect/common/pipeline_state_accessor.h b/app/src/main/cpp/skyline/gpu/interconnect/common/pipeline_state_accessor.h index 9ef3c2dc..e06e4d44 100644 --- a/app/src/main/cpp/skyline/gpu/interconnect/common/pipeline_state_accessor.h +++ b/app/src/main/cpp/skyline/gpu/interconnect/common/pipeline_state_accessor.h @@ -31,7 +31,7 @@ namespace skyline::gpu::interconnect { /** * @brief Marks that all Get* operations on the pipeline state has finished and the pipeline is build */ - virtual void MarkComplete() const = 0; + virtual void MarkComplete() = 0; virtual ~PipelineStateAccessor() = default; }; diff --git a/app/src/main/cpp/skyline/gpu/interconnect/kepler_compute/pipeline_manager.h b/app/src/main/cpp/skyline/gpu/interconnect/kepler_compute/pipeline_manager.h index 868dcf19..1ca4f03a 100644 --- a/app/src/main/cpp/skyline/gpu/interconnect/kepler_compute/pipeline_manager.h +++ b/app/src/main/cpp/skyline/gpu/interconnect/kepler_compute/pipeline_manager.h @@ -10,7 +10,6 @@ #include "packed_pipeline_state.h" #include "constant_buffers.h" - namespace skyline::gpu { class TextureView; } diff --git a/app/src/main/cpp/skyline/gpu/interconnect/kepler_compute/pipeline_state.cpp b/app/src/main/cpp/skyline/gpu/interconnect/kepler_compute/pipeline_state.cpp index b85a2f6b..1b5a1b96 100644 --- a/app/src/main/cpp/skyline/gpu/interconnect/kepler_compute/pipeline_state.cpp +++ b/app/src/main/cpp/skyline/gpu/interconnect/kepler_compute/pipeline_state.cpp @@ -38,7 +38,7 @@ namespace skyline::gpu::interconnect::kepler_compute { packedState.sharedMemorySize = qmd.sharedMemorySize; packedState.bindlessTextureConstantBufferSlotSelect = bindlessTexture.constantBufferSlotSelect; - return pipelineManager.FindOrCreate(ctx, textures, constantBuffers, packedState, stage.binary); + return ctx.gpu.computePipelineManager.FindOrCreate(ctx, textures, constantBuffers, packedState, stage.binary); } void PipelineState::PurgeCaches() { diff --git a/app/src/main/cpp/skyline/gpu/interconnect/kepler_compute/pipeline_state.h b/app/src/main/cpp/skyline/gpu/interconnect/kepler_compute/pipeline_state.h index c3064318..7c9faa02 100644 --- a/app/src/main/cpp/skyline/gpu/interconnect/kepler_compute/pipeline_state.h +++ b/app/src/main/cpp/skyline/gpu/interconnect/kepler_compute/pipeline_state.h @@ -49,8 +49,6 @@ namespace skyline::gpu::interconnect::kepler_compute { const engine_common::BindlessTexture &bindlessTexture; PackedPipelineState packedState{}; - PipelineManager pipelineManager; - public: PipelineState(DirtyManager &manager, const EngineRegisters &engine); diff --git a/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/graphics_pipeline_state_accessor.cpp b/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/graphics_pipeline_state_accessor.cpp index e67adf3f..13075435 100644 --- a/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/graphics_pipeline_state_accessor.cpp +++ b/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/graphics_pipeline_state_accessor.cpp @@ -1,33 +1,37 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/) +#include +#include #include "graphics_pipeline_state_accessor.h" namespace skyline::gpu::interconnect::maxwell3d { - RuntimeGraphicsPipelineStateAccessor::RuntimeGraphicsPipelineStateAccessor(PipelineStateBundle &bundle, + RuntimeGraphicsPipelineStateAccessor::RuntimeGraphicsPipelineStateAccessor(std::unique_ptr bundle, InterconnectContext &ctx, Textures &textures, ConstantBufferSet &constantBuffers, const std::array &shaderBinaries) - : bundle{bundle}, ctx{ctx}, textures{textures}, constantBuffers{constantBuffers}, shaderBinaries{shaderBinaries} {} + : bundle{std::move(bundle)}, ctx{ctx}, textures{textures}, constantBuffers{constantBuffers}, shaderBinaries{shaderBinaries} {} Shader::TextureType RuntimeGraphicsPipelineStateAccessor::GetTextureType(u32 index) const { Shader::TextureType type{textures.GetTextureType(ctx, index)}; - bundle.AddTextureType(index, type); + bundle->AddTextureType(index, type); return type; } u32 RuntimeGraphicsPipelineStateAccessor::GetConstantBufferValue(u32 shaderStage, u32 index, u32 offset) const { u32 value{constantBuffers[shaderStage][index].Read(ctx.executor, offset)}; - bundle.AddConstantBufferValue(shaderStage, index, offset, value); + bundle->AddConstantBufferValue(shaderStage, index, offset, value); return value; } ShaderBinary RuntimeGraphicsPipelineStateAccessor::GetShaderBinary(u32 pipelineStage) const { ShaderBinary binary{shaderBinaries[pipelineStage]}; - bundle.SetShaderBinary(pipelineStage, binary); + bundle->SetShaderBinary(pipelineStage, binary); return binary; } - void RuntimeGraphicsPipelineStateAccessor::MarkComplete() const {} + void RuntimeGraphicsPipelineStateAccessor::MarkComplete() { + ctx.gpu.graphicsPipelineCacheManager->QueueWrite(std::move(bundle)); + } } diff --git a/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/graphics_pipeline_state_accessor.h b/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/graphics_pipeline_state_accessor.h index cc55e7d2..49259df8 100644 --- a/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/graphics_pipeline_state_accessor.h +++ b/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/graphics_pipeline_state_accessor.h @@ -15,14 +15,14 @@ namespace skyline::gpu::interconnect::maxwell3d { */ class RuntimeGraphicsPipelineStateAccessor : public PipelineStateAccessor { private: - PipelineStateBundle &bundle; + std::unique_ptr bundle; InterconnectContext &ctx; Textures &textures; ConstantBufferSet &constantBuffers; std::array shaderBinaries; public: - RuntimeGraphicsPipelineStateAccessor(PipelineStateBundle &bundle, + RuntimeGraphicsPipelineStateAccessor(std::unique_ptr bundle, InterconnectContext &ctx, Textures &textures, ConstantBufferSet &constantBuffers, const std::array &shaderBinaries); @@ -33,6 +33,6 @@ namespace skyline::gpu::interconnect::maxwell3d { ShaderBinary GetShaderBinary(u32 pipelineStage) const override; - void MarkComplete() const override; + void MarkComplete() override; }; } diff --git a/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/packed_pipeline_state.h b/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/packed_pipeline_state.h index 1e579a05..ea458448 100644 --- a/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/packed_pipeline_state.h +++ b/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/packed_pipeline_state.h @@ -15,6 +15,7 @@ namespace skyline::gpu::interconnect::maxwell3d { /** * @brief Packed struct of pipeline state suitable for use as a map key * @note This is heavily based around yuzu's pipeline key with some packing modifications + * @note Any modifications to this struct *MUST* be accompanied by a pipeline cache version bump * @url https://github.com/yuzu-emu/yuzu/blob/9c701774562ea490296b9cbea3dbd8c096bc4483/src/video_core/renderer_vulkan/fixed_pipeline_state.h#L20 */ struct PackedPipelineState { diff --git a/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/pipeline_manager.cpp b/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/pipeline_manager.cpp index e9cdf064..ff847313 100644 --- a/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/pipeline_manager.cpp +++ b/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/pipeline_manager.cpp @@ -4,9 +4,11 @@ #include #include #include +#include #include #include #include +#include "graphics_pipeline_state_accessor.h" #include "pipeline_manager.h" namespace skyline::gpu::interconnect::maxwell3d { diff --git a/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/pipeline_state.cpp b/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/pipeline_state.cpp index ff35d86d..e12c15d1 100644 --- a/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/pipeline_state.cpp +++ b/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/pipeline_state.cpp @@ -432,7 +432,7 @@ namespace skyline::gpu::interconnect::maxwell3d { } } - auto newPipeline{pipelineManager.FindOrCreate(ctx, textures, constantBuffers, packedState, shaderBinaries)}; + auto newPipeline{ctx.gpu.graphicsPipelineManager->FindOrCreate(ctx, textures, constantBuffers, packedState, shaderBinaries)}; if (pipeline) pipeline->AddTransition(newPipeline); pipeline = newPipeline; diff --git a/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/pipeline_state.h b/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/pipeline_state.h index 1683a90f..955b5a1a 100644 --- a/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/pipeline_state.h +++ b/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/pipeline_state.h @@ -299,8 +299,6 @@ namespace skyline::gpu::interconnect::maxwell3d { }; private: - PipelineManager pipelineManager{}; - PackedPipelineState packedState{}; dirty::BoundSubresource engine; diff --git a/app/src/main/cpp/skyline/gpu/pipeline_cache_manager.cpp b/app/src/main/cpp/skyline/gpu/pipeline_cache_manager.cpp new file mode 100644 index 00000000..11a93e29 --- /dev/null +++ b/app/src/main/cpp/skyline/gpu/pipeline_cache_manager.cpp @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include +#include +#include "pipeline_cache_manager.h" + +namespace skyline::gpu { + struct PipelineCacheFileHeader { + static constexpr u32 Magic{util::MakeMagic("PCHE")}; //!< The magic value used to identify a pipeline cache file + static constexpr u32 Version{1}; //!< The version of the pipeline cache file format, MUST be incremented for any format changes + + u32 magic{Magic}; + u32 version{Version}; + + auto operator<=>(const PipelineCacheFileHeader &) const = default; + }; + + static constexpr PipelineCacheFileHeader ValidPipelineCacheFileHeader{}; + + void PipelineCacheManager::Run() { + std::ofstream stream{stagingPath, std::ios::binary | std::ios::trunc}; + stream.write(reinterpret_cast(&ValidPipelineCacheFileHeader), sizeof(PipelineCacheFileHeader)); + + while (true) { + std::unique_lock lock(writeMutex); + if (writeQueue.empty()) + stream.flush(); + + writeCondition.wait(lock, [this] { return !writeQueue.empty(); }); + auto bundle{std::move(writeQueue.front())}; + writeQueue.pop(); + lock.unlock(); + bundle->Serialise(stream); + } + } + + bool PipelineCacheManager::ValidateHeader(std::ifstream &stream) { + if (stream.fail()) + return false; + + PipelineCacheFileHeader header{}; + stream.read(reinterpret_cast(&header), sizeof(header)); + return header == ValidPipelineCacheFileHeader; + } + + void PipelineCacheManager::MergeStaging() { + std::ifstream stagingStream{stagingPath, std::ios::binary}; + if (stagingStream.fail()) + return; // If the staging file doesn't exist then there's nothing to merge + + if (!ValidateHeader(stagingStream)) { + Logger::Warn("Discarding invalid pipeline cache staging file"); + return; + } + + std::ofstream mainStream{mainPath, std::ios::binary | std::ios::app}; + mainStream << stagingStream.rdbuf(); + } + + PipelineCacheManager::PipelineCacheManager(const DeviceState &state, const std::string &path) + : stagingPath{path + ".staging"}, mainPath{path} { + bool didExist{std::filesystem::exists(mainPath)}; + if (didExist) { // If the main file exists then we need to validate it + std::ifstream mainStream{mainPath, std::ios::binary}; + if (!ValidateHeader(mainStream)) { // Force a recreation of the file if it's invalid + Logger::Warn("Discarding invalid pipeline cache main file"); + std::filesystem::remove(mainPath); + didExist = false; + } + } + + if (!didExist) { // If the main file didn't exist we need to write the header + std::filesystem::create_directories(std::filesystem::path{mainPath}.parent_path()); + std::ofstream mainStream{mainPath, std::ios::binary | std::ios::app}; + mainStream.write(reinterpret_cast(&ValidPipelineCacheFileHeader), sizeof(PipelineCacheFileHeader)); + } + + // Merge any staging changes into the main file before starting the writer thread + MergeStaging(); + writerThread = std::thread(&PipelineCacheManager::Run, this); + } + + void PipelineCacheManager::QueueWrite(std::unique_ptr bundle) { + std::scoped_lock lock{writeMutex}; + writeQueue.emplace(std::move(bundle)); + writeCondition.notify_one(); + } + + std::ifstream PipelineCacheManager::OpenReadStream() { + auto mainStream{std::ifstream{mainPath, std::ios::binary}}; + if (!ValidateHeader(mainStream)) + throw exception("Pipeline cache main file corrupted at runtime!"); + + return mainStream; + } + + void PipelineCacheManager::InvalidateAllAfter(u64 offset) { + std::filesystem::resize_file(mainPath, offset); + } +} diff --git a/app/src/main/cpp/skyline/gpu/pipeline_cache_manager.h b/app/src/main/cpp/skyline/gpu/pipeline_cache_manager.h new file mode 100644 index 00000000..21b22713 --- /dev/null +++ b/app/src/main/cpp/skyline/gpu/pipeline_cache_manager.h @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include +#include +#include "interconnect/common/pipeline_state_bundle.h" + +namespace skyline::gpu { + /** + * @brief Manages access and validation of the underlying pipeline cache files + */ + class PipelineCacheManager { + private: + std::thread writerThread; + std::queue> writeQueue; //!< The queue of pipeline state bundles to be written to the cache + std::mutex writeMutex; //!< Protects access to the write queue + std::condition_variable writeCondition; //!< Notifies the writer thread when the write queue is not empty + std::string stagingPath; //!< The path to the staging pipeline cache file, which will be actively written to at runtime + std::string mainPath; //!< The path to the main pipeline cache file + + void Run(); + + bool ValidateHeader(std::ifstream &stream); + + void MergeStaging(); + + public: + PipelineCacheManager(const DeviceState &state, const std::string &path); + + /** + * @brief Queues a pipeline state bundle to be written to the cache + */ + void QueueWrite(std::unique_ptr bundle); + + std::ifstream OpenReadStream(); + + /** + * @brief Shrinks the pipeline cache file to `offset` bytes, removing any (potentially invalid) data after that point + */ + void InvalidateAllAfter(u64 offset); + }; +} diff --git a/app/src/main/cpp/skyline/os.cpp b/app/src/main/cpp/skyline/os.cpp index 7ecde478..a7bc6887 100644 --- a/app/src/main/cpp/skyline/os.cpp +++ b/app/src/main/cpp/skyline/os.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) +#include "gpu.h" #include "nce.h" #include "nce/guest.h" #include "kernel/types/KProcess.h" @@ -50,6 +51,8 @@ namespace skyline::kernel { } }(); + state.gpu->Initialise(); + auto &process{state.process}; process = std::make_shared(state);