Major refactor of the Effects API.
authorEthan Lee <flibitijibibo@flibitijibibo.com>
Fri, 24 Apr 2020 09:13:10 -0400
changeset 1245 ad9a16c8b023
parent 1244 311c02c8475a
child 1246 cd51a61272e4
Major refactor of the Effects API. The previous API was very GL-specific and had no abstraction at all, meaning new backends had to copypaste WAY too much code just to get started. Additionally, parsing/compiled were separate but still very tightly coupled, to the point where memory leaks in the GL implementation were inevitable. TL;DR: It was crap and nobody liked working with it. The new API provides only a compile function, and moves ALL the effect work to a common implementation. This is done by providing a shader context at compile time, with functions that map almost directly to the existing MojoShader GL API. This is MUCH less work to maintain and add to, to the point where you can see how a parse API is still possible in the updated testparse! It also means new shader backends no longer need to worry about the effects framework so much. The Metal work in this patch was done by Caleb Cornett, as always.
mojoshader.h
mojoshader_effects.c
mojoshader_effects.h
mojoshader_metal.c
mojoshader_opengl.c
--- a/mojoshader.h	Fri Apr 24 09:05:12 2020 -0400
+++ b/mojoshader.h	Fri Apr 24 09:13:10 2020 -0400
@@ -2691,6 +2691,18 @@
                                                          const MOJOSHADER_samplerMap *smap,
                                                          const unsigned int smapcount);
 
+/*
+ * Increments a shader's internal refcount. To decrement the refcount, call
+ *  MOJOSHADER_glDeleteShader().
+ *
+ * This call is NOT thread safe! As most OpenGL implementations are not thread
+ *  safe, you should probably only call this from the same thread that created
+ *  the GL context.
+ *
+ * This call requires a valid MOJOSHADER_glContext to have been made current,
+ *  or it will crash your program. See MOJOSHADER_glMakeContextCurrent().
+ */
+DECLSPEC void MOJOSHADER_glShaderAddRef(MOJOSHADER_glShader *shader);
 
 /*
  * Get the MOJOSHADER_parseData structure that was produced from the
@@ -2779,6 +2791,22 @@
                                        MOJOSHADER_glShader *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 is NOT thread safe! As most OpenGL implementations are not thread
+ *  safe, you should probably only call this from the same thread that created
+ *  the GL context.
+ *
+ * This call requires a valid MOJOSHADER_glContext to have been made current,
+ *  or it will crash your program. See MOJOSHADER_glMakeContextCurrent().
+ */
+DECLSPEC void MOJOSHADER_glGetBoundShaders(MOJOSHADER_glShader **vshader,
+                                           MOJOSHADER_glShader **pshader);
+
+/*
  * Set a floating-point uniform value (what Direct3D calls a "constant").
  *
  * There is a single array of 4-float "registers" shared by all vertex shaders.
@@ -3059,6 +3087,36 @@
                                                   unsigned int bcount);
 
 /*
+ * Fills register pointers with pointers that are directly used to push uniform
+ *  data to the GL 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 is NOT thread safe! As most OpenGL implementations are not thread
+ *  safe, you should probably only call this from the same thread that created
+ *  the GL context.
+ *
+ * This call requires a valid MOJOSHADER_glContext to have been made current,
+ *  or it will crash your program. See MOJOSHADER_glMakeContextCurrent().
+ */
+DECLSPEC void MOJOSHADER_glMapUniformBufferMemory(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_glMapUniformBufferMemory().
+ *
+ * This call is NOT thread safe! As most OpenGL implementations are not thread
+ *  safe, you should probably only call this from the same thread that created
+ *  the GL context.
+ *
+ * This call requires a valid MOJOSHADER_glContext to have been made current,
+ *  or it will crash your program. See MOJOSHADER_glMakeContextCurrent().
+ */
+DECLSPEC void MOJOSHADER_glUnmapUniformBufferMemory();
+
+/*
  * Set up the vector for the TEXBEM opcode. Most apps can ignore this API.
  *
  * Shader Model 1.1 through 1.3 had an instruction for "fake bump mapping"
@@ -3263,31 +3321,37 @@
 typedef struct MOJOSHADER_mtlShader MOJOSHADER_mtlShader;
 
 /*
- * Get the MTLFunction* from the given MOJOSHADER_mtlShader.
+ * Prepare MojoShader to manage Metal shaders.
+ *
+ * This is really just for the effects framework. Don't call this unless
+ *  you know for sure that you need it.
+ *
+ * You must call this only once, AFTER you have created your MTLDevice.
+ *  This function will lookup the Objective-C selectors it needs, after which
+ *  it may call them at any time until you call MOJOSHADER_mtlDestroyContext().
+ *
+ * (device) refers to the active MTLDevice, cast from id<MTLDevice> to void*.
  *
- * 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.
+ * (framesInFlight) is the maximum number of frames that can be processed
+ *  simultaneously. This determines how many uniform buffers will be
+ *  allocated for each shader.
  *
- * Always call this after submitting the final command buffer for a frame!
+ * 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 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);
+DECLSPEC int MOJOSHADER_mtlCreateContext(void *mtlDevice, int framesInFlight,
+                                         MOJOSHADER_malloc m, MOJOSHADER_free f,
+                                         void *malloc_d);
 
 /*
  * Get any error state we might have picked up, such as failed shader
@@ -3307,6 +3371,162 @@
  */
 DECLSPEC const char *MOJOSHADER_mtlGetError(void);
 
+/*
+ * Transform a buffer of Direct3D shader bytecode into a struct containing
+ *  Metal shader metadata.
+ *
+ * This function is only for convenience, specifically for compatibility with
+ *  the effects API.
+ *
+ * Despite the name, this does NOT compile a shader! The actual compilation
+ *  happens inside MOJOSHADER_mtlCompileLibrary, which batch-compiles an effect
+ *  into a single MTLLibrary.
+ *
+ *   (mainfn) is the name of the shader's main function.
+ *   (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_mtlContext to have been created,
+ *  or it will crash your program. See MOJOSHADER_mtlCreateContext().
+ */
+DECLSPEC 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);
+
+/*
+ * Increments a shader's internal refcount. To decrement the refcount, call
+ *  MOJOSHADER_mtlDeleteShader().
+ */
+DECLSPEC void MOJOSHADER_mtlShaderAddRef(MOJOSHADER_mtlShader *shader);
+
+/*
+ * Get the MOJOSHADER_parseData structure that was produced from the
+ *  call to MOJOSHADER_mtlCompileShader().
+ *
+ * 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_mtlGetShaderParseData(
+                                                MOJOSHADER_mtlShader *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_mtlContext to have been created,
+ *  or it will crash your program. See MOJOSHADER_mtlCreateContext().
+ */
+DECLSPEC void MOJOSHADER_mtlBindShaders(MOJOSHADER_mtlShader *vshader,
+                                        MOJOSHADER_mtlShader *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_mtlContext to have been created,
+ *  or it will crash your program. See MOJOSHADER_mtlCreateContext().
+ */
+DECLSPEC void MOJOSHADER_mtlGetBoundShaders(MOJOSHADER_mtlShader **vshader,
+                                            MOJOSHADER_mtlShader **pshader);
+
+/*
+ * This queries for the uniform buffer and byte offset 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_mtlContext to have been created,
+ *  or it will crash your program. See MOJOSHADER_mtlCreateContext().
+ */
+DECLSPEC void MOJOSHADER_mtlGetUniformBuffers(void **vbuf, int *voff,
+                                              void **pbuf, int *poff);
+
+/*
+ * Fills register pointers with pointers that are directly used to push uniform
+ *  data to the Metal 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_mtlContext to have been created,
+ *  or it will crash your program. See MOJOSHADER_mtlCreateContext().
+ */
+DECLSPEC void MOJOSHADER_mtlMapUniformBufferMemory(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_mtlMapUniformBufferMemory().
+ *
+ * This call requires a valid MOJOSHADER_mtlContext to have been created,
+ *  or it will crash your program. See MOJOSHADER_mtlCreateContext().
+ */
+DECLSPEC void MOJOSHADER_mtlUnmapUniformBufferMemory();
+
+/*
+ * 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.
+ */
+DECLSPEC int MOJOSHADER_mtlGetVertexAttribLocation(MOJOSHADER_mtlShader *vert,
+                                          MOJOSHADER_usage usage, int index);
+
+/*
+ * Free the resources of a compiled shader. This will delete the MojoShader
+ *  shader struct and free memory.
+ *
+ * This does NOT release the actual shader! The shader data belongs to an
+ *  MTLLibrary that must be deleted with MOJOSHADER_mtlDeleteLibrary().
+ *
+ * This call requires a valid MOJOSHADER_mtlContext to have been created,
+ *  or it will crash your program. See MOJOSHADER_mtlCreateContext().
+ */
+DECLSPEC void MOJOSHADER_mtlDeleteShader(MOJOSHADER_mtlShader *shader);
+
+/*
+ * 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(void);
+
+/*
+ * Deinitialize MojoShader's Metal shader management.
+ *
+ * This will clean up resources previously allocated for the active context.
+ *
+ * This will NOT clean up shaders you created! Please destroy all shaders
+ *  before calling this function.
+ */
+DECLSPEC void MOJOSHADER_mtlDestroyContext(void);
+
 
 /* Effects interface... */
 #include "mojoshader_effects.h"
--- a/mojoshader_effects.c	Fri Apr 24 09:05:12 2020 -0400
+++ b/mojoshader_effects.c	Fri Apr 24 09:13:10 2020 -0400
@@ -605,16 +605,17 @@
                              const uint8 **ptr,
                              uint32 *len,
                              MOJOSHADER_effect *effect,
-                             const char *profile,
                              const MOJOSHADER_swizzle *swiz,
                              const unsigned int swizcount,
                              const MOJOSHADER_samplerMap *smap,
-                             const unsigned int smapcount,
-                             MOJOSHADER_malloc m,
-                             MOJOSHADER_free f,
-                             void *d)
+                             const unsigned int smapcount)
 {
     int i, j;
+    MOJOSHADER_parseData *pd;
+    MOJOSHADER_malloc m = effect->ctx.m;
+    MOJOSHADER_free f = effect->ctx.f;
+    void *d = effect->ctx.malloc_data;
+
     if (numsmallobjects == 0) return;
 
     for (i = 1; i < numsmallobjects + 1; i++)
@@ -654,44 +655,46 @@
               || object->type == MOJOSHADER_SYMTYPE_VERTEXSHADER)
         {
             char mainfn[32];
-            snprintf(mainfn, sizeof (mainfn), "ShaderFunction%u", (unsigned int) index);
+            snprintf(mainfn, sizeof(mainfn), "ShaderFunction%u", (unsigned int) index);
             object->shader.technique = -1;
             object->shader.pass = -1;
-            object->shader.shader = MOJOSHADER_parse(profile, mainfn, *ptr, length,
-                                                     swiz, swizcount, smap, smapcount,
-                                                     m, f, d);
             // !!! FIXME: check for errors.
-            for (j = 0; j < object->shader.shader->symbol_count; j++)
-                if (object->shader.shader->symbols[j].register_set == MOJOSHADER_SYMREGSET_SAMPLER)
+            object->shader.shader = effect->ctx.compileShader(mainfn, *ptr, length,
+                                                              swiz, swizcount,
+                                                              smap, smapcount);
+            pd = effect->ctx.getParseData(object->shader.shader);
+
+            for (j = 0; j < pd->symbol_count; j++)
+                if (pd->symbols[j].register_set == MOJOSHADER_SYMREGSET_SAMPLER)
                     object->shader.sampler_count++;
-            object->shader.param_count = object->shader.shader->symbol_count;
+            object->shader.param_count = pd->symbol_count;
             object->shader.params = (uint32 *) m(object->shader.param_count * sizeof (uint32), d);
             object->shader.samplers = (MOJOSHADER_samplerStateRegister *) m(object->shader.sampler_count * sizeof (MOJOSHADER_samplerStateRegister), d);
             uint32 curSampler = 0;
-            for (j = 0; j < object->shader.shader->symbol_count; j++)
+            for (j = 0; j < pd->symbol_count; j++)
             {
                 int par = findparameter(effect->params,
                                         effect->param_count,
-                                        object->shader.shader->symbols[j].name);
+                                        pd->symbols[j].name);
                 object->shader.params[j] = par;
-                if (object->shader.shader->symbols[j].register_set == MOJOSHADER_SYMREGSET_SAMPLER)
+                if (pd->symbols[j].register_set == MOJOSHADER_SYMREGSET_SAMPLER)
                 {
                     object->shader.samplers[curSampler].sampler_name = effect->params[par].value.name;
-                    object->shader.samplers[curSampler].sampler_register = object->shader.shader->symbols[j].register_index;
+                    object->shader.samplers[curSampler].sampler_register = pd->symbols[j].register_index;
                     object->shader.samplers[curSampler].sampler_state_count = effect->params[par].value.value_count;
                     object->shader.samplers[curSampler].sampler_states = effect->params[par].value.valuesSS;
                     curSampler++;
                 } // if
             } // for
