Преглед на файлове

Merge pull request #583 from m0ppers/vulkan

add vulkan demo
Richard Gill преди 1 година
родител
ревизия
a18175cff0

+ 4 - 0
demo/glfw_vulkan/.clang-format

@@ -0,0 +1,4 @@
+---
+BasedOnStyle: LLVM
+# same as .editorconfig
+IndentWidth: 4

+ 3 - 0
demo/glfw_vulkan/.gitignore

@@ -0,0 +1,3 @@
+src/nuklearshaders/*.spv
+src/nuklear_glfw_vulkan.h
+shaders/*.spv

+ 29 - 0
demo/glfw_vulkan/Makefile

@@ -0,0 +1,29 @@
+# Install
+BIN = demo
+
+# Flags
+CFLAGS += -std=c89 -Wall -Wextra -pedantic -O2
+
+SRC = main.c
+OBJ = $(SRC:.c=.o)
+
+ifeq ($(OS),Windows_NT)
+BIN := $(BIN).exe
+LIBS = -lglfw3 -lvulkan -lm
+else
+	UNAME_S := $(shell uname -s)
+	GLFW3 := $(shell pkg-config --libs glfw3)
+    LIBS = $(GLFW3) -lvulkan -lm
+endif
+
+
+$(BIN): shaders/demo.vert.spv shaders/demo.frag.spv
+	@mkdir -p bin
+	rm -f bin/$(BIN) $(OBJS)
+	$(CC) $(SRC) $(CFLAGS) -o bin/$(BIN) $(LIBS)
+
+shaders/demo.vert.spv: shaders/demo.vert
+	glslc --target-env=vulkan shaders/demo.vert -o shaders/demo.vert.spv
+
+shaders/demo.frag.spv: shaders/demo.frag
+	glslc --target-env=vulkan shaders/demo.frag -o shaders/demo.frag.spv

+ 52 - 0
demo/glfw_vulkan/README.md

@@ -0,0 +1,52 @@
+# nuklear glfw vulkan
+
+## Theory of operation
+
+The nuklear glfw vulkan integration creates an independent graphics pipeline that will render the nuklear UI to separate render targets.
+The application is responsible to fully manage these render targets. So it must ensure they are properly sized (and resized when requested).
+
+Furthermore it is assumed that you will have a swap chain in place and the number of nuklear overlay images and number of swap chain images match.
+
+This is how you can integrate it in your application:
+
+```
+/*
+Setup: overlay_images have been created and their number match with the number
+of the swap_chain_images of your application. The overlay_images in this
+example have the same format as your swap_chain images (optional)
+*/
+    struct nk_context *ctx = nk_glfw3_init(
+        demo.win, demo.device, demo.physical_device, demo.indices.graphics,
+        demo.overlay_image_views, demo.swap_chain_images_len,
+        demo.swap_chain_image_format, NK_GLFW3_INSTALL_CALLBACKS,
+        MAX_VERTEX_BUFFER, MAX_ELEMENT_BUFFER);
+[...]
+/*
+in your draw loop draw you can then render to the overlay image at
+`image_index`
+your own application can then wait for the semaphore and produce
+the swap_chain_image at `image_index`
+this should simply sample from the overlay_image (see example)
+*/
+nk_semaphore semaphore =
+    nk_glfw3_render(demo.graphics_queue, image_index,
+                    demo.image_available, NK_ANTI_ALIASING_ON);
+    if (!render(&demo, &bg, nk_semaphore, image_index)) {
+        fprintf(stderr, "render failed\n");
+        return false;
+    }
+```
+
+You must call `nk_glfw3_resize` whenever the size of the overlay_images resize.
+
+## Using images
+
+Images can be used by providing a VkImageView as an nk_image_ptr to nuklear:
+
+```
+img = nk_image_ptr(demo.demo_texture_image_view);
+```
+
+Note that they must have SHADER_READ_OPTIMAL layout
+
+It is currently not possible to specify how they are being sampled. The nuklear glfw vulkan integration uses a fixed sampler that does linear filtering.

+ 2240 - 0
demo/glfw_vulkan/main.c

@@ -0,0 +1,2240 @@
+/* nuklear - 1.32.0 - public domain */
+#include <limits.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <time.h>
+
+#define GLFW_INCLUDE_VULKAN
+#include <GLFW/glfw3.h>
+
+#define NK_INCLUDE_FIXED_TYPES
+#define NK_INCLUDE_STANDARD_IO
+#define NK_INCLUDE_STANDARD_VARARGS
+#define NK_INCLUDE_DEFAULT_ALLOCATOR
+#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
+#define NK_INCLUDE_FONT_BAKING
+#define NK_INCLUDE_DEFAULT_FONT
+#define NK_IMPLEMENTATION
+#define NK_GLFW_VULKAN_IMPLEMENTATION
+#define NK_KEYSTATE_BASED_INPUT
+#include "../../nuklear.h"
+#include "nuklear_glfw_vulkan.h"
+
+#define WINDOW_WIDTH 1200
+#define WINDOW_HEIGHT 800
+
+#define MAX_VERTEX_BUFFER 512 * 1024
+#define MAX_ELEMENT_BUFFER 128 * 1024
+
+/* ===============================================================
+ *
+ *                          EXAMPLE
+ *
+ * ===============================================================*/
+/* This are some code examples to provide a small overview of what can be
+ * done with this library. To try out an example uncomment the defines */
+#define INCLUDE_ALL
+/*#define INCLUDE_STYLE */
+/*#define INCLUDE_CALCULATOR */
+/*#define INCLUDE_CANVAS */
+/*#define INCLUDE_OVERVIEW*/
+/*#define INCLUDE_NODE_EDITOR */
+
+#ifdef INCLUDE_ALL
+#define INCLUDE_STYLE
+#define INCLUDE_CALCULATOR
+#define INCLUDE_CANVAS
+#define INCLUDE_OVERVIEW
+#define INCLUDE_NODE_EDITOR
+#endif
+
+#ifdef INCLUDE_STYLE
+#include "../../demo/common/style.c"
+#endif
+#ifdef INCLUDE_CALCULATOR
+#include "../../demo/common/calculator.c"
+#endif
+#ifdef INCLUDE_CANVAS
+#include "../../demo/common/canvas.c"
+#endif
+#ifdef INCLUDE_OVERVIEW
+#include "../../demo/common/overview.c"
+#endif
+#ifdef INCLUDE_NODE_EDITOR
+#include "../../demo/common/node_editor.c"
+#endif
+
+/* ===============================================================
+ *
+ *                          DEMO
+ *
+ * ===============================================================*/
+
+static const char *validation_layer_name = "VK_LAYER_KHRONOS_validation";
+
+struct queue_family_indices {
+    int graphics;
+    int present;
+};
+
+struct swap_chain_support_details {
+    VkSurfaceCapabilitiesKHR capabilities;
+    VkSurfaceFormatKHR *formats;
+    uint32_t formats_len;
+    VkPresentModeKHR *present_modes;
+    uint32_t present_modes_len;
+};
+
+void swap_chain_support_details_free(
+    struct swap_chain_support_details *swap_chain_support) {
+    if (swap_chain_support->formats_len > 0) {
+        free(swap_chain_support->formats);
+        swap_chain_support->formats = NULL;
+    }
+    if (swap_chain_support->present_modes_len > 0) {
+        free(swap_chain_support->present_modes);
+        swap_chain_support->present_modes = NULL;
+    }
+}
+
+struct vulkan_demo {
+    GLFWwindow *win;
+    VkInstance instance;
+    VkDebugUtilsMessengerEXT debug_messenger;
+    VkSurfaceKHR surface;
+    VkPhysicalDevice physical_device;
+    struct queue_family_indices indices;
+    VkDevice device;
+    VkQueue graphics_queue;
+    VkQueue present_queue;
+    VkSampler sampler;
+
+    VkSwapchainKHR swap_chain;
+    VkImage *swap_chain_images;
+    uint32_t swap_chain_images_len;
+    VkImageView *swap_chain_image_views;
+    VkFormat swap_chain_image_format;
+    VkExtent2D swap_chain_image_extent;
+
+    VkImage *overlay_images;
+    VkImageView *overlay_image_views;
+    VkDeviceMemory *overlay_image_memories;
+
+    VkRenderPass render_pass;
+    VkFramebuffer *framebuffers;
+    VkDescriptorSetLayout descriptor_set_layout;
+    VkDescriptorPool descriptor_pool;
+    VkDescriptorSet *descriptor_sets;
+    VkPipelineLayout pipeline_layout;
+    VkPipeline pipeline;
+    VkCommandPool command_pool;
+    VkCommandBuffer *command_buffers;
+    VkSemaphore image_available;
+    VkSemaphore render_finished;
+
+    VkImage demo_texture_image;
+    VkImageView demo_texture_image_view;
+    VkDeviceMemory demo_texture_memory;
+
+    VkFence render_fence;
+};
+
+static void glfw_error_callback(int e, const char *d) {
+    fprintf(stderr, "Error %d: %s\n", e, d);
+}
+
+VKAPI_ATTR VkBool32 VKAPI_CALL
+vulkan_debug_callback(VkDebugUtilsMessageSeverityFlagBitsEXT message_severity,
+                      VkDebugUtilsMessageTypeFlagsEXT message_type,
+                      const VkDebugUtilsMessengerCallbackDataEXT *callback_data,
+                      void *user_data) {
+    (void)message_severity;
+    (void)message_type;
+    (void)user_data;
+    fprintf(stderr, "validation layer: %s\n", callback_data->pMessage);
+
+    return VK_FALSE;
+}
+
+bool check_validation_layer_support() {
+    uint32_t layer_count;
+    bool ret = false;
+    VkResult result;
+    uint32_t i;
+    VkLayerProperties *available_layers = NULL;
+
+    result = vkEnumerateInstanceLayerProperties(&layer_count, NULL);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkEnumerateInstanceLayerProperties failed: %d\n",
+                result);
+        return ret;
+    }
+
+    available_layers = malloc(layer_count * sizeof(VkLayerProperties));
+    result = vkEnumerateInstanceLayerProperties(&layer_count, available_layers);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkEnumerateInstanceLayerProperties failed: %d\n",
+                result);
+        goto cleanup;
+    }
+
+    printf("Available vulkan layers:\n");
+    for (i = 0; i < layer_count; i++) {
+        printf("  %s\n", available_layers[i].layerName);
+        if (strcmp(validation_layer_name, available_layers[i].layerName) == 0) {
+            ret = true;
+            break;
+        }
+    }
+cleanup:
+    free(available_layers);
+    return ret;
+}
+
+VkResult create_debug_utils_messenger_ext(
+    VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT *pCreateInfo,
+    const VkAllocationCallbacks *pAllocator,
+    VkDebugUtilsMessengerEXT *pDebugMessenger) {
+    PFN_vkCreateDebugUtilsMessengerEXT func =
+        (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
+            instance, "vkCreateDebugUtilsMessengerEXT");
+    if (func != NULL) {
+        return func(instance, pCreateInfo, pAllocator, pDebugMessenger);
+    } else {
+        return VK_ERROR_EXTENSION_NOT_PRESENT;
+    }
+}
+
+bool create_debug_callback(struct vulkan_demo *demo) {
+    VkResult result;
+
+    VkDebugUtilsMessengerCreateInfoEXT create_info;
+    memset(&create_info, 0, sizeof(VkDebugUtilsMessengerCreateInfoEXT));
+    create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
+    create_info.messageSeverity =
+        VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
+        VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
+    create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
+                              VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
+                              VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
+    create_info.pfnUserCallback = vulkan_debug_callback;
+
+    result = create_debug_utils_messenger_ext(demo->instance, &create_info,
+                                              NULL, &demo->debug_messenger);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "create_debug_utils_messenger_ext failed %d\n", result);
+        return false;
+    }
+    return true;
+}
+
+bool create_instance(struct vulkan_demo *demo) {
+    uint32_t i;
+    uint32_t available_instance_extension_count;
+    VkResult result;
+    VkExtensionProperties *available_instance_extensions = NULL;
+    bool ret = false;
+    VkApplicationInfo app_info;
+    VkInstanceCreateInfo create_info;
+    uint32_t glfw_extension_count;
+    const char **glfw_extensions;
+    uint32_t enabled_extension_count;
+    const char **enabled_extensions = NULL;
+    bool validation_layers_installed;
+
+    validation_layers_installed = check_validation_layer_support();
+
+    if (!validation_layers_installed) {
+        fprintf(stdout,
+                "Couldn't find validation layer %s. Continuing without "
+                "validation layers.\n",
+                validation_layer_name);
+    }
+    result = vkEnumerateInstanceExtensionProperties(
+        NULL, &available_instance_extension_count, NULL);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkEnumerateInstanceExtensionProperties failed %d\n",
+                result);
+        return ret;
+    }
+
+    available_instance_extensions = malloc(available_instance_extension_count *
+                                           sizeof(VkExtensionProperties));
+
+    result = vkEnumerateInstanceExtensionProperties(
+        NULL, &available_instance_extension_count,
+        available_instance_extensions);
+
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkEnumerateInstanceExtensionProperties failed %d\n",
+                result);
+        goto cleanup;
+    }
+
+    printf("available instance extensions:\n");
+    for (i = 0; i < available_instance_extension_count; i++) {
+        printf("  %s\n", available_instance_extensions[i].extensionName);
+    }
+
+    glfw_extensions = glfwGetRequiredInstanceExtensions(&glfw_extension_count);
+
+    enabled_extension_count = glfw_extension_count;
+    if (validation_layers_installed) {
+        enabled_extension_count += 1;
+        enabled_extensions = malloc(enabled_extension_count * sizeof(char *));
+        memcpy(enabled_extensions, glfw_extensions,
+               glfw_extension_count * sizeof(char *));
+        enabled_extensions[glfw_extension_count] =
+            VK_EXT_DEBUG_UTILS_EXTENSION_NAME;
+    } else {
+        enabled_extensions = malloc(enabled_extension_count * sizeof(char *));
+        memcpy(enabled_extensions, glfw_extensions,
+               glfw_extension_count * sizeof(char *));
+    }
+
+    printf("Trying to enable the following instance extensions: ");
+    for (i = 0; i < enabled_extension_count; i++) {
+        if (i > 0) {
+            printf(", ");
+        }
+        printf(enabled_extensions[i]);
+    }
+    printf("\n");
+    for (i = 0; i < enabled_extension_count; i++) {
+        int extension_missing = 1;
+        uint32_t j;
+        for (j = 0; j < available_instance_extension_count; j++) {
+            if (strcmp(enabled_extensions[i],
+                       available_instance_extensions[j].extensionName) == 0) {
+                extension_missing = 0;
+                break;
+            }
+        }
+        if (extension_missing) {
+            fprintf(stderr, "Extension %s is missing\n", enabled_extensions[i]);
+            return ret;
+        }
+    }
+
+    memset(&app_info, 0, sizeof(VkApplicationInfo));
+    app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
+    app_info.pApplicationName = "Demo";
+    app_info.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
+    app_info.pEngineName = "No Engine";
+    app_info.engineVersion = VK_MAKE_VERSION(1, 0, 0);
+    app_info.apiVersion = VK_API_VERSION_1_0;
+
+    memset(&create_info, 0, sizeof(VkInstanceCreateInfo));
+    create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+    create_info.pApplicationInfo = &app_info;
+    create_info.enabledExtensionCount = enabled_extension_count;
+    create_info.ppEnabledExtensionNames = enabled_extensions;
+    if (validation_layers_installed) {
+        create_info.enabledLayerCount = 1;
+        create_info.ppEnabledLayerNames = &validation_layer_name;
+    }
+    result = vkCreateInstance(&create_info, NULL, &demo->instance);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkCreateInstance result %d\n", result);
+        return ret;
+    }
+    if (validation_layers_installed) {
+        ret = create_debug_callback(demo);
+    } else {
+        ret = true;
+    }
+cleanup:
+    if (available_instance_extensions) {
+        free(available_instance_extensions);
+    }
+    if (enabled_extensions) {
+        free(enabled_extensions);
+    }
+
+    return ret;
+}
+
+bool create_surface(struct vulkan_demo *demo) {
+    VkResult result;
+    result = glfwCreateWindowSurface(demo->instance, demo->win, NULL,
+                                     &demo->surface);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "creating vulkan surface failed: %d\n", result);
+        return false;
+    }
+    return true;
+}
+
+bool find_queue_families(VkPhysicalDevice physical_device, VkSurfaceKHR surface,
+                         struct queue_family_indices *indices) {
+    VkResult result;
+    uint32_t queue_family_count = 0;
+    uint32_t i = 0;
+    bool ret = false;
+    VkQueueFamilyProperties *queue_family_properties;
+    VkBool32 present_support;
+
+    vkGetPhysicalDeviceQueueFamilyProperties(physical_device,
+                                             &queue_family_count, NULL);
+
+    queue_family_properties =
+        malloc(queue_family_count * sizeof(VkQueueFamilyProperties));
+    vkGetPhysicalDeviceQueueFamilyProperties(
+        physical_device, &queue_family_count, queue_family_properties);
+
+    for (i = 0; i < queue_family_count; i++) {
+        if (queue_family_properties[i].queueCount == 0) {
+            continue;
+        }
+        if (queue_family_properties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
+            indices->graphics = i;
+        }
+
+        result = vkGetPhysicalDeviceSurfaceSupportKHR(
+            physical_device, i, surface, &present_support);
+        if (result != VK_SUCCESS) {
+            fprintf(stderr,
+                    "vkGetPhysicalDeviceSurfaceSupportKHR failed with %d\n",
+                    result);
+            goto cleanup;
+        }
+        if (present_support == VK_TRUE) {
+            indices->present = i;
+        }
+        if (indices->graphics >= 0 && indices->present >= 0) {
+            break;
+        }
+    }
+    ret = true;
+cleanup:
+    free(queue_family_properties);
+    return ret;
+}
+
+bool query_swap_chain_support(
+    VkPhysicalDevice device, VkSurfaceKHR surface,
+    struct swap_chain_support_details *swap_chain_support) {
+    VkResult result;
+
+    result = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
+        device, surface, &swap_chain_support->capabilities);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr,
+                "vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed: %d\n",
+                result);
+        return false;
+    }
+
+    result = vkGetPhysicalDeviceSurfaceFormatsKHR(
+        device, surface, &swap_chain_support->formats_len, NULL);
+
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: %d\n",
+                result);
+        return false;
+    }
+
+    if (swap_chain_support->formats_len != 0) {
+        swap_chain_support->formats = malloc(swap_chain_support->formats_len *
+                                             sizeof(VkSurfaceFormatKHR));
+        result = vkGetPhysicalDeviceSurfaceFormatsKHR(
+            device, surface, &swap_chain_support->formats_len,
+            swap_chain_support->formats);
+
+        if (result != VK_SUCCESS) {
+            fprintf(stderr, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: %d\n",
+                    result);
+            return false;
+        }
+    }
+
+    result = vkGetPhysicalDeviceSurfacePresentModesKHR(
+        device, surface, &swap_chain_support->present_modes_len, NULL);
+
+    if (result != VK_SUCCESS) {
+        fprintf(stderr,
+                "vkGetPhysicalDeviceSurfacePresentModesKHR failed: %d\n",
+                result);
+        return false;
+    }
+
+    if (swap_chain_support->present_modes_len != 0) {
+        swap_chain_support->present_modes = malloc(
+            swap_chain_support->present_modes_len * sizeof(VkPresentModeKHR));
+        result = vkGetPhysicalDeviceSurfacePresentModesKHR(
+            device, surface, &swap_chain_support->present_modes_len,
+            swap_chain_support->present_modes);
+
+        if (result != VK_SUCCESS) {
+            fprintf(stderr,
+                    "vkGetPhysicalDeviceSurfacePresentModesKHR failed: %d\n",
+                    result);
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool is_suitable_physical_device(VkPhysicalDevice physical_device,
+                                 VkSurfaceKHR surface,
+                                 struct queue_family_indices *indices) {
+    VkResult result;
+    uint32_t device_extension_count;
+    uint32_t i;
+    VkExtensionProperties *device_extensions;
+    bool ret = false;
+    struct swap_chain_support_details swap_chain_support;
+    int found_khr_surface = 0;
+
+    VkPhysicalDeviceProperties device_properties;
+    vkGetPhysicalDeviceProperties(physical_device, &device_properties);
+
+    printf("Probing physical device %s\n", device_properties.deviceName);
+
+    result = vkEnumerateDeviceExtensionProperties(
+        physical_device, NULL, &device_extension_count, NULL);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkEnumerateDeviceExtensionProperties failed: %d\n",
+                result);
+        return false;
+    }
+
+    device_extensions =
+        malloc(device_extension_count * sizeof(VkExtensionProperties));
+
+    result = vkEnumerateDeviceExtensionProperties(
+        physical_device, NULL, &device_extension_count, device_extensions);
+
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkEnumerateDeviceExtensionProperties failed: %d\n",
+                result);
+        goto cleanup;
+    }
+
+    printf("  Supported device extensions:\n");
+
+    for (i = 0; i < device_extension_count; i++) {
+        printf("    %s\n", device_extensions[i].extensionName);
+        if (strcmp(VK_KHR_SWAPCHAIN_EXTENSION_NAME,
+                   device_extensions[i].extensionName) == 0) {
+            found_khr_surface = 1;
+            break;
+        }
+    }
+    if (!found_khr_surface) {
+        printf("  Device doesnt support %s\n", VK_KHR_SWAPCHAIN_EXTENSION_NAME);
+        goto cleanup;
+    }
+    if (!find_queue_families(physical_device, surface, indices)) {
+        goto cleanup;
+    }
+    if (indices->graphics < 0 || indices->present < 0) {
+        printf("  Device is missing graphics and/or present support. graphics: "
+               "%d, present: %d\n",
+               indices->graphics, indices->present);
+        goto cleanup;
+    }
+
+    if (!query_swap_chain_support(physical_device, surface,
+                                  &swap_chain_support)) {
+        goto cleanup;
+    }
+
+    if (swap_chain_support.formats_len == 0) {
+        printf(" Device doesn't support any swap chain formats\n");
+        goto cleanup;
+    }
+
+    if (swap_chain_support.present_modes_len == 0) {
+        printf(" Device doesn't support any swap chain present modes\n");
+        goto cleanup;
+    }
+    ret = true;
+
+cleanup:
+    free(device_extensions);
+    swap_chain_support_details_free(&swap_chain_support);
+
+    return ret;
+}
+
+bool create_physical_device(struct vulkan_demo *demo) {
+    uint32_t device_count = 0;
+    VkPhysicalDevice *physical_devices;
+    VkResult result;
+    uint32_t i;
+    bool ret = false;
+
+    result = vkEnumeratePhysicalDevices(demo->instance, &device_count, NULL);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkEnumeratePhysicalDevices failed: %d\n", result);
+        return ret;
+    }
+    if (device_count == 0) {
+        fprintf(stderr, "no vulkan capable GPU found!");
+        return ret;
+    }
+
+    physical_devices = malloc(device_count * sizeof(VkPhysicalDevice));
+    result = vkEnumeratePhysicalDevices(demo->instance, &device_count,
+                                        physical_devices);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkEnumeratePhysicalDevices failed: %d\n", result);
+        goto cleanup;
+    }
+
+    for (i = 0; i < device_count; i++) {
+        struct queue_family_indices indices = {-1, -1};
+        if (is_suitable_physical_device(physical_devices[i], demo->surface,
+                                        &indices)) {
+            printf("  Selecting this device for rendering. Queue families: "
+                   "graphics: %d, present: %d!\n",
+                   indices.graphics, indices.present);
+            demo->physical_device = physical_devices[i];
+            demo->indices = indices;
+            break;
+        }
+    }
+    if (demo->physical_device == NULL) {
+        fprintf(stderr, "failed to find a suitable GPU!\n");
+    } else {
+        ret = true;
+    }
+cleanup:
+    free(physical_devices);
+    return ret;
+}
+
+bool create_logical_device(struct vulkan_demo *demo) {
+    VkResult result;
+    bool ret = false;
+    float queuePriority = 1.0f;
+    uint32_t num_queues = 1;
+    VkDeviceQueueCreateInfo *queue_create_infos;
+    VkDeviceCreateInfo create_info;
+    const char *swap_chain_extension_name = VK_KHR_SWAPCHAIN_EXTENSION_NAME;
+
+    queue_create_infos = calloc(2, sizeof(VkDeviceQueueCreateInfo));
+    queue_create_infos[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+    queue_create_infos[0].queueFamilyIndex = demo->indices.graphics;
+    queue_create_infos[0].queueCount = 1;
+    queue_create_infos[0].pQueuePriorities = &queuePriority;
+
+    if (demo->indices.present != demo->indices.graphics) {
+        queue_create_infos[1].sType =
+            VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+        queue_create_infos[1].queueFamilyIndex = demo->indices.present;
+        queue_create_infos[1].queueCount = 1;
+        queue_create_infos[1].pQueuePriorities = &queuePriority;
+        num_queues = 2;
+    }
+
+    memset(&create_info, 0, sizeof(VkDeviceCreateInfo));
+    create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
+    create_info.queueCreateInfoCount = num_queues;
+    create_info.pQueueCreateInfos = queue_create_infos;
+    create_info.enabledExtensionCount = 1;
+    create_info.ppEnabledExtensionNames = &swap_chain_extension_name;
+
+    result = vkCreateDevice(demo->physical_device, &create_info, NULL,
+                            &demo->device);
+
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkCreateDevice failed: %d\n", result);
+        goto cleanup;
+    }
+
+    vkGetDeviceQueue(demo->device, demo->indices.graphics, 0,
+                     &demo->graphics_queue);
+    vkGetDeviceQueue(demo->device, demo->indices.present, 0,
+                     &demo->present_queue);
+    ret = true;
+cleanup:
+    free(queue_create_infos);
+    return ret;
+}
+
+bool create_sampler(struct vulkan_demo *demo) {
+    VkResult result;
+    VkSamplerCreateInfo sampler_info;
+
+    memset(&sampler_info, 0, sizeof(VkSamplerCreateInfo));
+    sampler_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
+    sampler_info.pNext = NULL;
+    sampler_info.maxAnisotropy = 1.0;
+    sampler_info.magFilter = VK_FILTER_LINEAR;
+    sampler_info.minFilter = VK_FILTER_LINEAR;
+    sampler_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+    sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+    sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+    sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+    sampler_info.mipLodBias = 0.0f;
+    sampler_info.compareEnable = VK_FALSE;
+    sampler_info.compareOp = VK_COMPARE_OP_ALWAYS;
+    sampler_info.minLod = 0.0f;
+    sampler_info.maxLod = 0.0f;
+    sampler_info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK;
+
+    result = vkCreateSampler(demo->device, &sampler_info, NULL, &demo->sampler);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkCreateSampler failed: %d\n", result);
+        return false;
+    }
+    return true;
+}
+
+VkSurfaceFormatKHR
+choose_swap_surface_format(VkSurfaceFormatKHR *available_formats,
+                           uint32_t available_formats_len) {
+    VkSurfaceFormatKHR undefined_format = {VK_FORMAT_B8G8R8A8_UNORM,
+                                           VK_COLOR_SPACE_SRGB_NONLINEAR_KHR};
+    uint32_t i;
+    if (available_formats_len == 1 &&
+        available_formats[0].format == VK_FORMAT_UNDEFINED) {
+        return undefined_format;
+    }
+
+    for (i = 0; i < available_formats_len; i++) {
+        if (available_formats[i].format == VK_FORMAT_B8G8R8A8_UNORM &&
+            available_formats[i].colorSpace ==
+                VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
+            return available_formats[i];
+        }
+    }
+
+    return available_formats[0];
+}
+
+VkPresentModeKHR
+choose_swap_present_mode(VkPresentModeKHR *available_present_modes,
+                         uint32_t available_present_modes_len) {
+    uint32_t i;
+    for (i = 0; i < available_present_modes_len; i++) {
+        /*
+        best mode to ensure good input latency while ensuring we are not
+        producing tearing
+        */
+        if (available_present_modes[i] == VK_PRESENT_MODE_MAILBOX_KHR) {
+            return available_present_modes[i];
+        }
+    }
+
+    /* must be supported */
+    return VK_PRESENT_MODE_FIFO_KHR;
+}
+
+VkExtent2D choose_swap_extent(struct vulkan_demo *demo,
+                              VkSurfaceCapabilitiesKHR *capabilities) {
+    int width, height;
+    VkExtent2D actual_extent;
+    if (capabilities->currentExtent.width != 0xFFFFFFFF) {
+        return capabilities->currentExtent;
+    } else {
+        /* not window size! */
+        glfwGetFramebufferSize(demo->win, &width, &height);
+
+        actual_extent.width = (uint32_t)width;
+        actual_extent.height = (uint32_t)height;
+
+        actual_extent.width = NK_MAX(
+            capabilities->minImageExtent.width,
+            NK_MIN(capabilities->maxImageExtent.width, actual_extent.width));
+        actual_extent.height = NK_MAX(
+            capabilities->minImageExtent.height,
+            NK_MIN(capabilities->maxImageExtent.height, actual_extent.height));
+
+        return actual_extent;
+    }
+}
+
+bool create_swap_chain(struct vulkan_demo *demo) {
+    struct swap_chain_support_details swap_chain_support;
+    VkSurfaceFormatKHR surface_format;
+    VkPresentModeKHR present_mode;
+    VkExtent2D extent;
+    VkResult result;
+    VkSwapchainCreateInfoKHR create_info;
+    uint32_t queue_family_indices[2];
+    uint32_t old_swap_chain_images_len;
+    bool ret = false;
+
+    queue_family_indices[0] = (uint32_t)demo->indices.graphics;
+    queue_family_indices[1] = (uint32_t)demo->indices.present;
+
+    if (!query_swap_chain_support(demo->physical_device, demo->surface,
+                                  &swap_chain_support)) {
+        goto cleanup;
+    }
+    surface_format = choose_swap_surface_format(swap_chain_support.formats,
+                                                swap_chain_support.formats_len);
+    present_mode = choose_swap_present_mode(
+        swap_chain_support.present_modes, swap_chain_support.present_modes_len);
+    extent = choose_swap_extent(demo, &swap_chain_support.capabilities);
+
+    demo->swap_chain_images_len =
+        swap_chain_support.capabilities.minImageCount + 1;
+    if (swap_chain_support.capabilities.maxImageCount > 0 &&
+        demo->swap_chain_images_len >
+            swap_chain_support.capabilities.maxImageCount) {
+        demo->swap_chain_images_len =
+            swap_chain_support.capabilities.maxImageCount;
+    }
+
+    memset(&create_info, 0, sizeof(VkSwapchainCreateInfoKHR));
+    create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
+    create_info.surface = demo->surface;
+
+    create_info.minImageCount = demo->swap_chain_images_len;
+    create_info.imageFormat = surface_format.format;
+    create_info.imageColorSpace = surface_format.colorSpace;
+    create_info.imageExtent = extent;
+    create_info.imageArrayLayers = 1;
+    create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+
+    if (demo->indices.graphics != demo->indices.present) {
+        create_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
+        create_info.queueFamilyIndexCount = 2;
+        create_info.pQueueFamilyIndices = queue_family_indices;
+    } else {
+        create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
+    }
+
+    create_info.preTransform = swap_chain_support.capabilities.currentTransform;
+    create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
+    create_info.presentMode = present_mode;
+    create_info.clipped = VK_TRUE;
+
+    result = vkCreateSwapchainKHR(demo->device, &create_info, NULL,
+                                  &demo->swap_chain);
+
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkCreateSwapchainKHR failed: %d\n", result);
+        goto cleanup;
+    }
+
+    old_swap_chain_images_len = demo->swap_chain_images_len;
+    result = vkGetSwapchainImagesKHR(demo->device, demo->swap_chain,
+                                     &demo->swap_chain_images_len, NULL);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkGetSwapchainImagesKHR failed: %d\n", result);
+        goto cleanup;
+    }
+    if (old_swap_chain_images_len > 0 &&
+        old_swap_chain_images_len != demo->swap_chain_images_len) {
+        fprintf(stderr,
+                "number of assigned swap chain images changed between "
+                "runs. old: %u, new: %u\n",
+                (unsigned)old_swap_chain_images_len,
+                (unsigned)demo->swap_chain_images_len);
+        goto cleanup;
+    }
+    if (demo->swap_chain_images == NULL) {
+        demo->swap_chain_images =
+            malloc(demo->swap_chain_images_len * sizeof(VkImage));
+    }
+    result = vkGetSwapchainImagesKHR(demo->device, demo->swap_chain,
+                                     &demo->swap_chain_images_len,
+                                     demo->swap_chain_images);
+
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkGetSwapchainImagesKHR failed: %d\n", result);
+        return false;
+    }
+
+    demo->swap_chain_image_format = surface_format.format;
+    demo->swap_chain_image_extent = extent;
+
+    ret = true;
+cleanup:
+    swap_chain_support_details_free(&swap_chain_support);
+
+    return ret;
+}
+
+bool create_swap_chain_image_views(struct vulkan_demo *demo) {
+    uint32_t i;
+    VkResult result;
+    VkImageViewCreateInfo create_info;
+
+    memset(&create_info, 0, sizeof(VkImageViewCreateInfo));
+    create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+    create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
+    create_info.format = demo->swap_chain_image_format;
+    create_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
+    create_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
+    create_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
+    create_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
+    create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    create_info.subresourceRange.baseMipLevel = 0;
+    create_info.subresourceRange.levelCount = 1;
+    create_info.subresourceRange.baseArrayLayer = 0;
+    create_info.subresourceRange.layerCount = 1;
+
+    if (!demo->swap_chain_image_views) {
+        demo->swap_chain_image_views =
+            malloc(demo->swap_chain_images_len * sizeof(VkImageView));
+    }
+
+    for (i = 0; i < demo->swap_chain_images_len; i++) {
+        create_info.image = demo->swap_chain_images[i];
+        result = vkCreateImageView(demo->device, &create_info, NULL,
+                                   &demo->swap_chain_image_views[i]);
+
+        if (result != VK_SUCCESS) {
+            fprintf(stderr, "vkCreateImageView failed: %d\n", result);
+            return false;
+        }
+    }
+    return true;
+}
+
+bool create_overlay_images(struct vulkan_demo *demo) {
+    uint32_t i, j;
+    VkResult result;
+    VkMemoryRequirements mem_requirements;
+    VkPhysicalDeviceMemoryProperties mem_properties;
+    int found;
+    VkImageCreateInfo image_info;
+    VkMemoryAllocateInfo alloc_info;
+    VkImageViewCreateInfo image_view_info;
+
+    memset(&image_info, 0, sizeof(VkImageCreateInfo));
+    image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+    image_info.imageType = VK_IMAGE_TYPE_2D;
+    image_info.extent.width = demo->swap_chain_image_extent.width;
+    image_info.extent.height = demo->swap_chain_image_extent.height;
+    image_info.extent.depth = 1;
+    image_info.mipLevels = 1;
+    image_info.arrayLayers = 1;
+    image_info.format = demo->swap_chain_image_format;
+    image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
+    image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+    image_info.usage =
+        VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+    image_info.samples = VK_SAMPLE_COUNT_1_BIT;
+    image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+    memset(&alloc_info, 0, sizeof(VkMemoryAllocateInfo));
+    alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+
+    memset(&image_view_info, 0, sizeof(VkImageViewCreateInfo));
+    image_view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+    image_view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
+    image_view_info.format = demo->swap_chain_image_format;
+    image_view_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
+    image_view_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
+    image_view_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
+    image_view_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
+    image_view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    image_view_info.subresourceRange.baseMipLevel = 0;
+    image_view_info.subresourceRange.levelCount = 1;
+    image_view_info.subresourceRange.baseArrayLayer = 0;
+    image_view_info.subresourceRange.layerCount = 1;
+
+    if (!demo->overlay_images) {
+        demo->overlay_images =
+            malloc(demo->swap_chain_images_len * sizeof(VkImage));
+    }
+
+    if (!demo->overlay_image_memories) {
+        demo->overlay_image_memories =
+            malloc(demo->swap_chain_images_len * sizeof(VkDeviceMemory));
+    }
+    if (!demo->overlay_image_views) {
+        demo->overlay_image_views =
+            malloc(demo->swap_chain_images_len * sizeof(VkImageView));
+    }
+
+    for (i = 0; i < demo->swap_chain_images_len; i++) {
+        result = vkCreateImage(demo->device, &image_info, NULL,
+                               &demo->overlay_images[i]);
+
+        if (result != VK_SUCCESS) {
+            fprintf(stderr, "vkCreateImage failed for index %lu: %d\n",
+                    (unsigned long)i, result);
+            return false;
+        }
+
+        vkGetImageMemoryRequirements(demo->device, demo->overlay_images[i],
+                                     &mem_requirements);
+
+        alloc_info.allocationSize = mem_requirements.size;
+
+        vkGetPhysicalDeviceMemoryProperties(demo->physical_device,
+                                            &mem_properties);
+        found = 0;
+        for (j = 0; j < mem_properties.memoryTypeCount; j++) {
+            if ((mem_requirements.memoryTypeBits & (1 << j)) &&
+                (mem_properties.memoryTypes[j].propertyFlags &
+                 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) ==
+                    VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) {
+                found = 1;
+                break;
+            }
+        }
+        if (!found) {
+            fprintf(stderr,
+                    "failed to find suitable memory type for index %lu!\n",
+                    (unsigned long)i);
+            return false;
+        }
+        alloc_info.memoryTypeIndex = j;
+        result = vkAllocateMemory(demo->device, &alloc_info, NULL,
+                                  &demo->overlay_image_memories[i]);
+        if (result != VK_SUCCESS) {
+            fprintf(stderr,
+                    "failed to allocate vulkan memory for index %lu: %d!\n",
+                    (unsigned long)i, result);
+            return false;
+        }
+        result = vkBindImageMemory(demo->device, demo->overlay_images[i],
+                                   demo->overlay_image_memories[i], 0);
+        if (result != VK_SUCCESS) {
+            fprintf(stderr, "Couldn't bind image memory for index %lu: %d\n",
+                    (unsigned long)i, result);
+            return false;
+        }
+
+        image_view_info.image = demo->overlay_images[i];
+        result = vkCreateImageView(demo->device, &image_view_info, NULL,
+                                   &demo->overlay_image_views[i]);
+
+        if (result != VK_SUCCESS) {
+            fprintf(stderr, "vkCreateImageView failed for index %lu: %d\n",
+                    (unsigned long)i, result);
+            return false;
+        }
+    }
+    return true;
+}
+
+bool create_render_pass(struct vulkan_demo *demo) {
+    VkAttachmentDescription attachment;
+    VkAttachmentReference color_attachment_ref;
+    VkSubpassDescription subpass;
+    VkSubpassDependency dependency;
+    VkRenderPassCreateInfo render_pass_info;
+    VkResult result;
+
+    memset(&attachment, 0, sizeof(VkAttachmentDescription));
+    attachment.format = demo->swap_chain_image_format;
+    attachment.samples = VK_SAMPLE_COUNT_1_BIT;
+    attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+    attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+    attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+    attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+    attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+    attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+
+    memset(&color_attachment_ref, 0, sizeof(VkAttachmentReference));
+    color_attachment_ref.attachment = 0;
+    color_attachment_ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+    memset(&subpass, 0, sizeof(VkSubpassDescription));
+    subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+    subpass.colorAttachmentCount = 1;
+    subpass.pColorAttachments = &color_attachment_ref;
+
+    memset(&dependency, 0, sizeof(VkSubpassDependency));
+    dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
+    dependency.dstSubpass = 0;
+    dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+    dependency.srcAccessMask = 0;
+    dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+    dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
+                               VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+
+    memset(&render_pass_info, 0, sizeof(VkRenderPassCreateInfo));
+    render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+    render_pass_info.attachmentCount = 1;
+    render_pass_info.pAttachments = &attachment;
+    render_pass_info.subpassCount = 1;
+    render_pass_info.pSubpasses = &subpass;
+    render_pass_info.dependencyCount = 1;
+    render_pass_info.pDependencies = &dependency;
+
+    result = vkCreateRenderPass(demo->device, &render_pass_info, NULL,
+                                &demo->render_pass);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkCreateRenderPass failed: %d\n", result);
+        return false;
+    }
+    return true;
+}
+
+bool create_framebuffers(struct vulkan_demo *demo) {
+    uint32_t i;
+    VkResult result;
+    VkFramebufferCreateInfo framebuffer_info;
+
+    if (!demo->framebuffers) {
+        demo->framebuffers =
+            malloc(demo->swap_chain_images_len * sizeof(VkFramebuffer));
+    }
+
+    memset(&framebuffer_info, 0, sizeof(VkFramebufferCreateInfo));
+    framebuffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+    framebuffer_info.renderPass = demo->render_pass;
+    framebuffer_info.attachmentCount = 1;
+    framebuffer_info.width = demo->swap_chain_image_extent.width;
+    framebuffer_info.height = demo->swap_chain_image_extent.height;
+    framebuffer_info.layers = 1;
+
+    for (i = 0; i < demo->swap_chain_images_len; i++) {
+        framebuffer_info.pAttachments = &demo->swap_chain_image_views[i];
+
+        result = vkCreateFramebuffer(demo->device, &framebuffer_info, NULL,
+                                     &demo->framebuffers[i]);
+        if (result != VK_SUCCESS) {
+            fprintf(stderr, "vkCreateFramebuffer failed from index %lu: %d\n",
+                    (unsigned long)i, result);
+            return false;
+        }
+    }
+    return true;
+}
+
+bool create_descriptor_set_layout(struct vulkan_demo *demo) {
+    VkDescriptorSetLayoutBinding overlay_layout_binding;
+    VkDescriptorSetLayoutCreateInfo descriptor_set_layout_create_nfo;
+    VkResult result;
+
+    memset(&overlay_layout_binding, 0, sizeof(VkDescriptorSetLayoutBinding));
+    overlay_layout_binding.binding = 0;
+    overlay_layout_binding.descriptorCount = 1;
+    overlay_layout_binding.descriptorType =
+        VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+    overlay_layout_binding.pImmutableSamplers = NULL;
+    overlay_layout_binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+
+    memset(&descriptor_set_layout_create_nfo, 0,
+           sizeof(VkDescriptorSetLayoutCreateInfo));
+    descriptor_set_layout_create_nfo.sType =
+        VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+    descriptor_set_layout_create_nfo.bindingCount = 1;
+    descriptor_set_layout_create_nfo.pBindings = &overlay_layout_binding;
+
+    result = vkCreateDescriptorSetLayout(demo->device,
+                                         &descriptor_set_layout_create_nfo,
+                                         NULL, &demo->descriptor_set_layout);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkCreateDescriptorSetLayout failed: %d\n", result);
+        return false;
+    }
+    return true;
+}
+
+bool create_descriptor_pool(struct vulkan_demo *demo) {
+    VkDescriptorPoolSize pool_size;
+    VkDescriptorPoolCreateInfo pool_info;
+    VkResult result;
+
+    memset(&pool_size, 0, sizeof(VkDescriptorPoolSize));
+    pool_size.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+    pool_size.descriptorCount = demo->swap_chain_images_len;
+
+    memset(&pool_info, 0, sizeof(VkDescriptorPoolCreateInfo));
+    pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
+    pool_info.poolSizeCount = 1;
+    pool_info.pPoolSizes = &pool_size;
+    pool_info.maxSets = demo->swap_chain_images_len;
+    result = vkCreateDescriptorPool(demo->device, &pool_info, NULL,
+                                    &demo->descriptor_pool);
+
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkCreateDescriptorPool failed: %d\n", result);
+        return false;
+    }
+    return true;
+}
+
+void update_descriptor_sets(struct vulkan_demo *demo) {
+    uint32_t i;
+    VkDescriptorImageInfo descriptor_image_info;
+    VkWriteDescriptorSet descriptor_write;
+
+    memset(&descriptor_image_info, 0, sizeof(VkDescriptorImageInfo));
+    descriptor_image_info.imageLayout =
+        VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+    descriptor_image_info.sampler = demo->sampler;
+
+    memset(&descriptor_write, 0, sizeof(VkWriteDescriptorSet));
+    descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+    descriptor_write.dstBinding = 0;
+    descriptor_write.dstArrayElement = 0;
+    descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+    descriptor_write.descriptorCount = 1;
+    descriptor_write.pImageInfo = &descriptor_image_info;
+
+    for (i = 0; i < demo->swap_chain_images_len; i++) {
+        descriptor_write.dstSet = demo->descriptor_sets[i];
+        descriptor_image_info.imageView = demo->overlay_image_views[i];
+
+        vkUpdateDescriptorSets(demo->device, 1, &descriptor_write, 0, NULL);
+    }
+}
+
+bool create_descriptor_sets(struct vulkan_demo *demo) {
+    bool ret = false;
+    VkDescriptorSetLayout *descriptor_set_layouts;
+    VkDescriptorSetAllocateInfo alloc_info;
+    uint32_t i;
+    VkResult result;
+
+    demo->descriptor_sets =
+        malloc(demo->swap_chain_images_len * sizeof(VkDescriptorSet));
+    descriptor_set_layouts =
+        malloc(demo->swap_chain_images_len * sizeof(VkDescriptorSetLayout));
+
+    for (i = 0; i < demo->swap_chain_images_len; i++) {
+        descriptor_set_layouts[i] = demo->descriptor_set_layout;
+    }
+
+    memset(&alloc_info, 0, sizeof(VkDescriptorSetAllocateInfo));
+    alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+    alloc_info.descriptorPool = demo->descriptor_pool;
+    alloc_info.descriptorSetCount = demo->swap_chain_images_len;
+    alloc_info.pSetLayouts = descriptor_set_layouts;
+    result = vkAllocateDescriptorSets(demo->device, &alloc_info,
+                                      demo->descriptor_sets);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkAllocateDescriptorSets failed: %d\n", result);
+        goto cleanup;
+    }
+
+    update_descriptor_sets(demo);
+
+    ret = true;
+cleanup:
+    free(descriptor_set_layouts);
+
+    return ret;
+}
+
+bool create_shader_module(VkDevice device, char *shader_buffer,
+                          size_t shader_buffer_len,
+                          VkShaderModule *shader_module) {
+    VkShaderModuleCreateInfo create_info;
+    VkResult result;
+
+    memset(&create_info, 0, sizeof(VkShaderModuleCreateInfo));
+    create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+    create_info.codeSize = shader_buffer_len;
+    create_info.pCode = (const uint32_t *)shader_buffer;
+
+    result = vkCreateShaderModule(device, &create_info, NULL, shader_module);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkCreateShaderModule failed: %d\n", result);
+        return false;
+    }
+
+    return true;
+}
+
+bool create_graphics_pipeline(struct vulkan_demo *demo) {
+    bool ret = false;
+    char *vert_shader_code = NULL;
+    char *frag_shader_code = NULL;
+    VkShaderModule vert_shader_module;
+    VkShaderModule frag_shader_module;
+    FILE *fp;
+    size_t file_len;
+    VkPipelineShaderStageCreateInfo vert_shader_stage_info;
+    VkPipelineShaderStageCreateInfo frag_shader_stage_info;
+    VkPipelineShaderStageCreateInfo shader_stages[2];
+    VkPipelineVertexInputStateCreateInfo vertex_input_info;
+    VkPipelineInputAssemblyStateCreateInfo input_assembly;
+    VkViewport viewport;
+    VkRect2D scissor;
+    VkPipelineViewportStateCreateInfo viewport_state;
+    VkPipelineRasterizationStateCreateInfo rasterizer;
+    VkPipelineMultisampleStateCreateInfo multisampling;
+    VkPipelineColorBlendAttachmentState color_blend_attachment;
+    VkPipelineColorBlendStateCreateInfo color_blending;
+    VkPipelineLayoutCreateInfo pipeline_layout_info;
+    VkResult result;
+    VkGraphicsPipelineCreateInfo pipeline_info;
+
+    fp = fopen("shaders/demo.vert.spv", "r");
+    if (!fp) {
+        fprintf(stderr, "Couldn't open shaders/demo.vert.spv\n");
+        return false;
+    }
+    fseek(fp, 0, SEEK_END);
+    file_len = ftell(fp);
+    vert_shader_code = malloc(file_len);
+    fseek(fp, 0, 0);
+    fread(vert_shader_code, 1, file_len, fp);
+    fclose(fp);
+
+    if (!create_shader_module(demo->device, vert_shader_code, file_len,
+                              &vert_shader_module)) {
+        goto cleanup;
+    }
+
+    fp = fopen("shaders/demo.frag.spv", "r");
+    if (!fp) {
+        fprintf(stderr, "Couldn't open shaders/demo.frag.spv\n");
+        return false;
+    }
+    fseek(fp, 0, SEEK_END);
+    file_len = ftell(fp);
+    frag_shader_code = malloc(file_len);
+    fseek(fp, 0, 0);
+    fread(frag_shader_code, 1, file_len, fp);
+    fclose(fp);
+
+    if (!create_shader_module(demo->device, frag_shader_code, file_len,
+                              &frag_shader_module)) {
+        goto cleanup;
+    }
+
+    memset(&vert_shader_stage_info, 0, sizeof(VkPipelineShaderStageCreateInfo));
+    vert_shader_stage_info.sType =
+        VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+    vert_shader_stage_info.stage = VK_SHADER_STAGE_VERTEX_BIT;
+    vert_shader_stage_info.module = vert_shader_module;
+    vert_shader_stage_info.pName = "main";
+
+    memset(&frag_shader_stage_info, 0, sizeof(VkPipelineShaderStageCreateInfo));
+    frag_shader_stage_info.sType =
+        VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+    frag_shader_stage_info.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
+    frag_shader_stage_info.module = frag_shader_module;
+    frag_shader_stage_info.pName = "main";
+
+    shader_stages[0] = vert_shader_stage_info;
+    shader_stages[1] = frag_shader_stage_info;
+
+    memset(&vertex_input_info, 0, sizeof(VkPipelineVertexInputStateCreateInfo));
+    vertex_input_info.sType =
+        VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
+
+    memset(&input_assembly, 0, sizeof(VkPipelineInputAssemblyStateCreateInfo));
+    input_assembly.sType =
+        VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
+    input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
+    input_assembly.primitiveRestartEnable = VK_FALSE;
+
+    memset(&viewport, 0, sizeof(VkViewport));
+    viewport.x = 0.0f;
+    viewport.y = 0.0f;
+    viewport.width = (float)demo->swap_chain_image_extent.width;
+    viewport.height = (float)demo->swap_chain_image_extent.height;
+    viewport.minDepth = 0.0f;
+    viewport.maxDepth = 1.0f;
+
+    memset(&scissor, 0, sizeof(VkRect2D));
+    scissor.extent.width = demo->swap_chain_image_extent.width;
+    scissor.extent.height = demo->swap_chain_image_extent.height;
+
+    memset(&viewport_state, 0, sizeof(VkPipelineViewportStateCreateInfo));
+    viewport_state.sType =
+        VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
+    viewport_state.viewportCount = 1;
+    viewport_state.pViewports = &viewport;
+    viewport_state.scissorCount = 1;
+    viewport_state.pScissors = &scissor;
+
+    memset(&rasterizer, 0, sizeof(VkPipelineRasterizationStateCreateInfo));
+    rasterizer.sType =
+        VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
+    rasterizer.depthClampEnable = VK_FALSE;
+    rasterizer.rasterizerDiscardEnable = VK_FALSE;
+    rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
+    rasterizer.lineWidth = 1.0f;
+    rasterizer.cullMode = VK_CULL_MODE_FRONT_BIT;
+    rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
+    rasterizer.depthBiasEnable = VK_FALSE;
+
+    memset(&multisampling, 0, sizeof(VkPipelineMultisampleStateCreateInfo));
+    multisampling.sType =
+        VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
+    multisampling.sampleShadingEnable = VK_FALSE;
+    multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
+
+    memset(&color_blend_attachment, 0,
+           sizeof(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;
+    color_blend_attachment.blendEnable = VK_TRUE;
+    color_blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
+    color_blend_attachment.dstColorBlendFactor =
+        VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+    color_blend_attachment.colorBlendOp = VK_BLEND_OP_ADD;
+    color_blend_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
+    color_blend_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
+    color_blend_attachment.alphaBlendOp = VK_BLEND_OP_ADD;
+
+    memset(&color_blending, 0, sizeof(VkPipelineColorBlendStateCreateInfo));
+    color_blending.sType =
+        VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
+    color_blending.logicOpEnable = VK_FALSE;
+    color_blending.logicOp = VK_LOGIC_OP_COPY;
+    color_blending.attachmentCount = 1;
+    color_blending.pAttachments = &color_blend_attachment;
+    color_blending.blendConstants[0] = 1.0f;
+    color_blending.blendConstants[1] = 1.0f;
+    color_blending.blendConstants[2] = 1.0f;
+    color_blending.blendConstants[3] = 1.0f;
+
+    memset(&pipeline_layout_info, 0, sizeof(VkPipelineLayoutCreateInfo));
+    pipeline_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+    pipeline_layout_info.setLayoutCount = 0;
+    pipeline_layout_info.pushConstantRangeCount = 0;
+    pipeline_layout_info.setLayoutCount = 1;
+    pipeline_layout_info.pSetLayouts = &demo->descriptor_set_layout;
+
+    result = vkCreatePipelineLayout(demo->device, &pipeline_layout_info, NULL,
+                                    &demo->pipeline_layout);
+
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkCreatePipelineLayout failed: %d\n", result);
+        goto cleanup;
+    }
+
+    memset(&pipeline_info, 0, sizeof(VkGraphicsPipelineCreateInfo));
+    pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
+    pipeline_info.stageCount = 2;
+    pipeline_info.pStages = shader_stages;
+    pipeline_info.pVertexInputState = &vertex_input_info;
+    pipeline_info.pInputAssemblyState = &input_assembly;
+    pipeline_info.pViewportState = &viewport_state;
+    pipeline_info.pRasterizationState = &rasterizer;
+    pipeline_info.pMultisampleState = &multisampling;
+    pipeline_info.pColorBlendState = &color_blending;
+    pipeline_info.layout = demo->pipeline_layout;
+    pipeline_info.renderPass = demo->render_pass;
+    pipeline_info.basePipelineHandle = NULL;
+
+    result = vkCreateGraphicsPipelines(demo->device, NULL, 1, &pipeline_info,
+                                       NULL, &demo->pipeline);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkCreateGraphicsPipelines failed: %d\n", result);
+        goto cleanup;
+    }
+    ret = true;
+cleanup:
+    if (frag_shader_module) {
+        vkDestroyShaderModule(demo->device, frag_shader_module, NULL);
+    }
+    if (frag_shader_code) {
+        free(frag_shader_code);
+    }
+    if (vert_shader_module) {
+        vkDestroyShaderModule(demo->device, vert_shader_module, NULL);
+    }
+    if (vert_shader_code) {
+        free(vert_shader_code);
+    }
+
+    return ret;
+}
+
+bool create_command_pool(struct vulkan_demo *demo) {
+    VkCommandPoolCreateInfo pool_info;
+    VkResult result;
+
+    memset(&pool_info, 0, sizeof(VkCommandPoolCreateInfo));
+    pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+    pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+    pool_info.queueFamilyIndex = demo->indices.graphics;
+
+    result = vkCreateCommandPool(demo->device, &pool_info, NULL,
+                                 &demo->command_pool);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkCreateCommandPool failed: %d\n", result);
+        return false;
+    }
+    return true;
+}
+
+bool create_command_buffers(struct vulkan_demo *demo) {
+    VkCommandBufferAllocateInfo alloc_info;
+    VkResult result;
+
+    demo->command_buffers =
+        malloc(demo->swap_chain_images_len * sizeof(VkCommandBuffer));
+
+    memset(&alloc_info, 0, sizeof(VkCommandBufferAllocateInfo));
+    alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+    alloc_info.commandPool = demo->command_pool;
+    alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+    alloc_info.commandBufferCount = demo->swap_chain_images_len;
+
+    result = vkAllocateCommandBuffers(demo->device, &alloc_info,
+                                      demo->command_buffers);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkAllocateCommandBuffers failed: %d\n", result);
+        return false;
+    }
+
+    return true;
+}
+
+bool create_semaphores(struct vulkan_demo *demo) {
+    VkSemaphoreCreateInfo semaphore_info;
+    VkResult result;
+
+    memset(&semaphore_info, 0, sizeof(VkSemaphoreCreateInfo));
+    semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+    result = vkCreateSemaphore(demo->device, &semaphore_info, NULL,
+                               &demo->image_available);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkCreateSemaphore failed: %d\n", result);
+        return false;
+    }
+    result = vkCreateSemaphore(demo->device, &semaphore_info, NULL,
+                               &demo->render_finished);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkCreateSemaphore failed: %d\n", result);
+        return false;
+    }
+    return true;
+}
+
+bool create_fence(struct vulkan_demo *demo) {
+    VkResult result;
+    VkFenceCreateInfo fence_create_info;
+
+    memset(&fence_create_info, 0, sizeof(VkFenceCreateInfo));
+    fence_create_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+    fence_create_info.flags = VK_FENCE_CREATE_SIGNALED_BIT;
+
+    result = vkCreateFence(demo->device, &fence_create_info, NULL,
+                           &demo->render_fence);
+
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkCreateFence failed: %d\n", result);
+        return false;
+    }
+    return true;
+}
+
+bool create_swap_chain_related_resources(struct vulkan_demo *demo) {
+    if (!create_swap_chain(demo)) {
+        return false;
+    }
+    if (!create_swap_chain_image_views(demo)) {
+        return false;
+    }
+    if (!create_overlay_images(demo)) {
+        return false;
+    }
+    if (!create_render_pass(demo)) {
+        return false;
+    }
+    if (!create_framebuffers(demo)) {
+        return false;
+    }
+    if (!create_graphics_pipeline(demo)) {
+        return false;
+    }
+    return true;
+}
+
+bool destroy_swap_chain_related_resources(struct vulkan_demo *demo) {
+    uint32_t i;
+    VkResult result;
+
+    result = vkQueueWaitIdle(demo->graphics_queue);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkQueueWaitIdle failed: %d\n", result);
+        return false;
+    }
+
+    for (i = 0; i < demo->swap_chain_images_len; i++) {
+        vkDestroyFramebuffer(demo->device, demo->framebuffers[i], NULL);
+        vkDestroyImageView(demo->device, demo->overlay_image_views[i], NULL);
+        vkDestroyImage(demo->device, demo->overlay_images[i], NULL);
+        vkFreeMemory(demo->device, demo->overlay_image_memories[i], NULL);
+        vkDestroyImageView(demo->device, demo->swap_chain_image_views[i], NULL);
+    }
+    vkDestroySwapchainKHR(demo->device, demo->swap_chain, NULL);
+    vkDestroyRenderPass(demo->device, demo->render_pass, NULL);
+    vkDestroyPipeline(demo->device, demo->pipeline, NULL);
+    vkDestroyPipelineLayout(demo->device, demo->pipeline_layout, NULL);
+    return true;
+}
+
+bool create_demo_texture(struct vulkan_demo *demo) {
+    VkResult result;
+    VkMemoryRequirements mem_requirements;
+    VkPhysicalDeviceMemoryProperties mem_properties;
+    int found;
+    uint32_t i;
+    VkImageCreateInfo image_info;
+    VkMemoryAllocateInfo alloc_info;
+    VkImageViewCreateInfo image_view_info;
+    VkBufferCreateInfo buffer_info;
+    struct {
+        VkDeviceMemory memory;
+        VkBuffer buffer;
+    } staging_buffer;
+    void *data;
+    VkCommandBuffer command_buffer;
+    VkCommandBufferBeginInfo begin_info;
+    VkImageMemoryBarrier image_transfer_dst_memory_barrier;
+    VkBufferImageCopy buffer_copy_region;
+    VkImageMemoryBarrier image_shader_memory_barrier;
+    VkFence fence;
+    VkFenceCreateInfo fence_create;
+    VkSubmitInfo submit_info;
+
+    memset(&image_info, 0, sizeof(VkImageCreateInfo));
+    image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+    image_info.imageType = VK_IMAGE_TYPE_2D;
+    image_info.extent.width = 2;
+    image_info.extent.height = 2;
+    image_info.extent.depth = 1;
+    image_info.mipLevels = 1;
+    image_info.arrayLayers = 1;
+    image_info.format = VK_FORMAT_R8_UNORM;
+    image_info.tiling = VK_IMAGE_TILING_LINEAR;
+    image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+    image_info.usage =
+        VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
+    image_info.samples = VK_SAMPLE_COUNT_1_BIT;
+    image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+    memset(&alloc_info, 0, sizeof(VkMemoryAllocateInfo));
+    alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+
+    memset(&image_view_info, 0, sizeof(VkImageViewCreateInfo));
+    image_view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+    image_view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
+    image_view_info.format = VK_FORMAT_R8_UNORM;
+    image_view_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
+    image_view_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
+    image_view_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
+    image_view_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
+    image_view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    image_view_info.subresourceRange.baseMipLevel = 0;
+    image_view_info.subresourceRange.levelCount = 1;
+    image_view_info.subresourceRange.baseArrayLayer = 0;
+    image_view_info.subresourceRange.layerCount = 1;
+
+    result = vkCreateImage(demo->device, &image_info, NULL,
+                           &demo->demo_texture_image);
+
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkCreateImage failed: %d\n", result);
+        return false;
+    }
+
+    vkGetImageMemoryRequirements(demo->device, demo->demo_texture_image,
+                                 &mem_requirements);
+
+    alloc_info.allocationSize = mem_requirements.size;
+
+    vkGetPhysicalDeviceMemoryProperties(demo->physical_device, &mem_properties);
+    found = 0;
+    for (i = 0; i < mem_properties.memoryTypeCount; i++) {
+        if ((mem_requirements.memoryTypeBits & (1 << i)) &&
+            (mem_properties.memoryTypes[i].propertyFlags &
+             VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) ==
+                VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) {
+            found = 1;
+            break;
+        }
+    }
+    if (!found) {
+        fprintf(stderr, "failed to find suitable memory for demo texture!\n");
+        return false;
+    }
+    alloc_info.memoryTypeIndex = i;
+    result = vkAllocateMemory(demo->device, &alloc_info, NULL,
+                              &demo->demo_texture_memory);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr,
+                "failed to allocate vulkan memory for demo texture: %d!\n",
+                result);
+        return false;
+    }
+    result = vkBindImageMemory(demo->device, demo->demo_texture_image,
+                               demo->demo_texture_memory, 0);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "Couldn't bind image memory for demo texture: %d\n",
+                result);
+        return false;
+    }
+
+    image_view_info.image = demo->demo_texture_image;
+    result = vkCreateImageView(demo->device, &image_view_info, NULL,
+                               &demo->demo_texture_image_view);
+
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkCreateImageView failed for demo texture: %d\n",
+                result);
+        return false;
+    }
+
+    memset(&buffer_info, 0, sizeof(VkBufferCreateInfo));
+    buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+    buffer_info.size = alloc_info.allocationSize;
+    buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+    buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+    result = vkCreateBuffer(demo->device, &buffer_info, NULL,
+                            &staging_buffer.buffer);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkCreateBuffer failed for demo texture: %d\n", result);
+        return false;
+    }
+    vkGetBufferMemoryRequirements(demo->device, staging_buffer.buffer,
+                                  &mem_requirements);
+
+    alloc_info.allocationSize = mem_requirements.size;
+    found = 0;
+    for (i = 0; i < mem_properties.memoryTypeCount; i++) {
+        if ((mem_requirements.memoryTypeBits & (1 << i)) &&
+            (mem_properties.memoryTypes[i].propertyFlags &
+             (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+              VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) ==
+                (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+                 VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) {
+            found = 1;
+            break;
+        }
+    }
+    if (!found) {
+        fprintf(stderr, "failed to find suitable staging buffer memory for "
+                        "demo texture!\n");
+        return false;
+    }
+    alloc_info.memoryTypeIndex = i;
+    result = vkAllocateMemory(demo->device, &alloc_info, NULL,
+                              &staging_buffer.memory);
+    if (!found) {
+        fprintf(stderr, "vkAllocateMemory failed for demo texture: %d\n",
+                result);
+        return false;
+    }
+    result = vkBindBufferMemory(demo->device, staging_buffer.buffer,
+                                staging_buffer.memory, 0);
+    if (!found) {
+        fprintf(stderr, "vkBindBufferMemory failed for demo texture: %d\n",
+                result);
+        return false;
+    }
+
+    result = vkMapMemory(demo->device, staging_buffer.memory, 0,
+                         sizeof(uint32_t), 0, &data);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkMapMemory failed for demo texture: %d\n", result);
+        return false;
+    }
+    *((uint32_t *)data) = 0x00FFFF00;
+    vkUnmapMemory(demo->device, staging_buffer.memory);
+
+    memset(&begin_info, 0, sizeof(VkCommandBufferBeginInfo));
+    begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+
+    command_buffer = demo->command_buffers[0];
+    result = vkBeginCommandBuffer(command_buffer, &begin_info);
+
+    memset(&image_transfer_dst_memory_barrier, 0, sizeof(VkImageMemoryBarrier));
+    image_transfer_dst_memory_barrier.sType =
+        VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+    image_transfer_dst_memory_barrier.image = demo->demo_texture_image;
+    image_transfer_dst_memory_barrier.srcQueueFamilyIndex =
+        VK_QUEUE_FAMILY_IGNORED;
+    image_transfer_dst_memory_barrier.dstQueueFamilyIndex =
+        VK_QUEUE_FAMILY_IGNORED;
+    image_transfer_dst_memory_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+    image_transfer_dst_memory_barrier.newLayout =
+        VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+    image_transfer_dst_memory_barrier.subresourceRange.aspectMask =
+        VK_IMAGE_ASPECT_COLOR_BIT;
+    image_transfer_dst_memory_barrier.subresourceRange.levelCount = 1;
+    image_transfer_dst_memory_barrier.subresourceRange.layerCount = 1;
+    image_transfer_dst_memory_barrier.dstAccessMask =
+        VK_ACCESS_TRANSFER_WRITE_BIT;
+
+    vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+                         VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1,
+                         &image_transfer_dst_memory_barrier);
+
+    memset(&buffer_copy_region, 0, sizeof(VkBufferImageCopy));
+    buffer_copy_region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    buffer_copy_region.imageSubresource.layerCount = 1;
+    buffer_copy_region.imageExtent.width = 2;
+    buffer_copy_region.imageExtent.height = 2;
+    buffer_copy_region.imageExtent.depth = 1;
+
+    vkCmdCopyBufferToImage(
+        command_buffer, staging_buffer.buffer, demo->demo_texture_image,
+        VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &buffer_copy_region);
+
+    memset(&image_shader_memory_barrier, 0, sizeof(VkImageMemoryBarrier));
+    image_shader_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+    image_shader_memory_barrier.image = demo->demo_texture_image;
+    image_shader_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    image_shader_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    image_shader_memory_barrier.oldLayout =
+        VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+    image_shader_memory_barrier.newLayout =
+        VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+    image_shader_memory_barrier.subresourceRange.aspectMask =
+        VK_IMAGE_ASPECT_COLOR_BIT;
+    image_shader_memory_barrier.subresourceRange.levelCount = 1;
+    image_shader_memory_barrier.subresourceRange.layerCount = 1;
+    image_shader_memory_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+    image_shader_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
+
+    vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
+                         VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, 0,
+                         NULL, 1, &image_shader_memory_barrier);
+
+    result = vkEndCommandBuffer(command_buffer);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkEndCommandBuffer failed for demo texture: %d\n",
+                result);
+        return false;
+    }
+
+    memset(&fence_create, 0, sizeof(VkFenceCreateInfo));
+    fence_create.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+    result = vkCreateFence(demo->device, &fence_create, NULL, &fence);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkCreateFence failed for demo texture: %d\n", result);
+        return false;
+    }
+
+    memset(&submit_info, 0, sizeof(VkSubmitInfo));
+    submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+    submit_info.commandBufferCount = 1;
+    submit_info.pCommandBuffers = &command_buffer;
+
+    result = vkQueueSubmit(demo->graphics_queue, 1, &submit_info, fence);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkQueueSubmit failed for demo texture: %d\n", result);
+        return false;
+    }
+    result = vkWaitForFences(demo->device, 1, &fence, VK_TRUE, UINT64_MAX);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkWaitForFences failed for demo texture: %d\n",
+                result);
+        return false;
+    }
+
+    vkDestroyBuffer(demo->device, staging_buffer.buffer, NULL);
+    vkFreeMemory(demo->device, staging_buffer.memory, NULL);
+    vkDestroyFence(demo->device, fence, NULL);
+
+    return true;
+}
+
+bool create_vulkan_demo(struct vulkan_demo *demo) {
+    if (!create_instance(demo)) {
+        return false;
+    }
+    if (!create_surface(demo)) {
+        return false;
+    }
+    if (!create_physical_device(demo)) {
+        return false;
+    }
+    if (!create_logical_device(demo)) {
+        return false;
+    }
+    if (!create_sampler(demo)) {
+        return false;
+    }
+    if (!create_descriptor_set_layout(demo)) {
+        return false;
+    }
+    if (!create_swap_chain_related_resources(demo)) {
+        return false;
+    }
+    if (!create_descriptor_pool(demo)) {
+        return false;
+    }
+    if (!create_descriptor_sets(demo)) {
+        return false;
+    }
+    if (!create_command_pool(demo)) {
+        return false;
+    }
+    if (!create_command_buffers(demo)) {
+        return false;
+    }
+    if (!create_semaphores(demo)) {
+        return false;
+    }
+    if (!create_fence(demo)) {
+        return false;
+    }
+    if (!create_demo_texture(demo)) {
+        return false;
+    }
+
+    return true;
+}
+
+bool recreate_swap_chain(struct vulkan_demo *demo) {
+    printf("recreating swapchain\n");
+    if (!destroy_swap_chain_related_resources(demo)) {
+        return false;
+    }
+    if (!create_swap_chain_related_resources(demo)) {
+        return false;
+    }
+    update_descriptor_sets(demo);
+    nk_glfw3_resize(demo->swap_chain_image_extent.width,
+                    demo->swap_chain_image_extent.height);
+    return true;
+}
+
+bool render(struct vulkan_demo *demo, struct nk_colorf *bg,
+            VkSemaphore wait_semaphore, uint32_t image_index) {
+    VkCommandBufferBeginInfo command_buffer_begin_info;
+    VkCommandBuffer command_buffer;
+    VkRenderPassBeginInfo render_pass_info;
+    VkSubmitInfo submit_info;
+    VkPipelineStageFlags wait_stage =
+        VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+    VkResult result;
+    VkPresentInfoKHR present_info;
+    VkClearValue clear_color;
+
+    memcpy(&clear_color.color, bg, sizeof(VkClearColorValue));
+
+    memset(&command_buffer_begin_info, 0, sizeof(VkCommandBufferBeginInfo));
+    command_buffer_begin_info.sType =
+        VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+
+    command_buffer = demo->command_buffers[image_index];
+    result = vkBeginCommandBuffer(command_buffer, &command_buffer_begin_info);
+
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkBeginCommandBuffer failed: %d\n", result);
+        return false;
+    }
+
+    memset(&render_pass_info, 0, sizeof(VkRenderPassBeginInfo));
+    render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
+    render_pass_info.renderPass = demo->render_pass;
+    render_pass_info.framebuffer = demo->framebuffers[image_index];
+    render_pass_info.renderArea.offset.x = 0;
+    render_pass_info.renderArea.offset.y = 0;
+    render_pass_info.renderArea.extent = demo->swap_chain_image_extent;
+    render_pass_info.clearValueCount = 1;
+    render_pass_info.pClearValues = &clear_color;
+
+    vkCmdBeginRenderPass(command_buffer, &render_pass_info,
+                         VK_SUBPASS_CONTENTS_INLINE);
+
+    vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
+                      demo->pipeline);
+    vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
+                            demo->pipeline_layout, 0, 1,
+                            &demo->descriptor_sets[image_index], 0, NULL);
+    vkCmdDraw(command_buffer, 3, 1, 0, 0);
+
+    vkCmdEndRenderPass(command_buffer);
+
+    result = vkEndCommandBuffer(command_buffer);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkEndCommandBuffer failed: %d\n", result);
+        return false;
+    }
+
+    memset(&submit_info, 0, sizeof(VkSubmitInfo));
+    submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+    submit_info.waitSemaphoreCount = 1;
+    submit_info.pWaitSemaphores = &wait_semaphore;
+    submit_info.pWaitDstStageMask = &wait_stage;
+    submit_info.commandBufferCount = 1;
+    submit_info.pCommandBuffers = &demo->command_buffers[image_index];
+    submit_info.signalSemaphoreCount = 1;
+    submit_info.pSignalSemaphores = &demo->render_finished;
+
+    result = vkQueueSubmit(demo->graphics_queue, 1, &submit_info,
+                           demo->render_fence);
+
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkQueueSubmit failed: %d\n", result);
+        return false;
+    }
+
+    memset(&present_info, 0, sizeof(VkPresentInfoKHR));
+    present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
+    present_info.waitSemaphoreCount = 1;
+    present_info.pWaitSemaphores = &demo->render_finished;
+    present_info.swapchainCount = 1;
+    present_info.pSwapchains = &demo->swap_chain;
+    present_info.pImageIndices = &image_index;
+
+    result = vkQueuePresentKHR(demo->present_queue, &present_info);
+
+    if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
+        recreate_swap_chain(demo);
+    } else if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkQueuePresentKHR failed: %d\n", result);
+        return false;
+    }
+    return true;
+}
+
+VkResult
+destroy_debug_utils_messenger_ext(VkInstance instance,
+                                  VkDebugUtilsMessengerEXT debugMessenger,
+                                  const VkAllocationCallbacks *pAllocator) {
+    PFN_vkDestroyDebugUtilsMessengerEXT func =
+        (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
+            instance, "vkDestroyDebugUtilsMessengerEXT");
+    if (func != NULL) {
+        func(instance, debugMessenger, pAllocator);
+        return VK_SUCCESS;
+    } else {
+        return VK_ERROR_EXTENSION_NOT_PRESENT;
+    }
+}
+
+bool cleanup(struct vulkan_demo *demo) {
+    VkResult result;
+
+    printf("cleaning up\n");
+    result = vkDeviceWaitIdle(demo->device);
+    if (result != VK_SUCCESS) {
+        fprintf(stderr, "vkDeviceWaitIdle failed: %d\n", result);
+        return false;
+    }
+
+    destroy_swap_chain_related_resources(demo);
+
+    vkFreeCommandBuffers(demo->device, demo->command_pool,
+                         demo->swap_chain_images_len, demo->command_buffers);
+    vkDestroyCommandPool(demo->device, demo->command_pool, NULL);
+    vkDestroySampler(demo->device, demo->sampler, NULL);
+    vkDestroySemaphore(demo->device, demo->render_finished, NULL);
+    vkDestroySemaphore(demo->device, demo->image_available, NULL);
+    vkDestroyFence(demo->device, demo->render_fence, NULL);
+
+    vkDestroyImage(demo->device, demo->demo_texture_image, NULL);
+    vkDestroyImageView(demo->device, demo->demo_texture_image_view, NULL);
+    vkFreeMemory(demo->device, demo->demo_texture_memory, NULL);
+
+    vkDestroyDescriptorSetLayout(demo->device, demo->descriptor_set_layout,
+                                 NULL);
+    vkDestroyDescriptorPool(demo->device, demo->descriptor_pool, NULL);
+
+    vkDestroyDevice(demo->device, NULL);
+    vkDestroySurfaceKHR(demo->instance, demo->surface, NULL);
+
+    if (demo->debug_messenger) {
+        result = destroy_debug_utils_messenger_ext(demo->instance,
+                                                   demo->debug_messenger, NULL);
+        if (result != VK_SUCCESS) {
+            fprintf(stderr, "Couldn't destroy debug messenger: %d\n", result);
+            return false;
+        }
+    }
+    vkDestroyInstance(demo->instance, NULL);
+    if (demo->swap_chain_images) {
+        free(demo->swap_chain_images);
+    }
+    if (demo->swap_chain_image_views) {
+        free(demo->swap_chain_image_views);
+    }
+
+    if (demo->overlay_images) {
+        free(demo->overlay_images);
+    }
+    if (demo->overlay_image_views) {
+        free(demo->overlay_image_views);
+    }
+    if (demo->overlay_image_memories) {
+        free(demo->overlay_image_memories);
+    }
+
+    if (demo->descriptor_sets) {
+        free(demo->descriptor_sets);
+    }
+    if (demo->framebuffers) {
+        free(demo->framebuffers);
+    }
+    if (demo->command_buffers) {
+        free(demo->command_buffers);
+    }
+
+    return true;
+}
+
+int main(void) {
+    struct vulkan_demo demo;
+    struct nk_context *ctx;
+    struct nk_colorf bg;
+    struct nk_image img;
+    uint32_t image_index;
+    VkResult result;
+    VkSemaphore nk_semaphore;
+
+    glfwSetErrorCallback(glfw_error_callback);
+    if (!glfwInit()) {
+        fprintf(stderr, "[GFLW] failed to init!\n");
+        exit(1);
+    }
+    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
+    memset(&demo, 0, sizeof(struct vulkan_demo));
+    demo.win =
+        glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Demo", NULL, NULL);
+
+    if (!create_vulkan_demo(&demo)) {
+        fprintf(stderr, "failed to create vulkan demo!\n");
+        exit(1);
+    }
+    ctx = nk_glfw3_init(
+        demo.win, demo.device, demo.physical_device, demo.indices.graphics,
+        demo.overlay_image_views, demo.swap_chain_images_len,
+        demo.swap_chain_image_format, NK_GLFW3_INSTALL_CALLBACKS,
+        MAX_VERTEX_BUFFER, MAX_ELEMENT_BUFFER);
+    /* Load Fonts: if none of these are loaded a default font will be used  */
+    /* Load Cursor: if you uncomment cursor loading please hide the cursor */
+    {
+        struct nk_font_atlas *atlas;
+        nk_glfw3_font_stash_begin(&atlas);
+        /*struct nk_font *droid = nk_font_atlas_add_from_file(atlas,
+         * "../../../extra_font/DroidSans.ttf", 14, 0);*/
+        /*struct nk_font *roboto = nk_font_atlas_add_from_file(atlas,
+         * "../../../extra_font/Roboto-Regular.ttf", 14, 0);*/
+        /*struct nk_font *future = nk_font_atlas_add_from_file(atlas,
+         * "../../../extra_font/kenvector_future_thin.ttf", 13, 0);*/
+        /*struct nk_font *clean = nk_font_atlas_add_from_file(atlas,
+         * "../../../extra_font/ProggyClean.ttf", 12, 0);*/
+        /*struct nk_font *tiny = nk_font_atlas_add_from_file(atlas,
+         * "../../../extra_font/ProggyTiny.ttf", 10, 0);*/
+        /*struct nk_font *cousine = nk_font_atlas_add_from_file(atlas,
+         * "../../../extra_font/Cousine-Regular.ttf", 13, 0);*/
+        nk_glfw3_font_stash_end(demo.graphics_queue);
+        /*nk_style_load_all_cursors(ctx, atlas->cursors);*/
+    /*nk_style_set_font(ctx, &droid->handle);*/}
+
+#ifdef INCLUDE_STYLE
+    /* ease regression testing during Nuklear release process; not needed for
+     * anything else */
+#ifdef STYLE_WHITE
+    set_style(ctx, THEME_WHITE);
+#elif defined(STYLE_RED)
+    set_style(ctx, THEME_RED);
+#elif defined(STYLE_BLUE)
+    set_style(ctx, THEME_BLUE);
+#elif defined(STYLE_DARK)
+    set_style(ctx, THEME_DARK);
+#endif
+#endif
+
+    img = nk_image_ptr(demo.demo_texture_image_view);
+    bg.r = 0.10f, bg.g = 0.18f, bg.b = 0.24f, bg.a = 1.0f;
+    while (!glfwWindowShouldClose(demo.win)) {
+        /* Input */
+        glfwPollEvents();
+        nk_glfw3_new_frame();
+
+        /* GUI */
+        if (nk_begin(ctx, "Demo", nk_rect(50, 50, 230, 250),
+                     NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_SCALABLE |
+                         NK_WINDOW_MINIMIZABLE | NK_WINDOW_TITLE)) {
+            enum { EASY, HARD };
+            static int op = EASY;
+            static int property = 20;
+            nk_layout_row_static(ctx, 30, 80, 1);
+            if (nk_button_label(ctx, "button"))
+                fprintf(stdout, "button pressed\n");
+
+            nk_layout_row_dynamic(ctx, 30, 2);
+            if (nk_option_label(ctx, "easy", op == EASY))
+                op = EASY;
+            if (nk_option_label(ctx, "hard", op == HARD))
+                op = HARD;
+
+            nk_layout_row_dynamic(ctx, 25, 1);
+            nk_property_int(ctx, "Compression:", 0, &property, 100, 10, 1);
+
+            nk_layout_row_dynamic(ctx, 20, 1);
+            nk_label(ctx, "background:", NK_TEXT_LEFT);
+            nk_layout_row_dynamic(ctx, 25, 1);
+            if (nk_combo_begin_color(ctx, nk_rgb_cf(bg),
+                                     nk_vec2(nk_widget_width(ctx), 400))) {
+                nk_layout_row_dynamic(ctx, 120, 1);
+                bg = nk_color_picker(ctx, bg, NK_RGBA);
+                nk_layout_row_dynamic(ctx, 25, 1);
+                bg.r = nk_propertyf(ctx, "#R:", 0, bg.r, 1.0f, 0.01f, 0.005f);
+                bg.g = nk_propertyf(ctx, "#G:", 0, bg.g, 1.0f, 0.01f, 0.005f);
+                bg.b = nk_propertyf(ctx, "#B:", 0, bg.b, 1.0f, 0.01f, 0.005f);
+                bg.a = nk_propertyf(ctx, "#A:", 0, bg.a, 1.0f, 0.01f, 0.005f);
+                nk_combo_end(ctx);
+            }
+        }
+        nk_end(ctx);
+
+        /* Bindless Texture */
+        if (nk_begin(ctx, "Texture", nk_rect(500, 300, 200, 200),
+                     NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_SCALABLE |
+                         NK_WINDOW_MINIMIZABLE | NK_WINDOW_TITLE)) {
+            struct nk_command_buffer *canvas = nk_window_get_canvas(ctx);
+            struct nk_rect total_space = nk_window_get_content_region(ctx);
+            nk_draw_image(canvas, total_space, &img, nk_white);
+        }
+        nk_end(ctx);
+
+        /* -------------- EXAMPLES ---------------- */
+#ifdef INCLUDE_CALCULATOR
+        calculator(ctx);
+#endif
+#ifdef INCLUDE_CANVAS
+        canvas(ctx);
+#endif
+#ifdef INCLUDE_OVERVIEW
+        overview(ctx);
+#endif
+#ifdef INCLUDE_NODE_EDITOR
+        node_editor(ctx);
+#endif
+        /* ----------------------------------------- */
+
+        result = vkWaitForFences(demo.device, 1, &demo.render_fence, VK_TRUE,
+                                 UINT64_MAX);
+
+        if (result != VK_SUCCESS) {
+            fprintf(stderr, "vkWaitForFences failed: %d\n", result);
+            return false;
+        }
+
+        result = vkResetFences(demo.device, 1, &demo.render_fence);
+        if (result != VK_SUCCESS) {
+            fprintf(stderr, "vkResetFences failed: %d\n", result);
+            return false;
+        }
+
+        result =
+            vkAcquireNextImageKHR(demo.device, demo.swap_chain, UINT64_MAX,
+                                  demo.image_available, NULL, &image_index);
+
+        if (result == VK_ERROR_OUT_OF_DATE_KHR) {
+            continue;
+        }
+        if (result != VK_SUCCESS) {
+            fprintf(stderr, "vkAcquireNextImageKHR failed: %d\n", result);
+            return false;
+        }
+
+        /* Draw */
+        nk_semaphore =
+            nk_glfw3_render(demo.graphics_queue, image_index,
+                            demo.image_available, NK_ANTI_ALIASING_ON);
+        if (!render(&demo, &bg, nk_semaphore, image_index)) {
+            fprintf(stderr, "render failed\n");
+            return false;
+        }
+    }
+    nk_glfw3_shutdown();
+    cleanup(&demo);
+    glfwTerminate();
+    return 0;
+}

+ 1647 - 0
demo/glfw_vulkan/nuklear_glfw_vulkan.h

@@ -0,0 +1,1647 @@
+/*
+ * Nuklear - 1.32.0 - public domain
+ * no warrenty implied; use at your own risk.
+ * authored from 2015-2016 by Micha Mettke
+ */
+/*
+ * ==============================================================
+ *
+ *                              API
+ *
+ * ===============================================================
+ */
+#ifndef NK_GLFW_VULKAN_H_
+#define NK_GLFW_VULKAN_H_
+
+unsigned char nuklearshaders_nuklear_vert_spv[] = {
+  0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0b, 0x00, 0x0d, 0x00,
+  0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, 0x30,
+  0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00,
+  0x0a, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
+  0x2a, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00,
+  0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0xc2, 0x01, 0x00, 0x00,
+  0x04, 0x00, 0x09, 0x00, 0x47, 0x4c, 0x5f, 0x41, 0x52, 0x42, 0x5f, 0x73,
+  0x65, 0x70, 0x61, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x68, 0x61, 0x64,
+  0x65, 0x72, 0x5f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x00, 0x00,
+  0x04, 0x00, 0x0a, 0x00, 0x47, 0x4c, 0x5f, 0x47, 0x4f, 0x4f, 0x47, 0x4c,
+  0x45, 0x5f, 0x63, 0x70, 0x70, 0x5f, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x5f,
+  0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69,
+  0x76, 0x65, 0x00, 0x00, 0x04, 0x00, 0x08, 0x00, 0x47, 0x4c, 0x5f, 0x47,
+  0x4f, 0x4f, 0x47, 0x4c, 0x45, 0x5f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64,
+  0x65, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x00,
+  0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e,
+  0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x08, 0x00, 0x00, 0x00,
+  0x67, 0x6c, 0x5f, 0x50, 0x65, 0x72, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78,
+  0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x08, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x67, 0x6c, 0x5f, 0x50, 0x6f, 0x73, 0x69, 0x74,
+  0x69, 0x6f, 0x6e, 0x00, 0x05, 0x00, 0x03, 0x00, 0x0a, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x00, 0x00,
+  0x55, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x42, 0x75, 0x66, 0x66, 0x65,
+  0x72, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x00, 0x06, 0x00, 0x06, 0x00,
+  0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x72, 0x6f, 0x6a,
+  0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00,
+  0x10, 0x00, 0x00, 0x00, 0x75, 0x62, 0x6f, 0x00, 0x05, 0x00, 0x05, 0x00,
+  0x16, 0x00, 0x00, 0x00, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e,
+  0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x27, 0x00, 0x00, 0x00,
+  0x66, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x00, 0x00, 0x00,
+  0x05, 0x00, 0x04, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x63, 0x6f, 0x6c, 0x6f,
+  0x72, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x42, 0x00, 0x00, 0x00,
+  0x66, 0x72, 0x61, 0x67, 0x55, 0x76, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00,
+  0x43, 0x00, 0x00, 0x00, 0x75, 0x76, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00,
+  0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x08, 0x00, 0x00, 0x00,
+  0x02, 0x00, 0x00, 0x00, 0x48, 0x00, 0x04, 0x00, 0x0e, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00,
+  0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x0e, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+  0x47, 0x00, 0x03, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+  0x47, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00,
+  0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00,
+  0x16, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x47, 0x00, 0x04, 0x00, 0x27, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x2a, 0x00, 0x00, 0x00,
+  0x1e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00,
+  0x42, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x47, 0x00, 0x04, 0x00, 0x43, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
+  0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+  0x16, 0x00, 0x03, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+  0x17, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+  0x04, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x03, 0x00, 0x08, 0x00, 0x00, 0x00,
+  0x07, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00,
+  0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00,
+  0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+  0x15, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00,
+  0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x04, 0x00,
+  0x0d, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+  0x1e, 0x00, 0x03, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
+  0x20, 0x00, 0x04, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+  0x0e, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x0f, 0x00, 0x00, 0x00,
+  0x10, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
+  0x11, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
+  0x17, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+  0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x15, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00,
+  0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
+  0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x20, 0x00, 0x04, 0x00,
+  0x1e, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+  0x15, 0x00, 0x04, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x20, 0x00, 0x00, 0x00,
+  0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
+  0x22, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+  0x3b, 0x00, 0x04, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
+  0x03, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x28, 0x00, 0x00, 0x00,
+  0x20, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
+  0x29, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
+  0x3b, 0x00, 0x04, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x20, 0x00, 0x00, 0x00,
+  0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
+  0x2c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+  0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x7f, 0x43, 0x2b, 0x00, 0x04, 0x00, 0x20, 0x00, 0x00, 0x00,
+  0x36, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00,
+  0x20, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+  0x20, 0x00, 0x04, 0x00, 0x41, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+  0x14, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x41, 0x00, 0x00, 0x00,
+  0x42, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00,
+  0x15, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00,
+  0x05, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x11, 0x00, 0x00, 0x00,
+  0x12, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+  0x3d, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
+  0x12, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00,
+  0x17, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00,
+  0x06, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
+  0x1b, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x50, 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
+  0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
+  0x19, 0x00, 0x00, 0x00, 0x91, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00,
+  0x1d, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
+  0x41, 0x00, 0x05, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00,
+  0x0a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00,
+  0x1f, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00,
+  0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+  0x0c, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00,
+  0x06, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
+  0x7f, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00,
+  0x24, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x22, 0x00, 0x00, 0x00,
+  0x26, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+  0x21, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x26, 0x00, 0x00, 0x00,
+  0x25, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x2c, 0x00, 0x00, 0x00,
+  0x2d, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00,
+  0x3d, 0x00, 0x04, 0x00, 0x20, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00,
+  0x2d, 0x00, 0x00, 0x00, 0x70, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
+  0x2f, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x88, 0x00, 0x05, 0x00,
+  0x06, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00,
+  0x30, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x2c, 0x00, 0x00, 0x00,
+  0x32, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
+  0x3d, 0x00, 0x04, 0x00, 0x20, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
+  0x32, 0x00, 0x00, 0x00, 0x70, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
+  0x34, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x88, 0x00, 0x05, 0x00,
+  0x06, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
+  0x30, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x2c, 0x00, 0x00, 0x00,
+  0x37, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00,
+  0x3d, 0x00, 0x04, 0x00, 0x20, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00,
+  0x37, 0x00, 0x00, 0x00, 0x70, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
+  0x39, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x88, 0x00, 0x05, 0x00,
+  0x06, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00,
+  0x30, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x2c, 0x00, 0x00, 0x00,
+  0x3c, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00,
+  0x3d, 0x00, 0x04, 0x00, 0x20, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00,
+  0x3c, 0x00, 0x00, 0x00, 0x70, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
+  0x3e, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x88, 0x00, 0x05, 0x00,
+  0x06, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00,
+  0x30, 0x00, 0x00, 0x00, 0x50, 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00,
+  0x40, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00,
+  0x3a, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00,
+  0x27, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00,
+  0x14, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00,
+  0x3e, 0x00, 0x03, 0x00, 0x42, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00,
+  0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00
+};
+unsigned int nuklearshaders_nuklear_vert_spv_len = 1856;
+unsigned char nuklearshaders_nuklear_frag_spv[] = {
+  0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0b, 0x00, 0x0d, 0x00,
+  0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, 0x30,
+  0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00,
+  0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00,
+  0x11, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
+  0x10, 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+  0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0xc2, 0x01, 0x00, 0x00,
+  0x04, 0x00, 0x09, 0x00, 0x47, 0x4c, 0x5f, 0x41, 0x52, 0x42, 0x5f, 0x73,
+  0x65, 0x70, 0x61, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x68, 0x61, 0x64,
+  0x65, 0x72, 0x5f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x00, 0x00,
+  0x04, 0x00, 0x0a, 0x00, 0x47, 0x4c, 0x5f, 0x47, 0x4f, 0x4f, 0x47, 0x4c,
+  0x45, 0x5f, 0x63, 0x70, 0x70, 0x5f, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x5f,
+  0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69,
+  0x76, 0x65, 0x00, 0x00, 0x04, 0x00, 0x08, 0x00, 0x47, 0x4c, 0x5f, 0x47,
+  0x4f, 0x4f, 0x47, 0x4c, 0x45, 0x5f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64,
+  0x65, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x00,
+  0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e,
+  0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x09, 0x00, 0x00, 0x00,
+  0x74, 0x65, 0x78, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x00, 0x00, 0x00, 0x00,
+  0x05, 0x00, 0x06, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x63, 0x75, 0x72, 0x72,
+  0x65, 0x6e, 0x74, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x00, 0x00,
+  0x05, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00, 0x66, 0x72, 0x61, 0x67,
+  0x55, 0x76, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x15, 0x00, 0x00, 0x00,
+  0x6f, 0x75, 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x00, 0x00, 0x00, 0x00,
+  0x05, 0x00, 0x05, 0x00, 0x17, 0x00, 0x00, 0x00, 0x66, 0x72, 0x61, 0x67,
+  0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00,
+  0x0d, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x47, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00,
+  0x1e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00,
+  0x15, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x47, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
+  0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+  0x16, 0x00, 0x03, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+  0x17, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+  0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00,
+  0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x19, 0x00, 0x09, 0x00,
+  0x0a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x03, 0x00,
+  0x0b, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
+  0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+  0x3b, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x0f, 0x00, 0x00, 0x00,
+  0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
+  0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+  0x3b, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00,
+  0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00,
+  0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+  0x20, 0x00, 0x04, 0x00, 0x16, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x07, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x16, 0x00, 0x00, 0x00,
+  0x17, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00,
+  0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x03, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00,
+  0x3b, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+  0x07, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00,
+  0x0e, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00,
+  0x0f, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
+  0x57, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
+  0x0e, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00,
+  0x09, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00,
+  0x07, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
+  0x3d, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
+  0x09, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00,
+  0x1a, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
+  0x3e, 0x00, 0x03, 0x00, 0x15, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
+  0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00
+};
+unsigned int nuklearshaders_nuklear_frag_spv_len = 860;
+
+#include <stddef.h>
+#include <string.h>
+#define GLFW_INCLUDE_VULKAN
+#include <GLFW/glfw3.h>
+
+enum nk_glfw_init_state { NK_GLFW3_DEFAULT = 0, NK_GLFW3_INSTALL_CALLBACKS };
+
+NK_API struct nk_context *
+nk_glfw3_init(GLFWwindow *win, VkDevice logical_device,
+              VkPhysicalDevice physical_device,
+              uint32_t graphics_queue_family_index, VkImageView *image_views,
+              uint32_t image_views_len, VkFormat color_format,
+              enum nk_glfw_init_state init_state,
+              VkDeviceSize max_vertex_buffer, VkDeviceSize max_element_buffer);
+NK_API void nk_glfw3_shutdown(void);
+NK_API void nk_glfw3_font_stash_begin(struct nk_font_atlas **atlas);
+NK_API void nk_glfw3_font_stash_end(VkQueue graphics_queue);
+NK_API void nk_glfw3_new_frame();
+NK_API VkSemaphore nk_glfw3_render(VkQueue graphics_queue,
+                                   uint32_t buffer_index,
+                                   VkSemaphore wait_semaphore,
+                                   enum nk_anti_aliasing AA);
+NK_API void nk_glfw3_resize(uint32_t framebuffer_width,
+                            uint32_t framebuffer_height);
+NK_API void nk_glfw3_device_destroy(void);
+NK_API void nk_glfw3_device_create(
+    VkDevice logical_device, VkPhysicalDevice physical_device,
+    uint32_t graphics_queue_family_index, VkImageView *image_views,
+    uint32_t image_views_len, VkFormat color_format,
+    VkDeviceSize max_vertex_buffer, VkDeviceSize max_element_buffer,
+    uint32_t framebuffer_width, uint32_t framebuffer_height);
+
+NK_API void nk_glfw3_char_callback(GLFWwindow *win, unsigned int codepoint);
+NK_API void nk_gflw3_scroll_callback(GLFWwindow *win, double xoff, double yoff);
+NK_API void nk_glfw3_mouse_button_callback(GLFWwindow *win, int button,
+                                           int action, int mods);
+
+#endif
+/*
+ * ==============================================================
+ *
+ *                          IMPLEMENTATION
+ *
+ * ===============================================================
+ */
+#ifdef NK_GLFW_VULKAN_IMPLEMENTATION
+#undef NK_GLFW_VULKAN_IMPLEMENTATION
+
+#ifndef NK_GLFW_TEXT_MAX
+#define NK_GLFW_TEXT_MAX 256
+#endif
+#ifndef NK_GLFW_DOUBLE_CLICK_LO
+#define NK_GLFW_DOUBLE_CLICK_LO 0.02
+#endif
+#ifndef NK_GLFW_DOUBLE_CLICK_HI
+#define NK_GLFW_DOUBLE_CLICK_HI 0.2
+#endif
+#ifndef NK_GLFW_MAX_TEXTURES
+#define NK_GLFW_MAX_TEXTURES 256
+#endif
+
+#define VK_COLOR_COMPONENT_MASK_RGBA                                           \
+    VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |                      \
+        VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT
+
+struct nk_glfw_vertex {
+    float position[2];
+    float uv[2];
+    nk_byte col[4];
+};
+
+struct nk_vulkan_texture_descriptor_set {
+    VkImageView image_view;
+    VkDescriptorSet descriptor_set;
+};
+
+struct nk_glfw_device {
+    struct nk_buffer cmds;
+    struct nk_draw_null_texture tex_null;
+    int max_vertex_buffer;
+    int max_element_buffer;
+    VkDevice logical_device;
+    VkPhysicalDevice physical_device;
+    VkImageView *image_views;
+    uint32_t image_views_len;
+    VkFormat color_format;
+    VkFramebuffer *framebuffers;
+    uint32_t framebuffers_len;
+    VkCommandBuffer *command_buffers;
+    uint32_t command_buffers_len;
+    VkSampler sampler;
+    VkCommandPool command_pool;
+    VkSemaphore render_completed;
+    VkBuffer vertex_buffer;
+    VkDeviceMemory vertex_memory;
+    void *mapped_vertex;
+    VkBuffer index_buffer;
+    VkDeviceMemory index_memory;
+    void *mapped_index;
+    VkBuffer uniform_buffer;
+    VkDeviceMemory uniform_memory;
+    void *mapped_uniform;
+    VkRenderPass render_pass;
+    VkDescriptorPool descriptor_pool;
+    VkDescriptorSetLayout uniform_descriptor_set_layout;
+    VkDescriptorSet uniform_descriptor_set;
+    VkDescriptorSetLayout texture_descriptor_set_layout;
+    struct nk_vulkan_texture_descriptor_set *texture_descriptor_sets;
+    uint32_t texture_descriptor_sets_len;
+    VkPipelineLayout pipeline_layout;
+    VkPipeline pipeline;
+    VkImage font_image;
+    VkImageView font_image_view;
+    VkDeviceMemory font_memory;
+};
+
+static struct nk_glfw {
+    GLFWwindow *win;
+    int width, height;
+    int display_width, display_height;
+    struct nk_glfw_device vulkan;
+    struct nk_context ctx;
+    struct nk_font_atlas atlas;
+    struct nk_vec2 fb_scale;
+    unsigned int text[NK_GLFW_TEXT_MAX];
+    int text_len;
+    struct nk_vec2 scroll;
+    double last_button_click;
+    int is_double_click_down;
+    struct nk_vec2 double_click_pos;
+} glfw;
+
+struct Mat4f {
+    float m[16];
+};
+
+NK_INTERN uint32_t nk_glfw3_find_memory_index(
+    VkPhysicalDevice physical_device, uint32_t type_filter,
+    VkMemoryPropertyFlags properties) {
+    VkPhysicalDeviceMemoryProperties mem_properties;
+    uint32_t i;
+
+    vkGetPhysicalDeviceMemoryProperties(physical_device, &mem_properties);
+    for (i = 0; i < mem_properties.memoryTypeCount; i++) {
+        if ((type_filter & (1 << i)) &&
+            (mem_properties.memoryTypes[i].propertyFlags & properties) ==
+                properties) {
+            return i;
+        }
+    }
+
+    assert(0);
+    return 0;
+}
+
+NK_INTERN void nk_glfw3_create_sampler(struct nk_glfw_device *dev) {
+    VkResult result;
+    VkSamplerCreateInfo sampler_info;
+    memset(&sampler_info, 0, sizeof(VkSamplerCreateInfo));
+
+    sampler_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
+    sampler_info.pNext = NULL;
+    sampler_info.maxAnisotropy = 1.0;
+    sampler_info.magFilter = VK_FILTER_LINEAR;
+    sampler_info.minFilter = VK_FILTER_LINEAR;
+    sampler_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+    sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+    sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+    sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+    sampler_info.mipLodBias = 0.0f;
+    sampler_info.compareEnable = VK_FALSE;
+    sampler_info.compareOp = VK_COMPARE_OP_ALWAYS;
+    sampler_info.minLod = 0.0f;
+    sampler_info.maxLod = 0.0f;
+    sampler_info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK;
+
+    result = vkCreateSampler(dev->logical_device, &sampler_info, NULL,
+                             &dev->sampler);
+    NK_ASSERT(result == VK_SUCCESS);
+}
+
+NK_INTERN void
+nk_glfw3_create_command_pool(struct nk_glfw_device *dev,
+                             uint32_t graphics_queue_family_index) {
+    VkResult result;
+    VkCommandPoolCreateInfo pool_info;
+    memset(&pool_info, 0, sizeof(VkCommandPoolCreateInfo));
+
+    pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+    pool_info.queueFamilyIndex = graphics_queue_family_index;
+    pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+    result = vkCreateCommandPool(dev->logical_device, &pool_info, NULL,
+                                 &dev->command_pool);
+    NK_ASSERT(result == VK_SUCCESS);
+}
+
+NK_INTERN void nk_glfw3_create_command_buffers(struct nk_glfw_device *dev) {
+    VkResult result;
+    VkCommandBufferAllocateInfo allocate_info;
+    memset(&allocate_info, 0, sizeof(VkCommandBufferAllocateInfo));
+
+    dev->command_buffers = (VkCommandBuffer *)malloc(dev->image_views_len *
+                                                     sizeof(VkCommandBuffer));
+    dev->command_buffers_len = dev->image_views_len;
+
+    allocate_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+    allocate_info.commandPool = dev->command_pool;
+    allocate_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+    allocate_info.commandBufferCount = dev->command_buffers_len;
+
+    result = vkAllocateCommandBuffers(dev->logical_device, &allocate_info,
+                                      dev->command_buffers);
+    NK_ASSERT(result == VK_SUCCESS);
+}
+
+NK_INTERN void nk_glfw3_create_semaphore(struct nk_glfw_device *dev) {
+    VkResult result;
+    VkSemaphoreCreateInfo semaphore_info;
+    memset(&semaphore_info, 0, sizeof(VkSemaphoreCreateInfo));
+
+    semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+    result = (vkCreateSemaphore(dev->logical_device, &semaphore_info, NULL,
+                                &dev->render_completed));
+    NK_ASSERT(result == VK_SUCCESS);
+}
+
+NK_INTERN void nk_glfw3_create_buffer_and_memory(struct nk_glfw_device *dev,
+                                                 VkBuffer *buffer,
+                                                 VkBufferUsageFlags usage,
+                                                 VkDeviceMemory *memory,
+                                                 VkDeviceSize size) {
+    VkMemoryRequirements mem_reqs;
+    VkResult result;
+    VkBufferCreateInfo buffer_info;
+    VkMemoryAllocateInfo alloc_info;
+
+    memset(&buffer_info, 0, sizeof(VkBufferCreateInfo));
+    buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+    buffer_info.size = size;
+    buffer_info.usage = usage;
+    buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+    result = vkCreateBuffer(dev->logical_device, &buffer_info, NULL, buffer);
+    NK_ASSERT(result == VK_SUCCESS);
+
+    vkGetBufferMemoryRequirements(dev->logical_device, *buffer, &mem_reqs);
+
+    memset(&alloc_info, 0, sizeof(VkMemoryAllocateInfo));
+    alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+    alloc_info.allocationSize = mem_reqs.size;
+    alloc_info.memoryTypeIndex = nk_glfw3_find_memory_index(
+        dev->physical_device, mem_reqs.memoryTypeBits,
+        VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+            VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+
+    result = vkAllocateMemory(dev->logical_device, &alloc_info, NULL, memory);
+    NK_ASSERT(result == VK_SUCCESS);
+    result = vkBindBufferMemory(dev->logical_device, *buffer, *memory, 0);
+    NK_ASSERT(result == VK_SUCCESS);
+}
+
+NK_INTERN void nk_glfw3_create_render_pass(struct nk_glfw_device *dev) {
+    VkAttachmentDescription attachment;
+    VkAttachmentReference color_reference;
+    VkSubpassDependency subpass_dependency;
+    VkSubpassDescription subpass_description;
+    VkRenderPassCreateInfo render_pass_info;
+    VkResult result;
+
+    memset(&attachment, 0, sizeof(VkAttachmentDescription));
+    attachment.format = dev->color_format;
+    attachment.samples = VK_SAMPLE_COUNT_1_BIT;
+    attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+    attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+    attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+    attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+    attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+    attachment.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+
+    memset(&color_reference, 0, sizeof(VkAttachmentReference));
+    color_reference.attachment = 0;
+    color_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+    memset(&subpass_dependency, 0, sizeof(VkSubpassDependency));
+    subpass_dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
+    subpass_dependency.srcAccessMask = 0;
+    subpass_dependency.srcStageMask =
+        VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+    subpass_dependency.dstSubpass = 0;
+    subpass_dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
+                                       VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+    subpass_dependency.dstStageMask =
+        VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+
+    memset(&subpass_description, 0, sizeof(VkSubpassDescription));
+    subpass_description.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+    subpass_description.colorAttachmentCount = 1;
+    subpass_description.pColorAttachments = &color_reference;
+
+    memset(&render_pass_info, 0, sizeof(VkRenderPassCreateInfo));
+    render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+    render_pass_info.attachmentCount = 1;
+    render_pass_info.pAttachments = &attachment;
+    render_pass_info.subpassCount = 1;
+    render_pass_info.pSubpasses = &subpass_description;
+    render_pass_info.dependencyCount = 1;
+    render_pass_info.pDependencies = &subpass_dependency;
+
+    result = vkCreateRenderPass(dev->logical_device, &render_pass_info, NULL,
+                                &dev->render_pass);
+    NK_ASSERT(result == VK_SUCCESS);
+}
+
+NK_INTERN void nk_glfw3_create_framebuffers(struct nk_glfw_device *dev,
+                                            uint32_t framebuffer_width,
+                                            uint32_t framebuffer_height) {
+
+    VkFramebufferCreateInfo framebuffer_create_info;
+    uint32_t i;
+    VkResult result;
+
+    dev->framebuffers =
+        (VkFramebuffer *)malloc(dev->image_views_len * sizeof(VkFramebuffer));
+
+    memset(&framebuffer_create_info, 0, sizeof(VkFramebufferCreateInfo));
+    framebuffer_create_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+    framebuffer_create_info.renderPass = dev->render_pass;
+    framebuffer_create_info.attachmentCount = 1;
+    framebuffer_create_info.width = framebuffer_width;
+    framebuffer_create_info.height = framebuffer_height;
+    framebuffer_create_info.layers = 1;
+    for (i = 0; i < dev->image_views_len; i++) {
+        framebuffer_create_info.pAttachments = &dev->image_views[i];
+        result =
+            vkCreateFramebuffer(dev->logical_device, &framebuffer_create_info,
+                                NULL, &dev->framebuffers[i]);
+        NK_ASSERT(result == VK_SUCCESS);
+    }
+    dev->framebuffers_len = dev->image_views_len;
+}
+
+NK_INTERN void nk_glfw3_create_descriptor_pool(struct nk_glfw_device *dev) {
+    VkDescriptorPoolSize pool_sizes[2];
+    VkDescriptorPoolCreateInfo pool_info;
+    VkResult result;
+
+    memset(&pool_sizes, 0, sizeof(VkDescriptorPoolSize) * 2);
+    pool_sizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+    pool_sizes[0].descriptorCount = 1;
+    pool_sizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+    pool_sizes[1].descriptorCount = NK_GLFW_MAX_TEXTURES;
+
+    memset(&pool_info, 0, sizeof(VkDescriptorPoolCreateInfo));
+    pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
+    pool_info.poolSizeCount = 2;
+    pool_info.pPoolSizes = pool_sizes;
+    pool_info.maxSets = 1 + NK_GLFW_MAX_TEXTURES;
+
+    result = vkCreateDescriptorPool(dev->logical_device, &pool_info, NULL,
+                                    &dev->descriptor_pool);
+    NK_ASSERT(result == VK_SUCCESS);
+}
+
+NK_INTERN void
+nk_glfw3_create_uniform_descriptor_set_layout(struct nk_glfw_device *dev) {
+    VkDescriptorSetLayoutBinding binding;
+    VkDescriptorSetLayoutCreateInfo descriptor_set_info;
+    VkResult result;
+
+    memset(&binding, 0, sizeof(VkDescriptorSetLayoutBinding));
+    binding.binding = 0;
+    binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+    binding.descriptorCount = 1;
+    binding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
+
+    memset(&descriptor_set_info, 0, sizeof(VkDescriptorSetLayoutCreateInfo));
+    descriptor_set_info.sType =
+        VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+    descriptor_set_info.bindingCount = 1;
+    descriptor_set_info.pBindings = &binding;
+
+    result =
+        vkCreateDescriptorSetLayout(dev->logical_device, &descriptor_set_info,
+                                    NULL, &dev->uniform_descriptor_set_layout);
+
+    NK_ASSERT(result == VK_SUCCESS);
+}
+
+NK_INTERN void
+nk_glfw3_create_and_update_uniform_descriptor_set(struct nk_glfw_device *dev) {
+    VkDescriptorSetAllocateInfo allocate_info;
+    VkDescriptorBufferInfo buffer_info;
+    VkWriteDescriptorSet descriptor_write;
+    VkResult result;
+
+    memset(&allocate_info, 0, sizeof(VkDescriptorSetAllocateInfo));
+    allocate_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+    allocate_info.descriptorPool = dev->descriptor_pool;
+    allocate_info.descriptorSetCount = 1;
+    allocate_info.pSetLayouts = &dev->uniform_descriptor_set_layout;
+
+    result = vkAllocateDescriptorSets(dev->logical_device, &allocate_info,
+                                      &dev->uniform_descriptor_set);
+    NK_ASSERT(result == VK_SUCCESS);
+
+    memset(&buffer_info, 0, sizeof(VkDescriptorBufferInfo));
+    buffer_info.buffer = dev->uniform_buffer;
+    buffer_info.offset = 0;
+    buffer_info.range = sizeof(struct Mat4f);
+
+    memset(&descriptor_write, 0, sizeof(VkWriteDescriptorSet));
+    descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+    descriptor_write.dstSet = dev->uniform_descriptor_set;
+    descriptor_write.dstBinding = 0;
+    descriptor_write.dstArrayElement = 0;
+    descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+    descriptor_write.descriptorCount = 1;
+    descriptor_write.pBufferInfo = &buffer_info;
+
+    vkUpdateDescriptorSets(dev->logical_device, 1, &descriptor_write, 0, NULL);
+}
+
+NK_INTERN void
+nk_glfw3_create_texture_descriptor_set_layout(struct nk_glfw_device *dev) {
+    VkDescriptorSetLayoutBinding binding;
+    VkDescriptorSetLayoutCreateInfo descriptor_set_info;
+    VkResult result;
+
+    memset(&binding, 0, sizeof(VkDescriptorSetLayoutBinding));
+    binding.binding = 0;
+    binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+    binding.descriptorCount = 1;
+    binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+
+    memset(&descriptor_set_info, 0, sizeof(VkDescriptorSetLayoutCreateInfo));
+    descriptor_set_info.sType =
+        VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+    descriptor_set_info.bindingCount = 1;
+    descriptor_set_info.pBindings = &binding;
+
+    result =
+        vkCreateDescriptorSetLayout(dev->logical_device, &descriptor_set_info,
+                                    NULL, &dev->texture_descriptor_set_layout);
+
+    NK_ASSERT(result == VK_SUCCESS);
+}
+
+NK_INTERN void
+nk_glfw3_create_texture_descriptor_sets(struct nk_glfw_device *dev) {
+    VkDescriptorSetLayout *descriptor_set_layouts;
+    VkDescriptorSet *descriptor_sets;
+    VkDescriptorSetAllocateInfo allocate_info;
+    VkResult result;
+    int i;
+
+    descriptor_set_layouts = (VkDescriptorSetLayout *)malloc(
+        NK_GLFW_MAX_TEXTURES * sizeof(VkDescriptorSetLayout));
+    descriptor_sets = (VkDescriptorSet *)malloc(NK_GLFW_MAX_TEXTURES *
+                                                sizeof(VkDescriptorSet));
+
+    dev->texture_descriptor_sets =
+        (struct nk_vulkan_texture_descriptor_set *)malloc(
+            NK_GLFW_MAX_TEXTURES *
+            sizeof(struct nk_vulkan_texture_descriptor_set));
+    dev->texture_descriptor_sets_len = 0;
+
+    for (i = 0; i < NK_GLFW_MAX_TEXTURES; i++) {
+        descriptor_set_layouts[i] = dev->texture_descriptor_set_layout;
+        descriptor_sets[i] = dev->texture_descriptor_sets[i].descriptor_set;
+    }
+
+    memset(&allocate_info, 0, sizeof(VkDescriptorSetAllocateInfo));
+    allocate_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+    allocate_info.descriptorPool = dev->descriptor_pool;
+    allocate_info.descriptorSetCount = NK_GLFW_MAX_TEXTURES;
+    allocate_info.pSetLayouts = descriptor_set_layouts;
+
+    result = vkAllocateDescriptorSets(dev->logical_device, &allocate_info,
+                                      descriptor_sets);
+    NK_ASSERT(result == VK_SUCCESS);
+
+    for (i = 0; i < NK_GLFW_MAX_TEXTURES; i++) {
+        dev->texture_descriptor_sets[i].descriptor_set = descriptor_sets[i];
+    }
+    free(descriptor_set_layouts);
+    free(descriptor_sets);
+}
+
+NK_INTERN void nk_glfw3_create_pipeline_layout(struct nk_glfw_device *dev) {
+    VkPipelineLayoutCreateInfo pipeline_layout_info;
+    VkDescriptorSetLayout descriptor_set_layouts[2];
+    VkResult result;
+
+    descriptor_set_layouts[0] = dev->uniform_descriptor_set_layout;
+    descriptor_set_layouts[1] = dev->texture_descriptor_set_layout;
+
+    memset(&pipeline_layout_info, 0, sizeof(VkPipelineLayoutCreateInfo));
+    pipeline_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+    pipeline_layout_info.setLayoutCount = 2;
+    pipeline_layout_info.pSetLayouts = descriptor_set_layouts;
+
+    result = (vkCreatePipelineLayout(dev->logical_device, &pipeline_layout_info,
+                                     NULL, &dev->pipeline_layout));
+    NK_ASSERT(result == VK_SUCCESS);
+}
+
+NK_INTERN VkPipelineShaderStageCreateInfo
+nk_glfw3_create_shader(struct nk_glfw_device *dev, unsigned char *spv_shader,
+                       uint32_t size, VkShaderStageFlagBits stage_bit) {
+    VkShaderModuleCreateInfo create_info;
+    VkPipelineShaderStageCreateInfo shader_info;
+    VkShaderModule module = NULL;
+    VkResult result;
+
+    memset(&create_info, 0, sizeof(VkShaderModuleCreateInfo));
+    create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+    create_info.codeSize = size;
+    create_info.pCode = (const uint32_t *)spv_shader;
+    result =
+        vkCreateShaderModule(dev->logical_device, &create_info, NULL, &module);
+    NK_ASSERT(result == VK_SUCCESS);
+
+    memset(&shader_info, 0, sizeof(VkPipelineShaderStageCreateInfo));
+    shader_info.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+    shader_info.stage = stage_bit;
+    shader_info.module = module;
+    shader_info.pName = "main";
+    return shader_info;
+}
+
+NK_INTERN void nk_glfw3_create_pipeline(struct nk_glfw_device *dev) {
+    VkPipelineInputAssemblyStateCreateInfo input_assembly_state;
+    VkPipelineRasterizationStateCreateInfo rasterization_state;
+    VkPipelineColorBlendAttachmentState attachment_state = {
+        VK_TRUE,
+        VK_BLEND_FACTOR_SRC_ALPHA,
+        VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
+        VK_BLEND_OP_ADD,
+        VK_BLEND_FACTOR_SRC_ALPHA,
+        VK_BLEND_FACTOR_ONE,
+        VK_BLEND_OP_ADD,
+        VK_COLOR_COMPONENT_MASK_RGBA,
+    };
+    VkPipelineColorBlendStateCreateInfo color_blend_state;
+    VkPipelineViewportStateCreateInfo viewport_state;
+    VkPipelineMultisampleStateCreateInfo multisample_state;
+    VkDynamicState dynamic_states[2] = {VK_DYNAMIC_STATE_VIEWPORT,
+                                        VK_DYNAMIC_STATE_SCISSOR};
+    VkPipelineDynamicStateCreateInfo dynamic_state;
+    VkPipelineShaderStageCreateInfo shader_stages[2];
+    VkVertexInputBindingDescription vertex_input_info;
+    VkVertexInputAttributeDescription vertex_attribute_description[3];
+    VkPipelineVertexInputStateCreateInfo vertex_input;
+    VkGraphicsPipelineCreateInfo pipeline_info;
+    VkResult result;
+
+    memset(&input_assembly_state, 0,
+           sizeof(VkPipelineInputAssemblyStateCreateInfo));
+    input_assembly_state.sType =
+        VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
+    input_assembly_state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
+    input_assembly_state.primitiveRestartEnable = VK_FALSE;
+
+    memset(&rasterization_state, 0,
+           sizeof(VkPipelineRasterizationStateCreateInfo));
+    rasterization_state.sType =
+        VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
+    rasterization_state.polygonMode = VK_POLYGON_MODE_FILL;
+    rasterization_state.cullMode = VK_CULL_MODE_NONE;
+    rasterization_state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
+    rasterization_state.lineWidth = 1.0f;
+
+    memset(&color_blend_state, 0, sizeof(VkPipelineColorBlendStateCreateInfo));
+    color_blend_state.sType =
+        VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
+    color_blend_state.attachmentCount = 1;
+    color_blend_state.pAttachments = &attachment_state;
+
+    memset(&viewport_state, 0, sizeof(VkPipelineViewportStateCreateInfo));
+    viewport_state.sType =
+        VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
+    viewport_state.viewportCount = 1;
+    viewport_state.scissorCount = 1;
+
+    memset(&multisample_state, 0, sizeof(VkPipelineMultisampleStateCreateInfo));
+    multisample_state.sType =
+        VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
+    multisample_state.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
+
+    memset(&dynamic_state, 0, sizeof(VkPipelineDynamicStateCreateInfo));
+    dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
+    dynamic_state.pDynamicStates = dynamic_states;
+    dynamic_state.dynamicStateCount = 2;
+
+    shader_stages[0] = nk_glfw3_create_shader(
+        dev, nuklearshaders_nuklear_vert_spv,
+        nuklearshaders_nuklear_vert_spv_len, VK_SHADER_STAGE_VERTEX_BIT);
+    shader_stages[1] = nk_glfw3_create_shader(
+        dev, nuklearshaders_nuklear_frag_spv,
+        nuklearshaders_nuklear_frag_spv_len, VK_SHADER_STAGE_FRAGMENT_BIT);
+
+    memset(&vertex_input_info, 0, sizeof(VkVertexInputBindingDescription));
+    vertex_input_info.binding = 0;
+    vertex_input_info.stride = sizeof(struct nk_glfw_vertex);
+    vertex_input_info.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
+
+    memset(&vertex_attribute_description, 0,
+           sizeof(VkVertexInputAttributeDescription) * 3);
+    vertex_attribute_description[0].location = 0;
+    vertex_attribute_description[0].format = VK_FORMAT_R32G32_SFLOAT;
+    vertex_attribute_description[0].offset =
+        NK_OFFSETOF(struct nk_glfw_vertex, position);
+    vertex_attribute_description[1].location = 1;
+    vertex_attribute_description[1].format = VK_FORMAT_R32G32_SFLOAT;
+    vertex_attribute_description[1].offset =
+        NK_OFFSETOF(struct nk_glfw_vertex, uv);
+    vertex_attribute_description[2].location = 2;
+    vertex_attribute_description[2].format = VK_FORMAT_R8G8B8A8_UINT;
+    vertex_attribute_description[2].offset =
+        NK_OFFSETOF(struct nk_glfw_vertex, col);
+
+    memset(&vertex_input, 0, sizeof(VkPipelineVertexInputStateCreateInfo));
+    vertex_input.sType =
+        VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
+    vertex_input.vertexBindingDescriptionCount = 1;
+    vertex_input.pVertexBindingDescriptions = &vertex_input_info;
+    vertex_input.vertexAttributeDescriptionCount = 3;
+    vertex_input.pVertexAttributeDescriptions = vertex_attribute_description;
+
+    memset(&pipeline_info, 0, sizeof(VkGraphicsPipelineCreateInfo));
+    pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
+    pipeline_info.flags = 0;
+    pipeline_info.stageCount = 2;
+    pipeline_info.pStages = shader_stages;
+    pipeline_info.pVertexInputState = &vertex_input;
+    pipeline_info.pInputAssemblyState = &input_assembly_state;
+    pipeline_info.pViewportState = &viewport_state;
+    pipeline_info.pRasterizationState = &rasterization_state;
+    pipeline_info.pMultisampleState = &multisample_state;
+    pipeline_info.pColorBlendState = &color_blend_state;
+    pipeline_info.pDynamicState = &dynamic_state;
+    pipeline_info.layout = dev->pipeline_layout;
+    pipeline_info.renderPass = dev->render_pass;
+    pipeline_info.basePipelineIndex = -1;
+    pipeline_info.basePipelineHandle = NULL;
+
+    result = vkCreateGraphicsPipelines(dev->logical_device, NULL, 1,
+                                       &pipeline_info, NULL, &dev->pipeline);
+    NK_ASSERT(result == VK_SUCCESS);
+
+    vkDestroyShaderModule(dev->logical_device, shader_stages[0].module, NULL);
+    vkDestroyShaderModule(dev->logical_device, shader_stages[1].module, NULL);
+}
+
+NK_INTERN void nk_glfw3_create_render_resources(struct nk_glfw_device *dev,
+                                                uint32_t framebuffer_width,
+                                                uint32_t framebuffer_height) {
+    nk_glfw3_create_render_pass(dev);
+    nk_glfw3_create_framebuffers(dev, framebuffer_width, framebuffer_height);
+    nk_glfw3_create_descriptor_pool(dev);
+    nk_glfw3_create_uniform_descriptor_set_layout(dev);
+    nk_glfw3_create_and_update_uniform_descriptor_set(dev);
+    nk_glfw3_create_texture_descriptor_set_layout(dev);
+    nk_glfw3_create_texture_descriptor_sets(dev);
+    nk_glfw3_create_pipeline_layout(dev);
+    nk_glfw3_create_pipeline(dev);
+}
+
+NK_API void nk_glfw3_device_create(
+    VkDevice logical_device, VkPhysicalDevice physical_device,
+    uint32_t graphics_queue_family_index, VkImageView *image_views,
+    uint32_t image_views_len, VkFormat color_format,
+    VkDeviceSize max_vertex_buffer, VkDeviceSize max_element_buffer,
+    uint32_t framebuffer_width, uint32_t framebuffer_height) {
+    struct nk_glfw_device *dev = &glfw.vulkan;
+    dev->max_vertex_buffer = max_vertex_buffer;
+    dev->max_element_buffer = max_element_buffer;
+    nk_buffer_init_default(&dev->cmds);
+    dev->logical_device = logical_device;
+    dev->physical_device = physical_device;
+    dev->image_views = image_views;
+    dev->image_views_len = image_views_len;
+    dev->color_format = color_format;
+    dev->framebuffers = NULL;
+    dev->framebuffers_len = 0;
+
+    nk_glfw3_create_sampler(dev);
+    nk_glfw3_create_command_pool(dev, graphics_queue_family_index);
+    nk_glfw3_create_command_buffers(dev);
+    nk_glfw3_create_semaphore(dev);
+
+    nk_glfw3_create_buffer_and_memory(dev, &dev->vertex_buffer,
+                                      VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
+                                      &dev->vertex_memory, max_vertex_buffer);
+    nk_glfw3_create_buffer_and_memory(dev, &dev->index_buffer,
+                                      VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
+                                      &dev->index_memory, max_element_buffer);
+    nk_glfw3_create_buffer_and_memory(
+        dev, &dev->uniform_buffer, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+        &dev->uniform_memory, sizeof(struct Mat4f));
+
+    vkMapMemory(dev->logical_device, dev->vertex_memory, 0, max_vertex_buffer,
+                0, &dev->mapped_vertex);
+    vkMapMemory(dev->logical_device, dev->index_memory, 0, max_element_buffer,
+                0, &dev->mapped_index);
+    vkMapMemory(dev->logical_device, dev->uniform_memory, 0,
+                sizeof(struct Mat4f), 0, &dev->mapped_uniform);
+
+    nk_glfw3_create_render_resources(dev, framebuffer_width,
+                                     framebuffer_height);
+}
+
+NK_INTERN void nk_glfw3_device_upload_atlas(VkQueue graphics_queue,
+                                            const void *image, int width,
+                                            int height) {
+    struct nk_glfw_device *dev = &glfw.vulkan;
+
+    VkImageCreateInfo image_info;
+    VkResult result;
+    VkMemoryRequirements mem_reqs;
+    VkMemoryAllocateInfo alloc_info;
+    VkBufferCreateInfo buffer_info;
+    uint8_t *data = 0;
+    VkCommandBufferBeginInfo begin_info;
+    VkCommandBuffer command_buffer;
+    VkImageMemoryBarrier image_memory_barrier;
+    VkBufferImageCopy buffer_copy_region;
+    VkImageMemoryBarrier image_shader_memory_barrier;
+    VkFence fence;
+    VkFenceCreateInfo fence_create;
+    VkSubmitInfo submit_info;
+    VkImageViewCreateInfo image_view_info;
+    struct {
+        VkDeviceMemory memory;
+        VkBuffer buffer;
+    } staging_buffer;
+
+    memset(&image_info, 0, sizeof(VkImageCreateInfo));
+    image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+    image_info.imageType = VK_IMAGE_TYPE_2D;
+    image_info.format = VK_FORMAT_R8G8B8A8_UNORM;
+    image_info.extent.width = (uint32_t)width;
+    image_info.extent.height = (uint32_t)height;
+    image_info.extent.depth = 1;
+    image_info.mipLevels = 1;
+    image_info.arrayLayers = 1;
+    image_info.samples = VK_SAMPLE_COUNT_1_BIT;
+    image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
+    image_info.usage =
+        VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
+    image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+    image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+
+    result =
+        vkCreateImage(dev->logical_device, &image_info, NULL, &dev->font_image);
+    NK_ASSERT(result == VK_SUCCESS);
+
+    vkGetImageMemoryRequirements(dev->logical_device, dev->font_image,
+                                 &mem_reqs);
+
+    memset(&alloc_info, 0, sizeof(VkMemoryAllocateInfo));
+    alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+    alloc_info.allocationSize = mem_reqs.size;
+    alloc_info.memoryTypeIndex = nk_glfw3_find_memory_index(
+        dev->physical_device, mem_reqs.memoryTypeBits,
+        VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+
+    result = vkAllocateMemory(dev->logical_device, &alloc_info, NULL,
+                              &dev->font_memory);
+    NK_ASSERT(result == VK_SUCCESS);
+    result = vkBindImageMemory(dev->logical_device, dev->font_image,
+                               dev->font_memory, 0);
+    NK_ASSERT(result == VK_SUCCESS);
+
+    memset(&buffer_info, 0, sizeof(VkBufferCreateInfo));
+    buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+    buffer_info.size = alloc_info.allocationSize;
+    buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+    buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+    result = vkCreateBuffer(dev->logical_device, &buffer_info, NULL,
+                            &staging_buffer.buffer);
+    NK_ASSERT(result == VK_SUCCESS);
+    vkGetBufferMemoryRequirements(dev->logical_device, staging_buffer.buffer,
+                                  &mem_reqs);
+
+    alloc_info.allocationSize = mem_reqs.size;
+    alloc_info.memoryTypeIndex = nk_glfw3_find_memory_index(
+        dev->physical_device, mem_reqs.memoryTypeBits,
+        VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+            VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+
+    result = vkAllocateMemory(dev->logical_device, &alloc_info, NULL,
+                              &staging_buffer.memory);
+    NK_ASSERT(result == VK_SUCCESS);
+    result = vkBindBufferMemory(dev->logical_device, staging_buffer.buffer,
+                                staging_buffer.memory, 0);
+    NK_ASSERT(result == VK_SUCCESS);
+
+    result = vkMapMemory(dev->logical_device, staging_buffer.memory, 0,
+                         alloc_info.allocationSize, 0, (void **)&data);
+    NK_ASSERT(result == VK_SUCCESS);
+    memcpy(data, image, width * height * 4);
+    vkUnmapMemory(dev->logical_device, staging_buffer.memory);
+
+    memset(&begin_info, 0, sizeof(VkCommandBufferBeginInfo));
+    begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+
+    NK_ASSERT(dev->command_buffers_len > 0);
+    /*
+    use the same command buffer as for render as we are regenerating the
+    buffer during render anyway
+    */
+    command_buffer = dev->command_buffers[0];
+    result = vkBeginCommandBuffer(command_buffer, &begin_info);
+    NK_ASSERT(result == VK_SUCCESS);
+
+    memset(&image_memory_barrier, 0, sizeof(VkImageMemoryBarrier));
+    image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+    image_memory_barrier.image = dev->font_image;
+    image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    image_memory_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+    image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+    image_memory_barrier.subresourceRange.aspectMask =
+        VK_IMAGE_ASPECT_COLOR_BIT;
+    image_memory_barrier.subresourceRange.levelCount = 1;
+    image_memory_barrier.subresourceRange.layerCount = 1;
+    image_memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+
+    vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+                         VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1,
+                         &image_memory_barrier);
+
+    memset(&buffer_copy_region, 0, sizeof(VkBufferImageCopy));
+    buffer_copy_region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    buffer_copy_region.imageSubresource.layerCount = 1;
+    buffer_copy_region.imageExtent.width = (uint32_t)width;
+    buffer_copy_region.imageExtent.height = (uint32_t)height;
+    buffer_copy_region.imageExtent.depth = 1;
+
+    vkCmdCopyBufferToImage(
+        command_buffer, staging_buffer.buffer, dev->font_image,
+        VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &buffer_copy_region);
+
+    memset(&image_shader_memory_barrier, 0, sizeof(VkImageMemoryBarrier));
+    image_shader_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+    image_shader_memory_barrier.image = dev->font_image;
+    image_shader_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    image_shader_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    image_shader_memory_barrier.oldLayout =
+        VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+    image_shader_memory_barrier.newLayout =
+        VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+    image_shader_memory_barrier.subresourceRange.aspectMask =
+        VK_IMAGE_ASPECT_COLOR_BIT;
+    image_shader_memory_barrier.subresourceRange.levelCount = 1;
+    image_shader_memory_barrier.subresourceRange.layerCount = 1;
+    image_shader_memory_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+    image_shader_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
+
+    vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
+                         VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, 0,
+                         NULL, 1, &image_shader_memory_barrier);
+
+    result = vkEndCommandBuffer(command_buffer);
+    NK_ASSERT(result == VK_SUCCESS);
+
+    memset(&fence_create, 0, sizeof(VkFenceCreateInfo));
+    fence_create.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+
+    result = vkCreateFence(dev->logical_device, &fence_create, NULL, &fence);
+    NK_ASSERT(result == VK_SUCCESS);
+
+    memset(&submit_info, 0, sizeof(VkSubmitInfo));
+    submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+    submit_info.commandBufferCount = 1;
+    submit_info.pCommandBuffers = &command_buffer;
+
+    result = vkQueueSubmit(graphics_queue, 1, &submit_info, fence);
+    NK_ASSERT(result == VK_SUCCESS);
+    result =
+        vkWaitForFences(dev->logical_device, 1, &fence, VK_TRUE, UINT64_MAX);
+    NK_ASSERT(result == VK_SUCCESS);
+
+    vkDestroyFence(dev->logical_device, fence, NULL);
+
+    vkFreeMemory(dev->logical_device, staging_buffer.memory, NULL);
+    vkDestroyBuffer(dev->logical_device, staging_buffer.buffer, NULL);
+
+    memset(&image_view_info, 0, sizeof(VkImageViewCreateInfo));
+    image_view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+    image_view_info.image = dev->font_image;
+    image_view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
+    image_view_info.format = image_info.format;
+    image_view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    image_view_info.subresourceRange.layerCount = 1;
+    image_view_info.subresourceRange.levelCount = 1;
+
+    result = vkCreateImageView(dev->logical_device, &image_view_info, NULL,
+                               &dev->font_image_view);
+    NK_ASSERT(result == VK_SUCCESS);
+}
+
+NK_INTERN void nk_glfw3_destroy_render_resources(struct nk_glfw_device *dev) {
+    uint32_t i;
+
+    vkDestroyPipeline(dev->logical_device, dev->pipeline, NULL);
+    vkDestroyPipelineLayout(dev->logical_device, dev->pipeline_layout, NULL);
+    vkDestroyDescriptorSetLayout(dev->logical_device,
+                                 dev->texture_descriptor_set_layout, NULL);
+    vkDestroyDescriptorSetLayout(dev->logical_device,
+                                 dev->uniform_descriptor_set_layout, NULL);
+    vkDestroyDescriptorPool(dev->logical_device, dev->descriptor_pool, NULL);
+    for (i = 0; i < dev->framebuffers_len; i++) {
+        vkDestroyFramebuffer(dev->logical_device, dev->framebuffers[i], NULL);
+    }
+    free(dev->framebuffers);
+    dev->framebuffers_len = 0;
+    free(dev->texture_descriptor_sets);
+    dev->texture_descriptor_sets_len = 0;
+    vkDestroyRenderPass(dev->logical_device, dev->render_pass, NULL);
+}
+
+NK_API void nk_glfw3_resize(uint32_t framebuffer_width,
+                            uint32_t framebuffer_height) {
+    struct nk_glfw_device *dev = &glfw.vulkan;
+    glfwGetWindowSize(glfw.win, &glfw.width, &glfw.height);
+    glfwGetFramebufferSize(glfw.win, &glfw.display_width, &glfw.display_height);
+    glfw.fb_scale.x = (float)glfw.display_width / (float)glfw.width;
+    glfw.fb_scale.y = (float)glfw.display_height / (float)glfw.height;
+
+    nk_glfw3_destroy_render_resources(dev);
+    nk_glfw3_create_render_resources(dev, framebuffer_width,
+                                     framebuffer_height);
+}
+
+NK_API void nk_glfw3_device_destroy(void) {
+    struct nk_glfw_device *dev = &glfw.vulkan;
+
+    vkDeviceWaitIdle(dev->logical_device);
+
+    nk_glfw3_destroy_render_resources(dev);
+
+    vkFreeCommandBuffers(dev->logical_device, dev->command_pool,
+                         dev->command_buffers_len, dev->command_buffers);
+    vkDestroyCommandPool(dev->logical_device, dev->command_pool, NULL);
+    vkDestroySemaphore(dev->logical_device, dev->render_completed, NULL);
+
+    vkUnmapMemory(dev->logical_device, dev->vertex_memory);
+    vkUnmapMemory(dev->logical_device, dev->index_memory);
+    vkUnmapMemory(dev->logical_device, dev->uniform_memory);
+
+    vkFreeMemory(dev->logical_device, dev->vertex_memory, NULL);
+    vkFreeMemory(dev->logical_device, dev->index_memory, NULL);
+    vkFreeMemory(dev->logical_device, dev->uniform_memory, NULL);
+
+    vkDestroyBuffer(dev->logical_device, dev->vertex_buffer, NULL);
+    vkDestroyBuffer(dev->logical_device, dev->index_buffer, NULL);
+    vkDestroyBuffer(dev->logical_device, dev->uniform_buffer, NULL);
+
+    vkDestroySampler(dev->logical_device, dev->sampler, NULL);
+
+    vkFreeMemory(dev->logical_device, dev->font_memory, NULL);
+    vkDestroyImage(dev->logical_device, dev->font_image, NULL);
+    vkDestroyImageView(dev->logical_device, dev->font_image_view, NULL);
+
+    free(dev->command_buffers);
+    nk_buffer_free(&dev->cmds);
+}
+
+NK_API
+void nk_glfw3_shutdown(void) {
+    nk_font_atlas_clear(&glfw.atlas);
+    nk_free(&glfw.ctx);
+    nk_glfw3_device_destroy();
+    memset(&glfw, 0, sizeof(glfw));
+}
+
+NK_API void nk_glfw3_font_stash_begin(struct nk_font_atlas **atlas) {
+    nk_font_atlas_init_default(&glfw.atlas);
+    nk_font_atlas_begin(&glfw.atlas);
+    *atlas = &glfw.atlas;
+}
+
+NK_API void nk_glfw3_font_stash_end(VkQueue graphics_queue) {
+    struct nk_glfw_device *dev = &glfw.vulkan;
+
+    const void *image;
+    int w, h;
+    image = nk_font_atlas_bake(&glfw.atlas, &w, &h, NK_FONT_ATLAS_RGBA32);
+    nk_glfw3_device_upload_atlas(graphics_queue, image, w, h);
+    nk_font_atlas_end(&glfw.atlas, nk_handle_ptr(dev->font_image_view),
+                      &dev->tex_null);
+    if (glfw.atlas.default_font) {
+        nk_style_set_font(&glfw.ctx, &glfw.atlas.default_font->handle);
+    }
+}
+
+NK_API void nk_glfw3_new_frame(void) {
+    int i;
+    double x, y;
+    struct nk_context *ctx = &glfw.ctx;
+    struct GLFWwindow *win = glfw.win;
+
+    nk_input_begin(ctx);
+    for (i = 0; i < glfw.text_len; ++i)
+        nk_input_unicode(ctx, glfw.text[i]);
+
+#ifdef NK_GLFW_GL4_MOUSE_GRABBING
+    /* optional grabbing behavior */
+    if (ctx->input.mouse.grab)
+        glfwSetInputMode(glfw.win, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
+    else if (ctx->input.mouse.ungrab)
+        glfwSetInputMode(glfw.win, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
+#endif
+
+    nk_input_key(ctx, NK_KEY_DEL,
+                 glfwGetKey(win, GLFW_KEY_DELETE) == GLFW_PRESS);
+    nk_input_key(ctx, NK_KEY_ENTER,
+                 glfwGetKey(win, GLFW_KEY_ENTER) == GLFW_PRESS);
+    nk_input_key(ctx, NK_KEY_TAB, glfwGetKey(win, GLFW_KEY_TAB) == GLFW_PRESS);
+    nk_input_key(ctx, NK_KEY_BACKSPACE,
+                 glfwGetKey(win, GLFW_KEY_BACKSPACE) == GLFW_PRESS);
+    nk_input_key(ctx, NK_KEY_UP, glfwGetKey(win, GLFW_KEY_UP) == GLFW_PRESS);
+    nk_input_key(ctx, NK_KEY_DOWN,
+                 glfwGetKey(win, GLFW_KEY_DOWN) == GLFW_PRESS);
+    nk_input_key(ctx, NK_KEY_TEXT_START,
+                 glfwGetKey(win, GLFW_KEY_HOME) == GLFW_PRESS);
+    nk_input_key(ctx, NK_KEY_TEXT_END,
+                 glfwGetKey(win, GLFW_KEY_END) == GLFW_PRESS);
+    nk_input_key(ctx, NK_KEY_SCROLL_START,
+                 glfwGetKey(win, GLFW_KEY_HOME) == GLFW_PRESS);
+    nk_input_key(ctx, NK_KEY_SCROLL_END,
+                 glfwGetKey(win, GLFW_KEY_END) == GLFW_PRESS);
+    nk_input_key(ctx, NK_KEY_SCROLL_DOWN,
+                 glfwGetKey(win, GLFW_KEY_PAGE_DOWN) == GLFW_PRESS);
+    nk_input_key(ctx, NK_KEY_SCROLL_UP,
+                 glfwGetKey(win, GLFW_KEY_PAGE_UP) == GLFW_PRESS);
+    nk_input_key(ctx, NK_KEY_SHIFT,
+                 glfwGetKey(win, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS ||
+                     glfwGetKey(win, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS);
+
+    if (glfwGetKey(win, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS ||
+        glfwGetKey(win, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS) {
+        nk_input_key(ctx, NK_KEY_COPY,
+                     glfwGetKey(win, GLFW_KEY_C) == GLFW_PRESS);
+        nk_input_key(ctx, NK_KEY_PASTE,
+                     glfwGetKey(win, GLFW_KEY_V) == GLFW_PRESS);
+        nk_input_key(ctx, NK_KEY_CUT,
+                     glfwGetKey(win, GLFW_KEY_X) == GLFW_PRESS);
+        nk_input_key(ctx, NK_KEY_TEXT_UNDO,
+                     glfwGetKey(win, GLFW_KEY_Z) == GLFW_PRESS);
+        nk_input_key(ctx, NK_KEY_TEXT_REDO,
+                     glfwGetKey(win, GLFW_KEY_R) == GLFW_PRESS);
+        nk_input_key(ctx, NK_KEY_TEXT_WORD_LEFT,
+                     glfwGetKey(win, GLFW_KEY_LEFT) == GLFW_PRESS);
+        nk_input_key(ctx, NK_KEY_TEXT_WORD_RIGHT,
+                     glfwGetKey(win, GLFW_KEY_RIGHT) == GLFW_PRESS);
+        nk_input_key(ctx, NK_KEY_TEXT_LINE_START,
+                     glfwGetKey(win, GLFW_KEY_B) == GLFW_PRESS);
+        nk_input_key(ctx, NK_KEY_TEXT_LINE_END,
+                     glfwGetKey(win, GLFW_KEY_E) == GLFW_PRESS);
+        nk_input_key(ctx, NK_KEY_TEXT_SELECT_ALL,
+                     glfwGetKey(win, GLFW_KEY_A) == GLFW_PRESS);
+    } else {
+        nk_input_key(ctx, NK_KEY_LEFT,
+                     glfwGetKey(win, GLFW_KEY_LEFT) == GLFW_PRESS);
+        nk_input_key(ctx, NK_KEY_RIGHT,
+                     glfwGetKey(win, GLFW_KEY_RIGHT) == GLFW_PRESS);
+        nk_input_key(ctx, NK_KEY_COPY, 0);
+        nk_input_key(ctx, NK_KEY_PASTE, 0);
+        nk_input_key(ctx, NK_KEY_CUT, 0);
+        nk_input_key(ctx, NK_KEY_SHIFT, 0);
+    }
+
+    glfwGetCursorPos(win, &x, &y);
+    nk_input_motion(ctx, (int)x, (int)y);
+#ifdef NK_GLFW_GL4_MOUSE_GRABBING
+    if (ctx->input.mouse.grabbed) {
+        glfwSetCursorPos(glfw.win, ctx->input.mouse.prev.x,
+                         ctx->input.mouse.prev.y);
+        ctx->input.mouse.pos.x = ctx->input.mouse.prev.x;
+        ctx->input.mouse.pos.y = ctx->input.mouse.prev.y;
+    }
+#endif
+    nk_input_button(ctx, NK_BUTTON_LEFT, (int)x, (int)y,
+                    glfwGetMouseButton(win, GLFW_MOUSE_BUTTON_LEFT) ==
+                        GLFW_PRESS);
+    nk_input_button(ctx, NK_BUTTON_MIDDLE, (int)x, (int)y,
+                    glfwGetMouseButton(win, GLFW_MOUSE_BUTTON_MIDDLE) ==
+                        GLFW_PRESS);
+    nk_input_button(ctx, NK_BUTTON_RIGHT, (int)x, (int)y,
+                    glfwGetMouseButton(win, GLFW_MOUSE_BUTTON_RIGHT) ==
+                        GLFW_PRESS);
+    nk_input_button(ctx, NK_BUTTON_DOUBLE, (int)glfw.double_click_pos.x,
+                    (int)glfw.double_click_pos.y, glfw.is_double_click_down);
+    nk_input_scroll(ctx, glfw.scroll);
+    nk_input_end(&glfw.ctx);
+    glfw.text_len = 0;
+    glfw.scroll = nk_vec2(0, 0);
+}
+
+NK_INTERN void update_texture_descriptor_set(
+    struct nk_glfw_device *dev,
+    struct nk_vulkan_texture_descriptor_set *texture_descriptor_set,
+    VkImageView image_view) {
+    VkDescriptorImageInfo descriptor_image_info;
+    VkWriteDescriptorSet descriptor_write;
+
+    texture_descriptor_set->image_view = image_view;
+
+    memset(&descriptor_image_info, 0, sizeof(VkDescriptorImageInfo));
+    descriptor_image_info.sampler = dev->sampler;
+    descriptor_image_info.imageView = texture_descriptor_set->image_view;
+    descriptor_image_info.imageLayout =
+        VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+
+    memset(&descriptor_write, 0, sizeof(VkWriteDescriptorSet));
+    descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+    descriptor_write.dstSet = texture_descriptor_set->descriptor_set;
+    descriptor_write.dstBinding = 0;
+    descriptor_write.dstArrayElement = 0;
+    descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+    descriptor_write.descriptorCount = 1;
+    descriptor_write.pImageInfo = &descriptor_image_info;
+
+    vkUpdateDescriptorSets(dev->logical_device, 1, &descriptor_write, 0, NULL);
+}
+
+NK_API
+VkSemaphore nk_glfw3_render(VkQueue graphics_queue, uint32_t buffer_index,
+                            VkSemaphore wait_semaphore,
+                            enum nk_anti_aliasing AA) {
+    struct nk_glfw_device *dev = &glfw.vulkan;
+    struct nk_buffer vbuf, ebuf;
+
+    struct Mat4f projection = {
+        {2.0f, 0.0f, 0.0f, 0.0f, 0.0f, -2.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f,
+         0.0f, -1.0f, 1.0f, 0.0f, 1.0f},
+    };
+
+    VkCommandBufferBeginInfo begin_info;
+    VkClearValue clear_value = {{{0.0f, 0.0f, 0.0f, 0.0f}}};
+    VkRenderPassBeginInfo render_pass_begin_nfo;
+    VkCommandBuffer command_buffer;
+    VkResult result;
+    VkViewport viewport;
+
+    VkDeviceSize doffset = 0;
+    VkImageView current_texture = NULL;
+    uint32_t index_offset = 0;
+    VkRect2D scissor;
+    uint32_t wait_semaphore_count;
+    VkSemaphore *wait_semaphores;
+    VkPipelineStageFlags wait_stage =
+        VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+    VkSubmitInfo submit_info;
+
+    projection.m[0] /= glfw.width;
+    projection.m[5] /= glfw.height;
+
+    memcpy(dev->mapped_uniform, &projection, sizeof(projection));
+
+    memset(&begin_info, 0, sizeof(VkCommandBufferBeginInfo));
+    begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+
+    memset(&render_pass_begin_nfo, 0, sizeof(VkRenderPassBeginInfo));
+    render_pass_begin_nfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
+    render_pass_begin_nfo.renderPass = dev->render_pass;
+    render_pass_begin_nfo.renderArea.extent.width = (uint32_t)glfw.width;
+    render_pass_begin_nfo.renderArea.extent.height = (uint32_t)glfw.height;
+    render_pass_begin_nfo.clearValueCount = 1;
+    render_pass_begin_nfo.pClearValues = &clear_value;
+    render_pass_begin_nfo.framebuffer = dev->framebuffers[buffer_index];
+
+    command_buffer = dev->command_buffers[buffer_index];
+
+    result = vkBeginCommandBuffer(command_buffer, &begin_info);
+    NK_ASSERT(result == VK_SUCCESS);
+    vkCmdBeginRenderPass(command_buffer, &render_pass_begin_nfo,
+                         VK_SUBPASS_CONTENTS_INLINE);
+
+    memset(&viewport, 0, sizeof(VkViewport));
+    viewport.width = (float)glfw.width;
+    viewport.height = (float)glfw.height;
+    viewport.maxDepth = 1.0f;
+    vkCmdSetViewport(command_buffer, 0, 1, &viewport);
+
+    vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
+                      dev->pipeline);
+    vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
+                            dev->pipeline_layout, 0, 1,
+                            &dev->uniform_descriptor_set, 0, NULL);
+    {
+        /* convert from command queue into draw list and draw to screen */
+        const struct nk_draw_command *cmd;
+        /* load draw vertices & elements directly into vertex + element buffer
+         */
+        {
+            /* fill convert configuration */
+            struct nk_convert_config config;
+            static const struct nk_draw_vertex_layout_element vertex_layout[] =
+                {{NK_VERTEX_POSITION, NK_FORMAT_FLOAT,
+                  NK_OFFSETOF(struct nk_glfw_vertex, position)},
+                 {NK_VERTEX_TEXCOORD, NK_FORMAT_FLOAT,
+                  NK_OFFSETOF(struct nk_glfw_vertex, uv)},
+                 {NK_VERTEX_COLOR, NK_FORMAT_R8G8B8A8,
+                  NK_OFFSETOF(struct nk_glfw_vertex, col)},
+                 {NK_VERTEX_LAYOUT_END}};
+            NK_MEMSET(&config, 0, sizeof(config));
+            config.vertex_layout = vertex_layout;
+            config.vertex_size = sizeof(struct nk_glfw_vertex);
+            config.vertex_alignment = NK_ALIGNOF(struct nk_glfw_vertex);
+            config.tex_null = dev->tex_null;
+            config.circle_segment_count = 22;
+            config.curve_segment_count = 22;
+            config.arc_segment_count = 22;
+            config.global_alpha = 1.0f;
+            config.shape_AA = AA;
+            config.line_AA = AA;
+
+            /* setup buffers to load vertices and elements */
+            nk_buffer_init_fixed(&vbuf, dev->mapped_vertex,
+                                 (size_t)dev->max_vertex_buffer);
+            nk_buffer_init_fixed(&ebuf, dev->mapped_index,
+                                 (size_t)dev->max_element_buffer);
+            nk_convert(&glfw.ctx, &dev->cmds, &vbuf, &ebuf, &config);
+        }
+
+        /* iterate over and execute each draw command */
+
+        vkCmdBindVertexBuffers(command_buffer, 0, 1, &dev->vertex_buffer,
+                               &doffset);
+        vkCmdBindIndexBuffer(command_buffer, dev->index_buffer, 0,
+                             VK_INDEX_TYPE_UINT16);
+
+        nk_draw_foreach(cmd, &glfw.ctx, &dev->cmds) {
+            if (!cmd->texture.ptr) {
+                continue;
+            }
+            if (cmd->texture.ptr && cmd->texture.ptr != current_texture) {
+                int found = 0;
+                uint32_t i;
+                for (i = 0; i < dev->texture_descriptor_sets_len; i++) {
+                    if (dev->texture_descriptor_sets[i].image_view ==
+                        cmd->texture.ptr) {
+                        found = 1;
+                        break;
+                    }
+                }
+
+                if (!found) {
+                    update_texture_descriptor_set(
+                        dev, &dev->texture_descriptor_sets[i],
+                        (VkImageView)cmd->texture.ptr);
+                    dev->texture_descriptor_sets_len++;
+                }
+                vkCmdBindDescriptorSets(
+                    command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
+                    dev->pipeline_layout, 1, 1,
+                    &dev->texture_descriptor_sets[i].descriptor_set, 0, NULL);
+            }
+
+            if (!cmd->elem_count)
+                continue;
+
+            scissor.offset.x = (int32_t)(NK_MAX(cmd->clip_rect.x, 0.f));
+            scissor.offset.y = (int32_t)(NK_MAX(cmd->clip_rect.y, 0.f));
+            scissor.extent.width = (uint32_t)(cmd->clip_rect.w);
+            scissor.extent.height = (uint32_t)(cmd->clip_rect.h);
+            vkCmdSetScissor(command_buffer, 0, 1, &scissor);
+            vkCmdDrawIndexed(command_buffer, cmd->elem_count, 1, index_offset,
+                             0, 0);
+            index_offset += cmd->elem_count;
+        }
+        nk_clear(&glfw.ctx);
+    }
+
+    vkCmdEndRenderPass(command_buffer);
+    result = vkEndCommandBuffer(command_buffer);
+    NK_ASSERT(result == VK_SUCCESS);
+
+    if (wait_semaphore) {
+        wait_semaphore_count = 1;
+        wait_semaphores = &wait_semaphore;
+    } else {
+        wait_semaphore_count = 0;
+        wait_semaphores = NULL;
+    }
+
+    memset(&submit_info, 0, sizeof(VkSubmitInfo));
+    submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+    submit_info.commandBufferCount = 1;
+    submit_info.pCommandBuffers = &command_buffer;
+    submit_info.pWaitDstStageMask = &wait_stage;
+    submit_info.waitSemaphoreCount = wait_semaphore_count;
+    submit_info.pWaitSemaphores = wait_semaphores;
+    submit_info.signalSemaphoreCount = 1;
+    submit_info.pSignalSemaphores = &dev->render_completed;
+
+    result = vkQueueSubmit(graphics_queue, 1, &submit_info, NULL);
+    NK_ASSERT(result == VK_SUCCESS);
+
+    return dev->render_completed;
+}
+
+NK_API void nk_glfw3_char_callback(GLFWwindow *win, unsigned int codepoint) {
+    (void)win;
+    if (glfw.text_len < NK_GLFW_TEXT_MAX)
+        glfw.text[glfw.text_len++] = codepoint;
+}
+
+NK_API void nk_gflw3_scroll_callback(GLFWwindow *win, double xoff,
+                                     double yoff) {
+    (void)win;
+    (void)xoff;
+    glfw.scroll.x += (float)xoff;
+    glfw.scroll.y += (float)yoff;
+}
+
+NK_API void nk_glfw3_mouse_button_callback(GLFWwindow *window, int button,
+                                           int action, int mods) {
+    double x, y;
+    NK_UNUSED(mods);
+    if (button != GLFW_MOUSE_BUTTON_LEFT)
+        return;
+    glfwGetCursorPos(window, &x, &y);
+    if (action == GLFW_PRESS) {
+        double dt = glfwGetTime() - glfw.last_button_click;
+        if (dt > NK_GLFW_DOUBLE_CLICK_LO && dt < NK_GLFW_DOUBLE_CLICK_HI) {
+            glfw.is_double_click_down = nk_true;
+            glfw.double_click_pos = nk_vec2((float)x, (float)y);
+        }
+        glfw.last_button_click = glfwGetTime();
+    } else
+        glfw.is_double_click_down = nk_false;
+}
+
+NK_INTERN void nk_glfw3_clipboard_paste(nk_handle usr,
+                                        struct nk_text_edit *edit) {
+    const char *text = glfwGetClipboardString(glfw.win);
+    if (text)
+        nk_textedit_paste(edit, text, nk_strlen(text));
+    (void)usr;
+}
+
+NK_INTERN void nk_glfw3_clipboard_copy(nk_handle usr, const char *text,
+                                       int len) {
+    char *str = 0;
+    (void)usr;
+    if (!len)
+        return;
+    str = (char *)malloc((size_t)len + 1);
+    if (!str)
+        return;
+    memcpy(str, text, (size_t)len);
+    str[len] = '\0';
+    glfwSetClipboardString(glfw.win, str);
+    free(str);
+}
+
+NK_API struct nk_context *
+nk_glfw3_init(GLFWwindow *win, VkDevice logical_device,
+              VkPhysicalDevice physical_device,
+              uint32_t graphics_queue_family_index, VkImageView *image_views,
+              uint32_t image_views_len, VkFormat color_format,
+              enum nk_glfw_init_state init_state,
+              VkDeviceSize max_vertex_buffer, VkDeviceSize max_element_buffer) {
+    memset(&glfw, 0, sizeof(struct nk_glfw));
+    glfw.win = win;
+    if (init_state == NK_GLFW3_INSTALL_CALLBACKS) {
+        glfwSetScrollCallback(win, nk_gflw3_scroll_callback);
+        glfwSetCharCallback(win, nk_glfw3_char_callback);
+        glfwSetMouseButtonCallback(win, nk_glfw3_mouse_button_callback);
+    }
+    nk_init_default(&glfw.ctx, 0);
+    glfw.ctx.clip.copy = nk_glfw3_clipboard_copy;
+    glfw.ctx.clip.paste = nk_glfw3_clipboard_paste;
+    glfw.ctx.clip.userdata = nk_handle_ptr(0);
+    glfw.last_button_click = 0;
+
+    glfwGetWindowSize(win, &glfw.width, &glfw.height);
+    glfwGetFramebufferSize(win, &glfw.display_width, &glfw.display_height);
+
+    nk_glfw3_device_create(logical_device, physical_device,
+                           graphics_queue_family_index, image_views,
+                           image_views_len, color_format, max_vertex_buffer,
+                           max_element_buffer, (uint32_t)glfw.display_width,
+                           (uint32_t)glfw.display_height);
+
+    glfw.is_double_click_down = nk_false;
+    glfw.double_click_pos = nk_vec2(0, 0);
+
+    return &glfw.ctx;
+}
+
+#endif

+ 12 - 0
demo/glfw_vulkan/shaders/demo.frag

@@ -0,0 +1,12 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(binding = 0) uniform sampler2D overlay;
+
+layout(location = 0) in vec2 inUV;
+
+layout(location = 0) out vec4 outColor;
+
+void main() {
+    outColor = texture(overlay, inUV);
+}

+ 10 - 0
demo/glfw_vulkan/shaders/demo.vert

@@ -0,0 +1,10 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout (location = 0) out vec2 outUV;
+
+void main()
+{
+    outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
+    gl_Position = vec4(outUV * 2.0f + -1.0f, 0.0f, 1.0f);
+}

+ 11 - 0
demo/glfw_vulkan/src/Makefile

@@ -0,0 +1,11 @@
+create_shader_inlined_header: nuklearshaders/nuklear.vert.spv nuklearshaders/nuklear.frag.spv
+	awk -v st='// NUKLEAR_SHADERS_START' -v et='// NUKLEAR_SHADERS_END' -v repl="$$(xxd -i nuklearshaders/nuklear.vert.spv && xxd -i nuklearshaders/nuklear.frag.spv)" '$$0 == st{del=1} $$0 == et{$$0 = repl; del=0} !del' nuklear_glfw_vulkan.in.h > nuklear_glfw_vulkan.h
+
+nuklearshaders/nuklear.vert.spv: nuklearshaders/nuklear.vert
+	glslc --target-env=vulkan nuklearshaders/nuklear.vert -o nuklearshaders/nuklear.vert.spv
+
+nuklearshaders/nuklear.frag.spv: nuklearshaders/nuklear.frag
+	glslc --target-env=vulkan nuklearshaders/nuklear.frag -o nuklearshaders/nuklear.frag.spv
+
+clean:
+	rm nuklearshaders/nuklear.vert.spv nuklearshaders/nuklear.frag.spv nuklear_glfw_vulkan.h

+ 5 - 0
demo/glfw_vulkan/src/README.md

@@ -0,0 +1,5 @@
+Contrary to OpenGL Vulkan needs precompiled shaders in the SPIR-V format which makes it a bit more difficult to inline the shadercode.
+
+After executing `make` the result should be a self contained `nuklear_glfw_vulkan.h`. Copy the result file to the parent directory and the "release" should be done.
+
+You will need to have `xxd`, `glslc` and `awk` installed for this.

+ 1424 - 0
demo/glfw_vulkan/src/nuklear_glfw_vulkan.in.h

@@ -0,0 +1,1424 @@
+/*
+ * Nuklear - 1.32.0 - public domain
+ * no warrenty implied; use at your own risk.
+ * authored from 2015-2016 by Micha Mettke
+ */
+/*
+ * ==============================================================
+ *
+ *                              API
+ *
+ * ===============================================================
+ */
+#ifndef NK_GLFW_VULKAN_H_
+#define NK_GLFW_VULKAN_H_
+
+// NUKLEAR_SHADERS_START
+// will be replaced with the real shader code
+// so we can have some ide support while editing the .in file
+#include "nuklear.h"
+
+unsigned char nuklearshaders_nuklear_vert_spv[] = {};
+unsigned int nuklearshaders_nuklear_vert_spv_len = 0;
+unsigned char nuklearshaders_nuklear_frag_spv[] = {};
+unsigned int nuklearshaders_nuklear_frag_spv_len = 0;
+// NUKLEAR_SHADERS_END
+
+#include <stddef.h>
+#include <string.h>
+#define GLFW_INCLUDE_VULKAN
+#include <GLFW/glfw3.h>
+
+enum nk_glfw_init_state { NK_GLFW3_DEFAULT = 0, NK_GLFW3_INSTALL_CALLBACKS };
+
+NK_API struct nk_context *
+nk_glfw3_init(GLFWwindow *win, VkDevice logical_device,
+              VkPhysicalDevice physical_device,
+              uint32_t graphics_queue_family_index, VkImageView *image_views,
+              uint32_t image_views_len, VkFormat color_format,
+              enum nk_glfw_init_state init_state,
+              VkDeviceSize max_vertex_buffer, VkDeviceSize max_element_buffer);
+NK_API void nk_glfw3_shutdown(void);
+NK_API void nk_glfw3_font_stash_begin(struct nk_font_atlas **atlas);
+NK_API void nk_glfw3_font_stash_end(VkQueue graphics_queue);
+NK_API void nk_glfw3_new_frame();
+NK_API VkSemaphore nk_glfw3_render(VkQueue graphics_queue,
+                                   uint32_t buffer_index,
+                                   VkSemaphore wait_semaphore,
+                                   enum nk_anti_aliasing AA);
+NK_API void nk_glfw3_resize(uint32_t framebuffer_width,
+                            uint32_t framebuffer_height);
+NK_API void nk_glfw3_device_destroy(void);
+NK_API void nk_glfw3_device_create(
+    VkDevice logical_device, VkPhysicalDevice physical_device,
+    uint32_t graphics_queue_family_index, VkImageView *image_views,
+    uint32_t image_views_len, VkFormat color_format,
+    VkDeviceSize max_vertex_buffer, VkDeviceSize max_element_buffer,
+    uint32_t framebuffer_width, uint32_t framebuffer_height);
+
+NK_API void nk_glfw3_char_callback(GLFWwindow *win, unsigned int codepoint);
+NK_API void nk_gflw3_scroll_callback(GLFWwindow *win, double xoff, double yoff);
+NK_API void nk_glfw3_mouse_button_callback(GLFWwindow *win, int button,
+                                           int action, int mods);
+
+#endif
+/*
+ * ==============================================================
+ *
+ *                          IMPLEMENTATION
+ *
+ * ===============================================================
+ */
+#ifdef NK_GLFW_VULKAN_IMPLEMENTATION
+#undef NK_GLFW_VULKAN_IMPLEMENTATION
+
+#ifndef NK_GLFW_TEXT_MAX
+#define NK_GLFW_TEXT_MAX 256
+#endif
+#ifndef NK_GLFW_DOUBLE_CLICK_LO
+#define NK_GLFW_DOUBLE_CLICK_LO 0.02
+#endif
+#ifndef NK_GLFW_DOUBLE_CLICK_HI
+#define NK_GLFW_DOUBLE_CLICK_HI 0.2
+#endif
+#ifndef NK_GLFW_MAX_TEXTURES
+#define NK_GLFW_MAX_TEXTURES 256
+#endif
+
+#define VK_COLOR_COMPONENT_MASK_RGBA                                           \
+    VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |                      \
+        VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT
+
+struct nk_glfw_vertex {
+    float position[2];
+    float uv[2];
+    nk_byte col[4];
+};
+
+struct nk_vulkan_texture_descriptor_set {
+    VkImageView image_view;
+    VkDescriptorSet descriptor_set;
+};
+
+struct nk_glfw_device {
+    struct nk_buffer cmds;
+    struct nk_draw_null_texture tex_null;
+    int max_vertex_buffer;
+    int max_element_buffer;
+    VkDevice logical_device;
+    VkPhysicalDevice physical_device;
+    VkImageView *image_views;
+    uint32_t image_views_len;
+    VkFormat color_format;
+    VkFramebuffer *framebuffers;
+    uint32_t framebuffers_len;
+    VkCommandBuffer *command_buffers;
+    uint32_t command_buffers_len;
+    VkSampler sampler;
+    VkCommandPool command_pool;
+    VkSemaphore render_completed;
+    VkBuffer vertex_buffer;
+    VkDeviceMemory vertex_memory;
+    void *mapped_vertex;
+    VkBuffer index_buffer;
+    VkDeviceMemory index_memory;
+    void *mapped_index;
+    VkBuffer uniform_buffer;
+    VkDeviceMemory uniform_memory;
+    void *mapped_uniform;
+    VkRenderPass render_pass;
+    VkDescriptorPool descriptor_pool;
+    VkDescriptorSetLayout uniform_descriptor_set_layout;
+    VkDescriptorSet uniform_descriptor_set;
+    VkDescriptorSetLayout texture_descriptor_set_layout;
+    struct nk_vulkan_texture_descriptor_set *texture_descriptor_sets;
+    uint32_t texture_descriptor_sets_len;
+    VkPipelineLayout pipeline_layout;
+    VkPipeline pipeline;
+    VkImage font_image;
+    VkImageView font_image_view;
+    VkDeviceMemory font_memory;
+};
+
+static struct nk_glfw {
+    GLFWwindow *win;
+    int width, height;
+    int display_width, display_height;
+    struct nk_glfw_device vulkan;
+    struct nk_context ctx;
+    struct nk_font_atlas atlas;
+    struct nk_vec2 fb_scale;
+    unsigned int text[NK_GLFW_TEXT_MAX];
+    int text_len;
+    struct nk_vec2 scroll;
+    double last_button_click;
+    int is_double_click_down;
+    struct nk_vec2 double_click_pos;
+} glfw;
+
+struct Mat4f {
+    float m[16];
+};
+
+NK_INTERN uint32_t nk_glfw3_find_memory_index(
+    VkPhysicalDevice physical_device, uint32_t type_filter,
+    VkMemoryPropertyFlags properties) {
+    VkPhysicalDeviceMemoryProperties mem_properties;
+    uint32_t i;
+
+    vkGetPhysicalDeviceMemoryProperties(physical_device, &mem_properties);
+    for (i = 0; i < mem_properties.memoryTypeCount; i++) {
+        if ((type_filter & (1 << i)) &&
+            (mem_properties.memoryTypes[i].propertyFlags & properties) ==
+                properties) {
+            return i;
+        }
+    }
+
+    assert(0);
+    return 0;
+}
+
+NK_INTERN void nk_glfw3_create_sampler(struct nk_glfw_device *dev) {
+    VkResult result;
+    VkSamplerCreateInfo sampler_info;
+    memset(&sampler_info, 0, sizeof(VkSamplerCreateInfo));
+
+    sampler_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
+    sampler_info.pNext = NULL;
+    sampler_info.maxAnisotropy = 1.0;
+    sampler_info.magFilter = VK_FILTER_LINEAR;
+    sampler_info.minFilter = VK_FILTER_LINEAR;
+    sampler_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+    sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+    sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+    sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+    sampler_info.mipLodBias = 0.0f;
+    sampler_info.compareEnable = VK_FALSE;
+    sampler_info.compareOp = VK_COMPARE_OP_ALWAYS;
+    sampler_info.minLod = 0.0f;
+    sampler_info.maxLod = 0.0f;
+    sampler_info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK;
+
+    result = vkCreateSampler(dev->logical_device, &sampler_info, NULL,
+                             &dev->sampler);
+    NK_ASSERT(result == VK_SUCCESS);
+}
+
+NK_INTERN void
+nk_glfw3_create_command_pool(struct nk_glfw_device *dev,
+                             uint32_t graphics_queue_family_index) {
+    VkResult result;
+    VkCommandPoolCreateInfo pool_info;
+    memset(&pool_info, 0, sizeof(VkCommandPoolCreateInfo));
+
+    pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+    pool_info.queueFamilyIndex = graphics_queue_family_index;
+    pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+    result = vkCreateCommandPool(dev->logical_device, &pool_info, NULL,
+                                 &dev->command_pool);
+    NK_ASSERT(result == VK_SUCCESS);
+}
+
+NK_INTERN void nk_glfw3_create_command_buffers(struct nk_glfw_device *dev) {
+    VkResult result;
+    VkCommandBufferAllocateInfo allocate_info;
+    memset(&allocate_info, 0, sizeof(VkCommandBufferAllocateInfo));
+
+    dev->command_buffers = (VkCommandBuffer *)malloc(dev->image_views_len *
+                                                     sizeof(VkCommandBuffer));
+    dev->command_buffers_len = dev->image_views_len;
+
+    allocate_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+    allocate_info.commandPool = dev->command_pool;
+    allocate_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+    allocate_info.commandBufferCount = dev->command_buffers_len;
+
+    result = vkAllocateCommandBuffers(dev->logical_device, &allocate_info,
+                                      dev->command_buffers);
+    NK_ASSERT(result == VK_SUCCESS);
+}
+
+NK_INTERN void nk_glfw3_create_semaphore(struct nk_glfw_device *dev) {
+    VkResult result;
+    VkSemaphoreCreateInfo semaphore_info;
+    memset(&semaphore_info, 0, sizeof(VkSemaphoreCreateInfo));
+
+    semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+    result = (vkCreateSemaphore(dev->logical_device, &semaphore_info, NULL,
+                                &dev->render_completed));
+    NK_ASSERT(result == VK_SUCCESS);
+}
+
+NK_INTERN void nk_glfw3_create_buffer_and_memory(struct nk_glfw_device *dev,
+                                                 VkBuffer *buffer,
+                                                 VkBufferUsageFlags usage,
+                                                 VkDeviceMemory *memory,
+                                                 VkDeviceSize size) {
+    VkMemoryRequirements mem_reqs;
+    VkResult result;
+    VkBufferCreateInfo buffer_info;
+    VkMemoryAllocateInfo alloc_info;
+
+    memset(&buffer_info, 0, sizeof(VkBufferCreateInfo));
+    buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+    buffer_info.size = size;
+    buffer_info.usage = usage;
+    buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+    result = vkCreateBuffer(dev->logical_device, &buffer_info, NULL, buffer);
+    NK_ASSERT(result == VK_SUCCESS);
+
+    vkGetBufferMemoryRequirements(dev->logical_device, *buffer, &mem_reqs);
+
+    memset(&alloc_info, 0, sizeof(VkMemoryAllocateInfo));
+    alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+    alloc_info.allocationSize = mem_reqs.size;
+    alloc_info.memoryTypeIndex = nk_glfw3_find_memory_index(
+        dev->physical_device, mem_reqs.memoryTypeBits,
+        VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+            VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+
+    result = vkAllocateMemory(dev->logical_device, &alloc_info, NULL, memory);
+    NK_ASSERT(result == VK_SUCCESS);
+    result = vkBindBufferMemory(dev->logical_device, *buffer, *memory, 0);
+    NK_ASSERT(result == VK_SUCCESS);
+}
+
+NK_INTERN void nk_glfw3_create_render_pass(struct nk_glfw_device *dev) {
+    VkAttachmentDescription attachment;
+    VkAttachmentReference color_reference;
+    VkSubpassDependency subpass_dependency;
+    VkSubpassDescription subpass_description;
+    VkRenderPassCreateInfo render_pass_info;
+    VkResult result;
+
+    memset(&attachment, 0, sizeof(VkAttachmentDescription));
+    attachment.format = dev->color_format;
+    attachment.samples = VK_SAMPLE_COUNT_1_BIT;
+    attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+    attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+    attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+    attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+    attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+    attachment.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+
+    memset(&color_reference, 0, sizeof(VkAttachmentReference));
+    color_reference.attachment = 0;
+    color_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+    memset(&subpass_dependency, 0, sizeof(VkSubpassDependency));
+    subpass_dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
+    subpass_dependency.srcAccessMask = 0;
+    subpass_dependency.srcStageMask =
+        VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+    subpass_dependency.dstSubpass = 0;
+    subpass_dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
+                                       VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+    subpass_dependency.dstStageMask =
+        VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+
+    memset(&subpass_description, 0, sizeof(VkSubpassDescription));
+    subpass_description.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+    subpass_description.colorAttachmentCount = 1;
+    subpass_description.pColorAttachments = &color_reference;
+
+    memset(&render_pass_info, 0, sizeof(VkRenderPassCreateInfo));
+    render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+    render_pass_info.attachmentCount = 1;
+    render_pass_info.pAttachments = &attachment;
+    render_pass_info.subpassCount = 1;
+    render_pass_info.pSubpasses = &subpass_description;
+    render_pass_info.dependencyCount = 1;
+    render_pass_info.pDependencies = &subpass_dependency;
+
+    result = vkCreateRenderPass(dev->logical_device, &render_pass_info, NULL,
+                                &dev->render_pass);
+    NK_ASSERT(result == VK_SUCCESS);
+}
+
+NK_INTERN void nk_glfw3_create_framebuffers(struct nk_glfw_device *dev,
+                                            uint32_t framebuffer_width,
+                                            uint32_t framebuffer_height) {
+
+    VkFramebufferCreateInfo framebuffer_create_info;
+    uint32_t i;
+    VkResult result;
+
+    dev->framebuffers =
+        (VkFramebuffer *)malloc(dev->image_views_len * sizeof(VkFramebuffer));
+
+    memset(&framebuffer_create_info, 0, sizeof(VkFramebufferCreateInfo));
+    framebuffer_create_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+    framebuffer_create_info.renderPass = dev->render_pass;
+    framebuffer_create_info.attachmentCount = 1;
+    framebuffer_create_info.width = framebuffer_width;
+    framebuffer_create_info.height = framebuffer_height;
+    framebuffer_create_info.layers = 1;
+    for (i = 0; i < dev->image_views_len; i++) {
+        framebuffer_create_info.pAttachments = &dev->image_views[i];
+        result =
+            vkCreateFramebuffer(dev->logical_device, &framebuffer_create_info,
+                                NULL, &dev->framebuffers[i]);
+        NK_ASSERT(result == VK_SUCCESS);
+    }
+    dev->framebuffers_len = dev->image_views_len;
+}
+
+NK_INTERN void nk_glfw3_create_descriptor_pool(struct nk_glfw_device *dev) {
+    VkDescriptorPoolSize pool_sizes[2];
+    VkDescriptorPoolCreateInfo pool_info;
+    VkResult result;
+
+    memset(&pool_sizes, 0, sizeof(VkDescriptorPoolSize) * 2);
+    pool_sizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+    pool_sizes[0].descriptorCount = 1;
+    pool_sizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+    pool_sizes[1].descriptorCount = NK_GLFW_MAX_TEXTURES;
+
+    memset(&pool_info, 0, sizeof(VkDescriptorPoolCreateInfo));
+    pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
+    pool_info.poolSizeCount = 2;
+    pool_info.pPoolSizes = pool_sizes;
+    pool_info.maxSets = 1 + NK_GLFW_MAX_TEXTURES;
+
+    result = vkCreateDescriptorPool(dev->logical_device, &pool_info, NULL,
+                                    &dev->descriptor_pool);
+    NK_ASSERT(result == VK_SUCCESS);
+}
+
+NK_INTERN void
+nk_glfw3_create_uniform_descriptor_set_layout(struct nk_glfw_device *dev) {
+    VkDescriptorSetLayoutBinding binding;
+    VkDescriptorSetLayoutCreateInfo descriptor_set_info;
+    VkResult result;
+
+    memset(&binding, 0, sizeof(VkDescriptorSetLayoutBinding));
+    binding.binding = 0;
+    binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+    binding.descriptorCount = 1;
+    binding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
+
+    memset(&descriptor_set_info, 0, sizeof(VkDescriptorSetLayoutCreateInfo));
+    descriptor_set_info.sType =
+        VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+    descriptor_set_info.bindingCount = 1;
+    descriptor_set_info.pBindings = &binding;
+
+    result =
+        vkCreateDescriptorSetLayout(dev->logical_device, &descriptor_set_info,
+                                    NULL, &dev->uniform_descriptor_set_layout);
+
+    NK_ASSERT(result == VK_SUCCESS);
+}
+
+NK_INTERN void
+nk_glfw3_create_and_update_uniform_descriptor_set(struct nk_glfw_device *dev) {
+    VkDescriptorSetAllocateInfo allocate_info;
+    VkDescriptorBufferInfo buffer_info;
+    VkWriteDescriptorSet descriptor_write;
+    VkResult result;
+
+    memset(&allocate_info, 0, sizeof(VkDescriptorSetAllocateInfo));
+    allocate_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+    allocate_info.descriptorPool = dev->descriptor_pool;
+    allocate_info.descriptorSetCount = 1;
+    allocate_info.pSetLayouts = &dev->uniform_descriptor_set_layout;
+
+    result = vkAllocateDescriptorSets(dev->logical_device, &allocate_info,
+                                      &dev->uniform_descriptor_set);
+    NK_ASSERT(result == VK_SUCCESS);
+
+    memset(&buffer_info, 0, sizeof(VkDescriptorBufferInfo));
+    buffer_info.buffer = dev->uniform_buffer;
+    buffer_info.offset = 0;
+    buffer_info.range = sizeof(struct Mat4f);
+
+    memset(&descriptor_write, 0, sizeof(VkWriteDescriptorSet));
+    descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+    descriptor_write.dstSet = dev->uniform_descriptor_set;
+    descriptor_write.dstBinding = 0;
+    descriptor_write.dstArrayElement = 0;
+    descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+    descriptor_write.descriptorCount = 1;
+    descriptor_write.pBufferInfo = &buffer_info;
+
+    vkUpdateDescriptorSets(dev->logical_device, 1, &descriptor_write, 0, NULL);
+}
+
+NK_INTERN void
+nk_glfw3_create_texture_descriptor_set_layout(struct nk_glfw_device *dev) {
+    VkDescriptorSetLayoutBinding binding;
+    VkDescriptorSetLayoutCreateInfo descriptor_set_info;
+    VkResult result;
+
+    memset(&binding, 0, sizeof(VkDescriptorSetLayoutBinding));
+    binding.binding = 0;
+    binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+    binding.descriptorCount = 1;
+    binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+
+    memset(&descriptor_set_info, 0, sizeof(VkDescriptorSetLayoutCreateInfo));
+    descriptor_set_info.sType =
+        VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+    descriptor_set_info.bindingCount = 1;
+    descriptor_set_info.pBindings = &binding;
+
+    result =
+        vkCreateDescriptorSetLayout(dev->logical_device, &descriptor_set_info,
+                                    NULL, &dev->texture_descriptor_set_layout);
+
+    NK_ASSERT(result == VK_SUCCESS);
+}
+
+NK_INTERN void
+nk_glfw3_create_texture_descriptor_sets(struct nk_glfw_device *dev) {
+    VkDescriptorSetLayout *descriptor_set_layouts;
+    VkDescriptorSet *descriptor_sets;
+    VkDescriptorSetAllocateInfo allocate_info;
+    VkResult result;
+    int i;
+
+    descriptor_set_layouts = (VkDescriptorSetLayout *)malloc(
+        NK_GLFW_MAX_TEXTURES * sizeof(VkDescriptorSetLayout));
+    descriptor_sets = (VkDescriptorSet *)malloc(NK_GLFW_MAX_TEXTURES *
+                                                sizeof(VkDescriptorSet));
+
+    dev->texture_descriptor_sets =
+        (struct nk_vulkan_texture_descriptor_set *)malloc(
+            NK_GLFW_MAX_TEXTURES *
+            sizeof(struct nk_vulkan_texture_descriptor_set));
+    dev->texture_descriptor_sets_len = 0;
+
+    for (i = 0; i < NK_GLFW_MAX_TEXTURES; i++) {
+        descriptor_set_layouts[i] = dev->texture_descriptor_set_layout;
+        descriptor_sets[i] = dev->texture_descriptor_sets[i].descriptor_set;
+    }
+
+    memset(&allocate_info, 0, sizeof(VkDescriptorSetAllocateInfo));
+    allocate_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+    allocate_info.descriptorPool = dev->descriptor_pool;
+    allocate_info.descriptorSetCount = NK_GLFW_MAX_TEXTURES;
+    allocate_info.pSetLayouts = descriptor_set_layouts;
+
+    result = vkAllocateDescriptorSets(dev->logical_device, &allocate_info,
+                                      descriptor_sets);
+    NK_ASSERT(result == VK_SUCCESS);
+
+    for (i = 0; i < NK_GLFW_MAX_TEXTURES; i++) {
+        dev->texture_descriptor_sets[i].descriptor_set = descriptor_sets[i];
+    }
+    free(descriptor_set_layouts);
+    free(descriptor_sets);
+}
+
+NK_INTERN void nk_glfw3_create_pipeline_layout(struct nk_glfw_device *dev) {
+    VkPipelineLayoutCreateInfo pipeline_layout_info;
+    VkDescriptorSetLayout descriptor_set_layouts[2];
+    VkResult result;
+
+    descriptor_set_layouts[0] = dev->uniform_descriptor_set_layout;
+    descriptor_set_layouts[1] = dev->texture_descriptor_set_layout;
+
+    memset(&pipeline_layout_info, 0, sizeof(VkPipelineLayoutCreateInfo));
+    pipeline_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+    pipeline_layout_info.setLayoutCount = 2;
+    pipeline_layout_info.pSetLayouts = descriptor_set_layouts;
+
+    result = (vkCreatePipelineLayout(dev->logical_device, &pipeline_layout_info,
+                                     NULL, &dev->pipeline_layout));
+    NK_ASSERT(result == VK_SUCCESS);
+}
+
+NK_INTERN VkPipelineShaderStageCreateInfo
+nk_glfw3_create_shader(struct nk_glfw_device *dev, unsigned char *spv_shader,
+                       uint32_t size, VkShaderStageFlagBits stage_bit) {
+    VkShaderModuleCreateInfo create_info;
+    VkPipelineShaderStageCreateInfo shader_info;
+    VkShaderModule module = NULL;
+    VkResult result;
+
+    memset(&create_info, 0, sizeof(VkShaderModuleCreateInfo));
+    create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+    create_info.codeSize = size;
+    create_info.pCode = (const uint32_t *)spv_shader;
+    result =
+        vkCreateShaderModule(dev->logical_device, &create_info, NULL, &module);
+    NK_ASSERT(result == VK_SUCCESS);
+
+    memset(&shader_info, 0, sizeof(VkPipelineShaderStageCreateInfo));
+    shader_info.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+    shader_info.stage = stage_bit;
+    shader_info.module = module;
+    shader_info.pName = "main";
+    return shader_info;
+}
+
+NK_INTERN void nk_glfw3_create_pipeline(struct nk_glfw_device *dev) {
+    VkPipelineInputAssemblyStateCreateInfo input_assembly_state;
+    VkPipelineRasterizationStateCreateInfo rasterization_state;
+    VkPipelineColorBlendAttachmentState attachment_state = {
+        VK_TRUE,
+        VK_BLEND_FACTOR_SRC_ALPHA,
+        VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
+        VK_BLEND_OP_ADD,
+        VK_BLEND_FACTOR_SRC_ALPHA,
+        VK_BLEND_FACTOR_ONE,
+        VK_BLEND_OP_ADD,
+        VK_COLOR_COMPONENT_MASK_RGBA,
+    };
+    VkPipelineColorBlendStateCreateInfo color_blend_state;
+    VkPipelineViewportStateCreateInfo viewport_state;
+    VkPipelineMultisampleStateCreateInfo multisample_state;
+    VkDynamicState dynamic_states[2] = {VK_DYNAMIC_STATE_VIEWPORT,
+                                        VK_DYNAMIC_STATE_SCISSOR};
+    VkPipelineDynamicStateCreateInfo dynamic_state;
+    VkPipelineShaderStageCreateInfo shader_stages[2];
+    VkVertexInputBindingDescription vertex_input_info;
+    VkVertexInputAttributeDescription vertex_attribute_description[3];
+    VkPipelineVertexInputStateCreateInfo vertex_input;
+    VkGraphicsPipelineCreateInfo pipeline_info;
+    VkResult result;
+
+    memset(&input_assembly_state, 0,
+           sizeof(VkPipelineInputAssemblyStateCreateInfo));
+    input_assembly_state.sType =
+        VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
+    input_assembly_state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
+    input_assembly_state.primitiveRestartEnable = VK_FALSE;
+
+    memset(&rasterization_state, 0,
+           sizeof(VkPipelineRasterizationStateCreateInfo));
+    rasterization_state.sType =
+        VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
+    rasterization_state.polygonMode = VK_POLYGON_MODE_FILL;
+    rasterization_state.cullMode = VK_CULL_MODE_NONE;
+    rasterization_state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
+    rasterization_state.lineWidth = 1.0f;
+
+    memset(&color_blend_state, 0, sizeof(VkPipelineColorBlendStateCreateInfo));
+    color_blend_state.sType =
+        VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
+    color_blend_state.attachmentCount = 1;
+    color_blend_state.pAttachments = &attachment_state;
+
+    memset(&viewport_state, 0, sizeof(VkPipelineViewportStateCreateInfo));
+    viewport_state.sType =
+        VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
+    viewport_state.viewportCount = 1;
+    viewport_state.scissorCount = 1;
+
+    memset(&multisample_state, 0, sizeof(VkPipelineMultisampleStateCreateInfo));
+    multisample_state.sType =
+        VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
+    multisample_state.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
+
+    memset(&dynamic_state, 0, sizeof(VkPipelineDynamicStateCreateInfo));
+    dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
+    dynamic_state.pDynamicStates = dynamic_states;
+    dynamic_state.dynamicStateCount = 2;
+
+    shader_stages[0] = nk_glfw3_create_shader(
+        dev, nuklearshaders_nuklear_vert_spv,
+        nuklearshaders_nuklear_vert_spv_len, VK_SHADER_STAGE_VERTEX_BIT);
+    shader_stages[1] = nk_glfw3_create_shader(
+        dev, nuklearshaders_nuklear_frag_spv,
+        nuklearshaders_nuklear_frag_spv_len, VK_SHADER_STAGE_FRAGMENT_BIT);
+
+    memset(&vertex_input_info, 0, sizeof(VkVertexInputBindingDescription));
+    vertex_input_info.binding = 0;
+    vertex_input_info.stride = sizeof(struct nk_glfw_vertex);
+    vertex_input_info.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
+
+    memset(&vertex_attribute_description, 0,
+           sizeof(VkVertexInputAttributeDescription) * 3);
+    vertex_attribute_description[0].location = 0;
+    vertex_attribute_description[0].format = VK_FORMAT_R32G32_SFLOAT;
+    vertex_attribute_description[0].offset =
+        NK_OFFSETOF(struct nk_glfw_vertex, position);
+    vertex_attribute_description[1].location = 1;
+    vertex_attribute_description[1].format = VK_FORMAT_R32G32_SFLOAT;
+    vertex_attribute_description[1].offset =
+        NK_OFFSETOF(struct nk_glfw_vertex, uv);
+    vertex_attribute_description[2].location = 2;
+    vertex_attribute_description[2].format = VK_FORMAT_R8G8B8A8_UINT;
+    vertex_attribute_description[2].offset =
+        NK_OFFSETOF(struct nk_glfw_vertex, col);
+
+    memset(&vertex_input, 0, sizeof(VkPipelineVertexInputStateCreateInfo));
+    vertex_input.sType =
+        VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
+    vertex_input.vertexBindingDescriptionCount = 1;
+    vertex_input.pVertexBindingDescriptions = &vertex_input_info;
+    vertex_input.vertexAttributeDescriptionCount = 3;
+    vertex_input.pVertexAttributeDescriptions = vertex_attribute_description;
+
+    memset(&pipeline_info, 0, sizeof(VkGraphicsPipelineCreateInfo));
+    pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
+    pipeline_info.flags = 0;
+    pipeline_info.stageCount = 2;
+    pipeline_info.pStages = shader_stages;
+    pipeline_info.pVertexInputState = &vertex_input;
+    pipeline_info.pInputAssemblyState = &input_assembly_state;
+    pipeline_info.pViewportState = &viewport_state;
+    pipeline_info.pRasterizationState = &rasterization_state;
+    pipeline_info.pMultisampleState = &multisample_state;
+    pipeline_info.pColorBlendState = &color_blend_state;
+    pipeline_info.pDynamicState = &dynamic_state;
+    pipeline_info.layout = dev->pipeline_layout;
+    pipeline_info.renderPass = dev->render_pass;
+    pipeline_info.basePipelineIndex = -1;
+    pipeline_info.basePipelineHandle = NULL;
+
+    result = vkCreateGraphicsPipelines(dev->logical_device, NULL, 1,
+                                       &pipeline_info, NULL, &dev->pipeline);
+    NK_ASSERT(result == VK_SUCCESS);
+
+    vkDestroyShaderModule(dev->logical_device, shader_stages[0].module, NULL);
+    vkDestroyShaderModule(dev->logical_device, shader_stages[1].module, NULL);
+}
+
+NK_INTERN void nk_glfw3_create_render_resources(struct nk_glfw_device *dev,
+                                                uint32_t framebuffer_width,
+                                                uint32_t framebuffer_height) {
+    nk_glfw3_create_render_pass(dev);
+    nk_glfw3_create_framebuffers(dev, framebuffer_width, framebuffer_height);
+    nk_glfw3_create_descriptor_pool(dev);
+    nk_glfw3_create_uniform_descriptor_set_layout(dev);
+    nk_glfw3_create_and_update_uniform_descriptor_set(dev);
+    nk_glfw3_create_texture_descriptor_set_layout(dev);
+    nk_glfw3_create_texture_descriptor_sets(dev);
+    nk_glfw3_create_pipeline_layout(dev);
+    nk_glfw3_create_pipeline(dev);
+}
+
+NK_API void nk_glfw3_device_create(
+    VkDevice logical_device, VkPhysicalDevice physical_device,
+    uint32_t graphics_queue_family_index, VkImageView *image_views,
+    uint32_t image_views_len, VkFormat color_format,
+    VkDeviceSize max_vertex_buffer, VkDeviceSize max_element_buffer,
+    uint32_t framebuffer_width, uint32_t framebuffer_height) {
+    struct nk_glfw_device *dev = &glfw.vulkan;
+    dev->max_vertex_buffer = max_vertex_buffer;
+    dev->max_element_buffer = max_element_buffer;
+    nk_buffer_init_default(&dev->cmds);
+    dev->logical_device = logical_device;
+    dev->physical_device = physical_device;
+    dev->image_views = image_views;
+    dev->image_views_len = image_views_len;
+    dev->color_format = color_format;
+    dev->framebuffers = NULL;
+    dev->framebuffers_len = 0;
+
+    nk_glfw3_create_sampler(dev);
+    nk_glfw3_create_command_pool(dev, graphics_queue_family_index);
+    nk_glfw3_create_command_buffers(dev);
+    nk_glfw3_create_semaphore(dev);
+
+    nk_glfw3_create_buffer_and_memory(dev, &dev->vertex_buffer,
+                                      VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
+                                      &dev->vertex_memory, max_vertex_buffer);
+    nk_glfw3_create_buffer_and_memory(dev, &dev->index_buffer,
+                                      VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
+                                      &dev->index_memory, max_element_buffer);
+    nk_glfw3_create_buffer_and_memory(
+        dev, &dev->uniform_buffer, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+        &dev->uniform_memory, sizeof(struct Mat4f));
+
+    vkMapMemory(dev->logical_device, dev->vertex_memory, 0, max_vertex_buffer,
+                0, &dev->mapped_vertex);
+    vkMapMemory(dev->logical_device, dev->index_memory, 0, max_element_buffer,
+                0, &dev->mapped_index);
+    vkMapMemory(dev->logical_device, dev->uniform_memory, 0,
+                sizeof(struct Mat4f), 0, &dev->mapped_uniform);
+
+    nk_glfw3_create_render_resources(dev, framebuffer_width,
+                                     framebuffer_height);
+}
+
+NK_INTERN void nk_glfw3_device_upload_atlas(VkQueue graphics_queue,
+                                            const void *image, int width,
+                                            int height) {
+    struct nk_glfw_device *dev = &glfw.vulkan;
+
+    VkImageCreateInfo image_info;
+    VkResult result;
+    VkMemoryRequirements mem_reqs;
+    VkMemoryAllocateInfo alloc_info;
+    VkBufferCreateInfo buffer_info;
+    uint8_t *data = 0;
+    VkCommandBufferBeginInfo begin_info;
+    VkCommandBuffer command_buffer;
+    VkImageMemoryBarrier image_memory_barrier;
+    VkBufferImageCopy buffer_copy_region;
+    VkImageMemoryBarrier image_shader_memory_barrier;
+    VkFence fence;
+    VkFenceCreateInfo fence_create;
+    VkSubmitInfo submit_info;
+    VkImageViewCreateInfo image_view_info;
+    struct {
+        VkDeviceMemory memory;
+        VkBuffer buffer;
+    } staging_buffer;
+
+    memset(&image_info, 0, sizeof(VkImageCreateInfo));
+    image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+    image_info.imageType = VK_IMAGE_TYPE_2D;
+    image_info.format = VK_FORMAT_R8G8B8A8_UNORM;
+    image_info.extent.width = (uint32_t)width;
+    image_info.extent.height = (uint32_t)height;
+    image_info.extent.depth = 1;
+    image_info.mipLevels = 1;
+    image_info.arrayLayers = 1;
+    image_info.samples = VK_SAMPLE_COUNT_1_BIT;
+    image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
+    image_info.usage =
+        VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
+    image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+    image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+
+    result =
+        vkCreateImage(dev->logical_device, &image_info, NULL, &dev->font_image);
+    NK_ASSERT(result == VK_SUCCESS);
+
+    vkGetImageMemoryRequirements(dev->logical_device, dev->font_image,
+                                 &mem_reqs);
+
+    memset(&alloc_info, 0, sizeof(VkMemoryAllocateInfo));
+    alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+    alloc_info.allocationSize = mem_reqs.size;
+    alloc_info.memoryTypeIndex = nk_glfw3_find_memory_index(
+        dev->physical_device, mem_reqs.memoryTypeBits,
+        VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+
+    result = vkAllocateMemory(dev->logical_device, &alloc_info, NULL,
+                              &dev->font_memory);
+    NK_ASSERT(result == VK_SUCCESS);
+    result = vkBindImageMemory(dev->logical_device, dev->font_image,
+                               dev->font_memory, 0);
+    NK_ASSERT(result == VK_SUCCESS);
+
+    memset(&buffer_info, 0, sizeof(VkBufferCreateInfo));
+    buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+    buffer_info.size = alloc_info.allocationSize;
+    buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+    buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+    result = vkCreateBuffer(dev->logical_device, &buffer_info, NULL,
+                            &staging_buffer.buffer);
+    NK_ASSERT(result == VK_SUCCESS);
+    vkGetBufferMemoryRequirements(dev->logical_device, staging_buffer.buffer,
+                                  &mem_reqs);
+
+    alloc_info.allocationSize = mem_reqs.size;
+    alloc_info.memoryTypeIndex = nk_glfw3_find_memory_index(
+        dev->physical_device, mem_reqs.memoryTypeBits,
+        VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+            VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+
+    result = vkAllocateMemory(dev->logical_device, &alloc_info, NULL,
+                              &staging_buffer.memory);
+    NK_ASSERT(result == VK_SUCCESS);
+    result = vkBindBufferMemory(dev->logical_device, staging_buffer.buffer,
+                                staging_buffer.memory, 0);
+    NK_ASSERT(result == VK_SUCCESS);
+
+    result = vkMapMemory(dev->logical_device, staging_buffer.memory, 0,
+                         alloc_info.allocationSize, 0, (void **)&data);
+    NK_ASSERT(result == VK_SUCCESS);
+    memcpy(data, image, width * height * 4);
+    vkUnmapMemory(dev->logical_device, staging_buffer.memory);
+
+    memset(&begin_info, 0, sizeof(VkCommandBufferBeginInfo));
+    begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+
+    NK_ASSERT(dev->command_buffers_len > 0);
+    /*
+    use the same command buffer as for render as we are regenerating the
+    buffer during render anyway
+    */
+    command_buffer = dev->command_buffers[0];
+    result = vkBeginCommandBuffer(command_buffer, &begin_info);
+    NK_ASSERT(result == VK_SUCCESS);
+
+    memset(&image_memory_barrier, 0, sizeof(VkImageMemoryBarrier));
+    image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+    image_memory_barrier.image = dev->font_image;
+    image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    image_memory_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+    image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+    image_memory_barrier.subresourceRange.aspectMask =
+        VK_IMAGE_ASPECT_COLOR_BIT;
+    image_memory_barrier.subresourceRange.levelCount = 1;
+    image_memory_barrier.subresourceRange.layerCount = 1;
+    image_memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+
+    vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+                         VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1,
+                         &image_memory_barrier);
+
+    memset(&buffer_copy_region, 0, sizeof(VkBufferImageCopy));
+    buffer_copy_region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    buffer_copy_region.imageSubresource.layerCount = 1;
+    buffer_copy_region.imageExtent.width = (uint32_t)width;
+    buffer_copy_region.imageExtent.height = (uint32_t)height;
+    buffer_copy_region.imageExtent.depth = 1;
+
+    vkCmdCopyBufferToImage(
+        command_buffer, staging_buffer.buffer, dev->font_image,
+        VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &buffer_copy_region);
+
+    memset(&image_shader_memory_barrier, 0, sizeof(VkImageMemoryBarrier));
+    image_shader_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+    image_shader_memory_barrier.image = dev->font_image;
+    image_shader_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    image_shader_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    image_shader_memory_barrier.oldLayout =
+        VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+    image_shader_memory_barrier.newLayout =
+        VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+    image_shader_memory_barrier.subresourceRange.aspectMask =
+        VK_IMAGE_ASPECT_COLOR_BIT;
+    image_shader_memory_barrier.subresourceRange.levelCount = 1;
+    image_shader_memory_barrier.subresourceRange.layerCount = 1;
+    image_shader_memory_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+    image_shader_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
+
+    vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
+                         VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, 0,
+                         NULL, 1, &image_shader_memory_barrier);
+
+    result = vkEndCommandBuffer(command_buffer);
+    NK_ASSERT(result == VK_SUCCESS);
+
+    memset(&fence_create, 0, sizeof(VkFenceCreateInfo));
+    fence_create.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+
+    result = vkCreateFence(dev->logical_device, &fence_create, NULL, &fence);
+    NK_ASSERT(result == VK_SUCCESS);
+
+    memset(&submit_info, 0, sizeof(VkSubmitInfo));
+    submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+    submit_info.commandBufferCount = 1;
+    submit_info.pCommandBuffers = &command_buffer;
+
+    result = vkQueueSubmit(graphics_queue, 1, &submit_info, fence);
+    NK_ASSERT(result == VK_SUCCESS);
+    result =
+        vkWaitForFences(dev->logical_device, 1, &fence, VK_TRUE, UINT64_MAX);
+    NK_ASSERT(result == VK_SUCCESS);
+
+    vkDestroyFence(dev->logical_device, fence, NULL);
+
+    vkFreeMemory(dev->logical_device, staging_buffer.memory, NULL);
+    vkDestroyBuffer(dev->logical_device, staging_buffer.buffer, NULL);
+
+    memset(&image_view_info, 0, sizeof(VkImageViewCreateInfo));
+    image_view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+    image_view_info.image = dev->font_image;
+    image_view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
+    image_view_info.format = image_info.format;
+    image_view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    image_view_info.subresourceRange.layerCount = 1;
+    image_view_info.subresourceRange.levelCount = 1;
+
+    result = vkCreateImageView(dev->logical_device, &image_view_info, NULL,
+                               &dev->font_image_view);
+    NK_ASSERT(result == VK_SUCCESS);
+}
+
+NK_INTERN void nk_glfw3_destroy_render_resources(struct nk_glfw_device *dev) {
+    uint32_t i;
+
+    vkDestroyPipeline(dev->logical_device, dev->pipeline, NULL);
+    vkDestroyPipelineLayout(dev->logical_device, dev->pipeline_layout, NULL);
+    vkDestroyDescriptorSetLayout(dev->logical_device,
+                                 dev->texture_descriptor_set_layout, NULL);
+    vkDestroyDescriptorSetLayout(dev->logical_device,
+                                 dev->uniform_descriptor_set_layout, NULL);
+    vkDestroyDescriptorPool(dev->logical_device, dev->descriptor_pool, NULL);
+    for (i = 0; i < dev->framebuffers_len; i++) {
+        vkDestroyFramebuffer(dev->logical_device, dev->framebuffers[i], NULL);
+    }
+    free(dev->framebuffers);
+    dev->framebuffers_len = 0;
+    free(dev->texture_descriptor_sets);
+    dev->texture_descriptor_sets_len = 0;
+    vkDestroyRenderPass(dev->logical_device, dev->render_pass, NULL);
+}
+
+NK_API void nk_glfw3_resize(uint32_t framebuffer_width,
+                            uint32_t framebuffer_height) {
+    struct nk_glfw_device *dev = &glfw.vulkan;
+    glfwGetWindowSize(glfw.win, &glfw.width, &glfw.height);
+    glfwGetFramebufferSize(glfw.win, &glfw.display_width, &glfw.display_height);
+    glfw.fb_scale.x = (float)glfw.display_width / (float)glfw.width;
+    glfw.fb_scale.y = (float)glfw.display_height / (float)glfw.height;
+
+    nk_glfw3_destroy_render_resources(dev);
+    nk_glfw3_create_render_resources(dev, framebuffer_width,
+                                     framebuffer_height);
+}
+
+NK_API void nk_glfw3_device_destroy(void) {
+    struct nk_glfw_device *dev = &glfw.vulkan;
+
+    vkDeviceWaitIdle(dev->logical_device);
+
+    nk_glfw3_destroy_render_resources(dev);
+
+    vkFreeCommandBuffers(dev->logical_device, dev->command_pool,
+                         dev->command_buffers_len, dev->command_buffers);
+    vkDestroyCommandPool(dev->logical_device, dev->command_pool, NULL);
+    vkDestroySemaphore(dev->logical_device, dev->render_completed, NULL);
+
+    vkUnmapMemory(dev->logical_device, dev->vertex_memory);
+    vkUnmapMemory(dev->logical_device, dev->index_memory);
+    vkUnmapMemory(dev->logical_device, dev->uniform_memory);
+
+    vkFreeMemory(dev->logical_device, dev->vertex_memory, NULL);
+    vkFreeMemory(dev->logical_device, dev->index_memory, NULL);
+    vkFreeMemory(dev->logical_device, dev->uniform_memory, NULL);
+
+    vkDestroyBuffer(dev->logical_device, dev->vertex_buffer, NULL);
+    vkDestroyBuffer(dev->logical_device, dev->index_buffer, NULL);
+    vkDestroyBuffer(dev->logical_device, dev->uniform_buffer, NULL);
+
+    vkDestroySampler(dev->logical_device, dev->sampler, NULL);
+
+    vkFreeMemory(dev->logical_device, dev->font_memory, NULL);
+    vkDestroyImage(dev->logical_device, dev->font_image, NULL);
+    vkDestroyImageView(dev->logical_device, dev->font_image_view, NULL);
+
+    free(dev->command_buffers);
+    nk_buffer_free(&dev->cmds);
+}
+
+NK_API
+void nk_glfw3_shutdown(void) {
+    nk_font_atlas_clear(&glfw.atlas);
+    nk_free(&glfw.ctx);
+    nk_glfw3_device_destroy();
+    memset(&glfw, 0, sizeof(glfw));
+}
+
+NK_API void nk_glfw3_font_stash_begin(struct nk_font_atlas **atlas) {
+    nk_font_atlas_init_default(&glfw.atlas);
+    nk_font_atlas_begin(&glfw.atlas);
+    *atlas = &glfw.atlas;
+}
+
+NK_API void nk_glfw3_font_stash_end(VkQueue graphics_queue) {
+    struct nk_glfw_device *dev = &glfw.vulkan;
+
+    const void *image;
+    int w, h;
+    image = nk_font_atlas_bake(&glfw.atlas, &w, &h, NK_FONT_ATLAS_RGBA32);
+    nk_glfw3_device_upload_atlas(graphics_queue, image, w, h);
+    nk_font_atlas_end(&glfw.atlas, nk_handle_ptr(dev->font_image_view),
+                      &dev->tex_null);
+    if (glfw.atlas.default_font) {
+        nk_style_set_font(&glfw.ctx, &glfw.atlas.default_font->handle);
+    }
+}
+
+NK_API void nk_glfw3_new_frame(void) {
+    int i;
+    double x, y;
+    struct nk_context *ctx = &glfw.ctx;
+    struct GLFWwindow *win = glfw.win;
+
+    nk_input_begin(ctx);
+    for (i = 0; i < glfw.text_len; ++i)
+        nk_input_unicode(ctx, glfw.text[i]);
+
+#ifdef NK_GLFW_GL4_MOUSE_GRABBING
+    /* optional grabbing behavior */
+    if (ctx->input.mouse.grab)
+        glfwSetInputMode(glfw.win, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
+    else if (ctx->input.mouse.ungrab)
+        glfwSetInputMode(glfw.win, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
+#endif
+
+    nk_input_key(ctx, NK_KEY_DEL,
+                 glfwGetKey(win, GLFW_KEY_DELETE) == GLFW_PRESS);
+    nk_input_key(ctx, NK_KEY_ENTER,
+                 glfwGetKey(win, GLFW_KEY_ENTER) == GLFW_PRESS);
+    nk_input_key(ctx, NK_KEY_TAB, glfwGetKey(win, GLFW_KEY_TAB) == GLFW_PRESS);
+    nk_input_key(ctx, NK_KEY_BACKSPACE,
+                 glfwGetKey(win, GLFW_KEY_BACKSPACE) == GLFW_PRESS);
+    nk_input_key(ctx, NK_KEY_UP, glfwGetKey(win, GLFW_KEY_UP) == GLFW_PRESS);
+    nk_input_key(ctx, NK_KEY_DOWN,
+                 glfwGetKey(win, GLFW_KEY_DOWN) == GLFW_PRESS);
+    nk_input_key(ctx, NK_KEY_TEXT_START,
+                 glfwGetKey(win, GLFW_KEY_HOME) == GLFW_PRESS);
+    nk_input_key(ctx, NK_KEY_TEXT_END,
+                 glfwGetKey(win, GLFW_KEY_END) == GLFW_PRESS);
+    nk_input_key(ctx, NK_KEY_SCROLL_START,
+                 glfwGetKey(win, GLFW_KEY_HOME) == GLFW_PRESS);
+    nk_input_key(ctx, NK_KEY_SCROLL_END,
+                 glfwGetKey(win, GLFW_KEY_END) == GLFW_PRESS);
+    nk_input_key(ctx, NK_KEY_SCROLL_DOWN,
+                 glfwGetKey(win, GLFW_KEY_PAGE_DOWN) == GLFW_PRESS);
+    nk_input_key(ctx, NK_KEY_SCROLL_UP,
+                 glfwGetKey(win, GLFW_KEY_PAGE_UP) == GLFW_PRESS);
+    nk_input_key(ctx, NK_KEY_SHIFT,
+                 glfwGetKey(win, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS ||
+                     glfwGetKey(win, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS);
+
+    if (glfwGetKey(win, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS ||
+        glfwGetKey(win, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS) {
+        nk_input_key(ctx, NK_KEY_COPY,
+                     glfwGetKey(win, GLFW_KEY_C) == GLFW_PRESS);
+        nk_input_key(ctx, NK_KEY_PASTE,
+                     glfwGetKey(win, GLFW_KEY_V) == GLFW_PRESS);
+        nk_input_key(ctx, NK_KEY_CUT,
+                     glfwGetKey(win, GLFW_KEY_X) == GLFW_PRESS);
+        nk_input_key(ctx, NK_KEY_TEXT_UNDO,
+                     glfwGetKey(win, GLFW_KEY_Z) == GLFW_PRESS);
+        nk_input_key(ctx, NK_KEY_TEXT_REDO,
+                     glfwGetKey(win, GLFW_KEY_R) == GLFW_PRESS);
+        nk_input_key(ctx, NK_KEY_TEXT_WORD_LEFT,
+                     glfwGetKey(win, GLFW_KEY_LEFT) == GLFW_PRESS);
+        nk_input_key(ctx, NK_KEY_TEXT_WORD_RIGHT,
+                     glfwGetKey(win, GLFW_KEY_RIGHT) == GLFW_PRESS);
+        nk_input_key(ctx, NK_KEY_TEXT_LINE_START,
+                     glfwGetKey(win, GLFW_KEY_B) == GLFW_PRESS);
+        nk_input_key(ctx, NK_KEY_TEXT_LINE_END,
+                     glfwGetKey(win, GLFW_KEY_E) == GLFW_PRESS);
+        nk_input_key(ctx, NK_KEY_TEXT_SELECT_ALL,
+                     glfwGetKey(win, GLFW_KEY_A) == GLFW_PRESS);
+    } else {
+        nk_input_key(ctx, NK_KEY_LEFT,
+                     glfwGetKey(win, GLFW_KEY_LEFT) == GLFW_PRESS);
+        nk_input_key(ctx, NK_KEY_RIGHT,
+                     glfwGetKey(win, GLFW_KEY_RIGHT) == GLFW_PRESS);
+        nk_input_key(ctx, NK_KEY_COPY, 0);
+        nk_input_key(ctx, NK_KEY_PASTE, 0);
+        nk_input_key(ctx, NK_KEY_CUT, 0);
+        nk_input_key(ctx, NK_KEY_SHIFT, 0);
+    }
+
+    glfwGetCursorPos(win, &x, &y);
+    nk_input_motion(ctx, (int)x, (int)y);
+#ifdef NK_GLFW_GL4_MOUSE_GRABBING
+    if (ctx->input.mouse.grabbed) {
+        glfwSetCursorPos(glfw.win, ctx->input.mouse.prev.x,
+                         ctx->input.mouse.prev.y);
+        ctx->input.mouse.pos.x = ctx->input.mouse.prev.x;
+        ctx->input.mouse.pos.y = ctx->input.mouse.prev.y;
+    }
+#endif
+    nk_input_button(ctx, NK_BUTTON_LEFT, (int)x, (int)y,
+                    glfwGetMouseButton(win, GLFW_MOUSE_BUTTON_LEFT) ==
+                        GLFW_PRESS);
+    nk_input_button(ctx, NK_BUTTON_MIDDLE, (int)x, (int)y,
+                    glfwGetMouseButton(win, GLFW_MOUSE_BUTTON_MIDDLE) ==
+                        GLFW_PRESS);
+    nk_input_button(ctx, NK_BUTTON_RIGHT, (int)x, (int)y,
+                    glfwGetMouseButton(win, GLFW_MOUSE_BUTTON_RIGHT) ==
+                        GLFW_PRESS);
+    nk_input_button(ctx, NK_BUTTON_DOUBLE, (int)glfw.double_click_pos.x,
+                    (int)glfw.double_click_pos.y, glfw.is_double_click_down);
+    nk_input_scroll(ctx, glfw.scroll);
+    nk_input_end(&glfw.ctx);
+    glfw.text_len = 0;
+    glfw.scroll = nk_vec2(0, 0);
+}
+
+NK_INTERN void update_texture_descriptor_set(
+    struct nk_glfw_device *dev,
+    struct nk_vulkan_texture_descriptor_set *texture_descriptor_set,
+    VkImageView image_view) {
+    VkDescriptorImageInfo descriptor_image_info;
+    VkWriteDescriptorSet descriptor_write;
+
+    texture_descriptor_set->image_view = image_view;
+
+    memset(&descriptor_image_info, 0, sizeof(VkDescriptorImageInfo));
+    descriptor_image_info.sampler = dev->sampler;
+    descriptor_image_info.imageView = texture_descriptor_set->image_view;
+    descriptor_image_info.imageLayout =
+        VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+
+    memset(&descriptor_write, 0, sizeof(VkWriteDescriptorSet));
+    descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+    descriptor_write.dstSet = texture_descriptor_set->descriptor_set;
+    descriptor_write.dstBinding = 0;
+    descriptor_write.dstArrayElement = 0;
+    descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+    descriptor_write.descriptorCount = 1;
+    descriptor_write.pImageInfo = &descriptor_image_info;
+
+    vkUpdateDescriptorSets(dev->logical_device, 1, &descriptor_write, 0, NULL);
+}
+
+NK_API
+VkSemaphore nk_glfw3_render(VkQueue graphics_queue, uint32_t buffer_index,
+                            VkSemaphore wait_semaphore,
+                            enum nk_anti_aliasing AA) {
+    struct nk_glfw_device *dev = &glfw.vulkan;
+    struct nk_buffer vbuf, ebuf;
+
+    struct Mat4f projection = {
+        {2.0f, 0.0f, 0.0f, 0.0f, 0.0f, -2.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f,
+         0.0f, -1.0f, 1.0f, 0.0f, 1.0f},
+    };
+
+    VkCommandBufferBeginInfo begin_info;
+    VkClearValue clear_value = {{{0.0f, 0.0f, 0.0f, 0.0f}}};
+    VkRenderPassBeginInfo render_pass_begin_nfo;
+    VkCommandBuffer command_buffer;
+    VkResult result;
+    VkViewport viewport;
+
+    VkDeviceSize doffset = 0;
+    VkImageView current_texture = NULL;
+    uint32_t index_offset = 0;
+    VkRect2D scissor;
+    uint32_t wait_semaphore_count;
+    VkSemaphore *wait_semaphores;
+    VkPipelineStageFlags wait_stage =
+        VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+    VkSubmitInfo submit_info;
+
+    projection.m[0] /= glfw.width;
+    projection.m[5] /= glfw.height;
+
+    memcpy(dev->mapped_uniform, &projection, sizeof(projection));
+
+    memset(&begin_info, 0, sizeof(VkCommandBufferBeginInfo));
+    begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+
+    memset(&render_pass_begin_nfo, 0, sizeof(VkRenderPassBeginInfo));
+    render_pass_begin_nfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
+    render_pass_begin_nfo.renderPass = dev->render_pass;
+    render_pass_begin_nfo.renderArea.extent.width = (uint32_t)glfw.width;
+    render_pass_begin_nfo.renderArea.extent.height = (uint32_t)glfw.height;
+    render_pass_begin_nfo.clearValueCount = 1;
+    render_pass_begin_nfo.pClearValues = &clear_value;
+    render_pass_begin_nfo.framebuffer = dev->framebuffers[buffer_index];
+
+    command_buffer = dev->command_buffers[buffer_index];
+
+    result = vkBeginCommandBuffer(command_buffer, &begin_info);
+    NK_ASSERT(result == VK_SUCCESS);
+    vkCmdBeginRenderPass(command_buffer, &render_pass_begin_nfo,
+                         VK_SUBPASS_CONTENTS_INLINE);
+
+    memset(&viewport, 0, sizeof(VkViewport));
+    viewport.width = (float)glfw.width;
+    viewport.height = (float)glfw.height;
+    viewport.maxDepth = 1.0f;
+    vkCmdSetViewport(command_buffer, 0, 1, &viewport);
+
+    vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
+                      dev->pipeline);
+    vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
+                            dev->pipeline_layout, 0, 1,
+                            &dev->uniform_descriptor_set, 0, NULL);
+    {
+        /* convert from command queue into draw list and draw to screen */
+        const struct nk_draw_command *cmd;
+        /* load draw vertices & elements directly into vertex + element buffer
+         */
+        {
+            /* fill convert configuration */
+            struct nk_convert_config config;
+            static const struct nk_draw_vertex_layout_element vertex_layout[] =
+                {{NK_VERTEX_POSITION, NK_FORMAT_FLOAT,
+                  NK_OFFSETOF(struct nk_glfw_vertex, position)},
+                 {NK_VERTEX_TEXCOORD, NK_FORMAT_FLOAT,
+                  NK_OFFSETOF(struct nk_glfw_vertex, uv)},
+                 {NK_VERTEX_COLOR, NK_FORMAT_R8G8B8A8,
+                  NK_OFFSETOF(struct nk_glfw_vertex, col)},
+                 {NK_VERTEX_LAYOUT_END}};
+            NK_MEMSET(&config, 0, sizeof(config));
+            config.vertex_layout = vertex_layout;
+            config.vertex_size = sizeof(struct nk_glfw_vertex);
+            config.vertex_alignment = NK_ALIGNOF(struct nk_glfw_vertex);
+            config.tex_null = dev->tex_null;
+            config.circle_segment_count = 22;
+            config.curve_segment_count = 22;
+            config.arc_segment_count = 22;
+            config.global_alpha = 1.0f;
+            config.shape_AA = AA;
+            config.line_AA = AA;
+
+            /* setup buffers to load vertices and elements */
+            nk_buffer_init_fixed(&vbuf, dev->mapped_vertex,
+                                 (size_t)dev->max_vertex_buffer);
+            nk_buffer_init_fixed(&ebuf, dev->mapped_index,
+                                 (size_t)dev->max_element_buffer);
+            nk_convert(&glfw.ctx, &dev->cmds, &vbuf, &ebuf, &config);
+        }
+
+        /* iterate over and execute each draw command */
+
+        vkCmdBindVertexBuffers(command_buffer, 0, 1, &dev->vertex_buffer,
+                               &doffset);
+        vkCmdBindIndexBuffer(command_buffer, dev->index_buffer, 0,
+                             VK_INDEX_TYPE_UINT16);
+
+        nk_draw_foreach(cmd, &glfw.ctx, &dev->cmds) {
+            if (!cmd->texture.ptr) {
+                continue;
+            }
+            if (cmd->texture.ptr && cmd->texture.ptr != current_texture) {
+                int found = 0;
+                uint32_t i;
+                for (i = 0; i < dev->texture_descriptor_sets_len; i++) {
+                    if (dev->texture_descriptor_sets[i].image_view ==
+                        cmd->texture.ptr) {
+                        found = 1;
+                        break;
+                    }
+                }
+
+                if (!found) {
+                    update_texture_descriptor_set(
+                        dev, &dev->texture_descriptor_sets[i],
+                        (VkImageView)cmd->texture.ptr);
+                    dev->texture_descriptor_sets_len++;
+                }
+                vkCmdBindDescriptorSets(
+                    command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
+                    dev->pipeline_layout, 1, 1,
+                    &dev->texture_descriptor_sets[i].descriptor_set, 0, NULL);
+            }
+
+            if (!cmd->elem_count)
+                continue;
+
+            scissor.offset.x = (int32_t)(NK_MAX(cmd->clip_rect.x, 0.f));
+            scissor.offset.y = (int32_t)(NK_MAX(cmd->clip_rect.y, 0.f));
+            scissor.extent.width = (uint32_t)(cmd->clip_rect.w);
+            scissor.extent.height = (uint32_t)(cmd->clip_rect.h);
+            vkCmdSetScissor(command_buffer, 0, 1, &scissor);
+            vkCmdDrawIndexed(command_buffer, cmd->elem_count, 1, index_offset,
+                             0, 0);
+            index_offset += cmd->elem_count;
+        }
+        nk_clear(&glfw.ctx);
+    }
+
+    vkCmdEndRenderPass(command_buffer);
+    result = vkEndCommandBuffer(command_buffer);
+    NK_ASSERT(result == VK_SUCCESS);
+
+    if (wait_semaphore) {
+        wait_semaphore_count = 1;
+        wait_semaphores = &wait_semaphore;
+    } else {
+        wait_semaphore_count = 0;
+        wait_semaphores = NULL;
+    }
+
+    memset(&submit_info, 0, sizeof(VkSubmitInfo));
+    submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+    submit_info.commandBufferCount = 1;
+    submit_info.pCommandBuffers = &command_buffer;
+    submit_info.pWaitDstStageMask = &wait_stage;
+    submit_info.waitSemaphoreCount = wait_semaphore_count;
+    submit_info.pWaitSemaphores = wait_semaphores;
+    submit_info.signalSemaphoreCount = 1;
+    submit_info.pSignalSemaphores = &dev->render_completed;
+
+    result = vkQueueSubmit(graphics_queue, 1, &submit_info, NULL);
+    NK_ASSERT(result == VK_SUCCESS);
+
+    return dev->render_completed;
+}
+
+NK_API void nk_glfw3_char_callback(GLFWwindow *win, unsigned int codepoint) {
+    (void)win;
+    if (glfw.text_len < NK_GLFW_TEXT_MAX)
+        glfw.text[glfw.text_len++] = codepoint;
+}
+
+NK_API void nk_gflw3_scroll_callback(GLFWwindow *win, double xoff,
+                                     double yoff) {
+    (void)win;
+    (void)xoff;
+    glfw.scroll.x += (float)xoff;
+    glfw.scroll.y += (float)yoff;
+}
+
+NK_API void nk_glfw3_mouse_button_callback(GLFWwindow *window, int button,
+                                           int action, int mods) {
+    double x, y;
+    NK_UNUSED(mods);
+    if (button != GLFW_MOUSE_BUTTON_LEFT)
+        return;
+    glfwGetCursorPos(window, &x, &y);
+    if (action == GLFW_PRESS) {
+        double dt = glfwGetTime() - glfw.last_button_click;
+        if (dt > NK_GLFW_DOUBLE_CLICK_LO && dt < NK_GLFW_DOUBLE_CLICK_HI) {
+            glfw.is_double_click_down = nk_true;
+            glfw.double_click_pos = nk_vec2((float)x, (float)y);
+        }
+        glfw.last_button_click = glfwGetTime();
+    } else
+        glfw.is_double_click_down = nk_false;
+}
+
+NK_INTERN void nk_glfw3_clipboard_paste(nk_handle usr,
+                                        struct nk_text_edit *edit) {
+    const char *text = glfwGetClipboardString(glfw.win);
+    if (text)
+        nk_textedit_paste(edit, text, nk_strlen(text));
+    (void)usr;
+}
+
+NK_INTERN void nk_glfw3_clipboard_copy(nk_handle usr, const char *text,
+                                       int len) {
+    char *str = 0;
+    (void)usr;
+    if (!len)
+        return;
+    str = (char *)malloc((size_t)len + 1);
+    if (!str)
+        return;
+    memcpy(str, text, (size_t)len);
+    str[len] = '\0';
+    glfwSetClipboardString(glfw.win, str);
+    free(str);
+}
+
+NK_API struct nk_context *
+nk_glfw3_init(GLFWwindow *win, VkDevice logical_device,
+              VkPhysicalDevice physical_device,
+              uint32_t graphics_queue_family_index, VkImageView *image_views,
+              uint32_t image_views_len, VkFormat color_format,
+              enum nk_glfw_init_state init_state,
+              VkDeviceSize max_vertex_buffer, VkDeviceSize max_element_buffer) {
+    memset(&glfw, 0, sizeof(struct nk_glfw));
+    glfw.win = win;
+    if (init_state == NK_GLFW3_INSTALL_CALLBACKS) {
+        glfwSetScrollCallback(win, nk_gflw3_scroll_callback);
+        glfwSetCharCallback(win, nk_glfw3_char_callback);
+        glfwSetMouseButtonCallback(win, nk_glfw3_mouse_button_callback);
+    }
+    nk_init_default(&glfw.ctx, 0);
+    glfw.ctx.clip.copy = nk_glfw3_clipboard_copy;
+    glfw.ctx.clip.paste = nk_glfw3_clipboard_paste;
+    glfw.ctx.clip.userdata = nk_handle_ptr(0);
+    glfw.last_button_click = 0;
+
+    glfwGetWindowSize(win, &glfw.width, &glfw.height);
+    glfwGetFramebufferSize(win, &glfw.display_width, &glfw.display_height);
+
+    nk_glfw3_device_create(logical_device, physical_device,
+                           graphics_queue_family_index, image_views,
+                           image_views_len, color_format, max_vertex_buffer,
+                           max_element_buffer, (uint32_t)glfw.display_width,
+                           (uint32_t)glfw.display_height);
+
+    glfw.is_double_click_down = nk_false;
+    glfw.double_click_pos = nk_vec2(0, 0);
+
+    return &glfw.ctx;
+}
+
+#endif

+ 13 - 0
demo/glfw_vulkan/src/nuklearshaders/nuklear.frag

@@ -0,0 +1,13 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(binding = 0, set = 1) uniform sampler2D currentTexture;
+
+layout(location = 0) in vec4 fragColor;
+layout(location = 1) in vec2 fragUv;
+layout(location = 0) out vec4 outColor;
+
+void main() {
+    vec4 texColor = texture(currentTexture, fragUv);
+    outColor = fragColor * texColor;
+}

+ 23 - 0
demo/glfw_vulkan/src/nuklearshaders/nuklear.vert

@@ -0,0 +1,23 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+out gl_PerVertex {
+    vec4 gl_Position;
+};
+
+layout(binding = 0) uniform UniformBufferObject {
+    mat4 projection;
+} ubo;
+
+layout(location = 0) in vec2 position;
+layout(location = 1) in vec2 uv;
+layout(location = 2) in uvec4 color;
+layout(location = 0) out vec4 fragColor;
+layout(location = 1) out vec2 fragUv;
+
+void main() {
+    gl_Position = ubo.projection * vec4(position, 0.0, 1.0);
+    gl_Position.y = -gl_Position.y;
+    fragColor = vec4(color[0]/255.0, color[1]/255.0, color[2]/255.0, color[3]/255.0);
+    fragUv = uv;
+}