From 58703de05c2f86cc19c2b76b040e412c532c3664 Mon Sep 17 00:00:00 2001
From: Hans-Kristian Arntzen <maister@archlinux.us>
Date: Sun, 26 Jun 2016 20:58:28 +0200
Subject: [PATCH] Vulkan: Add initial async compute.

Still not really async. Need to use multiple queues.
---
 .../Makefile                                  |  62 ++
 .../libretro-test.c                           | 618 ++++++++++++++++++
 .../libretro-test-vulkan-async-compute/link.T |   5 +
 .../shaders/Makefile                          |  15 +
 .../shaders/raymarch.comp                     |  18 +
 .../shaders/raymarch.comp.inc                 | 117 ++++
 6 files changed, 835 insertions(+)
 create mode 100644 cores/libretro-test-vulkan-async-compute/Makefile
 create mode 100644 cores/libretro-test-vulkan-async-compute/libretro-test.c
 create mode 100644 cores/libretro-test-vulkan-async-compute/link.T
 create mode 100644 cores/libretro-test-vulkan-async-compute/shaders/Makefile
 create mode 100644 cores/libretro-test-vulkan-async-compute/shaders/raymarch.comp
 create mode 100644 cores/libretro-test-vulkan-async-compute/shaders/raymarch.comp.inc