-            if (object->shader.shader->preshader)
+            if (pd->preshader)
             {
-                object->shader.preshader_param_count = object->shader.shader->preshader->symbol_count;
+                object->shader.preshader_param_count = pd->preshader->symbol_count;
                 object->shader.preshader_params = (uint32 *) m(object->shader.preshader_param_count * sizeof (uint32), d);
-                for (j = 0; j < object->shader.shader->preshader->symbol_count; j++)
+                for (j = 0; j < pd->preshader->symbol_count; j++)
                 {
                     object->shader.preshader_params[j] = findparameter(effect->params,
                                                                        effect->param_count,
-                                                                       object->shader.shader->preshader->symbols[j].name);
+                                                                       pd->preshader->symbols[j].name);
                 } // for
             } // if
         } // else if
@@ -712,16 +715,17 @@
                              const uint8 **ptr,
                              uint32 *len,
                              MOJOSHADER_effect *effect,
-                             const char *profile,
                              const MOJOSHADER_swizzle *swiz,
                              const unsigned int swizcount,
                              const MOJOSHADER_samplerMap *smap,
-                             const unsigned int smapcount,
-                             MOJOSHADER_malloc m,
-                             MOJOSHADER_free f,
-                             void *d)
+                             const unsigned int smapcount)
 {
     int i, j;
+    MOJOSHADER_parseData *pd;
+    MOJOSHADER_malloc m = effect->ctx.m;
+    MOJOSHADER_free f = effect->ctx.f;
+    void *d = effect->ctx.malloc_data;
+
     if (numlargeobjects == 0) return;
 
     int numobjects = numsmallobjects + numlargeobjects + 1;
@@ -747,7 +751,6 @@
             object->shader.technique = technique;
             object->shader.pass = index;
 
-            const char *emitter = profile;
             if (type == 2)
             {
                 /* This is a standalone preshader!
@@ -779,41 +782,43 @@
             {
                 char mainfn[32];
                 snprintf(mainfn, sizeof (mainfn), "ShaderFunction%u", (unsigned int) objectIndex);
-                object->shader.shader = MOJOSHADER_parse(emitter, mainfn, *ptr, length,
-                                                         swiz, swizcount, smap, smapcount,
-                                                         m, f, d);
                 // !!! FIXME: check for errors.
-                for (j = 0; j < object->shader.shader->symbol_count; j++)
-                    if (object->shader.shader->symbols[j].register_set == MOJOSHADER_SYMREGSET_SAMPLER)
+                object->shader.shader = effect->ctx.compileShader(mainfn, *ptr, length,
+                                                                  swiz, swizcount,
+                                                                  smap, smapcount);
+                pd = effect->ctx.getParseData(object->shader.shader);
+
+                for (j = 0; j < pd->symbol_count; j++)
+                    if (pd->symbols[j].register_set == MOJOSHADER_SYMREGSET_SAMPLER)
                         object->shader.sampler_count++;
-                object->shader.param_count = object->shader.shader->symbol_count;
+                object->shader.param_count = pd->symbol_count;
                 object->shader.params = (uint32 *) m(object->shader.param_count * sizeof (uint32), d);
                 object->shader.samplers = (MOJOSHADER_samplerStateRegister *) m(object->shader.sampler_count * sizeof (MOJOSHADER_samplerStateRegister), d);
                 uint32 curSampler = 0;
-                for (j = 0; j < object->shader.shader->symbol_count; j++)
+                for (j = 0; j < pd->symbol_count; j++)
                 {
                     int par = findparameter(effect->params,
                                             effect->param_count,
-                                            object->shader.shader->symbols[j].name);
+                                            pd->symbols[j].name);
                     object->shader.params[j] = par;
-                    if (object->shader.shader->symbols[j].register_set == MOJOSHADER_SYMREGSET_SAMPLER)
+                    if (pd->symbols[j].register_set == MOJOSHADER_SYMREGSET_SAMPLER)
                     {
                         object->shader.samplers[curSampler].sampler_name = effect->params[par].value.name;
-                        object->shader.samplers[curSampler].sampler_register = object->shader.shader->symbols[j].register_index;
+                        object->shader.samplers[curSampler].sampler_register = pd->symbols[j].register_index;
                         object->shader.samplers[curSampler].sampler_state_count = effect->params[par].value.value_count;
                         object->shader.samplers[curSampler].sampler_states = effect->params[par].value.valuesSS;
                         curSampler++;
                     } // if
                 } // for
-                if (object->shader.shader->preshader)
+                if (pd->preshader)
                 {
-                    object->shader.preshader_param_count = object->shader.shader->preshader->symbol_count;
+                    object->shader.preshader_param_count = pd->preshader->symbol_count;
                     object->shader.preshader_params = (uint32 *) m(object->shader.preshader_param_count * sizeof (uint32), d);
-                    for (j = 0; j < object->shader.shader->preshader->symbol_count; j++)
+                    for (j = 0; j < pd->preshader->symbol_count; j++)
                     {
                         object->shader.preshader_params[j] = findparameter(effect->params,
                                                                            effect->param_count,
-                                                                           object->shader.shader->preshader->symbols[j].name);
+                                                                           pd->preshader->symbols[j].name);
                     } // for
                 } // if
             }
@@ -848,38 +853,51 @@
     } // for
 } // readobjects
 
-MOJOSHADER_effect *MOJOSHADER_parseEffect(const char *profile,
-                                          const unsigned char *buf,
-                                          const unsigned int _len,
-                                          const MOJOSHADER_swizzle *swiz,
-                                          const unsigned int swizcount,
-                                          const MOJOSHADER_samplerMap *smap,
-                                          const unsigned int smapcount,
-                                          MOJOSHADER_malloc m,
-                                          MOJOSHADER_free f,
-                                          void *d)
+MOJOSHADER_effect *MOJOSHADER_compileEffect(const unsigned char *buf,
+                                            const unsigned int _len,
+                                            const MOJOSHADER_swizzle *swiz,
+                                            const unsigned int swizcount,
+                                            const MOJOSHADER_samplerMap *smap,
+                                            const unsigned int smapcount,
+                                            const MOJOSHADER_effectShaderContext *ctx)
 {
     const uint8 *ptr = (const uint8 *) buf;
     uint32 len = (uint32) _len;
+    MOJOSHADER_malloc m;
+    MOJOSHADER_free f;
+    void *d;
+
+    /* Need a backend! */
+    if (ctx == NULL)
+        return &MOJOSHADER_out_of_mem_effect;
 
     /* Supply both m and f, or neither */
-    if ( ((m == NULL) && (f != NULL)) || ((m != NULL) && (f == NULL)) )
+    if ( ((ctx->m == NULL) && (ctx->f != NULL))
+      || ((ctx->m != NULL) && (ctx->f == NULL)) )
         return &MOJOSHADER_out_of_mem_effect;
 
     /* Use default malloc/free if m/f were not passed */
-    if (m == NULL) m = MOJOSHADER_internal_malloc;
-    if (f == NULL) f = MOJOSHADER_internal_free;
+    if (ctx->m == NULL)
+        m = MOJOSHADER_internal_malloc;
+    else
+        m = ctx->m;
+    if (ctx->f == NULL)
+        f = MOJOSHADER_internal_free;
+    else
+        f = ctx->f;
+    d = ctx->malloc_data;
 
     /* malloc base effect structure */
-    MOJOSHADER_effect *retval = (MOJOSHADER_effect *) m(sizeof (MOJOSHADER_effect), d);
+    MOJOSHADER_effect *retval = (MOJOSHADER_effect *) m(sizeof (MOJOSHADER_effect),
+                                                        ctx->malloc_data);
     if (retval == NULL)
         return &MOJOSHADER_out_of_mem_effect;
     memset(retval, '\0', sizeof (*retval));
 
-    /* Store m/f/d in effect structure */
-    retval->malloc = m;
-    retval->free = f;
-    retval->malloc_data = d;
+    /* Store ctx in effect structure */
+    memcpy(&retval->ctx, ctx, sizeof(MOJOSHADER_effectShaderContext));
+    retval->ctx.m = m;
+    retval->ctx.f = f;
 
     if (len < 8)
         goto parseEffect_unexpectedEOF;
@@ -951,22 +969,12 @@
     const int numlargeobjects = readui32(&ptr, &len);
 
     /* Parse "small" object table */
-    readsmallobjects(numsmallobjects, &ptr, &len,
-                     retval,
-                     profile, swiz, swizcount, smap, smapcount,
-                     m, f, d);
+    readsmallobjects(numsmallobjects, &ptr, &len, retval,
+                     swiz, swizcount, smap, smapcount);
 
     /* Parse "large" object table. */
-    readlargeobjects(numlargeobjects, numsmallobjects, &ptr, &len,
-                     retval,
-                     profile, swiz, swizcount, smap, smapcount,
-                     m, f, d);
-
-    /* Store MojoShader profile in effect structure */
-    retval->profile = (char *) m(strlen(profile) + 1, d);
-    if (retval->profile == NULL)
-        goto parseEffect_outOfMemory;
-    strcpy((char *) retval->profile, profile);
+    readlargeobjects(numlargeobjects, numsmallobjects, &ptr, &len, retval,
+                     swiz, swizcount, smap, smapcount);
 
     return retval;
 
@@ -974,7 +982,7 @@
 parseEffect_notAnEffectsFile:
 parseEffect_unexpectedEOF:
 parseEffect_outOfMemory:
-    MOJOSHADER_freeEffect(retval);
+    MOJOSHADER_deleteEffect(retval);
     return &MOJOSHADER_out_of_mem_effect;
 } // MOJOSHADER_parseEffect
 
@@ -1009,14 +1017,14 @@
 } // freevalue
 
 
