#include "config.h"

#include "gskvulkanimageprivate.h"

#include "gskvulkanbufferprivate.h"
#include "gskvulkanframeprivate.h"
#include "gskvulkanmemoryprivate.h"
#include "gskvulkanycbcrprivate.h"

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

#include <fcntl.h>
#include <string.h>
#ifdef HAVE_DMABUF
#include <linux/dma-buf.h>
#endif

struct _GskVulkanImage
{
  GskGpuImage parent_instance;

  GskVulkanDevice *device;

  VkFormat vk_format;
  VkImageTiling vk_tiling;
  VkImageUsageFlags vk_usage;
  VkImage vk_image;
  VkImageView vk_image_view;
  VkFramebuffer vk_framebuffer;
  VkImageView vk_framebuffer_image_view;
  GskVulkanYcbcr *ycbcr;
  VkSemaphore vk_semaphore;
  struct {
    VkDescriptorSet vk_descriptor_set;
    gsize pool_id;
  } descriptor_sets[GSK_GPU_SAMPLER_N_SAMPLERS];

  VkPipelineStageFlags vk_pipeline_stage;
  VkImageLayout vk_image_layout;
  VkAccessFlags vk_access;

  GskVulkanAllocator *allocator;
  GskVulkanAllocation allocation;
};

G_DEFINE_TYPE (GskVulkanImage, gsk_vulkan_image, GSK_TYPE_GPU_IMAGE)

static gboolean
gsk_component_mapping_is_framebuffer_compatible (const VkComponentMapping *components)
{
  if ((components->r != VK_COMPONENT_SWIZZLE_R && components->r != VK_COMPONENT_SWIZZLE_IDENTITY) ||
      (components->g != VK_COMPONENT_SWIZZLE_G && components->g != VK_COMPONENT_SWIZZLE_IDENTITY) ||
      (components->b != VK_COMPONENT_SWIZZLE_B && components->b != VK_COMPONENT_SWIZZLE_IDENTITY) ||
      (components->a != VK_COMPONENT_SWIZZLE_A && components->a != VK_COMPONENT_SWIZZLE_IDENTITY))
    return FALSE;

  return TRUE;
}

static gboolean
gsk_vulkan_device_supports_format (GskVulkanDevice   *device,
                                   VkFormat           format,
                                   uint64_t           modifier,
                                   guint              n_planes,
                                   VkImageTiling      tiling,
                                   VkImageUsageFlags  usage,
                                   gsize              width,
                                   gsize              height,
                                   GskGpuImageFlags  *out_flags)
{
  VkDrmFormatModifierPropertiesEXT drm_mod_properties[100];
  VkDrmFormatModifierPropertiesListEXT drm_properties;
  VkPhysicalDevice vk_phys_device;
  VkFormatProperties2 properties;
  VkImageFormatProperties2 image_properties;
  VkFormatFeatureFlags features;
  VkResult res;
  gsize i;

  vk_phys_device = gsk_vulkan_device_get_vk_physical_device (device);

  drm_properties = (VkDrmFormatModifierPropertiesListEXT) {
    .sType = VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT,
    .drmFormatModifierCount = G_N_ELEMENTS (drm_mod_properties),
    .pDrmFormatModifierProperties = drm_mod_properties,
  };
  properties = (VkFormatProperties2) {
    .sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2,
    .pNext = (tiling != VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT) ? NULL : &drm_properties
  };
  vkGetPhysicalDeviceFormatProperties2 (vk_phys_device,
                                        format,
                                        &properties);

  switch ((int) tiling)
    {
      case VK_IMAGE_TILING_OPTIMAL:
        features = properties.formatProperties.optimalTilingFeatures;
        break;
      case VK_IMAGE_TILING_LINEAR:
        features = properties.formatProperties.linearTilingFeatures;
        break;
      case VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT:
        features = 0;
        for (i = 0; i < drm_properties.drmFormatModifierCount; i++)
          {
            if (drm_mod_properties[i].drmFormatModifier == modifier &&
                drm_mod_properties[i].drmFormatModifierPlaneCount == n_planes)
              {
                features = drm_mod_properties[i].drmFormatModifierTilingFeatures;
                break;
              }
          }
        if (features == 0)
          return FALSE;
        break;
      default:
        return FALSE;
    }

  if (!(features & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT))
    return FALSE;

  image_properties = (VkImageFormatProperties2) {
    .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2,
  };
  res = vkGetPhysicalDeviceImageFormatProperties2 (vk_phys_device,
                                                   &(VkPhysicalDeviceImageFormatInfo2) {
                                                     .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2,
                                                     .format = format,
                                                     .type = VK_IMAGE_TYPE_2D,
                                                     .tiling = tiling,
                                                     .usage = usage,
                                                     .flags = 0,
                                                     .pNext = (tiling != VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT) ? NULL : &(VkPhysicalDeviceImageDrmFormatModifierInfoEXT) {
                                                         .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT,
                                                         .drmFormatModifier = modifier,
                                                         .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
                                                         .queueFamilyIndexCount = 1,
                                                         .pQueueFamilyIndices = (uint32_t[1]) { gsk_vulkan_device_get_vk_queue_family_index (device) },
                                                      }
                                                   },
                                                   &image_properties);
  if (res != VK_SUCCESS)
    return FALSE;

  if (image_properties.imageFormatProperties.maxExtent.width < width ||
      image_properties.imageFormatProperties.maxExtent.height < height)
    return FALSE;

  *out_flags = 0;
  if ((features & VK_FORMAT_FEATURE_BLIT_SRC_BIT) == 0)
    *out_flags |= GSK_GPU_IMAGE_NO_BLIT;
  if (features & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)
    *out_flags |= GSK_GPU_IMAGE_FILTERABLE;
  if (features & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT)
    *out_flags |= GSK_GPU_IMAGE_RENDERABLE;

  return TRUE;
}

static void
gsk_vulkan_image_create_view (GskVulkanImage            *self,
                              VkFormat                   vk_format,
                              const VkComponentMapping  *vk_components,
                              VkSamplerYcbcrConversion   vk_conversion)
{
  GSK_VK_CHECK (vkCreateImageView, gsk_vulkan_device_get_vk_device (self->device),
                                 &(VkImageViewCreateInfo) {
                                     .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
                                     .image = self->vk_image,
                                     .viewType = VK_IMAGE_VIEW_TYPE_2D,
                                     .format = vk_format,
                                     .components = *vk_components,
                                     .subresourceRange = {
                                         .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
                                         .baseMipLevel = 0,
                                         .levelCount = VK_REMAINING_MIP_LEVELS,
                                         .baseArrayLayer = 0,
                                         .layerCount = 1,
                                     },
                                     .pNext = vk_conversion == VK_NULL_HANDLE ? NULL : &(VkSamplerYcbcrConversionInfo) {
                                         .sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO,
                                         .conversion = vk_conversion
                                     }
                                 },
                                 NULL,
                                 &self->vk_image_view);
}

