/*
 * Copyright (c) 2012 Arvin Schnell <arvin.schnell@gmail.com>
 * Copyright (c) 2012 Rob Clark <rob@ti.com>
 * Copyright © 2015 Intel Corporation
 * Copyright © 2019-2021 Collabora Ltd.
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * Parts of this code were adapted from vulkan-tutorial.com which is
 * Licensed as CC0 1.0 Universal. Other parts were adapted from vkcube which
 * is licensed as Apache 2.0
 */

#define VK_USE_PLATFORM_XCB_KHR
#include <vulkan/vulkan.h>

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <glib.h>
#include <gio/gio.h>
#include <json-glib/json-glib.h>

#include <steam-runtime-tools/glib-backports-internal.h>
#include <steam-runtime-tools/json-glib-backports-internal.h>
#include <steam-runtime-tools/utils-internal.h>

static const int WIDTH = 200;
static const int HEIGHT = 200;

static gboolean opt_pretty_print = FALSE;
static gboolean opt_print_version = FALSE;
static gboolean opt_visible = FALSE;

const int MAX_FRAMES_IN_FLIGHT = 2;

static const char *argv0;

#define do_vk(expr, error) (_do_vk (#expr, expr, error))

static const char *
get_vk_error_string (VkResult error_code)
{
  switch (error_code)
    {
#define STR(r) case VK_##r: return #r
        STR(SUCCESS);
        STR(NOT_READY);
        STR(TIMEOUT);
        STR(EVENT_SET);
        STR(EVENT_RESET);
        STR(INCOMPLETE);
        STR(ERROR_OUT_OF_HOST_MEMORY);
        STR(ERROR_OUT_OF_DEVICE_MEMORY);
        STR(ERROR_INITIALIZATION_FAILED);
        STR(ERROR_DEVICE_LOST);
        STR(ERROR_MEMORY_MAP_FAILED);
        STR(ERROR_LAYER_NOT_PRESENT);
        STR(ERROR_EXTENSION_NOT_PRESENT);
        STR(ERROR_FEATURE_NOT_PRESENT);
        STR(ERROR_INCOMPATIBLE_DRIVER);
        STR(ERROR_TOO_MANY_OBJECTS);
        STR(ERROR_FORMAT_NOT_SUPPORTED);
        STR(ERROR_FRAGMENTED_POOL);
#if VK_HEADER_VERSION >= 131
        STR(ERROR_UNKNOWN);
#endif
        STR(ERROR_OUT_OF_POOL_MEMORY);
        STR(ERROR_INVALID_EXTERNAL_HANDLE);
        STR(ERROR_FRAGMENTATION_EXT);
#if VK_HEADER_VERSION >= 97
        STR(ERROR_INVALID_DEVICE_ADDRESS_EXT);
#endif
        STR(ERROR_SURFACE_LOST_KHR);
        STR(ERROR_NATIVE_WINDOW_IN_USE_KHR);
        STR(SUBOPTIMAL_KHR);
        STR(ERROR_OUT_OF_DATE_KHR);
        STR(ERROR_INCOMPATIBLE_DISPLAY_KHR);
        STR(ERROR_VALIDATION_FAILED_EXT);
        STR(ERROR_INVALID_SHADER_NV);
#if VK_HEADER_VERSION >= 135
#if VK_HEADER_VERSION < 162
        STR(ERROR_INCOMPATIBLE_VERSION_KHR);
#endif
        STR(THREAD_IDLE_KHR);
        STR(THREAD_DONE_KHR);
        STR(OPERATION_DEFERRED_KHR);
        STR(OPERATION_NOT_DEFERRED_KHR);
        STR(ERROR_PIPELINE_COMPILE_REQUIRED_EXT);
#endif
#if VK_HEADER_VERSION >= 89
        STR(ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT);
#endif
        STR(ERROR_NOT_PERMITTED_EXT);
#if VK_HEADER_VERSION >= 105
        STR(ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT);
#endif
#if VK_HEADER_VERSION < 140
        STR(RESULT_RANGE_SIZE);
#endif
        STR(RESULT_MAX_ENUM);
#undef STR
      default:
        return "UNKNOWN_ERROR";
    }
}

static gboolean
_do_vk (const char *description,
        VkResult result,
        GError **error)
{
  if (result != VK_SUCCESS)
    return glnx_throw (error, "%s failed: %s (%d)", description,
                       get_vk_error_string (result), result);

  return TRUE;
}

static const char *device_extensions[] =
{
  VK_KHR_SWAPCHAIN_EXTENSION_NAME,
};

struct _QueueFamilyIndices
{
  uint32_t graphicsFamily;
  uint32_t presentFamily;
  gboolean hasGraphicsFamily;
  gboolean hasPresentFamily;
};

typedef struct _QueueFamilyIndices QueueFamilyIndices;

struct _SwapChainSupportDetails
{
  VkSurfaceCapabilitiesKHR capabilities;
  uint32_t format_count;
  uint32_t mode_count;
  VkSurfaceFormatKHR *formats;
  VkPresentModeKHR *present_modes;
};

typedef struct _SwapChainSupportDetails SwapChainSupportDetails;

struct _VulkanPhysicalDeviceProperties
{
  VkCommandBuffer *command_buffers;
  VkCommandPool command_pool;
  VkFence *in_flight_fences;
  VkSurfaceKHR surface;
  VkDevice device;
  VkExtent2D swapchain_extent;
  VkFormat swapchain_image_format;
  VkImage *swapchain_images;
  VkImageView *swapchain_image_views;
  VkPipeline graphics_pipeline;
  VkPipelineLayout pipeline_layout;
  VkRenderPass render_pass;
  VkSemaphore *image_available_semaphores;
  VkSemaphore *render_finished_semaphores;
  VkSwapchainKHR swapchain;
  VkQueue graphics_queue;
  VkQueue present_queue;
  uint32_t framebuffer_size;
  uint32_t current_frame;
  VkFramebuffer *swapchain_framebuffers;
};

