mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-12-26 15:11:47 +01:00
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:
parent
06bf1b38af
commit
e9bcdd06eb
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
};
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -10,7 +10,6 @@
|
||||
#include "packed_pipeline_state.h"
|
||||
#include "constant_buffers.h"
|
||||
|
||||
|
||||
namespace skyline::gpu {
|
||||
class TextureView;
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -299,8 +299,6 @@ namespace skyline::gpu::interconnect::maxwell3d {
|
||||
};
|
||||
|
||||
private:
|
||||
PipelineManager pipelineManager{};
|
||||
|
||||
PackedPipelineState packedState{};
|
||||
|
||||
dirty::BoundSubresource<EngineRegisters> engine;
|
||||
|
101
app/src/main/cpp/skyline/gpu/pipeline_cache_manager.cpp
Normal file
101
app/src/main/cpp/skyline/gpu/pipeline_cache_manager.cpp
Normal 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);
|
||||
}
|
||||
}
|
44
app/src/main/cpp/skyline/gpu/pipeline_cache_manager.h
Normal file
44
app/src/main/cpp/skyline/gpu/pipeline_cache_manager.h
Normal 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);
|
||||
};
|
||||
}
|
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user