#include "config.h"

#include "gskvulkandeviceprivate.h"

#include "gskgpuglobalsopprivate.h"
#include "gskgpushaderopprivate.h"
#include "gskvulkanbufferprivate.h"
#include "gskvulkanimageprivate.h"

#include "gdk/gdkdisplayprivate.h"
#include "gdk/gdkvulkancontextprivate.h"

struct _GskVulkanDevice
{
  GskGpuDevice parent_instance;

  GskVulkanAllocator *allocators[VK_MAX_MEMORY_TYPES];
  GskVulkanAllocator *external_allocator;
  GdkVulkanFeatures features;

  guint max_immutable_samplers;
  guint max_samplers;
  guint max_buffers;

  GHashTable *conversion_cache;
  GHashTable *render_pass_cache;
  GHashTable *pipeline_layouts;
  GskVulkanPipelineLayout *pipeline_layout_cache;

  VkCommandPool vk_command_pool;
  VkSampler vk_samplers[GSK_GPU_SAMPLER_N_SAMPLERS];
};

struct _GskVulkanDeviceClass
{
  GskGpuDeviceClass parent_class;
};

G_DEFINE_TYPE (GskVulkanDevice, gsk_vulkan_device, GSK_TYPE_GPU_DEVICE)

typedef struct _ConversionCacheEntry ConversionCacheEntry;
typedef struct _PipelineCacheKey PipelineCacheKey;
typedef struct _RenderPassCacheKey RenderPassCacheKey;
typedef struct _GskVulkanPipelineLayoutSetup GskVulkanPipelineLayoutSetup;

struct _GskVulkanPipelineLayoutSetup
{
  gsize n_buffers;
  gsize n_samplers;
  gsize n_immutable_samplers;
  VkSampler *immutable_samplers;
};

struct _GskVulkanPipelineLayout
{
  gint ref_count;

  VkDescriptorSetLayout vk_buffer_set_layout;
  VkDescriptorSetLayout vk_image_set_layout;
  VkPipelineLayout vk_pipeline_layout;
  GHashTable *pipeline_cache;

  GskVulkanPipelineLayoutSetup setup;
  VkSampler samplers[];
};

struct _ConversionCacheEntry
{
  VkFormat vk_format;

  /* actual data */
  VkSamplerYcbcrConversion vk_conversion;
  VkSampler vk_sampler;
};

struct _PipelineCacheKey
{
  const GskGpuShaderOpClass *op_class;
  guint32 variation;
  GskGpuShaderClip clip;
  GskGpuBlend blend;
  VkFormat format;
};

struct _RenderPassCacheKey
{
  VkFormat format;
  VkImageLayout from_layout;
  VkImageLayout to_layout;
};

static guint
conversion_cache_entry_hash (gconstpointer data)
{
  const ConversionCacheEntry *key = data;

  return key->vk_format;
}

static gboolean
conversion_cache_entry_equal (gconstpointer a,
                              gconstpointer b)
{
  const ConversionCacheEntry *keya = a;
  const ConversionCacheEntry *keyb = b;

  return keya->vk_format == keyb->vk_format;
}

static guint
pipeline_cache_key_hash (gconstpointer data)
{
  const PipelineCacheKey *key = data;

  return GPOINTER_TO_UINT (key->op_class) ^
         key->clip ^
         (key->variation << 2) ^
         (key->blend << 6) ^
         (key->format << 8);
}

static gboolean
pipeline_cache_key_equal (gconstpointer a,
                          gconstpointer b)
{
  const PipelineCacheKey *keya = a;
  const PipelineCacheKey *keyb = b;

  return keya->op_class == keyb->op_class &&
         keya->variation == keyb->variation &&
         keya->clip == keyb->clip &&
         keya->blend == keyb->blend &&
         keya->format == keyb->format;
}

static guint
render_pass_cache_key_hash (gconstpointer data)
{
  const RenderPassCacheKey *key = data;

  return (key->from_layout << 20) ^
         (key->to_layout << 16) ^
         (key->format);
}

static gboolean
render_pass_cache_key_equal (gconstpointer a,
                             gconstpointer b)
{
  const RenderPassCacheKey *keya = a;
  const RenderPassCacheKey *keyb = b;

  return keya->from_layout == keyb->from_layout &&
         keya->to_layout == keyb->to_layout &&
         keya->format == keyb->format;
}