typedef struct _VulkanPhysicalDeviceProperties VulkanPhysicalDeviceProperties;

static gboolean
queue_family_indices_is_complete (QueueFamilyIndices indices)
{
  return indices.hasGraphicsFamily && indices.hasPresentFamily;
}

static xcb_atom_t
get_atom (struct xcb_connection_t *conn,
          const char *name)
{
  xcb_intern_atom_cookie_t cookie;
  g_autofree xcb_intern_atom_reply_t *reply = NULL;
  xcb_atom_t atom;

  cookie = xcb_intern_atom (conn, 0, strlen (name), name);
  reply = xcb_intern_atom_reply (conn, cookie, NULL);
  if (reply)
    atom = reply->atom;
  else
    atom = XCB_NONE;

  return atom;
}

static gboolean
create_instance (VkInstance *vk_instance,
                 GError **error)
{
  g_return_val_if_fail (vk_instance != NULL, FALSE);

  *vk_instance = VK_NULL_HANDLE;

  static const char *required_extensions[] =
  {
    VK_KHR_XCB_SURFACE_EXTENSION_NAME,
    VK_KHR_SURFACE_EXTENSION_NAME,
  };

  const VkApplicationInfo app =
  {
    .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
    .pApplicationName = argv0,
    .applicationVersion = VK_MAKE_VERSION (1, 0, 0),
    .pEngineName = "No Engine",
    .engineVersion = VK_MAKE_VERSION(1, 0, 0),
    .apiVersion = VK_API_VERSION_1_0,
  };

  VkInstanceCreateInfo inst_info =
  {
    .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
    .pNext = NULL,
    .pApplicationInfo = &app,
    .enabledLayerCount = 0,
    .enabledExtensionCount = G_N_ELEMENTS (required_extensions),
    .ppEnabledExtensionNames = required_extensions,
  };

  if (!do_vk (vkCreateInstance (&inst_info, NULL, vk_instance), error))
    return FALSE;

  return TRUE;
}

static gboolean
create_surface (VkInstance vk_instance,
                VkPhysicalDevice physical_device,
                VulkanPhysicalDeviceProperties *dev_props,
                GError **error)
{
  xcb_connection_t *xcb_connection;
  xcb_screen_iterator_t iter;
  static const char title[] = "Vulkan Test";

  xcb_connection = xcb_connect (0, 0);
  if (xcb_connection_has_error (xcb_connection))
    return glnx_throw (error, "Unable to initialize xcb connection");

  uint32_t xcb_window = xcb_generate_id (xcb_connection);

  uint32_t window_values[] = {
    XCB_EVENT_MASK_EXPOSURE |
    XCB_EVENT_MASK_STRUCTURE_NOTIFY |
    XCB_EVENT_MASK_KEY_PRESS
  };

  iter = xcb_setup_roots_iterator (xcb_get_setup (xcb_connection));

  xcb_create_window (xcb_connection,
                     XCB_COPY_FROM_PARENT,
                     xcb_window,
                     iter.data->root,
                     0, 0,
                     WIDTH,
                     HEIGHT,
                     0,
                     XCB_WINDOW_CLASS_INPUT_OUTPUT,
                     iter.data->root_visual,
                     XCB_CW_EVENT_MASK, window_values);

  xcb_atom_t atom_wm_protocols = get_atom (xcb_connection, "WM_PROTOCOLS");
  xcb_atom_t atom_wm_delete_window = get_atom (xcb_connection, "WM_DELETE_WINDOW");
  xcb_change_property (xcb_connection,
                       XCB_PROP_MODE_REPLACE,
                       xcb_window,
                       atom_wm_protocols,
                       XCB_ATOM_ATOM,
                       32,
                       1, &atom_wm_delete_window);

  xcb_change_property (xcb_connection,
                       XCB_PROP_MODE_REPLACE,
                       xcb_window,
                       get_atom (xcb_connection, "_NET_WM_NAME"),
                       get_atom (xcb_connection, "UTF8_STRING"),
                       8,
                       strlen (title), title);

  // we don't normally want this test to be visible to the user
  if (opt_visible)
    xcb_map_window (xcb_connection, xcb_window);

  xcb_flush (xcb_connection);

  PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR get_xcb_presentation_support =
    (PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR)
    vkGetInstanceProcAddr (vk_instance, "vkGetPhysicalDeviceXcbPresentationSupportKHR");
  PFN_vkCreateXcbSurfaceKHR create_xcb_surface =
    (PFN_vkCreateXcbSurfaceKHR)
    vkGetInstanceProcAddr (vk_instance, "vkCreateXcbSurfaceKHR");

  if (!get_xcb_presentation_support (physical_device, 0,
                                     xcb_connection,
                                     iter.data->root_visual))
    return glnx_throw (error, "Vulkan not supported on given X window");

  VkXcbSurfaceCreateInfoKHR createSurfaceInfo =
  {
    .sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR,
    .connection = xcb_connection,
    .window = xcb_window,
  };

  if (!do_vk (create_xcb_surface (vk_instance, &createSurfaceInfo, NULL, &dev_props->surface),
              error))
    return FALSE;

  return TRUE;
}

static gboolean
get_physical_devices (VkInstance instance,
                      uint32_t *physical_device_count,
                      VkPhysicalDevice **physical_devices,
                      GError **error)
{
  g_return_val_if_fail (physical_device_count != NULL, FALSE);
  g_return_val_if_fail (physical_devices != NULL && *physical_devices == NULL, FALSE);

  if (!do_vk (vkEnumeratePhysicalDevices (instance, physical_device_count, NULL), error))
    return FALSE;

  g_debug ("Physical device count: %i\n", *physical_device_count);
  if (*physical_device_count == 0)
    return glnx_throw (error, "Failed to find GPUs with Vulkan support!");

  *physical_devices = g_new0 (VkPhysicalDevice, *physical_device_count);

  if (!do_vk (vkEnumeratePhysicalDevices (instance, physical_device_count, *physical_devices),
              error))
    return FALSE;

  return TRUE;
}

