/*
 * Copyright 2019 Google LLC
 * SPDX-License-Identifier: MIT
 *
 * based in part on anv and radv which are:
 * Copyright © 2015 Intel Corporation
 * Copyright © 2016 Red Hat.
 * Copyright © 2016 Bas Nieuwenhuizen
 */

#include "vn_pipeline.h"

#include "venus-protocol/vn_protocol_driver_pipeline.h"
#include "venus-protocol/vn_protocol_driver_pipeline_cache.h"
#include "venus-protocol/vn_protocol_driver_pipeline_layout.h"
#include "venus-protocol/vn_protocol_driver_shader_module.h"

#include "vn_descriptor_set.h"
#include "vn_device.h"
#include "vn_physical_device.h"
#include "vn_render_pass.h"

/**
 * Fields in the VkGraphicsPipelineCreateInfo pNext chain that we must track
 * to determine which fields are valid and which must be erased.
 */
struct vn_graphics_pipeline_info_self {
   union {
      /* Bitmask exists for testing if any field is set. */
      uint32_t mask;

      /* Group the fixes by Vulkan struct. Within each group, sort by struct
       * order.
       */
      struct {
         /** VkGraphicsPipelineCreateInfo::pStages */
         bool shader_stages : 1;
         /** VkGraphicsPipelineCreateInfo::pVertexInputState */
         bool vertex_input_state : 1;
         /** VkGraphicsPipelineCreateInfo::pInputAssemblyState */
         bool input_assembly_state : 1;
         /** VkGraphicsPipelineCreateInfo::pTessellationState */
         bool tessellation_state : 1;
         /** VkGraphicsPipelineCreateInfo::pViewportState */
         bool viewport_state : 1;
         /** VkGraphicsPipelineCreateInfo::pRasterizationState */
         bool rasterization_state : 1;
         /** VkGraphicsPipelineCreateInfo::pMultisampleState */
         bool multisample_state : 1;
         /** VkGraphicsPipelineCreateInfo::pDepthStencilState */
         bool depth_stencil_state : 1;
         /** VkGraphicsPipelineCreateInfo::pColorBlendState */
         bool color_blend_state : 1;
         /** VkGraphicsPipelineCreateInfo::layout */
         bool pipeline_layout : 1;
         /** VkGraphicsPipelineCreateInfo::renderPass */
         bool render_pass : 1;
         /** VkGraphicsPipelineCreateInfo::basePipelineHandle */
         bool base_pipeline_handle : 1;

         /** VkPipelineViewportStateCreateInfo::pViewports */
         bool viewport_state_viewports : 1;
         /** VkPipelineViewportStateCreateInfo::pScissors */
         bool viewport_state_scissors : 1;

         /** VkPipelineMultisampleStateCreateInfo::pSampleMask */
         bool multisample_state_sample_mask : 1;
         /** VkPipelineMultisampleStateCreateInfo::pNext
          *  VkPipelineSampleLocationsStateCreateInfoEXT::sampleLocationsInfo
          */
         bool multisample_state_sample_locations : 1;
      };
   };
};

static_assert(sizeof(struct vn_graphics_pipeline_info_self) ==
                 sizeof(((struct vn_graphics_pipeline_info_self){}).mask),
              "vn_graphics_pipeline_create_info_self::mask is too small");

/**
 * Fields in the VkGraphicsPipelineCreateInfo pNext chain that we must track
 * to determine which fields are valid and which must be erased.
 */
struct vn_graphics_pipeline_info_pnext {
   union {
      /* Bitmask exists for testing if any field is set. */
      uint32_t mask;

      /* Group the fixes by Vulkan struct. Within each group, sort by struct
       * order.
       */
      struct {
         /** VkPipelineRenderingCreateInfo, all format fields */
         bool rendering_info_formats : 1;
      };
   };
};

static_assert(sizeof(struct vn_graphics_pipeline_info_pnext) ==
                 sizeof(((struct vn_graphics_pipeline_info_pnext){}).mask),
              "vn_graphics_pipeline_create_info_pnext::mask is too small");

/**
 * Description of fixes needed for a single VkGraphicsPipelineCreateInfo
 * pNext chain.
 */
struct vn_graphics_pipeline_fix_desc {
   struct vn_graphics_pipeline_info_self self;
   struct vn_graphics_pipeline_info_pnext pnext;
};

/**
 * Typesafe bitmask for VkGraphicsPipelineLibraryFlagsEXT. Named members
 * reduce long lines.
 *
 * From the Vulkan 1.3.215 spec:
 *
 *    The state required for a graphics pipeline is divided into vertex input
 *    state, pre-rasterization shader state, fragment shader state, and
 *    fragment output state.
 */
struct vn_graphics_pipeline_library_state {
   union {
      VkGraphicsPipelineLibraryFlagsEXT mask;

      struct {
         /** VK_GRAPHICS_PIPELINE_LIBRARY_VERTEX_INPUT_INTERFACE_BIT_EXT */
         bool vertex_input : 1;
         /** VK_GRAPHICS_PIPELINE_LIBRARY_PRE_RASTERIZATION_SHADERS_BIT_EXT */
         bool pre_raster_shaders : 1;
         /** VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT */
         bool fragment_shader : 1;
         /** VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_OUTPUT_INTERFACE_BIT_EXT */
         bool fragment_output : 1;
      };
   };
};

/**
 * Compact bitmask for the subset of graphics VkDynamicState that
 * venus needs to track. Named members reduce long lines.
 *
 * We want a *compact* bitmask because enum VkDynamicState has large gaps due
 * to extensions.
 */
struct vn_graphics_dynamic_state {
   union {
      uint32_t mask;

      struct {
         /** VK_DYNAMIC_STATE_VERTEX_INPUT_EXT **/
         bool vertex_input : 1;
         /** VK_DYNAMIC_STATE_VIEWPORT */
         bool viewport : 1;
         /** VK_DYNAMIC_STATE_VIEWPORT_WITH_COUNT */
         bool viewport_with_count : 1;
         /** VK_DYNAMIC_STATE_SAMPLE_MASK_EXT */
         bool sample_mask : 1;
         /** VK_DYNAMIC_STATE_SCISSOR */
         bool scissor : 1;
         /** VK_DYNAMIC_STATE_SCISSOR_WITH_COUNT */
         bool scissor_with_count : 1;
         /** VK_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE */
         bool rasterizer_discard_enable : 1;
         /** VK_DYNAMIC_STATE_SAMPLE_LOCATIONS_EXT */
         bool sample_locations : 1;
         /** VK_DYNAMIC_STATE_SAMPLE_LOCATIONS_ENABLE_EXT */
         bool sample_locations_enable : 1;
      };
   };
};

/**
 * Graphics pipeline state that Venus tracks to determine which fixes are
 * required in the VkGraphicsPipelineCreateInfo pNext chain.
 *
 * This is the pipeline's fully linked state. That is, it includes the state
 * provided directly in VkGraphicsPipelineCreateInfo and the state provided
 * indirectly in VkPipelineLibraryCreateInfoKHR.
 */
struct vn_graphics_pipeline_state {
   /** The GPL state subsets that the pipeline provides. */
   struct vn_graphics_pipeline_library_state gpl;

   struct vn_graphics_dynamic_state dynamic;
   VkShaderStageFlags shader_stages;

   struct vn_render_pass_state {
      /**
       * The attachment aspects accessed by the pipeline.
       *
       * Valid if and only if VK_IMAGE_ASPECT_METADATA_BIT is unset.
       *
       * In a complete pipeline, this must be valid (and may be empty). In
       * a pipeline library, this may be invalid. We initialize this to be
       * invalid, and it remains invalid until we read the attachment info in
       * the VkGraphicsPipelineCreateInfo chain.
       *
       * The app provides the attachment info in
       * VkGraphicsPipelineCreateInfo::renderPass or
       * VkPipelineRenderingCreateInfo, but the validity of that info depends
       * on VkGraphicsPipelineLibraryFlagsEXT.
       */
      VkImageAspectFlags attachment_aspects;
   } render_pass;

   /** VkPipelineRasterizationStateCreateInfo::rasterizerDiscardEnable
    *
    * Valid if and only if gpl.pre_raster_shaders is set.
    */
   bool rasterizer_discard_enable;
};

struct vn_graphics_pipeline {
   struct vn_pipeline base;
   struct vn_graphics_pipeline_state state;
};

/**
 * Temporary storage for fixes in vkCreateGraphicsPipelines.
 *
 * Length of each array is vkCreateGraphicsPipelines::createInfoCount.
 */
struct vn_graphics_pipeline_fix_tmp {
   VkGraphicsPipelineCreateInfo *infos;
   VkPipelineMultisampleStateCreateInfo *multisample_state_infos;
   VkPipelineSampleLocationsStateCreateInfoEXT *sl_infos;
   VkPipelineViewportStateCreateInfo *viewport_state_infos;

   /* Fixing the pNext chain
    *
    * Extend when more extensions are supported.
    */
   VkGraphicsPipelineLibraryCreateInfoEXT *gpl_infos;
   VkPipelineCreateFlags2CreateInfo *flags2_infos;
   VkPipelineCreationFeedbackCreateInfo *feedback_infos;
   VkPipelineFragmentShadingRateStateCreateInfoKHR *fsr_infos;
   VkPipelineLibraryCreateInfoKHR *library_infos;
   VkPipelineRenderingCreateInfo *rendering_infos;
   VkPipelineRobustnessCreateInfo *robustness_infos;
   VkRenderingAttachmentLocationInfo *ral_infos;
   VkRenderingInputAttachmentIndexInfo *riai_infos;
};

/* shader module commands */