static GskVulkanPipelineLayout *
gsk_vulkan_pipeline_layout_new (GskVulkanDevice                    *self,
                                const GskVulkanPipelineLayoutSetup *setup)
{
  GskVulkanPipelineLayout *layout;
  GdkDisplay *display;
  gboolean descriptor_indexing;
  
  descriptor_indexing = gsk_vulkan_device_has_feature (self, GDK_VULKAN_FEATURE_DESCRIPTOR_INDEXING);

  layout = g_malloc (sizeof (GskVulkanPipelineLayout) + setup->n_immutable_samplers * sizeof (VkSampler));
  layout->ref_count = 1;

  layout->setup = *setup;
  if (setup->n_immutable_samplers)
    memcpy (layout->samplers, setup->immutable_samplers, setup->n_immutable_samplers * sizeof (VkSampler));
  layout->setup.immutable_samplers = layout->samplers;
  layout->pipeline_cache = g_hash_table_new (pipeline_cache_key_hash, pipeline_cache_key_equal);

  display = gsk_gpu_device_get_display (GSK_GPU_DEVICE (self));

  GSK_VK_CHECK (vkCreateDescriptorSetLayout, display->vk_device,
                                             &(VkDescriptorSetLayoutCreateInfo) {
                                                 .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
                                                 .bindingCount = 2,
                                                 .flags = 0,
                                                 .pBindings = (VkDescriptorSetLayoutBinding[2]) {
                                                     {
                                                         .binding = 0,
                                                         .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
                                                         .descriptorCount = MAX (1, layout->setup.n_immutable_samplers),
                                                         .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
                                                         .pImmutableSamplers = layout->setup.n_immutable_samplers
                                                                               ? layout->setup.immutable_samplers
                                                                               : (VkSampler[1]) {
                                                                                   gsk_vulkan_device_get_vk_sampler (self, GSK_GPU_SAMPLER_DEFAULT)
                                                                               },
                                                     },
                                                     {
                                                         .binding = 1,
                                                         .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
                                                         .descriptorCount = layout->setup.n_samplers,
                                                         .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT
                                                     }
                                                 },
                                                 .pNext = !descriptor_indexing ? NULL : &(VkDescriptorSetLayoutBindingFlagsCreateInfo) {
                                                   .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO,
                                                   .bindingCount = 2,
                                                   .pBindingFlags = (VkDescriptorBindingFlags[2]) {
                                                     VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT,
                                                     VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT
                                                     | VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT,
                                                   },
                                                 }
                                             },
                                             NULL,
                                             &layout->vk_image_set_layout);

  GSK_VK_CHECK (vkCreateDescriptorSetLayout, display->vk_device,
                                             &(VkDescriptorSetLayoutCreateInfo) {
                                                 .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
                                                 .bindingCount = 1,
                                                 .flags = 0,
                                                 .pBindings = (VkDescriptorSetLayoutBinding[1]) {
                                                     {
                                                         .binding = 0,
                                                         .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
                                                         .descriptorCount = layout->setup.n_buffers,
                                                         .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT
                                                     },
                                                 },
                                                 .pNext = !descriptor_indexing ? NULL : &(VkDescriptorSetLayoutBindingFlagsCreateInfo) {
                                                   .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO,
                                                   .bindingCount = 1,
                                                   .pBindingFlags = (VkDescriptorBindingFlags[1]) {
                                                     VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT
                                                     | VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT,
                                                   },
                                                 }
                                             },
                                             NULL,
                                             &layout->vk_buffer_set_layout);

  GSK_VK_CHECK (vkCreatePipelineLayout, display->vk_device,
                                        &(VkPipelineLayoutCreateInfo) {
                                            .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
                                            .setLayoutCount = 2,
                                            .pSetLayouts = (VkDescriptorSetLayout[2]) {
                                                layout->vk_image_set_layout,
                                                layout->vk_buffer_set_layout,
                                            },
                                            .pushConstantRangeCount = 1,
                                            .pPushConstantRanges = (VkPushConstantRange[1]) {
                                                {
                                                    .stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
                                                    .offset = 0,
                                                    .size = sizeof (GskGpuGlobalsInstance)
                                                }
                                            }
                                        },
                                        NULL,
                                        &layout->vk_pipeline_layout);

  g_hash_table_insert (self->pipeline_layouts, &layout->setup, layout);

  return layout;
}

static void
gsk_vulkan_pipeline_layout_unref (GskVulkanDevice         *self,
                                  GskVulkanPipelineLayout *layout)
{
  GdkDisplay *display;
  GHashTableIter iter;
  gpointer key, value;

  layout->ref_count--;
  if (layout->ref_count)
    return;

  if (!g_hash_table_remove (self->pipeline_layouts, &layout->setup))
    {
      g_assert_not_reached ();
    }

  display = gsk_gpu_device_get_display (GSK_GPU_DEVICE (self));

  vkDestroyDescriptorSetLayout (display->vk_device,
                                layout->vk_image_set_layout,
                                NULL);
  vkDestroyDescriptorSetLayout (display->vk_device,
                                layout->vk_buffer_set_layout,
                                NULL);

  vkDestroyPipelineLayout (display->vk_device,
                           layout->vk_pipeline_layout,
                           NULL);

  g_hash_table_iter_init (&iter, layout->pipeline_cache);
  while (g_hash_table_iter_next (&iter, &key, &value))
    {
      g_free (key);
      vkDestroyPipeline (display->vk_device, value, NULL);
    }
  g_hash_table_unref (layout->pipeline_cache);

  g_free (layout);
}

static void
gsk_vulkan_pipeline_layout_ref (GskVulkanDevice         *self,
                                GskVulkanPipelineLayout *layout)
{
  layout->ref_count++;
}

static guint
gsk_vulkan_pipeline_layout_setup_hash (gconstpointer data)
{
  const GskVulkanPipelineLayoutSetup *setup = data;
  guint result;
  gsize i;

  result = (setup->n_buffers << 23) |
           (setup->n_samplers << 7) |
           setup->n_immutable_samplers;

  for (i = 0; i < setup->n_immutable_samplers; i++)
    {
      result = (result << 13) ^
               GPOINTER_TO_SIZE (setup->immutable_samplers[i]) ^
               (GPOINTER_TO_SIZE (setup->immutable_samplers[i]) >> 32);
    }

  return result;
}

static gboolean
gsk_vulkan_pipeline_layout_setup_equal (gconstpointer a,
                                        gconstpointer b)
{
  const GskVulkanPipelineLayoutSetup *setupa = a;
  const GskVulkanPipelineLayoutSetup *setupb = b;
  gsize i;

  if (setupa->n_buffers != setupb->n_buffers ||
      setupa->n_samplers != setupb->n_samplers ||
      setupa->n_immutable_samplers != setupb->n_immutable_samplers)
    return FALSE;

  for (i = 0; i < setupa->n_immutable_samplers; i++)
    {
      if (setupa->immutable_samplers[i] != setupb->immutable_samplers[i])
        return FALSE;
    }

  return TRUE;
}

