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);