mojoshader_metal.c
changeset 1245 ad9a16c8b023
parent 1241 01fea3855ffb
child 1250 30f8b128b906
--- a/mojoshader_metal.c	Fri Apr 24 09:05:12 2020 -0400
+++ b/mojoshader_metal.c	Fri Apr 24 09:13:10 2020 -0400
@@ -26,8 +26,8 @@
 {
     const MOJOSHADER_parseData *parseData;
     MOJOSHADER_mtlUniformBuffer *ubo;
+    uint32 refcount;
     void *library; // MTLLibrary*
-    int numInternalBuffers;
 } MOJOSHADER_mtlShader;
 
 // Error state...
@@ -50,228 +50,73 @@
 
 /* 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;
+    int inUse;
 } MOJOSHADER_mtlUniformBuffer;
 
-/* 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 msg_s(
-        msg(classNSString, selAlloc),
-        selInitWithUTF8String,
-        str
-    );
-} // cstr_to_nsstr
-
-static const char *nsstr_to_cstr(void *str)
-{
-    return (char *) msg(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];
+typedef struct MOJOSHADER_mtlContext
+{
+    // Allocators...
+    MOJOSHADER_malloc malloc_fn;
+    MOJOSHADER_free free_fn;
+    void *malloc_data;
 
-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;
+    // 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];
 
-    i = 0;
-    for (i = 0; i < symbol_count; i++)
-    {
-        const MOJOSHADER_symbol *sym = &symbols[i];
-        const MOJOSHADER_effectValue *param = &params[param_loc[i]].value;
+    // Pointer to the active MTLDevice.
+    void* device;
 
-        // float/int registers are vec4, so they have 4 elements each
-        const uint32 start = sym->register_index << 2;
+    // The maximum number of frames in flight.
+    int framesInFlight;
+
+    // Array of UBOs that are being used in the current frame.
+    MOJOSHADER_mtlUniformBuffer **buffersInUse;
 
-        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
+    // The current capacity of the uniform buffer array.
+    int bufferArrayCapacity;
+
+    // The actual number of UBOs used in the current frame.
+    int numBuffersInUse;
+
+    // The currently bound shaders.
+    MOJOSHADER_mtlShader *vertexShader;
+    MOJOSHADER_mtlShader *pixelShader;
+
+    // Objective-C Selectors
+    void* classNSString;
+    void* selAlloc;
+    void* selInitWithUTF8String;
+    void* selUTF8String;
+    void* selLength;
+    void* selContents;
+    void* selNewBufferWithLength;
+    void* selRelease;
+    void* selNewLibraryWithSource;
+    void* selLocalizedDescription;
+    void* selNewFunctionWithName;
+    void* selRetain;
+} MOJOSHADER_mtlContext;
+
+static MOJOSHADER_mtlContext *ctx = NULL;
 
 /* Uniform buffer utilities */
 
@@ -286,22 +131,13 @@
     return align * ((n + align - 1) / align);
 } // next_highest_alignment
 
-static int UBO_buffer_length(void *buffer)
-{
-    return (int) msg(buffer, selLength);
-} // UBO_buffer_length
-
-static void *UBO_buffer_contents(void *buffer)
+static void* create_ubo_backing_buffer(MOJOSHADER_mtlUniformBuffer *ubo,
+                                                               int frame)
 {
-    return (void *) msg(buffer, selContents);
-} // UBO_buffer_contents
-
-static void *UBO_create_backing_buffer(MOJOSHADER_mtlUniformBuffer *ubo, int f)
-{
-    void *oldBuffer = ubo->internalBuffers[f];
+    void *oldBuffer = ubo->internalBuffers[frame];
     void *newBuffer = msg_ip(
-        ubo->device,
-        selNewBufferWithLength,
+        ctx->device,
+        ctx->selNewBufferWithLength,
         ubo->internalBufferSize,
         NULL
     );
@@ -309,29 +145,48 @@
     {
         // Copy over data from old buffer
         memcpy(
-            UBO_buffer_contents(newBuffer),
-            UBO_buffer_contents(oldBuffer),
-            UBO_buffer_length(oldBuffer)
+            msg(newBuffer, ctx->selContents),
+            msg(oldBuffer, ctx->selContents),
+            (int) msg(oldBuffer, ctx->selLength)
         );
 
         // Free the old buffer
-        msg(oldBuffer, selRelease);
+        msg(oldBuffer, ctx->selRelease);
     } //if
 
     return newBuffer;
-} // UBO_create_backing_buffer
+} // create_ubo_backing_buffer
 
