--- a/mojoshader_metal.c Tue Jul 07 17:44:32 2020 -0400
+++ b/mojoshader_metal.c Tue Jul 07 19:25:47 2020 -0400
@@ -7,28 +7,32 @@
* This file written by Ryan C. Gordon.
*/
+#define __MOJOSHADER_INTERNAL__ 1
+#include "mojoshader_internal.h"
+
#if (defined(__APPLE__) && defined(__MACH__))
#define PLATFORM_APPLE 1
+#endif /* (defined(__APPLE__) && defined(__MACH__)) */
+
+typedef struct MOJOSHADER_mtlShader
+{
+ const MOJOSHADER_parseData *parseData;
+ uint32 refcount;
+ void *handle; // MTLFunction*
+} MOJOSHADER_mtlShader;
+
+// profile-specific implementations...
+
+#if SUPPORT_PROFILE_METAL && PLATFORM_APPLE
+#ifdef MOJOSHADER_EFFECT_SUPPORT
+
#include "TargetConditionals.h"
#include <objc/message.h>
#define msg ((void* (*)(void*, void*))objc_msgSend)
#define msg_s ((void* (*)(void*, void*, const char*))objc_msgSend)
#define msg_p ((void* (*)(void*, void*, void*))objc_msgSend)
-#define msg_ip ((void* (*)(void*, void*, int, void*))objc_msgSend)
+#define msg_uu ((void* (*)(void*, void*, uint64, uint64))objc_msgSend)
#define msg_ppp ((void* (*)(void*, void*, void*, void*, void*))objc_msgSend)
-#endif /* (defined(__APPLE__) && defined(__MACH__)) */
-
-#define __MOJOSHADER_INTERNAL__ 1
-#include "mojoshader_internal.h"
-
-typedef struct MOJOSHADER_mtlUniformBuffer MOJOSHADER_mtlUniformBuffer;
-typedef struct MOJOSHADER_mtlShader
-{
- const MOJOSHADER_parseData *parseData;
- MOJOSHADER_mtlUniformBuffer *ubo;
- uint32 refcount;
- void *library; // MTLLibrary*
-} MOJOSHADER_mtlShader;
// Error state...
static char error_buffer[1024] = { '\0' };
@@ -43,23 +47,6 @@
set_error("out of memory");
} // out_of_memory
-// profile-specific implementations...
-
-#if SUPPORT_PROFILE_METAL && PLATFORM_APPLE
-#ifdef MOJOSHADER_EFFECT_SUPPORT
-
-/* Structs */
-
-typedef struct MOJOSHADER_mtlUniformBuffer
-{
- int bufferSize;
- void **internalBuffers; // MTLBuffer*
- int internalBufferSize;
- int internalOffset;
- int currentFrame;
- int inUse;
-} MOJOSHADER_mtlUniformBuffer;
-
// Max entries for each register file type...
#define MAX_REG_FILE_F 8192
#define MAX_REG_FILE_I 2047
@@ -85,17 +72,13 @@
// Pointer to the active MTLDevice.
void* device;
- // The maximum number of frames in flight.
- int framesInFlight;
-
- // Array of UBOs that are being used in the current frame.
- MOJOSHADER_mtlUniformBuffer **buffersInUse;
+ // The uniform MTLBuffer shared between all shaders in the context.
+ void *ubo;
- // The current capacity of the uniform buffer array.
- int bufferArrayCapacity;
-
- // The actual number of UBOs used in the current frame.
- int numBuffersInUse;
+ // The current offsets into the UBO, per shader.
+ int vertexUniformOffset;
+ int pixelUniformOffset;
+ int totalUniformOffset;
// The currently bound shaders.
MOJOSHADER_mtlShader *vertexShader;
@@ -104,16 +87,15 @@
// Objective-C Selectors
void* classNSString;
void* selAlloc;
+ void* selContents;
void* selInitWithUTF8String;
- void* selUTF8String;
void* selLength;
- void* selContents;
+ void* selLocalizedDescription;
void* selNewBufferWithLength;
- void* selRelease;
+ void* selNewFunctionWithName;
void* selNewLibraryWithSource;
- void* selLocalizedDescription;
- void* selNewFunctionWithName;
- void* selRetain;
+ void* selRelease;
+ void* selUTF8String;
} MOJOSHADER_mtlContext;
static MOJOSHADER_mtlContext *ctx = NULL;
@@ -125,206 +107,61 @@
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_SIMULATOR
int align = 16;
#else
+ // !!! FIXME: Will Apple Silicon Macs have a different minimum alignment?
int align = 256;
#endif
return align * ((n + align - 1) / align);
} // next_highest_alignment
-static void* create_ubo_backing_buffer(MOJOSHADER_mtlUniformBuffer *ubo,
- int frame)
-{
- void *oldBuffer = ubo->internalBuffers[frame];
- void *newBuffer = msg_ip(
- ctx->device,
- ctx->selNewBufferWithLength,
- ubo->internalBufferSize,
- NULL
- );
- if (oldBuffer != NULL)
- {
- // Copy over data from old buffer
- memcpy(
- msg(newBuffer, ctx->selContents),
- msg(oldBuffer, ctx->selContents),
- (int) msg(oldBuffer, ctx->selLength)
- );
-
- // Free the old buffer
- msg(oldBuffer, ctx->selRelease);
- } //if
-
- return newBuffer;
-} // create_ubo_backing_buffer
-
-static void predraw_ubo(MOJOSHADER_mtlUniformBuffer *ubo)
-{
- if (!ubo->inUse)
- {
- 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 = (int) msg(
- ubo->internalBuffers[ubo->currentFrame],
- ctx->selLength
- );
- if (ubo->internalOffset >= buflen)
- {
- // Double capacity when we're out of room
- if (ubo->internalOffset >= ubo->internalBufferSize)
- ubo->internalBufferSize *= 2;
-
- ubo->internalBuffers[ubo->currentFrame] =
- create_ubo_backing_buffer(ubo, ubo->currentFrame);
- } //if
-} // predraw_ubo
-
-static MOJOSHADER_mtlUniformBuffer* create_ubo(MOJOSHADER_mtlShader *shader,
- MOJOSHADER_malloc m, void* d)
-{
- int uniformCount = shader->parseData->uniform_count;
- if (uniformCount == 0)
- return NULL;
-
- // Calculate how big we need to make the buffer
- int buflen = 0;
- for (int i = 0; i < uniformCount; i += 1)
- {
- int arrayCount = shader->parseData->uniforms[i].array_count;
- int uniformSize = 16;
- if (shader->parseData->uniforms[i].type == MOJOSHADER_UNIFORM_BOOL)
- uniformSize = 1;
- buflen += (arrayCount ? arrayCount : 1) * uniformSize;
- } // for
-
- // 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++)
- {
- retval->internalBuffers[i] = NULL; // basically a memset('\0')
- retval->internalBuffers[i] = create_ubo_backing_buffer(retval, i);
- } // for
-
- return retval;
-} // create_ubo
-
-static void dealloc_ubo(MOJOSHADER_mtlShader *shader,
- MOJOSHADER_free f,
- void* d)
-{
- if (shader->ubo == NULL)
- return;
-
- for (int i = 0; i < ctx->framesInFlight; i++)
- {
- msg(shader->ubo->internalBuffers[i], ctx->selRelease);
- shader->ubo->internalBuffers[i] = NULL;
- } // for
-
- f(shader->ubo->internalBuffers, d);
- f(shader->ubo, d);
-} // dealloc_ubo
-
-static void *get_uniform_buffer(MOJOSHADER_mtlShader *shader)
-{
- if (shader == NULL || shader->ubo == NULL)
- return NULL;
-
- return shader->ubo->internalBuffers[shader->ubo->currentFrame];
-} // get_uniform_buffer
-
-static int get_uniform_offset(MOJOSHADER_mtlShader *shader)
-{
- if (shader == NULL || shader->ubo == NULL)
- return 0;
-
- return shader->ubo->internalOffset;
-} // get_uniform_offset
-
static void update_uniform_buffer(MOJOSHADER_mtlShader *shader)
{
- if (shader == NULL || shader->ubo == NULL)
+ if (shader == NULL || shader->parseData->uniform_count == 0)
return;
float *regF; int *regI; uint8 *regB;
if (shader->parseData->shader_type == MOJOSHADER_TYPE_VERTEX)
{
+ ctx->vertexUniformOffset = ctx->totalUniformOffset;
regF = ctx->vs_reg_file_f;
regI = ctx->vs_reg_file_i;
regB = ctx->vs_reg_file_b;
} // if
else
{
+ ctx->pixelUniformOffset = ctx->totalUniformOffset;
regF = ctx->ps_reg_file_f;
regI = ctx->ps_reg_file_i;
regB = ctx->ps_reg_file_b;
} // else
- predraw_ubo(shader->ubo);
- void *buf = shader->ubo->internalBuffers[shader->ubo->currentFrame];
- void *contents = msg(buf, ctx->selContents) + shader->ubo->internalOffset;
-
+ void *contents = msg(ctx->ubo, ctx->selContents) + ctx->totalUniformOffset;
int offset = 0;
for (int i = 0; i < shader->parseData->uniform_count; i++)
{
+ if (shader->parseData->uniforms[i].constant)
+ continue;
+
int idx = shader->parseData->uniforms[i].index;
int arrayCount = shader->parseData->uniforms[i].array_count;
+
+ void *src = NULL;
int size = arrayCount ? arrayCount : 1;
switch (shader->parseData->uniforms[i].type)
{
case MOJOSHADER_UNIFORM_FLOAT:
- memcpy(
- contents + (offset * 16),
- ®F[4 * idx],
- size * 16
- );
+ src = ®F[4 * idx];
+ size *= 16;
break;
case MOJOSHADER_UNIFORM_INT:
- // !!! FIXME: Need a test case
- memcpy(
- contents + (offset * 16),
- ®I[4 * idx],
- size * 16
- );
+ src = ®I[4 * idx];
+ size *= 16;
break;
case MOJOSHADER_UNIFORM_BOOL:
- // !!! FIXME: Need a test case
- memcpy(
- contents + offset,
- ®B[idx],
- size
- );
+ src = ®B[idx];
break;
default:
@@ -332,22 +169,34 @@
break;
} // switch
+ memcpy(contents + offset, src, size);
offset += size;
} // for
+
+ ctx->totalUniformOffset = next_highest_alignment(ctx->totalUniformOffset + offset);
+ if (ctx->totalUniformOffset >= (int) msg(ctx->ubo, ctx->selLength))
+ {
+ // !!! FIXME: Is there a better way to handle this?
+ assert(0 && "Uniform data exceeded the size of the buffer!");
+ } // if
} // update_uniform_buffer
/* Public API */
-int MOJOSHADER_mtlCreateContext(void* mtlDevice, int framesInFlight,
- MOJOSHADER_malloc m, MOJOSHADER_free f,
- void *malloc_d)
+MOJOSHADER_mtlContext *MOJOSHADER_mtlCreateContext(void* mtlDevice,
+ MOJOSHADER_malloc m, MOJOSHADER_free f,
+ void *malloc_d)
{
- assert(ctx == NULL);
+ MOJOSHADER_mtlContext *retval = NULL;
+ MOJOSHADER_mtlContext *current_ctx = ctx;
+ int i;
+
+ ctx = NULL;
if (m == NULL) m = MOJOSHADER_internal_malloc;
if (f == NULL) f = MOJOSHADER_internal_free;
- ctx = (MOJOSHADER_mtlContext *) m(sizeof(MOJOSHADER_mtlContext), malloc_d);
+ ctx = (MOJOSHADER_mtlContext *) m(sizeof (MOJOSHADER_mtlContext), malloc_d);
if (ctx == NULL)
{
out_of_memory();
@@ -361,43 +210,41 @@
// 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->selContents = sel_registerName("contents");
ctx->selInitWithUTF8String = sel_registerName("initWithUTF8String:");
- ctx->selUTF8String = sel_registerName("UTF8String");
ctx->selLength = sel_registerName("length");
- ctx->selContents = sel_registerName("contents");
+ ctx->selLocalizedDescription = sel_registerName("localizedDescription");
ctx->selNewBufferWithLength = sel_registerName("newBufferWithLength:options:");
+ ctx->selNewFunctionWithName = sel_registerName("newFunctionWithName:");
+ ctx->selNewLibraryWithSource = sel_registerName("newLibraryWithSource:options:error:");
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");
+ ctx->selUTF8String = sel_registerName("UTF8String");
- return 0;
+ // Create the uniform buffer
+ ctx->ubo = msg_uu(mtlDevice, ctx->selNewBufferWithLength,
+ next_highest_alignment(1000000), 0);
+
+ retval = ctx;
+ ctx = current_ctx;
+ return retval;
init_fail:
if (ctx != NULL)
f(ctx, malloc_d);
- return -1;
+ ctx = current_ctx;
+ return NULL;
} // MOJOSHADER_mtlCreateContext
-void MOJOSHADER_mtlDestroyContext(void)
+
+void MOJOSHADER_mtlMakeContextCurrent(MOJOSHADER_mtlContext *_ctx)
{
- ctx->free_fn(ctx->buffersInUse, ctx->malloc_data);
- ctx->free_fn(ctx, ctx->malloc_data);
- ctx = NULL;
-} // MOJOSHADER_mtlDestroyContext
+ ctx = _ctx;
+} // MOJOSHADER_mtlMakeContextCurrent
+
void *MOJOSHADER_mtlCompileLibrary(MOJOSHADER_effect *effect)
{
@@ -410,7 +257,7 @@
const char *repl;
MOJOSHADER_effectObject *object;
MOJOSHADER_mtlShader *shader;
- void *retval, *compileError, *shader_source_ns;
+ void *retval, *compileError, *shader_source_ns, *fnname;
// Count the number of shaders before allocating
src_len = 0;
@@ -488,7 +335,7 @@
return NULL;
} // if
- // Run through the shaders again, setting the library reference
+ // Run through the shaders again, getting the function handles
for (i = 0; i < effect->object_count; i++)
{
object = &effect->objects[i];
@@ -498,25 +345,32 @@
if (object->shader.is_preshader)
continue;
- ((MOJOSHADER_mtlShader*) object->shader.shader)->library = retval;
+ shader = (MOJOSHADER_mtlShader*) object->shader.shader;
+ fnname = msg_s(
+ msg(ctx->classNSString, ctx->selAlloc),
+ ctx->selInitWithUTF8String,
+ shader->parseData->mainfn
+ );
+ shader->handle = msg_p(
+ retval,
+ ctx->selNewFunctionWithName,
+ fnname
+ );
+ msg(fnname, ctx->selRelease);
} // if
} // for
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)
+ 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;
@@ -539,8 +393,7 @@
retval->parseData = pd;
retval->refcount = 1;
- retval->ubo = create_ubo(retval, m, d);
- retval->library = NULL; // populated by MOJOSHADER_mtlCompileLibrary
+ retval->handle = NULL; // populated by MOJOSHADER_mtlCompileLibrary
return retval;
@@ -550,26 +403,13 @@
return NULL;
} // MOJOSHADER_mtlCompileShader
+
void MOJOSHADER_mtlShaderAddRef(MOJOSHADER_mtlShader *shader)
{
if (shader != NULL)
shader->refcount++;
} // MOJOSHADER_mtlShaderAddRef
-void MOJOSHADER_mtlDeleteShader(MOJOSHADER_mtlShader *shader)
-{
- if (shader != NULL)
- {
- if (shader->refcount > 1)
- shader->refcount--;
- else
- {
- dealloc_ubo(shader, ctx->free_fn, ctx->malloc_data);
- MOJOSHADER_freeParseData(shader->parseData);
- ctx->free_fn(shader, ctx->malloc_data);
- } // else
- } // if
-} // MOJOSHADER_mtlDeleteShader
const MOJOSHADER_parseData *MOJOSHADER_mtlGetShaderParseData(
MOJOSHADER_mtlShader *shader)
@@ -577,6 +417,7 @@
return (shader != NULL) ? shader->parseData : NULL;
} // MOJOSHADER_mtlGetParseData
+
void MOJOSHADER_mtlBindShaders(MOJOSHADER_mtlShader *vshader,
MOJOSHADER_mtlShader *pshader)
{
@@ -588,6 +429,7 @@
ctx->pixelShader = pshader;
} // MOJOSHADER_mtlBindShaders
+
void MOJOSHADER_mtlGetBoundShaders(MOJOSHADER_mtlShader **vshader,
MOJOSHADER_mtlShader **pshader)
{
@@ -595,6 +437,7 @@
*pshader = ctx->pixelShader;
} // MOJOSHADER_mtlGetBoundShaders
+
void MOJOSHADER_mtlMapUniformBufferMemory(float **vsf, int **vsi, unsigned char **vsb,
float **psf, int **psi, unsigned char **psb)
{
@@ -606,6 +449,7 @@
*psb = ctx->ps_reg_file_b;
} // MOJOSHADER_mtlMapUniformBufferMemory
+
void MOJOSHADER_mtlUnmapUniformBufferMemory()
{
/* This has nothing to do with unmapping memory
@@ -616,48 +460,32 @@
update_uniform_buffer(ctx->pixelShader);
} // MOJOSHADER_mtlUnmapUniformBufferMemory
-void MOJOSHADER_mtlGetUniformBuffers(void **vbuf, int *voff,
- void **pbuf, int *poff)
+
+void MOJOSHADER_mtlGetUniformData(void **buf, int *voff, int *poff)
{
- *vbuf = get_uniform_buffer(ctx->vertexShader);
- *voff = get_uniform_offset(ctx->vertexShader);
- *pbuf = get_uniform_buffer(ctx->pixelShader);
- *poff = get_uniform_offset(ctx->pixelShader);
+ *buf = ctx->ubo;
+ *voff = ctx->vertexUniformOffset;
+ *poff = ctx->pixelUniformOffset;
} // MOJOSHADER_mtlGetUniformBuffers
+
void *MOJOSHADER_mtlGetFunctionHandle(MOJOSHADER_mtlShader *shader)
{
if (shader == NULL)
return NULL;
- void *fnname = msg_s(
- msg(ctx->classNSString, ctx->selAlloc),
- ctx->selInitWithUTF8String,
- shader->parseData->mainfn
- );
- void *ret = msg_p(
- shader->library,
- ctx->selNewFunctionWithName,
- fnname
- );
- msg(fnname, ctx->selRelease);
- msg(ret, ctx->selRetain);
+ return shader->handle;
+} // MOJOSHADER_mtlGetFunctionHandle
- return ret;
-} // MOJOSHADER_mtlGetFunctionHandle
void MOJOSHADER_mtlEndFrame()
{
- for (int i = 0; i < ctx->numBuffersInUse; i += 1)
- {
- MOJOSHADER_mtlUniformBuffer *buf = ctx->buffersInUse[i];
- buf->internalOffset = 0;
- buf->currentFrame = (buf->currentFrame + 1) % ctx->framesInFlight;
- buf->inUse = 0;
- } // for
- ctx->numBuffersInUse = 0;
+ ctx->totalUniformOffset = 0;
+ ctx->vertexUniformOffset = 0;
+ ctx->pixelUniformOffset = 0;
} // MOJOSHADER_mtlEndFrame
+
int MOJOSHADER_mtlGetVertexAttribLocation(MOJOSHADER_mtlShader *vert,
MOJOSHADER_usage usage, int index)
{
@@ -677,11 +505,48 @@
return -1;
} // MOJOSHADER_mtlGetVertexAttribLocation
+
const char *MOJOSHADER_mtlGetError(void)
{
return error_buffer;
} // MOJOSHADER_mtlGetError
+
+void MOJOSHADER_mtlDeleteLibrary(void *library)
+{
+ msg(library, ctx->selRelease);
+} // MOJOSHADER_mtlDeleteLibrary
+
+
+void MOJOSHADER_mtlDeleteShader(MOJOSHADER_mtlShader *shader)
+{
+ if (shader != NULL)
+ {
+ if (shader->refcount > 1)
+ shader->refcount--;
+ else
+ {
+ msg(shader->handle, ctx->selRelease);
+ MOJOSHADER_freeParseData(shader->parseData);
+ ctx->free_fn(shader, ctx->malloc_data);
+ } // else
+ } // if
+} // MOJOSHADER_mtlDeleteShader
+
+
+void MOJOSHADER_mtlDestroyContext(MOJOSHADER_mtlContext *_ctx)
+{
+ MOJOSHADER_mtlContext *current_ctx = ctx;
+ ctx = _ctx;
+
+ if (ctx->ubo != NULL)
+ msg(ctx->ubo, ctx->selRelease);
+
+ if (ctx != NULL)
+ ctx->free_fn(ctx, ctx->malloc_data);
+ ctx = ((current_ctx == _ctx) ? NULL : current_ctx);
+} // MOJOSHADER_mtlDestroyContext
+
#endif /* MOJOSHADER_EFFECT_SUPPORT */
#endif /* SUPPORT_PROFILE_METAL && PLATFORM_APPLE */