-void MOJOSHADER_freeEffect(const MOJOSHADER_effect *_effect)
+void MOJOSHADER_deleteEffect(const MOJOSHADER_effect *_effect)
 {
     MOJOSHADER_effect *effect = (MOJOSHADER_effect *) _effect;
     if ((effect == NULL) || (effect == &MOJOSHADER_out_of_mem_effect))
         return;  // no-op.
 
-    MOJOSHADER_free f = effect->free;
-    void *d = effect->malloc_data;
+    MOJOSHADER_free f = effect->ctx.f;
+    void *d = effect->ctx.malloc_data;
     int i, j, k;
 
     /* Free errors */
@@ -1027,9 +1035,6 @@
     } // for
     f((void *) effect->errors, d);
 
-    /* Free profile string */
-    f((void *) effect->profile, d);
-
     /* Free parameters, including annotations */
     for (i = 0; i < effect->param_count; i++)
     {
@@ -1082,7 +1087,7 @@
             if (object->shader.is_preshader)
                 MOJOSHADER_freePreshader(object->shader.preshader);
             else
-                MOJOSHADER_freeParseData(object->shader.shader);
+                effect->ctx.deleteShader(object->shader.shader);
             f((void *) object->shader.params, d);
             f((void *) object->shader.samplers, d);
             f((void *) object->shader.preshader_params, d);
@@ -1304,155 +1309,13 @@
 } // copypreshader
 
 
