mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-22 05:45:38 +00:00
MetalDevice: Implement pipeline cache
This commit is contained in:
parent
191957547a
commit
bd99688b9d
|
@ -291,51 +291,45 @@ void D3D12Device::GetPipelineCacheHeader(PIPELINE_CACHE_HEADER* hdr)
|
||||||
hdr->unused = 0;
|
hdr->unused = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool D3D12Device::ReadPipelineCache(std::optional<DynamicHeapArray<u8>> data)
|
bool D3D12Device::ReadPipelineCache(DynamicHeapArray<u8> data, Error* error)
|
||||||
{
|
{
|
||||||
PIPELINE_CACHE_HEADER expected_header;
|
PIPELINE_CACHE_HEADER expected_header;
|
||||||
GetPipelineCacheHeader(&expected_header);
|
GetPipelineCacheHeader(&expected_header);
|
||||||
if (data.has_value() && (data->size() < sizeof(PIPELINE_CACHE_HEADER) ||
|
if ((data.size() < sizeof(PIPELINE_CACHE_HEADER) ||
|
||||||
std::memcmp(data->data(), &expected_header, sizeof(PIPELINE_CACHE_HEADER)) != 0))
|
std::memcmp(data.data(), &expected_header, sizeof(PIPELINE_CACHE_HEADER)) != 0))
|
||||||
{
|
{
|
||||||
WARNING_LOG("Pipeline cache header does not match current device, ignoring.");
|
Error::SetStringView(error, "Pipeline cache header does not match current device.");
|
||||||
data.reset();
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
HRESULT hr =
|
const HRESULT hr =
|
||||||
m_device->CreatePipelineLibrary(data.has_value() ? (data->data() + sizeof(PIPELINE_CACHE_HEADER)) : nullptr,
|
m_device->CreatePipelineLibrary(&data[sizeof(PIPELINE_CACHE_HEADER)], data.size() - sizeof(PIPELINE_CACHE_HEADER),
|
||||||
data.has_value() ? (data->size() - sizeof(PIPELINE_CACHE_HEADER)) : 0,
|
|
||||||
IID_PPV_ARGS(m_pipeline_library.ReleaseAndGetAddressOf()));
|
IID_PPV_ARGS(m_pipeline_library.ReleaseAndGetAddressOf()));
|
||||||
if (SUCCEEDED(hr))
|
|
||||||
{
|
|
||||||
if (data.has_value())
|
|
||||||
s_pipeline_cache_data = std::move(data.value());
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try without the cache data.
|
|
||||||
if (data.has_value())
|
|
||||||
{
|
|
||||||
WARNING_LOG("CreatePipelineLibrary() failed, trying without cache data. Error: {}",
|
|
||||||
Error::CreateHResult(hr).GetDescription());
|
|
||||||
|
|
||||||
hr = m_device->CreatePipelineLibrary(nullptr, 0, IID_PPV_ARGS(m_pipeline_library.ReleaseAndGetAddressOf()));
|
|
||||||
if (SUCCEEDED(hr))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (FAILED(hr))
|
if (FAILED(hr))
|
||||||
{
|
{
|
||||||
WARNING_LOG("CreatePipelineLibrary() failed, pipeline caching will not be available. Error: {}",
|
Error::SetHResult(error, "CreatePipelineLibrary() failed: ", hr);
|
||||||
Error::CreateHResult(hr).GetDescription());
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Have to keep the buffer around, DX doesn't take a copy.
|
||||||
|
s_pipeline_cache_data = std::move(data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool D3D12Device::CreatePipelineCache(const std::string& path, Error* error)
|
||||||
|
{
|
||||||
|
const HRESULT hr =
|
||||||
|
m_device->CreatePipelineLibrary(nullptr, 0, IID_PPV_ARGS(m_pipeline_library.ReleaseAndGetAddressOf()));
|
||||||
|
if (FAILED(hr))
|
||||||
|
{
|
||||||
|
Error::SetHResult(error, "CreatePipelineLibrary() failed: ", hr);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool D3D12Device::GetPipelineCacheData(DynamicHeapArray<u8>* data)
|
bool D3D12Device::GetPipelineCacheData(DynamicHeapArray<u8>* data, Error* error)
|
||||||
{
|
{
|
||||||
if (!m_pipeline_library)
|
if (!m_pipeline_library)
|
||||||
return false;
|
return false;
|
||||||
|
@ -356,7 +350,7 @@ bool D3D12Device::GetPipelineCacheData(DynamicHeapArray<u8>* data)
|
||||||
const HRESULT hr = m_pipeline_library->Serialize(data->data() + sizeof(PIPELINE_CACHE_HEADER), size);
|
const HRESULT hr = m_pipeline_library->Serialize(data->data() + sizeof(PIPELINE_CACHE_HEADER), size);
|
||||||
if (FAILED(hr))
|
if (FAILED(hr))
|
||||||
{
|
{
|
||||||
ERROR_LOG("Serialize() failed with HRESULT {:08X}", static_cast<unsigned>(hr));
|
Error::SetHResult(error, "Serialize() failed: ", hr);
|
||||||
data->deallocate();
|
data->deallocate();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -187,8 +187,9 @@ protected:
|
||||||
FeatureMask disabled_features, Error* error) override;
|
FeatureMask disabled_features, Error* error) override;
|
||||||
void DestroyDevice() override;
|
void DestroyDevice() override;
|
||||||
|
|
||||||
bool ReadPipelineCache(std::optional<DynamicHeapArray<u8>> data) override;
|
bool ReadPipelineCache(DynamicHeapArray<u8> data, Error* error) override;
|
||||||
bool GetPipelineCacheData(DynamicHeapArray<u8>* data) override;
|
bool CreatePipelineCache(const std::string& path, Error* error) override;
|
||||||
|
bool GetPipelineCacheData(DynamicHeapArray<u8>* data, Error* error) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum DIRTY_FLAG : u32
|
enum DIRTY_FLAG : u32
|
||||||
|
|
|
@ -428,14 +428,29 @@ void GPUDevice::OpenShaderCache(std::string_view base_path, u32 version)
|
||||||
}
|
}
|
||||||
|
|
||||||
s_pipeline_cache_path = {};
|
s_pipeline_cache_path = {};
|
||||||
|
s_pipeline_cache_size = 0;
|
||||||
|
s_pipeline_cache_hash = {};
|
||||||
|
|
||||||
if (m_features.pipeline_cache && !base_path.empty())
|
if (m_features.pipeline_cache && !base_path.empty())
|
||||||
{
|
{
|
||||||
const std::string basename = GetShaderCacheBaseName("pipelines");
|
Error error;
|
||||||
std::string filename = Path::Combine(base_path, TinyString::from_format("{}.bin", basename));
|
s_pipeline_cache_path =
|
||||||
if (OpenPipelineCache(filename))
|
Path::Combine(base_path, TinyString::from_format("{}.bin", GetShaderCacheBaseName("pipelines")));
|
||||||
s_pipeline_cache_path = std::move(filename);
|
if (FileSystem::FileExists(s_pipeline_cache_path.c_str()))
|
||||||
else
|
{
|
||||||
WARNING_LOG("Failed to read pipeline cache.");
|
if (OpenPipelineCache(s_pipeline_cache_path, &error))
|
||||||
|
return;
|
||||||
|
|
||||||
|
WARNING_LOG("Failed to read pipeline cache '{}': {}", Path::GetFileName(s_pipeline_cache_path),
|
||||||
|
error.GetDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CreatePipelineCache(s_pipeline_cache_path, &error))
|
||||||
|
{
|
||||||
|
WARNING_LOG("Failed to create pipeline cache '{}': {}", Path::GetFileName(s_pipeline_cache_path),
|
||||||
|
error.GetDescription());
|
||||||
|
s_pipeline_cache_path = {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -445,25 +460,11 @@ void GPUDevice::CloseShaderCache()
|
||||||
|
|
||||||
if (!s_pipeline_cache_path.empty())
|
if (!s_pipeline_cache_path.empty())
|
||||||
{
|
{
|
||||||
DynamicHeapArray<u8> data;
|
Error error;
|
||||||
if (GetPipelineCacheData(&data))
|
if (!ClosePipelineCache(s_pipeline_cache_path, &error))
|
||||||
{
|
{
|
||||||
// Save disk writes if it hasn't changed, think of the poor SSDs.
|
WARNING_LOG("Failed to close pipeline cache '{}': {}", Path::GetFileName(s_pipeline_cache_path),
|
||||||
if (s_pipeline_cache_size != data.size() || s_pipeline_cache_hash != SHA1Digest::GetDigest(data.cspan()))
|
error.GetDescription());
|
||||||
{
|
|
||||||
Error error;
|
|
||||||
INFO_LOG("Compressing and writing {} bytes to '{}'", data.size(), Path::GetFileName(s_pipeline_cache_path));
|
|
||||||
if (!CompressHelpers::CompressToFile(CompressHelpers::CompressType::Zstandard, s_pipeline_cache_path.c_str(),
|
|
||||||
data.cspan(), -1, true, &error))
|
|
||||||
{
|
|
||||||
ERROR_LOG("Failed to write pipeline cache to '{}': {}", Path::GetFileName(s_pipeline_cache_path),
|
|
||||||
error.GetDescription());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
INFO_LOG("Skipping updating pipeline cache '{}' due to no changes.", Path::GetFileName(s_pipeline_cache_path));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s_pipeline_cache_path = {};
|
s_pipeline_cache_path = {};
|
||||||
|
@ -480,49 +481,56 @@ std::string GPUDevice::GetShaderCacheBaseName(std::string_view type) const
|
||||||
return fmt::format("{}_{}{}", lower_api_name, type, debug_suffix);
|
return fmt::format("{}_{}{}", lower_api_name, type, debug_suffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GPUDevice::OpenPipelineCache(const std::string& filename)
|
bool GPUDevice::OpenPipelineCache(const std::string& path, Error* error)
|
||||||
{
|
{
|
||||||
s_pipeline_cache_size = 0;
|
CompressHelpers::OptionalByteBuffer data =
|
||||||
s_pipeline_cache_hash = {};
|
CompressHelpers::DecompressFile(CompressHelpers::CompressType::Zstandard, path.c_str(), std::nullopt, error);
|
||||||
|
if (!data.has_value())
|
||||||
|
return false;
|
||||||
|
|
||||||
Error error;
|
const size_t cache_size = data->size();
|
||||||
CompressHelpers::OptionalByteBuffer data;
|
const std::array<u8, SHA1Digest::DIGEST_SIZE> cache_hash = SHA1Digest::GetDigest(data->cspan());
|
||||||
if (FileSystem::FileExists(filename.c_str()))
|
|
||||||
{
|
|
||||||
data =
|
|
||||||
CompressHelpers::DecompressFile(CompressHelpers::CompressType::Zstandard, filename.c_str(), std::nullopt, &error);
|
|
||||||
if (data.has_value())
|
|
||||||
{
|
|
||||||
s_pipeline_cache_size = data->size();
|
|
||||||
s_pipeline_cache_hash = SHA1Digest::GetDigest(data->cspan());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ERROR_LOG("Failed to load pipeline cache from '{}': {}", Path::GetFileName(filename), error.GetDescription());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
INFO_LOG("Loading {} byte pipeline cache with hash {}", s_pipeline_cache_size,
|
INFO_LOG("Loading {} byte pipeline cache with hash {}", s_pipeline_cache_size,
|
||||||
SHA1Digest::DigestToString(s_pipeline_cache_hash));
|
SHA1Digest::DigestToString(s_pipeline_cache_hash));
|
||||||
|
|
||||||
if (ReadPipelineCache(std::move(data)))
|
if (!ReadPipelineCache(std::move(data.value()), error))
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
s_pipeline_cache_size = 0;
|
|
||||||
s_pipeline_cache_hash = {};
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
s_pipeline_cache_size = cache_size;
|
||||||
|
s_pipeline_cache_hash = cache_hash;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GPUDevice::ReadPipelineCache(std::optional<DynamicHeapArray<u8>> data)
|
bool GPUDevice::CreatePipelineCache(const std::string& path, Error* error)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GPUDevice::GetPipelineCacheData(DynamicHeapArray<u8>* data)
|
bool GPUDevice::ClosePipelineCache(const std::string& path, Error* error)
|
||||||
|
{
|
||||||
|
DynamicHeapArray<u8> data;
|
||||||
|
if (!GetPipelineCacheData(&data, error))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Save disk writes if it hasn't changed, think of the poor SSDs.
|
||||||
|
if (s_pipeline_cache_size == data.size() && s_pipeline_cache_hash == SHA1Digest::GetDigest(data.cspan()))
|
||||||
|
{
|
||||||
|
INFO_LOG("Skipping updating pipeline cache '{}' due to no changes.", Path::GetFileName(path));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
INFO_LOG("Compressing and writing {} bytes to '{}'", data.size(), Path::GetFileName(path));
|
||||||
|
return CompressHelpers::CompressToFile(CompressHelpers::CompressType::Zstandard, path.c_str(), data.cspan(), -1, true,
|
||||||
|
error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GPUDevice::ReadPipelineCache(DynamicHeapArray<u8> data, Error* error)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GPUDevice::GetPipelineCacheData(DynamicHeapArray<u8>* data, Error* error)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1705,7 +1713,7 @@ bool GPUDevice::TranslateVulkanSpvToLanguage(const std::span<const u8> spirv, GP
|
||||||
|
|
||||||
if (m_features.framebuffer_fetch &&
|
if (m_features.framebuffer_fetch &&
|
||||||
((sres = dyn_libs::spvc_compiler_options_set_uint(soptions, SPVC_COMPILER_OPTION_MSL_VERSION,
|
((sres = dyn_libs::spvc_compiler_options_set_uint(soptions, SPVC_COMPILER_OPTION_MSL_VERSION,
|
||||||
SPVC_MAKE_MSL_VERSION(2, 3, 0))) != SPVC_SUCCESS))
|
target_version)) != SPVC_SUCCESS))
|
||||||
{
|
{
|
||||||
Error::SetStringFmt(error, "spvc_compiler_options_set_uint(SPVC_COMPILER_OPTION_MSL_VERSION) failed: {}",
|
Error::SetStringFmt(error, "spvc_compiler_options_set_uint(SPVC_COMPILER_OPTION_MSL_VERSION) failed: {}",
|
||||||
static_cast<int>(sres));
|
static_cast<int>(sres));
|
||||||
|
|
|
@ -750,9 +750,11 @@ protected:
|
||||||
virtual void DestroyDevice() = 0;
|
virtual void DestroyDevice() = 0;
|
||||||
|
|
||||||
std::string GetShaderCacheBaseName(std::string_view type) const;
|
std::string GetShaderCacheBaseName(std::string_view type) const;
|
||||||
virtual bool OpenPipelineCache(const std::string& filename);
|
virtual bool OpenPipelineCache(const std::string& path, Error* error);
|
||||||
virtual bool ReadPipelineCache(std::optional<DynamicHeapArray<u8>> data);
|
virtual bool CreatePipelineCache(const std::string& path, Error* error);
|
||||||
virtual bool GetPipelineCacheData(DynamicHeapArray<u8>* data);
|
virtual bool ReadPipelineCache(DynamicHeapArray<u8> data, Error* error);
|
||||||
|
virtual bool GetPipelineCacheData(DynamicHeapArray<u8>* data, Error* error);
|
||||||
|
virtual bool ClosePipelineCache(const std::string& path, Error* error);
|
||||||
|
|
||||||
virtual std::unique_ptr<GPUShader> CreateShaderFromBinary(GPUShaderStage stage, std::span<const u8> data,
|
virtual std::unique_ptr<GPUShader> CreateShaderFromBinary(GPUShaderStage stage, std::span<const u8> data,
|
||||||
Error* error) = 0;
|
Error* error) = 0;
|
||||||
|
|
|
@ -288,6 +288,9 @@ protected:
|
||||||
bool CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control,
|
bool CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control,
|
||||||
FeatureMask disabled_features, Error* error) override;
|
FeatureMask disabled_features, Error* error) override;
|
||||||
void DestroyDevice() override;
|
void DestroyDevice() override;
|
||||||
|
bool OpenPipelineCache(const std::string& path, Error* error) override;
|
||||||
|
bool CreatePipelineCache(const std::string& path, Error* error) override;
|
||||||
|
bool ClosePipelineCache(const std::string& path, Error* error) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr u32 VERTEX_BUFFER_SIZE = 8 * 1024 * 1024;
|
static constexpr u32 VERTEX_BUFFER_SIZE = 8 * 1024 * 1024;
|
||||||
|
@ -372,6 +375,7 @@ private:
|
||||||
MetalStreamBuffer m_texture_upload_buffer;
|
MetalStreamBuffer m_texture_upload_buffer;
|
||||||
|
|
||||||
id<MTLLibrary> m_shaders = nil;
|
id<MTLLibrary> m_shaders = nil;
|
||||||
|
id<MTLBinaryArchive> m_pipeline_archive = nil;
|
||||||
std::vector<std::pair<std::pair<GPUTexture::Format, GPUTexture::Format>, id<MTLComputePipelineState>>>
|
std::vector<std::pair<std::pair<GPUTexture::Format, GPUTexture::Format>, id<MTLComputePipelineState>>>
|
||||||
m_resolve_pipelines;
|
m_resolve_pipelines;
|
||||||
std::vector<std::pair<ClearPipelineConfig, id<MTLRenderPipelineState>>> m_clear_pipelines;
|
std::vector<std::pair<ClearPipelineConfig, id<MTLRenderPipelineState>>> m_clear_pipelines;
|
||||||
|
@ -400,6 +404,7 @@ private:
|
||||||
GSVector4i m_current_scissor = {};
|
GSVector4i m_current_scissor = {};
|
||||||
|
|
||||||
bool m_vsync_enabled = false;
|
bool m_vsync_enabled = false;
|
||||||
|
bool m_pipeline_cache_modified = false;
|
||||||
|
|
||||||
double m_accumulated_gpu_time = 0;
|
double m_accumulated_gpu_time = 0;
|
||||||
double m_last_gpu_time_end = 0;
|
double m_last_gpu_time_end = 0;
|
||||||
|
|
|
@ -155,8 +155,7 @@ MetalDevice::MetalDevice() : m_current_viewport(0, 0, 1, 1), m_current_scissor(0
|
||||||
|
|
||||||
MetalDevice::~MetalDevice()
|
MetalDevice::~MetalDevice()
|
||||||
{
|
{
|
||||||
Assert(m_layer == nil);
|
Assert(m_pipeline_archive == nil && m_layer == nil && m_device == nil);
|
||||||
Assert(m_device == nil);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MetalDevice::HasSurface() const
|
bool MetalDevice::HasSurface() const
|
||||||
|
@ -251,8 +250,9 @@ bool MetalDevice::CreateDevice(std::string_view adapter, std::optional<bool> exc
|
||||||
|
|
||||||
void MetalDevice::SetFeatures(FeatureMask disabled_features)
|
void MetalDevice::SetFeatures(FeatureMask disabled_features)
|
||||||
{
|
{
|
||||||
|
// Set version to Metal 2.3, that's all we're using. Use SPIRV-Cross version encoding.
|
||||||
m_render_api = RenderAPI::Metal;
|
m_render_api = RenderAPI::Metal;
|
||||||
m_render_api_version = 100; // TODO: Make this more meaningful.
|
m_render_api_version = 20300;
|
||||||
m_max_texture_size = GetMetalMaxTextureSize(m_device);
|
m_max_texture_size = GetMetalMaxTextureSize(m_device);
|
||||||
m_max_multisamples = GetMetalMaxMultisamples(m_device);
|
m_max_multisamples = GetMetalMaxMultisamples(m_device);
|
||||||
|
|
||||||
|
@ -277,8 +277,15 @@ void MetalDevice::SetFeatures(FeatureMask disabled_features)
|
||||||
m_features.explicit_present = false;
|
m_features.explicit_present = false;
|
||||||
m_features.timed_present = true;
|
m_features.timed_present = true;
|
||||||
m_features.shader_cache = true;
|
m_features.shader_cache = true;
|
||||||
m_features.pipeline_cache = false;
|
m_features.pipeline_cache = true;
|
||||||
m_features.prefer_unused_textures = true;
|
m_features.prefer_unused_textures = true;
|
||||||
|
|
||||||
|
// Disable pipeline cache on Intel, apparently it's buggy.
|
||||||
|
if ([[m_device name] containsString:@"Intel"])
|
||||||
|
{
|
||||||
|
WARNING_LOG("Disabling Metal pipeline cache on Intel GPU.");
|
||||||
|
m_features.pipeline_cache = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MetalDevice::LoadShaders()
|
bool MetalDevice::LoadShaders()
|
||||||
|
@ -313,6 +320,76 @@ bool MetalDevice::LoadShaders()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MetalDevice::OpenPipelineCache(const std::string& path, Error* error)
|
||||||
|
{
|
||||||
|
@autoreleasepool
|
||||||
|
{
|
||||||
|
MTLBinaryArchiveDescriptor* archiveDescriptor = [[[MTLBinaryArchiveDescriptor alloc] init] autorelease];
|
||||||
|
archiveDescriptor.url = [NSURL fileURLWithPath:StringViewToNSString(path)];
|
||||||
|
|
||||||
|
NSError* nserror = nil;
|
||||||
|
m_pipeline_archive = [m_device newBinaryArchiveWithDescriptor:archiveDescriptor error:&nserror];
|
||||||
|
if (m_pipeline_archive == nil)
|
||||||
|
{
|
||||||
|
NSErrorToErrorObject(error, "newBinaryArchiveWithDescriptor failed: ", nserror);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pipeline_cache_modified = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MetalDevice::CreatePipelineCache(const std::string& path, Error* error)
|
||||||
|
{
|
||||||
|
@autoreleasepool
|
||||||
|
{
|
||||||
|
MTLBinaryArchiveDescriptor* archiveDescriptor = [[[MTLBinaryArchiveDescriptor alloc] init] autorelease];
|
||||||
|
archiveDescriptor.url = nil;
|
||||||
|
|
||||||
|
NSError* nserror = nil;
|
||||||
|
m_pipeline_archive = [m_device newBinaryArchiveWithDescriptor:archiveDescriptor error:&nserror];
|
||||||
|
if (m_pipeline_archive == nil)
|
||||||
|
{
|
||||||
|
NSErrorToErrorObject(error, "newBinaryArchiveWithDescriptor failed: ", nserror);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pipeline_cache_modified = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MetalDevice::ClosePipelineCache(const std::string& path, Error* error)
|
||||||
|
{
|
||||||
|
if (!m_pipeline_archive)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const ScopedGuard closer = [this]() {
|
||||||
|
[m_pipeline_archive release];
|
||||||
|
m_pipeline_archive = nil;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!m_pipeline_cache_modified)
|
||||||
|
{
|
||||||
|
INFO_LOG("Not saving pipeline cache, it has not been modified.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@autoreleasepool
|
||||||
|
{
|
||||||
|
NSURL* url = [NSURL fileURLWithPath:StringViewToNSString(path)];
|
||||||
|
NSError* nserror = nil;
|
||||||
|
if (![m_pipeline_archive serializeToURL:url error:&nserror])
|
||||||
|
{
|
||||||
|
NSErrorToErrorObject(error, "serializeToURL failed: ", nserror);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
id<MTLFunction> MetalDevice::GetFunctionFromLibrary(id<MTLLibrary> library, NSString* name)
|
id<MTLFunction> MetalDevice::GetFunctionFromLibrary(id<MTLLibrary> library, NSString* name)
|
||||||
{
|
{
|
||||||
id<MTLFunction> function = [library newFunctionWithName:name];
|
id<MTLFunction> function = [library newFunctionWithName:name];
|
||||||
|
@ -609,30 +686,13 @@ void MetalShader::SetDebugName(std::string_view name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Clean this up, somehow..
|
|
||||||
namespace EmuFolders {
|
|
||||||
extern std::string DataRoot;
|
|
||||||
}
|
|
||||||
static void DumpShader(u32 n, std::string_view suffix, std::string_view data)
|
|
||||||
{
|
|
||||||
if (data.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto fp = FileSystem::OpenManagedCFile(
|
|
||||||
Path::Combine(EmuFolders::DataRoot, fmt::format("shader{}_{}.txt", suffix, n)).c_str(), "wb");
|
|
||||||
if (!fp)
|
|
||||||
return;
|
|
||||||
|
|
||||||
std::fwrite(data.data(), data.length(), 1, fp.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<GPUShader> MetalDevice::CreateShaderFromMSL(GPUShaderStage stage, std::string_view source,
|
std::unique_ptr<GPUShader> MetalDevice::CreateShaderFromMSL(GPUShaderStage stage, std::string_view source,
|
||||||
std::string_view entry_point, Error* error)
|
std::string_view entry_point, Error* error)
|
||||||
{
|
{
|
||||||
@autoreleasepool
|
@autoreleasepool
|
||||||
{
|
{
|
||||||
NSString* const ns_source = StringViewToNSString(source);
|
NSString* const ns_source = StringViewToNSString(source);
|
||||||
NSError* nserror = nullptr;
|
NSError* nserror = nil;
|
||||||
id<MTLLibrary> library = [m_device newLibraryWithSource:ns_source options:nil error:&nserror];
|
id<MTLLibrary> library = [m_device newLibraryWithSource:ns_source options:nil error:&nserror];
|
||||||
if (!library)
|
if (!library)
|
||||||
{
|
{
|
||||||
|
@ -897,13 +957,41 @@ std::unique_ptr<GPUPipeline> MetalDevice::CreatePipeline(const GPUPipeline::Grap
|
||||||
if (config.layout == GPUPipeline::Layout::SingleTextureBufferAndPushConstants)
|
if (config.layout == GPUPipeline::Layout::SingleTextureBufferAndPushConstants)
|
||||||
desc.fragmentBuffers[1].mutability = MTLMutabilityImmutable;
|
desc.fragmentBuffers[1].mutability = MTLMutabilityImmutable;
|
||||||
|
|
||||||
NSError* nserror = nullptr;
|
NSError* nserror = nil;
|
||||||
id<MTLRenderPipelineState> pipeline = [m_device newRenderPipelineStateWithDescriptor:desc error:&nserror];
|
|
||||||
|
// Try cached first.
|
||||||
|
id<MTLRenderPipelineState> pipeline = nil;
|
||||||
|
if (m_pipeline_archive != nil)
|
||||||
|
{
|
||||||
|
desc.binaryArchives = [NSArray arrayWithObjects:m_pipeline_archive, nil];
|
||||||
|
pipeline = [m_device newRenderPipelineStateWithDescriptor:desc
|
||||||
|
options:MTLPipelineOptionFailOnBinaryArchiveMiss
|
||||||
|
reflection:nil
|
||||||
|
error:&nserror];
|
||||||
|
if (pipeline == nil)
|
||||||
|
{
|
||||||
|
// Add it to the cache.
|
||||||
|
if (![m_pipeline_archive addRenderPipelineFunctionsWithDescriptor:desc error:&nserror])
|
||||||
|
{
|
||||||
|
LogNSError(nserror, "Failed to add render pipeline to binary archive");
|
||||||
|
desc.binaryArchives = nil;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_pipeline_cache_modified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (pipeline == nil)
|
if (pipeline == nil)
|
||||||
{
|
{
|
||||||
LogNSError(nserror, "Failed to create render pipeline state");
|
pipeline = [m_device newRenderPipelineStateWithDescriptor:desc error:&nserror];
|
||||||
NSErrorToErrorObject(error, "newRenderPipelineStateWithDescriptor failed: ", nserror);
|
if (pipeline == nil)
|
||||||
return {};
|
{
|
||||||
|
LogNSError(nserror, "Failed to create render pipeline state");
|
||||||
|
NSErrorToErrorObject(error, "newRenderPipelineStateWithDescriptor failed: ", nserror);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::unique_ptr<GPUPipeline>(new MetalPipeline(pipeline, depth, cull_mode, primitive));
|
return std::unique_ptr<GPUPipeline>(new MetalPipeline(pipeline, depth, cull_mode, primitive));
|
||||||
|
|
|
@ -529,7 +529,6 @@ void OpenGLDevice::DestroyDevice()
|
||||||
if (!m_gl_context)
|
if (!m_gl_context)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ClosePipelineCache();
|
|
||||||
DestroyBuffers();
|
DestroyBuffers();
|
||||||
|
|
||||||
m_gl_context->DoneCurrent();
|
m_gl_context->DoneCurrent();
|
||||||
|
|
|
@ -136,8 +136,9 @@ protected:
|
||||||
FeatureMask disabled_features, Error* error) override;
|
FeatureMask disabled_features, Error* error) override;
|
||||||
void DestroyDevice() override;
|
void DestroyDevice() override;
|
||||||
|
|
||||||
bool OpenPipelineCache(const std::string& filename) override;
|
bool OpenPipelineCache(const std::string& path, Error* error) override;
|
||||||
bool GetPipelineCacheData(DynamicHeapArray<u8>* data) override;
|
bool CreatePipelineCache(const std::string& path, Error* error) override;
|
||||||
|
bool ClosePipelineCache(const std::string& path, Error* error) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr u8 NUM_TIMESTAMP_QUERIES = 3;
|
static constexpr u8 NUM_TIMESTAMP_QUERIES = 3;
|
||||||
|
@ -172,7 +173,6 @@ private:
|
||||||
const GPUPipeline::GraphicsConfig& plconfig);
|
const GPUPipeline::GraphicsConfig& plconfig);
|
||||||
void AddToPipelineCache(OpenGLPipeline::ProgramCacheItem* it);
|
void AddToPipelineCache(OpenGLPipeline::ProgramCacheItem* it);
|
||||||
bool DiscardPipelineCache();
|
bool DiscardPipelineCache();
|
||||||
void ClosePipelineCache();
|
|
||||||
|
|
||||||
void ApplyRasterizationState(GPUPipeline::RasterizationState rs);
|
void ApplyRasterizationState(GPUPipeline::RasterizationState rs);
|
||||||
void ApplyDepthState(GPUPipeline::DepthState ds);
|
void ApplyDepthState(GPUPipeline::DepthState ds);
|
||||||
|
@ -224,7 +224,6 @@ private:
|
||||||
bool m_timestamp_query_started = false;
|
bool m_timestamp_query_started = false;
|
||||||
|
|
||||||
std::FILE* m_pipeline_disk_cache_file = nullptr;
|
std::FILE* m_pipeline_disk_cache_file = nullptr;
|
||||||
std::string m_pipeline_disk_cache_filename;
|
|
||||||
u32 m_pipeline_disk_cache_data_end = 0;
|
u32 m_pipeline_disk_cache_data_end = 0;
|
||||||
bool m_pipeline_disk_cache_changed = false;
|
bool m_pipeline_disk_cache_changed = false;
|
||||||
|
|
||||||
|
|
|
@ -753,49 +753,29 @@ void OpenGLDevice::SetPipeline(GPUPipeline* pipeline)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenGLDevice::OpenPipelineCache(const std::string& filename)
|
bool OpenGLDevice::OpenPipelineCache(const std::string& path, Error* error)
|
||||||
{
|
{
|
||||||
DebugAssert(!m_pipeline_disk_cache_file);
|
DebugAssert(!m_pipeline_disk_cache_file);
|
||||||
|
|
||||||
Error error;
|
m_pipeline_disk_cache_file = FileSystem::OpenCFile(path.c_str(), "r+b", error);
|
||||||
m_pipeline_disk_cache_file = FileSystem::OpenCFile(filename.c_str(), "r+b", &error);
|
|
||||||
m_pipeline_disk_cache_filename = filename;
|
|
||||||
|
|
||||||
if (!m_pipeline_disk_cache_file)
|
if (!m_pipeline_disk_cache_file)
|
||||||
{
|
return false;
|
||||||
// Multiple instances running? Ignore.
|
|
||||||
if (errno == EACCES)
|
|
||||||
{
|
|
||||||
m_pipeline_disk_cache_filename = {};
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it doesn't exist, we're going to create it.
|
|
||||||
if (errno != ENOENT)
|
|
||||||
{
|
|
||||||
WARNING_LOG("Failed to open shader cache: {}", error.GetDescription());
|
|
||||||
m_pipeline_disk_cache_filename = {};
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
WARNING_LOG("Disk cache does not exist, creating.");
|
|
||||||
return DiscardPipelineCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read footer.
|
// Read footer.
|
||||||
const s64 size = FileSystem::FSize64(m_pipeline_disk_cache_file);
|
const s64 size = FileSystem::FSize64(m_pipeline_disk_cache_file);
|
||||||
if (size < static_cast<s64>(sizeof(PipelineDiskCacheFooter)) ||
|
if (size < static_cast<s64>(sizeof(PipelineDiskCacheFooter)) ||
|
||||||
size >= static_cast<s64>(std::numeric_limits<u32>::max()))
|
size >= static_cast<s64>(std::numeric_limits<u32>::max()))
|
||||||
{
|
{
|
||||||
return DiscardPipelineCache();
|
Error::SetStringView(error, "Invalid cache file size.");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
PipelineDiskCacheFooter file_footer;
|
PipelineDiskCacheFooter file_footer;
|
||||||
if (FileSystem::FSeek64(m_pipeline_disk_cache_file, size - sizeof(PipelineDiskCacheFooter), SEEK_SET) != 0 ||
|
if (FileSystem::FSeek64(m_pipeline_disk_cache_file, size - sizeof(PipelineDiskCacheFooter), SEEK_SET) != 0 ||
|
||||||
std::fread(&file_footer, sizeof(file_footer), 1, m_pipeline_disk_cache_file) != 1)
|
std::fread(&file_footer, sizeof(file_footer), 1, m_pipeline_disk_cache_file) != 1)
|
||||||
{
|
{
|
||||||
ERROR_LOG("Failed to read disk cache footer.");
|
Error::SetStringView(error, "Invalid cache file footer.");
|
||||||
return DiscardPipelineCache();
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
PipelineDiskCacheFooter expected_footer;
|
PipelineDiskCacheFooter expected_footer;
|
||||||
|
@ -809,8 +789,8 @@ bool OpenGLDevice::OpenPipelineCache(const std::string& filename)
|
||||||
std::strncmp(file_footer.driver_version, expected_footer.driver_version, std::size(file_footer.driver_version)) !=
|
std::strncmp(file_footer.driver_version, expected_footer.driver_version, std::size(file_footer.driver_version)) !=
|
||||||
0)
|
0)
|
||||||
{
|
{
|
||||||
ERROR_LOG("Disk cache does not match expected driver/version.");
|
Error::SetStringView(error, "Cache does not match expected driver/version.");
|
||||||
return DiscardPipelineCache();
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_pipeline_disk_cache_data_end = static_cast<u32>(size) - sizeof(PipelineDiskCacheFooter) -
|
m_pipeline_disk_cache_data_end = static_cast<u32>(size) - sizeof(PipelineDiskCacheFooter) -
|
||||||
|
@ -818,8 +798,8 @@ bool OpenGLDevice::OpenPipelineCache(const std::string& filename)
|
||||||
if (m_pipeline_disk_cache_data_end < 0 ||
|
if (m_pipeline_disk_cache_data_end < 0 ||
|
||||||
FileSystem::FSeek64(m_pipeline_disk_cache_file, m_pipeline_disk_cache_data_end, SEEK_SET) != 0)
|
FileSystem::FSeek64(m_pipeline_disk_cache_file, m_pipeline_disk_cache_data_end, SEEK_SET) != 0)
|
||||||
{
|
{
|
||||||
ERROR_LOG("Failed to seek to start of index entries.");
|
Error::SetStringView(error, "Failed to seek to start of index entries.");
|
||||||
return DiscardPipelineCache();
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read entries.
|
// Read entries.
|
||||||
|
@ -829,14 +809,16 @@ bool OpenGLDevice::OpenPipelineCache(const std::string& filename)
|
||||||
if (std::fread(&entry, sizeof(entry), 1, m_pipeline_disk_cache_file) != 1 ||
|
if (std::fread(&entry, sizeof(entry), 1, m_pipeline_disk_cache_file) != 1 ||
|
||||||
(static_cast<s64>(entry.offset) + static_cast<s64>(entry.compressed_size)) >= size)
|
(static_cast<s64>(entry.offset) + static_cast<s64>(entry.compressed_size)) >= size)
|
||||||
{
|
{
|
||||||
ERROR_LOG("Failed to read disk cache entry.");
|
Error::SetStringView(error, "Failed to read disk cache entry.");
|
||||||
return DiscardPipelineCache();
|
m_program_cache.clear();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_program_cache.find(entry.key) != m_program_cache.end())
|
if (m_program_cache.find(entry.key) != m_program_cache.end())
|
||||||
{
|
{
|
||||||
ERROR_LOG("Duplicate program in disk cache.");
|
Error::SetStringView(error, "Duplicate program in disk cache.");
|
||||||
return DiscardPipelineCache();
|
m_program_cache.clear();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
OpenGLPipeline::ProgramCacheItem pitem;
|
OpenGLPipeline::ProgramCacheItem pitem;
|
||||||
|
@ -853,10 +835,15 @@ bool OpenGLDevice::OpenPipelineCache(const std::string& filename)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenGLDevice::GetPipelineCacheData(DynamicHeapArray<u8>* data)
|
bool OpenGLDevice::CreatePipelineCache(const std::string& path, Error* error)
|
||||||
{
|
{
|
||||||
// Self-managed.
|
m_pipeline_disk_cache_file = FileSystem::OpenCFile(path.c_str(), "w+b", error);
|
||||||
return false;
|
if (!m_pipeline_disk_cache_file)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m_pipeline_disk_cache_data_end = 0;
|
||||||
|
m_pipeline_disk_cache_changed = true;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
GLuint OpenGLDevice::CreateProgramFromPipelineCache(const OpenGLPipeline::ProgramCacheItem& it,
|
GLuint OpenGLDevice::CreateProgramFromPipelineCache(const OpenGLPipeline::ProgramCacheItem& it,
|
||||||
|
@ -976,42 +963,42 @@ bool OpenGLDevice::DiscardPipelineCache()
|
||||||
it = m_program_cache.erase(it);
|
it = m_program_cache.erase(it);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_pipeline_disk_cache_file)
|
if (!m_pipeline_disk_cache_file)
|
||||||
std::fclose(m_pipeline_disk_cache_file);
|
|
||||||
|
|
||||||
Error error;
|
|
||||||
m_pipeline_disk_cache_data_end = 0;
|
|
||||||
m_pipeline_disk_cache_file = FileSystem::OpenCFile(m_pipeline_disk_cache_filename.c_str(), "w+b", &error);
|
|
||||||
if (!m_pipeline_disk_cache_file) [[unlikely]]
|
|
||||||
{
|
{
|
||||||
ERROR_LOG("Failed to reopen pipeline cache: {}", error.GetDescription());
|
// Probably shouldn't get here...
|
||||||
m_pipeline_disk_cache_filename = {};
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Error error;
|
||||||
|
if (!FileSystem::FTruncate64(m_pipeline_disk_cache_file, 0, &error))
|
||||||
|
{
|
||||||
|
ERROR_LOG("Failed to truncate pipeline cache: {}", error.GetDescription());
|
||||||
|
std::fclose(m_pipeline_disk_cache_file);
|
||||||
|
m_pipeline_disk_cache_file = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pipeline_disk_cache_data_end = 0;
|
||||||
|
m_pipeline_disk_cache_changed = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLDevice::ClosePipelineCache()
|
bool OpenGLDevice::ClosePipelineCache(const std::string& filename, Error* error)
|
||||||
{
|
{
|
||||||
const ScopedGuard file_closer = [this]() {
|
|
||||||
if (m_pipeline_disk_cache_file)
|
|
||||||
{
|
|
||||||
std::fclose(m_pipeline_disk_cache_file);
|
|
||||||
m_pipeline_disk_cache_file = nullptr;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!m_pipeline_disk_cache_changed)
|
if (!m_pipeline_disk_cache_changed)
|
||||||
{
|
{
|
||||||
VERBOSE_LOG("Not updating pipeline cache because it has not changed.");
|
VERBOSE_LOG("Not updating pipeline cache because it has not changed.");
|
||||||
return;
|
std::fclose(m_pipeline_disk_cache_file);
|
||||||
|
m_pipeline_disk_cache_file = nullptr;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FileSystem::FSeek64(m_pipeline_disk_cache_file, m_pipeline_disk_cache_data_end, SEEK_SET) != 0) [[unlikely]]
|
// Rewrite footer/index entries.
|
||||||
|
if (!FileSystem::FSeek64(m_pipeline_disk_cache_file, m_pipeline_disk_cache_data_end, SEEK_SET, error) != 0)
|
||||||
{
|
{
|
||||||
ERROR_LOG("Failed to seek to data end.");
|
std::fclose(m_pipeline_disk_cache_file);
|
||||||
return;
|
m_pipeline_disk_cache_file = nullptr;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 count = 0;
|
u32 count = 0;
|
||||||
|
@ -1030,8 +1017,10 @@ void OpenGLDevice::ClosePipelineCache()
|
||||||
|
|
||||||
if (std::fwrite(&entry, sizeof(entry), 1, m_pipeline_disk_cache_file) != 1) [[unlikely]]
|
if (std::fwrite(&entry, sizeof(entry), 1, m_pipeline_disk_cache_file) != 1) [[unlikely]]
|
||||||
{
|
{
|
||||||
ERROR_LOG("Failed to write index entry.");
|
Error::SetErrno(error, "fwrite() for entry failed: ", errno);
|
||||||
return;
|
std::fclose(m_pipeline_disk_cache_file);
|
||||||
|
m_pipeline_disk_cache_file = nullptr;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
count++;
|
count++;
|
||||||
|
@ -1042,5 +1031,14 @@ void OpenGLDevice::ClosePipelineCache()
|
||||||
footer.num_programs = count;
|
footer.num_programs = count;
|
||||||
|
|
||||||
if (std::fwrite(&footer, sizeof(footer), 1, m_pipeline_disk_cache_file) != 1) [[unlikely]]
|
if (std::fwrite(&footer, sizeof(footer), 1, m_pipeline_disk_cache_file) != 1) [[unlikely]]
|
||||||
ERROR_LOG("Failed to write footer.");
|
{
|
||||||
|
Error::SetErrno(error, "fwrite() for footer failed: ", errno);
|
||||||
|
std::fclose(m_pipeline_disk_cache_file);
|
||||||
|
m_pipeline_disk_cache_file = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::fclose(m_pipeline_disk_cache_file) != 0)
|
||||||
|
Error::SetErrno(error, "fclose() failed: ", errno);
|
||||||
|
m_pipeline_disk_cache_file = nullptr;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2098,37 +2098,37 @@ void VulkanDevice::DestroyDevice()
|
||||||
Vulkan::UnloadVulkanLibrary();
|
Vulkan::UnloadVulkanLibrary();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VulkanDevice::ValidatePipelineCacheHeader(const VK_PIPELINE_CACHE_HEADER& header)
|
bool VulkanDevice::ValidatePipelineCacheHeader(const VK_PIPELINE_CACHE_HEADER& header, Error* error)
|
||||||
{
|
{
|
||||||
if (header.header_length < sizeof(VK_PIPELINE_CACHE_HEADER))
|
if (header.header_length < sizeof(VK_PIPELINE_CACHE_HEADER))
|
||||||
{
|
{
|
||||||
ERROR_LOG("Pipeline cache failed validation: Invalid header length");
|
Error::SetStringView(error, "Invalid header length");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (header.header_version != VK_PIPELINE_CACHE_HEADER_VERSION_ONE)
|
if (header.header_version != VK_PIPELINE_CACHE_HEADER_VERSION_ONE)
|
||||||
{
|
{
|
||||||
ERROR_LOG("Pipeline cache failed validation: Invalid header version");
|
Error::SetStringView(error, "Invalid header version");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (header.vendor_id != m_device_properties.vendorID)
|
if (header.vendor_id != m_device_properties.vendorID)
|
||||||
{
|
{
|
||||||
ERROR_LOG("Pipeline cache failed validation: Incorrect vendor ID (file: 0x{:X}, device: 0x{:X})", header.vendor_id,
|
Error::SetStringFmt(error, "Incorrect vendor ID (file: 0x{:X}, device: 0x{:X})", header.vendor_id,
|
||||||
m_device_properties.vendorID);
|
m_device_properties.vendorID);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (header.device_id != m_device_properties.deviceID)
|
if (header.device_id != m_device_properties.deviceID)
|
||||||
{
|
{
|
||||||
ERROR_LOG("Pipeline cache failed validation: Incorrect device ID (file: 0x{:X}, device: 0x{:X})", header.device_id,
|
Error::SetStringFmt(error, "Incorrect device ID (file: 0x{:X}, device: 0x{:X})", header.device_id,
|
||||||
m_device_properties.deviceID);
|
m_device_properties.deviceID);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (std::memcmp(header.uuid, m_device_properties.pipelineCacheUUID, VK_UUID_SIZE) != 0)
|
if (std::memcmp(header.uuid, m_device_properties.pipelineCacheUUID, VK_UUID_SIZE) != 0)
|
||||||
{
|
{
|
||||||
ERROR_LOG("Pipeline cache failed validation: Incorrect UUID");
|
Error::SetStringView(error, "Incorrect UUID");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2144,35 +2144,46 @@ void VulkanDevice::FillPipelineCacheHeader(VK_PIPELINE_CACHE_HEADER* header)
|
||||||
std::memcpy(header->uuid, m_device_properties.pipelineCacheUUID, VK_UUID_SIZE);
|
std::memcpy(header->uuid, m_device_properties.pipelineCacheUUID, VK_UUID_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VulkanDevice::ReadPipelineCache(std::optional<DynamicHeapArray<u8>> data)
|
bool VulkanDevice::ReadPipelineCache(DynamicHeapArray<u8> data, Error* error)
|
||||||
{
|
{
|
||||||
if (data.has_value())
|
if (data.size() < sizeof(VK_PIPELINE_CACHE_HEADER))
|
||||||
{
|
{
|
||||||
if (data->size() < sizeof(VK_PIPELINE_CACHE_HEADER))
|
Error::SetStringView(error, "Pipeline cache is too small.");
|
||||||
{
|
return false;
|
||||||
ERROR_LOG("Pipeline cache is too small, ignoring.");
|
|
||||||
data.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
VK_PIPELINE_CACHE_HEADER header;
|
|
||||||
std::memcpy(&header, data->data(), sizeof(header));
|
|
||||||
if (!ValidatePipelineCacheHeader(header))
|
|
||||||
data.reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const VkPipelineCacheCreateInfo ci{VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, nullptr, 0,
|
// alignment reasons...
|
||||||
data.has_value() ? data->size() : 0, data.has_value() ? data->data() : nullptr};
|
VK_PIPELINE_CACHE_HEADER header;
|
||||||
|
std::memcpy(&header, data.data(), sizeof(header));
|
||||||
|
if (!ValidatePipelineCacheHeader(header, error))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const VkPipelineCacheCreateInfo ci{VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, nullptr, 0, data.size(),
|
||||||
|
data.data()};
|
||||||
VkResult res = vkCreatePipelineCache(m_device, &ci, nullptr, &m_pipeline_cache);
|
VkResult res = vkCreatePipelineCache(m_device, &ci, nullptr, &m_pipeline_cache);
|
||||||
if (res != VK_SUCCESS)
|
if (res != VK_SUCCESS)
|
||||||
{
|
{
|
||||||
LOG_VULKAN_ERROR(res, "vkCreatePipelineCache() failed: ");
|
Vulkan::SetErrorObject(error, "vkCreatePipelineCache() failed: ", res);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VulkanDevice::GetPipelineCacheData(DynamicHeapArray<u8>* data)
|
bool VulkanDevice::CreatePipelineCache(const std::string& path, Error* error)
|
||||||
|
{
|
||||||
|
const VkPipelineCacheCreateInfo ci{VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, nullptr, 0, 0, nullptr};
|
||||||
|
VkResult res = vkCreatePipelineCache(m_device, &ci, nullptr, &m_pipeline_cache);
|
||||||
|
if (res != VK_SUCCESS)
|
||||||
|
{
|
||||||
|
Vulkan::SetErrorObject(error, "vkCreatePipelineCache() failed: ", res);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VulkanDevice::GetPipelineCacheData(DynamicHeapArray<u8>* data, Error* error)
|
||||||
{
|
{
|
||||||
if (m_pipeline_cache == VK_NULL_HANDLE)
|
if (m_pipeline_cache == VK_NULL_HANDLE)
|
||||||
return false;
|
return false;
|
||||||
|
@ -2181,7 +2192,7 @@ bool VulkanDevice::GetPipelineCacheData(DynamicHeapArray<u8>* data)
|
||||||
VkResult res = vkGetPipelineCacheData(m_device, m_pipeline_cache, &data_size, nullptr);
|
VkResult res = vkGetPipelineCacheData(m_device, m_pipeline_cache, &data_size, nullptr);
|
||||||
if (res != VK_SUCCESS)
|
if (res != VK_SUCCESS)
|
||||||
{
|
{
|
||||||
LOG_VULKAN_ERROR(res, "vkGetPipelineCacheData() failed: ");
|
Vulkan::SetErrorObject(error, "vkGetPipelineCacheData() failed: ", res);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2189,7 +2200,7 @@ bool VulkanDevice::GetPipelineCacheData(DynamicHeapArray<u8>* data)
|
||||||
res = vkGetPipelineCacheData(m_device, m_pipeline_cache, &data_size, data->data());
|
res = vkGetPipelineCacheData(m_device, m_pipeline_cache, &data_size, data->data());
|
||||||
if (res != VK_SUCCESS)
|
if (res != VK_SUCCESS)
|
||||||
{
|
{
|
||||||
LOG_VULKAN_ERROR(res, "vkGetPipelineCacheData() (2) failed: ");
|
Vulkan::SetErrorObject(error, "vkGetPipelineCacheData() (2) failed: ", res);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -234,8 +234,9 @@ protected:
|
||||||
FeatureMask disabled_features, Error* error) override;
|
FeatureMask disabled_features, Error* error) override;
|
||||||
void DestroyDevice() override;
|
void DestroyDevice() override;
|
||||||
|
|
||||||
bool ReadPipelineCache(std::optional<DynamicHeapArray<u8>> data) override;
|
bool ReadPipelineCache(DynamicHeapArray<u8> data, Error* error) override;
|
||||||
bool GetPipelineCacheData(DynamicHeapArray<u8>* data) override;
|
bool CreatePipelineCache(const std::string& path, Error* error) override;
|
||||||
|
bool GetPipelineCacheData(DynamicHeapArray<u8>* data, Error* error) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum DIRTY_FLAG : u32
|
enum DIRTY_FLAG : u32
|
||||||
|
@ -305,7 +306,7 @@ private:
|
||||||
static VkInstance CreateVulkanInstance(const WindowInfo& wi, OptionalExtensions* oe, bool enable_debug_utils,
|
static VkInstance CreateVulkanInstance(const WindowInfo& wi, OptionalExtensions* oe, bool enable_debug_utils,
|
||||||
bool enable_validation_layer);
|
bool enable_validation_layer);
|
||||||
|
|
||||||
bool ValidatePipelineCacheHeader(const VK_PIPELINE_CACHE_HEADER& header);
|
bool ValidatePipelineCacheHeader(const VK_PIPELINE_CACHE_HEADER& header, Error* error);
|
||||||
void FillPipelineCacheHeader(VK_PIPELINE_CACHE_HEADER* header);
|
void FillPipelineCacheHeader(VK_PIPELINE_CACHE_HEADER* header);
|
||||||
|
|
||||||
// Enable/disable debug message runtime.
|
// Enable/disable debug message runtime.
|
||||||
|
|
Loading…
Reference in a new issue