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)
graphicsPipelineCacheManager.emplace(state,
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};
compilePendingDescs.erase(pipelineDescIt);
if (compilationCallback)
compilationCallback();
return pipeline;
}
@ -252,4 +255,15 @@ namespace skyline::gpu {
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
#include <functional>
#include <future>
#include <BS_thread_pool.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
BS::thread_pool pool;
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
@ -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<void()> callback);
/**
* @brief Unregisters the compilation callback
*/
void UnregisterCompilationCallback();
};
}

View File

@ -9,6 +9,7 @@
#include <gpu/graphics_pipeline_assembler.h>
#include <gpu/shader_manager.h>
#include <gpu.h>
#include <jvm.h>
#include <vulkan/vulkan_enums.hpp>
#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<u32> 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<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) {

View File

@ -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<ShaderBinary, engine::PipelineCount> &shaderBinaries);
};

View File

@ -8,19 +8,26 @@
namespace skyline::gpu {
struct PipelineCacheFileHeader {
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 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<const char *>(&ValidPipelineCacheFileHeader), sizeof(PipelineCacheFileHeader));
PipelineCacheFileHeader header{};
stream.write(reinterpret_cast<const char *>(&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<const char *>(&header), sizeof(PipelineCacheFileHeader));
stream.seekp(savedPosition);
}
}
@ -40,8 +55,8 @@ namespace skyline::gpu {
return false;
PipelineCacheFileHeader header{};
stream.read(reinterpret_cast<char *>(&header), sizeof(header));
return header == ValidPipelineCacheFileHeader;
stream.read(reinterpret_cast<char *>(&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<char *>(&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<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();
}
@ -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<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
@ -87,12 +115,17 @@ namespace skyline::gpu {
writeCondition.notify_one();
}
std::ifstream PipelineCacheManager::OpenReadStream() {
std::pair<std::ifstream, u32> 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<char *>(&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) {

View File

@ -34,7 +34,11 @@ namespace skyline::gpu {
*/
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