Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Vulkan pipeline caching #76348

Merged
merged 1 commit into from
May 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/config/project_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1354,6 +1354,7 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF("rendering/rendering_device/staging_buffer/block_size_kb", 256);
GLOBAL_DEF("rendering/rendering_device/staging_buffer/max_size_mb", 128);
GLOBAL_DEF("rendering/rendering_device/staging_buffer/texture_upload_region_size_px", 64);
GLOBAL_DEF("rendering/rendering_device/pipeline_cache/save_chunk_size_mb", 3.0);
GLOBAL_DEF("rendering/rendering_device/vulkan/max_descriptors_per_pool", 64);

GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "rendering/textures/canvas_textures/default_texture_filter", PROPERTY_HINT_ENUM, "Nearest,Linear,Linear Mipmap,Nearest Mipmap"), 1);
Expand Down
3 changes: 3 additions & 0 deletions doc/classes/ProjectSettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2305,6 +2305,9 @@
<member name="rendering/rendering_device/driver.windows" type="String" setter="" getter="" default="&quot;vulkan&quot;">
Windows override for [member rendering/rendering_device/driver].
</member>
<member name="rendering/rendering_device/pipeline_cache/save_chunk_size_mb" type="float" setter="" getter="" default="3.0">
Determines at which interval pipeline cache is saved to disk. The lower the value, the more often it is saved.
</member>
<member name="rendering/rendering_device/staging_buffer/block_size_kb" type="int" setter="" getter="" default="256">
</member>
<member name="rendering/rendering_device/staging_buffer/max_size_mb" type="int" setter="" getter="" default="128">
Expand Down
138 changes: 136 additions & 2 deletions drivers/vulkan/rendering_device_vulkan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

#include "core/config/project_settings.h"
#include "core/io/compression.h"
#include "core/io/dir_access.h"
#include "core/io/file_access.h"
#include "core/io/marshalls.h"
#include "core/os/os.h"
Expand Down Expand Up @@ -6397,9 +6398,13 @@ RID RenderingDeviceVulkan::render_pipeline_create(RID p_shader, FramebufferForma
graphics_pipeline_create_info.basePipelineIndex = 0;

RenderPipeline pipeline;
VkResult err = vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &graphics_pipeline_create_info, nullptr, &pipeline.pipeline);
VkResult err = vkCreateGraphicsPipelines(device, pipelines_cache.cache_object, 1, &graphics_pipeline_create_info, nullptr, &pipeline.pipeline);
ERR_FAIL_COND_V_MSG(err, RID(), "vkCreateGraphicsPipelines failed with error " + itos(err) + " for shader '" + shader->name + "'.");

if (pipelines_cache.cache_object != VK_NULL_HANDLE) {
_update_pipeline_cache();
}

pipeline.set_formats = shader->set_formats;
pipeline.push_constant_stages_mask = shader->push_constant.vk_stages_mask;
pipeline.pipeline_layout = shader->pipeline_layout;
Expand Down Expand Up @@ -6512,9 +6517,13 @@ RID RenderingDeviceVulkan::compute_pipeline_create(RID p_shader, const Vector<Pi
}

ComputePipeline pipeline;
VkResult err = vkCreateComputePipelines(device, VK_NULL_HANDLE, 1, &compute_pipeline_create_info, nullptr, &pipeline.pipeline);
VkResult err = vkCreateComputePipelines(device, pipelines_cache.cache_object, 1, &compute_pipeline_create_info, nullptr, &pipeline.pipeline);
ERR_FAIL_COND_V_MSG(err, RID(), "vkCreateComputePipelines failed with error " + itos(err) + ".");

if (pipelines_cache.cache_object != VK_NULL_HANDLE) {
_update_pipeline_cache();
}

pipeline.set_formats = shader->set_formats;
pipeline.push_constant_stages_mask = shader->push_constant.vk_stages_mask;
pipeline.pipeline_layout = shader->pipeline_layout;
Expand Down Expand Up @@ -8957,6 +8966,128 @@ void RenderingDeviceVulkan::initialize(VulkanContext *p_context, bool p_local_de
draw_list_split = false;

compute_list = nullptr;
_load_pipeline_cache();
print_verbose(vformat("Startup PSO cache (%.1f MiB)", pipelines_cache.buffer.size() / (1024.0f * 1024.0f)));
VkPipelineCacheCreateInfo cache_info = {};
cache_info.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
cache_info.pNext = nullptr;
cache_info.flags = 0;
cache_info.initialDataSize = pipelines_cache.buffer.size();
cache_info.pInitialData = pipelines_cache.buffer.ptr();
VkResult err = vkCreatePipelineCache(device, &cache_info, nullptr, &pipelines_cache.cache_object);

if (err != VK_SUCCESS) {
WARN_PRINT("vkCreatePipelinecache failed with error " + itos(err) + ".");
}
}