static GskGpuImage *
gsk_vulkan_device_create_offscreen_image (GskGpuDevice   *device,
                                          gboolean        with_mipmap,
                                          GdkMemoryDepth  depth,
                                          gsize           width,
                                          gsize           height)
{
  GskVulkanDevice *self = GSK_VULKAN_DEVICE (device);

  return gsk_vulkan_image_new_for_offscreen (self,
                                             with_mipmap,
                                             gdk_memory_depth_get_format (depth),
                                             width,
                                             height);
}

static GskGpuImage *
gsk_vulkan_device_create_atlas_image (GskGpuDevice *device,
                                      gsize         width,
                                      gsize         height)
{
  GskVulkanDevice *self = GSK_VULKAN_DEVICE (device);

  return gsk_vulkan_image_new_for_atlas (self,
                                         width,
                                         height);
}

static GskGpuImage *
gsk_vulkan_device_create_upload_image (GskGpuDevice    *device,
                                       gboolean         with_mipmap,
                                       GdkMemoryFormat  format,
                                       gsize            width,
                                       gsize            height)
{
  GskVulkanDevice *self = GSK_VULKAN_DEVICE (device);

  return gsk_vulkan_image_new_for_upload (self,
                                          with_mipmap,
                                          format,
                                          width,
                                          height);
}

static GskGpuImage *
gsk_vulkan_device_create_download_image (GskGpuDevice   *device,
                                         GdkMemoryDepth  depth,
                                         gsize           width,
                                         gsize           height)
{
  GskVulkanDevice *self = GSK_VULKAN_DEVICE (device);
  GskGpuImage *image;

#ifdef HAVE_DMABUF
  image = gsk_vulkan_image_new_dmabuf (self,
                                       gdk_memory_depth_get_format (depth),
                                       width,
                                       height);
  if (image != NULL)
    return image;
#endif

  image = gsk_vulkan_image_new_for_offscreen (self,
                                              FALSE,
                                              gdk_memory_depth_get_format (depth),
                                              width,
                                              height);

  return image;
}

static void
gsk_vulkan_device_make_current (GskGpuDevice *device)
{
}

static void
gsk_vulkan_device_finalize (GObject *object)
{
  GskVulkanDevice *self = GSK_VULKAN_DEVICE (object);
  GskGpuDevice *device = GSK_GPU_DEVICE (self);
  GdkDisplay *display;
  GHashTableIter iter;
  gpointer key, value;
  gsize i;

  g_object_steal_data (G_OBJECT (gsk_gpu_device_get_display (device)), "-gsk-vulkan-device");

  display = gsk_gpu_device_get_display (device);

  if (self->pipeline_layout_cache)
    gsk_vulkan_pipeline_layout_unref (self, self->pipeline_layout_cache);
  g_assert (g_hash_table_size (self->pipeline_layouts) == 0);
  g_hash_table_unref (self->pipeline_layouts);

  g_hash_table_iter_init (&iter, self->conversion_cache);
  while (g_hash_table_iter_next (&iter, &key, &value))
    {
      ConversionCacheEntry *entry = key;
      vkDestroySamplerYcbcrConversion (display->vk_device, entry->vk_conversion, NULL);
      vkDestroySampler (display->vk_device, entry->vk_sampler, NULL);
      g_free (key);
    }
  g_hash_table_unref (self->conversion_cache);

  g_hash_table_iter_init (&iter, self->render_pass_cache);
  while (g_hash_table_iter_next (&iter, &key, &value))
    {
      g_free (key);
      vkDestroyRenderPass (display->vk_device, value, NULL);
    }
  g_hash_table_unref (self->render_pass_cache);

  for (i = 0; i < G_N_ELEMENTS (self->vk_samplers); i++)
    {
      if (self->vk_samplers[i] != VK_NULL_HANDLE)
        vkDestroySampler (display->vk_device,
                          self->vk_samplers[i],
                          NULL);
    }

  vkDestroyCommandPool (display->vk_device,
                        self->vk_command_pool,
                        NULL);

  for (i = 0; i < VK_MAX_MEMORY_TYPES; i++)
    g_clear_pointer (&self->allocators[i], gsk_vulkan_allocator_unref);
  g_clear_pointer (&self->external_allocator, gsk_vulkan_allocator_unref);

  gdk_display_unref_vulkan (display);

  G_OBJECT_CLASS (gsk_vulkan_device_parent_class)->finalize (object);
}

static void
gsk_vulkan_device_class_init (GskVulkanDeviceClass *klass)
{
  GskGpuDeviceClass *gpu_device_class = GSK_GPU_DEVICE_CLASS (klass);
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  gpu_device_class->create_offscreen_image = gsk_vulkan_device_create_offscreen_image;
  gpu_device_class->create_atlas_image = gsk_vulkan_device_create_atlas_image;
  gpu_device_class->create_upload_image = gsk_vulkan_device_create_upload_image;
  gpu_device_class->create_download_image = gsk_vulkan_device_create_download_image;
  gpu_device_class->make_current = gsk_vulkan_device_make_current;

  object_class->finalize = gsk_vulkan_device_finalize;
}

static void
gsk_vulkan_device_init (GskVulkanDevice *self)
{
  self->conversion_cache = g_hash_table_new (conversion_cache_entry_hash, conversion_cache_entry_equal);
  self->render_pass_cache = g_hash_table_new (render_pass_cache_key_hash, render_pass_cache_key_equal);
  self->pipeline_layouts = g_hash_table_new (gsk_vulkan_pipeline_layout_setup_hash, gsk_vulkan_pipeline_layout_setup_equal);
}