static gboolean
gsk_vulkan_device_check_format (GskVulkanDevice          *device,
                                VkFormat                  vk_format,
                                const VkComponentMapping *vk_components,
                                GskGpuImageFlags          required_flags,
                                VkImageTiling             vk_tiling,
                                VkImageUsageFlags         vk_usage,
                                gsize                     width,
                                gsize                     height,
                                VkImageTiling            *out_tiling,
                                GskGpuImageFlags         *out_flags)
{
#define CHECK_FLAGS (GSK_GPU_IMAGE_NO_BLIT | GSK_GPU_IMAGE_FILTERABLE | GSK_GPU_IMAGE_RENDERABLE)
  GskGpuImageFlags flags;

  if (vk_format == VK_FORMAT_UNDEFINED)
    return FALSE;

  if (vk_usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT &&
      !gsk_component_mapping_is_framebuffer_compatible (vk_components))
    return FALSE;

  if (gsk_vulkan_device_supports_format (device,
                                         vk_format,
                                         0, 1,
                                         vk_tiling, vk_usage,
                                         width, height,
                                         &flags) &&
      ((flags & required_flags & CHECK_FLAGS) == (required_flags & CHECK_FLAGS)))
    {
      *out_tiling = vk_tiling;
      *out_flags = flags;
      return TRUE;
    }

  if (vk_tiling == VK_IMAGE_TILING_LINEAR &&
      gsk_vulkan_device_supports_format (device,
                                         vk_format,
                                         0, 1,
                                         VK_IMAGE_TILING_OPTIMAL, vk_usage,
                                         width, height,
                                         &flags) &&
      ((flags & required_flags & CHECK_FLAGS) == (required_flags & CHECK_FLAGS)))
    {
      *out_tiling = VK_IMAGE_TILING_OPTIMAL;
      *out_flags = flags;
      return TRUE;
    }

  return FALSE;
#undef CHECK_FLAGS
}

static GskVulkanImage *
gsk_vulkan_image_new (GskVulkanDevice           *device,
                      GdkMemoryFormat            format,
                      gboolean                   try_srgb,
                      GskGpuImageFlags           required_flags,
                      gsize                      width,
                      gsize                      height,
                      VkImageTiling              tiling,
                      VkImageUsageFlags          usage,
                      VkPipelineStageFlags       stage,
                      VkImageLayout              layout,
                      VkAccessFlags              access,
                      VkMemoryPropertyFlags      memory)
{
  VkMemoryRequirements requirements;
  GskVulkanImage *self;
  VkDevice vk_device;
  GskGpuImageFlags flags;
  VkFormat vk_format, vk_srgb_format;
  VkComponentMapping vk_components;

  g_assert (width > 0 && height > 0);

  vk_srgb_format = VK_FORMAT_UNDEFINED;

  /* First, try the actual format */
  vk_format = gdk_memory_format_vk_format (format, &vk_components);
  if (try_srgb)
    vk_srgb_format = gdk_memory_format_vk_srgb_format (format);
  if (gsk_vulkan_device_check_format (device, vk_srgb_format, &vk_components, required_flags,
                                      tiling, usage, width, height,
                                      &tiling, &flags))
    {
      vk_format = vk_srgb_format;
    }
  else if (!gsk_vulkan_device_check_format (device, vk_format, &vk_components, required_flags,
                                            tiling, usage, width, height,
                                            &tiling, &flags))
    {
      GdkMemoryFormat rgba_format;

      /* Second, try the potential RGBA format */
      vk_format = gdk_memory_format_vk_rgba_format (format, &rgba_format, &vk_components);
      if (try_srgb && vk_format != VK_FORMAT_UNDEFINED)
        vk_srgb_format = gdk_memory_format_vk_srgb_format (rgba_format);
      else
        vk_srgb_format = VK_FORMAT_UNDEFINED;
      if (gsk_vulkan_device_check_format (device, vk_srgb_format, &vk_components, required_flags,
                                          tiling, usage, width, height,
                                          &tiling, &flags))
        {
          vk_format = vk_srgb_format;
        }
      else if (!gsk_vulkan_device_check_format (device, vk_format, &vk_components, required_flags,
                                                tiling, usage, width, height,
                                                &tiling, &flags))
        {
          const GdkMemoryFormat *fallbacks;
          gsize i;

          /* Next, try the fallbacks */
          fallbacks = gdk_memory_format_get_fallbacks (format);
          for (i = 0; fallbacks[i] != -1; i++)
            {
              vk_format = gdk_memory_format_vk_format (fallbacks[i], &vk_components);
              if (try_srgb)
                vk_srgb_format = gdk_memory_format_vk_srgb_format (fallbacks[i]);
              if (gsk_vulkan_device_check_format (device, vk_srgb_format, &vk_components, required_flags,
                                                  tiling, usage, width, height,
                                                  &tiling, &flags))
                {
                  vk_format = vk_srgb_format;
                  format = fallbacks[i];
                  break;
                }
              else if (gsk_vulkan_device_check_format (device, vk_format, &vk_components, required_flags,
                                                       tiling, usage, width, height,
                                                       &tiling, &flags))
                {
                  format = fallbacks[i];
                  break;
                }
            }

          /* No format found. Likely, the width/height are too big */
          if (fallbacks[i] == -1)
            return NULL;
        }
    }

  if (vk_format == vk_srgb_format)
    flags |= GSK_GPU_IMAGE_SRGB;

  if (gdk_memory_format_alpha (format) == GDK_MEMORY_ALPHA_STRAIGHT)
    flags |= GSK_GPU_IMAGE_STRAIGHT_ALPHA;

  if (((flags & (GSK_GPU_IMAGE_FILTERABLE | GSK_GPU_IMAGE_RENDERABLE | GSK_GPU_IMAGE_NO_BLIT | GSK_GPU_IMAGE_STRAIGHT_ALPHA)) ==
       (GSK_GPU_IMAGE_FILTERABLE | GSK_GPU_IMAGE_RENDERABLE)) &&
      (required_flags & GSK_GPU_IMAGE_CAN_MIPMAP))
    flags |= GSK_GPU_IMAGE_CAN_MIPMAP;

  if (!gsk_component_mapping_is_framebuffer_compatible (&vk_components))
    flags |= GSK_GPU_IMAGE_NO_BLIT;

  vk_device = gsk_vulkan_device_get_vk_device (device);

  self = g_object_new (GSK_TYPE_VULKAN_IMAGE, NULL);

  self->device = g_object_ref (device);
  self->vk_format = vk_format;
  self->vk_tiling = tiling;
  self->vk_usage = usage;
  self->vk_pipeline_stage = stage;
  self->vk_image_layout = layout;
  self->vk_access = access;

  gsk_gpu_image_setup (GSK_GPU_IMAGE (self), flags, format, width, height);

  GSK_VK_CHECK (vkCreateImage, vk_device,
                                &(VkImageCreateInfo) {
                                    .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
                                    .flags = 0,
                                    .imageType = VK_IMAGE_TYPE_2D,
                                    .format = vk_format,
                                    .extent = { width, height, 1 },
                                    .mipLevels = (flags & GSK_GPU_IMAGE_CAN_MIPMAP) ? gsk_vulkan_mipmap_levels (width, height) : 1,
                                    .arrayLayers = 1,
                                    .samples = VK_SAMPLE_COUNT_1_BIT,
                                    .tiling = tiling,
                                    .usage = usage |
                                             (flags & GSK_GPU_IMAGE_NO_BLIT ? 0 : VK_IMAGE_USAGE_TRANSFER_SRC_BIT),
                                    .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
                                    .initialLayout = self->vk_image_layout,
                                },
                                NULL,
                                &self->vk_image);

  vkGetImageMemoryRequirements (vk_device,
                                self->vk_image,
                                &requirements);

  self->allocator = gsk_vulkan_device_find_allocator (device,
                                                      requirements.memoryTypeBits,
                                                      0,
                                                      tiling == VK_IMAGE_TILING_LINEAR ? GSK_VULKAN_MEMORY_MAPPABLE : 0);
  gsk_vulkan_alloc (self->allocator,
                    requirements.size,
                    requirements.alignment,
                    &self->allocation);

  GSK_VK_CHECK (vkBindImageMemory, vk_device,
                                   self->vk_image,
                                   self->allocation.vk_memory,
                                   self->allocation.offset);

  gsk_vulkan_image_create_view (self, vk_format, &vk_components, VK_NULL_HANDLE);

  return self;
}

