From 684a015c9852ede9d57103852217b428546fe472 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 26 Jun 2023 05:10:16 +0200 Subject: [PATCH] vulkan: Add a pipeline cache Make the display handle the cache, because we only need one. We store the cache in $CACHE_DIR/gtk-4.0/vulkan-pipeline-cache/$UUID.$VERSION so we regenerate caches for each different device (different UUID) and each different driver version. We also keep track of the etag of the cache file, so if 2 different applications update the cache, we can detect that. Vulkan allows merging caches, so the 2nd app reloads the new cache file and merges it into its cache before saving. --- gdk/gdkdisplayprivate.h | 4 + gdk/gdkvulkancontext.c | 222 +++++++++++++++++++++++++++++++++ gdk/gdkvulkancontextprivate.h | 3 + gsk/vulkan/gskvulkanpipeline.c | 6 +- 4 files changed, 234 insertions(+), 1 deletion(-) diff --git a/gdk/gdkdisplayprivate.h b/gdk/gdkdisplayprivate.h index 8c2ae867b3..72353cf207 100644 --- a/gdk/gdkdisplayprivate.h +++ b/gdk/gdkdisplayprivate.h @@ -100,6 +100,10 @@ struct _GdkDisplay VkDevice vk_device; VkQueue vk_queue; uint32_t vk_queue_family_index; + VkPipelineCache vk_pipeline_cache; + gsize vk_pipeline_cache_size; + char *vk_pipeline_cache_etag; + guint vk_save_pipeline_cache_source; guint vulkan_refcount; #endif /* GDK_RENDERING_VULKAN */ diff --git a/gdk/gdkvulkancontext.c b/gdk/gdkvulkancontext.c index a319c9c5dc..541104928c 100644 --- a/gdk/gdkvulkancontext.c +++ b/gdk/gdkvulkancontext.c @@ -931,6 +931,216 @@ gdk_vulkan_context_get_queue_family_index (GdkVulkanContext *context) return gdk_draw_context_get_display (GDK_DRAW_CONTEXT (context))->vk_queue_family_index; } +static char * +gdk_vulkan_get_pipeline_cache_dirname (void) +{ + return g_build_filename (g_get_user_cache_dir (), "gtk-4.0", "vulkan-pipeline-cache", NULL); +} + +static GFile * +gdk_vulkan_get_pipeline_cache_file (GdkDisplay *display) +{ + VkPhysicalDeviceProperties props; + char *dirname, *basename, *path; + GFile *result; + + vkGetPhysicalDeviceProperties (display->vk_physical_device, &props); + + dirname = gdk_vulkan_get_pipeline_cache_dirname (); + basename = g_strdup_printf ("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x" + "-%02x%02x%02x%02x%02x%02x.%u", + props.pipelineCacheUUID[0], props.pipelineCacheUUID[1], + props.pipelineCacheUUID[2], props.pipelineCacheUUID[3], + props.pipelineCacheUUID[4], props.pipelineCacheUUID[5], + props.pipelineCacheUUID[6], props.pipelineCacheUUID[7], + props.pipelineCacheUUID[8], props.pipelineCacheUUID[9], + props.pipelineCacheUUID[10], props.pipelineCacheUUID[11], + props.pipelineCacheUUID[12], props.pipelineCacheUUID[13], + props.pipelineCacheUUID[14], props.pipelineCacheUUID[15], + props.driverVersion); + + path = g_build_filename (dirname, basename, NULL); + result = g_file_new_for_path (path); + + g_free (path); + g_free (basename); + g_free (dirname); + + return result; +} + +static VkPipelineCache +gdk_display_load_pipeline_cache (GdkDisplay *display) +{ + GError *error = NULL; + VkPipelineCache result; + GFile *cache_file; + char *etag, *data; + gsize size; + + cache_file = gdk_vulkan_get_pipeline_cache_file (display); + if (!g_file_load_contents (cache_file, NULL, &data, &size, &etag, &error)) + { + GDK_DEBUG (VULKAN, "failed to load Vulkan pipeline cache file '%s': %s\n", + g_file_peek_path (cache_file), error->message); + g_object_unref (cache_file); + g_clear_error (&error); + return VK_NULL_HANDLE; + } + + if (GDK_VK_CHECK (vkCreatePipelineCache, display->vk_device, + &(VkPipelineCacheCreateInfo) { + .sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, + .initialDataSize = size, + .pInitialData = data, + }, + NULL, + &result) != VK_SUCCESS) + result = VK_NULL_HANDLE; + + g_free (data); + g_free (display->vk_pipeline_cache_etag); + display->vk_pipeline_cache_etag = etag; + display->vk_pipeline_cache_size = size; + + return result; +} + +static gboolean +gdk_vulkan_save_pipeline_cache (GdkDisplay *display) +{ + GError *error = NULL; + VkDevice device; + VkPipelineCache cache; + GFile *file; + char *path; + size_t size; + char *data, *etag; + + device = display->vk_device; + cache = display->vk_pipeline_cache; + + GDK_VK_CHECK (vkGetPipelineCacheData, device, cache, &size, NULL); + if (size == 0) + return TRUE; + + if (size == display->vk_pipeline_cache_size) + { + GDK_DEBUG (VULKAN, "pipeline cache size (%zu bytes) unchanged, skipping save", size); + return TRUE; + } + + + data = g_malloc (size); + if (GDK_VK_CHECK (vkGetPipelineCacheData, device, cache, &size, data) != VK_SUCCESS) + { + g_free (data); + return FALSE; + } + + path = gdk_vulkan_get_pipeline_cache_dirname (); + if (g_mkdir_with_parents (path, 0755) != 0) + { + g_warning_once ("Failed to create pipeline cache directory"); + g_free (path); + return FALSE; + } + g_free (path); + + file = gdk_vulkan_get_pipeline_cache_file (display); + + GDK_DEBUG (VULKAN, "Saving pipeline cache to %s", g_file_peek_path (file)); + + if (!g_file_replace_contents (file, + data, + size, + display->vk_pipeline_cache_etag, + FALSE, + 0, + &etag, + NULL, + &error)) + { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WRONG_ETAG)) + { + VkPipelineCache new_cache; + + GDK_DEBUG (VULKAN, "Pipeline cache file modified, merging into current"); + new_cache = gdk_display_load_pipeline_cache (display); + if (new_cache) + { + GDK_VK_CHECK (vkMergePipelineCaches, device, cache, 1, &new_cache); + vkDestroyPipelineCache (device, new_cache, NULL); + } + else + { + g_clear_pointer (&display->vk_pipeline_cache_etag, g_free); + } + g_clear_error (&error); + g_object_unref (file); + + /* try again */ + return gdk_vulkan_save_pipeline_cache (display); + } + + g_warning ("Failed to save pipeline cache: %s", error->message); + g_clear_error (&error); + g_object_unref (file); + return FALSE; + } + + g_object_unref (file); + g_free (display->vk_pipeline_cache_etag); + display->vk_pipeline_cache_etag = etag; + + return TRUE; +} + +static gboolean +gdk_vulkan_save_pipeline_cache_cb (gpointer data) +{ + GdkDisplay *display = data; + + gdk_vulkan_save_pipeline_cache (display); + + display->vk_save_pipeline_cache_source = 0; + return G_SOURCE_REMOVE; +} + +void +gdk_vulkan_context_pipeline_cache_updated (GdkVulkanContext *self) +{ + GdkDisplay *display = gdk_draw_context_get_display (GDK_DRAW_CONTEXT (self)); + + g_clear_handle_id (&display->vk_save_pipeline_cache_source, g_source_remove); + display->vk_save_pipeline_cache_source = g_timeout_add_seconds_full (G_PRIORITY_DEFAULT_IDLE - 10, + 10, /* random choice that is not now */ + gdk_vulkan_save_pipeline_cache_cb, + display, + NULL); +} + +static void +gdk_display_create_pipeline_cache (GdkDisplay *display) +{ + display->vk_pipeline_cache = gdk_display_load_pipeline_cache (display); + + GDK_VK_CHECK (vkCreatePipelineCache, display->vk_device, + &(VkPipelineCacheCreateInfo) { + .sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, + }, + NULL, + &display->vk_pipeline_cache); +} + +VkPipelineCache +gdk_vulkan_context_get_pipeline_cache (GdkVulkanContext *self) +{ + g_return_val_if_fail (GDK_IS_VULKAN_CONTEXT (self), NULL); + + return gdk_draw_context_get_display (GDK_DRAW_CONTEXT (self))->vk_pipeline_cache; +} + /** * gdk_vulkan_context_get_image_format: * @context: a `GdkVulkanContext` @@ -1381,6 +1591,8 @@ gdk_display_create_vulkan_instance (GdkDisplay *display, return FALSE; } + gdk_display_create_pipeline_cache (display); + return TRUE; } @@ -1409,6 +1621,16 @@ gdk_display_unref_vulkan (GdkDisplay *display) if (display->vulkan_refcount > 0) return; + if (display->vk_save_pipeline_cache_source) + { + gdk_vulkan_save_pipeline_cache_cb (display); + g_assert (display->vk_save_pipeline_cache_source == 0); + } + vkDestroyPipelineCache (display->vk_device, display->vk_pipeline_cache, NULL); + display->vk_device = VK_NULL_HANDLE; + g_clear_pointer (&display->vk_pipeline_cache_etag, g_free); + display->vk_pipeline_cache_size = 0; + vkDestroyDevice (display->vk_device, NULL); display->vk_device = VK_NULL_HANDLE; if (display->vk_debug_callback != VK_NULL_HANDLE) diff --git a/gdk/gdkvulkancontextprivate.h b/gdk/gdkvulkancontextprivate.h index cb06efd73c..479dc86afc 100644 --- a/gdk/gdkvulkancontextprivate.h +++ b/gdk/gdkvulkancontextprivate.h @@ -73,6 +73,9 @@ gboolean gdk_display_ref_vulkan (GdkDisp GError **error); void gdk_display_unref_vulkan (GdkDisplay *display); +VkPipelineCache gdk_vulkan_context_get_pipeline_cache (GdkVulkanContext *self); +void gdk_vulkan_context_pipeline_cache_updated (GdkVulkanContext *self); + GdkMemoryFormat gdk_vulkan_context_get_offscreen_format (GdkVulkanContext *context, GdkMemoryDepth depth); diff --git a/gsk/vulkan/gskvulkanpipeline.c b/gsk/vulkan/gskvulkanpipeline.c index 62abea88f0..ca4699e699 100644 --- a/gsk/vulkan/gskvulkanpipeline.c +++ b/gsk/vulkan/gskvulkanpipeline.c @@ -5,6 +5,8 @@ #include "gskvulkanpushconstantsprivate.h" #include "gskvulkanshaderprivate.h" +#include "gdk/gdkvulkancontextprivate.h" + #include typedef struct _GskVulkanPipelinePrivate GskVulkanPipelinePrivate; @@ -87,7 +89,7 @@ gsk_vulkan_pipeline_new (GType pipeline_type, priv->vertex_stride = vertex_input_state->pVertexBindingDescriptions[0].stride; GSK_VK_CHECK (vkCreateGraphicsPipelines, device, - VK_NULL_HANDLE, + gdk_vulkan_context_get_pipeline_cache (context), 1, &(VkGraphicsPipelineCreateInfo) { .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, @@ -160,6 +162,8 @@ gsk_vulkan_pipeline_new (GType pipeline_type, NULL, &priv->pipeline); + gdk_vulkan_context_pipeline_cache_updated (context); + return self; } -- 2.30.2