VKAPI_ATTR VkResult VKAPI_CALL
vn_CreateShaderModule(VkDevice device,
                      const VkShaderModuleCreateInfo *pCreateInfo,
                      const VkAllocationCallbacks *pAllocator,
                      VkShaderModule *pShaderModule)
{
   struct vn_device *dev = vn_device_from_handle(device);
   const VkAllocationCallbacks *alloc =
      pAllocator ? pAllocator : &dev->base.vk.alloc;

   struct vn_shader_module *mod =
      vk_zalloc(alloc, sizeof(*mod), VN_DEFAULT_ALIGN,
                VK_SYSTEM_ALLOCATION_SCOPE_OBJECT);
   if (!mod)
      return vn_error(dev->instance, VK_ERROR_OUT_OF_HOST_MEMORY);

   vn_object_base_init(&mod->base, VK_OBJECT_TYPE_SHADER_MODULE, &dev->base);

   VkShaderModule mod_handle = vn_shader_module_to_handle(mod);
   vn_async_vkCreateShaderModule(dev->primary_ring, device, pCreateInfo, NULL,
                                 &mod_handle);

   *pShaderModule = mod_handle;

   return VK_SUCCESS;
}

VKAPI_ATTR void VKAPI_CALL
vn_DestroyShaderModule(VkDevice device,
                       VkShaderModule shaderModule,
                       const VkAllocationCallbacks *pAllocator)
{
   struct vn_device *dev = vn_device_from_handle(device);
   struct vn_shader_module *mod = vn_shader_module_from_handle(shaderModule);
   const VkAllocationCallbacks *alloc =
      pAllocator ? pAllocator : &dev->base.vk.alloc;

   if (!mod)
      return;

   vn_async_vkDestroyShaderModule(dev->primary_ring, device, shaderModule,
                                  NULL);

   vn_object_base_fini(&mod->base);
   vk_free(alloc, mod);
}

/* pipeline layout commands */

static void
vn_pipeline_layout_destroy(struct vn_device *dev,
                           struct vn_pipeline_layout *pipeline_layout)
{
   const VkAllocationCallbacks *alloc = &dev->base.vk.alloc;
   if (pipeline_layout->push_descriptor_set_layout) {
      vn_descriptor_set_layout_unref(
         dev, pipeline_layout->push_descriptor_set_layout);
   }
   vn_async_vkDestroyPipelineLayout(
      dev->primary_ring, vn_device_to_handle(dev),
      vn_pipeline_layout_to_handle(pipeline_layout), NULL);

   vn_object_base_fini(&pipeline_layout->base);
   vk_free(alloc, pipeline_layout);
}

static inline struct vn_pipeline_layout *
vn_pipeline_layout_ref(struct vn_device *dev,
                       struct vn_pipeline_layout *pipeline_layout)
{
   vn_refcount_inc(&pipeline_layout->refcount);
   return pipeline_layout;
}

static inline void
vn_pipeline_layout_unref(struct vn_device *dev,
                         struct vn_pipeline_layout *pipeline_layout)
{
   if (vn_refcount_dec(&pipeline_layout->refcount))
      vn_pipeline_layout_destroy(dev, pipeline_layout);
}

VKAPI_ATTR VkResult VKAPI_CALL
vn_CreatePipelineLayout(VkDevice device,
                        const VkPipelineLayoutCreateInfo *pCreateInfo,
                        const VkAllocationCallbacks *pAllocator,
                        VkPipelineLayout *pPipelineLayout)
{
   struct vn_device *dev = vn_device_from_handle(device);
   /* ignore pAllocator as the pipeline layout is reference-counted */
   const VkAllocationCallbacks *alloc = &dev->base.vk.alloc;

   struct vn_pipeline_layout *layout =
      vk_zalloc(alloc, sizeof(*layout), VN_DEFAULT_ALIGN,
                VK_SYSTEM_ALLOCATION_SCOPE_DEVICE);
   if (!layout)
      return vn_error(dev->instance, VK_ERROR_OUT_OF_HOST_MEMORY);

   vn_object_base_init(&layout->base, VK_OBJECT_TYPE_PIPELINE_LAYOUT,
                       &dev->base);
   layout->refcount = VN_REFCOUNT_INIT(1);

   for (uint32_t i = 0; i < pCreateInfo->setLayoutCount; i++) {
      struct vn_descriptor_set_layout *descriptor_set_layout =
         vn_descriptor_set_layout_from_handle(pCreateInfo->pSetLayouts[i]);

      /* Avoid null derefs. pSetLayouts may contain VK_NULL_HANDLE.
       *
       * From the Vulkan 1.3.254 spec:
       *    VUID-VkPipelineLayoutCreateInfo-pSetLayouts-parameter
       *
       *    If setLayoutCount is not 0, pSetLayouts must be a valid pointer to
       *    an array of setLayoutCount valid or VK_NULL_HANDLE
       *    VkDescriptorSetLayout handles
       */
      if (descriptor_set_layout &&
          descriptor_set_layout->is_push_descriptor) {
         layout->push_descriptor_set_layout =
            vn_descriptor_set_layout_ref(dev, descriptor_set_layout);
         break;
      }
   }

   layout->has_push_constant_ranges = pCreateInfo->pushConstantRangeCount > 0;

   VkPipelineLayout layout_handle = vn_pipeline_layout_to_handle(layout);
   vn_async_vkCreatePipelineLayout(dev->primary_ring, device, pCreateInfo,
                                   NULL, &layout_handle);

   *pPipelineLayout = layout_handle;

   return VK_SUCCESS;
}

VKAPI_ATTR void VKAPI_CALL
vn_DestroyPipelineLayout(VkDevice device,
                         VkPipelineLayout pipelineLayout,
                         const VkAllocationCallbacks *pAllocator)
{
   struct vn_device *dev = vn_device_from_handle(device);
   struct vn_pipeline_layout *layout =
      vn_pipeline_layout_from_handle(pipelineLayout);

   if (!layout)
      return;

   vn_pipeline_layout_unref(dev, layout);
}

/* pipeline cache commands */

VKAPI_ATTR VkResult VKAPI_CALL
vn_CreatePipelineCache(VkDevice device,
                       const VkPipelineCacheCreateInfo *pCreateInfo,
                       const VkAllocationCallbacks *pAllocator,
                       VkPipelineCache *pPipelineCache)
{
   struct vn_device *dev = vn_device_from_handle(device);
   const VkAllocationCallbacks *alloc =
      pAllocator ? pAllocator : &dev->base.vk.alloc;

   struct vn_pipeline_cache *cache =
      vk_zalloc(alloc, sizeof(*cache), VN_DEFAULT_ALIGN,
                VK_SYSTEM_ALLOCATION_SCOPE_OBJECT);
   if (!cache)
      return vn_error(dev->instance, VK_ERROR_OUT_OF_HOST_MEMORY);

   vn_object_base_init(&cache->base, VK_OBJECT_TYPE_PIPELINE_CACHE,
                       &dev->base);

   VkPipelineCacheCreateInfo local_create_info;
   if (pCreateInfo->initialDataSize) {
      const struct vk_pipeline_cache_header *header =
         pCreateInfo->pInitialData;

      local_create_info = *pCreateInfo;
      local_create_info.initialDataSize -= header->header_size;
      local_create_info.pInitialData += header->header_size;
      pCreateInfo = &local_create_info;
   }

   VkPipelineCache cache_handle = vn_pipeline_cache_to_handle(cache);
   vn_async_vkCreatePipelineCache(dev->primary_ring, device, pCreateInfo,
                                  NULL, &cache_handle);

   *pPipelineCache = cache_handle;

   return VK_SUCCESS;
}

VKAPI_ATTR void VKAPI_CALL
vn_DestroyPipelineCache(VkDevice device,
                        VkPipelineCache pipelineCache,
                        const VkAllocationCallbacks *pAllocator)
{
   struct vn_device *dev = vn_device_from_handle(device);
   struct vn_pipeline_cache *cache =
      vn_pipeline_cache_from_handle(pipelineCache);
   const VkAllocationCallbacks *alloc =
      pAllocator ? pAllocator : &dev->base.vk.alloc;

   if (!cache)
      return;

   vn_async_vkDestroyPipelineCache(dev->primary_ring, device, pipelineCache,
                                   NULL);

   vn_object_base_fini(&cache->base);
   vk_free(alloc, cache);
}

static struct vn_ring *
vn_get_target_ring(struct vn_device *dev)
{
   if (vn_tls_get_async_pipeline_create())
      return dev->primary_ring;

   struct vn_ring *ring = vn_tls_get_ring(dev->instance);
   if (!ring)
      return NULL;

   if (ring != dev->primary_ring) {
      /* Ensure pipeline create and pipeline cache retrieval dependencies are
       * ready on the renderer side.
       *
       * TODO:
       * - For pipeline create, track ring seqnos of layout and renderpass
       *   objects it depends on, and only wait for those seqnos once.
       * - For pipeline cache retrieval, track ring seqno of pipeline cache
       *   object it depends on. Treat different sync mode separately.
       */
      vn_ring_wait_all(dev->primary_ring);
   }
   return ring;
}

VKAPI_ATTR VkResult VKAPI_CALL
vn_GetPipelineCacheData(VkDevice device,
                        VkPipelineCache pipelineCache,
                        size_t *pDataSize,
                        void *pData)
{
   struct vn_device *dev = vn_device_from_handle(device);
   struct vn_physical_device *physical_dev = dev->physical_device;
   struct vn_ring *target_ring = vn_get_target_ring(dev);

   struct vk_pipeline_cache_header *header = pData;
   VkResult result;
   if (!pData) {
      result = vn_call_vkGetPipelineCacheData(target_ring, device,
                                              pipelineCache, pDataSize, NULL);
      if (result != VK_SUCCESS)
         return vn_error(dev->instance, result);

      *pDataSize += sizeof(*header);
      return VK_SUCCESS;
   }

   if (*pDataSize <= sizeof(*header)) {
      *pDataSize = 0;
      return VK_INCOMPLETE;
   }

   const struct vk_properties *props = &physical_dev->base.vk.properties;
   header->header_size = sizeof(*header);
   header->header_version = VK_PIPELINE_CACHE_HEADER_VERSION_ONE;
   header->vendor_id = props->vendorID;
   header->device_id = props->deviceID;
   memcpy(header->uuid, props->pipelineCacheUUID, VK_UUID_SIZE);

   *pDataSize -= header->header_size;
   result =
      vn_call_vkGetPipelineCacheData(target_ring, device, pipelineCache,
                                     pDataSize, pData + header->header_size);
   if (result < VK_SUCCESS)
      return vn_error(dev->instance, result);

   *pDataSize += header->header_size;

   return result;
}