static void
gsk_vulkan_device_create_vk_objects (GskVulkanDevice *self)
{
  GdkDisplay *display;

  display = gsk_gpu_device_get_display (GSK_GPU_DEVICE (self));

  GSK_VK_CHECK (vkCreateCommandPool, display->vk_device,
                                     &(const VkCommandPoolCreateInfo) {
                                         .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
                                         .queueFamilyIndex = display->vk_queue_family_index,
                                         .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
                                     },
                                     NULL,
                                     &self->vk_command_pool);
}

static void
gsk_vulkan_device_setup (GskVulkanDevice *self,
                         GdkDisplay      *display)
{
  VkPhysicalDeviceVulkan12Properties vk12_props = {
    .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_PROPERTIES,
  };
  VkPhysicalDeviceProperties2 vk_props = {
    .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
    .pNext = &vk12_props
  };

  vkGetPhysicalDeviceProperties2 (display->vk_physical_device, &vk_props);
  if (gsk_vulkan_device_has_feature (self, GDK_VULKAN_FEATURE_DESCRIPTOR_INDEXING))
    {
      self->max_buffers = vk12_props.maxPerStageDescriptorUpdateAfterBindUniformBuffers;
      self->max_samplers = vk12_props.maxPerStageDescriptorUpdateAfterBindSampledImages;
    }
  else
    {
      self->max_buffers = vk_props.properties.limits.maxPerStageDescriptorUniformBuffers;
      self->max_samplers = vk_props.properties.limits.maxPerStageDescriptorSampledImages;
    }
  if (!gsk_vulkan_device_has_feature (self, GDK_VULKAN_FEATURE_DYNAMIC_INDEXING) ||
      !gsk_vulkan_device_has_feature (self, GDK_VULKAN_FEATURE_NONUNIFORM_INDEXING))
    {
      /* These numbers can be improved in the shader sources by adding more
       * entries to the big if() ladders */
      self->max_buffers = MIN (self->max_buffers, 32);
      self->max_samplers = MIN (self->max_samplers, 32);
    }
  self->max_immutable_samplers = MIN (self->max_samplers / 3, 32);
  gsk_gpu_device_setup (GSK_GPU_DEVICE (self),
                        display,
                        vk_props.properties.limits.maxImageDimension2D);
}

GskGpuDevice *
gsk_vulkan_device_get_for_display (GdkDisplay  *display,
                                   GError     **error)
{
  GskVulkanDevice *self;

  self = g_object_get_data (G_OBJECT (display), "-gsk-vulkan-device");
  if (self)
    return GSK_GPU_DEVICE (g_object_ref (self));

  if (!gdk_display_init_vulkan (display, error))
    return NULL;

  self = g_object_new (GSK_TYPE_VULKAN_DEVICE, NULL);
  self->features = display->vulkan_features;

  gsk_vulkan_device_setup (self, display);

  gsk_vulkan_device_create_vk_objects (self);

  g_object_set_data (G_OBJECT (display), "-gsk-vulkan-device", self);

  return GSK_GPU_DEVICE (self);
}

gsize
gsk_vulkan_device_get_max_immutable_samplers (GskVulkanDevice *self)
{
  return self->max_immutable_samplers;
}

gsize
gsk_vulkan_device_get_max_samplers (GskVulkanDevice *self)
{
  return self->max_samplers;
}

gsize
gsk_vulkan_device_get_max_buffers (GskVulkanDevice *self)
{
  return self->max_buffers;
}

gboolean
gsk_vulkan_device_has_feature (GskVulkanDevice   *self,
                               GdkVulkanFeatures  feature)
{
  return (self->features & feature) == feature;
}

VkDevice
gsk_vulkan_device_get_vk_device (GskVulkanDevice *self)
{
  return gsk_gpu_device_get_display (GSK_GPU_DEVICE (self))->vk_device;
}

VkPhysicalDevice
gsk_vulkan_device_get_vk_physical_device (GskVulkanDevice *self)
{
  return gsk_gpu_device_get_display (GSK_GPU_DEVICE (self))->vk_physical_device;
}

VkQueue
gsk_vulkan_device_get_vk_queue (GskVulkanDevice *self)
{
  return gsk_gpu_device_get_display (GSK_GPU_DEVICE (self))->vk_queue;
}

uint32_t
gsk_vulkan_device_get_vk_queue_family_index (GskVulkanDevice *self)
{
  return gsk_gpu_device_get_display (GSK_GPU_DEVICE (self))->vk_queue_family_index;
}

VkDescriptorSetLayout
gsk_vulkan_device_get_vk_image_set_layout (GskVulkanDevice         *self,
                                           GskVulkanPipelineLayout *layout)
{
  return layout->vk_image_set_layout;
}

VkDescriptorSetLayout
gsk_vulkan_device_get_vk_buffer_set_layout (GskVulkanDevice         *self,
                                            GskVulkanPipelineLayout *layout)
{
  return layout->vk_buffer_set_layout;
}

VkPipelineLayout
gsk_vulkan_device_get_vk_pipeline_layout (GskVulkanDevice         *self,
                                          GskVulkanPipelineLayout *layout)
{
  return layout->vk_pipeline_layout;
}

VkCommandPool
gsk_vulkan_device_get_vk_command_pool (GskVulkanDevice *self)
{
  return self->vk_command_pool;
}