GskGpuImage *
gsk_vulkan_image_new_for_upload (GskVulkanDevice *device,
                                 gboolean         with_mipmap,
                                 GdkMemoryFormat  format,
                                 gboolean         try_srgb,
                                 gsize            width,
                                 gsize            height)
{
  GskVulkanImage *self;

  self = gsk_vulkan_image_new (device,
                               format,
                               try_srgb,
                               with_mipmap ? (GSK_GPU_IMAGE_CAN_MIPMAP | GSK_GPU_IMAGE_RENDERABLE | GSK_GPU_IMAGE_FILTERABLE) : 0,
                               width,
                               height,
                               VK_IMAGE_TILING_LINEAR,
                               VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
                               VK_IMAGE_USAGE_TRANSFER_DST_BIT |
                               VK_IMAGE_USAGE_SAMPLED_BIT,
                               VK_PIPELINE_STAGE_TRANSFER_BIT,
                               VK_IMAGE_LAYOUT_PREINITIALIZED,
                               VK_ACCESS_TRANSFER_WRITE_BIT,
                               VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);

  return GSK_GPU_IMAGE (self);
}

static gboolean
gsk_vulkan_image_can_map (GskVulkanImage *self)
{
  if (GSK_DEBUG_CHECK (STAGING))
    return FALSE;

  if (self->vk_tiling != VK_IMAGE_TILING_LINEAR)
    return FALSE;

  if (self->vk_image_layout != VK_IMAGE_LAYOUT_PREINITIALIZED &&
      self->vk_image_layout != VK_IMAGE_LAYOUT_GENERAL)
    return FALSE;

  if ((self->allocation.memory_flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) == 0)
    return FALSE;

  return self->allocation.map != NULL;
}

guchar *
gsk_vulkan_image_get_data (GskVulkanImage *self,
                           gsize          *out_stride)
{
  VkImageSubresource image_res;
  VkSubresourceLayout image_layout;

  if (!gsk_vulkan_image_can_map (self))
    return NULL;

  image_res.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
  image_res.mipLevel = 0;
  image_res.arrayLayer = 0;

  vkGetImageSubresourceLayout (gsk_vulkan_device_get_vk_device (self->device),
                               self->vk_image, &image_res, &image_layout);

  *out_stride = image_layout.rowPitch;

  return self->allocation.map + image_layout.offset;
}

GskGpuImage *
gsk_vulkan_image_new_for_swapchain (GskVulkanDevice  *device,
                                    VkImage           image,
                                    VkFormat          format,
                                    GdkMemoryFormat   memory_format,
                                    gsize             width,
                                    gsize             height)
{
  GskVulkanImage *self;
  GskGpuImageFlags flags = 0;

  self = g_object_new (GSK_TYPE_VULKAN_IMAGE, NULL);

  self->device = g_object_ref (device);
  self->vk_tiling = VK_IMAGE_TILING_OPTIMAL;
  self->vk_image = image;
  self->vk_format = format;
  self->vk_pipeline_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
  self->vk_image_layout = VK_IMAGE_LAYOUT_UNDEFINED;
  self->vk_access = 0;

  if (format == gdk_memory_format_vk_srgb_format (memory_format))
    flags |= GSK_GPU_IMAGE_SRGB;

  /* FIXME: The flags here are very suboptimal */
  gsk_gpu_image_setup (GSK_GPU_IMAGE (self), flags, memory_format, width, height);

  gsk_vulkan_image_create_view (self,
                                format,
                                &(VkComponentMapping) {
                                  VK_COMPONENT_SWIZZLE_R,
                                  VK_COMPONENT_SWIZZLE_G,
                                  VK_COMPONENT_SWIZZLE_B,
                                  VK_COMPONENT_SWIZZLE_A
                                },
                                VK_NULL_HANDLE);

  return GSK_GPU_IMAGE (self);
}

GskGpuImage *
gsk_vulkan_image_new_for_atlas (GskVulkanDevice *device,
                                gsize            width,
                                gsize            height)
{
  GskVulkanImage *self;

  self = gsk_vulkan_image_new (device,
                               GDK_MEMORY_DEFAULT,
                               FALSE,
                               GSK_GPU_IMAGE_FILTERABLE | GSK_GPU_IMAGE_RENDERABLE,
                               width,
                               height,
                               VK_IMAGE_TILING_OPTIMAL,
                               VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
                               VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
                               VK_IMAGE_LAYOUT_UNDEFINED,
                               0,
                               VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);

  return GSK_GPU_IMAGE (self);
}

GskGpuImage *
gsk_vulkan_image_new_for_offscreen (GskVulkanDevice *device,
                                    gboolean         with_mipmap,
                                    GdkMemoryFormat  preferred_format,
                                    gboolean         try_srgb,
                                    gsize            width,
                                    gsize            height)
{
  GskVulkanImage *self;

  self = gsk_vulkan_image_new (device,
                               preferred_format,
                               try_srgb,
                               GSK_GPU_IMAGE_RENDERABLE |
                               (with_mipmap ? GSK_GPU_IMAGE_CAN_MIPMAP | GSK_GPU_IMAGE_FILTERABLE : 0),
                               width,
                               height,
                               VK_IMAGE_TILING_OPTIMAL,
                               VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
                               VK_IMAGE_USAGE_SAMPLED_BIT |
                               VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
                               VK_IMAGE_USAGE_TRANSFER_DST_BIT,
                               VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
                               VK_IMAGE_LAYOUT_UNDEFINED,
                               VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
                               VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);

  return GSK_GPU_IMAGE (self);
}

#ifdef HAVE_DMABUF
static gboolean
gsk_vulkan_device_check_dmabuf_format (GskVulkanDevice          *device,
                                       VkFormat                  vk_format,
                                       const VkComponentMapping *vk_components,
                                       gsize                     width,
                                       gsize                     height,
                                       uint64_t                  modifiers[100],
                                       GskGpuImageFlags         *out_flags,
                                       gsize                    *out_n_modifiers)