VKAPI_ATTR VkResult VKAPI_CALL
vn_MergePipelineCaches(VkDevice device,
                       VkPipelineCache dstCache,
                       uint32_t srcCacheCount,
                       const VkPipelineCache *pSrcCaches)
{
   struct vn_device *dev = vn_device_from_handle(device);

   vn_async_vkMergePipelineCaches(dev->primary_ring, device, dstCache,
                                  srcCacheCount, pSrcCaches);

   return VK_SUCCESS;
}

/* pipeline commands */

static struct vn_graphics_pipeline *
vn_graphics_pipeline_from_handle(VkPipeline pipeline_h)
{
   struct vn_pipeline *p = vn_pipeline_from_handle(pipeline_h);
   assert(p->type == VN_PIPELINE_TYPE_GRAPHICS);
   return (struct vn_graphics_pipeline *)p;
}

static bool
vn_create_pipeline_handles(struct vn_device *dev,
                           enum vn_pipeline_type type,
                           uint32_t pipeline_count,
                           VkPipeline *pipeline_handles,
                           const VkAllocationCallbacks *alloc)
{
   size_t pipeline_size;

   switch (type) {
   case VN_PIPELINE_TYPE_GRAPHICS:
      pipeline_size = sizeof(struct vn_graphics_pipeline);
      break;
   case VN_PIPELINE_TYPE_COMPUTE:
   case VN_PIPELINE_TYPE_RAY_TRACING:
      pipeline_size = sizeof(struct vn_pipeline);
      break;
   }

   for (uint32_t i = 0; i < pipeline_count; i++) {
      struct vn_pipeline *pipeline =
         vk_zalloc(alloc, pipeline_size, VN_DEFAULT_ALIGN,
                   VK_SYSTEM_ALLOCATION_SCOPE_OBJECT);

      if (!pipeline) {
         for (uint32_t j = 0; j < i; j++) {
            pipeline = vn_pipeline_from_handle(pipeline_handles[j]);
            vn_object_base_fini(&pipeline->base);
            vk_free(alloc, pipeline);
         }

         memset(pipeline_handles, 0,
                pipeline_count * sizeof(pipeline_handles[0]));
         return false;
      }

      vn_object_base_init(&pipeline->base, VK_OBJECT_TYPE_PIPELINE,
                          &dev->base);
      pipeline->type = type;
      pipeline_handles[i] = vn_pipeline_to_handle(pipeline);
   }

   return true;
}

static void
vn_destroy_pipeline_handles_internal(struct vn_device *dev,
                                     uint32_t pipeline_count,
                                     VkPipeline *pipeline_handles,
                                     const VkAllocationCallbacks *alloc,
                                     bool failed_only)
{
   for (uint32_t i = 0; i < pipeline_count; i++) {
      struct vn_pipeline *pipeline =
         vn_pipeline_from_handle(pipeline_handles[i]);

      if (!failed_only || pipeline->base.id == 0) {
         if (pipeline->layout) {
            vn_pipeline_layout_unref(dev, pipeline->layout);
         }
         vn_object_base_fini(&pipeline->base);
         vk_free(alloc, pipeline);
         pipeline_handles[i] = VK_NULL_HANDLE;
      }
   }
}

static inline void
vn_destroy_pipeline_handles(struct vn_device *dev,
                            uint32_t pipeline_count,
                            VkPipeline *pipeline_handles,
                            const VkAllocationCallbacks *alloc)
{
   vn_destroy_pipeline_handles_internal(dev, pipeline_count, pipeline_handles,
                                        alloc, false);
}

static inline void
vn_destroy_failed_pipeline_handles(struct vn_device *dev,
                                   uint32_t pipeline_count,
                                   VkPipeline *pipeline_handles,
                                   const VkAllocationCallbacks *alloc)
{
   vn_destroy_pipeline_handles_internal(dev, pipeline_count, pipeline_handles,
                                        alloc, true);
}

#define VN_PIPELINE_CREATE_SYNC_MASK                                         \
   (VK_PIPELINE_CREATE_2_FAIL_ON_PIPELINE_COMPILE_REQUIRED_BIT |             \
    VK_PIPELINE_CREATE_2_EARLY_RETURN_ON_FAILURE_BIT)

static struct vn_graphics_pipeline_fix_tmp *
vn_graphics_pipeline_fix_tmp_alloc(const VkAllocationCallbacks *alloc,
                                   uint32_t info_count,
                                   bool alloc_pnext)
{
   struct vn_graphics_pipeline_fix_tmp *tmp;
   VkGraphicsPipelineCreateInfo *infos;
   VkPipelineMultisampleStateCreateInfo *multisample_state_infos;
   VkPipelineSampleLocationsStateCreateInfoEXT *sl_infos;
   VkPipelineViewportStateCreateInfo *viewport_state_infos;

   /* for pNext */
   VkGraphicsPipelineLibraryCreateInfoEXT *gpl_infos;
   VkPipelineCreateFlags2CreateInfo *flags2_infos;
   VkPipelineCreationFeedbackCreateInfo *feedback_infos;
   VkPipelineFragmentShadingRateStateCreateInfoKHR *fsr_infos;
   VkPipelineLibraryCreateInfoKHR *library_infos;
   VkPipelineRenderingCreateInfo *rendering_infos;
   VkPipelineRobustnessCreateInfo *robustness_infos;
   VkRenderingAttachmentLocationInfo *ral_infos;
   VkRenderingInputAttachmentIndexInfo *riai_infos;

   VK_MULTIALLOC(ma);
   vk_multialloc_add(&ma, &tmp, __typeof__(*tmp), 1);
   vk_multialloc_add(&ma, &infos, __typeof__(*infos), info_count);
   vk_multialloc_add(&ma, &multisample_state_infos,
                     __typeof__(*multisample_state_infos), info_count);
   vk_multialloc_add(&ma, &sl_infos, __typeof__(*sl_infos), info_count);
   vk_multialloc_add(&ma, &viewport_state_infos,
                     __typeof__(*viewport_state_infos), info_count);

   if (alloc_pnext) {
      vk_multialloc_add(&ma, &gpl_infos, __typeof__(*gpl_infos), info_count);
      vk_multialloc_add(&ma, &flags2_infos, __typeof__(*flags2_infos),
                        info_count);
      vk_multialloc_add(&ma, &feedback_infos, __typeof__(*feedback_infos),
                        info_count);
      vk_multialloc_add(&ma, &fsr_infos, __typeof__(*fsr_infos), info_count);
      vk_multialloc_add(&ma, &library_infos, __typeof__(*library_infos),
                        info_count);
      vk_multialloc_add(&ma, &rendering_infos, __typeof__(*rendering_infos),
                        info_count);
      vk_multialloc_add(&ma, &robustness_infos, __typeof__(*robustness_infos),
                        info_count);
      vk_multialloc_add(&ma, &ral_infos, __typeof__(*ral_infos), info_count);
      vk_multialloc_add(&ma, &riai_infos, __typeof__(*riai_infos),
                        info_count);
   }

   if (!vk_multialloc_zalloc(&ma, alloc, VK_SYSTEM_ALLOCATION_SCOPE_COMMAND))
      return NULL;

   tmp->infos = infos;
   tmp->multisample_state_infos = multisample_state_infos;
   tmp->sl_infos = sl_infos;
   tmp->viewport_state_infos = viewport_state_infos;

   if (alloc_pnext) {
      tmp->gpl_infos = gpl_infos;
      tmp->flags2_infos = flags2_infos;
      tmp->feedback_infos = feedback_infos;
      tmp->fsr_infos = fsr_infos;
      tmp->library_infos = library_infos;
      tmp->rendering_infos = rendering_infos;
      tmp->robustness_infos = robustness_infos;
      tmp->ral_infos = ral_infos;
      tmp->riai_infos = riai_infos;
   }

   return tmp;
}

/**
 * Update \a gpl with the VkGraphicsPipelineLibraryFlagsEXT that the pipeline
 * provides directly (without linking). The spec says that the pipeline always
 * provides flags, but may do it implicitly.
 *
 * From the Vulkan 1.3.251 spec:
 *
 *    If this structure [VkGraphicsPipelineLibraryCreateInfoEXT] is
 *    omitted, and either VkGraphicsPipelineCreateInfo::flags includes
 *    VK_PIPELINE_CREATE_LIBRARY_BIT_KHR or the
 *    VkGraphicsPipelineCreateInfo::pNext chain includes
 *    a VkPipelineLibraryCreateInfoKHR structure with a libraryCount
 *    greater than 0, it is as if flags is 0. Otherwise if this
 *    structure is omitted, it is as if flags includes all possible subsets
 *    of the graphics pipeline (i.e. a complete graphics pipeline).
 */
static void
vn_graphics_pipeline_library_state_update(
   const VkGraphicsPipelineCreateInfo *info,
   VkPipelineCreateFlags2 flags2,
   struct vn_graphics_pipeline_library_state *restrict gpl)
{
   const VkGraphicsPipelineLibraryCreateInfoEXT *gpl_info =
      vk_find_struct_const(info->pNext,
                           GRAPHICS_PIPELINE_LIBRARY_CREATE_INFO_EXT);
   const VkPipelineLibraryCreateInfoKHR *lib_info =
      vk_find_struct_const(info->pNext, PIPELINE_LIBRARY_CREATE_INFO_KHR);
   const uint32_t lib_count = lib_info ? lib_info->libraryCount : 0;

