/******************************************************************************
 * The MIT License (MIT)
 *
 * Copyright (c) 2019-2021 Baldur Karlsson
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 ******************************************************************************/

#include "vk_test.h"

static const VkDescriptorUpdateTemplateEntryKHR constEntry = {
    4, 0, 1, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 0, 16,
};

RD_TEST(VK_Parameter_Zoo, VulkanGraphicsTest)
{
  static constexpr const char *Description =
      "General tests of parameters known to cause problems - e.g. optional values that should be "
      "ignored, edge cases, special values, etc.";

  const std::string pixel2 = R"EOSHADER(
#version 450 core

#extension GL_EXT_samplerless_texture_functions : enable

layout(location = 0, index = 0) out vec4 Color;

layout(binding = 0) uniform texture2D tex;

void main()
{
	Color = vec4(0, 1, 0, 1) * texelFetch(tex, ivec2(0), 0);
}

)EOSHADER";

  const std::string immutpixel = R"EOSHADER(
#version 450 core

layout(location = 0, index = 0) out vec4 Color;

layout(binding = 0) uniform sampler2D tex;

void main()
{
	Color = vec4(0, 1, 0, 1) * textureLod(tex, vec2(0), 0);
}

)EOSHADER";

  const std::string refpixel = R"EOSHADER(
#version 450 core
#extension GL_EXT_samplerless_texture_functions : enable

layout(location = 0, index = 0) out vec4 Color;

layout(binding = 0) uniform texture2D tex;

void main()
{
	Color = vec4(0, 1, 0, 1) * texelFetch(tex, ivec2(0), 0);
}

)EOSHADER";

  const std::string asm_vertex = R"EOSHADER(
               OpCapability Shader
               OpMemoryModel Logical GLSL450
               OpEntryPoint Vertex %main "main" %idx %pos %imgres
               OpDecorate %idx BuiltIn VertexIndex
               OpDecorate %pos BuiltIn Position
               OpDecorate %imgres Location 0
               OpDecorate %imgs DescriptorSet 0
               OpDecorate %imgs Binding 10
               OpMemberDecorate %PushData 0 Offset 0
               OpDecorate %PushData Block
       %void = OpTypeVoid
          %3 = OpTypeFunction %void
       %uint = OpTypeInt 32 0
        %int = OpTypeInt 32 1
      %float = OpTypeFloat 32
      %int_0 = OpConstant %int 0
         %10 = OpTypeImage %float 2D 0 0 0 1 Unknown
     %uint_4 = OpConstant %uint 4
%_arr_10_uint_4 = OpTypeArray %10 %uint_4
%_ptr_UniformConstant__arr_10_uint_4 = OpTypePointer UniformConstant %_arr_10_uint_4
%_ptr_UniformConstant_10 = OpTypePointer UniformConstant %10
       %imgs = OpVariable %_ptr_UniformConstant__arr_10_uint_4 UniformConstant
   %PushData = OpTypeStruct %uint
%_ptr_PushConstant_PushData = OpTypePointer PushConstant %PushData
%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
       %push = OpVariable %_ptr_PushConstant_PushData PushConstant
      %v2int = OpTypeVector %int 2
    %v4float = OpTypeVector %float 4
    %v2float = OpTypeVector %float 2
%_arr_v2float_uint_4 = OpTypeArray %v2float %uint_4
%_ptr_Function__arr_v2float_uint_4 = OpTypePointer Function %_arr_v2float_uint_4
   %float_n1 = OpConstant %float -1
    %float_1 = OpConstant %float 1
         %21 = OpConstantComposite %v2float %float_n1 %float_1
         %22 = OpConstantComposite %v2float %float_1 %float_1
         %23 = OpConstantComposite %v2float %float_n1 %float_n1
         %24 = OpConstantComposite %v2float %float_1 %float_n1
         %25 = OpConstantComposite %_arr_v2float_uint_4 %21 %22 %23 %24
%_ptr_Function_v2float = OpTypePointer Function %v2float
    %float_0 = OpConstant %float 0
%_ptr_Input_uint = OpTypePointer Input %uint
        %idx = OpVariable %_ptr_Input_uint Input