{
  const VkFormatFeatureFlags required = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT;
  VkDrmFormatModifierPropertiesEXT drm_mod_properties[100];
  VkDrmFormatModifierPropertiesListEXT drm_properties;
  VkPhysicalDevice vk_phys_device;
  VkFormatProperties2 properties;
  VkImageFormatProperties2 image_properties;
  GskGpuImageFlags flags;
  gsize i, n_modifiers;
  VkResult res;

  drm_properties = (VkDrmFormatModifierPropertiesListEXT) {
    .sType = VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT,
    .drmFormatModifierCount = G_N_ELEMENTS (drm_mod_properties),
    .pDrmFormatModifierProperties = drm_mod_properties,
  };
  properties = (VkFormatProperties2) {
    .sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2,
    .pNext = &drm_properties
  };

  if (vk_format == VK_FORMAT_UNDEFINED)
    return FALSE;

  if (!gsk_component_mapping_is_framebuffer_compatible (vk_components))
    return FALSE;

  vk_phys_device = gsk_vulkan_device_get_vk_physical_device (device);

  vkGetPhysicalDeviceFormatProperties2 (vk_phys_device,
                                        vk_format,
                                        &properties);

  flags = GSK_GPU_IMAGE_FILTERABLE | GSK_GPU_IMAGE_RENDERABLE;
  n_modifiers = 0;
  for (i = 0; i < drm_properties.drmFormatModifierCount; i++)
    {
      if ((drm_mod_properties[i].drmFormatModifierTilingFeatures & required) != required)
        continue;

      image_properties = (VkImageFormatProperties2) {
        .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2,
      };
      res = vkGetPhysicalDeviceImageFormatProperties2 (vk_phys_device,
                                                       &(VkPhysicalDeviceImageFormatInfo2) {
                                                         .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2,
                                                         .format = vk_format,
                                                         .type = VK_IMAGE_TYPE_2D,
                                                         .tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT,
                                                         .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
                                                         .flags = 0,
                                                         .pNext = &(VkPhysicalDeviceImageDrmFormatModifierInfoEXT) {
                                                             .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT,
                                                             .drmFormatModifier = drm_mod_properties[i].drmFormatModifier,
                                                             .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
                                                             .queueFamilyIndexCount = 1,
                                                             .pQueueFamilyIndices = (uint32_t[1]) { gsk_vulkan_device_get_vk_queue_family_index (device) },
                                                          }
                                                       },
                                                       &image_properties);
      if (res != VK_SUCCESS)
        continue;

      if (image_properties.imageFormatProperties.maxExtent.width < width ||
          image_properties.imageFormatProperties.maxExtent.height < height)
        continue;

      /* we could check the real used format after creation, but for now: */
      if ((drm_mod_properties[i].drmFormatModifierTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT) == 0)
        flags |= GSK_GPU_IMAGE_NO_BLIT;
      if ((drm_mod_properties[i].drmFormatModifierTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) == 0)
        flags &= ~GSK_GPU_IMAGE_FILTERABLE;
      if ((drm_mod_properties[i].drmFormatModifierTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT) == 0)
        flags &= ~GSK_GPU_IMAGE_RENDERABLE;

      modifiers[n_modifiers++] = drm_mod_properties[i].drmFormatModifier;
    }

  if (n_modifiers == 0)
    return FALSE;

  *out_flags = flags;
  *out_n_modifiers = n_modifiers;
  return TRUE;
}

GskGpuImage *
gsk_vulkan_image_new_dmabuf (GskVulkanDevice *device,
                             GdkMemoryFormat  format,
                             gboolean         try_srgb,
                             gsize            width,
                             gsize            height)
{
  uint64_t modifiers[100];
  VkDevice vk_device;
  VkFormat vk_format, vk_srgb_format;
  VkComponentMapping vk_components;
  VkMemoryRequirements requirements;
  GskVulkanImage *self;
  VkResult res;
  gsize n_modifiers;
  GskGpuImageFlags flags;

  if (!gsk_vulkan_device_has_feature (device, GDK_VULKAN_FEATURE_DMABUF))
    return NULL;

  vk_srgb_format = VK_FORMAT_UNDEFINED;

  vk_device = gsk_vulkan_device_get_vk_device (device);

  /* First, try the actual format */
  vk_format = gdk_memory_format_vk_format (format, &vk_components);
  if (try_srgb)
    vk_srgb_format = gdk_memory_format_vk_srgb_format (format);
  if (gsk_vulkan_device_check_dmabuf_format (device, vk_srgb_format, &vk_components, width, height,
                                             modifiers, &flags, &n_modifiers))
    {
      vk_format = vk_srgb_format;
    }
  else if (!gsk_vulkan_device_check_dmabuf_format (device, vk_format, &vk_components, width, height,
                                                   modifiers, &flags, &n_modifiers))
    {
      /* Second, try the potential RGBA format, but as a fallback */
      GdkMemoryFormat rgba_format;
      vk_format = gdk_memory_format_vk_rgba_format (format, &rgba_format, NULL);
      if (vk_format != VK_FORMAT_UNDEFINED)
        {
          vk_format = gdk_memory_format_vk_format (rgba_format, &vk_components);
          if (try_srgb)
            vk_srgb_format = gdk_memory_format_vk_srgb_format (format);
        }
      else
        vk_srgb_format = VK_FORMAT_UNDEFINED;
      if (gsk_vulkan_device_check_dmabuf_format (device, vk_srgb_format, &vk_components, width, height,
                                                 modifiers, &flags, &n_modifiers))
        {
          vk_format = vk_srgb_format;
          format = rgba_format;
        }
      else if (gsk_vulkan_device_check_dmabuf_format (device, vk_format, &vk_components, width, height,
                                                      modifiers, &flags, &n_modifiers))
        {
          format = rgba_format;
        }
      else
        {
          const GdkMemoryFormat *fallbacks;
          gsize i;

          /* Next, try the fallbacks */
          fallbacks = gdk_memory_format_get_fallbacks (format);
          for (i = 0; fallbacks[i] != -1; i++)
            {
              vk_format = gdk_memory_format_vk_format (fallbacks[i], &vk_components);
              if (try_srgb)
                vk_srgb_format = gdk_memory_format_vk_srgb_format (format);
              if (gsk_vulkan_device_check_dmabuf_format (device, vk_srgb_format, &vk_components, width, height,
                                                         modifiers, &flags, &n_modifiers))
                {
                  vk_format = vk_srgb_format;
                  format = fallbacks[i];
                  break;
                }
              else if (gsk_vulkan_device_check_dmabuf_format (device, vk_format, &vk_components, width, height,
                                                              modifiers, &flags, &n_modifiers))
                {
                  format = fallbacks[i];
                  break;
                }
            }

          /* No format found. Likely, the width/height are too big */
          if (fallbacks[i] == -1)
            return NULL;
        }
    }

  self = g_object_new (GSK_TYPE_VULKAN_IMAGE, NULL);

  self->device = g_object_ref (device);
  self->vk_tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT;
  self->vk_format = vk_format;
  self->vk_pipeline_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
  self->vk_image_layout = VK_IMAGE_LAYOUT_UNDEFINED;
  self->vk_access = 0;

  gsk_gpu_image_setup (GSK_GPU_IMAGE (self),
                       flags | GSK_GPU_IMAGE_EXTERNAL |
                       (vk_format == vk_srgb_format ? GSK_GPU_IMAGE_SRGB : 0) |
                       (gdk_memory_format_alpha (format) == GDK_MEMORY_ALPHA_STRAIGHT ? GSK_GPU_IMAGE_STRAIGHT_ALPHA : 0) |
                       (gsk_component_mapping_is_framebuffer_compatible (&vk_components) ? 0 : GSK_GPU_IMAGE_NO_BLIT),
                       format,
                       width, height);

  res = vkCreateImage (vk_device,
                       &(VkImageCreateInfo) {
                           .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
                           .flags = 0,
                           .imageType = VK_IMAGE_TYPE_2D,
                           .format = vk_format,
                           .extent = { width, height, 1 },
                           .mipLevels = 1,
                           .arrayLayers = 1,
                           .samples = VK_SAMPLE_COUNT_1_BIT,
                           .tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT,
                           .usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
                                    ((flags & GSK_GPU_IMAGE_NO_BLIT) ? 0 : VK_IMAGE_USAGE_TRANSFER_SRC_BIT),
                           .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
                           .initialLayout = self->vk_image_layout,
                           .pNext = &(VkExternalMemoryImageCreateInfo) {
                               .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
                               .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
                               .pNext = &(VkImageDrmFormatModifierListCreateInfoEXT) {
                                   .sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_LIST_CREATE_INFO_EXT,
                                   .drmFormatModifierCount = n_modifiers,
                                   .pDrmFormatModifiers = modifiers
                               }
                           },
                       },
                       NULL,
                       &self->vk_image);
  if (res != VK_SUCCESS)
    {
      gsk_vulkan_handle_result (res, "vkCreateImage");
      return NULL;
    }

  vkGetImageMemoryRequirements (vk_device,
                                self->vk_image,
                                &requirements);

  self->allocator = gsk_vulkan_device_get_external_allocator (device);
  gsk_vulkan_allocator_ref (self->allocator);

  gsk_vulkan_alloc (self->allocator,
                    requirements.size,
                    requirements.alignment,
                    &self->allocation);

  GSK_VK_CHECK (vkAllocateMemory, vk_device,
                                  &(VkMemoryAllocateInfo) {
                                      .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
                                      .allocationSize = requirements.size,
                                      .memoryTypeIndex = g_bit_nth_lsf (requirements.memoryTypeBits, -1),
                                      .pNext = &(VkExportMemoryAllocateInfo) {
                                          .sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO,
                                          .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
                                      }
                                  },
                                  NULL,
                                  &self->allocation.vk_memory);

  GSK_VK_CHECK (vkBindImageMemory, vk_device,
                                   self->vk_image,
                                   self->allocation.vk_memory,
                                   self->allocation.offset);

  gsk_vulkan_image_create_view (self, vk_format, &vk_components, VK_NULL_HANDLE);

  return GSK_GPU_IMAGE (self);
}