   if (gpl_info) {
      gpl->mask |= gpl_info->flags;
   } else if ((flags2 & VK_PIPELINE_CREATE_2_LIBRARY_BIT_KHR) ||
              lib_count > 0) {
      gpl->mask |= 0;
   } else {
      gpl->mask |=
         VK_GRAPHICS_PIPELINE_LIBRARY_VERTEX_INPUT_INTERFACE_BIT_EXT |
         VK_GRAPHICS_PIPELINE_LIBRARY_PRE_RASTERIZATION_SHADERS_BIT_EXT |
         VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT |
         VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_OUTPUT_INTERFACE_BIT_EXT;
   }
}

/**
 * Update \a dynamic with the VkDynamicState that the pipeline provides
 * directly (without linking).
 *
 * \a direct_gpl The VkGraphicsPipelineLibraryFlagsEXT that the pipeline sets
 *    directly (without linking).
 */
static void
vn_graphics_dynamic_state_update(
   const VkGraphicsPipelineCreateInfo *info,
   struct vn_graphics_pipeline_library_state direct_gpl,
   struct vn_graphics_dynamic_state *restrict dynamic)
{
   const VkPipelineDynamicStateCreateInfo *dyn_info = info->pDynamicState;
   if (!dyn_info)
      return;

   struct vn_graphics_dynamic_state raw = { 0 };

   for (uint32_t i = 0; i < dyn_info->dynamicStateCount; i++) {
      switch (dyn_info->pDynamicStates[i]) {
      case VK_DYNAMIC_STATE_VERTEX_INPUT_EXT:
         raw.vertex_input = true;
         break;
      case VK_DYNAMIC_STATE_VIEWPORT:
         raw.viewport = true;
         break;
      case VK_DYNAMIC_STATE_VIEWPORT_WITH_COUNT:
         raw.viewport_with_count = true;
         break;
      case VK_DYNAMIC_STATE_SAMPLE_MASK_EXT:
         raw.sample_mask = true;
         break;
      case VK_DYNAMIC_STATE_SCISSOR:
         raw.scissor = true;
         break;
      case VK_DYNAMIC_STATE_SCISSOR_WITH_COUNT:
         raw.scissor_with_count = true;
         break;
      case VK_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE:
         raw.rasterizer_discard_enable = true;
         break;
      case VK_DYNAMIC_STATE_SAMPLE_LOCATIONS_EXT:
         raw.sample_locations = true;
         break;
      case VK_DYNAMIC_STATE_SAMPLE_LOCATIONS_ENABLE_EXT:
         raw.sample_locations_enable = true;
         break;
      default:
         break;
      }
   }

   /* We must ignore VkDynamicState unrelated to the
    * VkGraphicsPipelineLibraryFlagsEXT that the pipeline provides directly
    * (without linking).
    *
    *    [Vulkan 1.3.252]
    *    Dynamic state values set via pDynamicState must be ignored if the
    *    state they correspond to is not otherwise statically set by one of
    *    the state subsets used to create the pipeline.
    *
    * In general, we must update dynamic state bits with `|=` rather than `=`
    * because multiple GPL state subsets can enable the same dynamic state.
    *
    *    [Vulkan 1.3.252]
    *    Any linked library that has dynamic state enabled that same dynamic
    *    state must also be enabled in all the other linked libraries to which
    *    that dynamic state applies.
    */
   if (direct_gpl.vertex_input) {
      dynamic->vertex_input |= raw.vertex_input;
   }
   if (direct_gpl.pre_raster_shaders) {
      dynamic->viewport |= raw.viewport;
      dynamic->viewport_with_count |= raw.viewport_with_count;
      dynamic->scissor |= raw.scissor;
      dynamic->scissor_with_count |= raw.scissor_with_count;
      dynamic->rasterizer_discard_enable |= raw.rasterizer_discard_enable;
   }
   if (direct_gpl.fragment_shader) {
      dynamic->sample_mask |= raw.sample_mask;
      dynamic->sample_locations |= raw.sample_locations;
      dynamic->sample_locations_enable |= raw.sample_locations_enable;
   }
   if (direct_gpl.fragment_output) {
      dynamic->sample_mask |= raw.sample_mask;
      dynamic->sample_locations |= raw.sample_locations;
      dynamic->sample_locations_enable |= raw.sample_locations_enable;
   }
}

/**
 * Update \a shader_stages with the VkShaderStageFlags that the pipeline
 * provides directly (without linking).
 *
 * \a direct_gpl The VkGraphicsPipelineLibraryFlagsEXT that the pipeline sets
 *    directly (without linking).
 */
static void
vn_graphics_shader_stages_update(
   const VkGraphicsPipelineCreateInfo *info,
   struct vn_graphics_pipeline_library_state direct_gpl,
   struct vn_graphics_pipeline_fix_desc *restrict valid,
   VkShaderStageFlags *restrict shader_stages)
{
   /* From the Vulkan 1.3.251 spec:
    *
    *    VUID-VkGraphicsPipelineCreateInfo-flags-06640
    *
    *    If VkGraphicsPipelineLibraryCreateInfoEXT::flags includes
    *    VK_GRAPHICS_PIPELINE_LIBRARY_PRE_RASTERIZATION_SHADERS_BIT_EXT or
    *    VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT, pStages must be
    *    a valid pointer to an array of stageCount valid
    *    VkPipelineShaderStageCreateInfo structures
    */
   if (!direct_gpl.pre_raster_shaders && !direct_gpl.fragment_shader)
      return;

   valid->self.shader_stages = true;

   for (uint32_t i = 0; i < info->stageCount; i++) {
      /* We do not need to ignore the stages irrelevant to the GPL flags.
       * The following VUs require the app to provide only relevant stages.
       *
       * VUID-VkGraphicsPipelineCreateInfo-pStages-06894
       * VUID-VkGraphicsPipelineCreateInfo-pStages-06895
       * VUID-VkGraphicsPipelineCreateInfo-pStages-06896
       */
      *shader_stages |= info->pStages[i].stage;
   }
}

/**
 * Update the render pass state with the state that the pipeline provides
 * directly (without linking).
 *
 * \a direct_gpl The VkGraphicsPipelineLibraryFlagsEXT that the pipeline sets
 *    directly (without linking).
 */
static void
vn_render_pass_state_update(
   const VkGraphicsPipelineCreateInfo *info,
   struct vn_graphics_pipeline_library_state direct_gpl,
   struct vn_graphics_pipeline_fix_desc *restrict valid,
   struct vn_render_pass_state *restrict state)
{
   /* We must set validity before early returns, to ensure we don't erase
    * valid info during fixup.  We must not erase valid info because, even if
    * we don't read it, the host driver may read it.
    */

   /* VUID-VkGraphicsPipelineCreateInfo-flags-06643
    *
    * If VkGraphicsPipelineLibraryCreateInfoEXT::flags includes
    * VK_GRAPHICS_PIPELINE_LIBRARY_PRE_RASTERIZATION_SHADERS_BIT_EXT, or
    * VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT,
    * VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_OUTPUT_INTERFACE_BIT_EXT, and
    * renderPass is not VK_NULL_HANDLE, renderPass must be a valid
    * VkRenderPass handle
    */
   valid->self.render_pass |= direct_gpl.pre_raster_shaders ||
                              direct_gpl.fragment_shader ||
                              direct_gpl.fragment_output;

   /* VUID-VkGraphicsPipelineCreateInfo-renderPass-06579
    *
    * If the pipeline requires fragment output interface state, and renderPass
    * is VK_NULL_HANDLE, and
    * VkPipelineRenderingCreateInfo::colorAttachmentCount is not 0,
    * VkPipelineRenderingCreateInfo::pColorAttachmentFormats must be a valid
    * pointer to an array of colorAttachmentCount valid VkFormat values
    *
    * VUID-VkGraphicsPipelineCreateInfo-renderPass-06580
    *
    * If the pipeline requires fragment output interface state, and renderPass
    * is VK_NULL_HANDLE, each element of
    * VkPipelineRenderingCreateInfo::pColorAttachmentFormats must be a valid
    * VkFormat value
    */
   valid->pnext.rendering_info_formats |=
      direct_gpl.fragment_output && !info->renderPass;

   if (state->attachment_aspects != VK_IMAGE_ASPECT_METADATA_BIT) {
      /* We have previously collected the pipeline's attachment aspects.  We
       * do not need to inspect the attachment info again because VUs ensure
       * that all valid render pass info used to create the pipeline and its
       * linked pipelines are compatible.  Ignored info is not required to be
       * compatible across linked pipeline libraries. An example of ignored
       * info is VkPipelineRenderingCreateInfo::pColorAttachmentFormats
       * without
       * VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_OUTPUT_INTERFACE_BIT_EXT.
       *
       * VUID-VkGraphicsPipelineCreateInfo-renderpass-06625
       * VUID-VkGraphicsPipelineCreateInfo-pLibraries-06628
       */
      return;
   }

   if (valid->self.render_pass && info->renderPass) {
      struct vn_render_pass *pass =
         vn_render_pass_from_handle(info->renderPass);
      state->attachment_aspects =
         pass->subpasses[info->subpass].attachment_aspects;
      return;
   }

   if (valid->pnext.rendering_info_formats) {
      state->attachment_aspects = 0;

      /* From the Vulkan 1.3.255 spec:
       *
       *    When a pipeline is created without a VkRenderPass, if this
       *    structure [VkPipelineRenderingCreateInfo] is present in the pNext
       *    chain of VkGraphicsPipelineCreateInfo, it specifies the view mask
       *    and format of attachments used for rendering.  If this structure
       *    is not specified, and the pipeline does not include
       *    a VkRenderPass, viewMask and colorAttachmentCount are 0, and
       *    depthAttachmentFormat and stencilAttachmentFormat are
       *    VK_FORMAT_UNDEFINED. If a graphics pipeline is created with
       *    a valid VkRenderPass, parameters of this structure are ignored.
       *
       * However, other spec text clearly states that the format members of
       * VkPipelineRenderingCreateInfo are ignored unless the pipeline
       * provides fragment output interface state directly (without linking).
       */
      const VkPipelineRenderingCreateInfo *r_info =
         vk_find_struct_const(info->pNext, PIPELINE_RENDERING_CREATE_INFO);

      if (r_info) {
         for (uint32_t i = 0; i < r_info->colorAttachmentCount; i++) {
            if (r_info->pColorAttachmentFormats[i]) {
               state->attachment_aspects |= VK_IMAGE_ASPECT_COLOR_BIT;
               break;
            }
         }
         if (r_info->depthAttachmentFormat)
            state->attachment_aspects |= VK_IMAGE_ASPECT_DEPTH_BIT;
         if (r_info->stencilAttachmentFormat)
            state->attachment_aspects |= VK_IMAGE_ASPECT_STENCIL_BIT;
      }

      return;
   }