-static void UBO_predraw(MOJOSHADER_mtlUniformBuffer *ubo)
+static void predraw_ubo(MOJOSHADER_mtlUniformBuffer *ubo)
 {
-    if (!ubo->alreadyWritten)
+    if (!ubo->inUse)
     {
-        ubo->alreadyWritten = 1;
+        ubo->inUse = 1;
+        ctx->buffersInUse[ctx->numBuffersInUse++] = ubo;
+
+        // Double the array size if we run out of room
+        if (ctx->numBuffersInUse >= ctx->bufferArrayCapacity)
+        {
+            int oldlen = ctx->bufferArrayCapacity;
+            ctx->bufferArrayCapacity *= 2;
+            MOJOSHADER_mtlUniformBuffer **tmp;
+            tmp = (MOJOSHADER_mtlUniformBuffer**) ctx->malloc_fn(
+                ctx->bufferArrayCapacity * sizeof(MOJOSHADER_mtlUniformBuffer *),
+                ctx->malloc_data
+            );
+            memcpy(tmp, ctx->buffersInUse, oldlen * sizeof(MOJOSHADER_mtlUniformBuffer *));
+            ctx->free_fn(ctx->buffersInUse, ctx->malloc_data);
+            ctx->buffersInUse = tmp;
+        }
         return;
     } // if
 
     ubo->internalOffset += ubo->bufferSize;
 
-    int buflen = UBO_buffer_length(ubo->internalBuffers[ubo->currentFrame]);
+    int buflen = (int) msg(
+        ubo->internalBuffers[ubo->currentFrame],
+        ctx->selLength
+    );
     if (ubo->internalOffset >= buflen)
     {
         // Double capacity when we're out of room
@@ -339,23 +194,12 @@
             ubo->internalBufferSize *= 2;
 
         ubo->internalBuffers[ubo->currentFrame] =
-            UBO_create_backing_buffer(ubo, ubo->currentFrame);
+            create_ubo_backing_buffer(ubo, ubo->currentFrame);
     } //if
-} // UBO_predraw
+} // predraw_ubo
 
-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)
+static MOJOSHADER_mtlUniformBuffer* create_ubo(MOJOSHADER_mtlShader *shader,
+                                               MOJOSHADER_malloc m, void* d)
 {
     int uniformCount = shader->parseData->uniform_count;
     if (uniformCount == 0)
@@ -372,27 +216,24 @@
         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++)
