From 3cae2e05ae47943d60ab327f11b9752a3cc0398b Mon Sep 17 00:00:00 2001 From: lynxnb Date: Mon, 24 Apr 2023 17:45:49 +0200 Subject: [PATCH] Show pipelines loading screen while GPU is loading them --- app/src/main/cpp/skyline/gpu.cpp | 2 +- .../gpu/graphics_pipeline_assembler.cpp | 14 +++++ .../skyline/gpu/graphics_pipeline_assembler.h | 12 ++++ .../maxwell_3d/pipeline_manager.cpp | 16 +++++- .../maxwell_3d/pipeline_manager.h | 2 +- .../skyline/gpu/pipeline_cache_manager.cpp | 57 +++++++++++++++---- .../cpp/skyline/gpu/pipeline_cache_manager.h | 6 +- 7 files changed, 91 insertions(+), 18 deletions(-) diff --git a/app/src/main/cpp/skyline/gpu.cpp b/app/src/main/cpp/skyline/gpu.cpp index 84e0d765..4d55cc2d 100644 --- a/app/src/main/cpp/skyline/gpu.cpp +++ b/app/src/main/cpp/skyline/gpu.cpp @@ -414,6 +414,6 @@ namespace skyline::gpu { if (!*state.settings->disableShaderCache) graphicsPipelineCacheManager.emplace(state, state.os->publicAppFilesPath + "graphics_pipeline_cache/" + titleId); - graphicsPipelineManager.emplace(*this); + graphicsPipelineManager.emplace(*this, *state.jvm); } } diff --git a/app/src/main/cpp/skyline/gpu/graphics_pipeline_assembler.cpp b/app/src/main/cpp/skyline/gpu/graphics_pipeline_assembler.cpp index d8cb6222..7cf14e2c 100644 --- a/app/src/main/cpp/skyline/gpu/graphics_pipeline_assembler.cpp +++ b/app/src/main/cpp/skyline/gpu/graphics_pipeline_assembler.cpp @@ -214,6 +214,9 @@ namespace skyline::gpu { std::scoped_lock lock{mutex}; compilePendingDescs.erase(pipelineDescIt); + if (compilationCallback) + compilationCallback(); + return pipeline; } @@ -252,4 +255,15 @@ namespace skyline::gpu { SerialisePipelineCache(gpu, pipelineCacheDir, rawData); }); } + + void GraphicsPipelineAssembler::RegisterCompilationCallback(std::function callback) { + if (compilationCallback) + throw exception("A compilation callback is already registered"); + + compilationCallback = std::move(callback); + } + + void GraphicsPipelineAssembler::UnregisterCompilationCallback() { + compilationCallback = {}; + } } diff --git a/app/src/main/cpp/skyline/gpu/graphics_pipeline_assembler.h b/app/src/main/cpp/skyline/gpu/graphics_pipeline_assembler.h index 2d5c9a9e..4408ffa5 100644 --- a/app/src/main/cpp/skyline/gpu/graphics_pipeline_assembler.h +++ b/app/src/main/cpp/skyline/gpu/graphics_pipeline_assembler.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include #include @@ -59,6 +60,7 @@ namespace skyline::gpu { vk::raii::PipelineCache vkPipelineCache; //!< A Vulkan Pipeline Cache which stores all unique graphics pipelines BS::thread_pool pool; std::string pipelineCacheDir; + std::function compilationCallback; /** * @brief All unique metadata in a single attachment for a compatible render pass according to Render Pass Compatibility clause in the Vulkan specification @@ -158,5 +160,15 @@ namespace skyline::gpu { * @brief Saves the current Vulkan pipeline cache to the filesystem */ void SavePipelineCache(); + + /** + * @brief Registers a callback that is called whenever a pipeline is compiled + */ + void RegisterCompilationCallback(std::function callback); + + /** + * @brief Unregisters the compilation callback + */ + void UnregisterCompilationCallback(); }; } 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 3bb78d57..a8002ea4 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 @@ -9,6 +9,7 @@ #include #include #include +#include #include #include "graphics_pipeline_state_accessor.h" #include "pipeline_manager.h" @@ -942,12 +943,19 @@ namespace skyline::gpu::interconnect::maxwell3d { }); } - PipelineManager::PipelineManager(GPU &gpu) { + PipelineManager::PipelineManager(GPU &gpu, JvmManager &jvm) { if (!gpu.graphicsPipelineCacheManager) return; - std::ifstream stream{gpu.graphicsPipelineCacheManager->OpenReadStream()}; + std::atomic compiledCount{}; + auto [stream, totalPipelineCount]{gpu.graphicsPipelineCacheManager->OpenReadStream()}; i64 lastKnownGoodOffset{stream.tellg()}; + + jvm.ShowPipelineLoadingScreen(totalPipelineCount); + gpu.graphicsPipelineAssembler->RegisterCompilationCallback([&]() { + jvm.UpdatePipelineLoadingProgress(++compiledCount); + }); + try { auto startTime{util::GetTimeNs()}; PipelineStateBundle bundle; @@ -986,8 +994,10 @@ namespace skyline::gpu::interconnect::maxwell3d { } catch (const exception &e) { Logger::Warn("Pipeline cache corrupted at: 0x{:X}, error: {}", lastKnownGoodOffset, e.what()); gpu.graphicsPipelineCacheManager->InvalidateAllAfter(static_cast(lastKnownGoodOffset)); - return; } + + gpu.graphicsPipelineAssembler->UnregisterCompilationCallback(); + jvm.HidePipelineLoadingScreen(); } Pipeline *PipelineManager::FindOrCreate(InterconnectContext &ctx, Textures &textures, ConstantBufferSet &constantBuffers, const PackedPipelineState &packedState, const std::array &shaderBinaries) { diff --git a/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/pipeline_manager.h b/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/pipeline_manager.h index fdc95e04..ed612fe7 100644 --- a/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/pipeline_manager.h +++ b/app/src/main/cpp/skyline/gpu/interconnect/maxwell_3d/pipeline_manager.h @@ -272,7 +272,7 @@ namespace skyline::gpu::interconnect::maxwell3d { #endif public: - PipelineManager(GPU &gpu); + PipelineManager(GPU &gpu, JvmManager &jvm); Pipeline *FindOrCreate(InterconnectContext &ctx, Textures &textures, ConstantBufferSet &constantBuffers, const PackedPipelineState &packedState, const std::array &shaderBinaries); }; diff --git a/app/src/main/cpp/skyline/gpu/pipeline_cache_manager.cpp b/app/src/main/cpp/skyline/gpu/pipeline_cache_manager.cpp index 87b1f13b..41f27367 100644 --- a/app/src/main/cpp/skyline/gpu/pipeline_cache_manager.cpp +++ b/app/src/main/cpp/skyline/gpu/pipeline_cache_manager.cpp @@ -8,19 +8,26 @@ 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{2}; //!< The version of the pipeline cache file format, MUST be incremented for any format changes + static constexpr u32 Version{3}; //!< The version of the pipeline cache file format, MUST be incremented for any format changes u32 magic{Magic}; u32 version{Version}; + u32 count{0}; //!< The total number of pipeline cache bundles in the file auto operator<=>(const PipelineCacheFileHeader &) const = default; - }; - static constexpr PipelineCacheFileHeader ValidPipelineCacheFileHeader{}; + /** + * @brief Checks if the header is valid + */ + bool IsValid() { + return magic == Magic && version == Version; + } + }; void PipelineCacheManager::Run() { std::ofstream stream{stagingPath, std::ios::binary | std::ios::trunc}; - stream.write(reinterpret_cast(&ValidPipelineCacheFileHeader), sizeof(PipelineCacheFileHeader)); + PipelineCacheFileHeader header{}; + stream.write(reinterpret_cast(&header), sizeof(PipelineCacheFileHeader)); while (true) { std::unique_lock lock(writeMutex); @@ -31,7 +38,15 @@ namespace skyline::gpu { auto bundle{std::move(writeQueue.front())}; writeQueue.pop(); lock.unlock(); + bundle->Serialise(stream); + + header.count++; + // Rewrite the header with the updated count + auto savedPosition{stream.tellp()}; + stream.seekp(0, std::ios_base::beg); + stream.write(reinterpret_cast(&header), sizeof(PipelineCacheFileHeader)); + stream.seekp(savedPosition); } } @@ -40,8 +55,8 @@ namespace skyline::gpu { return false; PipelineCacheFileHeader header{}; - stream.read(reinterpret_cast(&header), sizeof(header)); - return header == ValidPipelineCacheFileHeader; + stream.read(reinterpret_cast(&header), sizeof(PipelineCacheFileHeader)); + return header.IsValid(); } void PipelineCacheManager::MergeStaging() { @@ -49,12 +64,24 @@ namespace skyline::gpu { if (stagingStream.fail()) return; // If the staging file doesn't exist then there's nothing to merge - if (!ValidateHeader(stagingStream)) { + PipelineCacheFileHeader stagingHeader{}; + stagingStream.read(reinterpret_cast(&stagingHeader), sizeof(PipelineCacheFileHeader)); + if (!stagingHeader.IsValid()) { Logger::Warn("Discarding invalid pipeline cache staging file"); return; } - std::ofstream mainStream{mainPath, std::ios::binary | std::ios::app}; + std::fstream mainStream{mainPath, std::ios::binary | std::ios::in | std::ios::out}; + PipelineCacheFileHeader mainHeader{}; + mainStream.seekg(0, std::ios_base::beg); + mainStream.read(reinterpret_cast(&mainHeader), sizeof(PipelineCacheFileHeader)); + + // Update the main header with the new count + mainHeader.count += stagingHeader.count; + mainStream.seekp(0, std::ios_base::beg); + mainStream.write(reinterpret_cast(&mainHeader), sizeof(PipelineCacheFileHeader)); + + mainStream.seekp(0, std::ios::end); mainStream << stagingStream.rdbuf(); } @@ -73,7 +100,8 @@ namespace skyline::gpu { 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)); + PipelineCacheFileHeader header{}; + mainStream.write(reinterpret_cast(&header), sizeof(PipelineCacheFileHeader)); } // Merge any staging changes into the main file before starting the writer thread @@ -87,12 +115,17 @@ namespace skyline::gpu { writeCondition.notify_one(); } - std::ifstream PipelineCacheManager::OpenReadStream() { + std::pair PipelineCacheManager::OpenReadStream() { auto mainStream{std::ifstream{mainPath, std::ios::binary}}; - if (!ValidateHeader(mainStream)) + if (mainStream.fail()) + throw exception("Pipeline cache main file missing at runtime!"); + + PipelineCacheFileHeader header{}; + mainStream.read(reinterpret_cast(&header), sizeof(PipelineCacheFileHeader)); + if (!header.IsValid()) throw exception("Pipeline cache main file corrupted at runtime!"); - return mainStream; + return {std::move(mainStream), header.count}; } void PipelineCacheManager::InvalidateAllAfter(u64 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 index 21b22713..62e97583 100644 --- a/app/src/main/cpp/skyline/gpu/pipeline_cache_manager.h +++ b/app/src/main/cpp/skyline/gpu/pipeline_cache_manager.h @@ -34,7 +34,11 @@ namespace skyline::gpu { */ void QueueWrite(std::unique_ptr bundle); - std::ifstream OpenReadStream(); + /** + * @brief Opens the main pipeline cache file for reading + * @return A pair containing the stream and the total pipeline count + */ + std::pair OpenReadStream(); /** * @brief Shrinks the pipeline cache file to `offset` bytes, removing any (potentially invalid) data after that point