   /* Aspects remain invalid. */
   assert(state->attachment_aspects == VK_IMAGE_ASPECT_METADATA_BIT);
}

static void
vn_graphics_pipeline_state_merge(
   struct vn_graphics_pipeline_state *restrict dst,
   const struct vn_graphics_pipeline_state *restrict src)
{
   /* The Vulkan 1.3.251 spec says:
    *    VUID-VkGraphicsPipelineCreateInfo-pLibraries-06611
    *
    *    Any pipeline libraries included via
    *    VkPipelineLibraryCreateInfoKHR::pLibraries must not include any state
    *    subset already defined by this structure or defined by any other
    *    pipeline library in VkPipelineLibraryCreateInfoKHR::pLibraries
    */
   assert(!(dst->gpl.mask & src->gpl.mask));

   dst->gpl.mask |= src->gpl.mask;
   dst->dynamic.mask |= src->dynamic.mask;
   dst->shader_stages |= src->shader_stages;

   VkImageAspectFlags src_aspects = src->render_pass.attachment_aspects;
   VkImageAspectFlags *dst_aspects = &dst->render_pass.attachment_aspects;

   if (src_aspects != VK_IMAGE_ASPECT_METADATA_BIT) {
      if (*dst_aspects != VK_IMAGE_ASPECT_METADATA_BIT) {
         /* All linked pipelines must have compatible render pass info. */
         assert(*dst_aspects == src_aspects);
      } else {
         *dst_aspects = src_aspects;
      }
   }

   if (dst->gpl.pre_raster_shaders)
      dst->rasterizer_discard_enable = src->rasterizer_discard_enable;
}

/**
 * Fill \a state by reading the VkGraphicsPipelineCreateInfo pNext chain,
 * including any linked pipeline libraries. Return in \a out_fix_desc
 * a description of required fixes to the VkGraphicsPipelineCreateInfo chain.
 *
 * \pre state is zero-filled
 *
 * The logic for choosing which struct members to ignore, and which members
 * have valid values, is derived from the Vulkan spec sections for
 * VkGraphicsPipelineCreateInfo, VkGraphicsPipelineLibraryCreateInfoEXT, and
 * VkPipelineLibraryCreateInfoKHR. As of Vulkan 1.3.255, the spec text and VUs
 * still contain inconsistencies regarding the validity of struct members, so
 * read it carefully. Many of the VUs were written before
 * VK_EXT_graphics_pipeline_library and never updated. (Lina's advice: Focus
 * primarily on understanding the non-VU text, and use VUs to verify your
 * comprehension).
 */
static void
vn_graphics_pipeline_state_fill(
   const VkGraphicsPipelineCreateInfo *info,
   VkPipelineCreateFlags2 flags2,
   struct vn_graphics_pipeline_state *restrict state,
   struct vn_graphics_pipeline_fix_desc *out_fix_desc)
{
   /* Assume that state is already zero-filled.
    *
    * Invalidate attachment_aspects.
    */
   state->render_pass.attachment_aspects = VK_IMAGE_ASPECT_METADATA_BIT;

   const VkPipelineRenderingCreateInfo *rendering_info =
      vk_find_struct_const(info->pNext, PIPELINE_RENDERING_CREATE_INFO);
   const VkPipelineLibraryCreateInfoKHR *lib_info =
      vk_find_struct_const(info->pNext, PIPELINE_LIBRARY_CREATE_INFO_KHR);
   const uint32_t lib_count = lib_info ? lib_info->libraryCount : 0;

   const VkPipelineSampleLocationsStateCreateInfoEXT *sl_info = NULL;

   /* This tracks which fields have valid values in the
    * VkGraphicsPipelineCreateInfo pNext chain.
    *
    * We initially assume that all fields are invalid. We flip fields from
    * invalid to valid as we dig through the pNext chain.
    *
    * A single field may be updated at multiple locations, therefore we update
    * with `|=` instead of `=`.
    *
    * If `valid.foo` is set, then foo has a valid value if foo exists in the
    * pNext chain. Even though NULL is not a valid pointer, NULL is considered
    * a valid *value* for a pointer-typed variable. Same for VK_NULL_HANDLE
    * and Vulkan handle-typed variables.
    *
    * Conversely, if `valid.foo` remains false at the end of this function,
    * then the Vulkan spec permits foo to have any value. If foo has a pointer
    * type, it may be an invalid pointer. If foo has a Vulkan handle type, it
    * may be an invalid handle.
    */
   struct vn_graphics_pipeline_fix_desc valid = { 0 };

   /* Merge the linked pipeline libraries. */
   for (uint32_t i = 0; i < lib_count; i++) {
      struct vn_graphics_pipeline *p =
         vn_graphics_pipeline_from_handle(lib_info->pLibraries[i]);
      vn_graphics_pipeline_state_merge(state, &p->state);
   }

   /* The VkGraphicsPipelineLibraryFlagsEXT that this pipeline provides
    * directly (without linking).
    */
   struct vn_graphics_pipeline_library_state direct_gpl = { 0 };
   vn_graphics_pipeline_library_state_update(info, flags2, &direct_gpl);

   /* From the Vulkan 1.3.251 spec:
    *    VUID-VkGraphicsPipelineCreateInfo-pLibraries-06611
    *
    *    Any pipeline libraries included via
    *    VkPipelineLibraryCreateInfoKHR::pLibraries must not include any state
    *    subset already defined by this structure or defined by any other
    *    pipeline library in VkPipelineLibraryCreateInfoKHR::pLibraries
    */
   assert(!(direct_gpl.mask & state->gpl.mask));

   /* Collect orthogonal state that is common to multiple GPL state subsets. */
   vn_graphics_dynamic_state_update(info, direct_gpl, &state->dynamic);
   vn_graphics_shader_stages_update(info, direct_gpl, &valid,
                                    &state->shader_stages);
   vn_render_pass_state_update(info, direct_gpl, &valid, &state->render_pass);

   /* Collect remaining pre-raster shaders state.
    *
    * Of the remaining state, we must first collect the pre-raster shaders
    * state because it influences how the other state is collected.
    */
   if (direct_gpl.pre_raster_shaders) {
      valid.self.tessellation_state |=
         (bool)(state->shader_stages &
                (VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT |
                 VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT));
      valid.self.rasterization_state = true;
      valid.self.pipeline_layout = true;

      if (info->pRasterizationState) {
         state->rasterizer_discard_enable =
            info->pRasterizationState->rasterizerDiscardEnable;
      }

      const bool is_raster_statically_disabled =
         !state->dynamic.rasterizer_discard_enable &&
         state->rasterizer_discard_enable;

      if (!is_raster_statically_disabled) {
         valid.self.viewport_state = true;

         valid.self.viewport_state_viewports =
            !state->dynamic.viewport && !state->dynamic.viewport_with_count;

         valid.self.viewport_state_scissors =
            !state->dynamic.scissor && !state->dynamic.scissor_with_count;
      }

      /* Defer setting the flag until all its state is filled. */
      state->gpl.pre_raster_shaders = true;
   }

   /* Collect remaining vertex input interface state.
    *
    * TODO(VK_EXT_mesh_shader): Update.
    */
   if (direct_gpl.vertex_input) {
      const bool may_have_vertex_shader =
         !state->gpl.pre_raster_shaders ||
         (state->shader_stages & VK_SHADER_STAGE_VERTEX_BIT);

      valid.self.vertex_input_state |=
         may_have_vertex_shader && !state->dynamic.vertex_input;

      valid.self.input_assembly_state |= may_have_vertex_shader;

      /* Defer setting the flag until all its state is filled. */
      state->gpl.vertex_input = true;
   }

   /* Does this pipeline have rasterization statically disabled? If disabled,
    * then this pipeline does not directly provide fragment shader state nor
    * fragment output state.
    *
    * About fragment shader state, the Vulkan 1.3.254 spec says:
    *
    *    If a pipeline specifies pre-rasterization state either directly or by
    *    including it as a pipeline library and rasterizerDiscardEnable is set
    *    to VK_FALSE or VK_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE is used,
    *    this state must be specified to create a complete graphics pipeline.
    *
    *    If a pipeline includes
    *    VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT in
    *    VkGraphicsPipelineLibraryCreateInfoEXT::flags either explicitly or as
    *    a default, and either the conditions requiring this state for
    *    a complete graphics pipeline are met or this pipeline does not
    *    specify pre-rasterization state in any way, that pipeline must
    *    specify this state directly.
    *
    * About fragment output state, the Vulkan 1.3.254 spec says the same, but
    * with VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_OUTPUT_INTERFACE_BIT_EXT.
    */
   const bool is_raster_statically_disabled =
      state->gpl.pre_raster_shaders &&
      !state->dynamic.rasterizer_discard_enable &&
      state->rasterizer_discard_enable;