GskGpuImage *
gsk_vulkan_image_new_for_dmabuf (GskVulkanDevice *device,
                                 gsize            width,
                                 gsize            height,
                                 const GdkDmabuf *dmabuf,
                                 gboolean         premultiplied)
{
  GskVulkanImage *self;
  VkDevice vk_device;
  VkFormat vk_format;
  VkComponentMapping vk_components;
  VkSamplerYcbcrConversion vk_conversion;
  PFN_vkGetMemoryFdPropertiesKHR func_vkGetMemoryFdPropertiesKHR;
  gsize i;
  int fd;
  VkResult res;
  GdkMemoryFormat format;
  GskGpuImageFlags flags;
  gboolean is_yuv;

  if (!gsk_vulkan_device_has_feature (device, GDK_VULKAN_FEATURE_DMABUF))
    {
      GDK_DEBUG (DMABUF, "Vulkan does not support dmabufs");
      return NULL;
    }

  if (!gdk_dmabuf_get_memory_format (dmabuf->fourcc, premultiplied, &format))
    {
      /* We should never get dmabufs with fourccs we've never checked we support */
      g_return_val_if_reached (NULL);
    }

  vk_device = gsk_vulkan_device_get_vk_device (device);
  func_vkGetMemoryFdPropertiesKHR = (PFN_vkGetMemoryFdPropertiesKHR) vkGetDeviceProcAddr (vk_device, "vkGetMemoryFdPropertiesKHR");

  vk_format = gdk_dmabuf_get_vk_format (dmabuf->fourcc, &vk_components);
  if (vk_format == VK_FORMAT_UNDEFINED)
    {
      GDK_DEBUG (DMABUF, "GTK's Vulkan doesn't support fourcc %.4s", (char *) &dmabuf->fourcc);
      return NULL;
    }
  if (!gdk_dmabuf_fourcc_is_yuv (dmabuf->fourcc, &is_yuv))
    {
      g_assert_not_reached ();
    }

  /* FIXME: Add support for disjoint images */
  if (gdk_dmabuf_is_disjoint (dmabuf))
    {
      GDK_DEBUG (DMABUF, "FIXME: Add support for disjoint dmabufs to Vulkan");
      return NULL;
    }

  if (!gsk_vulkan_device_supports_format (device,
                                          vk_format,
                                          dmabuf->modifier,
                                          dmabuf->n_planes,
                                          VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT,
                                          VK_IMAGE_USAGE_SAMPLED_BIT,
                                          width, height,
                                          &flags))
    {
      GDK_DEBUG (DMABUF, "Vulkan driver does not support format %.4s::%016llx with %u planes",
                 (char *) &dmabuf->fourcc, (unsigned long long) dmabuf->modifier, dmabuf->n_planes);
      return NULL;
    }

  self = g_object_new (GSK_TYPE_VULKAN_IMAGE, NULL);

  self->device = g_object_ref (device);
  self->vk_tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT;
  self->vk_format = vk_format;
  self->vk_pipeline_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
  self->vk_image_layout = VK_IMAGE_LAYOUT_GENERAL;
  self->vk_access = 0;

  res  = vkCreateImage (vk_device,
                        &(VkImageCreateInfo) {
                            .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
                            .flags = 0, //disjoint ? VK_IMAGE_CREATE_DISJOINT_BIT : 0,
                            .imageType = VK_IMAGE_TYPE_2D,
                            .format = vk_format,
                            .extent = { width, height, 1 },
                            .mipLevels = 1,
                            .arrayLayers = 1,
                            .samples = VK_SAMPLE_COUNT_1_BIT,
                            .tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT,
                            .usage = VK_IMAGE_USAGE_SAMPLED_BIT |
                                     (flags & GSK_GPU_IMAGE_NO_BLIT ? 0 : VK_IMAGE_USAGE_TRANSFER_SRC_BIT),
                            .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
                            .initialLayout = self->vk_image_layout,
                            .pNext = &(VkExternalMemoryImageCreateInfo) {
                                .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
                                .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
                                .pNext = &(VkImageDrmFormatModifierExplicitCreateInfoEXT) {
                                    .sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT,
                                    .drmFormatModifier = dmabuf->modifier,
                                    .drmFormatModifierPlaneCount = dmabuf->n_planes,
                                    .pPlaneLayouts = (VkSubresourceLayout[4]) {
                                        {
                                            .offset = dmabuf->planes[0].offset,
                                            .rowPitch = dmabuf->planes[0].stride,
                                        },
                                        {
                                            .offset = dmabuf->planes[1].offset,
                                            .rowPitch = dmabuf->planes[1].stride,
                                        },
                                        {
                                            .offset = dmabuf->planes[2].offset,
                                            .rowPitch = dmabuf->planes[2].stride,
                                        },
                                        {
                                            .offset = dmabuf->planes[3].offset,
                                            .rowPitch = dmabuf->planes[3].stride,
                                        },
                                    },
                                }
                            },
                        },
                        NULL,
                        &self->vk_image);
  if (res != VK_SUCCESS)
    {
      GDK_DEBUG (DMABUF, "vkCreateImage() failed: %s", gdk_vulkan_strerror (res));
      return NULL;
    }

  gsk_gpu_image_setup (GSK_GPU_IMAGE (self),
                       flags |
                       (gdk_memory_format_alpha (format) == GDK_MEMORY_ALPHA_STRAIGHT ? GSK_GPU_IMAGE_STRAIGHT_ALPHA : 0) |
                       (is_yuv ? (GSK_GPU_IMAGE_EXTERNAL | GSK_GPU_IMAGE_NO_BLIT) : 0) |
                       (gsk_component_mapping_is_framebuffer_compatible (&vk_components) ? 0 : GSK_GPU_IMAGE_NO_BLIT),
                       format,
                       width, height);

  self->allocator = gsk_vulkan_device_get_external_allocator (device);
  gsk_vulkan_allocator_ref (self->allocator);

  fd = fcntl (dmabuf->planes[0].fd, F_DUPFD_CLOEXEC, (int) 3);
  if (fd < 0)
    {
      GDK_DEBUG (DMABUF, "Vulkan failed to dup() fd: %s", g_strerror (errno));
      vkDestroyImage (vk_device, self->vk_image, NULL);
      return NULL;
    }

  for (i = 0; i < 1 /* disjoint ? dmabuf->n_planes : 1 */; i++)
    {
      VkMemoryFdPropertiesKHR fd_props = {
          .sType = VK_STRUCTURE_TYPE_MEMORY_FD_PROPERTIES_KHR,
      };
      VkMemoryRequirements2 requirements = {
          .sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2,
      };

      GSK_VK_CHECK (func_vkGetMemoryFdPropertiesKHR, vk_device,
                                                     VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
                                                     fd,
                                                     &fd_props);

      vkGetImageMemoryRequirements2 (vk_device,
                                     &(VkImageMemoryRequirementsInfo2) {
                                         .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2,
                                         .image = self->vk_image,
                                         //.pNext = !disjoint ? NULL : &(VkImagePlaneMemoryRequirementsInfo) {
                                         //    .sType = VK_STRUCTURE_TYPE_IMAGE_PLANE_MEMORY_REQUIREMENTS_INFO,
                                         //    .planeAspect = aspect_flags[i]
                                         //},
                                     },
                                     &requirements);

      if (gsk_vulkan_device_has_feature (device, GDK_VULKAN_FEATURE_SEMAPHORE_IMPORT))
        {
          int sync_file_fd = gdk_dmabuf_export_sync_file (fd, DMA_BUF_SYNC_READ);
          if (sync_file_fd >= 0)
            {
              PFN_vkImportSemaphoreFdKHR func_vkImportSemaphoreFdKHR;
              func_vkImportSemaphoreFdKHR = (PFN_vkImportSemaphoreFdKHR) vkGetDeviceProcAddr (vk_device, "vkImportSemaphoreFdKHR");

              GSK_VK_CHECK (vkCreateSemaphore, vk_device,
                                               &(VkSemaphoreCreateInfo) {
                                                   .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
                                               },
                                               NULL,
                                               &self->vk_semaphore);

              GSK_VK_CHECK (func_vkImportSemaphoreFdKHR, vk_device,
                                                         &(VkImportSemaphoreFdInfoKHR) {
                                                             .sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR,
                                                             .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
                                                             .flags = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT,
                                                             .semaphore = self->vk_semaphore,
                                                             .fd = sync_file_fd,
                                                         });
            }
        }

      gsk_vulkan_alloc (self->allocator,
                        requirements.memoryRequirements.size,
                        requirements.memoryRequirements.alignment,
                        &self->allocation);
      GSK_VK_CHECK (vkAllocateMemory, vk_device,
                                      &(VkMemoryAllocateInfo) {
                                          .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
                                          .allocationSize = requirements.memoryRequirements.size,
                                          .memoryTypeIndex = g_bit_nth_lsf (fd_props.memoryTypeBits, -1),
                                          .pNext = &(VkImportMemoryFdInfoKHR) {
                                              .sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR,
                                              .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
                                              .fd = fd,
                                              .pNext = &(VkMemoryDedicatedAllocateInfo) {
                                                  .sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO,
                                                  .image = self->vk_image,
                                              }
                                          }
                                      },
                                      NULL,
                                      &self->allocation.vk_memory);
    }

#if 1
  GSK_VK_CHECK (vkBindImageMemory2, gsk_vulkan_device_get_vk_device (self->device),
                                    1,
                                    &(VkBindImageMemoryInfo) {
                                        .sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO,
                                        .image = self->vk_image,
                                        .memory = self->allocation.vk_memory,
                                        .memoryOffset = self->allocation.offset,
                                    });
#else
  GSK_VK_CHECK (vkBindImageMemory2, gsk_vulkan_device_get_vk_device (self->device),
                                    dmabuf->n_planes,
                                    (VkBindImageMemoryInfo[4]) {
                                        {
                                            .sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO,
                                            .image = self->vk_image,
                                            .memory = self->allocation.vk_memory,
                                            .memoryOffset = dmabuf->planes[0].offset,
                                            .pNext = &(VkBindImagePlaneMemoryInfo) {
                                                .sType = VK_STRUCTURE_TYPE_BIND_IMAGE_PLANE_MEMORY_INFO,
                                                .planeAspect = VK_IMAGE_ASPECT_MEMORY_PLANE_0_BIT_EXT,
                                            },
                                        },
                                        {
                                            .sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO,
                                            .image = self->vk_image,
                                            .memory = self->allocation.vk_memory,
                                            .memoryOffset = dmabuf->planes[1].offset,
                                            .pNext = &(VkBindImagePlaneMemoryInfo) {
                                                .sType = VK_STRUCTURE_TYPE_BIND_IMAGE_PLANE_MEMORY_INFO,
                                                .planeAspect = VK_IMAGE_ASPECT_MEMORY_PLANE_1_BIT_EXT,
                                            },

                                        },
                                        {
                                            .sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO,
                                            .image = self->vk_image,
                                            .memory = self->allocation.vk_memory,
                                            .memoryOffset = dmabuf->planes[2].offset,
                                            .pNext = &(VkBindImagePlaneMemoryInfo) {
                                                .sType = VK_STRUCTURE_TYPE_BIND_IMAGE_PLANE_MEMORY_INFO,
                                                .planeAspect = VK_IMAGE_ASPECT_MEMORY_PLANE_2_BIT_EXT,
                                            },
                                        },
                                        {
                                            .sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO,
                                            .image = self->vk_image,
                                            .memory = self->allocation.vk_memory,
                                            .memoryOffset = dmabuf->planes[3].offset,
                                            .pNext = &(VkBindImagePlaneMemoryInfo) {
                                                .sType = VK_STRUCTURE_TYPE_BIND_IMAGE_PLANE_MEMORY_INFO,
                                                .planeAspect = VK_IMAGE_ASPECT_MEMORY_PLANE_3_BIT_EXT,
                                            },
                                        }
                                    });
#endif

  if (is_yuv)
    {
      self->ycbcr = gsk_vulkan_device_get_ycbcr (device, vk_format);
      gsk_vulkan_ycbcr_ref (self->ycbcr);
      vk_conversion = gsk_vulkan_ycbcr_get_vk_conversion (self->ycbcr);
    }
  else
    vk_conversion = VK_NULL_HANDLE;

  gsk_vulkan_image_create_view (self,
                                vk_format,
                                &vk_components,
                                vk_conversion);

  GDK_DEBUG (DMABUF, "Vulkan uploaded %zux%zu %.4s::%016llx %sdmabuf",
             width, height,
             (char *) &dmabuf->fourcc, (unsigned long long) dmabuf->modifier,
             is_yuv ? "YUV " : "");

  return GSK_GPU_IMAGE (self);
}