void RenderingDeviceVulkan::_load_pipeline_cache() {
if (!DirAccess::exists("user://vulkan/")) {
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_USERDATA);

if (da.is_valid()) {
da->make_dir_recursive("user://vulkan/");
}
}

if (FileAccess::exists("user://vulkan/pipelines.cache")) {
Error file_error;
Vector<uint8_t> file_data = FileAccess::get_file_as_bytes("user://vulkan/pipelines.cache", &file_error);
if (file_error != OK || file_data.size() <= (int)sizeof(PipelineCacheHeader)) {
WARN_PRINT("Invalid/corrupt pipelines cache.");
return;
}
PipelineCacheHeader header = {};
memcpy((char *)&header, file_data.ptr(), sizeof(PipelineCacheHeader));
if (header.magic != 868 + VK_PIPELINE_CACHE_HEADER_VERSION_ONE) {
WARN_PRINT("Invalid pipelines cache magic number.");
return;
}
pipelines_cache.buffer.resize(file_data.size() - sizeof(PipelineCacheHeader));
memcpy(pipelines_cache.buffer.ptrw(), file_data.ptr() + sizeof(PipelineCacheHeader), pipelines_cache.buffer.size());
VkPhysicalDeviceProperties props;
vkGetPhysicalDeviceProperties(context->get_physical_device(), &props);
bool invalid_uuid = false;
for (size_t i = 0; i < VK_UUID_SIZE; i++) {
if (header.uuid[i] != props.pipelineCacheUUID[i]) {
invalid_uuid = true;
break;
}
}
if (header.data_hash != hash_murmur3_buffer(pipelines_cache.buffer.ptr(), pipelines_cache.buffer.size()) || header.data_size != (uint32_t)pipelines_cache.buffer.size() || header.vendor_id != props.vendorID || header.device_id != props.deviceID || header.driver_abi != sizeof(void *) || invalid_uuid) {
WARN_PRINT("Invalid pipelines cache header.");
pipelines_cache.current_size = 0;
pipelines_cache.buffer.clear();
} else {
pipelines_cache.current_size = pipelines_cache.buffer.size();
}
}
}

void RenderingDeviceVulkan::_update_pipeline_cache(bool p_closing) {
size_t pso_blob_size = 0;
float save_interval = GLOBAL_GET("rendering/rendering_device/pipeline_cache/save_chunk_size_mb");
VkResult vr = vkGetPipelineCacheData(device, pipelines_cache.cache_object, &pso_blob_size, nullptr);
ERR_FAIL_COND(vr);
size_t difference = (pso_blob_size - pipelines_cache.current_size) / (1024 * 1024);
if (p_closing && Engine::get_singleton()->is_editor_hint()) {
// This is mostly for the editor to check if after playing the game, game's pipeline cache size still matches with editor's cache.
_load_pipeline_cache();
if (pipelines_cache.current_size > pso_blob_size) {
pso_blob_size = pipelines_cache.current_size;
if (pipelines_cache_save_task != WorkerThreadPool::INVALID_TASK_ID || !WorkerThreadPool::get_singleton()->is_task_completed(pipelines_cache_save_task)) {
WorkerThreadPool::get_singleton()->wait_for_task_completion(pipelines_cache_save_task);
}
}
}
if (pso_blob_size == pipelines_cache.current_size) {
return;
} else if (difference < save_interval && !p_closing) {
return;
}

if (p_closing) {
if (pipelines_cache_save_task == WorkerThreadPool::INVALID_TASK_ID || WorkerThreadPool::get_singleton()->is_task_completed(pipelines_cache_save_task)) {
pipelines_cache_save_task = WorkerThreadPool::get_singleton()->add_template_task(this, &RenderingDeviceVulkan::_save_pipeline_cache_threaded, pso_blob_size, false, "PipelineCacheSave");
WorkerThreadPool::get_singleton()->wait_for_task_completion(pipelines_cache_save_task);
} else {
WorkerThreadPool::get_singleton()->wait_for_task_completion(pipelines_cache_save_task);
pipelines_cache_save_task = WorkerThreadPool::get_singleton()->add_template_task(this, &RenderingDeviceVulkan::_save_pipeline_cache_threaded, pso_blob_size, false, "PipelineCacheSave");
WorkerThreadPool::get_singleton()->wait_for_task_completion(pipelines_cache_save_task);
}
} else {
if (pipelines_cache_save_task == WorkerThreadPool::INVALID_TASK_ID || WorkerThreadPool::get_singleton()->is_task_completed(pipelines_cache_save_task)) {
pipelines_cache_save_task = WorkerThreadPool::get_singleton()->add_template_task(this, &RenderingDeviceVulkan::_save_pipeline_cache_threaded, pso_blob_size, false, "PipelineCacheSave");
}
}
}