   /* Collect remaining states shared by fragment shader and fragment output
    * interface, just to avoid duplicate codes.
    */
   if (direct_gpl.fragment_shader || direct_gpl.fragment_output) {
      if (!is_raster_statically_disabled) {
         /* Validity of pMultisampleState is easy here.
          *
          *    VUID-VkGraphicsPipelineCreateInfo-pMultisampleState-06629
          *
          *    If the pipeline requires fragment shader state
          *    pMultisampleState must be NULL or a valid pointer to a valid
          *    VkPipelineMultisampleStateCreateInfo structure
          *
          *    VUID-VkGraphicsPipelineCreateInfo-rasterizerDiscardEnable-00751
          *
          *    If the pipeline requires fragment output interface state,
          *    pMultisampleState must be a valid pointer to a valid
          *    VkPipelineMultisampleStateCreateInfo structure
          */
         valid.self.multisample_state = true;

         valid.self.multisample_state_sample_mask =
            !state->dynamic.sample_mask;

         /* If VK_DYNAMIC_STATE_SAMPLE_LOCATIONS_EXT is used, the static
          * sampleLocationsInfo is ignored. Whether custom sample locations
          * have been enabled depends on if
          * VkPipelineSampleLocationsStateCreateInfoEXT has been chained and if
          * VkPipelineSampleLocationsStateCreateInfoEXT::sampleLocationsEnable
          * is VK_TRUE.
          *
          * If VK_DYNAMIC_STATE_SAMPLE_LOCATIONS_EXT is not used, the static
          * sampleLocationsInfo validity depends on whether
          * VK_DYNAMIC_STATE_SAMPLE_LOCATIONS_ENABLE_EXT dynamic state is used
          * or the static sampleLocationsEnable is true.
          */
         if (info->pMultisampleState) {
            sl_info = vk_find_struct_const(
               info->pMultisampleState->pNext,
               PIPELINE_SAMPLE_LOCATIONS_STATE_CREATE_INFO_EXT);
         }
         if (!state->dynamic.sample_locations) {
            if (state->dynamic.sample_locations_enable)
               valid.self.multisample_state_sample_locations = true;
            else if (sl_info && sl_info->sampleLocationsEnable == VK_TRUE)
               valid.self.multisample_state_sample_locations = true;
         }

         if (state->render_pass.attachment_aspects &
             (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT))
            valid.self.depth_stencil_state = true;
      }
   }

   /* Collect remaining fragment shader state. */
   if (direct_gpl.fragment_shader) {
      if (!is_raster_statically_disabled) {
         if (state->render_pass.attachment_aspects ==
                VK_IMAGE_ASPECT_METADATA_BIT &&
             (flags2 & VK_PIPELINE_CREATE_2_LIBRARY_BIT_KHR)) {
            /* The app has not yet provided render pass info, neither directly
             * in this VkGraphicsPipelineCreateInfo nor in any linked pipeline
             * libraries. Therefore we do not know if the final complete
             * pipeline will have any depth or stencil attachments. If the
             * final complete pipeline does have depth or stencil attachments,
             * then the pipeline will use
             * VkPipelineDepthStencilStateCreateInfo. Therefore, we must not
             * ignore it.
             */
            valid.self.depth_stencil_state = true;
         }

         valid.self.pipeline_layout = true;
      }

      /* Defer setting the flag until all its state is filled. */
      state->gpl.fragment_shader = true;
   }

   /* Collect remaining fragment output interface state. */
   if (direct_gpl.fragment_output) {
      if (!is_raster_statically_disabled) {
         if (state->render_pass.attachment_aspects &
             VK_IMAGE_ASPECT_COLOR_BIT)
            valid.self.color_blend_state = true;
      }

      /* Defer setting the flag until all its state is filled. */
      state->gpl.fragment_output = true;
   }

   /* After direct_gpl states collection, check the final state to validate
    * VkPipelineLayout in case of being the final layout in linked pipeline.
    *
    * From the Vulkan 1.3.275 spec:
    *    VUID-VkGraphicsPipelineCreateInfo-layout-06602
    *
    *    If the pipeline requires fragment shader state or pre-rasterization
    *    shader state, layout must be a valid VkPipelineLayout handle
    */
   if ((state->gpl.fragment_shader && !is_raster_statically_disabled) ||
       state->gpl.pre_raster_shaders)
      valid.self.pipeline_layout = true;

   /* Pipeline Derivatives
    *
    *    VUID-VkGraphicsPipelineCreateInfo-flags-07984
    *
    *    If flags contains the VK_PIPELINE_CREATE_DERIVATIVE_BIT flag, and
    *    basePipelineIndex is -1, basePipelineHandle must be a valid graphics
    *    VkPipeline handle
    */
   if ((flags2 & VK_PIPELINE_CREATE_2_DERIVATIVE_BIT) &&
       info->basePipelineIndex == -1)
      valid.self.base_pipeline_handle = true;

   *out_fix_desc = (struct vn_graphics_pipeline_fix_desc) {
      .self = {
         /* clang-format off */
         .shader_stages =
            !valid.self.shader_stages &&
            info->pStages,
         .vertex_input_state =
            !valid.self.vertex_input_state &&
            info->pVertexInputState,
         .input_assembly_state =
            !valid.self.input_assembly_state &&
            info->pInputAssemblyState,
         .tessellation_state =
            !valid.self.tessellation_state &&
            info->pTessellationState,
         .viewport_state =
            !valid.self.viewport_state &&
            info->pViewportState,
         .viewport_state_viewports =
            !valid.self.viewport_state_viewports &&
            valid.self.viewport_state &&
            info->pViewportState &&
            info->pViewportState->pViewports &&
            info->pViewportState->viewportCount,
         .viewport_state_scissors =
            !valid.self.viewport_state_scissors &&
            valid.self.viewport_state &&
            info->pViewportState &&
            info->pViewportState->pScissors &&
            info->pViewportState->scissorCount,
         .rasterization_state =
            !valid.self.rasterization_state &&
            info->pRasterizationState,
         .multisample_state =
            !valid.self.multisample_state &&
            info->pMultisampleState,
         .multisample_state_sample_mask =
            !valid.self.multisample_state_sample_mask &&
            valid.self.multisample_state &&
            info->pMultisampleState &&
            info->pMultisampleState->pSampleMask,
         .multisample_state_sample_locations =
            !valid.self.multisample_state_sample_locations &&
            valid.self.multisample_state &&
            sl_info && sl_info->sampleLocationsInfo.sampleLocationsCount,
         .depth_stencil_state =
            !valid.self.depth_stencil_state &&
            info->pDepthStencilState,
         .color_blend_state =
            !valid.self.color_blend_state &&
            info->pColorBlendState,
         .pipeline_layout =
            !valid.self.pipeline_layout &&
            info->layout,
         .render_pass =
            !valid.self.render_pass &&
            info->renderPass,
         .base_pipeline_handle =
            !valid.self.base_pipeline_handle &&
            info->basePipelineHandle,
         /* clang-format on */
      },
      .pnext = {
         /* clang-format off */
         .rendering_info_formats =
            !valid.pnext.rendering_info_formats &&
            rendering_info &&
            rendering_info->pColorAttachmentFormats &&
            rendering_info->colorAttachmentCount,
         /* clang-format on */
      },
   };
}

static void
vn_multisample_info_pnext_init(
   const VkPipelineMultisampleStateCreateInfo *info,
   struct vn_graphics_pipeline_fix_tmp *fix_tmp,
   uint32_t index)
{
   VkPipelineSampleLocationsStateCreateInfoEXT *sl =
      &fix_tmp->sl_infos[index];

   VkBaseOutStructure *cur = (void *)fix_tmp->infos[index].pMultisampleState;

   vk_foreach_struct_const(src, info->pNext) {
      void *next = NULL;
      switch (src->sType) {
      case VK_STRUCTURE_TYPE_PIPELINE_SAMPLE_LOCATIONS_STATE_CREATE_INFO_EXT:
         memcpy(sl, src, sizeof(*sl));
         next = sl;
         break;
      default:
         break;
      }

      if (next) {
         cur->pNext = next;
         cur = next;
      }
   }

   cur->pNext = NULL;
}

static void
vn_fix_graphics_pipeline_create_info_self(
   const struct vn_graphics_pipeline_info_self *ignore,
   const VkGraphicsPipelineCreateInfo *info,
   struct vn_graphics_pipeline_fix_tmp *fix_tmp,
   uint32_t index)
{
   /* VkGraphicsPipelineCreateInfo */
   if (ignore->shader_stages) {
      fix_tmp->infos[index].stageCount = 0;
      fix_tmp->infos[index].pStages = NULL;
   }
   if (ignore->vertex_input_state)
      fix_tmp->infos[index].pVertexInputState = NULL;
   if (ignore->input_assembly_state)
      fix_tmp->infos[index].pInputAssemblyState = NULL;
   if (ignore->tessellation_state)
      fix_tmp->infos[index].pTessellationState = NULL;
   if (ignore->viewport_state)
      fix_tmp->infos[index].pViewportState = NULL;
   if (ignore->rasterization_state)
      fix_tmp->infos[index].pRasterizationState = NULL;
   if (ignore->multisample_state)
      fix_tmp->infos[index].pMultisampleState = NULL;
   if (ignore->depth_stencil_state)
      fix_tmp->infos[index].pDepthStencilState = NULL;
   if (ignore->color_blend_state)
      fix_tmp->infos[index].pColorBlendState = NULL;
   if (ignore->pipeline_layout)
      fix_tmp->infos[index].layout = VK_NULL_HANDLE;
   if (ignore->base_pipeline_handle)
      fix_tmp->infos[index].basePipelineHandle = VK_NULL_HANDLE;

   /* VkPipelineMultisampleStateCreateInfo */
   if (ignore->multisample_state_sample_mask ||
       ignore->multisample_state_sample_locations) {
      /* Swap original pMultisampleState with temporary state. */
      fix_tmp->multisample_state_infos[index] = *info->pMultisampleState;
      fix_tmp->infos[index].pMultisampleState =
         &fix_tmp->multisample_state_infos[index];

      if (ignore->multisample_state_sample_mask)
         fix_tmp->multisample_state_infos[index].pSampleMask = NULL;
      if (ignore->multisample_state_sample_locations) {
         /* initialize pNext chain with allocated tmp storage */
         vn_multisample_info_pnext_init(info->pMultisampleState, fix_tmp,
                                        index);
         fix_tmp->sl_infos[index].sampleLocationsInfo.sampleLocationsCount =
            0;
      }
   }

   /* VkPipelineViewportStateCreateInfo */
   if (ignore->viewport_state_viewports || ignore->viewport_state_scissors) {
      /* Swap original pViewportState with temporary state. */
      fix_tmp->viewport_state_infos[index] = *info->pViewportState;
      fix_tmp->infos[index].pViewportState =
         &fix_tmp->viewport_state_infos[index];

      if (ignore->viewport_state_viewports)
         fix_tmp->viewport_state_infos[index].pViewports = NULL;
      if (ignore->viewport_state_scissors)
         fix_tmp->viewport_state_infos[index].pScissors = NULL;
   }
}