static void
close_the_fd (gpointer the_fd)
{
  close (GPOINTER_TO_INT (the_fd));
}

static guint
gsk_vulkan_image_get_n_planes (GskVulkanImage *self,
                               guint64         modifier)
{
  VkDrmFormatModifierPropertiesEXT drm_mod_properties[100];
  VkDrmFormatModifierPropertiesListEXT drm_properties = {
    .sType = VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT,
    .drmFormatModifierCount = G_N_ELEMENTS (drm_mod_properties),
    .pDrmFormatModifierProperties = drm_mod_properties,
  };
  VkFormatProperties2 properties = {
    .sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2,
    .pNext = &drm_properties
  };
  gsize i;

  vkGetPhysicalDeviceFormatProperties2 (gsk_vulkan_device_get_vk_physical_device (self->device),
                                        self->vk_format,
                                        &properties);
  
  for (i = 0; i < drm_properties.drmFormatModifierCount; i++)
    {
      if (drm_mod_properties[i].drmFormatModifier == modifier)
        return drm_mod_properties[i].drmFormatModifierPlaneCount;
    }

  g_return_val_if_reached (0);
}

GdkTexture *
gsk_vulkan_image_to_dmabuf_texture (GskVulkanImage *self)
{
  GskGpuImage *image = GSK_GPU_IMAGE (self);
  GdkDmabufTextureBuilder *builder;
  GError *error = NULL;
  PFN_vkGetImageDrmFormatModifierPropertiesEXT func_vkGetImageDrmFormatModifierPropertiesEXT;
  PFN_vkGetMemoryFdKHR func_vkGetMemoryFdKHR;
  VkImageDrmFormatModifierPropertiesEXT properties = {
    .sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_PROPERTIES_EXT,
  };
  VkSubresourceLayout layout;
  VkDevice vk_device;
  GdkTexture *texture;
  VkResult res;
  guint32 fourcc;
  int fd;
  guint plane, n_planes;

  if (!(gsk_gpu_image_get_flags (image) & GSK_GPU_IMAGE_EXTERNAL))
    return FALSE;
 
  fourcc = gdk_memory_format_get_dmabuf_fourcc (gsk_gpu_image_get_format (image));
  if (fourcc == 0)
    return FALSE;

  vk_device = gsk_vulkan_device_get_vk_device (self->device);

  func_vkGetImageDrmFormatModifierPropertiesEXT = (PFN_vkGetImageDrmFormatModifierPropertiesEXT)
    vkGetDeviceProcAddr (vk_device, "vkGetImageDrmFormatModifierPropertiesEXT");
  func_vkGetMemoryFdKHR = (PFN_vkGetMemoryFdKHR) vkGetDeviceProcAddr (vk_device, "vkGetMemoryFdKHR");
  res = GSK_VK_CHECK (func_vkGetImageDrmFormatModifierPropertiesEXT, vk_device, self->vk_image, &properties);
  if (res != VK_SUCCESS)
    return FALSE;
  n_planes = gsk_vulkan_image_get_n_planes (self, properties.drmFormatModifier);
  if (n_planes == 0 || n_planes > GDK_DMABUF_MAX_PLANES)
    return FALSE;
  res = GSK_VK_CHECK (func_vkGetMemoryFdKHR, vk_device,
                                             &(VkMemoryGetFdInfoKHR) {
                                                 .sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR,
                                                 .memory = self->allocation.vk_memory,
                                                 .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
                                             },
                                             &fd);
  if (res != VK_SUCCESS)
    return FALSE;

  builder = gdk_dmabuf_texture_builder_new ();
  gdk_dmabuf_texture_builder_set_display (builder, gsk_gpu_device_get_display (GSK_GPU_DEVICE (self->device)));
  gdk_dmabuf_texture_builder_set_width (builder, gsk_gpu_image_get_width (image));
  gdk_dmabuf_texture_builder_set_height (builder, gsk_gpu_image_get_height (image));
  gdk_dmabuf_texture_builder_set_fourcc (builder, fourcc);
  gdk_dmabuf_texture_builder_set_modifier (builder, properties.drmFormatModifier);
  gdk_dmabuf_texture_builder_set_premultiplied (builder, !(gsk_gpu_image_get_flags (image) & GSK_GPU_IMAGE_STRAIGHT_ALPHA));
  gdk_dmabuf_texture_builder_set_n_planes (builder, n_planes);
  
  for (plane = 0; plane < n_planes; plane++)
    {
      static const VkImageAspectFlagBits aspect[GDK_DMABUF_MAX_PLANES] = {
        VK_IMAGE_ASPECT_MEMORY_PLANE_0_BIT_EXT,
        VK_IMAGE_ASPECT_MEMORY_PLANE_1_BIT_EXT,
        VK_IMAGE_ASPECT_MEMORY_PLANE_2_BIT_EXT,
        VK_IMAGE_ASPECT_MEMORY_PLANE_3_BIT_EXT
      };
      vkGetImageSubresourceLayout (vk_device,
                                   self->vk_image,
                                   &(VkImageSubresource) {
                                       .aspectMask = aspect[plane],
                                       .mipLevel = 0,
                                       .arrayLayer = 0
                                   },
                                   &layout);
      gdk_dmabuf_texture_builder_set_fd (builder, plane, fd);
      gdk_dmabuf_texture_builder_set_stride (builder, plane, layout.rowPitch);
      gdk_dmabuf_texture_builder_set_offset (builder, plane, layout.offset);
    }

  texture = gdk_dmabuf_texture_builder_build (builder, close_the_fd, GINT_TO_POINTER (fd), &error);
  g_object_unref (builder);
  if (texture == NULL)
    {
      GDK_DEBUG (VULKAN, "Failed to create dmabuf texture: %s", error->message);
      g_clear_error (&error);
      close (fd);
      return NULL;
    }

  gsk_gpu_image_toggle_ref_texture (GSK_GPU_IMAGE (self), texture);

  return texture;
}
#endif

