Add support for Vulkan rendering.
authorEvan Hemsley <evan@moonside.games>
Wed, 01 Jul 2020 04:29:09 -0400
changeset 1271 5a67d082c55f
parent 1270 fb28ba997299
child 1272 cddbd25553fe
Add support for Vulkan rendering. Co-authors include: - Caleb Cornett <caleb.cornett@outlook.com> - Martin Krošlák <kroslakma@gmail.com> - Ethan Lee <flibitijibibo@flibitijibibo.com>
CMakeLists.txt
mojoshader.h
mojoshader_vulkan.c
mojoshader_vulkan_vkfuncs.h
--- a/CMakeLists.txt	Thu Jun 25 10:35:29 2020 -0400
+++ b/CMakeLists.txt	Wed Jul 01 04:29:09 2020 -0400
@@ -164,6 +164,7 @@
     mojoshader_opengl.c
     mojoshader_metal.c
     mojoshader_d3d11.c
+    mojoshader_vulkan.c
     profiles/mojoshader_profile_arb1.c
     profiles/mojoshader_profile_bytecode.c
     profiles/mojoshader_profile_d3d.c
@@ -190,6 +191,10 @@
     TARGET_LINK_LIBRARIES(mojoshader ${LIBM} ${LOBJC} ${CARBON_FRAMEWORK})
 ENDIF(BUILD_SHARED_LIBS)
 