QueueFamilyIndices find_queue_families (VkPhysicalDevice dev,
                                        VkSurfaceKHR surface);

static gboolean
create_logical_device (VkPhysicalDevice physical_device,
                       VulkanPhysicalDeviceProperties *dev_props,
                       GError **error)
{
  uint32_t i;
  QueueFamilyIndices indices = find_queue_families (physical_device, dev_props->surface);
  uint32_t unique_queue_families[] =
  {
    indices.graphicsFamily,
    indices.presentFamily
  };
  VkDeviceQueueCreateInfo *queueCreateInfos = g_new0 (VkDeviceQueueCreateInfo,
                                                      G_N_ELEMENTS (unique_queue_families));
  float queuePriority = 1.0f;

  g_return_val_if_fail (dev_props != NULL, FALSE);

  for (i = 0; i < G_N_ELEMENTS (unique_queue_families); i++)
    {
      VkDeviceQueueCreateInfo queueCreateInfo =
      {
        .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
        .queueFamilyIndex = unique_queue_families[i],
        .queueCount = 1,
        .pQueuePriorities = &queuePriority,
      };
      queueCreateInfos[i] = queueCreateInfo;
    }

  VkPhysicalDeviceFeatures deviceFeatures = { .robustBufferAccess = VK_FALSE };

  VkDeviceCreateInfo createInfo =
  {
    .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
    .queueCreateInfoCount = G_N_ELEMENTS (unique_queue_families),
    .pQueueCreateInfos = queueCreateInfos,
    .pEnabledFeatures = &deviceFeatures,
    .enabledExtensionCount = G_N_ELEMENTS (device_extensions),
    .ppEnabledExtensionNames = device_extensions,
    .enabledLayerCount = 0,
  };

  if (!do_vk (vkCreateDevice (physical_device, &createInfo, NULL, &dev_props->device), error))
    return FALSE;

  vkGetDeviceQueue (dev_props->device, indices.graphicsFamily, 0, &dev_props->graphics_queue);
  vkGetDeviceQueue (dev_props->device, indices.presentFamily, 0, &dev_props->present_queue);

  return TRUE;
}