void RenderingDeviceVulkan::_save_pipeline_cache_threaded(size_t p_pso_blob_size) {
pipelines_cache.current_size = p_pso_blob_size;
pipelines_cache.buffer.clear();
pipelines_cache.buffer.resize(p_pso_blob_size);
VkResult vr = vkGetPipelineCacheData(device, pipelines_cache.cache_object, &p_pso_blob_size, pipelines_cache.buffer.ptrw());
ERR_FAIL_COND(vr);
print_verbose(vformat("Updated PSO cache (%.1f MiB)", p_pso_blob_size / (1024.0f * 1024.0f)));

VkPhysicalDeviceProperties props;
vkGetPhysicalDeviceProperties(context->get_physical_device(), &props);
PipelineCacheHeader header = {};
header.magic = 868 + VK_PIPELINE_CACHE_HEADER_VERSION_ONE;
header.data_size = pipelines_cache.buffer.size();
header.data_hash = hash_murmur3_buffer(pipelines_cache.buffer.ptr(), pipelines_cache.buffer.size());
header.device_id = props.deviceID;
header.vendor_id = props.vendorID;
header.driver_version = props.driverVersion;
for (size_t i = 0; i < VK_UUID_SIZE; i++) {
header.uuid[i] = props.pipelineCacheUUID[i];
}
header.driver_abi = sizeof(void *);
Ref<FileAccess> f = FileAccess::open("user://vulkan/pipelines.cache", FileAccess::WRITE, nullptr);
if (f.is_valid()) {
f->store_buffer((const uint8_t *)&header, sizeof(PipelineCacheHeader));
f->store_buffer(pipelines_cache.buffer);
}
}

template <class T>
Expand Down Expand Up @@ -9332,6 +9463,9 @@ void RenderingDeviceVulkan::finalize() {
vkDestroyCommandPool(device, frames[i].command_pool, nullptr);
vkDestroyQueryPool(device, frames[i].timestamp_pool, nullptr);
}
_update_pipeline_cache(true);

vkDestroyPipelineCache(device, pipelines_cache.cache_object, nullptr);

for (int i = 0; i < split_draw_list_allocators.size(); i++) {
vkDestroyCommandPool(device, split_draw_list_allocators[i].command_pool, nullptr);
Expand Down
26 changes: 26 additions & 0 deletions drivers/vulkan/rendering_device_vulkan.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#ifndef RENDERING_DEVICE_VULKAN_H
#define RENDERING_DEVICE_VULKAN_H

#include "core/object/worker_thread_pool.h"
#include "core/os/thread_safe.h"
#include "core/templates/local_vector.h"
#include "core/templates/oa_hash_map.h"
Expand Down Expand Up @@ -792,6 +793,31 @@ class RenderingDeviceVulkan : public RenderingDevice {

RID_Owner<RenderPipeline, true> render_pipeline_owner;

struct PipelineCacheHeader {
uint32_t magic;
uint32_t data_size;
uint64_t data_hash;
uint32_t vendor_id;
uint32_t device_id;
uint32_t driver_version;
uint8_t uuid[VK_UUID_SIZE];
uint8_t driver_abi;
};

struct PipelineCache {
size_t current_size = 0;
Vector<uint8_t> buffer;
VkPipelineCache cache_object = VK_NULL_HANDLE;
};

PipelineCache pipelines_cache;

WorkerThreadPool::TaskID pipelines_cache_save_task = WorkerThreadPool::INVALID_TASK_ID;

void _load_pipeline_cache();
void _update_pipeline_cache(bool p_closing = false);
void _save_pipeline_cache_threaded(size_t pso_blob_size);

struct ComputePipeline {
RID shader;
Vector<uint32_t> set_formats;
Expand Down