+    // Allocate the UBO
+    MOJOSHADER_mtlUniformBuffer *retval;
+    retval = (MOJOSHADER_mtlUniformBuffer *) m(sizeof(MOJOSHADER_mtlUniformBuffer), d);
+    retval->bufferSize = next_highest_alignment(buflen);
+    retval->internalBufferSize = retval->bufferSize * 16; // pre-allocate some extra room!
+    retval->internalBuffers = m(ctx->framesInFlight * sizeof(void*), d);
+    retval->internalOffset = 0;
+    retval->inUse = 0;
+    retval->currentFrame = 0;
+
+    // Create the backing buffers
+    for (int i = 0; i < ctx->framesInFlight; i++)
     {
-        ubo->internalBuffers[i] = NULL;
-        ubo->internalBuffers[i] = UBO_create_backing_buffer(ubo, i);
+        retval->internalBuffers[i] = NULL; // basically a memset('\0')
+        retval->internalBuffers[i] = create_ubo_backing_buffer(retval, 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;
+    return retval;
 } // create_ubo
 
 static void dealloc_ubo(MOJOSHADER_mtlShader *shader,
@@ -402,10 +243,9 @@
     if (shader->ubo == NULL)
         return;
 
-    LL_remove_node(&ubos, shader->ubo, f, d);
-    for (int i = 0; i < shader->ubo->numInternalBuffers; i++)
+    for (int i = 0; i < ctx->framesInFlight; i++)
     {
-        msg(shader->ubo->internalBuffers[i], selRelease);
+        msg(shader->ubo->internalBuffers[i], ctx->selRelease);
         shader->ubo->internalBuffers[i] = NULL;
     } // for
 
@@ -437,20 +277,20 @@
     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;
+        regF = ctx->vs_reg_file_f;
+        regI = ctx->vs_reg_file_i;
+        regB = ctx->vs_reg_file_b;
     } // if
     else
     {
-        regF = ps_reg_file_f;
-        regI = ps_reg_file_i;
-        regB = ps_reg_file_b;
+        regF = ctx->ps_reg_file_f;
+        regI = ctx->ps_reg_file_i;
+        regB = ctx->ps_reg_file_b;
     } // else
 
-    UBO_predraw(shader->ubo);
+    predraw_ubo(shader->ubo);
     void *buf = shader->ubo->internalBuffers[shader->ubo->currentFrame];
-    void *contents = UBO_buffer_contents(buf) + shader->ubo->internalOffset;
+    void *contents = msg(buf, ctx->selContents) + shader->ubo->internalOffset;
 
     int offset = 0;
     for (int i = 0; i < shader->parseData->uniform_count; i++)
@@ -498,64 +338,113 @@
 
 /* Public API */
 
-MOJOSHADER_mtlEffect *MOJOSHADER_mtlCompileEffect(MOJOSHADER_effect *effect,
-                                                  void *mtlDevice,
-                                                  int numBackingBuffers)
+int MOJOSHADER_mtlCreateContext(void* mtlDevice, int framesInFlight,
+                                MOJOSHADER_malloc m, MOJOSHADER_free f,
+                                void *malloc_d)
 {
-    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;
+    assert(ctx == NULL);
 
-    // Make sure the Objective-C selectors have been initialized...
-    if (selAlloc == NULL)
-        initSelectors();
+    if (m == NULL) m = MOJOSHADER_internal_malloc;
+    if (f == NULL) f = MOJOSHADER_internal_free;
 
-    MOJOSHADER_mtlEffect *retval = (MOJOSHADER_mtlEffect *) m(sizeof (MOJOSHADER_mtlEffect), d);
-    if (retval == NULL)
+    ctx = (MOJOSHADER_mtlContext *) m(sizeof(MOJOSHADER_mtlContext), malloc_d);
+    if (ctx == NULL)
     {
         out_of_memory();
-        return NULL;
+        goto init_fail;
     } // if
-    memset(retval, '\0', sizeof (MOJOSHADER_mtlEffect));
+
+    memset(ctx, '\0', sizeof (MOJOSHADER_mtlContext));
+    ctx->malloc_fn = m;
+    ctx->free_fn = f;
+    ctx->malloc_data = malloc_d;
+
+    // Initialize the Metal state
+    ctx->device = mtlDevice;
+    ctx->framesInFlight = framesInFlight;
+
+    // Allocate the uniform buffer object array
+    ctx->bufferArrayCapacity = 32; // arbitrary!
+    ctx->buffersInUse = ctx->malloc_fn(
+        ctx->bufferArrayCapacity * sizeof(MOJOSHADER_mtlUniformBuffer *),
+        ctx->malloc_data
+    );
+
+    // Grab references to Objective-C selectors
+    ctx->classNSString = objc_getClass("NSString");
+    ctx->selAlloc = sel_registerName("alloc");
+    ctx->selInitWithUTF8String = sel_registerName("initWithUTF8String:");
+    ctx->selUTF8String = sel_registerName("UTF8String");
+    ctx->selLength = sel_registerName("length");
+    ctx->selContents = sel_registerName("contents");
+    ctx->selNewBufferWithLength = sel_registerName("newBufferWithLength:options:");
+    ctx->selRelease = sel_registerName("release");
+    ctx->selNewLibraryWithSource = sel_registerName("newLibraryWithSource:options:error:");
+    ctx->selLocalizedDescription = sel_registerName("localizedDescription");
+    ctx->selNewFunctionWithName = sel_registerName("newFunctionWithName:");
+    ctx->selRetain = sel_registerName("retain");
+
+    return 0;
+
+init_fail:
+    if (ctx != NULL)
+        f(ctx, malloc_d);
+    return -1;
+} // MOJOSHADER_mtlCreateContext
+
+void MOJOSHADER_mtlDestroyContext(void)
+{
+    ctx->free_fn(ctx->buffersInUse, ctx->malloc_data);
+    ctx->free_fn(ctx, ctx->malloc_data);
+} // MOJOSHADER_mtlDestroyContext
+
+void *MOJOSHADER_mtlCompileLibrary(MOJOSHADER_effect *effect)
+{
+    MOJOSHADER_malloc m = ctx->malloc_fn;
+    MOJOSHADER_free f = ctx->free_fn;
+    void *d = ctx->malloc_data;
+
+    int i, src_len, src_pos, output_len;
+    char *shader_source, *ptr;
+    const char *repl;
+    MOJOSHADER_effectObject *object;
+    MOJOSHADER_mtlShader *shader;
+    void *retval, *compileError, *shader_source_ns;
 
     // Count the number of shaders before allocating
+    src_len = 0;
     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];
+        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;
+                shader = (MOJOSHADER_mtlShader*) object->shader.shader;
+                src_len += shader->parseData->output_len;
+            } // if
+        } // if
+    } // for
+
+    // Allocate shader source buffer
+    shader_source = (char *) m(src_len + 1, d);
+    memset(shader_source, '\0', src_len + 1);
+    src_pos = 0;
+
+    // Copy all the source text into the buffer
+    for (i = 0; i < effect->object_count; i++)
+    {
+        object = &effect->objects[i];
+        if (object->type == MOJOSHADER_SYMTYPE_PIXELSHADER
+         || object->type == MOJOSHADER_SYMTYPE_VERTEXSHADER)
+        {
+            if (!object->shader.is_preshader)
+            {
+                shader = (MOJOSHADER_mtlShader*) object->shader.shader;
+                memcpy(&shader_source[src_pos], shader->parseData->output,
+                                                shader->parseData->output_len);
+                src_pos += shader->parseData->output_len;
             } // if
         } // if
     } // for
