Introduce a pipeline cache manager for simple read/write cache accesses

All writes are done async into a staging file, which is then merged into the main pipeline cache file at the time of the next launch. Upon encountering file corruption the cache can be trimmed up to the last-known-good entry to avoid any excessive loss of data from just one error.
This commit is contained in:
Billy Laws 2022-12-10 15:43:06 +00:00
parent 06bf1b38af
commit e9bcdd06eb
16 changed files with 184 additions and 17 deletions

View File

@ -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

View File

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

View File

@ -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<PipelineCacheManager> graphicsPipelineCacheManager;
std::optional<interconnect::maxwell3d::PipelineManager> 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();
};
}

View File

@ -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;
};

View File

@ -10,7 +10,6 @@
#include "packed_pipeline_state.h"
#include "constant_buffers.h"
namespace skyline::gpu {
class TextureView;
}

View File

@ -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() {

View File

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

View File

@ -1,33 +1,37 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <gpu.h>
#include <gpu/pipeline_cache_manager.h>
#include "graphics_pipeline_state_accessor.h"
namespace skyline::gpu::interconnect::maxwell3d {
RuntimeGraphicsPipelineStateAccessor::RuntimeGraphicsPipelineStateAccessor(PipelineStateBundle &bundle,
RuntimeGraphicsPipelineStateAccessor::RuntimeGraphicsPipelineStateAccessor(std::unique_ptr<PipelineStateBundle> bundle,
InterconnectContext &ctx,
Textures &textures, ConstantBufferSet &constantBuffers,
const std::array<ShaderBinary, engine::PipelineCount> &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<u32>(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));
}
}

View File

@ -15,14 +15,14 @@ namespace skyline::gpu::interconnect::maxwell3d {
*/
class RuntimeGraphicsPipelineStateAccessor : public PipelineStateAccessor {
private:
PipelineStateBundle &bundle;
std::unique_ptr<PipelineStateBundle> bundle;
InterconnectContext &ctx;
Textures &textures;
ConstantBufferSet &constantBuffers;
std::array<ShaderBinary, engine::PipelineCount> shaderBinaries;
public:
RuntimeGraphicsPipelineStateAccessor(PipelineStateBundle &bundle,
RuntimeGraphicsPipelineStateAccessor(std::unique_ptr<PipelineStateBundle> bundle,
InterconnectContext &ctx,
Textures &textures, ConstantBufferSet &constantBuffers,
const std::array<ShaderBinary, engine::PipelineCount> &shaderBinaries);
@ -33,6 +33,6 @@ namespace skyline::gpu::interconnect::maxwell3d {
ShaderBinary GetShaderBinary(u32 pipelineStage) const override;
void MarkComplete() const override;
void MarkComplete() override;
};
}

View File

@ -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 {

View File

@ -4,9 +4,11 @@
#include <gpu/texture/texture.h>
#include <gpu/interconnect/command_executor.h>
#include <gpu/interconnect/common/pipeline.inc>
#include <gpu/interconnect/common/file_pipeline_state_accessor.h>
#include <gpu/cache/graphics_pipeline_cache.h>
#include <gpu/shader_manager.h>
#include <gpu.h>
#include "graphics_pipeline_state_accessor.h"
#include "pipeline_manager.h"
namespace skyline::gpu::interconnect::maxwell3d {

View File

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

View File

@ -299,8 +299,6 @@ namespace skyline::gpu::interconnect::maxwell3d {
};
private:
PipelineManager pipelineManager{};
PackedPipelineState packedState{};
dirty::BoundSubresource<EngineRegisters> engine;

View File

@ -0,0 +1,101 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <ostream>
#include <os.h>
#include "pipeline_cache_manager.h"
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{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<const char *>(&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<char *>(&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<const char *>(&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<interconnect::PipelineStateBundle> 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);
}
}

View File

@ -0,0 +1,44 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <queue>
#include <common.h>
#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<std::unique_ptr<interconnect::PipelineStateBundle>> 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<interconnect::PipelineStateBundle> 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);
};
}

View File

@ -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<kernel::type::KProcess>(state);