diff --git a/cores/libretro-test-vulkan-async-compute/Makefile b/cores/libretro-test-vulkan-async-compute/Makefile
new file mode 100644
index 0000000000..e13f4560c4
--- /dev/null
+++ b/cores/libretro-test-vulkan-async-compute/Makefile
@@ -0,0 +1,62 @@
+ifeq ($(platform),)
+	platform = unix
+	ifeq ($(shell uname -a),)
+		platform = win
+	else ifneq ($(findstring MINGW,$(shell uname -a)),)
+		platform = win
+	else ifneq ($(findstring Darwin,$(shell uname -a)),)
+		platform = osx
+		arch = intel
+	ifeq ($(shell uname -p),powerpc)
+		arch = ppc
+	endif
+	else ifneq ($(findstring win,$(shell uname -a)),)
+		platform = win
+	endif
+endif
+
+# system platform
+system_platform = unix
+ifeq ($(shell uname -a),)
+	EXE_EXT = .exe
+	system_platform = win
+else ifneq ($(findstring MINGW,$(shell uname -a)),)
+	system_platform = win
+endif
+
+TARGET_NAME = testvulkan_async_compute
+
+ifeq ($(platform), unix)
+   TARGET := $(TARGET_NAME)_libretro.so
+   fpic := -fPIC
+   SHARED := -shared -Wl,--version-script=link.T -Wl,--no-undefined
+else
+   CC = gcc
+   TARGET := $(TARGET_NAME)_libretro.dll
+   SHARED := -shared -static-libgcc -static-libstdc++ -s -Wl,--version-script=link.T -Wl,--no-undefined
+   CFLAGS += -I..
+endif
+
+ifeq ($(DEBUG), 1)
+   CFLAGS += -O0 -g
+else
+   CFLAGS += -O3
+endif
+
+CFLAGS += -std=gnu99
+OBJECTS := libretro-test.o ../../libretro-common/vulkan/vulkan_symbol_wrapper.o
+CFLAGS += -Wall -pedantic $(fpic)
+
+all: $(TARGET)
+
+$(TARGET): $(OBJECTS)
+	$(CC) $(fpic) $(SHARED) $(INCLUDES) -o $@ $(OBJECTS) $(LIBS) -lm
+
+%.o: %.c
+	$(CC) -I../../libretro-common/include $(CFLAGS) -c -o $@ $<
+
+clean:
+	rm -f $(OBJECTS) $(TARGET)
+
+.PHONY: clean
+
diff --git a/cores/libretro-test-vulkan-async-compute/libretro-test.c b/cores/libretro-test-vulkan-async-compute/libretro-test.c
new file mode 100644
index 0000000000..a09a35049a
--- /dev/null
+++ b/cores/libretro-test-vulkan-async-compute/libretro-test.c
@@ -0,0 +1,618 @@
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include "vulkan/vulkan_symbol_wrapper.h"
+#include <libretro_vulkan.h>
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+static struct retro_hw_render_callback hw_render;
+static const struct retro_hw_render_interface_vulkan *vulkan;
+static unsigned frame_count;
+
+#define BASE_WIDTH 640
+#define BASE_HEIGHT 360
+#define MAX_SYNC 8
+
+struct buffer
+{
+   VkBuffer buffer;
+   VkDeviceMemory memory;
+};
+
+struct vulkan_data
+{
+   unsigned index;
+   unsigned num_swapchain_images;
+   uint32_t swapchain_mask;
+
+   VkPhysicalDeviceMemoryProperties memory_properties;
+   VkPhysicalDeviceProperties gpu_properties;
+
+   VkDescriptorSetLayout set_layout;
+   VkDescriptorPool desc_pool;
+   VkDescriptorSet desc_set[MAX_SYNC];
+
+   VkPipelineCache pipeline_cache;
+   VkPipelineLayout pipeline_layout;
+   VkPipeline pipeline;
+
+   struct retro_vulkan_image images[MAX_SYNC];
+   VkDeviceMemory image_memory[MAX_SYNC];
+   VkCommandPool cmd_pool[MAX_SYNC];
+   VkCommandBuffer cmd[MAX_SYNC];
+   VkSemaphore acquire_semaphores[MAX_SYNC];
+};
+static struct vulkan_data vk;
+
+void retro_init(void)
+{}
+
+void retro_deinit(void)
+{}
+
+unsigned retro_api_version(void)
+{
+   return RETRO_API_VERSION;
+}
+
+void retro_set_controller_port_device(unsigned port, unsigned device)
+{
+   (void)port;
+   (void)device;
+}
+
+void retro_get_system_info(struct retro_system_info *info)
+{
+   memset(info, 0, sizeof(*info));
+   info->library_name     = "TestCore Async Compute Vulkan";
+   info->library_version  = "v1";
+   info->need_fullpath    = false;
+   info->valid_extensions = NULL; // Anything is fine, we don't care.
+}
+
+void retro_get_system_av_info(struct retro_system_av_info *info)
+{
+   info->timing = (struct retro_system_timing) {
+      .fps = 60.0,
+      .sample_rate = 30000.0,
+   };
+
+   info->geometry = (struct retro_game_geometry) {
+      .base_width   = BASE_WIDTH,
+      .base_height  = BASE_HEIGHT,
+      .max_width    = BASE_WIDTH,
+      .max_height   = BASE_HEIGHT,
+      .aspect_ratio = (float)BASE_WIDTH / (float)BASE_HEIGHT,
+   };
+}
+
+static retro_video_refresh_t video_cb;
+static retro_audio_sample_t audio_cb;
+static retro_audio_sample_batch_t audio_batch_cb;
+static retro_environment_t environ_cb;
+static retro_input_poll_t input_poll_cb;
+static retro_input_state_t input_state_cb;
+
+void retro_set_environment(retro_environment_t cb)
+{
+   environ_cb = cb;
+
+   bool no_rom = true;
+   cb(RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME, &no_rom);
+}
+
+void retro_set_audio_sample(retro_audio_sample_t cb)
+{
+   audio_cb = cb;
+}
+
+void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb)
+{
+   audio_batch_cb = cb;
+}
+
+void retro_set_input_poll(retro_input_poll_t cb)
+{
+   input_poll_cb = cb;
+}
+
+void retro_set_input_state(retro_input_state_t cb)
+{
+   input_state_cb = cb;
+}
+
+void retro_set_video_refresh(retro_video_refresh_t cb)
+{
+   video_cb = cb;
+}
+
+static uint32_t find_memory_type_from_requirements(
+      uint32_t device_requirements, uint32_t host_requirements)
+{
+   const VkPhysicalDeviceMemoryProperties *props = &vk.memory_properties;
+   for (uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; i++)
+   {
+      if (device_requirements & (1u << i))
+      {
+         if ((props->memoryTypes[i].propertyFlags & host_requirements) == host_requirements)
+         {
+            return i;
+         }
+      }
+   }
+
+   return 0;
+}
+
+static void vulkan_test_render(void)
+{
+   VkCommandBuffer cmd = vk.cmd[vk.index];
+
+   VkCommandBufferBeginInfo begin_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
+   begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+   vkResetCommandBuffer(cmd, 0);
+   vkBeginCommandBuffer(cmd, &begin_info);
+
+   VkImageMemoryBarrier prepare_rendering = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER };
+   prepare_rendering.srcAccessMask = 0;
+   prepare_rendering.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
+   prepare_rendering.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+   prepare_rendering.newLayout = VK_IMAGE_LAYOUT_GENERAL;
+   prepare_rendering.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+   prepare_rendering.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+   prepare_rendering.image = vk.images[vk.index].create_info.image;
+   prepare_rendering.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+   prepare_rendering.subresourceRange.levelCount = 1;
+   prepare_rendering.subresourceRange.layerCount = 1;
+   vkCmdPipelineBarrier(cmd,
+         VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+         false, 
+         0, NULL,
+         0, NULL,
+         1, &prepare_rendering);
+
+   vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, vk.pipeline);
+   vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE,
+         vk.pipeline_layout, 0,
+         1, &vk.desc_set[vk.index], 0, NULL);
+
+   const float constants[4] = {
+      1.0f / BASE_WIDTH,
+      1.0f / BASE_HEIGHT,
+      (float)frame_count++,
+      0.0f,
+   };
+   vkCmdPushConstants(cmd, vk.pipeline_layout,
+         VK_SHADER_STAGE_COMPUTE_BIT,
+         0, 16, constants);
+
+   vkCmdDispatch(cmd, BASE_WIDTH / 8, BASE_HEIGHT / 8, 1);
+
+   VkImageMemoryBarrier prepare_presentation = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER };
+   prepare_presentation.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
+   prepare_presentation.dstAccessMask = 0;
+   prepare_presentation.oldLayout = VK_IMAGE_LAYOUT_GENERAL;
+   prepare_presentation.newLayout = VK_IMAGE_LAYOUT_GENERAL;
+   prepare_presentation.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+   prepare_presentation.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+   prepare_presentation.image = vk.images[vk.index].create_info.image;
+   prepare_presentation.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+   prepare_presentation.subresourceRange.levelCount = 1;
+   prepare_presentation.subresourceRange.layerCount = 1;
+   vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+         VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
+         false,
+         0, NULL,
+         0, NULL,
+         1, &prepare_presentation);
+
+   vkEndCommandBuffer(cmd);
+
+   vulkan->lock_queue(vulkan->handle);
+   VkSubmitInfo submit = { VK_STRUCTURE_TYPE_SUBMIT_INFO };
+   submit.commandBufferCount = 1;
+   submit.pCommandBuffers = &cmd;
+   submit.signalSemaphoreCount = 1;
+   submit.pSignalSemaphores = &vk.acquire_semaphores[vk.index];
+   vkQueueSubmit(vulkan->queue, 1, &submit, VK_NULL_HANDLE);
+   vulkan->unlock_queue(vulkan->handle);
+}
+
+static VkShaderModule create_shader_module(const uint32_t *data, size_t size)
+{
+   VkShaderModuleCreateInfo module_info = { VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO };
+   VkShaderModule module;
+   module_info.codeSize = size;
+   module_info.pCode = data;
+   vkCreateShaderModule(vulkan->device, &module_info, NULL, &module);
+   return module;
+}
+
+static void init_descriptor(void)
+{
+   VkDevice device = vulkan->device;
+
+   VkDescriptorSetLayoutBinding binding = {0};
+   binding.binding = 0;
+   binding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
+   binding.descriptorCount = 1;
+   binding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
+   binding.pImmutableSamplers = NULL;
+
+   const VkDescriptorPoolSize pool_sizes[1] = {
+      { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, vk.num_swapchain_images },
+   };
+
+   const VkPushConstantRange range = {
+      VK_SHADER_STAGE_COMPUTE_BIT,
+      0, 16,
+   };
+
+   VkDescriptorSetLayoutCreateInfo set_layout_info = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO };
+   set_layout_info.bindingCount = 1;
+   set_layout_info.pBindings = &binding;
+   vkCreateDescriptorSetLayout(device, &set_layout_info, NULL, &vk.set_layout);
+
+   VkPipelineLayoutCreateInfo layout_info = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO };
+   layout_info.setLayoutCount = 1;
+   layout_info.pSetLayouts = &vk.set_layout;
+   layout_info.pushConstantRangeCount = 1;
+   layout_info.pPushConstantRanges = &range;
+   vkCreatePipelineLayout(device, &layout_info, NULL, &vk.pipeline_layout);
+
+   VkDescriptorPoolCreateInfo pool_info = { VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO };
+   pool_info.maxSets = vk.num_swapchain_images;
+   pool_info.poolSizeCount = 1;
+   pool_info.pPoolSizes = pool_sizes;
+   pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
+   vkCreateDescriptorPool(device, &pool_info, NULL, &vk.desc_pool);
+
+   VkDescriptorSetAllocateInfo alloc_info = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO };
+   alloc_info.descriptorPool = vk.desc_pool;
+   alloc_info.descriptorSetCount = 1;
+   alloc_info.pSetLayouts = &vk.set_layout;
+
+   for (unsigned i = 0; i < vk.num_swapchain_images; i++)
+   {
+      vkAllocateDescriptorSets(device, &alloc_info, &vk.desc_set[i]);
+
+      VkWriteDescriptorSet write = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET };
+      VkDescriptorImageInfo image_info;
+
+      write.dstSet = vk.desc_set[i];
+      write.dstBinding = 0;
+      write.descriptorCount = 1;
+      write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
+      write.pImageInfo = &image_info;
+
+      image_info.imageView = vk.images[i].image_view;
+      image_info.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
+      image_info.sampler = VK_NULL_HANDLE;
+
+      vkUpdateDescriptorSets(device, 1, &write, 0, NULL);
+   }
+}
+
+static void init_pipeline(void)
+{
+   VkDevice device = vulkan->device;
+
+   static const uint32_t raymarch_comp[] =
+#include "shaders/raymarch.comp.inc"
+      ;
+
+   VkComputePipelineCreateInfo pipe = { VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO };
+
+   pipe.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+   pipe.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
+   pipe.stage.module = create_shader_module(raymarch_comp, sizeof(raymarch_comp));
+   pipe.stage.pName = "main";
+   pipe.layout = vk.pipeline_layout;
+
+   vkCreateComputePipelines(vulkan->device, vk.pipeline_cache, 1, &pipe, NULL, &vk.pipeline);
+   vkDestroyShaderModule(device, pipe.stage.module, NULL);
+}
+
+static void init_swapchain(void)
+{
+   VkDevice device = vulkan->device;
+
+   for (unsigned i = 0; i < vk.num_swapchain_images; i++)
+   {
+      VkImageCreateInfo image = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
+
+      image.imageType = VK_IMAGE_TYPE_2D;
+      image.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
+      image.format = VK_FORMAT_R8G8B8A8_UNORM;
+      image.extent.width = BASE_WIDTH;
+      image.extent.height = BASE_HEIGHT;
+      image.extent.depth = 1;
+      image.samples = VK_SAMPLE_COUNT_1_BIT;
+      image.tiling = VK_IMAGE_TILING_OPTIMAL;
+      image.usage =
+         VK_IMAGE_USAGE_STORAGE_BIT |
+         VK_IMAGE_USAGE_SAMPLED_BIT |
+         VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
+      image.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+      image.mipLevels = 1;
+      image.arrayLayers = 1;
+
+      vkCreateImage(device, &image, NULL, &vk.images[i].create_info.image);
+
+      VkMemoryAllocateInfo alloc = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };
+      VkMemoryRequirements mem_reqs;
+
+      vkGetImageMemoryRequirements(device, vk.images[i].create_info.image, &mem_reqs);
+      alloc.allocationSize = mem_reqs.size;
+      alloc.memoryTypeIndex = find_memory_type_from_requirements(
+            mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+      vkAllocateMemory(device, &alloc, NULL, &vk.image_memory[i]);
+      vkBindImageMemory(device, vk.images[i].create_info.image, vk.image_memory[i], 0);
+
+      vk.images[i].create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+      vk.images[i].create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
+      vk.images[i].create_info.format = VK_FORMAT_R8G8B8A8_UNORM;
+      vk.images[i].create_info.subresourceRange.baseMipLevel = 0;
+      vk.images[i].create_info.subresourceRange.baseArrayLayer = 0;
+      vk.images[i].create_info.subresourceRange.levelCount = 1;
+      vk.images[i].create_info.subresourceRange.layerCount = 1;
+      vk.images[i].create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+      vk.images[i].create_info.components.r = VK_COMPONENT_SWIZZLE_R;
+      vk.images[i].create_info.components.g = VK_COMPONENT_SWIZZLE_G;
+      vk.images[i].create_info.components.b = VK_COMPONENT_SWIZZLE_B;
+      vk.images[i].create_info.components.a = VK_COMPONENT_SWIZZLE_A;
+
+      vkCreateImageView(device, &vk.images[i].create_info,
+            NULL, &vk.images[i].image_view);
+      vk.images[i].image_layout = VK_IMAGE_LAYOUT_GENERAL;
+
+      VkSemaphoreCreateInfo sem_info = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO };
+      vkCreateSemaphore(device, &sem_info, NULL, &vk.acquire_semaphores[i]);
+   }
+}
+
+static void init_command(void)
+{
+   VkCommandPoolCreateInfo pool_info = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO };
+   VkCommandBufferAllocateInfo info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
+
+   pool_info.queueFamilyIndex = vulkan->queue_index;
+   pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+
+   for (unsigned i = 0; i < vk.num_swapchain_images; i++)
+   {
+      vkCreateCommandPool(vulkan->device, &pool_info, NULL, &vk.cmd_pool[i]);
+      info.commandPool = vk.cmd_pool[i];
+      info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+      info.commandBufferCount = 1;
+      vkAllocateCommandBuffers(vulkan->device, &info, &vk.cmd[i]);
+   }
+}
+
+static void vulkan_test_init(void)
+{
+   vkGetPhysicalDeviceProperties(vulkan->gpu, &vk.gpu_properties);
+   vkGetPhysicalDeviceMemoryProperties(vulkan->gpu, &vk.memory_properties);
+
+   unsigned num_images = 0;
+   uint32_t mask = vulkan->get_sync_index_mask(vulkan->handle);
+   for (unsigned i = 0; i < 32; i++)
+      if (mask & (1u << i))
+         num_images = i + 1;
+   vk.num_swapchain_images = num_images;
+   vk.swapchain_mask = mask;
+
+   init_command();
+   init_swapchain();
+   init_descriptor();
+
+   VkPipelineCacheCreateInfo pipeline_cache_info = { VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO };
+   vkCreatePipelineCache(vulkan->device, &pipeline_cache_info,
+         NULL, &vk.pipeline_cache);
+
+   init_pipeline();
+}
+
+static void vulkan_test_deinit(void)
+{
+   if (!vulkan)
+      return;
+
+   VkDevice device = vulkan->device;
+   vkDeviceWaitIdle(device);
+
+   for (unsigned i = 0; i < vk.num_swapchain_images; i++)
+   {
+      vkDestroyImageView(device, vk.images[i].image_view, NULL);
+      vkFreeMemory(device, vk.image_memory[i], NULL);
+      vkDestroyImage(device, vk.images[i].create_info.image, NULL);
+      vkDestroySemaphore(device, vk.acquire_semaphores[i], NULL);
+   }
+
+   vkFreeDescriptorSets(device, vk.desc_pool, vk.num_swapchain_images, vk.desc_set);
+   vkDestroyDescriptorPool(device, vk.desc_pool, NULL);
+
+   vkDestroyPipeline(device, vk.pipeline, NULL);
+   vkDestroyDescriptorSetLayout(device, vk.set_layout, NULL);
+   vkDestroyPipelineLayout(device, vk.pipeline_layout, NULL);
+   vkDestroyPipelineCache(device, vk.pipeline_cache, NULL);
+
+   for (unsigned i = 0; i < vk.num_swapchain_images; i++)
+   {
+      vkFreeCommandBuffers(device, vk.cmd_pool[i], 1, &vk.cmd[i]);
+      vkDestroyCommandPool(device, vk.cmd_pool[i], NULL);
+   }
+
+   memset(&vk, 0, sizeof(vk));
+}
+
+void retro_run(void)
+{
+   input_poll_cb();
+
+   if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP))
+   {
+   }
+
+   /* Very lazy way to do this. */
+   if (vulkan->get_sync_index_mask(vulkan->handle) != vk.swapchain_mask)
+   {
+      vulkan_test_deinit();
+      vulkan_test_init();
+   }
+
+   vulkan->wait_sync_index(vulkan->handle);
+
+   vk.index = vulkan->get_sync_index(vulkan->handle);
+   vulkan_test_render();
+   vulkan->set_image(vulkan->handle, &vk.images[vk.index], 1, &vk.acquire_semaphores[vk.index], VK_QUEUE_FAMILY_IGNORED);
+   video_cb(RETRO_HW_FRAME_BUFFER_VALID, BASE_WIDTH, BASE_HEIGHT, 0);
+}
+
+static void context_reset(void)
+{
+   fprintf(stderr, "Context reset!\n");
+   if (!environ_cb(RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE, (void**)&vulkan) || !vulkan)
+   {
+      fprintf(stderr, "Failed to get HW rendering interface!\n");
+      return;
+   }
+
+   if (vulkan->interface_version != RETRO_HW_RENDER_INTERFACE_VULKAN_VERSION)
+   {
+      fprintf(stderr, "HW render interface mismatch, expected %u, got %u!\n",
+            RETRO_HW_RENDER_INTERFACE_VULKAN_VERSION, vulkan->interface_version);
+      vulkan = NULL;
+      return;
+   }
+
+   vulkan_symbol_wrapper_init(vulkan->get_instance_proc_addr);
+   vulkan_symbol_wrapper_load_core_instance_symbols(vulkan->instance);
+   vulkan_symbol_wrapper_load_core_device_symbols(vulkan->device);
+   vulkan_test_init();
+}
+
+static void context_destroy(void)
+{
+   fprintf(stderr, "Context destroy!\n");
+   vulkan_test_deinit();
+   vulkan = NULL;
+   memset(&vk, 0, sizeof(vk));
+}
+
+static const VkApplicationInfo *get_application_info(void)
+{
+   static const VkApplicationInfo info = {
+      VK_STRUCTURE_TYPE_APPLICATION_INFO,
+      NULL,
+      "libretro-test-vulkan-async-compute",
+      0,
+      "libretro-test-vulkan-async-compute",
+      0,
+      VK_MAKE_VERSION(1, 0, 18),
+   };
+   return &info;
+}
+
+static bool retro_init_hw_context(void)
+{
+   hw_render.context_type = RETRO_HW_CONTEXT_VULKAN;
+   hw_render.version_major = VK_MAKE_VERSION(1, 0, 18);
+   hw_render.version_minor = 0;
+   hw_render.context_reset = context_reset;
+   hw_render.context_destroy = context_destroy;
+   hw_render.cache_context = false;
+   if (!environ_cb(RETRO_ENVIRONMENT_SET_HW_RENDER, &hw_render))
+      return false;
+
+   static const struct retro_hw_render_context_negotiation_interface_vulkan iface = {
+      RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN,
+      RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN_VERSION,
+
+      get_application_info,
+      NULL,
+   };
+
+   environ_cb(RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE, (void*)&iface);
+
+   return true;
+}
+
+bool retro_load_game(const struct retro_game_info *info)
+{
+   if (!retro_init_hw_context())
+   {
+      fprintf(stderr, "HW Context could not be initialized, exiting...\n");
+      return false;
+   }
+
+   fprintf(stderr, "Loaded game!\n");
+   (void)info;
+
+   frame_count = 0;
+   return true;
+}
+
+void retro_unload_game(void)
+{}
+
+unsigned retro_get_region(void)
+{
+   return RETRO_REGION_NTSC;
+}
+
+bool retro_load_game_special(unsigned type, const struct retro_game_info *info, size_t num)
+{
+   (void)type;
+   (void)info;
+   (void)num;
+   return false;
+}
+
+size_t retro_serialize_size(void)
+{
+   return 0;
+}
+
+bool retro_serialize(void *data, size_t size)
+{
+   (void)data;
+   (void)size;
+   return false;
+}
+
+bool retro_unserialize(const void *data, size_t size)
+{
+   (void)data;
+   (void)size;
+   return false;
+}
+
+void *retro_get_memory_data(unsigned id)
+{
+   (void)id;
+   return NULL;
+}
+
+size_t retro_get_memory_size(unsigned id)
+{
+   (void)id;
+   return 0;
+}
+
+void retro_reset(void)
+{}
+
+void retro_cheat_reset(void)
+{}
+
+void retro_cheat_set(unsigned index, bool enabled, const char *code)
+{
+   (void)index;
+   (void)enabled;
+   (void)code;
+}
+
diff --git a/cores/libretro-test-vulkan-async-compute/link.T b/cores/libretro-test-vulkan-async-compute/link.T
new file mode 100644
index 0000000000..b0c262db9e
--- /dev/null
+++ b/cores/libretro-test-vulkan-async-compute/link.T
@@ -0,0 +1,5 @@
+{
+   global: retro_*;
+   local: *;
+};
+
diff --git a/cores/libretro-test-vulkan-async-compute/shaders/Makefile b/cores/libretro-test-vulkan-async-compute/shaders/Makefile
new file mode 100644
index 0000000000..f55f5114fe
--- /dev/null
+++ b/cores/libretro-test-vulkan-async-compute/shaders/Makefile
@@ -0,0 +1,15 @@
+COMP_SHADERS := $(wildcard *.comp)
+SPIRV := $(COMP_SHADERS:.comp=.comp.inc)
+
+GLSLANG := glslc
+GLSLFLAGS := -mfmt=c
+
+all: $(SPIRV)
+
+%.comp.inc: %.comp
+	$(GLSLANG) $(GLSLFLAGS) -o $@ $<
+
+clean:
+	rm -f $(SPIRV)
+
+.PHONY: clean
diff --git a/cores/libretro-test-vulkan-async-compute/shaders/raymarch.comp b/cores/libretro-test-vulkan-async-compute/shaders/raymarch.comp
new file mode 100644
index 0000000000..a8adc23d4d
--- /dev/null
+++ b/cores/libretro-test-vulkan-async-compute/shaders/raymarch.comp
@@ -0,0 +1,18 @@
+#version 310 es
+layout(local_size_x = 8, local_size_y = 8) in;
+
+layout(rgba8, set = 0, binding = 0) uniform writeonly mediump image2D uImage;
+
+layout(push_constant, std430) uniform PushConstants
+{
+   vec2 inv_resolution;
+   float frame;
+   float dummy;
+} constants;
+
+void main()
+{
+   vec2 uv = (vec2(gl_GlobalInvocationID.xy) + 0.5) * constants.inv_resolution;
+   vec4 color = vec4(sin(uv.x * 50.0 + constants.frame * 0.1) + 0.5, cos(uv.y * 55.0 + constants.frame * 0.2), 0.2, 1.0);
+   imageStore(uImage, ivec2(gl_GlobalInvocationID.xy), color);
+}
diff --git a/cores/libretro-test-vulkan-async-compute/shaders/raymarch.comp.inc b/cores/libretro-test-vulkan-async-compute/shaders/raymarch.comp.inc
new file mode 100644
index 0000000000..e347663d2d
--- /dev/null
+++ b/cores/libretro-test-vulkan-async-compute/shaders/raymarch.comp.inc
@@ -0,0 +1,117 @@
+{0x07230203,0x00010000,0x00080001,0x00000048,
+0x00000000,0x00020011,0x00000001,0x0006000b,
+0x00000001,0x4c534c47,0x6474732e,0x3035342e,
+0x00000000,0x0003000e,0x00000000,0x00000001,
+0x0006000f,0x00000005,0x00000004,0x6e69616d,
+0x00000000,0x0000000d,0x00060010,0x00000004,
+0x00000011,0x00000008,0x00000008,0x00000001,
+0x00030003,0x00000001,0x00000136,0x000a0004,
+0x475f4c47,0x4c474f4f,0x70635f45,0x74735f70,
+0x5f656c79,0x656e696c,0x7269645f,0x69746365,
+0x00006576,0x00080004,0x475f4c47,0x4c474f4f,
+0x6e695f45,0x64756c63,0x69645f65,0x74636572,
+0x00657669,0x00040005,0x00000004,0x6e69616d,
+0x00000000,0x00030005,0x00000009,0x00007675,
+0x00080005,0x0000000d,0x475f6c67,0x61626f6c,
+0x766e496c,0x7461636f,0x496e6f69,0x00000044,
+0x00060005,0x00000015,0x68737550,0x736e6f43,
+0x746e6174,0x00000073,0x00070006,0x00000015,
+0x00000000,0x5f766e69,0x6f736572,0x6974756c,
+0x00006e6f,0x00050006,0x00000015,0x00000001,
+0x6d617266,0x00000065,0x00050006,0x00000015,
+0x00000002,0x6d6d7564,0x00000079,0x00050005,
+0x00000017,0x736e6f63,0x746e6174,0x00000073,
+0x00040005,0x00000020,0x6f6c6f63,0x00000072,
+0x00040005,0x0000003f,0x616d4975,0x00006567,
+0x00040047,0x0000000d,0x0000000b,0x0000001c,
+0x00050048,0x00000015,0x00000000,0x00000023,
+0x00000000,0x00050048,0x00000015,0x00000001,
+0x00000023,0x00000008,0x00050048,0x00000015,
+0x00000002,0x00000023,0x0000000c,0x00030047,
+0x00000015,0x00000002,0x00030047,0x0000003f,
+0x00000000,0x00040047,0x0000003f,0x00000022,
+0x00000000,0x00040047,0x0000003f,0x00000021,
+0x00000000,0x00030047,0x0000003f,0x00000019,
+0x00030047,0x00000040,0x00000000,0x00040047,
+0x00000047,0x0000000b,0x00000019,0x00020013,
+0x00000002,0x00030021,0x00000003,0x00000002,
+0x00030016,0x00000006,0x00000020,0x00040017,
+0x00000007,0x00000006,0x00000002,0x00040020,
+0x00000008,0x00000007,0x00000007,0x00040015,
+0x0000000a,0x00000020,0x00000000,0x00040017,
+0x0000000b,0x0000000a,0x00000003,0x00040020,
+0x0000000c,0x00000001,0x0000000b,0x0004003b,
+0x0000000c,0x0000000d,0x00000001,0x00040017,
+0x0000000e,0x0000000a,0x00000002,0x0004002b,
+0x00000006,0x00000012,0x3f000000,0x0005001e,
+0x00000015,0x00000007,0x00000006,0x00000006,
+0x00040020,0x00000016,0x00000009,0x00000015,
+0x0004003b,0x00000016,0x00000017,0x00000009,
+0x00040015,0x00000018,0x00000020,0x00000001,
+0x0004002b,0x00000018,0x00000019,0x00000000,
+0x00040020,0x0000001a,0x00000009,0x00000007,
+0x00040017,0x0000001e,0x00000006,0x00000004,
+0x00040020,0x0000001f,0x00000007,0x0000001e,
+0x0004002b,0x0000000a,0x00000021,0x00000000,
+0x00040020,0x00000022,0x00000007,0x00000006,
+0x0004002b,0x00000006,0x00000025,0x42480000,
+0x0004002b,0x00000018,0x00000027,0x00000001,
+0x00040020,0x00000028,0x00000009,0x00000006,
+0x0004002b,0x00000006,0x0000002b,0x3dcccccd,
+0x0004002b,0x0000000a,0x00000030,0x00000001,
+0x0004002b,0x00000006,0x00000033,0x425c0000,
+0x0004002b,0x00000006,0x00000037,0x3e4ccccd,
+0x0004002b,0x00000006,0x0000003b,0x3f800000,
+0x00090019,0x0000003d,0x00000006,0x00000001,
+0x00000000,0x00000000,0x00000000,0x00000002,
+0x00000004,0x00040020,0x0000003e,0x00000000,
+0x0000003d,0x0004003b,0x0000003e,0x0000003f,
+0x00000000,0x00040017,0x00000043,0x00000018,
+0x00000002,0x0004002b,0x0000000a,0x00000046,
+0x00000008,0x0006002c,0x0000000b,0x00000047,
+0x00000046,0x00000046,0x00000030,0x00050036,
+0x00000002,0x00000004,0x00000000,0x00000003,
+0x000200f8,0x00000005,0x0004003b,0x00000008,
+0x00000009,0x00000007,0x0004003b,0x0000001f,
+0x00000020,0x00000007,0x0004003d,0x0000000b,
+0x0000000f,0x0000000d,0x0007004f,0x0000000e,
+0x00000010,0x0000000f,0x0000000f,0x00000000,
+0x00000001,0x00040070,0x00000007,0x00000011,
+0x00000010,0x00050050,0x00000007,0x00000013,
+0x00000012,0x00000012,0x00050081,0x00000007,
+0x00000014,0x00000011,0x00000013,0x00050041,
+0x0000001a,0x0000001b,0x00000017,0x00000019,
+0x0004003d,0x00000007,0x0000001c,0x0000001b,
+0x00050085,0x00000007,0x0000001d,0x00000014,
+0x0000001c,0x0003003e,0x00000009,0x0000001d,
+0x00050041,0x00000022,0x00000023,0x00000009,
+0x00000021,0x0004003d,0x00000006,0x00000024,
+0x00000023,0x00050085,0x00000006,0x00000026,
+0x00000024,0x00000025,0x00050041,0x00000028,
+0x00000029,0x00000017,0x00000027,0x0004003d,
+0x00000006,0x0000002a,0x00000029,0x00050085,
+0x00000006,0x0000002c,0x0000002a,0x0000002b,
+0x00050081,0x00000006,0x0000002d,0x00000026,
+0x0000002c,0x0006000c,0x00000006,0x0000002e,
+0x00000001,0x0000000d,0x0000002d,0x00050081,
+0x00000006,0x0000002f,0x0000002e,0x00000012,
+0x00050041,0x00000022,0x00000031,0x00000009,
+0x00000030,0x0004003d,0x00000006,0x00000032,
+0x00000031,0x00050085,0x00000006,0x00000034,
+0x00000032,0x00000033,0x00050041,0x00000028,
+0x00000035,0x00000017,0x00000027,0x0004003d,
+0x00000006,0x00000036,0x00000035,0x00050085,
+0x00000006,0x00000038,0x00000036,0x00000037,
+0x00050081,0x00000006,0x00000039,0x00000034,
+0x00000038,0x0006000c,0x00000006,0x0000003a,
+0x00000001,0x0000000e,0x00000039,0x00070050,
+0x0000001e,0x0000003c,0x0000002f,0x0000003a,
+0x00000037,0x0000003b,0x0003003e,0x00000020,
+0x0000003c,0x0004003d,0x0000003d,0x00000040,
+0x0000003f,0x0004003d,0x0000000b,0x00000041,
+0x0000000d,0x0007004f,0x0000000e,0x00000042,
+0x00000041,0x00000041,0x00000000,0x00000001,
+0x0004007c,0x00000043,0x00000044,0x00000042,
+0x0004003d,0x0000001e,0x00000045,0x00000020,
+0x00040063,0x00000040,0x00000044,0x00000045,
+0x000100fd,0x00010038}