Show pipelines loading screen while GPU is loading them

This commit is contained in:
lynxnb 2023-04-24 17:45:49 +02:00
parent 3bb24a52ab
commit 3cae2e05ae
7 changed files with 91 additions and 18 deletions

View File

@ -414,6 +414,6 @@ namespace skyline::gpu {
if (!*state.settings->disableShaderCache) if (!*state.settings->disableShaderCache)
graphicsPipelineCacheManager.emplace(state, graphicsPipelineCacheManager.emplace(state,
state.os->publicAppFilesPath + "graphics_pipeline_cache/" + titleId); state.os->publicAppFilesPath + "graphics_pipeline_cache/" + titleId);
graphicsPipelineManager.emplace(*this); graphicsPipelineManager.emplace(*this, *state.jvm);
} }
} }

View File

@ -214,6 +214,9 @@ namespace skyline::gpu {
std::scoped_lock lock{mutex}; std::scoped_lock lock{mutex};
compilePendingDescs.erase(pipelineDescIt); compilePendingDescs.erase(pipelineDescIt);
if (compilationCallback)
compilationCallback();
return pipeline; return pipeline;
} }
@ -252,4 +255,15 @@ namespace skyline::gpu {
SerialisePipelineCache(gpu, pipelineCacheDir, rawData); SerialisePipelineCache(gpu, pipelineCacheDir, rawData);
}); });
} }
void GraphicsPipelineAssembler::RegisterCompilationCallback(std::function<void()> callback) {
if (compilationCallback)
throw exception("A compilation callback is already registered");
compilationCallback = std::move(callback);
}
void GraphicsPipelineAssembler::UnregisterCompilationCallback() {
compilationCallback = {};
}
} }

View File

@ -3,6 +3,7 @@
#pragma once #pragma once
#include <functional>
#include <future> #include <future>
#include <BS_thread_pool.hpp> #include <BS_thread_pool.hpp>
#include <vulkan/vulkan_raii.hpp> #include <vulkan/vulkan_raii.hpp>
@ -59,6 +60,7 @@ namespace skyline::gpu {
vk::raii::PipelineCache vkPipelineCache; //!< A Vulkan Pipeline Cache which stores all unique graphics pipelines vk::raii::PipelineCache vkPipelineCache; //!< A Vulkan Pipeline Cache which stores all unique graphics pipelines
BS::thread_pool pool; BS::thread_pool pool;
std::string pipelineCacheDir; std::string pipelineCacheDir;
std::function<void()> compilationCallback;
/** /**
* @brief All unique metadata in a single attachment for a compatible render pass according to Render Pass Compatibility clause in the Vulkan specification * @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 * @brief Saves the current Vulkan pipeline cache to the filesystem
*/ */
void SavePipelineCache(); void SavePipelineCache();
/**
* @brief Registers a callback that is called whenever a pipeline is compiled
*/
void RegisterCompilationCallback(std::function<void()> callback);
/**
* @brief Unregisters the compilation callback
*/
void UnregisterCompilationCallback();
}; };
} }

View File