static void
gsk_vulkan_image_get_projection_matrix (GskGpuImage       *image,
                                        graphene_matrix_t *out_projection)
{
  graphene_matrix_t scale_z;

  GSK_GPU_IMAGE_CLASS (gsk_vulkan_image_parent_class)->get_projection_matrix (image, out_projection);

  graphene_matrix_init_from_float (&scale_z,
                                   (float[16]) {
                                       1,   0,   0,   0,
                                       0,   1,   0,   0,
                                       0,   0, 0.5,   0,
                                       0,   0, 0.5,   1
                                   });

  graphene_matrix_multiply (out_projection, &scale_z, out_projection);
}

static void
gsk_vulkan_image_finalize (GObject *object)
{
  GskVulkanImage *self = GSK_VULKAN_IMAGE (object);
  VkDevice vk_device;
  gsize i;

  vk_device = gsk_vulkan_device_get_vk_device (self->device);

  g_clear_pointer (&self->ycbcr, gsk_vulkan_ycbcr_unref);

  for (i = 0; i < GSK_GPU_SAMPLER_N_SAMPLERS; i++)
    {
      if (self->descriptor_sets[i].vk_descriptor_set)
        gsk_vulkan_device_free_descriptor (self->device,
                                           self->descriptor_sets[i].pool_id,
                                           self->descriptor_sets[i].vk_descriptor_set);
    }

  if (self->vk_framebuffer != VK_NULL_HANDLE)
    vkDestroyFramebuffer (vk_device, self->vk_framebuffer, NULL);

  if (self->vk_framebuffer_image_view != VK_NULL_HANDLE &&
      self->vk_framebuffer_image_view != self->vk_image_view)
    vkDestroyImageView (vk_device, self->vk_framebuffer_image_view, NULL);

  if (self->vk_image_view != VK_NULL_HANDLE)
    vkDestroyImageView (vk_device, self->vk_image_view, NULL);

  if (self->vk_semaphore != VK_NULL_HANDLE)
    vkDestroySemaphore (vk_device, self->vk_semaphore, NULL);

  /* memory is NULL for for_swapchain() images, where we don't own
   * the VkImage */
  if (self->allocator)
    {
      vkDestroyImage (vk_device, self->vk_image, NULL);
      gsk_vulkan_free (self->allocator, &self->allocation);
      gsk_vulkan_allocator_unref (self->allocator);
    }

  g_object_unref (self->device);

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

static void
gsk_vulkan_image_class_init (GskVulkanImageClass *klass)
{
  GskGpuImageClass *image_class = GSK_GPU_IMAGE_CLASS (klass);
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  image_class->get_projection_matrix = gsk_vulkan_image_get_projection_matrix;

  object_class->finalize = gsk_vulkan_image_finalize;
}

static void
gsk_vulkan_image_init (GskVulkanImage *self)
{
}

VkFramebuffer
gsk_vulkan_image_get_vk_framebuffer (GskVulkanImage *self,
                                     VkRenderPass    render_pass)
{
  VkDevice vk_device;

  if (self->vk_framebuffer)
    return self->vk_framebuffer;

  vk_device = gsk_vulkan_device_get_vk_device (self->device);

  if (gsk_gpu_image_get_flags (GSK_GPU_IMAGE (self)) & GSK_GPU_IMAGE_CAN_MIPMAP)
    {
      GSK_VK_CHECK (vkCreateImageView, vk_device,
                                     &(VkImageViewCreateInfo) {
                                         .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
                                         .image = self->vk_image,
                                         .viewType = VK_IMAGE_VIEW_TYPE_2D,
                                         .format = self->vk_format,
                                         .subresourceRange = {
                                             .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
                                             .baseMipLevel = 0,
                                             .levelCount = 1,
                                             .baseArrayLayer = 0,
                                             .layerCount = 1,
                                         }
                                     },
                                 NULL,
                                 &self->vk_framebuffer_image_view);
    }
  else
    {
      self->vk_framebuffer_image_view = self->vk_image_view;
    }

  GSK_VK_CHECK (vkCreateFramebuffer, vk_device,
                                     &(VkFramebufferCreateInfo) {
                                         .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
                                         .renderPass = render_pass,
                                         .attachmentCount = 1,
                                         .pAttachments = (VkImageView[1]) {
                                             self->vk_framebuffer_image_view,
                                         },
                                         .width = gsk_gpu_image_get_width (GSK_GPU_IMAGE (self)),
                                         .height = gsk_gpu_image_get_height (GSK_GPU_IMAGE (self)),
                                         .layers = 1
                                     },
                                     NULL,
                                     &self->vk_framebuffer);

  return self->vk_framebuffer;
}

VkDescriptorSet
gsk_vulkan_image_get_vk_descriptor_set (GskVulkanImage *self,
                                        GskGpuSampler   sampler)
{
  if (!self->descriptor_sets[sampler].vk_descriptor_set)
    {
      self->descriptor_sets[sampler].vk_descriptor_set =
        gsk_vulkan_device_allocate_descriptor (self->device,
                                               self->ycbcr ? gsk_vulkan_ycbcr_get_vk_descriptor_set_layout (self->ycbcr)
                                                           : gsk_vulkan_device_get_vk_image_set_layout (self->device),
                                               &self->descriptor_sets[sampler].pool_id);

      vkUpdateDescriptorSets (gsk_vulkan_device_get_vk_device (self->device),
                              1,
                              &(VkWriteDescriptorSet) {
                                  .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
                                  .dstSet = self->descriptor_sets[sampler].vk_descriptor_set,
                                  .dstBinding = 0,
                                  .dstArrayElement = 0,
                                  .descriptorCount = 1,
                                  .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
                                  .pImageInfo = &(VkDescriptorImageInfo) {
                                      .sampler = self->ycbcr ? gsk_vulkan_ycbcr_get_vk_sampler (self->ycbcr)
                                                             : gsk_vulkan_device_get_vk_sampler (self->device, sampler),
                                      .imageView = self->vk_image_view,
                                      .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
                                  },
                              },
                              0,
                              NULL);
    }

  return self->descriptor_sets[sampler].vk_descriptor_set;
}

GskVulkanYcbcr *
gsk_vulkan_image_get_ycbcr (GskVulkanImage *self)
{
  return self->ycbcr;
}

VkImage
gsk_vulkan_image_get_vk_image (GskVulkanImage *self)
{
  return self->vk_image;
}

VkImageView
gsk_vulkan_image_get_vk_image_view (GskVulkanImage *self)
{
  return self->vk_image_view;
}

VkPipelineStageFlags
gsk_vulkan_image_get_vk_pipeline_stage (GskVulkanImage *self)
{
  return self->vk_pipeline_stage;
}

VkImageLayout
gsk_vulkan_image_get_vk_image_layout (GskVulkanImage *self)
{
  return self->vk_image_layout;
}

VkAccessFlags
gsk_vulkan_image_get_vk_access (GskVulkanImage *self)
{
  return self->vk_access;
}

void
gsk_vulkan_image_set_vk_image_layout (GskVulkanImage       *self,
                                      VkPipelineStageFlags  stage,
                                      VkImageLayout         image_layout,
                                      VkAccessFlags         access)
{
  self->vk_pipeline_stage = stage;
  self->vk_image_layout = image_layout;
  self->vk_access = access;
}

void
gsk_vulkan_image_transition (GskVulkanImage       *self,
                             GskVulkanSemaphores  *semaphores,
                             VkCommandBuffer       command_buffer,
                             VkPipelineStageFlags  stage,
                             VkImageLayout         image_layout,
                             VkAccessFlags         access)
{
  if (self->vk_pipeline_stage == stage &&
      self->vk_image_layout == image_layout &&
      self->vk_access == access)
    return;

  if (self->vk_pipeline_stage == VK_IMAGE_LAYOUT_GENERAL &&
      self->vk_semaphore)
    {
      gsk_vulkan_semaphores_add_wait (semaphores, self->vk_semaphore, stage);
    }

  vkCmdPipelineBarrier (command_buffer,
                        self->vk_pipeline_stage,
                        stage,
                        0,
                        0, NULL,
                        0, NULL,
                        1, &(VkImageMemoryBarrier) {
                            .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
                            .srcAccessMask = self->vk_access,
                            .dstAccessMask = access,
                            .oldLayout = self->vk_image_layout,
                            .newLayout = image_layout,
                            .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
                            .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
                            .image = self->vk_image,
                            .subresourceRange = {
                              .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
                              .baseMipLevel = 0,
                              .levelCount = VK_REMAINING_MIP_LEVELS,
                              .baseArrayLayer = 0,
                              .layerCount = 1
                            },
                        });

  gsk_vulkan_image_set_vk_image_layout (self, stage, image_layout, access);
}

VkFormat
gsk_vulkan_image_get_vk_format (GskVulkanImage *self)
{
  return self->vk_format;
}