static VkSurfaceFormatKHR
choose_swap_surface_format (VkSurfaceFormatKHR *formats,
                            uint32_t format_count)
{
  uint32_t i;

  for (i = 0; i < format_count; i++)
    {
      if (formats[i].format == VK_FORMAT_B8G8R8A8_UNORM
          && formats[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
        return formats[i];
    }

  return formats[0];
}

static VkPresentModeKHR
choose_swap_present_mode (VkPresentModeKHR *present_modes,
                          uint32_t mode_count)
{
  uint32_t i;

  g_return_val_if_fail (present_modes != NULL, VK_PRESENT_MODE_MAX_ENUM_KHR);

  for (i = 0; i < mode_count; i++)
    {
      if (present_modes[i] == VK_PRESENT_MODE_MAILBOX_KHR)
        return present_modes[i];
    }

  return VK_PRESENT_MODE_FIFO_KHR;
}

static VkExtent2D
choose_swap_extent (const VkSurfaceCapabilitiesKHR capabilities)
{
  if (capabilities.currentExtent.width == UINT32_MAX)
    {
      VkExtent2D actualExtent = { WIDTH, HEIGHT };

      actualExtent.width = MAX (capabilities.minImageExtent.width,
                                MIN (capabilities.maxImageExtent.width, actualExtent.width));
      actualExtent.height = MAX (capabilities.minImageExtent.height,
                                 MIN (capabilities.maxImageExtent.height, actualExtent.height));

      return actualExtent;
    }

  return capabilities.currentExtent;
}

static SwapChainSupportDetails
query_swapchain_support (VkPhysicalDevice dev,
                         VkSurfaceKHR surface)
{
  SwapChainSupportDetails details = { .format_count = 0, };

  vkGetPhysicalDeviceSurfaceCapabilitiesKHR (dev, surface, &details.capabilities);
  vkGetPhysicalDeviceSurfaceFormatsKHR (dev, surface, &details.format_count, NULL);

  if (details.format_count != 0)
    {
      details.formats = g_new0 (VkSurfaceFormatKHR, details.format_count);
      vkGetPhysicalDeviceSurfaceFormatsKHR (dev, surface, &details.format_count,
                                            details.formats);
    }

  vkGetPhysicalDeviceSurfacePresentModesKHR (dev, surface, &details.mode_count, NULL);

  if (details.mode_count != 0)
    {
      details.present_modes = g_new0 (VkPresentModeKHR, details.mode_count);
      vkGetPhysicalDeviceSurfacePresentModesKHR (dev, surface, &details.mode_count,
                                                 details.present_modes);
    }

  return details;
}

static gboolean
create_image_view (VkDevice device,
                   VkImage image,
                   VkFormat format,
                   VkImageView *image_view,
                   GError **error)
{
  VkImageViewCreateInfo create_info =
  {
    .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
    .image = image,
    .viewType = VK_IMAGE_VIEW_TYPE_2D,
    .format = format,
    .components.r = VK_COMPONENT_SWIZZLE_IDENTITY,
    .components.g = VK_COMPONENT_SWIZZLE_IDENTITY,
    .components.b = VK_COMPONENT_SWIZZLE_IDENTITY,
    .components.a = VK_COMPONENT_SWIZZLE_IDENTITY,
    .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
    .subresourceRange.baseMipLevel = 0,
    .subresourceRange.levelCount = 1,
    .subresourceRange.baseArrayLayer = 0,
    .subresourceRange.layerCount = 1,
  };

  if (!do_vk (vkCreateImageView (device, &create_info, NULL, image_view), error))
    return FALSE;

  return TRUE;
}

static gboolean
create_swapchain (VkPhysicalDevice physical_device,
                  VulkanPhysicalDeviceProperties *dev_props,
                  GError **error)
{
  uint32_t i;

  SwapChainSupportDetails swapchain_support = query_swapchain_support (physical_device,
                                                                       dev_props->surface);
  VkSurfaceFormatKHR surface_format = choose_swap_surface_format (swapchain_support.formats,
                                                                  swapchain_support.format_count);
  VkPresentModeKHR present_mode = choose_swap_present_mode (swapchain_support.present_modes,
                                                            swapchain_support.mode_count);
  VkExtent2D extent = choose_swap_extent (swapchain_support.capabilities);

  dev_props->framebuffer_size = swapchain_support.capabilities.minImageCount + 1;
  if (swapchain_support.capabilities.maxImageCount > 0
      && dev_props->framebuffer_size > swapchain_support.capabilities.maxImageCount)
    dev_props->framebuffer_size = swapchain_support.capabilities.maxImageCount;

  VkSwapchainCreateInfoKHR create_info =
  {
    .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
    .surface = dev_props->surface,
    .minImageCount = dev_props->framebuffer_size,
    .imageFormat = surface_format.format,
    .imageColorSpace = surface_format.colorSpace,
    .imageExtent = extent,
    .imageArrayLayers = 1,
    .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
    .preTransform = swapchain_support.capabilities.currentTransform,
    .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
    .presentMode = present_mode,
    .clipped = VK_TRUE,
    .oldSwapchain = VK_NULL_HANDLE,
  };

  QueueFamilyIndices indices = find_queue_families (physical_device, dev_props->surface);
  uint32_t queueFamilyIndices[] = {indices.graphicsFamily, indices.presentFamily};

  if (indices.graphicsFamily != indices.presentFamily)
    {
      create_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
      create_info.queueFamilyIndexCount = 2;
      create_info.pQueueFamilyIndices = queueFamilyIndices;
    }
  else
    {
      create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
    }

  if (!do_vk (vkCreateSwapchainKHR (dev_props->device, &create_info, NULL,
                                    &dev_props->swapchain), error))
    return FALSE;

  vkGetSwapchainImagesKHR (dev_props->device, dev_props->swapchain,
                           &dev_props->framebuffer_size, NULL);
  dev_props->swapchain_images = g_new0 (VkImage, dev_props->framebuffer_size);
  vkGetSwapchainImagesKHR (dev_props->device, dev_props->swapchain,
                           &dev_props->framebuffer_size, dev_props->swapchain_images);

  dev_props->swapchain_image_format = surface_format.format;
  dev_props->swapchain_extent = extent;

  dev_props->swapchain_image_views = g_new0 (VkImageView, dev_props->framebuffer_size);
  for (i = 0; i < dev_props->framebuffer_size; i++)
    {
      if (!create_image_view (dev_props->device, dev_props->swapchain_images[i],
                              dev_props->swapchain_image_format,
                              &dev_props->swapchain_image_views[i],
                              error))
        return FALSE;
    }

  return TRUE;
}

static gboolean
create_render_pass (VulkanPhysicalDeviceProperties *dev_props,
                    GError **error)
{
  VkAttachmentDescription color_attachment =
  {
    .format = dev_props->swapchain_image_format,
    .samples = VK_SAMPLE_COUNT_1_BIT,
    .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
    .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
    .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
    .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
    .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
    .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
  };

  VkAttachmentReference color_attachment_ref =
  {
    .attachment = 0,
    .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
  };

  VkSubpassDescription subpass =
  {
    .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
    .colorAttachmentCount = 1,
    .pColorAttachments = &color_attachment_ref,
  };

  VkSubpassDependency dependency =
  {
    .srcSubpass = VK_SUBPASS_EXTERNAL,
    .dstSubpass = 0,
    .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
    .srcAccessMask = 0,
    .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
    .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
  };

  VkRenderPassCreateInfo render_pass_info =
  {
    .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
    .attachmentCount = 1,
    .pAttachments = &color_attachment,
    .subpassCount = 1,
    .pSubpasses = &subpass,
    .dependencyCount = 1,
    .pDependencies = &dependency,
  };

  if (!do_vk (vkCreateRenderPass (dev_props->device, &render_pass_info, NULL,
                                  &dev_props->render_pass), error))
    return FALSE;

  return TRUE;
}

static gboolean
create_shader_module (VkDevice device,
                      const char *filename,
                      VkShaderModule *shader_module,
                      GError **error)
{
  g_autofree gchar *shader_data = NULL;
  gsize size;

  g_return_val_if_fail (filename != NULL, FALSE);
  g_return_val_if_fail (shader_module != NULL, FALSE);

  if (!g_file_get_contents (filename, &shader_data, &size, error))
    return FALSE;

  VkShaderModuleCreateInfo create_info =
  {
    .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
    .codeSize = size,
    .pCode = (uint32_t *) shader_data,
  };

  if (!do_vk (vkCreateShaderModule (device, &create_info, NULL, shader_module), error))
    return FALSE;

  return TRUE;
}

static gboolean
create_graphics_pipeline (VulkanPhysicalDeviceProperties *dev_props,
                          GError **error)
{
  g_autofree gchar *vert_path = NULL;
  g_autofree gchar *frag_path = NULL;
  g_autofree gchar *base_path = NULL;

  g_return_val_if_fail (dev_props != NULL, FALSE);

  base_path = g_strdup (getenv ("SRT_DATA_PATH"));
  if (base_path == NULL)
    base_path = g_path_get_dirname (argv0);

  vert_path = g_build_filename (base_path, "shaders", "/vert.spv", NULL);
  frag_path = g_build_filename (base_path, "shaders", "/frag.spv", NULL);

  VkShaderModule vert_shader_module;
  VkShaderModule frag_shader_module;
  if (!create_shader_module (dev_props->device, vert_path, &vert_shader_module, error))
    return FALSE;
  if (!create_shader_module (dev_props->device, frag_path, &frag_shader_module, error))
    return FALSE;

  VkPipelineShaderStageCreateInfo vert_shader_stage_info =
  {
    .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
    .stage = VK_SHADER_STAGE_VERTEX_BIT,
    .module = vert_shader_module,
    .pName = "main",
  };

  VkPipelineShaderStageCreateInfo frag_shader_stage_info =
  {
    .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
    .stage = VK_SHADER_STAGE_FRAGMENT_BIT,
    .module = frag_shader_module,
    .pName = "main",
  };

  VkPipelineShaderStageCreateInfo shader_stages[] =
  {
    vert_shader_stage_info,
    frag_shader_stage_info,
  };

  VkPipelineVertexInputStateCreateInfo vertex_input_info =
  {
    .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
    .vertexBindingDescriptionCount = 0,
    .vertexAttributeDescriptionCount = 0,
  };

  VkPipelineInputAssemblyStateCreateInfo input_assembly =
  {
    .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
    .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
    .primitiveRestartEnable = VK_FALSE,
  };

  VkViewport viewport =
  {
    .x = 0.0f,
    .y = 0.0f,
    .width = (float) dev_props->swapchain_extent.width,
    .height = (float) dev_props->swapchain_extent.height,
    .minDepth = 0.0f,
    .maxDepth = 1.0f,
  };

  VkRect2D scissor =
  {
    .offset = {0, 0},
    .extent = dev_props->swapchain_extent,
  };

  VkPipelineViewportStateCreateInfo viewport_state =
  {
    .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
    .viewportCount = 1,
    .pViewports = &viewport,
    .scissorCount = 1,
    .pScissors = &scissor,
  };

  VkPipelineRasterizationStateCreateInfo rasterizer =
  {
    .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
    .depthClampEnable = VK_FALSE,
    .rasterizerDiscardEnable = VK_FALSE,
    .polygonMode = VK_POLYGON_MODE_FILL,
    .lineWidth = 1.0f,
    .cullMode = VK_CULL_MODE_BACK_BIT,
    .frontFace = VK_FRONT_FACE_CLOCKWISE,
    .depthBiasEnable = VK_FALSE,
  };

  VkPipelineMultisampleStateCreateInfo multisampling =
  {
    .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
    .sampleShadingEnable = VK_FALSE,
    .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
  };

  VkPipelineColorBlendAttachmentState color_blend_attachment =
  {
    .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT
                      | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
    .blendEnable = VK_FALSE,
  };

  VkPipelineColorBlendStateCreateInfo color_blending =
  {
    .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
    .logicOpEnable = VK_FALSE,
    .logicOp = VK_LOGIC_OP_COPY,
    .attachmentCount = 1,
    .pAttachments = &color_blend_attachment,
    .blendConstants[0] = 0.0f,
    .blendConstants[1] = 0.0f,
    .blendConstants[2] = 0.0f,
    .blendConstants[3] = 0.0f,
  };

  VkPipelineLayoutCreateInfo pipeline_layout_info =
  {
    .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
    .setLayoutCount = 0,
    .pushConstantRangeCount = 0,
  };

  if (!do_vk (vkCreatePipelineLayout (dev_props->device, &pipeline_layout_info, NULL,
                                      &dev_props->pipeline_layout), error))
    return FALSE;

  VkGraphicsPipelineCreateInfo pipeline_info =
  {
    .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
    .stageCount = 2,
    .pStages = shader_stages,
    .pVertexInputState = &vertex_input_info,
    .pInputAssemblyState = &input_assembly,
    .pViewportState = &viewport_state,
    .pRasterizationState = &rasterizer,
    .pMultisampleState = &multisampling,
    .pColorBlendState = &color_blending,
    .layout = dev_props->pipeline_layout,
    .renderPass = dev_props->render_pass,
    .subpass = 0,
    .basePipelineHandle = VK_NULL_HANDLE,
  };

  if (!do_vk (vkCreateGraphicsPipelines (dev_props->device, VK_NULL_HANDLE, 1,
                                         &pipeline_info, NULL,
                                         &dev_props->graphics_pipeline), error))
    return FALSE;

  vkDestroyShaderModule (dev_props->device, frag_shader_module, NULL);
  vkDestroyShaderModule (dev_props->device, vert_shader_module, NULL);

  return TRUE;
}

static gboolean
create_framebuffers (VulkanPhysicalDeviceProperties *dev_props,
                     GError **error)
{
  uint32_t i = 0;
  dev_props->swapchain_framebuffers = g_new0 (VkFramebuffer, dev_props->framebuffer_size);

  g_return_val_if_fail (dev_props != NULL, FALSE);

  for (i = 0; i < dev_props->framebuffer_size; i++)
    {
      VkImageView attachments[] = {
        dev_props->swapchain_image_views[i]
      };

      VkFramebufferCreateInfo framebuffer_info =
      {
        .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
        .renderPass = dev_props->render_pass,
        .attachmentCount = 1,
        .pAttachments = attachments,
        .width = dev_props->swapchain_extent.width,
        .height = dev_props->swapchain_extent.height,
        .layers = 1,
      };

      if (!do_vk (vkCreateFramebuffer (dev_props->device, &framebuffer_info, NULL,
                                       &dev_props->swapchain_framebuffers[i]), error))
        return FALSE;
    }

  return TRUE;
}

static gboolean
create_command_pool (VkPhysicalDevice physical_device,
                     VulkanPhysicalDeviceProperties *dev_props,
                     GError **error)
{
  QueueFamilyIndices queue_family_indices = find_queue_families (physical_device,
                                                                 dev_props->surface);

  VkCommandPoolCreateInfo pool_info =
  {
    .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
    .queueFamilyIndex = queue_family_indices.graphicsFamily,
  };

  if (!do_vk (vkCreateCommandPool (dev_props->device, &pool_info, NULL,
                                   &dev_props->command_pool), error))
    return FALSE;

  return TRUE;
}

static gboolean
create_command_buffers (VulkanPhysicalDeviceProperties *dev_props,
                        GError **error)
{
  uint32_t i;

  g_return_val_if_fail (dev_props != NULL, FALSE);

  VkCommandBufferAllocateInfo alloc_info =
  {
    .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
    .commandPool = dev_props->command_pool,
    .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
    .commandBufferCount = dev_props->framebuffer_size,
  };

  dev_props->command_buffers = g_new0 (VkCommandBuffer, dev_props->framebuffer_size);

  if (!do_vk (vkAllocateCommandBuffers (dev_props->device, &alloc_info,
                                        dev_props->command_buffers), error))
    return FALSE;

  for (i = 0; i < dev_props->framebuffer_size; i++)
    {
      VkCommandBufferBeginInfo begin_info =
      {
        .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
      };

      if (!do_vk (vkBeginCommandBuffer (dev_props->command_buffers[i], &begin_info), error))
        return FALSE;

      VkClearValue clear_color = { { {0.0f, 0.0f, 0.0f, 1.0f} } };
      VkRenderPassBeginInfo render_pass_info =
      {
        .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
        .renderPass = dev_props->render_pass,
        .framebuffer = dev_props->swapchain_framebuffers[i],
        .renderArea.offset = {0, 0},
        .renderArea.extent = dev_props->swapchain_extent,
        .clearValueCount = 1,
        .pClearValues = &clear_color,
      };

      vkCmdBeginRenderPass (dev_props->command_buffers[i], &render_pass_info,
                            VK_SUBPASS_CONTENTS_INLINE);
      vkCmdBindPipeline (dev_props->command_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS,
                         dev_props->graphics_pipeline);
      vkCmdDraw (dev_props->command_buffers[i], 3, 1, 0, 0);
      vkCmdEndRenderPass (dev_props->command_buffers[i]);
      if (!do_vk (vkEndCommandBuffer (dev_props->command_buffers[i]), error))
        return FALSE;
    }

  return TRUE;
}

static gboolean
create_sync_objects (VulkanPhysicalDeviceProperties *dev_props,
                     GError **error)
{
  uint32_t i;

  g_return_val_if_fail (dev_props != NULL, FALSE);

  dev_props->image_available_semaphores = g_new0 (VkSemaphore, MAX_FRAMES_IN_FLIGHT);
  dev_props->render_finished_semaphores = g_new0 (VkSemaphore, MAX_FRAMES_IN_FLIGHT);
  dev_props->in_flight_fences = g_new0 (VkFence, MAX_FRAMES_IN_FLIGHT);

  VkSemaphoreCreateInfo semaphore_info =
  {
    .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
  };

  VkFenceCreateInfo fence_info =
  {
    .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
    .flags = VK_FENCE_CREATE_SIGNALED_BIT,
  };

  for (i = 0; i < MAX_FRAMES_IN_FLIGHT; i++)
    {
      if (!do_vk (vkCreateSemaphore (dev_props->device, &semaphore_info, NULL,
                                     (&dev_props->image_available_semaphores[i])), error))
        return FALSE;
      if (!do_vk (vkCreateSemaphore (dev_props->device, &semaphore_info, NULL,
                                     (&dev_props->render_finished_semaphores[i])), error))
        return FALSE;
      if (!do_vk (vkCreateFence (dev_props->device, &fence_info, NULL,
                                 (&dev_props->in_flight_fences[i])), error))
        return FALSE;
    }
  return TRUE;
}

static gboolean
draw_frame (VulkanPhysicalDeviceProperties *dev_props,
            GError **error)
{
  g_return_val_if_fail (dev_props != NULL, FALSE);

  if (!do_vk (vkWaitForFences (dev_props->device, 1,
                               &dev_props->in_flight_fences[dev_props->current_frame],
                               VK_TRUE, UINT64_MAX), error))
    return FALSE;

  if (!do_vk (vkResetFences (dev_props->device, 1,
                             &dev_props->in_flight_fences[dev_props->current_frame]),
                             error))
    return FALSE;

  uint32_t image_index;
  if (!do_vk (vkAcquireNextImageKHR (dev_props->device, dev_props->swapchain, UINT64_MAX,
                                     dev_props->image_available_semaphores[dev_props->current_frame],
                                     VK_NULL_HANDLE, &image_index), error))
    return FALSE;

  VkSemaphore wait_semaphores[] =
  {
    dev_props->image_available_semaphores[dev_props->current_frame],
  };
  VkPipelineStageFlags wait_stages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
  VkSemaphore signal_semaphores[] =
  {
    dev_props->render_finished_semaphores[dev_props->current_frame],
  };

  VkSubmitInfo submit_info =
  {
    .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
    .waitSemaphoreCount = 1,
    .pWaitSemaphores = wait_semaphores,
    .pWaitDstStageMask = wait_stages,
    .commandBufferCount = 1,
    .pCommandBuffers = &dev_props->command_buffers[image_index],
    .signalSemaphoreCount = 1,
    .pSignalSemaphores = signal_semaphores,
  };

  if (!do_vk (vkQueueSubmit (dev_props->graphics_queue, 1, &submit_info,
                             dev_props->in_flight_fences[dev_props->current_frame]), error))
    return FALSE;

  VkSwapchainKHR swapchains[] = {dev_props->swapchain};
  VkPresentInfoKHR present_info =
  {
    .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
    .waitSemaphoreCount = 1,
    .pWaitSemaphores = signal_semaphores,
    .swapchainCount = 1,
    .pSwapchains = swapchains,
    .pImageIndices = &image_index,
  };

  if (!do_vk (vkQueuePresentKHR (dev_props->present_queue, &present_info), error))
    return FALSE;

  dev_props->current_frame = (dev_props->current_frame + 1) % MAX_FRAMES_IN_FLIGHT;

  return TRUE;
}


QueueFamilyIndices
find_queue_families (VkPhysicalDevice dev,
                     VkSurfaceKHR surface)
{
  QueueFamilyIndices indices =
  {
    .hasGraphicsFamily = FALSE,
    .hasPresentFamily = FALSE,
  };

  uint32_t i;
  uint32_t queue_family_count = 0;
  vkGetPhysicalDeviceQueueFamilyProperties (dev, &queue_family_count, NULL);

  VkQueueFamilyProperties *queue_families = g_new0 (VkQueueFamilyProperties,
                                                    queue_family_count);
  vkGetPhysicalDeviceQueueFamilyProperties (dev, &queue_family_count, queue_families);

  for (i = 0; i < queue_family_count; i++)
    {
      VkQueueFamilyProperties queue_family = queue_families[i];
      if (queue_family.queueCount > 0 && queue_family.queueFlags & VK_QUEUE_GRAPHICS_BIT)
        {
          indices.graphicsFamily = i;
          indices.hasGraphicsFamily = TRUE;
        }

      VkBool32 presentSupport = VK_FALSE;
      vkGetPhysicalDeviceSurfaceSupportKHR (dev, i, surface, &presentSupport);

      if (queue_family.queueCount > 0 && presentSupport)
        {
          indices.presentFamily = i;
          indices.hasPresentFamily = TRUE;
        }

      if (queue_family_indices_is_complete (indices))
        break;
    }

  return indices;
}

static gboolean
device_wait_idle (VulkanPhysicalDeviceProperties *dev_props,
                  GError **error)
{
  if (!do_vk (vkDeviceWaitIdle (dev_props->device), error))
    return FALSE;

  return TRUE;
}

static gboolean
draw_test_triangle (VkInstance vk_instance,
                    VkPhysicalDevice physical_device,
                    GError **error)
{
  gsize i;
  gboolean ret = FALSE;

  VulkanPhysicalDeviceProperties dev_props = { .framebuffer_size = 0, };

  if (!create_surface (vk_instance, physical_device, &dev_props, error))
    goto out;
  if (!create_logical_device (physical_device, &dev_props, error))
    goto out;
  if (!create_swapchain (physical_device, &dev_props, error))
    goto out;
  if (!create_render_pass (&dev_props, error))
    goto out;
  if (!create_graphics_pipeline (&dev_props, error))
    goto out;
  if (!create_framebuffers (&dev_props, error))
    goto out;
  if (!create_command_pool (physical_device, &dev_props, error))
    goto out;
  if (!create_command_buffers (&dev_props, error))
    goto out;
  if (!create_sync_objects (&dev_props, error))
    goto out;

  dev_props.current_frame = 0;

  for (i = 0; i < (opt_visible ? 10000 : 10); i++)
    {
      if (!draw_frame (&dev_props, error))
        goto out;
    }

  if (!device_wait_idle (&dev_props, error))
    goto out;

  ret = TRUE;

out:
  /* Cleanup */
  if (dev_props.device != VK_NULL_HANDLE)
    {
      for (i = 0; i < MAX_FRAMES_IN_FLIGHT; i++)
        {
          if (dev_props.render_finished_semaphores != NULL
              && dev_props.render_finished_semaphores[i] != VK_NULL_HANDLE)
            vkDestroySemaphore (dev_props.device, dev_props.render_finished_semaphores[i], NULL);
          if (dev_props.image_available_semaphores != NULL
              && dev_props.image_available_semaphores[i] != VK_NULL_HANDLE)
            vkDestroySemaphore (dev_props.device, dev_props.image_available_semaphores[i], NULL);
          if (dev_props.in_flight_fences != NULL
              && dev_props.in_flight_fences[i] != VK_NULL_HANDLE)
            vkDestroyFence (dev_props.device, dev_props.in_flight_fences[i], NULL);
        }
      for (i = 0; i < dev_props.framebuffer_size; i++)
        {
          if (dev_props.swapchain_framebuffers != NULL
              && dev_props.swapchain_framebuffers[i] != VK_NULL_HANDLE)
            vkDestroyFramebuffer (dev_props.device, dev_props.swapchain_framebuffers[i], NULL);
          if (dev_props.swapchain_image_views != NULL
              && dev_props.swapchain_image_views[i] != VK_NULL_HANDLE)
          vkDestroyImageView (dev_props.device, dev_props.swapchain_image_views[i], NULL);
        }
      vkDestroyCommandPool (dev_props.device, dev_props.command_pool, NULL);
      if (dev_props.graphics_pipeline != VK_NULL_HANDLE)
        vkDestroyPipeline (dev_props.device, dev_props.graphics_pipeline, NULL);
      if (dev_props.pipeline_layout != VK_NULL_HANDLE)
        vkDestroyPipelineLayout (dev_props.device, dev_props.pipeline_layout, NULL);
      if (dev_props.render_pass != VK_NULL_HANDLE)
        vkDestroyRenderPass (dev_props.device, dev_props.render_pass, NULL);
      vkDestroySwapchainKHR (dev_props.device, dev_props.swapchain, NULL);
      vkDestroyDevice (dev_props.device, NULL);
    }

  if (vk_instance != VK_NULL_HANDLE)
    vkDestroySurfaceKHR (vk_instance, dev_props.surface, NULL);

  return ret;
}

static void
print_json_builder (JsonBuilder *builder,
                    FILE *original_stdout)
{
  g_autoptr(JsonNode) root = NULL;
  g_autoptr(JsonGenerator) generator = NULL;
  g_autofree gchar *json = NULL;

  root = json_builder_get_root (builder);
  generator = json_generator_new ();
  json_generator_set_pretty (generator, opt_pretty_print);
  json_generator_set_root (generator, root);
  json = json_generator_to_data (generator, NULL);
  if (fputs (json, original_stdout) < 0)
    g_warning ("Unable to write output: %s", g_strerror (errno));

  if (fputs ("\n", original_stdout) < 0)
    g_warning ("Unable to write final newline: %s", g_strerror (errno));
}

static void
print_physical_device_info (VkPhysicalDevice physical_device,
                            FILE *original_stdout)
{
  g_autoptr(JsonBuilder) builder = NULL;
  g_autofree gchar *api_version = NULL;
  g_autofree gchar *driver_version = NULL;
  g_autofree gchar *vendor_id = NULL;
  g_autofree gchar *device_id = NULL;
  VkPhysicalDeviceProperties device_properties;

  builder = json_builder_new ();
  json_builder_begin_object (builder);

  json_builder_set_member_name (builder, "device-info");
  json_builder_begin_object (builder);

  vkGetPhysicalDeviceProperties (physical_device, &device_properties);

  json_builder_set_member_name (builder, "device-name");
  json_builder_add_string_value (builder, device_properties.deviceName);

  json_builder_set_member_name (builder, "device-type");
  json_builder_add_int_value (builder, device_properties.deviceType);

  json_builder_set_member_name (builder, "api-version");
  api_version = g_strdup_printf ("%u.%u.%u",
                                 VK_VERSION_MAJOR (device_properties.apiVersion),
                                 VK_VERSION_MINOR (device_properties.apiVersion),
                                 VK_VERSION_PATCH (device_properties.apiVersion));
  json_builder_add_string_value (builder, api_version);

  json_builder_set_member_name (builder, "driver-version");
  driver_version = g_strdup_printf ("%u.%u.%u",
                                    VK_VERSION_MAJOR (device_properties.driverVersion),
                                    VK_VERSION_MINOR (device_properties.driverVersion),
                                    VK_VERSION_PATCH (device_properties.driverVersion));
  json_builder_add_string_value (builder, driver_version);

  json_builder_set_member_name (builder, "vendor-id");
  vendor_id = g_strdup_printf ("%#x", device_properties.vendorID);
  json_builder_add_string_value (builder, vendor_id);

  json_builder_set_member_name (builder, "device-id");
  device_id = g_strdup_printf ("%#x", device_properties.deviceID);
  json_builder_add_string_value (builder, device_id);

  json_builder_end_object (builder);
  json_builder_end_object (builder);

  print_json_builder (builder, original_stdout);
}

static void
print_draw_test_result (gsize index,
                        gboolean result,
                        const GError *error,
                        FILE *original_stdout)
{
  g_autoptr(JsonBuilder) builder = NULL;

  builder = json_builder_new ();
  json_builder_begin_object (builder);

  json_builder_set_member_name (builder, "test");
  json_builder_begin_object (builder);

  json_builder_set_member_name (builder, "index");
  json_builder_add_int_value (builder, index);

  json_builder_set_member_name (builder, "can-draw");
  json_builder_add_boolean_value (builder, result);

  if (error != NULL)
    {
      json_builder_set_member_name (builder, "error-message");
      json_builder_add_string_value (builder, error->message);
    }

  json_builder_end_object (builder);
  json_builder_end_object (builder);

  print_json_builder (builder, original_stdout);
}

static const GOptionEntry option_entries[] =
{
  { "pretty-print", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
    &opt_pretty_print, "The generated JSON will be pretty printed instead "
    "of being one object per line", NULL },
  { "version", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_print_version,
    "Print version number and exit", NULL },
  { "visible", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_visible,
    "Make test window visible", NULL },
  { NULL }
};

int main (int argc,
          char** argv)
{
  g_autoptr(FILE) original_stdout = NULL;
  g_autoptr(GError) local_error = NULL;
  g_autoptr(GOptionContext) option_context = NULL;
  g_autofree VkPhysicalDevice *physical_devices = NULL;
  VkInstance vk_instance;
  uint32_t physical_device_count = 0;
  GError **error = &local_error;
  int ret = EXIT_FAILURE;
  gboolean result;
  gsize i;

  argv0 = argv[0];

  option_context = g_option_context_new ("");
  g_option_context_add_main_entries (option_context, option_entries, NULL);

  if (!g_option_context_parse (option_context, &argc, &argv, NULL))
    return 2;

  if (opt_print_version)
    {
      /* Output version number as YAML for machine-readability,
       * inspired by `ostree --version` and `docker version` */
      g_print ("%s:\n"
               " Package: steam-runtime-tools\n"
               " Version: %s\n",
               argv[0], VERSION);
      return EXIT_SUCCESS;
    }

  /* stdout is reserved for machine-readable output, so avoid having
   * things like g_debug() pollute it. */
  original_stdout = _srt_divert_stdout_to_stderr (error);

  if (original_stdout == NULL)
    {
      g_printerr ("Unable to divert stdout to stderr: %s", local_error->message);
      return EXIT_FAILURE;
    }

  if (!create_instance (&vk_instance, error))
    goto out;

  if (!get_physical_devices (vk_instance, &physical_device_count, &physical_devices,
                             error))
    goto out;

  for (i = 0; i < physical_device_count; i++)
    print_physical_device_info (physical_devices[i], original_stdout);

  for (i = 0; i < physical_device_count; i++)
    {
      result = draw_test_triangle (vk_instance, physical_devices[i], error);
      print_draw_test_result (i, result, local_error, original_stdout);

      /* The eventual error has already been included in the drawing test JSON */
      g_clear_error (error);

      /* Return exit success if we are able to draw with at least one device */
      if (result)
        ret = EXIT_SUCCESS;
    }

out:
  if (local_error != NULL)
    g_printerr ("%s", local_error->message);

  vkDestroyInstance (vk_instance, NULL);

  return ret;
}