-MOJOSHADER_parseData *copyparsedata(const MOJOSHADER_parseData *src,
-                                    MOJOSHADER_malloc m,
-                                    void *d)
-{
-    int i;
-    uint32 siz;
-    char *stringcopy;
-    MOJOSHADER_parseData *retval;
-
-    retval = (MOJOSHADER_parseData *) m(sizeof (MOJOSHADER_parseData), d);
-    memset(retval, '\0', sizeof (MOJOSHADER_parseData));
-
-    /* Copy malloc/free */
-    retval->malloc = src->malloc;
-    retval->free = src->free;
-    retval->malloc_data = src->malloc_data;
-
-    // !!! FIXME: Out of memory check!
-    #define COPY_STRING(location) \
-        siz = strlen(src->location) + 1; \
-        stringcopy = (char *) m(siz, d); \
-        strcpy(stringcopy, src->location); \
-        retval->location = stringcopy; \
-
-    /* Copy errors */
-    siz = sizeof (MOJOSHADER_error) * src->error_count;
-    retval->error_count = src->error_count;
-    retval->errors = (MOJOSHADER_error *) m(siz, d);
-    // !!! FIXME: Out of memory check!
-    memset(retval->errors, '\0', siz);
-    for (i = 0; i < retval->error_count; i++)
-    {
-        COPY_STRING(errors[i].error)
-        COPY_STRING(errors[i].filename)
-        retval->errors[i].error_position = src->errors[i].error_position;
-    } // for
-
-    /* Copy profile string constant */
-    retval->profile = src->profile;
-
-    /* Copy shader output */
-    retval->output_len = src->output_len;
-    stringcopy = (char *) m(src->output_len, d);
-    memcpy(stringcopy, src->output, src->output_len);
-    retval->output = stringcopy;
-
-    /* Copy miscellaneous shader info */
-    retval->instruction_count = src->instruction_count;
-    retval->shader_type = src->shader_type;
-    retval->major_ver = src->major_ver;
-    retval->minor_ver = src->minor_ver;
-
-    /* Copy main function string */
-    COPY_STRING(mainfn);
-
-    /* Copy uniforms */
-    siz = sizeof (MOJOSHADER_uniform) * src->uniform_count;
-    retval->uniform_count = src->uniform_count;
-    retval->uniforms = (MOJOSHADER_uniform *) m(siz, d);
-    // !!! FIXME: Out of memory check!
-    memset(retval->uniforms, '\0', siz);
-    for (i = 0; i < retval->uniform_count; i++)
-    {
-        retval->uniforms[i].type = src->uniforms[i].type;
-        retval->uniforms[i].index = src->uniforms[i].index;
-        retval->uniforms[i].array_count = src->uniforms[i].array_count;
-        retval->uniforms[i].constant = src->uniforms[i].constant;
-        COPY_STRING(uniforms[i].name)
-    } // for
-
-    /* Copy constants */
-    siz = sizeof (MOJOSHADER_constant) * src->constant_count;
-    retval->constant_count = src->constant_count;
-    retval->constants = (MOJOSHADER_constant *) m(siz, d);
-    // !!! FIXME: Out of memory check!
-    memcpy(retval->constants, src->constants, siz);
-
-    /* Copy samplers */
-    siz = sizeof (MOJOSHADER_sampler) * src->sampler_count;
-    retval->sampler_count = src->sampler_count;
-    retval->samplers = (MOJOSHADER_sampler *) m(siz, d);
-    // !!! FIXME: Out of memory check!
-    memset(retval->samplers, '\0', siz);
-    for (i = 0; i < retval->sampler_count; i++)
-    {
-        retval->samplers[i].type = src->samplers[i].type;
-        retval->samplers[i].index = src->samplers[i].index;
-        COPY_STRING(samplers[i].name)
-        retval->samplers[i].texbem = src->samplers[i].texbem;
-    } // for
-
-    /* Copy attributes */
-    siz = sizeof (MOJOSHADER_attribute) * src->attribute_count;
-    retval->attribute_count = src->attribute_count;
-    retval->attributes = (MOJOSHADER_attribute *) m(siz, d);
-    // !!! FIXME: Out of memory check!
-    memset(retval->attributes, '\0', siz);
-    for (i = 0; i < retval->attribute_count; i++)
-    {
-        retval->attributes[i].usage = src->attributes[i].usage;
-        retval->attributes[i].index = src->attributes[i].index;
-        COPY_STRING(attributes[i].name)
-    } // for
-
-    /* Copy outputs */
-    siz = sizeof (MOJOSHADER_attribute) * src->output_count;
-    retval->output_count = src->output_count;
-    retval->outputs = (MOJOSHADER_attribute *) m(siz, d);
-    // !!! FIXME: Out of memory check!
-    memset(retval->outputs, '\0', siz);
-    for (i = 0; i < retval->output_count; i++)
-    {
-        retval->outputs[i].usage = src->outputs[i].usage;
-        retval->outputs[i].index = src->outputs[i].index;
-        COPY_STRING(outputs[i].name)
-    } // for
-
-    #undef COPY_STRING
-
-    /* Copy swizzles */
-    siz = sizeof (MOJOSHADER_swizzle) * src->swizzle_count;
-    retval->swizzle_count = src->swizzle_count;
-    retval->swizzles = (MOJOSHADER_swizzle *) m(siz, d);
-    // !!! FIXME: Out of memory check!
-    memcpy(retval->swizzles, src->swizzles, siz);
-
-    /* Copy symbols */
-    siz = sizeof (MOJOSHADER_symbol) * src->symbol_count;
-    retval->symbol_count = src->symbol_count;
-    retval->symbols = (MOJOSHADER_symbol *) m(siz, d);
-    // !!! FIXME: Out of memory check!
-    memset(retval->symbols, '\0', siz);
-    for (i = 0; i < retval->symbol_count; i++)
-        copysymbol(&retval->symbols[i], &src->symbols[i], m, d);
-
-    /* Copy preshader */
-    if (src->preshader != NULL)
-        retval->preshader = copypreshader(src->preshader, m, d);
-
-    return retval;
-} // copyparsedata
-
-
 MOJOSHADER_effect *MOJOSHADER_cloneEffect(const MOJOSHADER_effect *effect)
 {
     int i, j, k;
+    MOJOSHADER_parseData *pd;
     MOJOSHADER_effect *clone;
-    MOJOSHADER_malloc m = effect->malloc;
-    void *d = effect->malloc_data;
+    MOJOSHADER_malloc m = effect->ctx.m;
+    void *d = effect->ctx.malloc_data;
     uint32 siz = 0;
     char *stringcopy = NULL;
     uint32 curSampler;
@@ -1465,10 +1328,8 @@
         return NULL; // Maybe out_of_mem_effect instead?
     memset(clone, '\0', sizeof (MOJOSHADER_effect));
 
-    /* Copy malloc/free */
-    clone->malloc = effect->malloc;
-    clone->free = effect->free;
-    clone->malloc_data = effect->malloc_data;
+    /* Copy ctx */
+    memcpy(&clone->ctx, &effect->ctx, sizeof(MOJOSHADER_effectShaderContext));
 
     #define COPY_STRING(location) \
         siz = strlen(effect->location) + 1; \
@@ -1492,9 +1353,6 @@
         clone->errors[i].error_position = effect->errors[i].error_position;
     } // for
 
-    /* Copy profile string */
-    COPY_STRING(profile)
-
     /* Copy parameters */
     siz = sizeof (MOJOSHADER_effectParam) * effect->param_count;
     clone->param_count = effect->param_count;
@@ -1629,8 +1487,9 @@
                 continue;
             } // if
 
-            clone->objects[i].shader.shader = copyparsedata(effect->objects[i].shader.shader,
-                                                            m, d);
+            effect->ctx.shaderAddRef(effect->objects[i].shader.shader);
+            clone->objects[i].shader.shader = effect->objects[i].shader.shader;
+            pd = clone->ctx.getParseData(clone->objects[i].shader.shader);
 
             siz = sizeof (MOJOSHADER_samplerStateRegister) * effect->objects[i].shader.sampler_count;
             clone->objects[i].shader.sampler_count = effect->objects[i].shader.sampler_count;
@@ -1638,11 +1497,11 @@
             if (clone->objects[i].shader.samplers == NULL)
                 goto cloneEffect_outOfMemory;
             curSampler = 0;
-            for (j = 0; j < clone->objects[i].shader.shader->symbol_count; j++)
-                if (clone->objects[i].shader.shader->symbols[j].register_set == MOJOSHADER_SYMREGSET_SAMPLER)
+            for (j = 0; j < pd->symbol_count; j++)
+                if (pd->symbols[j].register_set == MOJOSHADER_SYMREGSET_SAMPLER)
                 {
                     clone->objects[i].shader.samplers[curSampler].sampler_name = clone->params[clone->objects[i].shader.params[j]].value.name;
-                    clone->objects[i].shader.samplers[curSampler].sampler_register = clone->objects[i].shader.shader->symbols[j].register_index;
+                    clone->objects[i].shader.samplers[curSampler].sampler_register = pd->symbols[j].register_index;
                     clone->objects[i].shader.samplers[curSampler].sampler_state_count = clone->params[clone->objects[i].shader.params[j]].value.value_count;
                     clone->objects[i].shader.samplers[curSampler].sampler_states = clone->params[clone->objects[i].shader.params[j]].value.valuesSS;
                     curSampler++;
@@ -1667,7 +1526,7 @@
     return clone;
 
 cloneEffect_outOfMemory:
-    MOJOSHADER_freeEffect(clone);
+    MOJOSHADER_deleteEffect(clone);
     return NULL;
 } // MOJOSHADER_cloneEffect
 
@@ -1744,6 +1603,270 @@
     return NULL;
 } // MOJOSHADER_effectFindNextValidTechnique
 