static VkSampler
gsk_vulkan_device_create_sampler (GskVulkanDevice          *self,
                                  VkSamplerYcbcrConversion  vk_conversion,
                                  VkFilter                  vk_filter,
                                  VkSamplerAddressMode      vk_address_mode,
                                  VkSamplerMipmapMode       vk_mipmap_mode,
                                  float                     max_lod)
{
  VkSampler result;

  GSK_VK_CHECK (vkCreateSampler, gsk_vulkan_device_get_vk_device (self),
                                 &(VkSamplerCreateInfo) {
                                     .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
                                     .magFilter = vk_filter,
                                     .minFilter = vk_filter,
                                     .mipmapMode = vk_mipmap_mode,
                                     .addressModeU = vk_address_mode,
                                     .addressModeV = vk_address_mode,
                                     .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
                                     .borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK,
                                     .unnormalizedCoordinates = VK_FALSE,
                                     .maxAnisotropy = 1.0,
                                     .minLod = 0.0,
                                     .maxLod = max_lod,
                                     .pNext = vk_conversion == VK_NULL_HANDLE ? NULL : &(VkSamplerYcbcrConversionInfo) {
                                         .sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO,
                                         .conversion = vk_conversion
                                     }
                                 },
                                 NULL,
                                 &result);

  return result;
}

VkSampler
gsk_vulkan_device_get_vk_sampler (GskVulkanDevice     *self,
                                  GskGpuSampler        sampler)
{
  const struct {
    VkFilter filter;
    VkSamplerAddressMode address_mode;
    VkSamplerMipmapMode mipmap_mode;
    float max_lod;
  } filter_attrs[GSK_GPU_SAMPLER_N_SAMPLERS] = {
      [GSK_GPU_SAMPLER_DEFAULT] = {
          .filter = VK_FILTER_LINEAR,
          .address_mode = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
          .mipmap_mode = VK_SAMPLER_MIPMAP_MODE_NEAREST,
          .max_lod = 0.0f,
      },
      [GSK_GPU_SAMPLER_TRANSPARENT] = {
          .filter = VK_FILTER_LINEAR,
          .address_mode = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
          .mipmap_mode = VK_SAMPLER_MIPMAP_MODE_NEAREST,
          .max_lod = 0.0f,
      },
      [GSK_GPU_SAMPLER_REPEAT] = {
          .filter = VK_FILTER_LINEAR,
          .address_mode = VK_SAMPLER_ADDRESS_MODE_REPEAT,
          .mipmap_mode = VK_SAMPLER_MIPMAP_MODE_NEAREST,
          .max_lod = 0.0f,
      },
      [GSK_GPU_SAMPLER_NEAREST] = {
          .filter = VK_FILTER_NEAREST,
          .address_mode = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
          .mipmap_mode = VK_SAMPLER_MIPMAP_MODE_NEAREST,
          .max_lod = 0.0f,
      },
      [GSK_GPU_SAMPLER_MIPMAP_DEFAULT] = {
          .filter = VK_FILTER_LINEAR,
          .address_mode = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
          .mipmap_mode = VK_SAMPLER_MIPMAP_MODE_LINEAR,
          .max_lod = VK_LOD_CLAMP_NONE,
      },
  };

  if (self->vk_samplers[sampler] == VK_NULL_HANDLE)
    {
      self->vk_samplers[sampler] = gsk_vulkan_device_create_sampler (self,
                                                                     VK_NULL_HANDLE,
                                                                     filter_attrs[sampler].filter,
                                                                     filter_attrs[sampler].address_mode,
                                                                     filter_attrs[sampler].mipmap_mode,
                                                                     filter_attrs[sampler].max_lod);
    }

  return self->vk_samplers[sampler];
}

VkSamplerYcbcrConversion
gsk_vulkan_device_get_vk_conversion (GskVulkanDevice *self,
                                     VkFormat         vk_format,
                                     VkSampler       *out_sampler)
{
  ConversionCacheEntry lookup;
  ConversionCacheEntry *entry;
  GdkDisplay *display;

  lookup = (ConversionCacheEntry) {
    .vk_format = vk_format,
  };
  entry = g_hash_table_lookup (self->conversion_cache, &lookup);
  if (entry)
    {
      if (out_sampler)
        *out_sampler = entry->vk_sampler;

      return entry->vk_conversion;
    }

  display = gsk_gpu_device_get_display (GSK_GPU_DEVICE (self));

  entry = g_memdup (&lookup, sizeof (ConversionCacheEntry));

  GSK_VK_CHECK (vkCreateSamplerYcbcrConversion, display->vk_device,
                                                &(VkSamplerYcbcrConversionCreateInfo) {
                                                    .sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO,
                                                    .format = vk_format,
                                                    .ycbcrModel = VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601,
                                                    .ycbcrRange = VK_SAMPLER_YCBCR_RANGE_ITU_FULL,
                                                    .components = (VkComponentMapping) {
                                                        VK_COMPONENT_SWIZZLE_IDENTITY,
                                                        VK_COMPONENT_SWIZZLE_IDENTITY,
                                                        VK_COMPONENT_SWIZZLE_IDENTITY,
                                                        VK_COMPONENT_SWIZZLE_IDENTITY
                                                    },
                                                    .xChromaOffset = VK_CHROMA_LOCATION_COSITED_EVEN,
                                                    .yChromaOffset = VK_CHROMA_LOCATION_COSITED_EVEN,
                                                    .chromaFilter = VK_FILTER_LINEAR,
                                                    .forceExplicitReconstruction = VK_FALSE
                                                },
                                                NULL,
                                                &entry->vk_conversion);

  entry->vk_sampler = gsk_vulkan_device_create_sampler (self,
                                                        entry->vk_conversion,
                                                        VK_FILTER_LINEAR,
                                                        VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
                                                        VK_SAMPLER_MIPMAP_MODE_NEAREST,
                                                        0.0f);

  g_hash_table_insert (self->conversion_cache, entry, entry);

  if (out_sampler)
    *out_sampler = entry->vk_sampler;

  return entry->vk_conversion;
}