%_ptr_Output_v4float = OpTypePointer Output %v4float
%pos = OpVariable %_ptr_Output_v4float Output
%imgres = OpVariable %_ptr_Output_v4float Output
       %main = OpFunction %void None %3
          %5 = OpLabel
         %45 = OpVariable %_ptr_Function__arr_v2float_uint_4 Function
         %39 = OpLoad %uint %idx
               OpStore %45 %25
         %49 = OpAccessChain %_ptr_Function_v2float %45 %39
         %50 = OpLoad %v2float %49
         %51 = OpCompositeExtract %float %50 0
         %52 = OpCompositeExtract %float %50 1
         %53 = OpCompositeConstruct %v4float %51 %52 %float_0 %float_1

               OpStore %pos %53

         %54 = OpAccessChain %_ptr_PushConstant_uint %push %int_0
         %55 = OpLoad %uint %54
         %56 = OpAccessChain %_ptr_UniformConstant_10 %imgs %55
         %57 = OpLoad %10 %56
         %58 = OpCompositeConstruct %v2int %int_0 %int_0
         %59 = OpImageFetch %v4float %57 %58 Lod %int_0

               OpStore %imgres %59

               OpReturn
               OpFunctionEnd
)EOSHADER";

  struct refdatastruct
  {
    VkDescriptorImageInfo sampler;
    VkDescriptorImageInfo combined;
    VkDescriptorImageInfo sampled;
    VkDescriptorImageInfo storage;
    VkBufferView unitexel;
    VkBufferView storetexel;
    VkDescriptorBufferInfo unibuf;
    VkDescriptorBufferInfo storebuf;
    VkDescriptorBufferInfo unibufdyn;
    VkDescriptorBufferInfo storebufdyn;
  };

  VkSampler refsamp[4];

  VkSampler refcombinedsamp[4];
  AllocatedImage refcombinedimg[4];
  VkImageView refcombinedimgview[4];

  AllocatedImage refsampled[4];
  VkImageView refsampledview[4];

  AllocatedImage refstorage[4];
  VkImageView refstorageview[4];

  AllocatedBuffer refunitexel[4];
  VkBufferView refunitexelview[4];

  AllocatedBuffer refstoretexel[4];
  VkBufferView refstoretexelview[4];

  AllocatedBuffer refunibuf[4];

  AllocatedBuffer refstorebuf[4];

  AllocatedBuffer refunibufdyn[4];

  AllocatedBuffer refstorebufdyn[4];

  refdatastruct GetData(uint32_t idx)
  {
    return {
        // sampler
        vkh::DescriptorImageInfo(VK_NULL_HANDLE, VK_IMAGE_LAYOUT_UNDEFINED, refsamp[idx]),
        // combined
        vkh::DescriptorImageInfo(refcombinedimgview[idx], VK_IMAGE_LAYOUT_GENERAL,
                                 refcombinedsamp[idx]),
        // sampled
        vkh::DescriptorImageInfo(refsampledview[idx], VK_IMAGE_LAYOUT_GENERAL, VK_NULL_HANDLE),
        // storage
        vkh::DescriptorImageInfo(refstorageview[idx], VK_IMAGE_LAYOUT_GENERAL, VK_NULL_HANDLE),
        // unitexel
        refunitexelview[idx],
        // storetexel
        refstoretexelview[idx],
        // unibuf
        vkh::DescriptorBufferInfo(refunibuf[idx].buffer),
        // storebuf
        vkh::DescriptorBufferInfo(refstorebuf[idx].buffer),
        // unibufdyn
        vkh::DescriptorBufferInfo(refunibufdyn[idx].buffer),
        // storebufdyn
        vkh::DescriptorBufferInfo(refstorebufdyn[idx].buffer),
    };
  }

  void Prepare(int argc, char **argv)
  {
    optDevExts.push_back(VK_EXT_TOOLING_INFO_EXTENSION_NAME);
    optDevExts.push_back(VK_KHR_DESCRIPTOR_UPDATE_TEMPLATE_EXTENSION_NAME);
    optDevExts.push_back(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
    optDevExts.push_back(VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME);
    optDevExts.push_back(VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME);
    optDevExts.push_back(VK_KHR_BIND_MEMORY_2_EXTENSION_NAME);

    VulkanGraphicsTest::Prepare(argc, argv);

    static VkPhysicalDeviceTimelineSemaphoreFeaturesKHR timeline = {
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES_KHR,
    };

    if(std::find(devExts.begin(), devExts.end(), VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME) !=
       devExts.end())
    {
      timeline.timelineSemaphore = VK_TRUE;
      timeline.pNext = (void *)devInfoNext;
      devInfoNext = &timeline;
    }

    static VkPhysicalDeviceVulkan12Features vk12 = {
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES,
    };

    if(physProperties.apiVersion >= VK_MAKE_VERSION(1, 2, 0))
    {
      // don't enable any features, just link the struct in.
      // deliberately replace the VkPhysicalDeviceTimelineSemaphoreFeaturesKHR above because it was
      // rolled into this struct - so we enable that one feature if we're using it
      devInfoNext = &vk12;

      VkPhysicalDeviceVulkan12Features vk12avail = {
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES,
      };

      getPhysFeatures2(&vk12avail);

      if(vk12avail.timelineSemaphore)
        vk12.timelineSemaphore = VK_TRUE;
    }

    static VkPhysicalDeviceTransformFeedbackFeaturesEXT xfbFeats = {
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_FEATURES_EXT,
    };

    if(std::find(devExts.begin(), devExts.end(), VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME) !=
       devExts.end())
    {
      xfbFeats.transformFeedback = VK_TRUE;
      xfbFeats.pNext = (void *)devInfoNext;
      devInfoNext = &xfbFeats;
    }
  }

  int main()
  {
    // initialise, create window, create context, etc
    if(!Init())
      return 3;

    std::vector<VkPhysicalDeviceToolPropertiesEXT> tools;

    if(std::find(devExts.begin(), devExts.end(), VK_EXT_TOOLING_INFO_EXTENSION_NAME) != devExts.end())
    {
      uint32_t toolCount = 0;
      vkGetPhysicalDeviceToolPropertiesEXT(phys, &toolCount, NULL);
      tools.resize(toolCount);
      vkGetPhysicalDeviceToolPropertiesEXT(phys, &toolCount, tools.data());

      TEST_LOG("%u tools available:", toolCount);
      for(VkPhysicalDeviceToolPropertiesEXT &tool : tools)
        TEST_LOG("  - %s", tool.name);
    }

    {
      VkPhysicalDeviceProperties2KHR props2 = vkh::PhysicalDeviceProperties2KHR();

      PFN_vkGetPhysicalDeviceProperties2KHR instfn =
          (PFN_vkGetPhysicalDeviceProperties2KHR)vkGetInstanceProcAddr(
              instance, "vkGetPhysicalDeviceProperties2KHR");
      PFN_vkGetPhysicalDeviceProperties2KHR devfn =
          (PFN_vkGetPhysicalDeviceProperties2KHR)vkGetDeviceProcAddr(
              device, "vkGetPhysicalDeviceProperties2KHR");

      instfn(phys, &props2);

      TEST_LOG("Got physical device %s", props2.properties.deviceName);

      props2 = vkh::PhysicalDeviceProperties2KHR();

      if(devfn != NULL)
      {
        TEST_ERROR("Unexpected non-NULL return from vkGetDeviceProcAddr for physdev function");

        // we expect this to crash
        devfn(phys, &props2);

        return 5;
      }
    }

    bool KHR_descriptor_update_template =
        std::find(devExts.begin(), devExts.end(),
                  VK_KHR_DESCRIPTOR_UPDATE_TEMPLATE_EXTENSION_NAME) != devExts.end();
    bool KHR_push_descriptor = std::find(devExts.begin(), devExts.end(),
                                         VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME) != devExts.end();
    bool EXT_transform_feedback =
        std::find(devExts.begin(), devExts.end(), VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME) !=
        devExts.end();
    bool KHR_timeline_semaphore =
        std::find(devExts.begin(), devExts.end(), VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME) !=
        devExts.end();

    if(physProperties.apiVersion >= VK_MAKE_VERSION(1, 2, 0))
    {
      VkPhysicalDeviceVulkan12Features vk12avail = {
          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES,
      };

      getPhysFeatures2(&vk12avail);

      if(vk12avail.timelineSemaphore)
        KHR_timeline_semaphore = true;
    }

    VkDescriptorSetLayout setlayout = createDescriptorSetLayout(vkh::DescriptorSetLayoutCreateInfo({
        {0, VK_DESCRIPTOR_TYPE_SAMPLER, 1, VK_SHADER_STAGE_VERTEX_BIT},
        {1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_VERTEX_BIT},
        {2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1, VK_SHADER_STAGE_VERTEX_BIT},
        {3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_VERTEX_BIT},
        {4, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT},
        {5, VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT},
        {6, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT},
        {7, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT},
        {8, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1, VK_SHADER_STAGE_VERTEX_BIT},
        {9, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1, VK_SHADER_STAGE_VERTEX_BIT},
        {10, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, VK_SHADER_STAGE_VERTEX_BIT},
    }));

    VkDescriptorSetLayout refsetlayout =
        createDescriptorSetLayout(vkh::DescriptorSetLayoutCreateInfo({
            {0, VK_DESCRIPTOR_TYPE_SAMPLER, 1, VK_SHADER_STAGE_VERTEX_BIT},
            {1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_VERTEX_BIT},
            {2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1, VK_SHADER_STAGE_VERTEX_BIT},
            {3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_VERTEX_BIT},
            {4, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT},
            {5, VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT},
            {6, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT},
            {7, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT},
            {8, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1, VK_SHADER_STAGE_VERTEX_BIT},
            {9, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1, VK_SHADER_STAGE_VERTEX_BIT},
        }));

    VkSampler invalidSampler = (VkSampler)0x1234;
    VkSampler validSampler = createSampler(vkh::SamplerCreateInfo(VK_FILTER_LINEAR));

    setName(validSampler, "validSampler");

    VkDescriptorSetLayout immutsetlayout =
        createDescriptorSetLayout(vkh::DescriptorSetLayoutCreateInfo({
            {0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT,
             &validSampler},
            {99, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_VERTEX_BIT, &invalidSampler},
        }));

    VkPipelineLayout layout, reflayout;

    if(KHR_push_descriptor)
    {
      VkDescriptorSetLayout pushlayout = createDescriptorSetLayout(vkh::DescriptorSetLayoutCreateInfo(
          {
              {5, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_VERTEX_BIT},
              {10, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT},
              {20, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT},
          },
          VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR));

      layout = createPipelineLayout(vkh::PipelineLayoutCreateInfo({setlayout, pushlayout}));

      VkDescriptorSetLayout refpushlayout =
          createDescriptorSetLayout(vkh::DescriptorSetLayoutCreateInfo(
              {
                  {0, VK_DESCRIPTOR_TYPE_SAMPLER, 1, VK_SHADER_STAGE_VERTEX_BIT},
                  {1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_VERTEX_BIT},
                  {2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1, VK_SHADER_STAGE_VERTEX_BIT},
                  {3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_VERTEX_BIT},
                  {4, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT},
                  {5, VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT},
                  {6, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT},
                  {7, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT},
              },
              VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR));

      if(KHR_descriptor_update_template)
        reflayout = createPipelineLayout(
            vkh::PipelineLayoutCreateInfo({refsetlayout, refsetlayout, refpushlayout}));
      else
        reflayout =
            createPipelineLayout(vkh::PipelineLayoutCreateInfo({refsetlayout, refpushlayout}));
    }
    else
    {
      layout = createPipelineLayout(vkh::PipelineLayoutCreateInfo({setlayout}));

      if(KHR_descriptor_update_template)
        reflayout = createPipelineLayout(vkh::PipelineLayoutCreateInfo({refsetlayout, refsetlayout}));
      else
        reflayout = createPipelineLayout(vkh::PipelineLayoutCreateInfo({refsetlayout}));
    }

    VkPipelineLayout immutlayout =
        createPipelineLayout(vkh::PipelineLayoutCreateInfo({immutsetlayout}));

    VkDescriptorSetLayout setlayout2 = createDescriptorSetLayout(vkh::DescriptorSetLayoutCreateInfo({
        {0, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1, VK_SHADER_STAGE_FRAGMENT_BIT},
        {1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1, VK_SHADER_STAGE_FRAGMENT_BIT},
    }));

    VkPipelineLayout layout2 = createPipelineLayout(vkh::PipelineLayoutCreateInfo({setlayout2}));

    VkDescriptorSet descset2 = allocateDescriptorSet(setlayout2);

    VkDescriptorSetLayout asm_setlayout =
        createDescriptorSetLayout(vkh::DescriptorSetLayoutCreateInfo({
            {0, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1, 0},
            {1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1, 0},
            {2, VK_DESCRIPTOR_TYPE_SAMPLER, 3, 0},
            {5, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, 0},
            {6, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, VK_SHADER_STAGE_VERTEX_BIT},
            {9, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3, VK_SHADER_STAGE_VERTEX_BIT},
            {10, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 4, VK_SHADER_STAGE_VERTEX_BIT},
        }));

    VkDescriptorSet asm_descset = allocateDescriptorSet(asm_setlayout);

    VkPipelineLayout asm_layout = createPipelineLayout(vkh::PipelineLayoutCreateInfo(
        {asm_setlayout}, {vkh::PushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, 0, 4)}));

    vkh::GraphicsPipelineCreateInfo pipeCreateInfo;

    pipeCreateInfo.layout = layout;
    pipeCreateInfo.renderPass = mainWindow->rp;

    pipeCreateInfo.vertexInputState.vertexBindingDescriptions = {vkh::vertexBind(0, DefaultA2V)};
    pipeCreateInfo.vertexInputState.vertexAttributeDescriptions = {
        vkh::vertexAttr(0, 0, DefaultA2V, pos), vkh::vertexAttr(1, 0, DefaultA2V, col),
        vkh::vertexAttr(2, 0, DefaultA2V, uv),
    };

    pipeCreateInfo.stages = {
        CompileShaderModule(VKDefaultVertex, ShaderLang::glsl, ShaderStage::vert, "main"),
        CompileShaderModule(VKDefaultPixel, ShaderLang::glsl, ShaderStage::frag, "main"),
    };

    VkPipeline pipe = createGraphicsPipeline(pipeCreateInfo);

    pipeCreateInfo.layout = reflayout;

    VkPipeline refpipe = createGraphicsPipeline(pipeCreateInfo);

    pipeCreateInfo.layout = immutlayout;

    pipeCreateInfo.stages = {
        CompileShaderModule(VKDefaultVertex, ShaderLang::glsl, ShaderStage::vert, "main"),
        CompileShaderModule(immutpixel, ShaderLang::glsl, ShaderStage::frag, "main"),
    };

    VkPipeline immutpipe = createGraphicsPipeline(pipeCreateInfo);

    pipeCreateInfo.stages = {
        CompileShaderModule(VKDefaultVertex, ShaderLang::glsl, ShaderStage::vert, "main"),
        CompileShaderModule(pixel2, ShaderLang::glsl, ShaderStage::frag, "main"),
    };

    pipeCreateInfo.layout = layout2;

    VkPipeline pipe2 = createGraphicsPipeline(pipeCreateInfo);

    pipeCreateInfo.stages = {
        CompileShaderModule(asm_vertex, ShaderLang::spvasm, ShaderStage::vert, "main"),
    };

    pipeCreateInfo.inputAssemblyState.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
    pipeCreateInfo.layout = asm_layout;

    VkPipeline asm_pipe;
    {
      vkh::GraphicsPipelineCreateInfo asm_info = pipeCreateInfo;

      asm_info.viewportState.viewports.clear();
      asm_info.viewportState.scissors.clear();
      asm_info.viewportState.viewportCount = 0;
      asm_info.viewportState.scissorCount = 0;
      asm_info.rasterizationState.rasterizerDiscardEnable = VK_TRUE;

      asm_pipe = createGraphicsPipeline(asm_info);
    }

    {
      // invalid handle - should not be used because the flag for derived pipelines is not used
      pipeCreateInfo.basePipelineHandle = (VkPipeline)0x1234;

      VkPipeline dummy;
      CHECK_VKR(vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, pipeCreateInfo, NULL, &dummy));
      vkDestroyPipeline(device, dummy, NULL);

      // invalid index - again should not be used
      pipeCreateInfo.basePipelineHandle = VK_NULL_HANDLE;
      pipeCreateInfo.basePipelineIndex = 3;

      CHECK_VKR(vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, pipeCreateInfo, NULL, &dummy));
      vkDestroyPipeline(device, dummy, NULL);

      pipeCreateInfo.basePipelineIndex = -1;

      // bake the pipeline info so we can mess with the pointers it normally doesn't handle
      VkGraphicsPipelineCreateInfo *baked =
          (VkGraphicsPipelineCreateInfo *)(const VkGraphicsPipelineCreateInfo *)pipeCreateInfo;

      // NULL should be fine, we have no tessellation shaders
      baked->pTessellationState = NULL;

      CHECK_VKR(vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, baked, NULL, &dummy));
      vkDestroyPipeline(device, dummy, NULL);

      // same with a garbage pointer
      baked->pTessellationState = (VkPipelineTessellationStateCreateInfo *)0x1234;

      CHECK_VKR(vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, baked, NULL, &dummy));
      vkDestroyPipeline(device, dummy, NULL);

      VkPipelineViewportStateCreateInfo *viewState =
          (VkPipelineViewportStateCreateInfo *)&pipeCreateInfo.viewportState;

      baked->pViewportState = viewState;

      // viewport and scissor are already dynamic, so just set viewports and scissors to invalid
      // pointers
      viewState->pViewports = (VkViewport *)0x1234;
      viewState->pScissors = (VkRect2D *)0x1234;

      CHECK_VKR(vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, baked, NULL, &dummy));
      vkDestroyPipeline(device, dummy, NULL);

      // if we disable rasterization, tons of things can be NULL/garbage
      pipeCreateInfo.rasterizationState.rasterizerDiscardEnable = VK_TRUE;

      baked->pViewportState = NULL;
      baked->pMultisampleState = NULL;
      baked->pDepthStencilState = NULL;
      baked->pColorBlendState = NULL;

      CHECK_VKR(vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, baked, NULL, &dummy));
      vkDestroyPipeline(device, dummy, NULL);

      baked->pViewportState = (VkPipelineViewportStateCreateInfo *)0x1234;
      baked->pMultisampleState = (VkPipelineMultisampleStateCreateInfo *)0x1234;
      baked->pDepthStencilState = (VkPipelineDepthStencilStateCreateInfo *)0x1234;
      baked->pColorBlendState = (VkPipelineColorBlendStateCreateInfo *)0x1234;

      CHECK_VKR(vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, baked, NULL, &dummy));
      vkDestroyPipeline(device, dummy, NULL);
    }

    AllocatedBuffer vb;

    if(std::find(devExts.begin(), devExts.end(), VK_KHR_BIND_MEMORY_2_EXTENSION_NAME) != devExts.end())
    {
      // if we have bind memory 2, try to bind two buffers simultaneously in one call and delete the
      // other buffer. This ensure we don't try to bind an invalid buffer on replay.
      VkBuffer tmp = VK_NULL_HANDLE;
      VmaAllocation tmpAlloc = {};

      vkCreateBuffer(device,
                     vkh::BufferCreateInfo(sizeof(DefaultTri), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT |
                                                                   VK_BUFFER_USAGE_TRANSFER_DST_BIT),
                     NULL, &vb.buffer);
      vkCreateBuffer(device,
                     vkh::BufferCreateInfo(sizeof(DefaultTri), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT |
                                                                   VK_BUFFER_USAGE_TRANSFER_DST_BIT),
                     NULL, &tmp);

      VmaAllocationCreateInfo allocInfo = {0, VMA_MEMORY_USAGE_CPU_TO_GPU};
      allocInfo.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;

      VmaAllocationInfo vbInfo, tmpInfo;

      vmaAllocateMemoryForBuffer(allocator, vb.buffer, &allocInfo, &vb.alloc, &vbInfo);
      vmaAllocateMemoryForBuffer(allocator, tmp, &allocInfo, &tmpAlloc, &tmpInfo);

      VkBindBufferMemoryInfoKHR binds[2] = {};
      binds[0].sType = VK_STRUCTURE_TYPE_BIND_BUFFER_MEMORY_INFO_KHR;
      binds[0].buffer = vb.buffer;
      binds[0].memory = vbInfo.deviceMemory;
      binds[0].memoryOffset = vbInfo.offset;
      binds[1].sType = VK_STRUCTURE_TYPE_BIND_BUFFER_MEMORY_INFO_KHR;
      binds[1].buffer = tmp;
      binds[1].memory = tmpInfo.deviceMemory;
      binds[1].memoryOffset = tmpInfo.offset;
      vkBindBufferMemory2KHR(device, 2, binds);

      vmaFreeMemory(allocator, tmpAlloc);
      vkDestroyBuffer(device, tmp, NULL);

      vb.allocator = allocator;
      bufferAllocs[vb.buffer] = vb.alloc;
    }
    else
    {
      vb = AllocatedBuffer(
          this, vkh::BufferCreateInfo(sizeof(DefaultTri), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT |
                                                              VK_BUFFER_USAGE_TRANSFER_DST_BIT),
          VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_CPU_TO_GPU}));
    }

    vb.upload(DefaultTri);

    VkDescriptorSet descset = allocateDescriptorSet(setlayout);
    VkDescriptorSet refdescset = allocateDescriptorSet(refsetlayout);
    VkDescriptorSet reftempldescset = allocateDescriptorSet(refsetlayout);

    VkDescriptorSet immutdescset = allocateDescriptorSet(immutsetlayout);

    AllocatedBuffer buf(this,
                        vkh::BufferCreateInfo(1024, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT |
                                                        VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
                                                        VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT |
                                                        VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT),
                        VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_CPU_TO_GPU}));

    VkBuffer invalidBuffer = (VkBuffer)0x1234;
    VkBuffer validBuffer = buf.buffer;

    AllocatedImage img(this,
                       vkh::ImageCreateInfo(4, 4, 0, VK_FORMAT_R32G32B32A32_SFLOAT,
                                            VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT |
                                                VK_IMAGE_USAGE_TRANSFER_DST_BIT),
                       VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_GPU_ONLY}));

    VkImage validImage = img.image;

    VkImageView validImgView = createImageView(
        vkh::ImageViewCreateInfo(validImage, VK_IMAGE_VIEW_TYPE_2D, VK_FORMAT_R32G32B32A32_SFLOAT));
    VkImageView invalidImgView = (VkImageView)0x1234;

    {
      VkCommandBuffer cmd = GetCommandBuffer();
      vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo());
      vkh::cmdPipelineBarrier(cmd, {
                                       vkh::ImageMemoryBarrier(0, 0, VK_IMAGE_LAYOUT_UNDEFINED,
                                                               VK_IMAGE_LAYOUT_GENERAL, img.image),
                                   });
      vkCmdClearColorImage(cmd, img.image, VK_IMAGE_LAYOUT_GENERAL,
                           vkh::ClearColorValue(1.0f, 1.0f, 1.0f, 1.0f), 1,
                           vkh::ImageSubresourceRange());
      vkh::cmdPipelineBarrier(
          cmd,
          {
              vkh::ImageMemoryBarrier(VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
                                      VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, img.image),
          });
      vkEndCommandBuffer(cmd);
      Submit(99, 99, {cmd});
    }

    VkBufferView validBufView =
        createBufferView(vkh::BufferViewCreateInfo(validBuffer, VK_FORMAT_R32G32B32A32_SFLOAT));
    VkBufferView invalidBufView = (VkBufferView)0x1234;

    // initialise the writes with the valid data
    std::vector<VkDescriptorBufferInfo> validBufInfos = {vkh::DescriptorBufferInfo(validBuffer)};
    std::vector<VkBufferView> validBufViews = {validBufView};
    std::vector<VkDescriptorImageInfo> validSoloImgs = {
        vkh::DescriptorImageInfo(validImgView, VK_IMAGE_LAYOUT_GENERAL),
    };
    std::vector<VkDescriptorImageInfo> validCombinedImgs = {
        vkh::DescriptorImageInfo(validImgView, VK_IMAGE_LAYOUT_GENERAL, validSampler),
    };
    std::vector<VkDescriptorImageInfo> validSamplers = {
        vkh::DescriptorImageInfo(VK_NULL_HANDLE, VK_IMAGE_LAYOUT_UNDEFINED, validSampler),
    };

    std::vector<VkWriteDescriptorSet> writes = {
        vkh::WriteDescriptorSet(descset, 0, VK_DESCRIPTOR_TYPE_SAMPLER, validSamplers),
        vkh::WriteDescriptorSet(descset, 1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
                                validCombinedImgs),
        vkh::WriteDescriptorSet(descset, 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, validSoloImgs),
        vkh::WriteDescriptorSet(descset, 3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, validSoloImgs),

        vkh::WriteDescriptorSet(descset, 4, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, validBufViews),
        vkh::WriteDescriptorSet(descset, 5, VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, validBufViews),

        vkh::WriteDescriptorSet(descset, 6, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, validBufInfos),
        vkh::WriteDescriptorSet(descset, 7, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, validBufInfos),
        vkh::WriteDescriptorSet(descset, 8, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, validBufInfos),
        vkh::WriteDescriptorSet(descset, 9, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, validBufInfos),

        vkh::WriteDescriptorSet(immutdescset, 0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
                                validCombinedImgs),
        vkh::WriteDescriptorSet(immutdescset, 0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
                                validSoloImgs),
    };

    // do a first update
    vkh::updateDescriptorSets(device, writes);

    // set invalid handles but valid pointers and try again
    VkDescriptorBufferInfo invalidBufInfo = {};
    invalidBufInfo.buffer = invalidBuffer;

    VkDescriptorImageInfo invalidImgInfo = {};
    invalidImgInfo.sampler = invalidSampler;
    invalidImgInfo.imageView = invalidImgView;

    validSoloImgs[0].sampler = invalidSampler;
    validSamplers[0].imageView = invalidImgView;

    writes[0].pTexelBufferView = &invalidBufView;
    writes[0].pBufferInfo = &invalidBufInfo;

    writes[1].pTexelBufferView = &invalidBufView;
    writes[1].pBufferInfo = &invalidBufInfo;

    writes[2].pTexelBufferView = &invalidBufView;
    writes[2].pBufferInfo = &invalidBufInfo;

    writes[3].pTexelBufferView = &invalidBufView;
    writes[3].pBufferInfo = &invalidBufInfo;

    writes[4].pImageInfo = &invalidImgInfo;
    writes[4].pBufferInfo = &invalidBufInfo;

    writes[5].pImageInfo = &invalidImgInfo;
    writes[5].pBufferInfo = &invalidBufInfo;

    writes[6].pTexelBufferView = &invalidBufView;
    writes[6].pImageInfo = &invalidImgInfo;

    writes[7].pTexelBufferView = &invalidBufView;
    writes[7].pImageInfo = &invalidImgInfo;

    writes[8].pTexelBufferView = &invalidBufView;
    writes[8].pImageInfo = &invalidImgInfo;

    writes[9].pTexelBufferView = &invalidBufView;
    writes[9].pImageInfo = &invalidImgInfo;

    writes[10].pTexelBufferView = &invalidBufView;
    writes[10].pBufferInfo = &invalidBufInfo;

    vkh::updateDescriptorSets(device, writes);

    // finally set invalid pointers too
    VkBufferView *invalidBufViews = (VkBufferView *)0x1234;
    vkh::DescriptorBufferInfo *invalidBufInfos = (vkh::DescriptorBufferInfo *)0x1234;
    vkh::DescriptorImageInfo *invalidImgInfos = (vkh::DescriptorImageInfo *)0x1234;

    writes[0].pTexelBufferView = invalidBufViews;
    writes[0].pBufferInfo = invalidBufInfos;

    writes[1].pTexelBufferView = invalidBufViews;
    writes[1].pBufferInfo = invalidBufInfos;

    writes[2].pTexelBufferView = invalidBufViews;
    writes[2].pBufferInfo = invalidBufInfos;

    writes[3].pTexelBufferView = invalidBufViews;
    writes[3].pBufferInfo = invalidBufInfos;

    writes[4].pImageInfo = invalidImgInfos;
    writes[4].pBufferInfo = invalidBufInfos;

    writes[5].pImageInfo = invalidImgInfos;
    writes[5].pBufferInfo = invalidBufInfos;

    writes[6].pTexelBufferView = invalidBufViews;
    writes[6].pImageInfo = invalidImgInfos;

    writes[7].pTexelBufferView = invalidBufViews;
    writes[7].pImageInfo = invalidImgInfos;

    writes[8].pTexelBufferView = invalidBufViews;
    writes[8].pImageInfo = invalidImgInfos;

    writes[9].pTexelBufferView = invalidBufViews;
    writes[9].pImageInfo = invalidImgInfos;

    vkh::updateDescriptorSets(device, writes);

    VkDescriptorUpdateTemplateKHR reftempl = VK_NULL_HANDLE;

    if(KHR_descriptor_update_template)
    {
      struct datastruct
      {
        VkBufferView view;
        VkDescriptorBufferInfo buf;
        VkDescriptorImageInfo img;
        VkDescriptorImageInfo combined;
        VkDescriptorImageInfo sampler;
      } data;

      data.view = validBufView;
      data.buf = validBufInfos[0];
      data.img = vkh::DescriptorImageInfo(validImgView, VK_IMAGE_LAYOUT_GENERAL);
      data.combined = vkh::DescriptorImageInfo(validImgView, VK_IMAGE_LAYOUT_GENERAL, validSampler);
      data.sampler =
          vkh::DescriptorImageInfo(VK_NULL_HANDLE, VK_IMAGE_LAYOUT_UNDEFINED, validSampler);
      data.img.sampler = invalidSampler;
      data.sampler.imageView = invalidImgView;

      std::vector<VkDescriptorUpdateTemplateEntryKHR> entries = {
          // descriptor count 0 updates are allowed
          {0, 0, 0, VK_DESCRIPTOR_TYPE_SAMPLER, 0, sizeof(data)},
          {0, 0, 1, VK_DESCRIPTOR_TYPE_SAMPLER, offsetof(datastruct, sampler), sizeof(data)},
          {1, 0, 1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, offsetof(datastruct, combined),
           sizeof(data)},
          {2, 0, 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, offsetof(datastruct, img), sizeof(data)},
          {3, 0, 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, offsetof(datastruct, img), sizeof(data)},
          {4, 0, 1, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, offsetof(datastruct, view), sizeof(data)},
          {5, 0, 1, VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, offsetof(datastruct, view), sizeof(data)},
          {6, 0, 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, offsetof(datastruct, buf), sizeof(data)},
          {7, 0, 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, offsetof(datastruct, buf), sizeof(data)},
          {8, 0, 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, offsetof(datastruct, buf),
           sizeof(data)},
          {9, 0, 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, offsetof(datastruct, buf),
           sizeof(data)},
      };

      VkDescriptorUpdateTemplateCreateInfoKHR createInfo = {};
      createInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_CREATE_INFO_KHR;
      createInfo.descriptorUpdateEntryCount = (uint32_t)entries.size();
      createInfo.pDescriptorUpdateEntries = entries.data();
      createInfo.templateType = VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_DESCRIPTOR_SET_KHR;
      createInfo.descriptorSetLayout = setlayout;
      createInfo.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
      createInfo.pipelineLayout = (VkPipelineLayout)0x1234;
      createInfo.set = 123456789;
      VkDescriptorUpdateTemplateKHR templ;
      vkCreateDescriptorUpdateTemplateKHR(device, &createInfo, NULL, &templ);

      vkUpdateDescriptorSetWithTemplateKHR(device, descset, templ, &data);

      vkDestroyDescriptorUpdateTemplateKHR(device, templ, NULL);

      // try with constant entry
      createInfo.descriptorUpdateEntryCount = 1;
      createInfo.pDescriptorUpdateEntries = &constEntry;
      vkCreateDescriptorUpdateTemplateKHR(device, &createInfo, NULL, &templ);
      vkUpdateDescriptorSetWithTemplateKHR(device, descset, templ, &data);
      vkDestroyDescriptorUpdateTemplateKHR(device, templ, NULL);

      entries = {
          {0, 0, 1, VK_DESCRIPTOR_TYPE_SAMPLER, offsetof(refdatastruct, sampler), sizeof(data)},
          {1, 0, 1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, offsetof(refdatastruct, combined),
           sizeof(data)},
          {2, 0, 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, offsetof(refdatastruct, sampled), sizeof(data)},
          {3, 0, 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, offsetof(refdatastruct, storage), sizeof(data)},
          {4, 0, 1, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, offsetof(refdatastruct, unitexel),
           sizeof(data)},
          {5, 0, 1, VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, offsetof(refdatastruct, storetexel),
           sizeof(data)},
          {6, 0, 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, offsetof(refdatastruct, unibuf), sizeof(data)},
          {7, 0, 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, offsetof(refdatastruct, storebuf),
           sizeof(data)},
          {8, 0, 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, offsetof(refdatastruct, unibufdyn),
           sizeof(data)},
          {9, 0, 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, offsetof(refdatastruct, storebufdyn),
           sizeof(data)},
      };

      createInfo.descriptorSetLayout = refsetlayout;
      createInfo.descriptorUpdateEntryCount = (uint32_t)entries.size();
      createInfo.pDescriptorUpdateEntries = entries.data();

      vkCreateDescriptorUpdateTemplateKHR(device, &createInfo, NULL, &reftempl);
    }

    struct pushdatastruct
    {
      VkDescriptorBufferInfo buf;
    } pushdata;

    pushdata.buf = validBufInfos[0];

    VkDescriptorUpdateTemplateKHR pushtempl = VK_NULL_HANDLE, refpushtempl = VK_NULL_HANDLE;
    if(KHR_descriptor_update_template && KHR_push_descriptor)
    {
      std::vector<VkDescriptorUpdateTemplateEntryKHR> entries = {
          {0, 0, 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, sizeof(pushdata)},
          {10, 0, 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, sizeof(pushdata)},
      };

      VkDescriptorUpdateTemplateCreateInfoKHR createInfo = {};
      createInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_CREATE_INFO_KHR;
      createInfo.descriptorUpdateEntryCount = (uint32_t)entries.size();
      createInfo.pDescriptorUpdateEntries = entries.data();
      createInfo.templateType = VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_PUSH_DESCRIPTORS_KHR;
      createInfo.descriptorSetLayout = (VkDescriptorSetLayout)0x1234;
      createInfo.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
      createInfo.pipelineLayout = layout;
      createInfo.set = 1;
      vkCreateDescriptorUpdateTemplateKHR(device, &createInfo, NULL, &pushtempl);

      entries = {
          {0, 0, 1, VK_DESCRIPTOR_TYPE_SAMPLER, offsetof(refdatastruct, sampler),
           sizeof(refdatastruct)},
          {1, 0, 1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, offsetof(refdatastruct, combined),
           sizeof(refdatastruct)},
          {2, 0, 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, offsetof(refdatastruct, sampled),
           sizeof(refdatastruct)},
          {3, 0, 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, offsetof(refdatastruct, storage),
           sizeof(refdatastruct)},
          {4, 0, 1, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, offsetof(refdatastruct, unitexel),
           sizeof(refdatastruct)},
          {5, 0, 1, VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, offsetof(refdatastruct, storetexel),
           sizeof(refdatastruct)},
          {6, 0, 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, offsetof(refdatastruct, unibuf),
           sizeof(refdatastruct)},
          {7, 0, 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, offsetof(refdatastruct, storebuf),
           sizeof(refdatastruct)},
      };

      createInfo.descriptorUpdateEntryCount = (uint32_t)entries.size();
      createInfo.pDescriptorUpdateEntries = entries.data();
      // set 0 = normal
      // set 1 = template
      // set 2 = push
      createInfo.pipelineLayout = reflayout;
      createInfo.set = 2;

      vkCreateDescriptorUpdateTemplateKHR(device, &createInfo, NULL, &refpushtempl);
    }

    // check that stale views in descriptors don't cause problems if the handle is re-used

    VkImageView view1, view2;
    CHECK_VKR(vkCreateImageView(device, vkh::ImageViewCreateInfo(img.image, VK_IMAGE_VIEW_TYPE_2D,
                                                                 VK_FORMAT_R32G32B32A32_SFLOAT),
                                NULL, &view1));
    CHECK_VKR(vkCreateImageView(device, vkh::ImageViewCreateInfo(img.image, VK_IMAGE_VIEW_TYPE_2D,
                                                                 VK_FORMAT_R32G32B32A32_SFLOAT),
                                NULL, &view2));

    vkh::updateDescriptorSets(
        device,
        {
            // bind view1 to binding 0, we will override this
            vkh::WriteDescriptorSet(descset2, 0, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
                                    {vkh::DescriptorImageInfo(view1, VK_IMAGE_LAYOUT_GENERAL)}),
            // we bind view2 to binding 1. This will become stale
            vkh::WriteDescriptorSet(descset2, 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
                                    {vkh::DescriptorImageInfo(view2, VK_IMAGE_LAYOUT_GENERAL)}),
        });

    vkDestroyImageView(device, view2, NULL);

    // create view3. Under RD, this is expected to get the same handle as view2 (but a new ID)
    VkImageView view3;
    CHECK_VKR(vkCreateImageView(device, vkh::ImageViewCreateInfo(img.image, VK_IMAGE_VIEW_TYPE_2D,
                                                                 VK_FORMAT_R32G32B32A32_SFLOAT),
                                NULL, &view3));

    if(rdoc)
    {
      TEST_ASSERT(view2 == view3,
                  "Expected view3 to be a re-used handle. Test isn't going to be valid");
    }

    vkh::updateDescriptorSets(
        device,
        {
            // bind view3 to 0. This means the same handle is now in both binding but only binding 0
            // is valid, binding 1 refers to the 'old' version of this handle.
            vkh::WriteDescriptorSet(descset2, 0, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
                                    {vkh::DescriptorImageInfo(view3, VK_IMAGE_LAYOUT_GENERAL)}),
            // this unbinds the stale view2. Nothing should happen, but if we're comparing by handle
            // this may remove a reference to view3 since it will have the same handle
            vkh::WriteDescriptorSet(descset2, 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
                                    {vkh::DescriptorImageInfo(view1, VK_IMAGE_LAYOUT_GENERAL)}),
        });

    vkh::updateDescriptorSets(
        device,
        {
            vkh::WriteDescriptorSet(asm_descset, 10, 0, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
                                    {vkh::DescriptorImageInfo(view3, VK_IMAGE_LAYOUT_GENERAL)}),
            vkh::WriteDescriptorSet(asm_descset, 10, 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
                                    {vkh::DescriptorImageInfo(view3, VK_IMAGE_LAYOUT_GENERAL)}),
            vkh::WriteDescriptorSet(asm_descset, 10, 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
                                    {vkh::DescriptorImageInfo(view3, VK_IMAGE_LAYOUT_GENERAL)}),
            vkh::WriteDescriptorSet(asm_descset, 10, 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
                                    {vkh::DescriptorImageInfo(view3, VK_IMAGE_LAYOUT_GENERAL)}),
        });

    VkSampler mutableSampler = createSampler(vkh::SamplerCreateInfo(VK_FILTER_NEAREST));

    setName(mutableSampler, "mutableSampler");

    // try writing a different sampler to the immutable sampler, it should not be applied
    vkh::updateDescriptorSets(
        device,
        {
            vkh::WriteDescriptorSet(
                immutdescset, 0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
                {
                    vkh::DescriptorImageInfo(validImgView, VK_IMAGE_LAYOUT_GENERAL, mutableSampler),
                }),
        });

    refdatastruct resetrefdata = {};
    resetrefdata.sampler.sampler = resetrefdata.combined.sampler = validSampler;
    resetrefdata.sampled.imageView = resetrefdata.combined.imageView =
        resetrefdata.storage.imageView = validImgView;
    resetrefdata.sampled.imageLayout = resetrefdata.combined.imageLayout =
        resetrefdata.storage.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
    resetrefdata.unitexel = resetrefdata.storetexel = validBufView;
    resetrefdata.unibuf.buffer = resetrefdata.storebuf.buffer = resetrefdata.unibufdyn.buffer =
        resetrefdata.storebufdyn.buffer = validBuffer;
    resetrefdata.unibuf.range = resetrefdata.storebuf.range = resetrefdata.unibufdyn.range =
        resetrefdata.storebufdyn.range = VK_WHOLE_SIZE;

    {
      VkCommandBuffer cmd = GetCommandBuffer();
      vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo());
      vkh::cmdPipelineBarrier(cmd, {
                                       vkh::ImageMemoryBarrier(0, 0, VK_IMAGE_LAYOUT_UNDEFINED,
                                                               VK_IMAGE_LAYOUT_GENERAL, img.image),
                                   });

      // create the specific resources that will only be referenced through descriptor updates
      for(int i = 0; i < 4; i++)
      {
        refsamp[i] = createSampler(vkh::SamplerCreateInfo(VK_FILTER_LINEAR));
        setName(refsamp[i], "refsamp" + std::to_string(i));

        refcombinedsamp[i] = createSampler(vkh::SamplerCreateInfo(VK_FILTER_LINEAR));
        setName(refcombinedsamp[i], "refcombinedsamp" + std::to_string(i));

        VkFormat fmt = VK_FORMAT_R32G32B32A32_SFLOAT;
        VmaAllocationCreateInfo allocInfo = {0, VMA_MEMORY_USAGE_GPU_ONLY};

        refcombinedimg[i] = AllocatedImage(
            this, vkh::ImageCreateInfo(2, 2, 0, fmt, VK_IMAGE_USAGE_SAMPLED_BIT), allocInfo);
        refcombinedimgview[i] = createImageView(
            vkh::ImageViewCreateInfo(refcombinedimg[i].image, VK_IMAGE_VIEW_TYPE_2D, fmt));
        setName(refcombinedimg[i].image, "refcombinedimg" + std::to_string(i));

        vkh::cmdPipelineBarrier(
            cmd, {
                     vkh::ImageMemoryBarrier(0, 0, VK_IMAGE_LAYOUT_UNDEFINED,
                                             VK_IMAGE_LAYOUT_GENERAL, refcombinedimg[i].image),
                 });

        refsampled[i] = AllocatedImage(
            this, vkh::ImageCreateInfo(2, 2, 0, fmt, VK_IMAGE_USAGE_SAMPLED_BIT), allocInfo);
        refsampledview[i] = createImageView(
            vkh::ImageViewCreateInfo(refsampled[i].image, VK_IMAGE_VIEW_TYPE_2D, fmt));
        setName(refsampled[i].image, "refsampled" + std::to_string(i));

        vkh::cmdPipelineBarrier(
            cmd, {
                     vkh::ImageMemoryBarrier(0, 0, VK_IMAGE_LAYOUT_UNDEFINED,
                                             VK_IMAGE_LAYOUT_GENERAL, refsampled[i].image),
                 });

        refstorage[i] = AllocatedImage(
            this, vkh::ImageCreateInfo(2, 2, 0, fmt, VK_IMAGE_USAGE_STORAGE_BIT), allocInfo);
        refstorageview[i] = createImageView(
            vkh::ImageViewCreateInfo(refstorage[i].image, VK_IMAGE_VIEW_TYPE_2D, fmt));
        setName(refstorage[i].image, "refstorage" + std::to_string(i));

        vkh::cmdPipelineBarrier(
            cmd, {
                     vkh::ImageMemoryBarrier(0, 0, VK_IMAGE_LAYOUT_UNDEFINED,
                                             VK_IMAGE_LAYOUT_GENERAL, refstorage[i].image),
                 });

        refunitexel[i] = AllocatedBuffer(
            this, vkh::BufferCreateInfo(256, VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT), allocInfo);
        refunitexelview[i] = createBufferView(vkh::BufferViewCreateInfo(refunitexel[i].buffer, fmt));
        setName(refunitexel[i].buffer, "refunitexel" + std::to_string(i));

        refstoretexel[i] = AllocatedBuffer(
            this, vkh::BufferCreateInfo(256, VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT), allocInfo);
        refstoretexelview[i] =
            createBufferView(vkh::BufferViewCreateInfo(refstoretexel[i].buffer, fmt));
        setName(refstoretexel[i].buffer, "refstoretexel" + std::to_string(i));

        refunibuf[i] = AllocatedBuffer(
            this, vkh::BufferCreateInfo(256, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT), allocInfo);
        setName(refunibuf[i].buffer, "refunibuf" + std::to_string(i));

        refstorebuf[i] = AllocatedBuffer(
            this, vkh::BufferCreateInfo(256, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT), allocInfo);
        setName(refstorebuf[i].buffer, "refstorebuf" + std::to_string(i));

        refunibufdyn[i] = AllocatedBuffer(
            this, vkh::BufferCreateInfo(256, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT), allocInfo);
        setName(refunibufdyn[i].buffer, "refunibufdyn" + std::to_string(i));

        refstorebufdyn[i] = AllocatedBuffer(
            this, vkh::BufferCreateInfo(256, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT), allocInfo);
        setName(refstorebufdyn[i].buffer, "refstorebufdyn" + std::to_string(i));
      }

      vkEndCommandBuffer(cmd);
      Submit(99, 99, {cmd});
    }

    VkBufferUsageFlags xfbUsage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;

    if(EXT_transform_feedback)
    {
      xfbUsage |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT;
      xfbUsage |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_COUNTER_BUFFER_BIT_EXT;
    }

    AllocatedBuffer xfbBuf(this, vkh::BufferCreateInfo(256, xfbUsage),
                           VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_GPU_ONLY}));

    if(vkSetDebugUtilsObjectNameEXT)
    {
      VkDebugUtilsObjectNameInfoEXT info = {};
      info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT;
      info.objectType = VK_OBJECT_TYPE_BUFFER;
      info.objectHandle = (uint64_t)xfbBuf.buffer;
      info.pObjectName = NULL;
      vkSetDebugUtilsObjectNameEXT(device, &info);
    }

    VkFence fence;
    CHECK_VKR(vkCreateFence(device, vkh::FenceCreateInfo(VK_FENCE_CREATE_SIGNALED_BIT), NULL, &fence));

    VkEvent ev;
    CHECK_VKR(vkCreateEvent(device, vkh::EventCreateInfo(), NULL, &ev));

    VkSemaphore sem = VK_NULL_HANDLE;

    if(KHR_timeline_semaphore)
    {
      VkSemaphoreTypeCreateInfo semType = {VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO};
      semType.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE;
      semType.initialValue = 1234;

      CHECK_VKR(vkCreateSemaphore(device, vkh::SemaphoreCreateInfo().next(&semType), NULL, &sem));
    }

    // check destroying NULL objects
    vkDestroyBuffer(device, NULL, NULL);
    vkDestroyBufferView(device, NULL, NULL);
    vkDestroyCommandPool(device, NULL, NULL);
    vkDestroyDescriptorPool(device, NULL, NULL);
    vkDestroyDescriptorSetLayout(device, NULL, NULL);
    vkDestroyDevice(NULL, NULL);
    vkDestroyEvent(device, NULL, NULL);
    vkDestroyFence(device, NULL, NULL);
    vkDestroyFramebuffer(device, NULL, NULL);
    vkDestroyImage(device, NULL, NULL);
    vkDestroyImageView(device, NULL, NULL);
    vkDestroyInstance(NULL, NULL);
    vkDestroyPipeline(device, NULL, NULL);
    vkDestroyPipelineCache(device, NULL, NULL);
    vkDestroyPipelineLayout(device, NULL, NULL);
    vkDestroyQueryPool(device, NULL, NULL);
    vkDestroyRenderPass(device, NULL, NULL);
    vkDestroySampler(device, NULL, NULL);
    vkDestroySemaphore(device, NULL, NULL);
    vkDestroyShaderModule(device, NULL, NULL);
    vkDestroySurfaceKHR(instance, NULL, NULL);
    vkDestroySwapchainKHR(device, NULL, NULL);
    if(KHR_descriptor_update_template)
    {
      vkDestroyDescriptorUpdateTemplateKHR(device, NULL, NULL);
    }

    VkCommandPool cmdPool;
    CHECK_VKR(vkCreateCommandPool(device, vkh::CommandPoolCreateInfo(), NULL, &cmdPool));
    VkDescriptorPool descPool;
    CHECK_VKR(vkCreateDescriptorPool(
        device, vkh::DescriptorPoolCreateInfo(128,
                                              {
                                                  {VK_DESCRIPTOR_TYPE_SAMPLER, 1024},
                                              },
                                              VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT),
        NULL, &descPool));

    VkCommandBuffer emptyCmd = VK_NULL_HANDLE;
    vkFreeCommandBuffers(device, cmdPool, 1, &emptyCmd);
    VkDescriptorSet emptyDesc = VK_NULL_HANDLE;
    vkFreeDescriptorSets(device, descPool, 1, &emptyDesc);
    vkFreeMemory(device, VK_NULL_HANDLE, NULL);

    vkDestroyCommandPool(device, cmdPool, NULL);
    vkDestroyDescriptorPool(device, descPool, NULL);

    while(Running())
    {
      // acquire and clear the backbuffer
      {
        VkCommandBuffer cmd = GetCommandBuffer();

        vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo());

        VkImage swapimg =
            StartUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL);

        vkCmdClearColorImage(cmd, swapimg, VK_IMAGE_LAYOUT_GENERAL,
                             vkh::ClearColorValue(0.2f, 0.2f, 0.2f, 1.0f), 1,
                             vkh::ImageSubresourceRange());

        vkEndCommandBuffer(cmd);

        Submit(0, 4, {cmd});
      }

      // try writing with an invalid sampler to the immutable, it should be ignored
      vkh::updateDescriptorSets(
          device,
          {
              vkh::WriteDescriptorSet(
                  immutdescset, 0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
                  {
                      vkh::DescriptorImageInfo(validImgView, VK_IMAGE_LAYOUT_GENERAL, invalidSampler),
                  }),
          });

      // do a bunch of spinning on fences/semaphores that should not be serialised exhaustively
      VkResult status = VK_SUCCESS;
      for(size_t i = 0; i < 1000; i++)
        status = vkGetFenceStatus(device, fence);

      if(status != VK_SUCCESS)
        TEST_WARN("Expected fence to be set (it was created signalled)");

      for(size_t i = 0; i < 1000; i++)
        status = vkGetEventStatus(device, ev);

      if(status != VK_EVENT_RESET)
        TEST_WARN("Expected event to be unset");

      if(KHR_timeline_semaphore)
      {
        uint64_t val = 0;
        for(size_t i = 0; i < 1000; i++)
          vkGetSemaphoreCounterValueKHR(device, sem, &val);

        if(val != 1234)
          TEST_WARN("Expected timeline semaphore value to be 1234");
      }

      // reference some resources through different descriptor types to ensure that they are
      // properly included
      {
        vkDeviceWaitIdle(device);

        refdatastruct refdata = GetData(0);
        refdatastruct reftempldata = GetData(1);
        refdatastruct refpushdata = GetData(2);
        refdatastruct refpushtempldata = GetData(3);

        vkh::updateDescriptorSets(
            device,
            {
                vkh::WriteDescriptorSet(refdescset, 0, VK_DESCRIPTOR_TYPE_SAMPLER, {refdata.sampler}),
                vkh::WriteDescriptorSet(refdescset, 1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
                                        {refdata.combined}),
                vkh::WriteDescriptorSet(refdescset, 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
                                        {refdata.sampled}),
                vkh::WriteDescriptorSet(refdescset, 3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
                                        {refdata.storage}),
                vkh::WriteDescriptorSet(refdescset, 4, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER,
                                        {refdata.unitexel}),
                vkh::WriteDescriptorSet(refdescset, 5, VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER,
                                        {refdata.storetexel}),
                vkh::WriteDescriptorSet(refdescset, 6, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
                                        {refdata.unibuf}),
                vkh::WriteDescriptorSet(refdescset, 7, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
                                        {refdata.storebuf}),
                vkh::WriteDescriptorSet(refdescset, 8, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
                                        {refdata.unibufdyn}),
                vkh::WriteDescriptorSet(refdescset, 9, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC,
                                        {refdata.storebufdyn}),
            });

        if(KHR_descriptor_update_template)
          vkUpdateDescriptorSetWithTemplateKHR(device, reftempldescset, reftempl, &reftempldata);

        VkCommandBuffer cmd = GetCommandBuffer();

        vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo());

        if(KHR_descriptor_update_template)
          setMarker(cmd, "KHR_descriptor_update_template");
        if(KHR_push_descriptor)
          setMarker(cmd, "KHR_push_descriptor");

        vkCmdBeginRenderPass(
            cmd, vkh::RenderPassBeginInfo(mainWindow->rp, mainWindow->GetFB(), mainWindow->scissor),
            VK_SUBPASS_CONTENTS_INLINE);

        vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, refpipe);

        uint32_t set = 0;

        // set 0 is always the normal one
        vkh::cmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, reflayout, set++,
                                   {refdescset}, {0, 0});

        // if we have update templates, set 1 is always the template one
        if(KHR_descriptor_update_template)
          vkh::cmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, reflayout, set++,
                                     {reftempldescset}, {0, 0});

        // push set comes after the ones above. Note that because we can't have more than one push
        // set, we test with the first set of refs here then do a template update (if supported) and
        // draw again to test the second set of refs.
        if(KHR_push_descriptor)
        {
          vkh::cmdPushDescriptorSets(
              cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, reflayout, set,
              {
                  vkh::WriteDescriptorSet(VK_NULL_HANDLE, 0, VK_DESCRIPTOR_TYPE_SAMPLER,
                                          {refpushdata.sampler}),
                  vkh::WriteDescriptorSet(VK_NULL_HANDLE, 1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
                                          {refpushdata.combined}),
                  vkh::WriteDescriptorSet(VK_NULL_HANDLE, 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
                                          {refpushdata.sampled}),
                  vkh::WriteDescriptorSet(VK_NULL_HANDLE, 3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
                                          {refpushdata.storage}),
                  vkh::WriteDescriptorSet(VK_NULL_HANDLE, 4, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER,
                                          {refpushdata.unitexel}),
                  vkh::WriteDescriptorSet(VK_NULL_HANDLE, 5, VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER,
                                          {refpushdata.storetexel}),
                  vkh::WriteDescriptorSet(VK_NULL_HANDLE, 6, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
                                          {refpushdata.unibuf}),
                  vkh::WriteDescriptorSet(VK_NULL_HANDLE, 7, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
                                          {refpushdata.storebuf}),
              });
        }

        VkViewport view = {128, 0, 128, 128, 0, 1};
        vkCmdSetViewport(cmd, 0, 1, &view);
        vkCmdSetScissor(cmd, 0, 1, &mainWindow->scissor);
        vkh::cmdBindVertexBuffers(cmd, 0, {vb.buffer}, {0});
        setMarker(cmd, "References");
        vkCmdDraw(cmd, 1, 1, 0, 0);

        if(KHR_descriptor_update_template && KHR_push_descriptor)
        {
          setMarker(cmd, "PushTemplReferences");
          vkCmdPushDescriptorSetWithTemplateKHR(cmd, refpushtempl, reflayout, set, &refpushtempldata);
          vkCmdDraw(cmd, 1, 1, 0, 0);
        }

        vkCmdEndRenderPass(cmd);

        vkEndCommandBuffer(cmd);

        Submit(1, 4, {cmd});

        vkDeviceWaitIdle(device);

        // scribble over the descriptor contents so that initial contents fetch never gets these
        // resources that way
        vkh::updateDescriptorSets(
            device,
            {
                vkh::WriteDescriptorSet(refdescset, 0, VK_DESCRIPTOR_TYPE_SAMPLER,
                                        {resetrefdata.sampler}),
                vkh::WriteDescriptorSet(refdescset, 1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
                                        {resetrefdata.combined}),
                vkh::WriteDescriptorSet(refdescset, 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
                                        {resetrefdata.sampled}),
                vkh::WriteDescriptorSet(refdescset, 3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
                                        {resetrefdata.storage}),
                vkh::WriteDescriptorSet(refdescset, 4, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER,
                                        {resetrefdata.unitexel}),
                vkh::WriteDescriptorSet(refdescset, 5, VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER,
                                        {resetrefdata.storetexel}),
                vkh::WriteDescriptorSet(refdescset, 6, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
                                        {resetrefdata.unibuf}),
                vkh::WriteDescriptorSet(refdescset, 7, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
                                        {resetrefdata.storebuf}),
                vkh::WriteDescriptorSet(refdescset, 8, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
                                        {resetrefdata.unibufdyn}),
                vkh::WriteDescriptorSet(refdescset, 9, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC,
                                        {resetrefdata.storebufdyn}),
            });

        if(KHR_descriptor_update_template)
          vkUpdateDescriptorSetWithTemplateKHR(device, reftempldescset, reftempl, &resetrefdata);
      }

      // check the rendering with our parameter tests is OK
      {
        vkDeviceWaitIdle(device);

        VkCommandBuffer cmd = GetCommandBuffer();

        vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo());

        VkImage swapimg =
            StartUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL);

        vkCmdClearColorImage(cmd, swapimg, VK_IMAGE_LAYOUT_GENERAL,
                             vkh::ClearColorValue(0.2f, 0.2f, 0.2f, 1.0f), 1,
                             vkh::ImageSubresourceRange());

        vkCmdBeginRenderPass(
            cmd, vkh::RenderPassBeginInfo(mainWindow->rp, mainWindow->GetFB(), mainWindow->scissor),
            VK_SUBPASS_CONTENTS_INLINE);

        if(!tools.empty())
        {
          pushMarker(cmd, "Tools available");
          for(VkPhysicalDeviceToolPropertiesEXT &tool : tools)
            setMarker(cmd, tool.name);
          popMarker(cmd);
        }

        uint32_t idx = 1;
        vkh::cmdBindVertexBuffers(cmd, 0, {vb.buffer}, {0});
        vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, asm_pipe);
        vkCmdPushConstants(cmd, asm_layout, VK_SHADER_STAGE_VERTEX_BIT, 0, 4, &idx);
        vkh::cmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, asm_layout, 0,
                                   {asm_descset}, {});

        setMarker(cmd, "ASM Draw");
        vkCmdDraw(cmd, 4, 1, 0, 0);

        vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe);
        vkCmdSetViewport(cmd, 0, 1, &mainWindow->viewport);
        vkCmdSetScissor(cmd, 0, 1, &mainWindow->scissor);
        vkh::cmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, {descset},
                                   {0, 0});
        if(KHR_push_descriptor)
          vkCmdPushDescriptorSetKHR(
              cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 1, 1,
              vkh::WriteDescriptorSet(VK_NULL_HANDLE, 20, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
                                      validBufInfos));
        if(KHR_descriptor_update_template && KHR_push_descriptor)
          vkCmdPushDescriptorSetWithTemplateKHR(cmd, pushtempl, layout, 1, &pushdata);
        vkCmdDraw(cmd, 3, 1, 0, 0);

        vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, immutpipe);
        vkh::cmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, immutlayout, 0,
                                   {immutdescset}, {});

        setMarker(cmd, "Immutable Draw");
        vkCmdDraw(cmd, 3, 1, 0, 0);

        vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe2);
        vkh::cmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, layout2, 0, {descset2}, {});

        setMarker(cmd, "Color Draw");
        vkCmdDraw(cmd, 3, 1, 0, 0);

        VkRect2D sc = {100, 100, 10, 10};
        vkCmdSetScissor(cmd, 0, 1, &sc);

        if(EXT_transform_feedback)
        {
          VkDeviceSize offs = 0;
          // pSizes is optional and can be NULL to use the whole buffer size
          vkCmdBindTransformFeedbackBuffersEXT(cmd, 0, 1, &xfbBuf.buffer, &offs, NULL);

          // pCounterBuffers is also optional
          vkCmdBeginTransformFeedbackEXT(cmd, 0, 0, NULL, NULL);
          vkCmdEndTransformFeedbackEXT(cmd, 0, 0, NULL, NULL);
        }

        vkCmdEndRenderPass(cmd);

        vkEndCommandBuffer(cmd);

        Submit(2, 4, {cmd});
      }

      // finish with the backbuffer
      {
        vkDeviceWaitIdle(device);

        VkCommandBuffer cmd = GetCommandBuffer();

        vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo());

        FinishUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL);

        vkEndCommandBuffer(cmd);

        Submit(3, 4, {cmd});
      }

      Present();
    }

    vkDeviceWaitIdle(device);

    vkDestroyEvent(device, ev, NULL);
    vkDestroyFence(device, fence, NULL);
    vkDestroySemaphore(device, sem, NULL);

    vkDestroyImageView(device, view1, NULL);
    vkDestroyImageView(device, view3, NULL);

    if(KHR_descriptor_update_template && KHR_push_descriptor)
      vkDestroyDescriptorUpdateTemplateKHR(device, pushtempl, NULL);

    if(KHR_descriptor_update_template)
      vkDestroyDescriptorUpdateTemplateKHR(device, reftempl, NULL);

    return 0;
  }
};

REGISTER_TEST();