+
+void MOJOSHADER_effectBegin(MOJOSHADER_effect *effect,
+                            unsigned int *numPasses,
+                            int saveShaderState,
+                            MOJOSHADER_effectStateChanges *stateChanges)
+{
+    *numPasses = effect->current_technique->pass_count;
+    effect->restore_shader_state = saveShaderState;
+    effect->state_changes = stateChanges;
+
+    if (effect->restore_shader_state)
+    {
+        effect->ctx.getBoundShaders(&effect->prev_vertex_shader,
+                                    &effect->prev_pixel_shader);
+    } // if
+} // MOJOSHADER_effectBegin
+
+
+void MOJOSHADER_effectBeginPass(MOJOSHADER_effect *effect,
+                                unsigned int pass)
+{
+    int i, j;
+    MOJOSHADER_effectPass *curPass;
+    MOJOSHADER_effectState *state;
+    MOJOSHADER_effectShader *rawVert = effect->current_vert_raw;
+    MOJOSHADER_effectShader *rawPixl = effect->current_pixl_raw;
+    int has_preshader = 0;
+
+    effect->ctx.getBoundShaders(&effect->current_vert,
+                                &effect->current_pixl);
+
+    assert(effect->current_pass == -1);
+    effect->current_pass = pass;
+    curPass = &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];
+        if (state->type == MOJOSHADER_RS_VERTEXSHADER)
+        {
+            rawVert = &effect->objects[*state->value.valuesI].shader;
+            if (rawVert->is_preshader)
+                has_preshader = 1;
+            else
+                effect->current_vert = rawVert->shader;
+        } // if
+        else if (state->type == MOJOSHADER_RS_PIXELSHADER)
+        {
+            rawPixl = &effect->objects[*state->value.valuesI].shader;
+            if (rawPixl->is_preshader)
+                has_preshader = 1;
+            else
+                effect->current_pixl = rawPixl->shader;
+        }
+    } // for
+
+    effect->state_changes->render_state_changes = curPass->states;
+    effect->state_changes->render_state_change_count = curPass->state_count;
+
+    effect->current_vert_raw = rawVert;
+    effect->current_pixl_raw = rawPixl;
+
+    /* 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)
+    {
+        effect->ctx.bindShaders(effect->current_vert,
+                                effect->current_pixl);
+        if (effect->current_vert_raw != NULL)
+        {
+            effect->state_changes->vertex_sampler_state_changes = rawVert->samplers;
+            effect->state_changes->vertex_sampler_state_change_count = rawVert->sampler_count;
+        } // if
+        if (effect->current_pixl_raw != NULL)
+        {
+            effect->state_changes->sampler_state_changes = rawPixl->samplers;
+            effect->state_changes->sampler_state_change_count = rawPixl->sampler_count;
+        } // if
+    } // if
+
+    MOJOSHADER_effectCommitChanges(effect);
+} // MOJOSHADER_effectBeginPass
+
+
+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 = &params[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
+
+
+void MOJOSHADER_effectCommitChanges(MOJOSHADER_effect *effect)
+{
+    MOJOSHADER_effectShader *rawVert = effect->current_vert_raw;
+    MOJOSHADER_effectShader *rawPixl = effect->current_pixl_raw;
+
+    /* Used for shader selection from preshaders */
+    int i, j;
+    MOJOSHADER_effectValue *param;
+    MOJOSHADER_parseData *pd;
+    float selector;
+    int shader_object;
+    int selector_ran = 0;
+
+    float *vs_reg_file_f, *ps_reg_file_f;
+    int *vs_reg_file_i, *ps_reg_file_i;
+    uint8 *vs_reg_file_b, *ps_reg_file_b;
+
+    /* 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, gls) \
+        if (raw != NULL && raw->is_preshader) \
+        { \
+            i = 0; \
+            do \
+            { \
+                param = &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 = effect->params[raw->params[0]].value.valuesI[(int) selector]; \
+            raw = &effect->objects[shader_object].shader; \
+            gls = raw->shader; \
+            selector_ran = 1; \
+        }
+    SELECT_SHADER_FROM_PRESHADER(rawVert, effect->current_vert)
+    SELECT_SHADER_FROM_PRESHADER(rawPixl, effect->current_pixl)
+    #undef SELECT_SHADER_FROM_PRESHADER
+    if (selector_ran)
+    {
+        effect->ctx.bindShaders(effect->current_vert,
+                                effect->current_pixl);
+        if (effect->current_vert_raw != NULL)
+        {
+            effect->state_changes->vertex_sampler_state_changes = rawVert->samplers;
+            effect->state_changes->vertex_sampler_state_change_count = rawVert->sampler_count;
+        } // if
+        if (effect->current_pixl_raw != NULL)
+        {
+            effect->state_changes->sampler_state_changes = rawPixl->samplers;
+            effect->state_changes->sampler_state_change_count = rawPixl->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) \
+        { \
+            pd = effect->ctx.getParseData(raw->shader); \
+            copy_parameter_data(effect->params, raw->params, \
+                                pd->symbols, \
+                                pd->symbol_count, \
+                                stage##_reg_file_f, \
+                                stage##_reg_file_i, \
+                                stage##_reg_file_b); \
+            if (pd->preshader) \
+            { \
+                copy_parameter_data(effect->params, raw->preshader_params, \
+                                    pd->preshader->symbols, \
+                                    pd->preshader->symbol_count, \
+                                    pd->preshader->registers, \
+                                    NULL, \
+                                    NULL); \
+                MOJOSHADER_runPreshader(pd->preshader, stage##_reg_file_f); \
+            } \
+        }
+    effect->ctx.mapUniformBufferMemory(&vs_reg_file_f, &vs_reg_file_i, &vs_reg_file_b,
+                                       &ps_reg_file_f, &ps_reg_file_i, &ps_reg_file_b);
+    COPY_PARAMETER_DATA(rawVert, vs)
+    COPY_PARAMETER_DATA(rawPixl, ps)
+    effect->ctx.unmapUniformBufferMemory();
+    #undef COPY_PARAMETER_DATA
+} // MOJOSHADER_effectCommitChanges
+
+
+void MOJOSHADER_effectEndPass(MOJOSHADER_effect *effect)
+{
+    assert(effect->current_pass != -1);
+    effect->current_pass = -1;
+} // MOJOSHADER_effectEndPass
+
+
+void MOJOSHADER_effectEnd(MOJOSHADER_effect *effect)
+{
+    if (effect->restore_shader_state)
+    {
+        effect->restore_shader_state = 0;
+        effect->ctx.bindShaders(effect->prev_vertex_shader,
+                                effect->prev_pixel_shader);
+    } // if
+
+    effect->state_changes = NULL;
+} // MOJOSHADER_effectEnd
+
 #endif // MOJOSHADER_EFFECT_SUPPORT
 
 // end of mojoshader_effects.c ...
--- a/mojoshader_effects.h	Fri Apr 24 09:05:12 2020 -0400
+++ b/mojoshader_effects.h	Fri Apr 24 09:13:10 2020 -0400
@@ -409,7 +409,7 @@
     MOJOSHADER_samplerStateRegister *samplers;
     MOJOSHADERNAMELESS union
     {
-        const MOJOSHADER_parseData *shader;
+        void *shader; /* glShader, mtlShader, etc. */
         const MOJOSHADER_preshader *preshader;
     };
 } MOJOSHADER_effectShader;
@@ -476,12 +476,67 @@
 
 
 /*
+ * VTable system for building/running effect shaders...
+ */
+
+typedef void* (MOJOSHADERCALL * MOJOSHADER_compileShaderFunc)(
+    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
+);
+typedef void (MOJOSHADERCALL * MOJOSHADER_shaderAddRefFunc)(void* shader);
+typedef void (MOJOSHADERCALL * MOJOSHADER_deleteShaderFunc)(void* shader);
+typedef MOJOSHADER_parseData* (MOJOSHADERCALL * MOJOSHADER_getParseDataFunc)(
+    void *shader
+);
+typedef void (MOJOSHADERCALL * MOJOSHADER_bindShadersFunc)(
+    void *vshader,
+    void *pshader
+);
+typedef void (MOJOSHADERCALL * MOJOSHADER_getBoundShadersFunc)(
+    void **vshader,
+    void **pshader
+);
+typedef void (MOJOSHADERCALL * MOJOSHADER_mapUniformBufferMemoryFunc)(
+    float **vsf, int **vsi, unsigned char **vsb,
+    float **psf, int **psi, unsigned char **psb
+);
+typedef void (MOJOSHADERCALL * MOJOSHADER_unmapUniformBufferMemoryFunc)();
+
+typedef struct MOJOSHADER_effectShaderContext
+{
+    /* Shader Backend */
+    MOJOSHADER_compileShaderFunc compileShader;
+    MOJOSHADER_shaderAddRefFunc shaderAddRef;
+    MOJOSHADER_deleteShaderFunc deleteShader;
+    MOJOSHADER_getParseDataFunc getParseData;
+    MOJOSHADER_bindShadersFunc bindShaders;
+    MOJOSHADER_getBoundShadersFunc getBoundShaders;
+    MOJOSHADER_mapUniformBufferMemoryFunc mapUniformBufferMemory;
+    MOJOSHADER_unmapUniformBufferMemoryFunc unmapUniformBufferMemory;
+
+    /* Allocator */
+    MOJOSHADER_malloc m;
+    MOJOSHADER_free f;
+    void *malloc_data;
+} MOJOSHADER_effectShaderContext;
+
+
+/*
  * Structure used to return data from parsing of an effect file...
  */
 /* !!! FIXME: most of these ints should be unsigned. */
 typedef struct MOJOSHADER_effect
 {
     /*
+     * Public members. These are the fields your application cares about!
+     */
+
+    /*
      * The number of elements pointed to by (errors).
      */
     int error_count;
@@ -494,11 +549,6 @@
     MOJOSHADER_error *errors;
 
     /*
-     * The name of the profile used to parse the shader. Will be NULL on error.
-     */
-    const char *profile;
-
-    /*
      * The number of params pointed to by (params).
      */
     int param_count;
@@ -524,6 +574,23 @@
     MOJOSHADER_effectTechnique *techniques;
 
     /*
+     * The number of elements pointed to by (objects).
+     */
+    int object_count;
+
+    /*
+     * (object_count) elements of data that specify objects used in
+     *  this effect.
+     * This can be NULL on error or if (object_count) is zero.
+     */
+    MOJOSHADER_effectObject *objects;
+
+    /*
+     * Semi-public members. These might be useful, but are better to access from
+     * a function, not directly.
+     */
+
+    /*
      * The technique currently being rendered by this effect.
      */
     const MOJOSHADER_effectTechnique *current_technique;
@@ -534,16 +601,8 @@
     int current_pass;
 
     /*
-     * The number of elements pointed to by (objects).
+     * Private Members. Do not touch anything below this line!
      */
-    int object_count;
-
-    /*
-     * (object_count) elements of data that specify objects used in
-     *  this effect.
-     * This can be NULL on error or if (object_count) is zero.
-     */
-    MOJOSHADER_effectObject *objects;
 
     /*
      * Value used to determine whether or not to restore the previous shader
@@ -557,42 +616,67 @@
     MOJOSHADER_effectStateChanges *state_changes;
 
     /*
-     * This is the malloc implementation you passed to MOJOSHADER_parseEffect().
+     * Values used to store the current shader state during execution.
      */
-    MOJOSHADER_malloc malloc;
+    MOJOSHADER_effectShader *current_vert_raw;
+    MOJOSHADER_effectShader *current_pixl_raw;
+    void *current_vert;
+    void *current_pixl;
 
     /*
-     * This is the free implementation you passed to MOJOSHADER_parseEffect().
+     * Values used to restore shader state after the effect has ended.
      */
-    MOJOSHADER_free free;
+    void *prev_vertex_shader;
+    void *prev_pixel_shader;
 
     /*
-     * This is the pointer you passed as opaque data for your allocator.
+     * This is the shader implementation you passed to MOJOSHADER_compileEffect().
      */
-    void *malloc_data;
+    MOJOSHADER_effectShaderContext ctx;
 } MOJOSHADER_effect;
 
 