VkRenderPass
gsk_vulkan_device_get_vk_render_pass (GskVulkanDevice *self,
                                      VkFormat         format,
                                      VkImageLayout    from_layout,
                                      VkImageLayout    to_layout)
{
  RenderPassCacheKey cache_key;
  VkRenderPass render_pass;
  GdkDisplay *display;

  cache_key = (RenderPassCacheKey) {
    .format = format,
    .from_layout = from_layout,
    .to_layout = to_layout,
  };
  render_pass = g_hash_table_lookup (self->render_pass_cache, &cache_key);
  if (render_pass)
    return render_pass;

  display = gsk_gpu_device_get_display (GSK_GPU_DEVICE (self));

  GSK_VK_CHECK (vkCreateRenderPass, display->vk_device,
                                    &(VkRenderPassCreateInfo) {
                                        .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
                                        .attachmentCount = 1,
                                        .pAttachments = (VkAttachmentDescription[]) {
                                           {
                                              .format = format,
                                              .samples = VK_SAMPLE_COUNT_1_BIT,
                                              .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
                                              .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
                                              .initialLayout = from_layout,
                                              .finalLayout = to_layout
                                           }
                                        },
                                        .subpassCount = 1,
                                        .pSubpasses = (VkSubpassDescription []) {
                                           {
                                              .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
                                              .inputAttachmentCount = 0,
                                              .colorAttachmentCount = 1,
                                              .pColorAttachments = (VkAttachmentReference []) {
                                                 {
                                                    .attachment = 0,
                                                     .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
                                                  }
                                               },
                                               .pResolveAttachments = (VkAttachmentReference []) {
                                                  {
                                                     .attachment = VK_ATTACHMENT_UNUSED,
                                                     .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
                                                  }
                                               },
                                               .pDepthStencilAttachment = NULL,
                                            }
                                         },
                                         .dependencyCount = 0,
                                      },
                                      NULL,
                                      &render_pass);

  g_hash_table_insert (self->render_pass_cache, g_memdup (&cache_key, sizeof (RenderPassCacheKey)), render_pass);

  return render_pass;
}

typedef struct _GskVulkanShaderSpecialization GskVulkanShaderSpecialization;
struct _GskVulkanShaderSpecialization
{
  guint32 clip;
  guint32 n_immutable_samplers;
  guint32 n_samplers;
  guint32 n_buffers;
  guint32 variation;
};

static VkPipelineColorBlendAttachmentState blend_attachment_states[3] = {
  [GSK_GPU_BLEND_OVER] = {
    .blendEnable = VK_TRUE,
    .colorBlendOp = VK_BLEND_OP_ADD,
    .srcColorBlendFactor = VK_BLEND_FACTOR_ONE,
    .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
    .alphaBlendOp = VK_BLEND_OP_ADD,
    .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
    .dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
    .colorWriteMask = VK_COLOR_COMPONENT_A_BIT
                    | VK_COLOR_COMPONENT_R_BIT
                    | VK_COLOR_COMPONENT_G_BIT
                    | VK_COLOR_COMPONENT_B_BIT
  },
  [GSK_GPU_BLEND_ADD] = {
    .blendEnable = VK_TRUE,
    .colorBlendOp = VK_BLEND_OP_ADD,
    .srcColorBlendFactor = VK_BLEND_FACTOR_ONE,
    .dstColorBlendFactor = VK_BLEND_FACTOR_ONE,
    .alphaBlendOp = VK_BLEND_OP_ADD,
    .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
    .dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
    .colorWriteMask = VK_COLOR_COMPONENT_A_BIT
                    | VK_COLOR_COMPONENT_R_BIT
                    | VK_COLOR_COMPONENT_G_BIT
                    | VK_COLOR_COMPONENT_B_BIT
  },
  [GSK_GPU_BLEND_CLEAR] = {
    .blendEnable = VK_TRUE,
    .colorBlendOp = VK_BLEND_OP_ADD,
    .srcColorBlendFactor = VK_BLEND_FACTOR_ZERO,
    .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
    .alphaBlendOp = VK_BLEND_OP_ADD,
    .srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
    .dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
    .colorWriteMask = VK_COLOR_COMPONENT_A_BIT
                    | VK_COLOR_COMPONENT_R_BIT
                    | VK_COLOR_COMPONENT_G_BIT
                    | VK_COLOR_COMPONENT_B_BIT
  },
};