@@ -565,13 +454,12 @@
     {
         // !!! 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;
+        repl = "[[  point_coord  ]]";
         while ((ptr = strstr(shader_source, "[[user(texcoord0)]]")))
         {
             memcpy(ptr, repl, strlen(repl));
 
-            // float4 -> float2
+            // "float4" -> "float2"
             int spaces = 0;
             while (spaces < 2)
                 if (*(ptr--) == ' ')
@@ -580,386 +468,192 @@
         } // while
     } // if
 
-    // Alloc shader information
-    retval->shaders = (MOJOSHADER_mtlShader *) m(retval->num_shaders * sizeof (MOJOSHADER_mtlShader), d);
-    if (retval->shaders == NULL)
+    // Compile the source into a library
+    compileError = NULL;
+    shader_source_ns = msg_s(
+        msg(ctx->classNSString, ctx->selAlloc),
+        ctx->selInitWithUTF8String,
+        shader_source
+    );
+    retval = msg_ppp(ctx->device, ctx->selNewLibraryWithSource,
+                        shader_source_ns, NULL, &compileError);
+    f(shader_source, d);
+    msg(shader_source_ns, ctx->selRelease);
+
+    if (retval == 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();
+        compileError = msg(compileError, ctx->selLocalizedDescription);
+        set_error((char*) msg(compileError, ctx->selUTF8String));
         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 = msg_ppp(
-        mtlDevice,
-        selNewLibraryWithSource,
-        shader_source_ns,
-        NULL,
-        &compileError
-    );
-    retval->library = library;
-    f(shader_source, d);
-    msg(shader_source_ns, selRelease);
-
-    if (library == NULL)
-    {
-        // Set the error
-        void *error_nsstr = msg(compileError, selLocalizedDescription);
-        set_error(nsstr_to_cstr(error_nsstr));
-
-        goto compile_shader_fail;
-    } // if
-
-    // Run through the shaders again, tracking the object indices
+    // Run through the shaders again, setting the library reference
     for (i = 0; i < effect->object_count; i++)
     {
-        MOJOSHADER_effectObject *object = &effect->objects[i];
+        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++;
+            ((MOJOSHADER_mtlShader*) object->shader.shader)->library = retval;
         } // if
     } // for
 