-/* Effect parsing interface... */
+/* Effect compiling interface... */
 
-/* !!! FIXME: document me. */
-DECLSPEC MOJOSHADER_effect *MOJOSHADER_parseEffect(const char *profile,
-                                                   const unsigned char *buf,
-                                                   const unsigned int _len,
-                                                   const MOJOSHADER_swizzle *swiz,
-                                                   const unsigned int swizcount,
-                                                   const MOJOSHADER_samplerMap *smap,
-                                                   const unsigned int smapcount,
-                                                   MOJOSHADER_malloc m,
-                                                   MOJOSHADER_free f,
-                                                   void *d);
+/* Fully compile/link the shaders found within the effect.
+ *
+ *   (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.
+ *   (ctx) contains all the function pointers needed to create and bind shaders
+ *   for a specific backend (OpenGL, Metal, etc).
+ *
+ * This function returns a MOJOSHADER_effect*, containing effect data which
+ *  includes shaders usable with the provided backend.
+ *
+ * This call is only as thread safe as the backend functions!
+ */
+DECLSPEC MOJOSHADER_effect *MOJOSHADER_compileEffect(const unsigned char *tokenbuf,
+                                                     const unsigned int bufsize,
+                                                     const MOJOSHADER_swizzle *swiz,
+                                                     const unsigned int swizcount,
+                                                     const MOJOSHADER_samplerMap *smap,
+                                                     const unsigned int smapcount,
+                                                     const MOJOSHADER_effectShaderContext *ctx);
 
+/* Delete the shaders that were allocated for an effect.
+ *
+ * (effect) is a MOJOSHADER_effect* obtained from MOJOSHADER_compileEffect().
+ *
+ * This call is only as thread safe as the backend functions!
+ */
+DECLSPEC void MOJOSHADER_deleteEffect(const MOJOSHADER_effect *effect);
 
-/* !!! FIXME: document me. */
-DECLSPEC void MOJOSHADER_freeEffect(const MOJOSHADER_effect *effect);
-
-
-/* !!! FIXME: document me. */
+/* Copies an effect, including current parameter/technique data.
+ *
+ * (effect) is a MOJOSHADER_effect* obtained from MOJOSHADER_compileEffect().
+ *
+ * This function returns a MOJOSHADER_effect*, containing effect data which
+ *  includes shaders usable with the provided backend.
+ *
+ * This call is only as thread safe as the backend functions!
+ */
 DECLSPEC MOJOSHADER_effect *MOJOSHADER_cloneEffect(const MOJOSHADER_effect *effect);
 
 
@@ -620,7 +704,7 @@
  *
  * This function maps to ID3DXEffect::SetRawValue.
  *
- * (effect) is a MOJOSHADER_effect* obtained from MOJOSHADER_parseEffect().
+ * (effect) is a MOJOSHADER_effect* obtained from MOJOSHADER_compileEffect().
  * (name) is the human-readable name of the parameter being modified.
  * (data) is the constant values to be applied to the parameter.
  * (offset) is the offset, in bytes, of the parameter data being modified.
@@ -641,7 +725,7 @@
  *
  * This function maps to ID3DXEffect::GetCurrentTechnique.
  *
- * (effect) is a MOJOSHADER_effect* obtained from MOJOSHADER_parseEffect().
+ * (effect) is a MOJOSHADER_effect* obtained from MOJOSHADER_compileEffect().
  *
  * This function returns the technique currently used by the given effect.
  *
@@ -653,7 +737,7 @@
  *
  * This function maps to ID3DXEffect::SetTechnique.
  *
- * (effect) is a MOJOSHADER_effect* obtained from MOJOSHADER_parseEffect().
+ * (effect) is a MOJOSHADER_effect* obtained from MOJOSHADER_compileEffect().
  * (technique) is the technique to be used by the effect when rendered.
  *
  * This function is thread safe.
@@ -665,7 +749,7 @@
  *
  * This function maps to ID3DXEffect::FindNextValidTechnique.
  *
- * (effect) is a MOJOSHADER_effect* obtained from MOJOSHADER_parseEffect().
+ * (effect) is a MOJOSHADER_effect* obtained from MOJOSHADER_compileEffect().
  * (technique) can either be a technique found in the given effect, or NULL to
  *  find the first technique in the given effect.
  *
@@ -678,39 +762,7 @@
                                                                                    const MOJOSHADER_effectTechnique *technique);
 
 
-/* OpenGL effect interface... */
-
-typedef struct MOJOSHADER_glEffect MOJOSHADER_glEffect;
-
-/* Fully compile/link the shaders found within the effect.
- *
- * The MOJOSHADER_glEffect* is solely for use within the OpenGL-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().
- *
- * This function returns a MOJOSHADER_glEffect*, containing OpenGL-specific
- *  data for an accompanying MOJOSHADER_effect*.
- *
- * This call is NOT thread safe! As most OpenGL implementations are not thread
- * safe, you should probably only call this from the same thread that created
- * the GL context.
- */
-DECLSPEC MOJOSHADER_glEffect *MOJOSHADER_glCompileEffect(MOJOSHADER_effect *effect);
-
-/* Delete the shaders that were allocated for an effect.
- *
- * (glEffect) is a MOJOSHADER_glEffect* obtained from
- *  MOJOSHADER_glCompileEffect().
- *
- * This call is NOT thread safe! As most OpenGL implementations are not thread
- * safe, you should probably only call this from the same thread that created
- * the GL context.
- */
-DECLSPEC void MOJOSHADER_glDeleteEffect(MOJOSHADER_glEffect *glEffect);
+/* Effect rendering interface... */
 
 /* Prepare the effect for rendering with the currently applied technique.
  *
@@ -720,7 +772,7 @@
  *  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
+ *  MOJOSHADER_effectBeginPass 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
@@ -728,197 +780,81 @@
  * track your own GL state and restore these states as needed for your
  * application.
  *
- * (glEffect) is a MOJOSHADER_glEffect* obtained from
- *  MOJOSHADER_glCompileEffect().
+ * (effect) is a MOJOSHADER_effect* obtained from MOJOSHADER_compileEffect().
  * (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_glEffectEnd.
+ *  restore the shader bindings after calling MOJOSHADER_effectEnd.
  * (renderState) will be filled by the effect to inform you of the render state
  *  changes introduced by the technique and its passes.
  *
- * This call is NOT thread safe! As most OpenGL implementations are not thread
- * safe, you should probably only call this from the same thread that created
- * the GL context.
+ * This call is only as thread safe as the backend functions!
  */
