From e9cea7af43b4e7439f55244580000f7a71167be2 Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Wed, 1 Jul 2020 04:29:09 -0400 Subject: [PATCH] Add support for Vulkan rendering. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authors include: - Caleb Cornett - Martin Krošlák - Ethan Lee --- CMakeLists.txt | 5 + mojoshader.h | 288 +++++++++++++ mojoshader_vulkan.c | 802 ++++++++++++++++++++++++++++++++++++ mojoshader_vulkan_vkfuncs.h | 41 ++ 4 files changed, 1136 insertions(+) create mode 100644 mojoshader_vulkan.c create mode 100644 mojoshader_vulkan_vkfuncs.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 219d099f..9aed53a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -164,6 +164,7 @@ ADD_LIBRARY(mojoshader 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 @@ IF(BUILD_SHARED_LIBS) TARGET_LINK_LIBRARIES(mojoshader ${LIBM} ${LOBJC} ${CARBON_FRAMEWORK}) ENDIF(BUILD_SHARED_LIBS) +TARGET_INCLUDE_DIRECTORIES(mojoshader PUBLIC + $ +) + SET_SOURCE_FILES_PROPERTIES( mojoshader_compiler.c PROPERTIES OBJECT_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/mojoshader_parser_hlsl.h" diff --git a/mojoshader.h b/mojoshader.h index bb29d0af..806a5afe 100644 --- a/mojoshader.h +++ b/mojoshader.h @@ -3532,6 +3532,294 @@ DECLSPEC void MOJOSHADER_mtlEndFrame(void); */ 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... */ diff --git a/mojoshader_vulkan.c b/mojoshader_vulkan.c new file mode 100644 index 00000000..e4743f99 --- /dev/null +++ b/mojoshader_vulkan.c @@ -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), + ®F[4 * index], + size * 16 + ); + break; + + case MOJOSHADER_UNIFORM_INT: + memcpy( + contents + (offset * 16), + ®I[4 * index], + size * 16 + ); + break; + + case MOJOSHADER_UNIFORM_BOOL: + memcpy( + contents + offset, + ®B[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 ... diff --git a/mojoshader_vulkan_vkfuncs.h b/mojoshader_vulkan_vkfuncs.h new file mode 100644 index 00000000..a6bd68a8 --- /dev/null +++ b/mojoshader_vulkan_vkfuncs.h @@ -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