-    retval->effect = effect;
+    return retval;
+} // MOJOSHADER_mtlCompileLibrary
+
+void MOJOSHADER_mtlDeleteLibrary(void *library)
+{
+    msg(library, ctx->selRelease);
+} // MOJOSHADER_mtlDeleteLibrary
+
+MOJOSHADER_mtlShader *MOJOSHADER_mtlCompileShader(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)
+{
+    MOJOSHADER_malloc m = ctx->malloc_fn;
+    MOJOSHADER_free f = ctx->free_fn;
+    void *d = ctx->malloc_data;
+
+    const MOJOSHADER_parseData *pd = MOJOSHADER_parse("metal", mainfn, tokenbuf,
+                                                     bufsize, swiz, swizcount,
+                                                     smap, smapcount, m, f, d);
+    if (pd->error_count > 0)
+    {
+        // !!! FIXME: put multiple errors in the buffer? Don't use
+        // !!! FIXME:  MOJOSHADER_mtlGetError() for this?
+        set_error(pd->errors[0].error);
+        goto compile_shader_fail;
+    } // if
+
+    MOJOSHADER_mtlShader *retval = (MOJOSHADER_mtlShader *) m(sizeof(MOJOSHADER_mtlShader), d);
+    if (retval == NULL)
+        goto compile_shader_fail;
+
+    retval->parseData = pd;
+    retval->refcount = 1;
+    retval->ubo = create_ubo(retval, m, d);
+    retval->library = NULL; // populated by MOJOSHADER_mtlCompileLibrary
+
     return retval;
 
 compile_shader_fail:
-    f(retval->shader_indices, d);
-    f(retval->shaders, d);
+    MOJOSHADER_freeParseData(retval->parseData);
     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
+} // MOJOSHADER_mtlCompileShader
 
-    /* Release the library */
-    msg(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)
+void MOJOSHADER_mtlShaderAddRef(MOJOSHADER_mtlShader *shader)
 {
-    *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);
+    if (shader != NULL)
+        shader->refcount++;
+} // MOJOSHADER_mtlShaderAddRef
 
-void MOJOSHADER_mtlEffectBeginPass(MOJOSHADER_mtlEffect *mtlEffect,
-                                   unsigned int pass,
-                                   MOJOSHADER_mtlShaderState *shState)
+void MOJOSHADER_mtlDeleteShader(MOJOSHADER_mtlShader *shader)
 {
-    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++)
+    if (shader != NULL)
     {
-        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 (shader->refcount > 1)
+            shader->refcount--;
+        else
+        {
+            dealloc_ubo(shader, ctx->free_fn, ctx->malloc_data);
+            ctx->free_fn(shader, ctx->malloc_data);
+        } // else
+    } // if
+} // MOJOSHADER_mtlDeleteShader
 