-DECLSPEC void MOJOSHADER_glEffectBegin(MOJOSHADER_glEffect *glEffect,
-                                       unsigned int *numPasses,
-                                       int saveShaderState,
-                                       MOJOSHADER_effectStateChanges *stateChanges);
+DECLSPEC void MOJOSHADER_effectBegin(MOJOSHADER_effect *effect,
+                                     unsigned int *numPasses,
+                                     int saveShaderState,
+                                     MOJOSHADER_effectStateChanges *stateChanges);
 
 /* Begin an effect pass from the currently applied technique.
  *
  * This function maps to ID3DXEffect::BeginPass.
  *
- * (glEffect) is a MOJOSHADER_glEffect* obtained from
- *  MOJOSHADER_glCompileEffect().
+ * (effect) is a MOJOSHADER_effect* obtained from MOJOSHADER_compileEffect().
  * (pass) is the index of the effect pass as found in the current technique.
  *
- * This call is NOT thread safe! As most OpenGL implementations are not thread
- * safe, you should probably only call this from the same thread that created
- * the GL context.
+ * This call is only as thread safe as the backend functions!
  */
-DECLSPEC void MOJOSHADER_glEffectBeginPass(MOJOSHADER_glEffect *glEffect,
-                                           unsigned int pass);
+DECLSPEC void MOJOSHADER_effectBeginPass(MOJOSHADER_effect *effect,
+                                         unsigned int pass);
 
 /* Push render state changes that occurred within an actively rendering pass.
  *
  * This function maps to ID3DXEffect::CommitChanges.
  *
- * (glEffect) is a MOJOSHADER_glEffect* obtained from
- *  MOJOSHADER_glCompileEffect().
+ * (effect) is a MOJOSHADER_effect* obtained from MOJOSHADER_compileEffect().
  *
- * This call is NOT thread safe! As most OpenGL implementations are not thread
- * safe, you should probably only call this from the same thread that created
- * the GL context.
+ * This call is only as thread safe as the backend functions!
  */
-DECLSPEC void MOJOSHADER_glEffectCommitChanges(MOJOSHADER_glEffect *glEffect);
+DECLSPEC void MOJOSHADER_effectCommitChanges(MOJOSHADER_effect *effect);
 
 /* End an effect pass from the currently applied technique.
  *
  * This function maps to ID3DXEffect::EndPass.
  *
- * (glEffect) is a MOJOSHADER_glEffect* obtained from
- *  MOJOSHADER_glCompileEffect().
+ * (effect) is a MOJOSHADER_effect* obtained from MOJOSHADER_compileEffect().
  *
- * This call is NOT thread safe! As most OpenGL implementations are not thread
- * safe, you should probably only call this from the same thread that created
- * the GL context.
+ * This call is only as thread safe as the backend functions!
  */
-DECLSPEC void MOJOSHADER_glEffectEndPass(MOJOSHADER_glEffect *glEffect);
+DECLSPEC void MOJOSHADER_effectEndPass(MOJOSHADER_effect *effect);
 
 /* Complete rendering the effect technique, and restore the render state.
  *
  * This function maps to ID3DXEffect::End.
  *
- * (glEffect) is a MOJOSHADER_glEffect* obtained from
- *  MOJOSHADER_glCompileEffect().
+ * (effect) is a MOJOSHADER_effect* obtained from MOJOSHADER_compileEffect().
  *
- * This call is NOT thread safe! As most OpenGL implementations are not thread
- * safe, you should probably only call this from the same thread that created
- * the GL context.
+ * This call is only as thread safe as the backend functions!
  */
-DECLSPEC void MOJOSHADER_glEffectEnd(MOJOSHADER_glEffect *glEffect);
+DECLSPEC void MOJOSHADER_effectEnd(MOJOSHADER_effect *effect);
 
 
-/* Metal effect interface... */
-
-typedef struct MOJOSHADER_mtlEffect MOJOSHADER_mtlEffect;
-typedef struct MOJOSHADER_mtlShaderState
-{
-    MOJOSHADER_mtlShader *vertexShader;
-    MOJOSHADER_mtlShader *fragmentShader;
-    void *vertexUniformBuffer; // MTLBuffer*
-    void *fragmentUniformBuffer; // MTLBuffer*
-    int vertexUniformOffset;
-    int fragmentUniformOffset;
-} MOJOSHADER_mtlShaderState;
+/* Profile-specific functions... */
 
-/* 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.
+/*
+ * Compile a MTLLibrary that contains all shaders of the given effect.
  *
- * 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.
+ * This call requires a valid MOJOSHADER_mtlContext to have been created,
+ *  or it will crash your program. See MOJOSHADER_mtlCreateContext().
  *
- * (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.
+ * Returns NULL on error, the generated MTLLibrary on success.
  */
-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);
+DECLSPEC void *MOJOSHADER_mtlCompileLibrary(MOJOSHADER_effect *effect);
 
-/* 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.
+/*
+ * Free the MTLLibrary given by (library).
  */
-DECLSPEC void MOJOSHADER_mtlEffectCommitChanges(MOJOSHADER_mtlEffect *mtlEffect,
-                                                MOJOSHADER_mtlShaderState *shState);
+DECLSPEC void MOJOSHADER_mtlDeleteLibrary(void *library);
 
-/* 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 */
 
--- 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
--- a/mojoshader_opengl.c	Fri Apr 24 09:05:12 2020 -0400
+++ b/mojoshader_opengl.c	Fri Apr 24 09:13:10 2020 -0400
@@ -1896,6 +1896,13 @@
 } // MOJOSHADER_glCompileShader
 
 
+void MOJOSHADER_glShaderAddRef(MOJOSHADER_glShader *shader)
+{
+    if (shader != NULL)
+        shader->refcount++;
+} // MOJOSHADER_glShaderAddRef
+
+
 const MOJOSHADER_parseData *MOJOSHADER_glGetShaderParseData(
                                                 MOJOSHADER_glShader *shader)
 {
@@ -2430,6 +2437,26 @@
 } // MOJOSHADER_glBindShaders
 
 
+void MOJOSHADER_glGetBoundShaders(MOJOSHADER_glShader **v,
+                                  MOJOSHADER_glShader **p)
+{
+    if (v != NULL)
+    {
+        if (ctx->bound_program != NULL)
+            *v = ctx->bound_program->vertex;
+        else
+            *v = NULL;
+    } // if
+    if (p != NULL)
+    {
+        if (ctx->bound_program != NULL)
+            *p = ctx->bound_program->fragment;
+        else
+            *p = NULL;
+    } // if
+} // MOJOSHADER_glGetBoundShaders
+
+
 static inline uint minuint(const uint a, const uint b)
 {
     return ((a < b) ? a : b);
@@ -2602,6 +2629,24 @@
 } // MOJOSHADER_glGetPixelShaderUniformB
 
 