VkPipeline
gsk_vulkan_device_get_vk_pipeline (GskVulkanDevice           *self,
                                   GskVulkanPipelineLayout   *layout,
                                   const GskGpuShaderOpClass *op_class,
                                   guint32                    variation,
                                   GskGpuShaderClip           clip,
                                   GskGpuBlend                blend,
                                   VkFormat                   format,
                                   VkRenderPass               render_pass)
{
  PipelineCacheKey cache_key;
  VkPipeline pipeline;
  GdkDisplay *display;
  const char *version_string;
  char *vertex_shader_name, *fragment_shader_name;

  cache_key = (PipelineCacheKey) {
    .op_class = op_class,
    .variation = variation,
    .clip = clip,
    .blend = blend,
    .format = format,
  };
  pipeline = g_hash_table_lookup (layout->pipeline_cache, &cache_key);
  if (pipeline)
    return pipeline;

  display = gsk_gpu_device_get_display (GSK_GPU_DEVICE (self));
  if (gsk_vulkan_device_has_feature (self, GDK_VULKAN_FEATURE_DYNAMIC_INDEXING) &&
      gsk_vulkan_device_has_feature (self, GDK_VULKAN_FEATURE_NONUNIFORM_INDEXING))
    version_string = ".1.2";
  else
    version_string = ".1.0";
  vertex_shader_name = g_strconcat ("/org/gtk/libgsk/shaders/vulkan/",
                                    op_class->shader_name,
                                    version_string,
                                    ".vert.spv",
                                    NULL);
  fragment_shader_name = g_strconcat ("/org/gtk/libgsk/shaders/vulkan/",
                                      op_class->shader_name,
                                      version_string,
                                      ".frag.spv",
                                      NULL);

  GSK_VK_CHECK (vkCreateGraphicsPipelines, display->vk_device,
                                           display->vk_pipeline_cache,
                                           1,
                                           &(VkGraphicsPipelineCreateInfo) {
                                               .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
                                               .stageCount = 2,
                                               .pStages = (VkPipelineShaderStageCreateInfo[2]) {
                                                   {
                                                       .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
                                                       .stage = VK_SHADER_STAGE_VERTEX_BIT,
                                                       .module = gdk_display_get_vk_shader_module (display, vertex_shader_name),
                                                       .pName = "main",
                                                       .pSpecializationInfo = &(VkSpecializationInfo) {
                                                           .mapEntryCount = 5,
                                                           .pMapEntries = (VkSpecializationMapEntry[5]) {
                                                               {
                                                                   .constantID = 0,
                                                                   .offset = G_STRUCT_OFFSET (GskVulkanShaderSpecialization, clip),
                                                                   .size = sizeof (guint32),
                                                               },
                                                               {
                                                                   .constantID = 1,
                                                                   .offset = G_STRUCT_OFFSET (GskVulkanShaderSpecialization, n_immutable_samplers),
                                                                   .size = sizeof (guint32),
                                                               },
                                                               {
                                                                   .constantID = 2,
                                                                   .offset = G_STRUCT_OFFSET (GskVulkanShaderSpecialization, n_samplers),
                                                                   .size = sizeof (guint32),
                                                               },
                                                               {
                                                                   .constantID = 3,
                                                                   .offset = G_STRUCT_OFFSET (GskVulkanShaderSpecialization, n_buffers),
                                                                   .size = sizeof (guint32),
                                                               },
                                                               {
                                                                   .constantID = 4,
                                                                   .offset = G_STRUCT_OFFSET (GskVulkanShaderSpecialization, variation),
                                                                   .size = sizeof (guint32),
                                                               },
                                                           },
                                                           .dataSize = sizeof (GskVulkanShaderSpecialization),
                                                           .pData = &(GskVulkanShaderSpecialization) {
                                                               .clip = clip,
                                                               .n_immutable_samplers = MAX (1, layout->setup.n_immutable_samplers),
                                                               .n_samplers = layout->setup.n_samplers - MAX (3 * layout->setup.n_immutable_samplers, 1),
                                                               .n_buffers = layout->setup.n_buffers,
                                                               .variation = variation,
                                                           },
                                                       },
                                                   },
                                                   {
                                                       .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
                                                       .stage = VK_SHADER_STAGE_FRAGMENT_BIT,
                                                       .module = gdk_display_get_vk_shader_module (display, fragment_shader_name),
                                                       .pName = "main",
                                                       .pSpecializationInfo = &(VkSpecializationInfo) {
                                                           .mapEntryCount = 5,
                                                           .pMapEntries = (VkSpecializationMapEntry[5]) {
                                                               {
                                                                   .constantID = 0,
                                                                   .offset = G_STRUCT_OFFSET (GskVulkanShaderSpecialization, clip),
                                                                   .size = sizeof (guint32),
                                                               },
                                                               {
                                                                   .constantID = 1,
                                                                   .offset = G_STRUCT_OFFSET (GskVulkanShaderSpecialization, n_immutable_samplers),
                                                                   .size = sizeof (guint32),
                                                               },
                                                               {
                                                                   .constantID = 2,
                                                                   .offset = G_STRUCT_OFFSET (GskVulkanShaderSpecialization, n_samplers),
                                                                   .size = sizeof (guint32),
                                                               },
                                                               {
                                                                   .constantID = 3,
                                                                   .offset = G_STRUCT_OFFSET (GskVulkanShaderSpecialization, n_buffers),
                                                                   .size = sizeof (guint32),
                                                               },
                                                               {
                                                                   .constantID = 4,
                                                                   .offset = G_STRUCT_OFFSET (GskVulkanShaderSpecialization, variation),
                                                                   .size = sizeof (guint32),
                                                               },
                                                           },
                                                           .dataSize = sizeof (GskVulkanShaderSpecialization),
                                                           .pData = &(GskVulkanShaderSpecialization) {
                                                               .clip = clip,
                                                               .n_immutable_samplers = MAX (1, layout->setup.n_immutable_samplers),
                                                               .n_samplers = layout->setup.n_samplers - MAX (3 * layout->setup.n_immutable_samplers, 1),
                                                               .n_buffers = layout->setup.n_buffers,
                                                               .variation = variation,
                                                           },
                                                       },
                                                   },
                                               },

                                               .pVertexInputState = op_class->vertex_input_state,
                                               .pInputAssemblyState = &(VkPipelineInputAssemblyStateCreateInfo) {
                                                   .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
                                                   .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
                                                   .primitiveRestartEnable = VK_FALSE,
                                               },
                                               .pTessellationState = NULL,
                                               .pViewportState = &(VkPipelineViewportStateCreateInfo) {
                                                   .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
                                                   .viewportCount = 1,
                                                   .scissorCount = 1
                                               },
                                               .pRasterizationState = &(VkPipelineRasterizationStateCreateInfo) {
                                                   .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
                                                   .depthClampEnable = VK_FALSE,
                                                   .rasterizerDiscardEnable = VK_FALSE,
                                                   .polygonMode = VK_POLYGON_MODE_FILL,
                                                   .cullMode = VK_CULL_MODE_NONE,
                                                   .frontFace = VK_FRONT_FACE_CLOCKWISE,
                                                   .lineWidth = 1.0f,
                                               },
                                               .pMultisampleState = &(VkPipelineMultisampleStateCreateInfo) {
                                                   .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
                                                   .rasterizationSamples = 1,
                                               },
                                               .pDepthStencilState = &(VkPipelineDepthStencilStateCreateInfo) {
                                                   .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO
                                               },
                                               .pColorBlendState = &(VkPipelineColorBlendStateCreateInfo) {
                                                   .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
                                                   .attachmentCount = 1,
                                                   .pAttachments = &blend_attachment_states[blend],
                                               },
                                               .pDynamicState = &(VkPipelineDynamicStateCreateInfo) {
                                                   .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
                                                   .dynamicStateCount = 2,
                                                   .pDynamicStates = (VkDynamicState[2]) {
                                                       VK_DYNAMIC_STATE_VIEWPORT,
                                                       VK_DYNAMIC_STATE_SCISSOR
                                                   },
                                               },
                                               .layout = layout->vk_pipeline_layout,
                                               .renderPass = render_pass,
                                               .subpass = 0,
                                               .basePipelineHandle = VK_NULL_HANDLE,
                                               .basePipelineIndex = -1,
                                           },
                                           NULL,
                                           &pipeline);

  g_free (fragment_shader_name);
  g_free (vertex_shader_name);

  g_hash_table_insert (layout->pipeline_cache, g_memdup (&cache_key, sizeof (PipelineCacheKey)), pipeline);
  gdk_display_vulkan_pipeline_cache_updated (display);

  return pipeline;
}