-    /* 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
+const MOJOSHADER_parseData *MOJOSHADER_mtlGetShaderParseData(
+                                                MOJOSHADER_mtlShader *shader)
+{
+    return (shader != NULL) ? shader->parseData : NULL;
+} // MOJOSHADER_mtlGetParseData
 
-        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_mtlBindShaders(MOJOSHADER_mtlShader *vshader,
+                               MOJOSHADER_mtlShader *pshader)
+{
+    // Use the last bound shaders in case of NULL
+    if (vshader != NULL)
+        ctx->vertexShader = vshader;
 
-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;
+    if (pshader != NULL)
+        ctx->pixelShader = pshader;
+} // MOJOSHADER_mtlBindShaders
 
-    /* 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;
+void MOJOSHADER_mtlGetBoundShaders(MOJOSHADER_mtlShader **vshader,
+                                   MOJOSHADER_mtlShader **pshader)
+{
+    *vshader = ctx->vertexShader;
+    *pshader = ctx->pixelShader;
+} // MOJOSHADER_mtlGetBoundShaders
 
-        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
+void MOJOSHADER_mtlMapUniformBufferMemory(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_mtlMapUniformBufferMemory
 
-    /* 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
+void MOJOSHADER_mtlUnmapUniformBufferMemory()
+{
+    /* This has nothing to do with unmapping memory
+     * and everything to do with updating uniform
+     * buffers with the latest parameter contents.
      */
-    // !!! 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(ctx->vertexShader);
+    update_uniform_buffer(ctx->pixelShader);
+} // MOJOSHADER_mtlUnmapUniformBufferMemory
 
-    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)
+void MOJOSHADER_mtlGetUniformBuffers(void **vbuf, int *voff,
+                                     void **pbuf, int *poff)
 {
-    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
+    *vbuf = get_uniform_buffer(ctx->vertexShader);
+    *voff = get_uniform_offset(ctx->vertexShader);
+    *pbuf = get_uniform_buffer(ctx->pixelShader);
+    *poff = get_uniform_offset(ctx->pixelShader);
+} // MOJOSHADER_mtlGetUniformBuffers
 
 void *MOJOSHADER_mtlGetFunctionHandle(MOJOSHADER_mtlShader *shader)
 {
     if (shader == NULL)
         return NULL;
 
-    void *fnname = cstr_to_nsstr(shader->parseData->mainfn);
+    void *fnname = msg_s(
+        msg(ctx->classNSString, ctx->selAlloc),
+        ctx->selInitWithUTF8String,
+        shader->parseData->mainfn
+    );
     void *ret = msg_p(
         shader->library,
-        selNewFunctionWithName,
+        ctx->selNewFunctionWithName,
         fnname
     );
-    msg(fnname, selRelease);
-    msg(ret, selRetain);
+    msg(fnname, ctx->selRelease);
+    msg(ret, ctx->selRetain);
 
     return ret;
 } // MOJOSHADER_mtlGetFunctionHandle
 
 void MOJOSHADER_mtlEndFrame()
 {
-    LLNODE *node = ubos;
-    while (node != NULL)
+    for (int i = 0; i < ctx->numBuffersInUse; i += 1)
     {
-        UBO_end_frame((MOJOSHADER_mtlUniformBuffer *) node->data);
-        node = node->next;
-    } // while
+        MOJOSHADER_mtlUniformBuffer *buf = ctx->buffersInUse[i];
+        buf->internalOffset = 0;
+        buf->currentFrame = (buf->currentFrame + 1) % ctx->framesInFlight;
+        buf->inUse = 0;
+    } // for
+    ctx->numBuffersInUse = 0;
 } // MOJOSHADER_mtlEndFrame
 
 int MOJOSHADER_mtlGetVertexAttribLocation(MOJOSHADER_mtlShader *vert,
@@ -970,8 +664,8 @@
 
     for (int i = 0; i < vert->parseData->attribute_count; i++)
     {
-        if (vert->parseData->attributes[i].usage == usage
-            && vert->parseData->attributes[i].index == index)
+        if (vert->parseData->attributes[i].usage == usage &&
+            vert->parseData->attributes[i].index == index)
         {
             return i;
         } // if