From 7985e191b6347a1d54ac15c0a8a1356eb8776a83 Mon Sep 17 00:00:00 2001 From: Caleb Cornett Date: Sun, 12 Jan 2020 11:39:49 -0500 Subject: [PATCH] Metal Effect support! --- CMakeLists.txt | 1 + mojoshader.h | 50 +++ mojoshader_effects.h | 113 +++++ mojoshader_metal.c | 1002 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1166 insertions(+) create mode 100644 mojoshader_metal.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 0026036e..aafd54f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -162,6 +162,7 @@ ADD_LIBRARY(mojoshader ${LIBRARY_FORMAT} mojoshader.c mojoshader_common.c mojoshader_opengl.c + mojoshader_metal.c profiles/mojoshader_profile_arb1.c profiles/mojoshader_profile_bytecode.c profiles/mojoshader_profile_d3d.c diff --git a/mojoshader.h b/mojoshader.h index 548138ca..7fb4324d 100644 --- a/mojoshader.h +++ b/mojoshader.h @@ -3252,6 +3252,56 @@ DECLSPEC void MOJOSHADER_glDeleteShader(MOJOSHADER_glShader *shader); */ DECLSPEC void MOJOSHADER_glDestroyContext(MOJOSHADER_glContext *ctx); + +/* Metal interface... */ + +typedef struct MOJOSHADER_mtlShader MOJOSHADER_mtlShader; + +/* + * Get the MTLFunction* from the given MOJOSHADER_mtlShader. + * + * This function calls [retain] on the MTLFunction* before returning! + * Please call [release] on the result when you no longer need it. + */ +DECLSPEC void *MOJOSHADER_mtlGetFunctionHandle(MOJOSHADER_mtlShader *shader); + +/* + * Swaps uniform buffers and resets offsets to prepare for the next frame. + * + * Always call this after submitting the final command buffer for a frame! + */ +DECLSPEC void MOJOSHADER_mtlEndFrame(); + +/* + * 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 MTLVertexAttributeDescriptor, or -1 if the stream is not used. + */ +int MOJOSHADER_mtlGetVertexAttribLocation(MOJOSHADER_mtlShader *vert, + MOJOSHADER_usage usage, int index); + +/* + * Get any error state we might have picked up, such as failed shader + * compilation. + * + * 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. + */ +DECLSPEC const char *MOJOSHADER_mtlGetError(void); + #ifdef __cplusplus } #endif diff --git a/mojoshader_effects.h b/mojoshader_effects.h index 715d1710..6086c723 100644 --- a/mojoshader_effects.h +++ b/mojoshader_effects.h @@ -800,6 +800,119 @@ DECLSPEC void MOJOSHADER_glEffectEndPass(MOJOSHADER_glEffect *glEffect); */ DECLSPEC void MOJOSHADER_glEffectEnd(MOJOSHADER_glEffect *glEffect); + +/* Metal effect interface... */ + +typedef struct MOJOSHADER_mtlEffect MOJOSHADER_mtlEffect; +typedef struct MOJOSHADER_mtlShader MOJOSHADER_mtlShader; +typedef struct MOJOSHADER_mtlShaderState MOJOSHADER_mtlShaderState; + +/* Fully compile/link the shaders found within the effect. + * + * The MOJOSHADER_mtlEffect* is solely for use within the Metal-specific calls. + * In all other cases you will be using the MOJOSHADER_effect* instead. + * + * In a typical use case, you will be calling this immediately after obtaining + * the MOJOSHADER_effect*. + * + * (effect) is a MOJOSHADER_effect* obtained from MOJOSHADER_parseEffect(). + * (mtlDevice) is a MTLDevice* obtained from a Metal device creation call, + * such as MTLCreateSystemDefaultDevice(). + * (numBackingBuffers) is the number of backing uniform buffers that you + * want to create for each shader. If you are using double-buffering, + * this should be 2; for triple buffering, this should be 3, etc. + * + * This function returns a MOJOSHADER_mtlEffect*, containing Metal-specific + * data for an accompanying MOJOSHADER_effect*. + */ +DECLSPEC MOJOSHADER_mtlEffect *MOJOSHADER_mtlCompileEffect(MOJOSHADER_effect *effect, + void *mtlDevice, + int numBackingBuffers); + +/* Delete the shaders that were allocated for an effect. + * + * (mtlEffect) is a MOJOSHADER_mtlEffect* obtained from + * MOJOSHADER_mtlCompileEffect(). + */ +DECLSPEC void MOJOSHADER_mtlDeleteEffect(MOJOSHADER_mtlEffect *mtlEffect); + +/* Prepare the effect for rendering with the currently applied technique. + * + * This function maps to ID3DXEffect::Begin. + * + * In addition to the expected Begin parameters, we also include a parameter + * to pass in a MOJOSHADER_effectRenderState. Rather than change the render + * state within MojoShader itself we will simply provide what the effect wants + * and allow you to use this information with your own renderer. + * MOJOSHADER_glEffectBeginPass will update with the render state desired by + * the current effect pass. + * + * Note that we only provide the ability to preserve the shader state, but NOT + * the ability to preserve the render/sampler states. You are expected to + * track your own Metal state and restore these states as needed for your + * application. + * + * (mtlEffect) is a MOJOSHADER_mtlEffect* obtained from + * MOJOSHADER_mtlCompileEffect(). + * (numPasses) will be filled with the number of passes that this technique + * will need to fully render. + * (saveShaderState) is a boolean value informing the effect whether or not to + * restore the shader bindings after calling MOJOSHADER_mtlEffectEnd. + * (renderState) will be filled by the effect to inform you of the render state + * changes introduced by the technique and its passes. + */ +DECLSPEC void MOJOSHADER_mtlEffectBegin(MOJOSHADER_mtlEffect *mtlEffect, + unsigned int *numPasses, + int saveShaderState, + MOJOSHADER_effectStateChanges *stateChanges); + +/* Begin an effect pass from the currently applied technique. + * + * This function maps to ID3DXEffect::BeginPass. + * + * (mtlEffect) is a MOJOSHADER_mtlEffect* obtained from + * MOJOSHADER_mtlCompileEffect(). + * (pass) is the index of the effect pass as found in the current technique. + * (state) is a pointer to the current shader state object. + * + * The MOJOSHADER_mtlShaderState pointed to by (shState) must be created + * before calling this function! + */ +DECLSPEC void MOJOSHADER_mtlEffectBeginPass(MOJOSHADER_mtlEffect *mtlEffect, + unsigned int pass, + MOJOSHADER_mtlShaderState *shState); + +/* Push render state changes that occurred within an actively rendering pass. + * + * This function maps to ID3DXEffect::CommitChanges. + * + * (mtlEffect) is a MOJOSHADER_mtlEffect* obtained from + * MOJOSHADER_mtlCompileEffect(). + * (state) is a pointer to the current shader state object. + */ +DECLSPEC void MOJOSHADER_mtlEffectCommitChanges(MOJOSHADER_mtlEffect *mtlEffect, + MOJOSHADER_mtlShaderState *shState); + +/* End an effect pass from the currently applied technique. + * + * This function maps to ID3DXEffect::EndPass. + * + * (mtlEffect) is a MOJOSHADER_mtlEffect* obtained from + * MOJOSHADER_mtlCompileEffect(). + */ +DECLSPEC void MOJOSHADER_mtlEffectEndPass(MOJOSHADER_mtlEffect *mtlEffect); + +/* Complete rendering the effect technique, and restore the render state. + * + * This function maps to ID3DXEffect::End. + * + * (mtlEffect) is a MOJOSHADER_mtlEffect* obtained from + * MOJOSHADER_glCompileEffect(). + * (state) is a pointer to the current shader state object. + */ +DECLSPEC void MOJOSHADER_mtlEffectEnd(MOJOSHADER_mtlEffect *mtlEffect, + MOJOSHADER_mtlShaderState *shState); + #endif /* MOJOSHADER_EFFECT_SUPPORT */ #endif /* MOJOSHADER_EFFECTS_H */ diff --git a/mojoshader_metal.c b/mojoshader_metal.c new file mode 100644 index 00000000..1b99f97b --- /dev/null +++ b/mojoshader_metal.c @@ -0,0 +1,1002 @@ +/** + * 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. + */ + +#if (defined(__APPLE__) && defined(__MACH__)) +#define PLATFORM_APPLE 1 +#include "TargetConditionals.h" +#define OBJC_OLD_DISPATCH_PROTOTYPES 1 +#include +#define objc_msgSend_STR ((void* (*)(void*, void*, const char*))objc_msgSend) +#define objc_msgSend_PTR ((void* (*)(void*, void*, void*))objc_msgSend) +#define objc_msgSend_INT_PTR ((void* (*)(void*, void*, int, void*))objc_msgSend) +#define objc_msgSend_PTR_PTR_PTR ((void* (*)(void*, void*, void*, void*, void*))objc_msgSend) +#endif /* (defined(__APPLE__) && defined(__MACH__)) */ + +#define __MOJOSHADER_INTERNAL__ 1 +#include "mojoshader_internal.h" + +typedef struct MOJOSHADER_mtlUniformBuffer MOJOSHADER_mtlUniformBuffer; +typedef struct MOJOSHADER_mtlShader +{ + const MOJOSHADER_parseData *parseData; + MOJOSHADER_mtlUniformBuffer *ubo; + void *library; // MTLLibrary* + int numInternalBuffers; +} MOJOSHADER_mtlShader; + +// 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 + +// profile-specific implementations... + +#if SUPPORT_PROFILE_METAL && PLATFORM_APPLE +#ifdef MOJOSHADER_EFFECT_SUPPORT + +/* Structs */ + +typedef struct MOJOSHADER_mtlEffect +{ + MOJOSHADER_effect *effect; + unsigned int num_shaders; + MOJOSHADER_mtlShader *shaders; + unsigned int *shader_indices; + unsigned int num_preshaders; + unsigned int *preshader_indices; + MOJOSHADER_mtlShader *current_vert; + MOJOSHADER_mtlShader *current_frag; + MOJOSHADER_effectShader *current_vert_raw; + MOJOSHADER_effectShader *current_frag_raw; + MOJOSHADER_mtlShader *prev_vert; + MOJOSHADER_mtlShader *prev_frag; + void *library; // MTLLibrary* +} MOJOSHADER_mtlEffect; + +typedef struct MOJOSHADER_mtlUniformBuffer +{ + void *device; // MTLDevice* + int bufferSize; + int numInternalBuffers; + void **internalBuffers; // MTLBuffer* + int internalBufferSize; + int internalOffset; + int currentFrame; + int alreadyWritten; +} MOJOSHADER_mtlUniformBuffer; + +typedef struct MOJOSHADER_mtlShaderState +{ + MOJOSHADER_mtlShader *vertexShader; + MOJOSHADER_mtlShader *fragmentShader; + void *vertexUniformBuffer; // MTLBuffer* + void *fragmentUniformBuffer; // MTLBuffer* + int vertexUniformOffset; + int fragmentUniformOffset; +} MOJOSHADER_mtlShaderState; + +/* Objective-C selector references */ + +static void *classNSString = NULL; +static void *selAlloc = NULL; +static void *selInitWithUTF8String = NULL; +static void *selUTF8String = NULL; +static void *selLength = NULL; +static void *selContents = NULL; +static void *selNewBufferWithLength = NULL; +static void *selRelease = NULL; +static void *selNewLibraryWithSource = NULL; +static void *selLocalizedDescription = NULL; +static void *selNewFunctionWithName = NULL; +static void *selRetain = NULL; + +/* Helper functions */ + +static void initSelectors(void) +{ + classNSString = (void*) objc_getClass("NSString"); + selAlloc = sel_registerName("alloc"); + selInitWithUTF8String = sel_registerName("initWithUTF8String:"); + selUTF8String = sel_registerName("UTF8String"); + selLength = sel_registerName("length"); + selContents = sel_registerName("contents"); + selNewBufferWithLength = sel_registerName("newBufferWithLength:options:"); + selRelease = sel_registerName("release"); + selNewLibraryWithSource = sel_registerName("newLibraryWithSource:options:error:"); + selLocalizedDescription = sel_registerName("localizedDescription"); + selNewFunctionWithName = sel_registerName("newFunctionWithName:"); + selRetain = sel_registerName("retain"); +} // initSelectors + +static void *cstr_to_nsstr(const char *str) +{ + return objc_msgSend_STR( + objc_msgSend(classNSString, selAlloc), + selInitWithUTF8String, + str + ); +} // cstr_to_nsstr + +static const char *nsstr_to_cstr(void *str) +{ + return (char *) objc_msgSend(str, selUTF8String); +} // nssstr_to_cstr + +/* Linked list */ + +typedef struct LLNODE { + MOJOSHADER_mtlUniformBuffer *data; + struct LLNODE *next; +} LLNODE; + +static LLNODE *LL_append_node(LLNODE **baseNode, + MOJOSHADER_malloc m, + void *d) +{ + LLNODE *prev = NULL; + LLNODE *node = *baseNode; + + /* Append a node to the linked list. */ + while (node != NULL) + { + prev = node; + node = node->next; + } // while + node = m(sizeof(LLNODE), d); + node->next = NULL; + + /* Connect the old to the new. */ + if (prev != NULL) + prev->next = node; + + /* Special case for the first node. */ + if (*baseNode == NULL) + *baseNode = node; + + return node; +} // LL_append_node + +static void LL_remove_node(LLNODE **baseNode, + MOJOSHADER_mtlUniformBuffer *data, + MOJOSHADER_free f, + void *d) +{ + LLNODE *prev = NULL; + LLNODE *node = *baseNode; + + /* Search for node with matching data pointer. */ + while (node != NULL && node->data != data) + { + prev = node; + node = node->next; + } // while + + if (node == NULL) + { + /* This should never happen. */ + assert(0); + } // if + + /* Clear data pointer. The data must be freed separately. */ + node->data = NULL; + + /* Connect the old to the new. */ + if (prev != NULL) + prev->next = node->next; + + /* Special cases where the first node is removed. */ + if (prev == NULL) + *baseNode = (node->next != NULL) ? node->next : NULL; + + /* Free the node! */ + f(node, d); +} // LL_remove_node + +/* Internal register utilities */ + +// 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 + +// 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]; +int vs_reg_file_i[MAX_REG_FILE_I * 4]; +uint8 vs_reg_file_b[MAX_REG_FILE_B]; +float ps_reg_file_f[MAX_REG_FILE_F * 4]; +int ps_reg_file_i[MAX_REG_FILE_I * 4]; +uint8 ps_reg_file_b[MAX_REG_FILE_B]; + +static inline void copy_parameter_data(MOJOSHADER_effectParam *params, + unsigned int *param_loc, + MOJOSHADER_symbol *symbols, + unsigned int symbol_count, + float *regf, int *regi, uint8 *regb) +{ + int i, j, r, c; + + i = 0; + for (i = 0; i < symbol_count; i++) + { + const MOJOSHADER_symbol *sym = &symbols[i]; + const MOJOSHADER_effectValue *param = ¶ms[param_loc[i]].value; + + // float/int registers are vec4, so they have 4 elements each + const uint32 start = sym->register_index << 2; + + if (param->type.parameter_type == MOJOSHADER_SYMTYPE_FLOAT) + memcpy(regf + start, param->valuesF, sym->register_count << 4); + else if (sym->register_set == MOJOSHADER_SYMREGSET_FLOAT4) + { + // Structs are a whole different world... + if (param->type.parameter_class == MOJOSHADER_SYMCLASS_STRUCT) + memcpy(regf + start, param->valuesF, sym->register_count << 4); + else + { + // Sometimes int/bool parameters get thrown into float registers... + j = 0; + do + { + c = 0; + do + { + regf[start + (j << 2) + c] = (float) param->valuesI[(j << 2) + c]; + } while (++c < param->type.columns); + } while (++j < sym->register_count); + } // else + } // else if + else if (sym->register_set == MOJOSHADER_SYMREGSET_INT4) + memcpy(regi + start, param->valuesI, sym->register_count << 4); + else if (sym->register_set == MOJOSHADER_SYMREGSET_BOOL) + { + j = 0; + r = 0; + do + { + c = 0; + do + { + // regb is not a vec4, enjoy that 'start' bitshift! -flibit + regb[(start >> 2) + r + c] = param->valuesI[(j << 2) + c]; + c++; + } while (c < param->type.columns && ((r + c) < sym->register_count)); + r += c; + j++; + } while (r < sym->register_count); + } // else if + } // for +} // copy_parameter_data + +/* Uniform buffer utilities */ + +static inline int next_highest_alignment(int n) +{ + #if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_SIMULATOR + int align = 16; + #else + int align = 256; + #endif + + return align * ((n + align - 1) / align); +} // next_highest_alignment + +static int UBO_buffer_length(void *buffer) +{ + return (int) objc_msgSend(buffer, selLength); +} // UBO_buffer_length + +static void *UBO_buffer_contents(void *buffer) +{ + return (void *) objc_msgSend(buffer, selContents); +} // UBO_buffer_contents + +static void *UBO_create_backing_buffer(MOJOSHADER_mtlUniformBuffer *ubo, int f) +{ + void *oldBuffer = ubo->internalBuffers[f]; + void *newBuffer = objc_msgSend_INT_PTR( + ubo->device, + selNewBufferWithLength, + ubo->internalBufferSize, + NULL + ); + if (oldBuffer != NULL) + { + // Copy over data from old buffer + memcpy( + UBO_buffer_contents(newBuffer), + UBO_buffer_contents(oldBuffer), + UBO_buffer_length(oldBuffer) + ); + + // Free the old buffer + objc_msgSend(oldBuffer, selRelease); + } //if + + return newBuffer; +} // UBO_create_backing_buffer + +static void UBO_predraw(MOJOSHADER_mtlUniformBuffer *ubo) +{ + if (!ubo->alreadyWritten) + { + ubo->alreadyWritten = 1; + return; + } // if + + ubo->internalOffset += ubo->bufferSize; + + int buflen = UBO_buffer_length(ubo->internalBuffers[ubo->currentFrame]); + if (ubo->internalOffset >= buflen) + { + // Double capacity when we're out of room + if (ubo->internalOffset >= ubo->internalBufferSize) + ubo->internalBufferSize *= 2; + + ubo->internalBuffers[ubo->currentFrame] = + UBO_create_backing_buffer(ubo, ubo->currentFrame); + } //if +} // UBO_predraw + +static void UBO_end_frame(MOJOSHADER_mtlUniformBuffer *ubo) +{ + ubo->internalOffset = 0; + ubo->currentFrame = (ubo->currentFrame + 1) % ubo->numInternalBuffers; + ubo->alreadyWritten = 0; +} // UBO_end_frame + +LLNODE *ubos = NULL; /* global linked list of all active UBOs */ + +static MOJOSHADER_mtlUniformBuffer *create_ubo(MOJOSHADER_mtlShader *shader, + void *mtlDevice, + MOJOSHADER_malloc m, + void *d) +{ + int uniformCount = shader->parseData->uniform_count; + if (uniformCount == 0) + return NULL; + + // Calculate how big we need to make the buffer + int buflen = 0; + for (int i = 0; i < uniformCount; i += 1) + { + int arrayCount = shader->parseData->uniforms[i].array_count; + int uniformSize = 16; + if (shader->parseData->uniforms[i].type == MOJOSHADER_UNIFORM_BOOL) + uniformSize = 1; + buflen += (arrayCount ? arrayCount : 1) * uniformSize; + } // for + + // Make the UBO + MOJOSHADER_mtlUniformBuffer *ubo = (MOJOSHADER_mtlUniformBuffer *) m(sizeof(MOJOSHADER_mtlUniformBuffer), d); + ubo->device = mtlDevice; + ubo->alreadyWritten = 0; + ubo->bufferSize = next_highest_alignment(buflen); + ubo->currentFrame = 0; + ubo->numInternalBuffers = shader->numInternalBuffers; + ubo->internalBufferSize = ubo->bufferSize * 16; // pre-allocate some extra room! + ubo->internalBuffers = m(ubo->numInternalBuffers * sizeof(void*), d); + ubo->internalOffset = 0; + for (int i = 0; i < ubo->numInternalBuffers; i++) + { + ubo->internalBuffers[i] = NULL; + ubo->internalBuffers[i] = UBO_create_backing_buffer(ubo, i); + } // for + + /* Add the UBO to the global list so it can be updated. */ + LLNODE *node = LL_append_node(&ubos, m, d); + node->data = ubo; + + return ubo; +} // create_ubo + +static void dealloc_ubo(MOJOSHADER_mtlShader *shader, + MOJOSHADER_free f, + void* d) +{ + if (shader->ubo == NULL) + return; + + LL_remove_node(&ubos, shader->ubo, f, d); + for (int i = 0; i < shader->ubo->numInternalBuffers; i++) + { + objc_msgSend(shader->ubo->internalBuffers[i], selRelease); + shader->ubo->internalBuffers[i] = NULL; + } // for + + f(shader->ubo->internalBuffers, d); + f(shader->ubo, d); +} // dealloc_ubo + +static void *get_uniform_buffer(MOJOSHADER_mtlShader *shader) +{ + if (shader == NULL || shader->ubo == NULL) + return NULL; + + return shader->ubo->internalBuffers[shader->ubo->currentFrame]; +} // get_uniform_buffer + +static int get_uniform_offset(MOJOSHADER_mtlShader *shader) +{ + if (shader == NULL || shader->ubo == NULL) + return 0; + + return shader->ubo->internalOffset; +} // get_uniform_offset + +static void update_uniform_buffer(MOJOSHADER_mtlShader *shader) +{ + if (shader == NULL || shader->ubo == NULL) + return; + + float *regF; int *regI; uint8 *regB; + if (shader->parseData->shader_type == MOJOSHADER_TYPE_VERTEX) + { + regF = vs_reg_file_f; + regI = vs_reg_file_i; + regB = vs_reg_file_b; + } // if + else + { + regF = ps_reg_file_f; + regI = ps_reg_file_i; + regB = ps_reg_file_b; + } // else + + UBO_predraw(shader->ubo); + void *buf = shader->ubo->internalBuffers[shader->ubo->currentFrame]; + void *contents = UBO_buffer_contents(buf) + shader->ubo->internalOffset; + + int offset = 0; + for (int i = 0; i < shader->parseData->uniform_count; i++) + { + int idx = shader->parseData->uniforms[i].index; + int arrayCount = shader->parseData->uniforms[i].array_count; + int size = arrayCount ? arrayCount : 1; + + switch (shader->parseData->uniforms[i].type) + { + case MOJOSHADER_UNIFORM_FLOAT: + memcpy( + contents + (offset * 16), + ®F[4 * idx], + size * 16 + ); + break; + + case MOJOSHADER_UNIFORM_INT: + // !!! FIXME: Need a test case + memcpy( + contents + (offset * 16), + ®I[4 * idx], + size * 16 + ); + break; + + case MOJOSHADER_UNIFORM_BOOL: + // !!! FIXME: Need a test case + memcpy( + contents + offset, + ®B[idx], + size + ); + break; + + default: + assert(0); // This should never happen. + break; + } // switch + + offset += size; + } // for +} // update_uniform_buffer + +/* Public API */ + +MOJOSHADER_mtlEffect *MOJOSHADER_mtlCompileEffect(MOJOSHADER_effect *effect, + void *mtlDevice, + int numBackingBuffers) +{ + int i; + MOJOSHADER_malloc m = effect->malloc; + MOJOSHADER_free f = effect->free; + void *d = effect->malloc_data; + int current_shader = 0; + int current_preshader = 0; + int src_len = 0; + + // Make sure the Objective-C selectors have been initialized... + if (selAlloc == NULL) + initSelectors(); + + MOJOSHADER_mtlEffect *retval = (MOJOSHADER_mtlEffect *) m(sizeof (MOJOSHADER_mtlEffect), d); + if (retval == NULL) + { + out_of_memory(); + return NULL; + } // if + memset(retval, '\0', sizeof (MOJOSHADER_mtlEffect)); + + // Count the number of shaders before allocating + for (i = 0; i < effect->object_count; i++) + { + MOJOSHADER_effectObject *object = &effect->objects[i]; + if (object->type == MOJOSHADER_SYMTYPE_PIXELSHADER + || object->type == MOJOSHADER_SYMTYPE_VERTEXSHADER) + { + if (object->shader.is_preshader) + retval->num_preshaders++; + else + { + retval->num_shaders++; + src_len += object->shader.shader->output_len; + } // else + } // if + } // for + + // Alloc shader source buffer + char *shader_source = (char *) m(src_len + 1, d); + memset(shader_source, '\0', src_len + 1); + int src_pos = 0; + + // Copy all the source text into the buffer + for (i = 0; i < effect->object_count; i++) + { + MOJOSHADER_effectObject *object = &effect->objects[i]; + if (object->type == MOJOSHADER_SYMTYPE_PIXELSHADER + || object->type == MOJOSHADER_SYMTYPE_VERTEXSHADER) + { + if (!object->shader.is_preshader) + { + int output_len = object->shader.shader->output_len; + memcpy(&shader_source[src_pos], object->shader.shader->output, output_len); + src_pos += output_len; + } // if + } // if + } // for + + // Handle texcoord0 -> point_coord conversion + if (strstr(shader_source, "[[point_size]]")) + { + // !!! FIXME: This assumes all texcoord0 attributes in the effect are + // !!! FIXME: actually point coords! It ain't necessarily so! -caleb + const char *repl = "[[ point_coord ]]"; + char *ptr; + while ((ptr = strstr(shader_source, "[[user(texcoord0)]]"))) + { + memcpy(ptr, repl, strlen(repl)); + + // float4 -> float2 + int spaces = 0; + while (spaces < 2) + if (*(ptr--) == ' ') + spaces++; + memcpy(ptr, "2", sizeof(char)); + } // while + } // if + + // Alloc shader information + retval->shaders = (MOJOSHADER_mtlShader *) m(retval->num_shaders * sizeof (MOJOSHADER_mtlShader), d); + if (retval->shaders == NULL) + { + f(retval, d); + out_of_memory(); + return NULL; + } // if + memset(retval->shaders, '\0', retval->num_shaders * sizeof (MOJOSHADER_mtlShader)); + retval->shader_indices = (unsigned int *) m(retval->num_shaders * sizeof (unsigned int), d); + if (retval->shader_indices == NULL) + { + f(retval->shaders, d); + f(retval, d); + out_of_memory(); + return NULL; + } // if + memset(retval->shader_indices, '\0', retval->num_shaders * sizeof (unsigned int)); + + // Alloc preshader information + if (retval->num_preshaders > 0) + { + retval->preshader_indices = (unsigned int *) m(retval->num_preshaders * sizeof (unsigned int), d); + if (retval->preshader_indices == NULL) + { + f(retval->shaders, d); + f(retval->shader_indices, d); + f(retval, d); + out_of_memory(); + return NULL; + } // if + memset(retval->preshader_indices, '\0', retval->num_preshaders * sizeof (unsigned int)); + } // if + + // Compile the source into a library + void *compileError = NULL; + void *shader_source_ns = cstr_to_nsstr(shader_source); + void *library = objc_msgSend_PTR_PTR_PTR( + mtlDevice, + selNewLibraryWithSource, + shader_source_ns, + NULL, + &compileError + ); + retval->library = library; + f(shader_source, d); + objc_msgSend(shader_source_ns, selRelease); + + if (library == NULL) + { + // Set the error + void *error_nsstr = objc_msgSend(compileError, selLocalizedDescription); + set_error(nsstr_to_cstr(error_nsstr)); + + goto compile_shader_fail; + } // if + + // Run through the shaders again, tracking the object indices + for (i = 0; i < effect->object_count; i++) + { + MOJOSHADER_effectObject *object = &effect->objects[i]; + if (object->type == MOJOSHADER_SYMTYPE_PIXELSHADER + || object->type == MOJOSHADER_SYMTYPE_VERTEXSHADER) + { + if (object->shader.is_preshader) + { + retval->preshader_indices[current_preshader++] = i; + continue; + } // if + + MOJOSHADER_mtlShader *curshader = &retval->shaders[current_shader]; + curshader->parseData = object->shader.shader; + curshader->numInternalBuffers = numBackingBuffers; + curshader->ubo = create_ubo(curshader, mtlDevice, m, d); + curshader->library = library; + + retval->shader_indices[current_shader] = i; + + current_shader++; + } // if + } // for + + retval->effect = effect; + return retval; + +compile_shader_fail: + f(retval->shader_indices, d); + f(retval->shaders, d); + f(retval, d); + return NULL; +} // MOJOSHADER_mtlCompileEffect + +void MOJOSHADER_mtlDeleteEffect(MOJOSHADER_mtlEffect *mtlEffect) +{ + MOJOSHADER_free f = mtlEffect->effect->free; + void *d = mtlEffect->effect->malloc_data; + + int i; + for (i = 0; i < mtlEffect->num_shaders; i++) + { + /* Release the uniform buffers */ + dealloc_ubo(&mtlEffect->shaders[i], f, d); + } // for + + /* Release the library */ + objc_msgSend(mtlEffect->library, selRelease); + + f(mtlEffect->shader_indices, d); + f(mtlEffect->preshader_indices, d); + f(mtlEffect, d); +} // MOJOSHADER_mtlDeleteEffect + + +void MOJOSHADER_mtlEffectBegin(MOJOSHADER_mtlEffect *mtlEffect, + unsigned int *numPasses, + int saveShaderState, + MOJOSHADER_effectStateChanges *stateChanges) +{ + *numPasses = mtlEffect->effect->current_technique->pass_count; + mtlEffect->effect->restore_shader_state = saveShaderState; + mtlEffect->effect->state_changes = stateChanges; + + if (mtlEffect->effect->restore_shader_state) + { + mtlEffect->prev_vert = mtlEffect->current_vert; + mtlEffect->prev_frag = mtlEffect->current_frag; + } // if +} // MOJOSHADER_mtlEffectBegin + +// Predeclare +void MOJOSHADER_mtlEffectCommitChanges(MOJOSHADER_mtlEffect *mtlEffect, + MOJOSHADER_mtlShaderState *shState); + +void MOJOSHADER_mtlEffectBeginPass(MOJOSHADER_mtlEffect *mtlEffect, + unsigned int pass, + MOJOSHADER_mtlShaderState *shState) +{ + int i, j; + MOJOSHADER_effectPass *curPass; + MOJOSHADER_effectState *state; + MOJOSHADER_effectShader *rawVert = mtlEffect->current_vert_raw; + MOJOSHADER_effectShader *rawFrag = mtlEffect->current_frag_raw; + int has_preshader = 0; + + assert(shState != NULL); + assert(mtlEffect->effect->current_pass == -1); + mtlEffect->effect->current_pass = pass; + curPass = &mtlEffect->effect->current_technique->passes[pass]; + + // !!! FIXME: I bet this could be stored at parse/compile time. -flibit + for (i = 0; i < curPass->state_count; i++) + { + state = &curPass->states[i]; + #define ASSIGN_SHADER(stype, raw, mtls) \ + (state->type == stype) \ + { \ + j = 0; \ + do \ + { \ + if (*state->value.valuesI == mtlEffect->shader_indices[j]) \ + { \ + raw = &mtlEffect->effect->objects[*state->value.valuesI].shader; \ + mtlEffect->mtls = &mtlEffect->shaders[j]; \ + break; \ + } \ + else if (mtlEffect->num_preshaders > 0 \ + && *state->value.valuesI == mtlEffect->preshader_indices[j]) \ + { \ + raw = &mtlEffect->effect->objects[*state->value.valuesI].shader; \ + has_preshader = 1; \ + break; \ + } \ + } while (++j < mtlEffect->num_shaders); \ + } + if ASSIGN_SHADER(MOJOSHADER_RS_VERTEXSHADER, rawVert, current_vert) + else if ASSIGN_SHADER(MOJOSHADER_RS_PIXELSHADER, rawFrag, current_frag) + #undef ASSIGN_SHADER + } // for + + mtlEffect->effect->state_changes->render_state_changes = curPass->states; + mtlEffect->effect->state_changes->render_state_change_count = curPass->state_count; + + mtlEffect->current_vert_raw = rawVert; + mtlEffect->current_frag_raw = rawFrag; + + /* If this effect pass has an array of shaders, we get to wait until + * CommitChanges to actually bind the final shaders. + * -flibit + */ + if (!has_preshader) + { + if (mtlEffect->current_vert != NULL) + { + MOJOSHADER_mtlShader *vert = mtlEffect->current_vert; + shState->vertexShader = vert; + shState->vertexUniformBuffer = get_uniform_buffer(vert); + shState->vertexUniformOffset = get_uniform_offset(vert); + } // if + + if (mtlEffect->current_frag != NULL) + { + MOJOSHADER_mtlShader *frag = mtlEffect->current_frag; + shState->fragmentShader = frag; + shState->fragmentUniformBuffer = get_uniform_buffer(frag); + shState->fragmentUniformOffset = get_uniform_offset(frag); + } // if + + if (mtlEffect->current_vert_raw != NULL) + { + mtlEffect->effect->state_changes->vertex_sampler_state_changes = rawVert->samplers; + mtlEffect->effect->state_changes->vertex_sampler_state_change_count = rawVert->sampler_count; + } // if + if (mtlEffect->current_frag_raw != NULL) + { + mtlEffect->effect->state_changes->sampler_state_changes = rawFrag->samplers; + mtlEffect->effect->state_changes->sampler_state_change_count = rawFrag->sampler_count; + } // if + } // if + + MOJOSHADER_mtlEffectCommitChanges(mtlEffect, shState); +} // MOJOSHADER_mtlEffectBeginPass + +void MOJOSHADER_mtlEffectCommitChanges(MOJOSHADER_mtlEffect *mtlEffect, + MOJOSHADER_mtlShaderState *shState) +{ + MOJOSHADER_effectShader *rawVert = mtlEffect->current_vert_raw; + MOJOSHADER_effectShader *rawFrag = mtlEffect->current_frag_raw; + + /* Used for shader selection from preshaders */ + int i, j; + MOJOSHADER_effectValue *param; + float selector; + int shader_object; + int selector_ran = 0; + + /* For effect passes with arrays of shaders, we have to run a preshader + * that determines which shader to use, based on a parameter's value. + * -flibit + */ + // !!! FIXME: We're just running the preshaders every time. Blech. -flibit + #define SELECT_SHADER_FROM_PRESHADER(raw, mtls) \ + if (raw != NULL && raw->is_preshader) \ + { \ + i = 0; \ + do \ + { \ + param = &mtlEffect->effect->params[raw->preshader_params[i]].value; \ + for (j = 0; j < (param->value_count >> 2); j++) \ + memcpy(raw->preshader->registers + raw->preshader->symbols[i].register_index + j, \ + param->valuesI + (j << 2), \ + param->type.columns << 2); \ + } while (++i < raw->preshader->symbol_count); \ + MOJOSHADER_runPreshader(raw->preshader, &selector); \ + shader_object = mtlEffect->effect->params[raw->params[0]].value.valuesI[(int) selector]; \ + raw = &mtlEffect->effect->objects[shader_object].shader; \ + i = 0; \ + do \ + { \ + if (shader_object == mtlEffect->shader_indices[i]) \ + { \ + mtls = &mtlEffect->shaders[i]; \ + break; \ + } \ + } while (++i < mtlEffect->num_shaders); \ + selector_ran = 1; \ + } + SELECT_SHADER_FROM_PRESHADER(rawVert, mtlEffect->current_vert) + SELECT_SHADER_FROM_PRESHADER(rawFrag, mtlEffect->current_frag) + #undef SELECT_SHADER_FROM_PRESHADER + if (selector_ran) + { + if (mtlEffect->current_vert != NULL) + shState->vertexShader = mtlEffect->current_vert; + + if (mtlEffect->current_frag != NULL) + shState->fragmentShader = mtlEffect->current_frag; + + if (mtlEffect->current_vert_raw != NULL) + { + mtlEffect->effect->state_changes->vertex_sampler_state_changes = rawVert->samplers; + mtlEffect->effect->state_changes->vertex_sampler_state_change_count = rawVert->sampler_count; + } // if + if (mtlEffect->current_frag_raw != NULL) + { + mtlEffect->effect->state_changes->sampler_state_changes = rawFrag->samplers; + mtlEffect->effect->state_changes->sampler_state_change_count = rawFrag->sampler_count; + } // if + } // if + + /* This is where parameters are copied into the constant buffers. + * If you're looking for where things slow down immensely, look at + * the copy_parameter_data() and MOJOSHADER_runPreshader() functions. + * -flibit + */ + // !!! FIXME: We're just copying everything every time. Blech. -flibit + // !!! FIXME: We're just running the preshaders every time. Blech. -flibit + // !!! FIXME: Will the preshader ever want int/bool registers? -flibit + #define COPY_PARAMETER_DATA(raw, stage) \ + if (raw != NULL) \ + { \ + copy_parameter_data(mtlEffect->effect->params, raw->params, \ + raw->shader->symbols, \ + raw->shader->symbol_count, \ + stage##_reg_file_f, \ + stage##_reg_file_i, \ + stage##_reg_file_b); \ + if (raw->shader->preshader) \ + { \ + copy_parameter_data(mtlEffect->effect->params, raw->preshader_params, \ + raw->shader->preshader->symbols, \ + raw->shader->preshader->symbol_count, \ + raw->shader->preshader->registers, \ + NULL, \ + NULL); \ + MOJOSHADER_runPreshader(raw->shader->preshader, stage##_reg_file_f); \ + } \ + } + COPY_PARAMETER_DATA(rawVert, vs) + COPY_PARAMETER_DATA(rawFrag, ps) + #undef COPY_PARAMETER_DATA + + update_uniform_buffer(shState->vertexShader); + shState->vertexUniformBuffer = get_uniform_buffer(shState->vertexShader); + shState->vertexUniformOffset = get_uniform_offset(shState->vertexShader); + + update_uniform_buffer(shState->fragmentShader); + shState->fragmentUniformBuffer = get_uniform_buffer(shState->fragmentShader); + shState->fragmentUniformOffset = get_uniform_offset(shState->fragmentShader); +} // MOJOSHADER_mtlEffectCommitChanges + + +void MOJOSHADER_mtlEffectEndPass(MOJOSHADER_mtlEffect *mtlEffect) +{ + assert(mtlEffect->effect->current_pass != -1); + mtlEffect->effect->current_pass = -1; +} // MOJOSHADER_mtlEffectEndPass + + +void MOJOSHADER_mtlEffectEnd(MOJOSHADER_mtlEffect *mtlEffect, + MOJOSHADER_mtlShaderState *shState) +{ + if (mtlEffect->effect->restore_shader_state) + { + mtlEffect->effect->restore_shader_state = 0; + shState->vertexShader = mtlEffect->prev_vert; + shState->fragmentShader = mtlEffect->prev_frag; + shState->vertexUniformBuffer = get_uniform_buffer(mtlEffect->prev_vert); + shState->fragmentUniformBuffer = get_uniform_buffer(mtlEffect->prev_frag); + shState->vertexUniformOffset = get_uniform_offset(mtlEffect->prev_vert); + shState->fragmentUniformOffset = get_uniform_offset(mtlEffect->prev_frag); + } // if + + mtlEffect->effect->state_changes = NULL; +} // MOJOSHADER_mtlEffectEnd + +void *MOJOSHADER_mtlGetFunctionHandle(MOJOSHADER_mtlShader *shader) +{ + if (shader == NULL) + return NULL; + + void *fnname = cstr_to_nsstr(shader->parseData->mainfn); + void *ret = objc_msgSend_PTR( + shader->library, + selNewFunctionWithName, + fnname + ); + objc_msgSend(fnname, selRelease); + objc_msgSend(ret, selRetain); + + return ret; +} // MOJOSHADER_mtlGetFunctionHandle + +void MOJOSHADER_mtlEndFrame() +{ + LLNODE *node = ubos; + while (node != NULL) + { + UBO_end_frame((MOJOSHADER_mtlUniformBuffer *) node->data); + node = node->next; + } // while +} // MOJOSHADER_mtlEndFrame + +int MOJOSHADER_mtlGetVertexAttribLocation(MOJOSHADER_mtlShader *vert, + MOJOSHADER_usage usage, int index) +{ + if (vert == NULL) + return -1; + + for (int 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, couldn't find requested attribute + return -1; +} // MOJOSHADER_mtlGetVertexAttribLocation + +const char *MOJOSHADER_mtlGetError(void) +{ + return error_buffer; +} // MOJOSHADER_mtlGetError + +#endif /* MOJOSHADER_EFFECT_SUPPORT */ +#endif /* SUPPORT_PROFILE_METAL && PLATFORM_APPLE */ + +// end of mojoshader_metal.c ...