static void
vn_graphics_pipeline_create_info_pnext_init(
   const VkGraphicsPipelineCreateInfo *info,
   struct vn_graphics_pipeline_fix_tmp *fix_tmp,
   uint32_t index)
{
   VkGraphicsPipelineLibraryCreateInfoEXT *gpl = &fix_tmp->gpl_infos[index];
   VkPipelineCreateFlags2CreateInfo *flags2 = &fix_tmp->flags2_infos[index];
   VkPipelineCreationFeedbackCreateInfo *feedback =
      &fix_tmp->feedback_infos[index];
   VkPipelineFragmentShadingRateStateCreateInfoKHR *fsr =
      &fix_tmp->fsr_infos[index];
   VkPipelineLibraryCreateInfoKHR *library = &fix_tmp->library_infos[index];
   VkPipelineRenderingCreateInfo *rendering =
      &fix_tmp->rendering_infos[index];
   VkPipelineRobustnessCreateInfo *robustness =
      &fix_tmp->robustness_infos[index];
   VkRenderingAttachmentLocationInfo *ral = &fix_tmp->ral_infos[index];
   VkRenderingInputAttachmentIndexInfo *riai = &fix_tmp->riai_infos[index];

   VkBaseOutStructure *cur = (void *)&fix_tmp->infos[index];

   vk_foreach_struct_const(src, info->pNext) {
      void *next = NULL;
      switch (src->sType) {
      case VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_LIBRARY_CREATE_INFO_EXT:
         memcpy(gpl, src, sizeof(*gpl));
         next = gpl;
         break;
      case VK_STRUCTURE_TYPE_PIPELINE_CREATE_FLAGS_2_CREATE_INFO:
         memcpy(flags2, src, sizeof(*flags2));
         next = flags2;
         break;
      case VK_STRUCTURE_TYPE_PIPELINE_CREATION_FEEDBACK_CREATE_INFO:
         memcpy(feedback, src, sizeof(*feedback));
         next = feedback;
         break;
      case VK_STRUCTURE_TYPE_PIPELINE_FRAGMENT_SHADING_RATE_STATE_CREATE_INFO_KHR:
         memcpy(fsr, src, sizeof(*fsr));
         next = fsr;
         break;
      case VK_STRUCTURE_TYPE_PIPELINE_LIBRARY_CREATE_INFO_KHR:
         memcpy(library, src, sizeof(*library));
         next = library;
         break;
      case VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO:
         memcpy(rendering, src, sizeof(*rendering));
         next = rendering;
         break;
      case VK_STRUCTURE_TYPE_PIPELINE_ROBUSTNESS_CREATE_INFO:
         memcpy(robustness, src, sizeof(*robustness));
         next = robustness;
         break;
      case VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_LOCATION_INFO:
         memcpy(ral, src, sizeof(*ral));
         next = ral;
         break;
      case VK_STRUCTURE_TYPE_RENDERING_INPUT_ATTACHMENT_INDEX_INFO:
         memcpy(riai, src, sizeof(*riai));
         next = riai;
         break;
      default:
         break;
      }

      if (next) {
         cur->pNext = next;
         cur = next;
      }
   }

   cur->pNext = NULL;
}

static void
vn_fix_graphics_pipeline_create_info_pnext(
   const struct vn_graphics_pipeline_info_pnext *ignore,
   const VkGraphicsPipelineCreateInfo *info,
   struct vn_graphics_pipeline_fix_tmp *fix_tmp,
   uint32_t index)
{
   /* initialize pNext chain with allocated tmp storage */
   vn_graphics_pipeline_create_info_pnext_init(info, fix_tmp, index);

   /* VkPipelineRenderingCreateInfo */
   if (ignore->rendering_info_formats) {
      fix_tmp->rendering_infos[index].colorAttachmentCount = 0;
      fix_tmp->rendering_infos[index].pColorAttachmentFormats = NULL;
   }
}

static const VkGraphicsPipelineCreateInfo *
vn_fix_graphics_pipeline_create_infos(
   struct vn_device *dev,
   uint32_t info_count,
   const VkGraphicsPipelineCreateInfo *infos,
   const struct vn_graphics_pipeline_fix_desc fix_descs[info_count],
   struct vn_graphics_pipeline_fix_tmp **out_fix_tmp,
   const VkAllocationCallbacks *alloc)
{
   uint32_t self_mask = 0;
   uint32_t pnext_mask = 0;
   for (uint32_t i = 0; i < info_count; i++) {
      self_mask |= fix_descs[i].self.mask;
      pnext_mask |= fix_descs[i].pnext.mask;
   }

   if (!self_mask && !pnext_mask) {
      /* No fix is needed. */
      *out_fix_tmp = NULL;
      return infos;
   }

   /* tell whether fixes are applied in tracing */
   VN_TRACE_SCOPE("sanitize pipeline");

   struct vn_graphics_pipeline_fix_tmp *fix_tmp =
      vn_graphics_pipeline_fix_tmp_alloc(alloc, info_count, pnext_mask);
   if (!fix_tmp)
      return NULL;

   memcpy(fix_tmp->infos, infos, info_count * sizeof(infos[0]));

   for (uint32_t i = 0; i < info_count; i++) {
      if (fix_descs[i].self.mask) {
         vn_fix_graphics_pipeline_create_info_self(&fix_descs[i].self,
                                                   &infos[i], fix_tmp, i);
      }
      if (fix_descs[i].pnext.mask) {
         vn_fix_graphics_pipeline_create_info_pnext(&fix_descs[i].pnext,
                                                    &infos[i], fix_tmp, i);
      }
   }

   *out_fix_tmp = fix_tmp;
   return fix_tmp->infos;
}

/**
 * We invalidate each VkPipelineCreationFeedback. This is a legal but useless
 * implementation.
 *
 * We invalidate because the venus protocol (as of 2022-08-25) does not know
 * that the VkPipelineCreationFeedback structs in the
 * VkGraphicsPipelineCreateInfo pNext are output parameters. Before
 * VK_EXT_pipeline_creation_feedback, the pNext chain was input-only.
 */
static void
vn_invalidate_pipeline_creation_feedback(const VkBaseInStructure *chain)
{
   const VkPipelineCreationFeedbackCreateInfo *feedback_info =
      vk_find_struct_const(chain, PIPELINE_CREATION_FEEDBACK_CREATE_INFO);

   if (!feedback_info)
      return;

   feedback_info->pPipelineCreationFeedback->flags = 0;

   for (uint32_t i = 0; i < feedback_info->pipelineStageCreationFeedbackCount;
        i++)
      feedback_info->pPipelineStageCreationFeedbacks[i].flags = 0;
}

static inline VkPipelineCreateFlags2
vn_pipeline_create_flags2(const void *pnext, VkPipelineCreateFlags flags)
{
   const VkPipelineCreateFlags2CreateInfo *flags2 =
      vk_find_struct_const(pnext, PIPELINE_CREATE_FLAGS_2_CREATE_INFO);
   return flags2 ? flags2->flags : flags;
}

VKAPI_ATTR VkResult VKAPI_CALL
vn_CreateGraphicsPipelines(VkDevice device,
                           VkPipelineCache pipelineCache,
                           uint32_t createInfoCount,
                           const VkGraphicsPipelineCreateInfo *pCreateInfos,
                           const VkAllocationCallbacks *pAllocator,
                           VkPipeline *pPipelines)
{
   struct vn_device *dev = vn_device_from_handle(device);
   const VkAllocationCallbacks *alloc =
      pAllocator ? pAllocator : &dev->base.vk.alloc;
   bool want_sync = false;
   VkResult result;

   /* silence -Wmaybe-uninitialized false alarm on release build with gcc */
   if (!createInfoCount)
      return VK_SUCCESS;

   memset(pPipelines, 0, sizeof(*pPipelines) * createInfoCount);

   if (!vn_create_pipeline_handles(dev, VN_PIPELINE_TYPE_GRAPHICS,
                                   createInfoCount, pPipelines, alloc)) {
      return vn_error(dev->instance, VK_ERROR_OUT_OF_HOST_MEMORY);
   }

   STACK_ARRAY(struct vn_graphics_pipeline_fix_desc, fix_descs,
               createInfoCount);
   for (uint32_t i = 0; i < createInfoCount; i++) {
      struct vn_graphics_pipeline *pipeline =
         vn_graphics_pipeline_from_handle(pPipelines[i]);

      const VkPipelineCreateFlags2 flags2 = vn_pipeline_create_flags2(
         pCreateInfos[i].pNext, pCreateInfos[i].flags);
      if (flags2 & VN_PIPELINE_CREATE_SYNC_MASK)
         want_sync = true;

      vn_graphics_pipeline_state_fill(&pCreateInfos[i], flags2,
                                      &pipeline->state, &fix_descs[i]);
   }

   struct vn_graphics_pipeline_fix_tmp *fix_tmp = NULL;
   pCreateInfos = vn_fix_graphics_pipeline_create_infos(
      dev, createInfoCount, pCreateInfos, fix_descs, &fix_tmp, alloc);
   if (!pCreateInfos) {
      vn_destroy_pipeline_handles(dev, createInfoCount, pPipelines, alloc);
      STACK_ARRAY_FINISH(fix_descs);
      return vn_error(dev->instance, VK_ERROR_OUT_OF_HOST_MEMORY);
   }

   for (uint32_t i = 0; i < createInfoCount; i++) {
      struct vn_pipeline *pipeline = vn_pipeline_from_handle(pPipelines[i]);
      struct vn_pipeline_layout *layout =
         vn_pipeline_layout_from_handle(pCreateInfos[i].layout);
      if (layout && (layout->push_descriptor_set_layout ||
                     layout->has_push_constant_ranges)) {
         pipeline->layout = vn_pipeline_layout_ref(dev, layout);
      }

      vn_invalidate_pipeline_creation_feedback(
         (const VkBaseInStructure *)pCreateInfos[i].pNext);
   }

   struct vn_ring *target_ring = vn_get_target_ring(dev);
   if (!target_ring) {
      vk_free(alloc, fix_tmp);
      vn_destroy_pipeline_handles(dev, createInfoCount, pPipelines, alloc);
      STACK_ARRAY_FINISH(fix_descs);
      return vn_error(dev->instance, VK_ERROR_OUT_OF_HOST_MEMORY);
   }

   if (want_sync || target_ring != dev->primary_ring) {
      if (target_ring == dev->primary_ring) {
         VN_TRACE_SCOPE("want sync");
      }

      result = vn_call_vkCreateGraphicsPipelines(
         target_ring, device, pipelineCache, createInfoCount, pCreateInfos,
         NULL, pPipelines);
      if (result != VK_SUCCESS)
         vn_destroy_failed_pipeline_handles(dev, createInfoCount, pPipelines,
                                            alloc);
   } else {
      vn_async_vkCreateGraphicsPipelines(target_ring, device, pipelineCache,
                                         createInfoCount, pCreateInfos, NULL,
                                         pPipelines);
      result = VK_SUCCESS;
   }

   vk_free(alloc, fix_tmp);
   STACK_ARRAY_FINISH(fix_descs);
   return vn_result(dev->instance, result);
}