+void MOJOSHADER_glMapUniformBufferMemory(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_glMapUniformBufferMemory
+
+
+void MOJOSHADER_glUnmapUniformBufferMemory()
+{
+    ctx->generation++;
+} // MOJOSHADER_glUnmapUniformBufferMemory
+
+
 static inline GLenum opengl_attr_type(const MOJOSHADER_attributeType type)
 {
     switch (type)
@@ -2945,431 +2990,5 @@
     ctx = ((current_ctx == _ctx) ? NULL : current_ctx);
 } // MOJOSHADER_glDestroyContext
 
-
-#ifdef MOJOSHADER_EFFECT_SUPPORT
-
-
-struct MOJOSHADER_glEffect
-{
-    MOJOSHADER_effect *effect;
-    unsigned int num_shaders;
-    MOJOSHADER_glShader *shaders;
-    unsigned int *shader_indices;
-    unsigned int num_preshaders;
-    unsigned int *preshader_indices;
-    MOJOSHADER_glShader *current_vert;
-    MOJOSHADER_glShader *current_frag;
-    MOJOSHADER_effectShader *current_vert_raw;
-    MOJOSHADER_effectShader *current_frag_raw;
-    MOJOSHADER_glProgram *prev_program;
-};
-
-
-MOJOSHADER_glEffect *MOJOSHADER_glCompileEffect(MOJOSHADER_effect *effect)
-{
-    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;
-    GLuint shader = 0;
-
-    MOJOSHADER_glEffect *retval = (MOJOSHADER_glEffect *) m(sizeof (MOJOSHADER_glEffect), d);
-    if (retval == NULL)
-    {
-        out_of_memory();
-        return NULL;
-    } // if
-    memset(retval, '\0', sizeof (MOJOSHADER_glEffect));
-
-    // 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++;
-        } // if
-    } // for
-
-    // Alloc shader information
-    retval->shaders = (MOJOSHADER_glShader *) m(retval->num_shaders * sizeof (MOJOSHADER_glShader), d);
-    if (retval->shaders == NULL)
-    {
-        f(retval, d);
-        out_of_memory();
-        return NULL;
-    } // if
-    memset(retval->shaders, '\0', retval->num_shaders * sizeof (MOJOSHADER_glShader));
-    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
-
-    // Run through the shaders again, compiling and 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
-            if (!ctx->profileCompileShader(object->shader.shader, &shader))
-                goto compile_shader_fail;
-            retval->shaders[current_shader].parseData = object->shader.shader;
-            retval->shaders[current_shader].handle = shader;
-            retval->shaders[current_shader].refcount = 1;
-            retval->shader_indices[current_shader] = i;
-            current_shader++;
-        } // if
-    } // for
-
-    retval->effect = effect;
-    return retval;
-
-compile_shader_fail:
-    for (i = 0; i < retval->num_shaders; i++)
-        if (retval->shaders[i].handle != 0)
-            ctx->profileDeleteShader(retval->shaders[i].handle);
-    f(retval->shader_indices, d);
-    f(retval->shaders, d);
-    f(retval, d);
-    return NULL;
-} // MOJOSHADER_glCompileEffect
-
-
-void MOJOSHADER_glDeleteEffect(MOJOSHADER_glEffect *glEffect)
-{
-    int i;
-    MOJOSHADER_free f = glEffect->effect->free;
-    void *d = glEffect->effect->malloc_data;
-
-    for (i = 0; i < glEffect->num_shaders; i++)
-    {
-        /* Arbitarily add a reference to the refcount.
-         * We're going to be calling glDeleteShader so we can clean out the
-         * program cache, but we can NOT let it free() the array elements!
-         * We'll do that ourselves, as we malloc()'d in CompileEffect.
-         * -flibit
-         */
-        glEffect->shaders[i].refcount++;
-        MOJOSHADER_glDeleteShader(&glEffect->shaders[i]);
-
-        /* Delete the shader, but do NOT delete the parse data!
-         * The parse data belongs to the parent effect.
-         * -flibit
-         */
-        ctx->profileDeleteShader(glEffect->shaders[i].handle);
-    } // for
-
-    f(glEffect->shader_indices, d);
-    f(glEffect->preshader_indices, d);
-    f(glEffect, d);
-} // MOJOSHADER_glDeleteEffect
-
-
-void MOJOSHADER_glEffectBegin(MOJOSHADER_glEffect *glEffect,
-                              unsigned int *numPasses,
-                              int saveShaderState,
-                              MOJOSHADER_effectStateChanges *stateChanges)
-{
-    *numPasses = glEffect->effect->current_technique->pass_count;
-    glEffect->effect->restore_shader_state = saveShaderState;
-    glEffect->effect->state_changes = stateChanges;
-
-    if (glEffect->effect->restore_shader_state)
-        glEffect->prev_program = ctx->bound_program;
-} // MOJOSHADER_glEffectBegin
-
-
-void MOJOSHADER_glEffectBeginPass(MOJOSHADER_glEffect *glEffect,
-                                  unsigned int pass)
-{
-    int i, j;
-    MOJOSHADER_effectPass *curPass;
-    MOJOSHADER_effectState *state;
-    MOJOSHADER_effectShader *rawVert = glEffect->current_vert_raw;
-    MOJOSHADER_effectShader *rawFrag = glEffect->current_frag_raw;
-    int has_preshader = 0;
-
-    if (ctx->bound_program != NULL)
-    {
-        glEffect->current_vert = ctx->bound_program->vertex;
-        glEffect->current_frag = ctx->bound_program->fragment;
-    } // if
-
-    assert(glEffect->effect->current_pass == -1);
-    glEffect->effect->current_pass = pass;
-    curPass = &glEffect->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, gls) \
-            (state->type == stype) \
-            { \
-                j = 0; \
-                do \
-                { \
-                    if (*state->value.valuesI == glEffect->shader_indices[j]) \
-                    { \
-                        raw = &glEffect->effect->objects[*state->value.valuesI].shader; \
-                        glEffect->gls = &glEffect->shaders[j]; \
-                        break; \
-                    } \
-                    else if (glEffect->num_preshaders > 0 \
-                          && *state->value.valuesI == glEffect->preshader_indices[j]) \
-                    { \
-                        raw = &glEffect->effect->objects[*state->value.valuesI].shader; \
-                        has_preshader = 1; \
-                        break; \
-                    } \
-                } while (++j < glEffect->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
-
-    glEffect->effect->state_changes->render_state_changes = curPass->states;
-    glEffect->effect->state_changes->render_state_change_count = curPass->state_count;
-
-    glEffect->current_vert_raw = rawVert;
-    glEffect->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)
-    {
-        MOJOSHADER_glBindShaders(glEffect->current_vert,
-                                 glEffect->current_frag);
-        if (glEffect->current_vert_raw != NULL)
-        {
-            glEffect->effect->state_changes->vertex_sampler_state_changes = rawVert->samplers;
-            glEffect->effect->state_changes->vertex_sampler_state_change_count = rawVert->sampler_count;
-        } // if
-        if (glEffect->current_frag_raw != NULL)
-        {
-            glEffect->effect->state_changes->sampler_state_changes = rawFrag->samplers;
-            glEffect->effect->state_changes->sampler_state_change_count = rawFrag->sampler_count;
-        } // if
-    } // if
-
-    MOJOSHADER_glEffectCommitChanges(glEffect);
-} // MOJOSHADER_glEffectBeginPass
-
-
-static inline void copy_parameter_data(MOJOSHADER_effectParam *params,
-                                       unsigned int *param_loc,
-                                       MOJOSHADER_symbol *symbols,
-                                       unsigned int symbol_count,
-                                       GLfloat *regf, GLint *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 = &params[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
-
-
-void MOJOSHADER_glEffectCommitChanges(MOJOSHADER_glEffect *glEffect)
-{
-    MOJOSHADER_effectShader *rawVert = glEffect->current_vert_raw;
-    MOJOSHADER_effectShader *rawFrag = glEffect->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, gls) \
-        if (raw != NULL && raw->is_preshader) \
-        { \
-            i = 0; \
-            do \
-            { \
-                param = &glEffect->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 = glEffect->effect->params[raw->params[0]].value.valuesI[(int) selector]; \
-            raw = &glEffect->effect->objects[shader_object].shader; \
-            i = 0; \
-            do \
-            { \
-                if (shader_object == glEffect->shader_indices[i]) \
-                { \
-                    gls = &glEffect->shaders[i]; \
-                    break; \
-                } \
-            } while (++i < glEffect->num_shaders); \
-            selector_ran = 1; \
-        }
-    SELECT_SHADER_FROM_PRESHADER(rawVert, glEffect->current_vert)
-    SELECT_SHADER_FROM_PRESHADER(rawFrag, glEffect->current_frag)
-    #undef SELECT_SHADER_FROM_PRESHADER
-    if (selector_ran)
-    {
-        MOJOSHADER_glBindShaders(glEffect->current_vert,
-                                 glEffect->current_frag);
-        if (glEffect->current_vert_raw != NULL)
-        {
-            glEffect->effect->state_changes->vertex_sampler_state_changes = rawVert->samplers;
-            glEffect->effect->state_changes->vertex_sampler_state_change_count = rawVert->sampler_count;
-        } // if
-        if (glEffect->current_frag_raw != NULL)
-        {
-            glEffect->effect->state_changes->sampler_state_changes = rawFrag->samplers;
-            glEffect->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(glEffect->effect->params, raw->params, \
-                                raw->shader->symbols, \
-                                raw->shader->symbol_count, \
-                                ctx->stage##_reg_file_f, \
-                                ctx->stage##_reg_file_i, \
-                                ctx->stage##_reg_file_b); \
-            if (raw->shader->preshader) \
-            { \
-                copy_parameter_data(glEffect->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, ctx->stage##_reg_file_f); \
-            } \
-        }
-    COPY_PARAMETER_DATA(rawVert, vs)
-    COPY_PARAMETER_DATA(rawFrag, ps)
-    #undef COPY_PARAMETER_DATA
-
-    ctx->generation++;
-} // MOJOSHADER_glEffectCommitChanges
-
-
-void MOJOSHADER_glEffectEndPass(MOJOSHADER_glEffect *glEffect)
-{
-    assert(glEffect->effect->current_pass != -1);
-    glEffect->effect->current_pass = -1;
-} // MOJOSHADER_glEffectEndPass
-
-
-void MOJOSHADER_glEffectEnd(MOJOSHADER_glEffect *glEffect)
-{
-    if (glEffect->effect->restore_shader_state)
-    {
-        glEffect->effect->restore_shader_state = 0;
-        MOJOSHADER_glBindProgram(glEffect->prev_program);
-    } // if
-
-    glEffect->effect->state_changes = NULL;
-} // MOJOSHADER_glEffectEnd
-
-
-#endif // MOJOSHADER_EFFECT_SUPPORT
-
 // end of mojoshader_opengl.c ...