mirror of
https://github.com/Takiiiiiiii/strato.git
synced 2025-07-17 08:46:39 +00:00
Show pipelines loading screen while GPU is loading them
A `count` field has been added to the pipeline cache file header, representing the total pipeline count contained in the cache. When loading pipelines, the value is used to display an accurate progress bar of the loading process.
This commit is contained in:
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 = {};
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
};
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user