+TARGET_INCLUDE_DIRECTORIES(mojoshader PUBLIC
+    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../Vulkan-Headers/include/vulkan>
+)
+
 SET_SOURCE_FILES_PROPERTIES(
     mojoshader_compiler.c
     PROPERTIES OBJECT_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/mojoshader_parser_hlsl.h"
--- a/mojoshader.h	Thu Jun 25 10:35:29 2020 -0400
+++ b/mojoshader.h	Wed Jul 01 04:29:09 2020 -0400
@@ -3532,6 +3532,294 @@
  */
 DECLSPEC void MOJOSHADER_mtlDestroyContext(void);
 
+/* Vulkan interface */
+
+/* Avoid including vulkan.h, don't define handles if it's already included */
+#ifdef VULKAN_H_
+#define NO_MOJOSHADER_VULKAN_TYPEDEFS
+#endif
+#ifndef NO_MOJOSHADER_VULKAN_TYPEDEFS
+#ifdef _WIN32
+#define VKAPI_CALL __stdcall
+#define VKAPI_PTR VKAPI_CALL
+#else
+#define VKAPI_CALL
+#define VKAPI_PTR
+#endif
+#define VK_DEFINE_HANDLE(object) typedef struct object##_T* object;
+
+#if defined(__LP64__) || defined(_WIN64) || defined(__x86_64__) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__)
+#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef struct object##_T *object;
+#else
+#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef unsigned long long object;
+#endif
+
+VK_DEFINE_HANDLE(VkInstance)
+VK_DEFINE_HANDLE(VkDevice)
+VK_DEFINE_HANDLE(VkPhysicalDevice)
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkBuffer)
+
+#endif /* !NO_MOJOSHADER_VULKAN_TYPEDEFS */
+
+typedef void (VKAPI_PTR *PFN_MOJOSHADER_vkVoidFunction)(void);
+typedef PFN_MOJOSHADER_vkVoidFunction (VKAPI_PTR *PFN_MOJOSHADER_vkGetDeviceProcAddr)(
+	VkDevice device,
+	const char* pName
+);
+typedef PFN_MOJOSHADER_vkVoidFunction (VKAPI_PTR *PFN_MOJOSHADER_vkGetInstanceProcAddr)(
+	VkInstance instance,
+	const char* pName
+);
+
+typedef struct MOJOSHADER_vkContext MOJOSHADER_vkContext;
+typedef struct MOJOSHADER_vkShader MOJOSHADER_vkShader;
+
+/*
+ * Prepares a context to manage Vulkan shaders.
+ *
+ * Don't call this unless you know for sure that you need it.
+ *
+ * You must call this after creating VkDevice and VkInstance.
+ *
+ * (instance) refers to VkInstance, cast to void*.
+ *
+ * (device) refers to VkDevice, cast to void*.
+ *
+ * (frames_in_flight) refers to the maximum number of frames that can be
+ * processed simultaneously.
+ *
+ * (lookup) refers to PFN_vkGetDeviceProcAddr, a function pointer that
+ * is used to dynamically link required Vulkan functions.
+ *
+ * You must pass in the graphics queue family index and the memory type index
+ * you will be using with your Vulkan instance.
+ *
+ * As MojoShader requires some memory to be allocated, you may provide a
+ *  custom allocator to this function, which will be used to allocate/free
+ *  memory. They function just like malloc() and free(). We do not use
+ *  realloc(). If you don't care, pass NULL in for the allocator functions.
+ *  If your allocator needs instance-specific data, you may supply it with the
+ *  (malloc_d) parameter. This pointer is passed as-is to your (m) and (f)
+ *  functions.
+ *
+ * The context created by this function will automatically become the current
+ *  context. No further action is needed by the caller.
+ *
+ * Returns 0 on success or -1 on failure.
+ */
+
+DECLSPEC MOJOSHADER_vkContext *MOJOSHADER_vkCreateContext(VkInstance *instance,
+                                                          VkPhysicalDevice *physical_device,
+                                                          VkDevice *logical_device,
+                                                          int frames_in_flight,
+                                                          PFN_MOJOSHADER_vkGetInstanceProcAddr instance_lookup,
+                                                          PFN_MOJOSHADER_vkGetDeviceProcAddr lookup,
+                                                          unsigned int graphics_queue_family_index,
+                                                          unsigned int max_uniform_buffer_range,
+                                                          unsigned int min_uniform_buffer_offset_alignment,
+                                                          MOJOSHADER_malloc m, MOJOSHADER_free f,
+                                                          void *malloc_d);
+
+/*
+ * You must call this before using the context that you got from
+ *  MOJOSHADER_vkCreateContext(), and must use it when you switch to a new GL
+ *  context.
+ *
+ * You can only have one MOJOSHADER_vkContext per actual Vulkan context, or
+ *  undefined behaviour will result.
+ *
+ * It is legal to call this with a NULL pointer to make no context current,
+ *  but you need a valid context to be current to use most of MojoShader.
+ */
+DECLSPEC void MOJOSHADER_vkMakeContextCurrent(MOJOSHADER_vkContext *_ctx);
+
+/*
+ * Get any error state we might have picked up.
+ *
+ * Returns a human-readable string. This string is for debugging purposes, and
+ *  not guaranteed to be localized, coherent, or user-friendly in any way.
+ *  It's for programmers!
+ *
+ * The latest error may remain between calls. New errors replace any existing
+ *  error. Don't check this string for a sign that an error happened, check
+ *  return codes instead and use this for explanation when debugging.
+ *
+ * Do not free the returned string: it's a pointer to a static internal
+ *  buffer. Do not keep the pointer around, either, as it's likely to become
+ *  invalid as soon as you call into MojoShader again.
+ *
+ * This call does NOT require a valid MOJOSHADER_vkContext to have been made
+ *  current. The error buffer is shared between contexts, so you can get
+ *  error results from a failed MOJOSHADER_vkCreateContext().
+ */
+DECLSPEC const char *MOJOSHADER_vkGetError();
+
+/*
+ * Deinitialize MojoShader's Vulkan shader management.
+ *
+ * You must call this once, while your Vulkan context (not MojoShader context) is
+ *  still current, if you previously had a successful call to
+ *  MOJOSHADER_vkCreateContext(). This should be the last MOJOSHADER_vk*
+ *  function you call until you've prepared a context again.
+ *
+ * This will clean up resources previously allocated, and may call into Vulkan.
+ *
+ * This will not clean up shaders and programs you created! Please call
+ *  MOJOSHADER_vkDeleteShader() and MOJOSHADER_vkDeleteProgram() to clean
+ *  those up before calling this function!
+ *
+ * This function destroys the MOJOSHADER_vkContext you pass it. If it's the
+ *  current context, then no context will be current upon return.
+ */
+DECLSPEC void MOJOSHADER_vkDestroyContext();
+
+/*
+ * Compile a buffer of Direct3D shader bytecode into a Vulkan shader module.
+ *
+ *   (tokenbuf) is a buffer of Direct3D shader bytecode.
+ *   (bufsize) is the size, in bytes, of the bytecode buffer.
+ *   (swiz), (swizcount), (smap), and (smapcount) are passed to
+ *   MOJOSHADER_parse() unmolested.
+ *
+ * Returns NULL on error, or a shader handle on success.
+ *
+ * This call requires a valid MOJOSHADER_vkContext to have been made current,
+ *  or it will crash your program. See MOJOSHADER_vkMakeContextCurrent().
+ *
+ * Compiled shaders from this function may not be shared between contexts.
+ */
+DECLSPEC MOJOSHADER_vkShader *MOJOSHADER_vkCompileShader(const char *mainfn,
+                                                         const unsigned char *tokenbuf,
+                                                         const unsigned int bufsize,
+                                                         const MOJOSHADER_swizzle *swiz,
+                                                         const unsigned int swizcount,
+                                                         const MOJOSHADER_samplerMap *smap,
+                                                         const unsigned int smapcount);
+
+/*
+ * Increments a shader's internal refcount.
+ *
+ * To decrement the refcount, call
+ *  MOJOSHADER_vkDeleteShader().
+ *
+ * This call requires a valid MOJOSHADER_vkContext to have been made current,
+ *  or it will crash your program. See MOJOSHADER_vkMakeContextCurrent().
+ */
+DECLSPEC void MOJOSHADER_vkShaderAddRef(MOJOSHADER_vkShader *shader);
+
+/*
+ * Increments a shader's internal refcount.
+ *
+ * To decrement the refcount, call MOJOSHADER_vkDeleteShader().
+ *
+ * This call requires a valid MOJOSHADER_vkContext to have been made current,
+ *  or it will crash your program. See MOJOSHADER_vkMakeContextCurrent().
+ */
+DECLSPEC void MOJOSHADER_vkDeleteShader(MOJOSHADER_vkShader *shader);
+
+/*
+ * Get the MOJOSHADER_parseData structure that was produced from the
+ *  call to MOJOSHADER_vkCompileShader().
+ *
+ * This data is read-only, and you should NOT attempt to free it. This
+ *  pointer remains valid until the shader is deleted.
+ */
+DECLSPEC const MOJOSHADER_parseData *MOJOSHADER_vkGetShaderParseData(
+                                                MOJOSHADER_vkShader *shader);
+
+/*
+ * This "binds" individual shaders, which effectively means the context
+ *  will store these shaders for later retrieval. No actual binding or
+ *  pipeline creation is performed.
+ *
+ * This function is only for convenience, specifically for compatibility
+ *  with the effects API.
+ *
+ * This call requires a valid MOJOSHADER_vkContext to have been made current,
+ *  or it will crash your program. See MOJOSHADER_vkMakeContextCurrent().
+ */
+DECLSPEC void MOJOSHADER_vkBindShaders(MOJOSHADER_vkShader *vshader,
+                                       MOJOSHADER_vkShader *pshader);
+
+/*
+ * This queries for the shaders currently bound to the active context.
+ *
+ * This function is only for convenience, specifically for compatibility
+ *  with the effects API.
+ *
+ * This call requires a valid MOJOSHADER_vkContext to have been made current,
+ *  or it will crash your program. See MOJOSHADER_vkMakeContextCurrent().
+ */
+DECLSPEC void MOJOSHADER_vkGetBoundShaders(MOJOSHADER_vkShader **vshader,
+                                           MOJOSHADER_vkShader **pshader);
+
+/*
+ * Fills register pointers with pointers that are directly used to push uniform
+ *  data to the Vulkan shader context.
+ *
+ * This function is really just for the effects API, you should NOT be using
+ *  this unless you know every single line of MojoShader from memory.
+ *
+ * This call requires a valid MOJOSHADER_vkContext to have been made current,
+ *  or it will crash your program. See MOJOSHADER_vkMakeContextCurrent().
+ */
+DECLSPEC void MOJOSHADER_vkMapUniformBufferMemory(float **vsf, int **vsi, unsigned char **vsb,
+                                                  float **psf, int **psi, unsigned char **psb);
+
+/*
+ * Tells the context that you are done with the memory mapped by
+ *  MOJOSHADER_vkMapUniformBufferMemory().
+ *
+ * This call requires a valid MOJOSHADER_vkContext to have been made current,
+ *  or it will crash your program. See MOJOSHADER_vkMakeContextCurrent().
+ */
+DECLSPEC void MOJOSHADER_vkUnmapUniformBufferMemory();
+
+/*
+ * This queries for the uniform buffer, byte offset and byte size for each of the
+ *  currently bound shaders.
+ *
+ * This function is only for convenience, specifically for compatibility with
+ *  the effects API.
+ *
+ * This call requires a valid MOJOSHADER_vkContext to have been made current,
+ *  or it will crash your program. See MOJOSHADER_vkMakeContextCurrent().
+ */
+DECLSPEC void MOJOSHADER_vkGetUniformBuffers(VkBuffer *vbuf,
+                                             unsigned long long *voff,
+                                             unsigned long long *vsize,
+                                             VkBuffer *pbuf,
+                                             unsigned long long *poff,
+                                             unsigned long long *psize);
+
+/*
+ * Prepares uniform buffers for reuse.
+ *
+ * Always call this after submitting the final command buffer for a frame!
+ */
+DECLSPEC void MOJOSHADER_vkEndFrame();
+
+/*
+ * Return the location of a vertex attribute for the given shader.
+ *
+ * (usage) and (index) map to Direct3D vertex declaration values: COLOR1 would
+ *  be MOJOSHADER_USAGE_COLOR and 1.
+ *
+ * The return value is the index of the attribute to be used to create
+ *  a VkVertexInputAttributeDescription, or -1 if the stream is not used.
+ *
+ *  This call requires a valid MOJOSHADER_vkContext to have been made current,
+ *  or it will crash your program. See MOJOSHADER_vkMakeContextCurrent().
+ */
+DECLSPEC int MOJOSHADER_vkGetVertexAttribLocation(MOJOSHADER_vkShader *vert,
+                                                  MOJOSHADER_usage usage,
+                                                  int index);
+
+/*
+ * Get the VkShaderModule from the given MOJOSHADER_vkShader.
+ */
+DECLSPEC unsigned long long MOJOSHADER_vkGetShaderModule(
+                                                MOJOSHADER_vkShader *shader);
 
 /* D3D11 interface... */
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mojoshader_vulkan.c	Wed Jul 01 04:29:09 2020 -0400
@@ -0,0 +1,802 @@
+/**
+ * MojoShader; generate shader programs from bytecode of compiled
+ *  Direct3D shaders.
+ *
+ * Please see the file LICENSE.txt in the source's root directory.
+ *
+ *  This file written by Ryan C. Gordon.
+ */
+
+#define __MOJOSHADER_INTERNAL__ 1
+#include "mojoshader_internal.h"
+
+#if SUPPORT_PROFILE_SPIRV
+
+#include "vulkan.h"
+
+#define VULKAN_INSTANCE_FUNCTION(ret, func, params) \
+    typedef ret (VKAPI_CALL *vkfntype_MOJOSHADER_##func) params;
+#define VULKAN_DEVICE_FUNCTION(ret, func, params) \
+	typedef ret (VKAPI_CALL *vkfntype_MOJOSHADER_##func) params;
+#include "mojoshader_vulkan_vkfuncs.h"
+
+#define UBO_BUFFER_COUNT 8
+#define UBO_BUFFER_SIZE 1048576 /* ~1MB */
+
+// Internal struct defs...
+
+typedef struct MOJOSHADER_vkShader
+{
+    VkShaderModule shaderModule;
+    const MOJOSHADER_parseData *parseData;
+    uint32_t refcount;
+} MOJOSHADER_vkShader;
+
+typedef struct MOJOSHADER_vkUniformBuffer
+{
+    VkBuffer buffer;
+    VkDeviceSize bufferSize;
+    VkDeviceSize memoryOffset;
+    VkDeviceSize dynamicOffset;
+    VkDeviceSize currentBlockSize;
+    int32_t full; // Records frame on which it became full, -1 if not full
+} MOJOSHADER_vkUniformBuffer;
+
+// Error state...
+static char error_buffer[1024] = { '\0' };
+
+static void set_error(const char *str)
+{
+    snprintf(error_buffer, sizeof (error_buffer), "%s", str);
+} // set_error
+
+static inline void out_of_memory(void)
+{
+    set_error("out of memory");
+} // out_of_memory
+
+/* Max entries for each register file type */
+#define MAX_REG_FILE_F 8192
+#define MAX_REG_FILE_I 2047
+#define MAX_REG_FILE_B 2047
+
+typedef struct MOJOSHADER_vkContext
+{
+    VkInstance *instance;
+    VkPhysicalDevice *physical_device;
+    VkDevice *logical_device;
+    PFN_vkGetInstanceProcAddr instance_proc_lookup;
+    PFN_vkGetDeviceProcAddr device_proc_lookup;
+    uint32_t graphics_queue_family_index;
+    uint32_t maxUniformBufferRange;
+    uint32_t minUniformBufferOffsetAlignment;
+
+    int32_t frames_in_flight;
+
+    MOJOSHADER_malloc malloc_fn;
+    MOJOSHADER_free free_fn;
+    void *malloc_data;
+
+    // The constant register files...
+    // !!! FIXME: Man, it kills me how much memory this takes...
+    // !!! FIXME:  ... make this dynamically allocated on demand.
+    float vs_reg_file_f[MAX_REG_FILE_F * 4];
+    int32_t vs_reg_file_i[MAX_REG_FILE_I * 4];
+    uint8_t vs_reg_file_b[MAX_REG_FILE_B * 4];
+    float ps_reg_file_f[MAX_REG_FILE_F * 4];
+    int32_t ps_reg_file_i[MAX_REG_FILE_I * 4];
+    uint8_t ps_reg_file_b[MAX_REG_FILE_B * 4];
+
+    VkDeviceMemory vertUboMemory;
+    MOJOSHADER_vkUniformBuffer **vertUboBuffers;
+    uint32_t vertUboCurrentIndex;
+
+    VkDeviceMemory fragUboMemory;
+    MOJOSHADER_vkUniformBuffer **fragUboBuffers;
+    uint32_t fragUboCurrentIndex;
+
+    uint32_t uboBufferCount;
+
+    MOJOSHADER_vkShader *vertexShader;
+    MOJOSHADER_vkShader *pixelShader;
+
+    uint32_t currentFrame;
+
+    #define VULKAN_INSTANCE_FUNCTION(ret, func, params) \
+        vkfntype_MOJOSHADER_##func func;
+    #define VULKAN_DEVICE_FUNCTION(ret, func, params) \
+        vkfntype_MOJOSHADER_##func func;
+    #include "mojoshader_vulkan_vkfuncs.h"
+} MOJOSHADER_vkContext;
+
+static MOJOSHADER_vkContext *ctx = NULL;
+
+static uint8_t find_memory_type(
+    MOJOSHADER_vkContext *ctx,
+	uint32_t typeFilter,
+	VkMemoryPropertyFlags properties,
+	uint32_t *result
+) {
+	uint32_t i;
+	VkPhysicalDeviceMemoryProperties memoryProperties;
+	ctx->vkGetPhysicalDeviceMemoryProperties(*ctx->physical_device, &memoryProperties);
+
+	for (i = 0; i < memoryProperties.memoryTypeCount; i++)
+	{
+		if ((typeFilter & (1 << i))
+		 && (memoryProperties.memoryTypes[i].propertyFlags & properties) == properties)
+		{
+			*result = i;
+			return 1;
+		} // if
+	} // for
+
+	return 0;
+} // find_memory_type
+
+static uint32_t next_highest_offset_alignment(uint32_t offset)
+{
+    return (
+        (offset + ctx->minUniformBufferOffsetAlignment - 1) /
+        ctx->minUniformBufferOffsetAlignment *
+        ctx->minUniformBufferOffsetAlignment
+    );
+} // next_highest_offset_alignment
+
+static MOJOSHADER_vkUniformBuffer *create_ubo(MOJOSHADER_vkContext *ctx,
+                                              MOJOSHADER_malloc m,
+                                              void *d
+) {
+    MOJOSHADER_vkUniformBuffer *result = (MOJOSHADER_vkUniformBuffer *) m(
+        sizeof(MOJOSHADER_vkUniformBuffer),
+        d
+    );
+    VkBufferCreateInfo bufferCreateInfo =
+    {
+        VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO
+    };
+
+    bufferCreateInfo.flags = 0;
+    bufferCreateInfo.size = UBO_BUFFER_SIZE;
+    bufferCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
+    bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+    bufferCreateInfo.queueFamilyIndexCount = 1;
+    bufferCreateInfo.pQueueFamilyIndices = &ctx->graphics_queue_family_index;
+
+    ctx->vkCreateBuffer(
+        *ctx->logical_device,
+        &bufferCreateInfo,
+        NULL,
+        &result->buffer
+    );
+
+    result->bufferSize = UBO_BUFFER_SIZE;
+    result->currentBlockSize = 0;
+    result->dynamicOffset = 0;
+    result->full = -1;
+
+    return result;
+} // create_ubo
+
+static uint32_t uniform_data_size(MOJOSHADER_vkShader *shader)
+{
+    int32_t i;
+    int32_t uniformSize;
+    int32_t buflen = 0;
+    for (i = 0; i < shader->parseData->uniform_count; i++)
+    {
+        const int32_t arrayCount = shader->parseData->uniforms[i].array_count;
+        uniformSize = 16;
+        if (shader->parseData->uniforms[i].type == MOJOSHADER_UNIFORM_BOOL)
+            uniformSize = 1;
+        buflen += (arrayCount ? arrayCount : 1) * uniformSize;
+    } // for
+
+    return buflen;
+} // uniform_data_size
+
+static VkBuffer get_uniform_buffer(MOJOSHADER_vkShader *shader)
+{
+    if (shader == NULL || shader->parseData->uniform_count == 0)
+        return VK_NULL_HANDLE;
+
+    if (shader->parseData->shader_type == MOJOSHADER_TYPE_VERTEX)
+        return ctx->vertUboBuffers[ctx->vertUboCurrentIndex]->buffer;
+    else
+        return ctx->fragUboBuffers[ctx->fragUboCurrentIndex]->buffer;
+} // get_uniform_buffer
+
+static VkDeviceSize get_uniform_offset(MOJOSHADER_vkShader *shader)
+{
+    if (shader == NULL || shader->parseData->uniform_count == 0)
+        return 0;
+
+    if (shader->parseData->shader_type == MOJOSHADER_TYPE_VERTEX)
+        return ctx->vertUboBuffers[ctx->vertUboCurrentIndex]->dynamicOffset;
+    else
+        return ctx->fragUboBuffers[ctx->fragUboCurrentIndex]->dynamicOffset;
+} // get_uniform_offset
+
+static VkDeviceSize get_uniform_size(MOJOSHADER_vkShader *shader)
+{
+    if (shader == NULL || shader->parseData->uniform_count == 0)
+        return 0;
+
+    if (shader->parseData->shader_type == MOJOSHADER_TYPE_VERTEX)
+        return ctx->vertUboBuffers[ctx->vertUboCurrentIndex]->currentBlockSize;
+    else
+        return ctx->fragUboBuffers[ctx->fragUboCurrentIndex]->currentBlockSize;
+} // get_uniform_size
+
+static void update_uniform_buffer(MOJOSHADER_vkShader *shader)
+{
+    int32_t i;
+    void *map;
+    int32_t offset;
+    uint8_t *contents;
+    float *regF; int *regI; uint8_t *regB;
+    MOJOSHADER_vkUniformBuffer *ubo;
+    VkDeviceMemory uboMemory;
+
+    if (shader == NULL || shader->parseData->uniform_count == 0)
+        return;
+
+    if (shader->parseData->shader_type == MOJOSHADER_TYPE_VERTEX)
+    {
+        regF = ctx->vs_reg_file_f;
+        regI = ctx->vs_reg_file_i;
+        regB = ctx->vs_reg_file_b;
+
+        ubo = ctx->vertUboBuffers[ctx->vertUboCurrentIndex];
+        uboMemory = ctx->vertUboMemory;
+    } // if
+    else
+    {
+        regF = ctx->ps_reg_file_f;
+        regI = ctx->ps_reg_file_i;
+        regB = ctx->ps_reg_file_b;
+
+        ubo = ctx->fragUboBuffers[ctx->fragUboCurrentIndex];
+        uboMemory = ctx->fragUboMemory;
+    } // else
+
+    ubo->dynamicOffset += ubo->currentBlockSize;
+
+    ubo->currentBlockSize = next_highest_offset_alignment(uniform_data_size(shader));
+
+    // Rotate buffer if it would overrun
+    if (ubo->dynamicOffset + ubo->currentBlockSize >= ubo->bufferSize)
+    {
+        ubo->full = ctx->currentFrame;
+
+        if (shader->parseData->shader_type == MOJOSHADER_TYPE_VERTEX)
+        {
+            for (i = 0; i < ctx->uboBufferCount; i++)
+            {
+                ctx->vertUboCurrentIndex = (ctx->vertUboCurrentIndex + 1) % ctx->uboBufferCount;
+                if (ctx->vertUboBuffers[ctx->vertUboCurrentIndex]->full == -1)
+                    break;
+            } // for
+
+            ubo = ctx->vertUboBuffers[ctx->vertUboCurrentIndex];
+        }
+        else
+        {
+            for (int i = 0; i < ctx->uboBufferCount; i++)
+            {
+                ctx->fragUboCurrentIndex = (ctx->fragUboCurrentIndex + 1) % ctx->uboBufferCount;
+                if (ctx->fragUboBuffers[ctx->fragUboCurrentIndex]->full == -1)
+                    break;
+            } // for
+
+            ubo = ctx->fragUboBuffers[ctx->fragUboCurrentIndex];
+        } // else
+
+        ubo->dynamicOffset = 0;
+        ubo->currentBlockSize = next_highest_offset_alignment(uniform_data_size(shader));
+
+        if (ubo->full >= 0)
+            set_error("all UBO buffers are full");
+    } // if
+
+    ctx->vkMapMemory(
+        *ctx->logical_device,
+        uboMemory,
+        ubo->memoryOffset,
+        ubo->bufferSize,
+        0,
+        &map
+    );
+
+    contents = ((uint8_t *) map) + ubo->dynamicOffset;
+
+    offset = 0;
+    for (i = 0; i < shader->parseData->uniform_count; i++)
+    {
+        const int32_t index = shader->parseData->uniforms[i].index;
+        const int32_t arrayCount = shader->parseData->uniforms[i].array_count;
+        const int32_t size = arrayCount ? arrayCount : 1;
+
+        switch (shader->parseData->uniforms[i].type)
+        {
+            case MOJOSHADER_UNIFORM_FLOAT:
+                memcpy(
+                    contents + (offset * 16),
+                    &regF[4 * index],
+                    size * 16
+                );
+                break;
+
+            case MOJOSHADER_UNIFORM_INT:
+                memcpy(
+                    contents + (offset * 16),
+                    &regI[4 * index],
+                    size * 16
+                );
+                break;
+
+            case MOJOSHADER_UNIFORM_BOOL:
+                memcpy(
+                    contents + offset,
+                    &regB[index],
+                    size
+                );
+                break;
+
+            default:
+                set_error(
+                    "SOMETHING VERY WRONG HAPPENED WHEN UPDATING UNIFORMS"
+                );
+                assert(0);
+                break;
+        } // switch
+
+        offset += size;
+    } // for
+
+    ctx->vkUnmapMemory(
+        *ctx->logical_device,
+        uboMemory
+    );
+} // update_uniform_buffer
+
+static void lookup_entry_points(MOJOSHADER_vkContext *ctx)
+{
+    #define VULKAN_INSTANCE_FUNCTION(ret, func, params) \
+        ctx->func = (vkfntype_MOJOSHADER_##func) ctx->instance_proc_lookup(*ctx->instance, #func);
+    #define VULKAN_DEVICE_FUNCTION(ret, func, params) \
+        ctx->func = (vkfntype_MOJOSHADER_##func) ctx->device_proc_lookup(*ctx->logical_device, #func);
+    #include "mojoshader_vulkan_vkfuncs.h"
+} // lookup_entry_points
+
+static int shader_bytecode_len(MOJOSHADER_vkShader *shader)
+{
+    return shader->parseData->output_len - sizeof(SpirvPatchTable);
+} // shader_bytecode_len
+
+static void delete_shader(
+    VkShaderModule shaderModule
+) {
+    ctx->vkDestroyShaderModule(
+        *ctx->logical_device,
+        shaderModule,
+        NULL
+    );
+} // delete_shader
+
+// Public API
+
+MOJOSHADER_vkContext *MOJOSHADER_vkCreateContext(
+    VkInstance *instance,
+    VkPhysicalDevice *physical_device,
+    VkDevice *logical_device,
+    int frames_in_flight,
+    PFN_MOJOSHADER_vkGetInstanceProcAddr instance_lookup,
+    PFN_MOJOSHADER_vkGetDeviceProcAddr device_lookup,
+    unsigned int graphics_queue_family_index,
+    unsigned int max_uniform_buffer_range,
+    unsigned int min_uniform_buffer_offset_alignment,
+    MOJOSHADER_malloc m, MOJOSHADER_free f,
+    void *malloc_d
+) {
+    int32_t i;
+    int32_t uboMemoryOffset;
+    VkMemoryAllocateInfo allocate_info =
+    {
+        VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO
+    };
+    VkMemoryRequirements memoryRequirements;
+    MOJOSHADER_vkContext* resultCtx;
+
+    if (m == NULL) m = MOJOSHADER_internal_malloc;
+    if (f == NULL) f = MOJOSHADER_internal_free;
+
+    resultCtx = (MOJOSHADER_vkContext *) m(sizeof(MOJOSHADER_vkContext), malloc_d);
+    if (resultCtx == NULL)
+    {
+        out_of_memory();
+        goto init_fail;
+    }
+
+    memset(resultCtx, '\0', sizeof(MOJOSHADER_vkContext));
+    resultCtx->malloc_fn = m;
+    resultCtx->free_fn = f;
+    resultCtx->malloc_data = malloc_d;
+
+    resultCtx->instance = (VkInstance*) instance;
+    resultCtx->physical_device = (VkPhysicalDevice*) physical_device;
+    resultCtx->logical_device = (VkDevice*) logical_device;
+    resultCtx->instance_proc_lookup = (PFN_vkGetInstanceProcAddr) instance_lookup;
+    resultCtx->device_proc_lookup = (PFN_vkGetDeviceProcAddr) device_lookup;
+    resultCtx->frames_in_flight = frames_in_flight;
+    resultCtx->graphics_queue_family_index = graphics_queue_family_index;
+    resultCtx->maxUniformBufferRange = max_uniform_buffer_range;
+    resultCtx->minUniformBufferOffsetAlignment = min_uniform_buffer_offset_alignment;
+    resultCtx->currentFrame = 0;
+
+    lookup_entry_points(resultCtx);
+
+    resultCtx->uboBufferCount = UBO_BUFFER_COUNT;
+
+    // Allocate vert UBO
+
+    resultCtx->vertUboCurrentIndex = 0;
+    resultCtx->vertUboBuffers = (MOJOSHADER_vkUniformBuffer**) m(
+        sizeof(MOJOSHADER_vkUniformBuffer*) * resultCtx->uboBufferCount,
+        malloc_d
+    );
+
+    for (i = 0; i < resultCtx->uboBufferCount; i++)
+        resultCtx->vertUboBuffers[i] = create_ubo(resultCtx, m, malloc_d);
+
+    resultCtx->vkGetBufferMemoryRequirements(
+        *resultCtx->logical_device,
+        resultCtx->vertUboBuffers[0]->buffer,
+        &memoryRequirements
+    );
+
+    allocate_info.allocationSize = UBO_BUFFER_SIZE * resultCtx->uboBufferCount;
+
+    if (!find_memory_type(resultCtx,
+                          memoryRequirements.memoryTypeBits,
+                          VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+                          &allocate_info.memoryTypeIndex))
+    {
+        set_error("failed to find suitable memory type for UBO memory");
+        return NULL;
+    } // if
+
+    resultCtx->vkAllocateMemory(
+        *resultCtx->logical_device,
+        &allocate_info,
+        NULL,
+        &resultCtx->vertUboMemory
+    );
+
+    uboMemoryOffset = 0;
+    for (i = 0; i < resultCtx->uboBufferCount; i++)
+    {
+        resultCtx->vertUboBuffers[i]->memoryOffset = uboMemoryOffset;
+
+        resultCtx->vkBindBufferMemory(
+            *resultCtx->logical_device,
+            resultCtx->vertUboBuffers[i]->buffer,
+            resultCtx->vertUboMemory,
+            uboMemoryOffset
+        );
+
+        uboMemoryOffset += UBO_BUFFER_SIZE;
+    } // for
+
+    // Allocate frag UBO
+
+    resultCtx->fragUboCurrentIndex = 0;
+    resultCtx->fragUboBuffers = (MOJOSHADER_vkUniformBuffer**) m(
+        sizeof(MOJOSHADER_vkUniformBuffer*) * resultCtx->uboBufferCount,
+        malloc_d
+    );
+
+    for (i = 0; i < resultCtx->uboBufferCount; i++)
+        resultCtx->fragUboBuffers[i] = create_ubo(resultCtx, m, malloc_d);
+
+    resultCtx->vkGetBufferMemoryRequirements(
+        *resultCtx->logical_device,
+        resultCtx->fragUboBuffers[0]->buffer,
+        &memoryRequirements
+    );
+
+    allocate_info.allocationSize = UBO_BUFFER_SIZE * resultCtx->uboBufferCount;
+
+    if (!find_memory_type(resultCtx,
+                          memoryRequirements.memoryTypeBits,
+                          VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+                          &allocate_info.memoryTypeIndex))
+    {
+        set_error("failed to find suitable memory type for UBO memory");
+        return NULL;
+    } // if
+
+    resultCtx->vkAllocateMemory(
+        *resultCtx->logical_device,
+        &allocate_info,
+        NULL,
+        &resultCtx->fragUboMemory
+    );
+
+    uboMemoryOffset = 0;
+    for (i = 0; i < resultCtx->uboBufferCount; i++)
+    {
+        resultCtx->fragUboBuffers[i]->memoryOffset = uboMemoryOffset;
+
+        resultCtx->vkBindBufferMemory(
+            *resultCtx->logical_device,
+            resultCtx->fragUboBuffers[i]->buffer,
+            resultCtx->fragUboMemory,
+            uboMemoryOffset
+        );
+
+        uboMemoryOffset += UBO_BUFFER_SIZE;
+    } // for
+
+    return resultCtx;
+
+init_fail:
+    if (resultCtx != NULL)
+        f(resultCtx, malloc_d);
+    return NULL;
+} // MOJOSHADER_vkCreateContext
+
+void MOJOSHADER_vkMakeContextCurrent(MOJOSHADER_vkContext *_ctx)
+{
+    ctx = _ctx;
+} // MOJOSHADER_vkMakeContextCurrent
+
+void MOJOSHADER_vkDestroyContext()
+{
+    int32_t i;
+    for (i = 0; i < ctx->uboBufferCount; i++)
+    {
+        ctx->vkDestroyBuffer(
+            *ctx->logical_device,
+            ctx->vertUboBuffers[i]->buffer,
+            NULL
+        );
+
+        ctx->free_fn(ctx->vertUboBuffers[i], ctx->malloc_data);
+
+        ctx->vkDestroyBuffer(
+            *ctx->logical_device,
+            ctx->fragUboBuffers[i]->buffer,
+            NULL
+        );
+
+        ctx->free_fn(ctx->fragUboBuffers[i], ctx->malloc_data);
+    } // for
+
+    ctx->free_fn(ctx->vertUboBuffers, ctx->malloc_data);
+    ctx->free_fn(ctx->fragUboBuffers, ctx->malloc_data);
+
+    ctx->vkFreeMemory(
+        *ctx->logical_device,
+        ctx->vertUboMemory,
+        NULL
+    );
+
+    ctx->vkFreeMemory(
+        *ctx->logical_device,
+        ctx->fragUboMemory,
+        NULL
+    );
+
+    ctx->free_fn(ctx, ctx->malloc_data);
+} // MOJOSHADER_vkDestroyContext
+
+MOJOSHADER_vkShader *MOJOSHADER_vkCompileShader(
+    const char *mainfn,
+    const unsigned char *tokenbuf,
+    const unsigned int bufsize,
+    const MOJOSHADER_swizzle *swiz,
+    const unsigned int swizcount,
+    const MOJOSHADER_samplerMap *smap,
+    const unsigned int smapcount
+) {
+    VkResult result;
+    VkShaderModule shaderModule;
+    VkShaderModuleCreateInfo shaderModuleCreateInfo =
+    {
+        VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO
+    };
+    MOJOSHADER_vkShader *shader;
+
+    const MOJOSHADER_parseData *pd = MOJOSHADER_parse(
+        "spirv", mainfn,
+        tokenbuf, bufsize,
+        swiz, swizcount,
+        smap, smapcount,
+        ctx->malloc_fn,
+        ctx->free_fn,
+        ctx->malloc_data
+    );
+
+    if (pd->error_count > 0)
+    {
+        set_error(pd->errors[0].error);
+        goto compile_shader_fail;
+    } // if
+
+    shader = (MOJOSHADER_vkShader *) ctx->malloc_fn(sizeof(MOJOSHADER_vkShader), ctx->malloc_data);
+    if (shader == NULL)
+    {
+        out_of_memory();
+        goto compile_shader_fail;
+    } // if
+
+    shader->parseData = pd;
+    shader->refcount = 1;
+
+    shaderModuleCreateInfo.flags = 0;
+    shaderModuleCreateInfo.codeSize = shader_bytecode_len(shader);
+    shaderModuleCreateInfo.pCode = (uint32_t*) pd->output;
+
+    result = ctx->vkCreateShaderModule(
+        *ctx->logical_device,
+        &shaderModuleCreateInfo,
+        NULL,
+        &shader->shaderModule
+    );
+
+    if (result != VK_SUCCESS)
+    {
+        // FIXME: should display VK error code
+        set_error("Error when creating VkShaderModule");
+        goto compile_shader_fail;
+    } // if
+
+    return shader;
+
+compile_shader_fail:
+    MOJOSHADER_freeParseData(pd);
+    if (shader != NULL)
+    {
+        delete_shader(shader->shaderModule);
+        ctx->free_fn(shader, ctx->malloc_data);
+    } // if
+    return NULL;
+
+} // MOJOSHADER_vkMakeContextCurrent
+
+void MOJOSHADER_vkShaderAddRef(MOJOSHADER_vkShader *shader)
+{
+    if (shader != NULL)
+        shader->refcount++;
+} // MOJOShader_vkShaderAddRef
+
+void MOJOSHADER_vkDeleteShader(MOJOSHADER_vkShader *shader)
+{
+    if (shader != NULL)
+    {
+        if (shader->refcount > 1)
+            shader->refcount--;
+        else
+        {
+            delete_shader(shader->shaderModule);
+            MOJOSHADER_freeParseData(shader->parseData);
+            ctx->free_fn(shader, ctx->malloc_data);
+        } // else
+    } // if
+} // MOJOSHADER_vkDeleteShader
+
+const MOJOSHADER_parseData *MOJOSHADER_vkGetShaderParseData(
+    MOJOSHADER_vkShader *shader
+) {
+    return (shader != NULL) ? shader->parseData : NULL;
+} // MOJOSHADER_vkGetShaderParseData
+
+void MOJOSHADER_vkBindShaders(MOJOSHADER_vkShader *vshader,
+                              MOJOSHADER_vkShader *pshader)
+{
+    /* NOOP if shader is null */
+
+    if (vshader != NULL)
+        ctx->vertexShader = vshader;
+    if (pshader != NULL)
+        ctx->pixelShader = pshader;
+} // MOJOSHADER_vkBindShaders
+
+void MOJOSHADER_vkGetBoundShaders(MOJOSHADER_vkShader **vshader,
+                                  MOJOSHADER_vkShader **pshader)
+{
+    *vshader = ctx->vertexShader;
+    *pshader = ctx->pixelShader;
+} // MOJOSHADER_vkGetBoundShaders
+
+void MOJOSHADER_vkMapUniformBufferMemory(float **vsf, int **vsi, unsigned char **vsb,
+                                         float **psf, int **psi, unsigned char **psb)
+{
+    *vsf = ctx->vs_reg_file_f;
+    *vsi = ctx->vs_reg_file_i;
+    *vsb = ctx->vs_reg_file_b;
+    *psf = ctx->ps_reg_file_f;
+    *psi = ctx->ps_reg_file_i;
+    *psb = ctx->ps_reg_file_b;
+} // MOJOSHADER_vkMapUniformBufferMemory
+
+void MOJOSHADER_vkUnmapUniformBufferMemory()
+{
+    /* Why is this function named unmap instead of update?
+     * the world may never know...
+     */
+
+    update_uniform_buffer(ctx->vertexShader);
+    update_uniform_buffer(ctx->pixelShader);
+} // MOJOSHADER_vkUnmapUniformBufferMemory
+
+void MOJOSHADER_vkGetUniformBuffers(VkBuffer *vbuf, unsigned long long *voff, unsigned long long *vsize,
+                                    VkBuffer *pbuf, unsigned long long *poff, unsigned long long *psize)
+{
+    *vbuf = get_uniform_buffer(ctx->vertexShader);
+    *voff = get_uniform_offset(ctx->vertexShader);
+    *vsize = get_uniform_size(ctx->vertexShader);
+    *pbuf = get_uniform_buffer(ctx->pixelShader);
+    *poff = get_uniform_offset(ctx->pixelShader);
+    *psize = get_uniform_size(ctx->pixelShader);
+} // MOJOSHADER_vkGetUniformBuffers
+
+void MOJOSHADER_vkEndFrame()
+{
+    int32_t i;
+    ctx->currentFrame = (ctx->currentFrame + 1) % ctx->frames_in_flight;
+    for (i = 0; i < ctx->uboBufferCount; i++)
+    {
+        if (ctx->vertUboBuffers[i]->full == ctx->currentFrame)
+        {
+            ctx->vertUboBuffers[i]->dynamicOffset = 0;
+            ctx->vertUboBuffers[i]->currentBlockSize = 0;
+            ctx->vertUboBuffers[i]->full = -1;
+        } // if
+
+        if (ctx->fragUboBuffers[i]->full == ctx->currentFrame)
+        {
+            ctx->fragUboBuffers[i]->dynamicOffset = 0;
+            ctx->fragUboBuffers[i]->currentBlockSize = 0;
+            ctx->fragUboBuffers[i]->full = -1;
+        } // if
+    } // for
+} // MOJOSHADER_VkEndFrame
+
+int MOJOSHADER_vkGetVertexAttribLocation(MOJOSHADER_vkShader *vert,
+                                         MOJOSHADER_usage usage, int index)
+{
+    int32_t i;
+    if (vert == NULL)
+        return -1;
+
+    for (i = 0; i < vert->parseData->attribute_count; i++)
+    {
+        if (vert->parseData->attributes[i].usage == usage &&
+            vert->parseData->attributes[i].index == index)
+        {
+            return i;
+        } // if
+    } // for
+
+    // failure
+    return -1;
+} //MOJOSHADER_vkGetVertexAttribLocation
+
+unsigned long long MOJOSHADER_vkGetShaderModule(MOJOSHADER_vkShader *shader)
+{
+    if (shader == NULL)
+        return 0;
+
+    return (unsigned long long) shader->shaderModule;
+} //MOJOSHADER_vkGetShaderModule
+
+const char *MOJOSHADER_vkGetError(void)
+{
+    return error_buffer;
+} // MOJOSHADER_vkGetError
+
+#endif /* SUPPORT_PROFILE_SPIRV */
+
+// end of mojoshader_vulkan.c ...
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mojoshader_vulkan_vkfuncs.h	Wed Jul 01 04:29:09 2020 -0400
@@ -0,0 +1,41 @@
+/**
+ * MojoShader; generate shader programs from bytecode of compiled
+ *  Direct3D shaders.
+ *
+ * Please see the file LICENSE.txt in the source's root directory.
+ *
+ *  This file written by Ryan C. Gordon.
+ */
+
+/*
+ * vkInstance, created by global vkCreateInstance function
+ */
+
+#ifndef VULKAN_INSTANCE_FUNCTION
+#define VULKAN_INSTANCE_FUNCTION(ext, ret, func, params)
+#endif
+VULKAN_INSTANCE_FUNCTION(void, vkGetPhysicalDeviceMemoryProperties, (VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties *pMemoryProperties))
+
+/*
+ * vkDevice, created by a vkInstance
+ */
+
+#ifndef VULKAN_DEVICE_FUNCTION
+#define VULKAN_DEVICE_FUNCTION(ext, ret, func, params)
+#endif
+VULKAN_DEVICE_FUNCTION(VkResult, vkAllocateMemory, (VkDevice device, const VkMemoryAllocateInfo *pAllocateInfo, const VkAllocationCallbacks *pAllocator, VkDeviceMemory *pMemory))
+VULKAN_DEVICE_FUNCTION(VkResult, vkBindBufferMemory, (VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize memoryOffset))
+VULKAN_DEVICE_FUNCTION(VkResult, vkCreateBuffer, (VkDevice device, const VkBufferCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkBuffer *pBuffer))
+VULKAN_DEVICE_FUNCTION(VkResult, vkCreateShaderModule, (VkDevice device, const VkShaderModuleCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkShaderModule *pShaderModule))
+VULKAN_DEVICE_FUNCTION(void, vkDestroyBuffer, (VkDevice device, VkBuffer buffer, const VkAllocationCallbacks *pAllocator))
+VULKAN_DEVICE_FUNCTION(void, vkDestroyShaderModule, (VkDevice device, VkShaderModule shaderModule, const VkAllocationCallbacks *pAllocator))
+VULKAN_DEVICE_FUNCTION(void, vkFreeMemory, (VkDevice device, VkDeviceMemory memory, const VkAllocationCallbacks *pAllocator))
+VULKAN_DEVICE_FUNCTION(void, vkGetBufferMemoryRequirements, (VkDevice device, VkBuffer buffer, VkMemoryRequirements *pMemoryRequirements))
+VULKAN_DEVICE_FUNCTION(VkResult, vkMapMemory, (VkDevice device, VkDeviceMemory memory, VkDeviceSize offset, VkDeviceSize size, VkMemoryMapFlags flags, void **ppData))
+VULKAN_DEVICE_FUNCTION(void, vkUnmapMemory, (VkDevice device, VkDeviceMemory memory))
+
+/*
+ * Redefine these every time you include this header!
+ */
+#undef VULKAN_INSTANCE_FUNCTION
+#undef VULKAN_DEVICE_FUNCTION