VKAPI_ATTR VkResult VKAPI_CALL
vn_CreateComputePipelines(VkDevice device,
                          VkPipelineCache pipelineCache,
                          uint32_t createInfoCount,
                          const VkComputePipelineCreateInfo *pCreateInfos,
                          const VkAllocationCallbacks *pAllocator,
                          VkPipeline *pPipelines)
{
   struct vn_device *dev = vn_device_from_handle(device);
   const VkAllocationCallbacks *alloc =
      pAllocator ? pAllocator : &dev->base.vk.alloc;
   bool want_sync = false;
   VkResult result;

   memset(pPipelines, 0, sizeof(*pPipelines) * createInfoCount);

   if (!vn_create_pipeline_handles(dev, VN_PIPELINE_TYPE_COMPUTE,
                                   createInfoCount, pPipelines, alloc))
      return vn_error(dev->instance, VK_ERROR_OUT_OF_HOST_MEMORY);

   for (uint32_t i = 0; i < createInfoCount; i++) {
      struct vn_pipeline *pipeline = vn_pipeline_from_handle(pPipelines[i]);
      struct vn_pipeline_layout *layout =
         vn_pipeline_layout_from_handle(pCreateInfos[i].layout);
      if (layout->push_descriptor_set_layout ||
          layout->has_push_constant_ranges) {
         pipeline->layout = vn_pipeline_layout_ref(dev, layout);
      }

      if (vn_pipeline_create_flags2(pCreateInfos[i].pNext,
                                    pCreateInfos[i].flags) &
          VN_PIPELINE_CREATE_SYNC_MASK)
         want_sync = true;

      vn_invalidate_pipeline_creation_feedback(
         (const VkBaseInStructure *)pCreateInfos[i].pNext);
   }

   struct vn_ring *target_ring = vn_get_target_ring(dev);
   if (!target_ring) {
      vn_destroy_pipeline_handles(dev, createInfoCount, pPipelines, alloc);
      return vn_error(dev->instance, VK_ERROR_OUT_OF_HOST_MEMORY);
   }

   if (want_sync || target_ring != dev->primary_ring) {
      result = vn_call_vkCreateComputePipelines(
         target_ring, device, pipelineCache, createInfoCount, pCreateInfos,
         NULL, pPipelines);
      if (result != VK_SUCCESS)
         vn_destroy_failed_pipeline_handles(dev, createInfoCount, pPipelines,
                                            alloc);
   } else {
      vn_async_vkCreateComputePipelines(target_ring, device, pipelineCache,
                                        createInfoCount, pCreateInfos, NULL,
                                        pPipelines);
      result = VK_SUCCESS;
   }

   return vn_result(dev->instance, result);
}

VKAPI_ATTR void VKAPI_CALL
vn_DestroyPipeline(VkDevice device,
                   VkPipeline _pipeline,
                   const VkAllocationCallbacks *pAllocator)
{
   struct vn_device *dev = vn_device_from_handle(device);
   struct vn_pipeline *pipeline = vn_pipeline_from_handle(_pipeline);
   const VkAllocationCallbacks *alloc =
      pAllocator ? pAllocator : &dev->base.vk.alloc;

   if (!pipeline)
      return;

   if (pipeline->layout) {
      vn_pipeline_layout_unref(dev, pipeline->layout);
   }

   vn_async_vkDestroyPipeline(dev->primary_ring, device, _pipeline, NULL);

   vn_object_base_fini(&pipeline->base);
   vk_free(alloc, pipeline);
}

VKAPI_ATTR VkResult VKAPI_CALL
vn_CreateRayTracingPipelinesKHR(
   VkDevice device,
   VkDeferredOperationKHR deferredOperation,
   VkPipelineCache pipelineCache,
   uint32_t createInfoCount,
   const VkRayTracingPipelineCreateInfoKHR *pCreateInfos,
   const VkAllocationCallbacks *pAllocator,
   VkPipeline *pPipelines)
{
   struct vn_device *dev = vn_device_from_handle(device);
   const VkAllocationCallbacks *alloc =
      pAllocator ? pAllocator : &dev->base.vk.alloc;
   bool want_sync = false;
   VkResult result = VK_SUCCESS;

   memset(pPipelines, 0, sizeof(*pPipelines) * createInfoCount);

   if (!vn_create_pipeline_handles(dev, VN_PIPELINE_TYPE_RAY_TRACING,
                                   createInfoCount, pPipelines, alloc))
      return vn_error(dev->instance, VK_ERROR_OUT_OF_HOST_MEMORY);

   for (uint32_t i = 0; i < createInfoCount; i++) {
      struct vn_pipeline *pipeline = vn_pipeline_from_handle(pPipelines[i]);
      struct vn_pipeline_layout *layout =
         vn_pipeline_layout_from_handle(pCreateInfos[i].layout);
      if (layout->push_descriptor_set_layout ||
          layout->has_push_constant_ranges) {
         pipeline->layout = vn_pipeline_layout_ref(dev, layout);
      }

      if (vn_pipeline_create_flags2(pCreateInfos[i].pNext,
                                    pCreateInfos[i].flags) &
          VN_PIPELINE_CREATE_SYNC_MASK)
         want_sync = true;

      vn_invalidate_pipeline_creation_feedback(
         (const VkBaseInStructure *)pCreateInfos[i].pNext);
   }

   /* TODO take deferredOperation into consideration */
   struct vn_ring *target_ring = vn_get_target_ring(dev);
   if (!target_ring) {
      vn_destroy_pipeline_handles(dev, createInfoCount, pPipelines, alloc);
      return vn_error(dev->instance, VK_ERROR_OUT_OF_HOST_MEMORY);
   }

   /* The VUID-VkRayTracingPipelineCreateInfoKHR-* set of VUs have well
    * validated the create info here. Nothing needs to be done.
    */
   if (want_sync || target_ring != dev->primary_ring) {
      result = vn_call_vkCreateRayTracingPipelinesKHR(
         target_ring, device, VK_NULL_HANDLE, pipelineCache, createInfoCount,
         pCreateInfos, NULL, pPipelines);
      if (result != VK_SUCCESS)
         vn_destroy_failed_pipeline_handles(dev, createInfoCount, pPipelines,
                                            alloc);
   } else {
      vn_async_vkCreateRayTracingPipelinesKHR(
         target_ring, device, VK_NULL_HANDLE, pipelineCache, createInfoCount,
         pCreateInfos, NULL, pPipelines);
      result = VK_SUCCESS;
   }

   if (deferredOperation != VK_NULL_HANDLE)
      return VK_OPERATION_DEFERRED_KHR;

   return vn_result(dev->instance, result);
}

VKAPI_ATTR VkResult VKAPI_CALL
vn_GetRayTracingCaptureReplayShaderGroupHandlesKHR(VkDevice device,
                                                   VkPipeline pipeline,
                                                   uint32_t firstGroup,
                                                   uint32_t groupCount,
                                                   size_t dataSize,
                                                   void *pData)
{
   struct vn_device *dev = vn_device_from_handle(device);

   return vn_call_vkGetRayTracingCaptureReplayShaderGroupHandlesKHR(
      dev->primary_ring, device, pipeline, firstGroup, groupCount, dataSize,
      pData);
}

VKAPI_ATTR VkResult VKAPI_CALL
vn_GetRayTracingShaderGroupHandlesKHR(VkDevice device,
                                      VkPipeline pipeline,
                                      uint32_t firstGroup,
                                      uint32_t groupCount,
                                      size_t dataSize,
                                      void *pData)
{
   struct vn_device *dev = vn_device_from_handle(device);

   return vn_call_vkGetRayTracingShaderGroupHandlesKHR(
      dev->primary_ring, device, pipeline, firstGroup, groupCount, dataSize,
      pData);
}

VKAPI_ATTR VkDeviceSize VKAPI_CALL
vn_GetRayTracingShaderGroupStackSizeKHR(VkDevice device,
                                        VkPipeline pipeline,
                                        uint32_t group,
                                        VkShaderGroupShaderKHR groupShader)
{
   struct vn_device *dev = vn_device_from_handle(device);

   /* TODO per background/deferred rt pipeline cache for its shader groups */
   return vn_call_vkGetRayTracingShaderGroupStackSizeKHR(
      dev->primary_ring, device, pipeline, group, groupShader);
}