@ -9,6 +9,7 @@
#include <gpu/graphics_pipeline_assembler.h> #include <gpu/graphics_pipeline_assembler.h>
#include <gpu/shader_manager.h> #include <gpu/shader_manager.h>
#include <gpu.h> #include <gpu.h>
#include <jvm.h>
#include <vulkan/vulkan_enums.hpp> #include <vulkan/vulkan_enums.hpp>
#include "graphics_pipeline_state_accessor.h" #include "graphics_pipeline_state_accessor.h"
#include "pipeline_manager.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) if (!gpu.graphicsPipelineCacheManager)
return; return;
std::ifstream stream{gpu.graphicsPipelineCacheManager->OpenReadStream()}; std::atomic<u32> compiledCount{};
auto [stream, totalPipelineCount]{gpu.graphicsPipelineCacheManager->OpenReadStream()};
i64 lastKnownGoodOffset{stream.tellg()}; i64 lastKnownGoodOffset{stream.tellg()};
jvm.ShowPipelineLoadingScreen(totalPipelineCount);
gpu.graphicsPipelineAssembler->RegisterCompilationCallback([&]() {
jvm.UpdatePipelineLoadingProgress(++compiledCount);
});
try { try {
auto startTime{util::GetTimeNs()}; auto startTime{util::GetTimeNs()};
PipelineStateBundle bundle; PipelineStateBundle bundle;
@ -986,8 +994,10 @@ namespace skyline::gpu::interconnect::maxwell3d {
} catch (const exception &e) { } catch (const exception &e) {
Logger::Warn("Pipeline cache corrupted at: 0x{:X}, error: {}", lastKnownGoodOffset, e.what()); Logger::Warn("Pipeline cache corrupted at: 0x{:X}, error: {}", lastKnownGoodOffset, e.what());
gpu.graphicsPipelineCacheManager->InvalidateAllAfter(static_cast<u64>(lastKnownGoodOffset)); gpu.graphicsPipelineCacheManager->InvalidateAllAfter(static_cast<u64>(lastKnownGoodOffset));
return;
} }
gpu.graphicsPipelineAssembler->UnregisterCompilationCallback();
jvm.HidePipelineLoadingScreen();
} }
Pipeline *PipelineManager::FindOrCreate(InterconnectContext &ctx, Textures &textures, ConstantBufferSet &constantBuffers, const PackedPipelineState &packedState, const std::array<ShaderBinary, engine::PipelineCount> &shaderBinaries) { Pipeline *PipelineManager::FindOrCreate(InterconnectContext &ctx, Textures &textures, ConstantBufferSet &constantBuffers, const PackedPipelineState &packedState, const std::array<ShaderBinary, engine::PipelineCount> &shaderBinaries) {

View File

@ -272,7 +272,7 @@ namespace skyline::gpu::interconnect::maxwell3d {
#endif #endif
public: public:
PipelineManager(GPU &gpu); PipelineManager(GPU &gpu, JvmManager &jvm);
Pipeline *FindOrCreate(InterconnectContext &ctx, Textures &textures, ConstantBufferSet &constantBuffers, const PackedPipelineState &packedState, const std::array<ShaderBinary, engine::PipelineCount> &shaderBinaries); Pipeline *FindOrCreate(InterconnectContext &ctx, Textures &textures, ConstantBufferSet &constantBuffers, const PackedPipelineState &packedState, const std::array<ShaderBinary, engine::PipelineCount> &shaderBinaries);
}; };

View File

@ -8,19 +8,26 @@
namespace skyline::gpu { namespace skyline::gpu {
struct PipelineCacheFileHeader { struct PipelineCacheFileHeader {
static constexpr u32 Magic{util::MakeMagic<u32>("PCHE")}; //!< The magic value used to identify a pipeline cache file static constexpr u32 Magic{util::MakeMagic<u32>("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 magic{Magic};
u32 version{Version}; u32 version{Version};
u32 count{0}; //!< The total number of pipeline cache bundles in the file
auto operator<=>(const PipelineCacheFileHeader &) const = default; 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() { void PipelineCacheManager::Run() {
std::ofstream stream{stagingPath, std::ios::binary | std::ios::trunc}; std::ofstream stream{stagingPath, std::ios::binary | std::ios::trunc};
stream.write(reinterpret_cast<const char *>(&ValidPipelineCacheFileHeader), sizeof(PipelineCacheFileHeader)); PipelineCacheFileHeader header{};
stream.write(reinterpret_cast<const char *>(&header), sizeof(PipelineCacheFileHeader));
while (true) { while (true) {
std::unique_lock lock(writeMutex); std::unique_lock lock(writeMutex);
@ -31,7 +38,15 @@ namespace skyline::gpu {
auto bundle{std::move(writeQueue.front())}; auto bundle{std::move(writeQueue.front())};
writeQueue.pop(); writeQueue.pop();
lock.unlock(); lock.unlock();
bundle->Serialise(stream); 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<const char *>(&header), sizeof(PipelineCacheFileHeader));
stream.seekp(savedPosition);
} }
} }
@ -40,8 +55,8 @@ namespace skyline::gpu {
return false; return false;
PipelineCacheFileHeader header{}; PipelineCacheFileHeader header{};
stream.read(reinterpret_cast<char *>(&header), sizeof(header)); stream.read(reinterpret_cast<char *>(&header), sizeof(PipelineCacheFileHeader));
return header == ValidPipelineCacheFileHeader; return header.IsValid();
} }
void PipelineCacheManager::MergeStaging() { void PipelineCacheManager::MergeStaging() {
@ -49,12 +64,24 @@ namespace skyline::gpu {
if (stagingStream.fail()) if (stagingStream.fail())
return; // If the staging file doesn't exist then there's nothing to merge return; // If the staging file doesn't exist then there's nothing to merge
if (!ValidateHeader(stagingStream)) { PipelineCacheFileHeader stagingHeader{};
stagingStream.read(reinterpret_cast<char *>(&stagingHeader), sizeof(PipelineCacheFileHeader));
if (!stagingHeader.IsValid()) {
Logger::Warn("Discarding invalid pipeline cache staging file"); Logger::Warn("Discarding invalid pipeline cache staging file");
return; 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<char *>(&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<const char *>(&mainHeader), sizeof(PipelineCacheFileHeader));
mainStream.seekp(0, std::ios::end);
mainStream << stagingStream.rdbuf(); 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 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::filesystem::create_directories(std::filesystem::path{mainPath}.parent_path());
std::ofstream mainStream{mainPath, std::ios::binary | std::ios::app}; std::ofstream mainStream{mainPath, std::ios::binary | std::ios::app};
mainStream.write(reinterpret_cast<const char *>(&ValidPipelineCacheFileHeader), sizeof(PipelineCacheFileHeader)); PipelineCacheFileHeader header{};
mainStream.write(reinterpret_cast<const char *>(&header), sizeof(PipelineCacheFileHeader));
} }
// Merge any staging changes into the main file before starting the writer thread // Merge any staging changes into the main file before starting the writer thread
@ -87,12 +115,17 @@ namespace skyline::gpu {
writeCondition.notify_one(); writeCondition.notify_one();
} }
std::ifstream PipelineCacheManager::OpenReadStream() { std::pair<std::ifstream, u32> PipelineCacheManager::OpenReadStream() {
auto mainStream{std::ifstream{mainPath, std::ios::binary}}; 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<char *>(&header), sizeof(PipelineCacheFileHeader));
if (!header.IsValid())
throw exception("Pipeline cache main file corrupted at runtime!"); throw exception("Pipeline cache main file corrupted at runtime!");
return mainStream; return {std::move(mainStream), header.count};
} }
void PipelineCacheManager::InvalidateAllAfter(u64 offset) { void PipelineCacheManager::InvalidateAllAfter(u64 offset) {

View File

@ -34,7 +34,11 @@ namespace skyline::gpu {
*/ */
void QueueWrite(std::unique_ptr<interconnect::PipelineStateBundle> bundle); void QueueWrite(std::unique_ptr<interconnect::PipelineStateBundle> 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<std::ifstream, u32> OpenReadStream();
/** /**
* @brief Shrinks the pipeline cache file to `offset` bytes, removing any (potentially invalid) data after that point * @brief Shrinks the pipeline cache file to `offset` bytes, removing any (potentially invalid) data after that point