GskVulkanPipelineLayout *
gsk_vulkan_device_acquire_pipeline_layout (GskVulkanDevice *self,
                                           VkSampler       *immutable_samplers,
                                           gsize            n_immutable_samplers,
                                           gsize            n_samplers,
                                           gsize            n_buffers)
{
  GskVulkanPipelineLayoutSetup setup;
  GskVulkanPipelineLayout *layout;

  /* round the number of samplers/buffer up a bit, so we don't (re)create
   * excessive amounts of layouts */
  n_samplers = MAX (n_samplers, 8);
  g_assert (n_samplers <= self->max_samplers);
  n_buffers = MAX (n_buffers, 8);
  g_assert (n_buffers <= self->max_buffers);
  setup.n_samplers = MIN (2 << g_bit_nth_msf (n_samplers - 1, -1), self->max_samplers);
  setup.n_buffers = MIN (2 << g_bit_nth_msf (n_buffers - 1, -1), self->max_buffers);
  setup.n_immutable_samplers = n_immutable_samplers;
  setup.immutable_samplers = immutable_samplers;

  layout = g_hash_table_lookup (self->pipeline_layouts, &setup);
  if (layout)
    {
      gsk_vulkan_pipeline_layout_ref (self, layout);
      return layout;
    }

  return gsk_vulkan_pipeline_layout_new (self, &setup);
}

void
gsk_vulkan_device_release_pipeline_layout (GskVulkanDevice         *self,
                                           GskVulkanPipelineLayout *layout)
{
  if (self->pipeline_layout_cache)
    gsk_vulkan_pipeline_layout_unref (self, self->pipeline_layout_cache);

  self->pipeline_layout_cache = layout;
}

void
gsk_vulkan_device_get_pipeline_sizes (GskVulkanDevice        *self,
                                      GskVulkanPipelineLayout*layout,
                                      gsize                  *n_immutable_samplers,
                                      gsize                  *n_samplers,
                                      gsize                  *n_buffers)
{
  *n_immutable_samplers = layout->setup.n_immutable_samplers;
  *n_samplers = layout->setup.n_samplers;
  *n_buffers = layout->setup.n_buffers;
}

static GskVulkanAllocator *
gsk_vulkan_device_get_allocator (GskVulkanDevice    *self,
                                 gsize               index,
                                 const VkMemoryType *type)
{
  if (self->allocators[index] == NULL)
    {
      self->allocators[index] = gsk_vulkan_direct_allocator_new (gsk_vulkan_device_get_vk_device (self),
                                                                 index,
                                                                 type);
      self->allocators[index] = gsk_vulkan_buddy_allocator_new (self->allocators[index],
                                                                1024 * 1024);
      //allocators[index] = gsk_vulkan_stats_allocator_new (allocators[index]);
    }

  return self->allocators[index];
}

/* following code found in
 * https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkPhysicalDeviceMemoryProperties.html */
GskVulkanAllocator *
gsk_vulkan_device_find_allocator (GskVulkanDevice       *self,
                                  uint32_t               allowed_types,
                                  VkMemoryPropertyFlags  required_flags,
                                  VkMemoryPropertyFlags  desired_flags)
{
  VkPhysicalDeviceMemoryProperties properties;
  uint32_t i, found;

  vkGetPhysicalDeviceMemoryProperties (gsk_vulkan_device_get_vk_physical_device (self),
                                       &properties);

  found = properties.memoryTypeCount;
  for (i = 0; i < properties.memoryTypeCount; i++)
    {
      if (!(allowed_types & (1 << i)))
        continue;

      if ((properties.memoryTypes[i].propertyFlags & required_flags) != required_flags)
        continue;

      found = MIN (i, found);

      if ((properties.memoryTypes[i].propertyFlags & desired_flags) == desired_flags)
        break;
  }

  g_assert (found < properties.memoryTypeCount);

  return gsk_vulkan_allocator_ref (gsk_vulkan_device_get_allocator (self, i, &properties.memoryTypes[i]));
}

GskVulkanAllocator *
gsk_vulkan_device_get_external_allocator (GskVulkanDevice *self)
{
  if (self->external_allocator == NULL)
    {
      self->external_allocator = gsk_vulkan_external_allocator_new (gsk_vulkan_device_get_vk_device (self));
      //self->external_allocator = gsk_vulkan_stats_allocator_new (self->external_allocator);
    }

  return self->external_allocator;
}
