/** * MojoShader; generate shader programs from bytecode of compiled * Direct3D shaders. * * Please see the file LICENSE.txt in the source's root directory. * * This file written by Ryan C. Gordon. */ #define __MOJOSHADER_INTERNAL__ 1 #include "mojoshader_internal.h" #if (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 #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_uu ((void* (*)(void*, void*, uint64, uint64))objc_msgSend) #define msg_ppp ((void* (*)(void*, void*, void*, void*, void*))objc_msgSend) // Error state... static char error_buffer[1024] = { '\0' }; static void set_error(const char *str) { snprintf(error_buffer, sizeof (error_buffer), "%s", str); } // set_error static inline void out_of_memory(void) { set_error("out of memory"); } // out_of_memory // Max entries for each register file type... #define MAX_REG_FILE_F 8192 #define MAX_REG_FILE_I 2047 #define MAX_REG_FILE_B 2047 typedef struct MOJOSHADER_mtlContext { // Allocators... MOJOSHADER_malloc malloc_fn; MOJOSHADER_free free_fn; void *malloc_data; // The constant register files... // !!! FIXME: Man, it kills me how much memory this takes... // !!! FIXME: ... make this dynamically allocated on demand. float vs_reg_file_f[MAX_REG_FILE_F * 4]; 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]; // Pointer to the active MTLDevice. void* device; // The uniform MTLBuffer shared between all shaders in the context. void *ubo; // The current offsets into the UBO, per shader. int vertexUniformOffset; int pixelUniformOffset; int totalUniformOffset; // The currently bound shaders. MOJOSHADER_mtlShader *vertexShader; MOJOSHADER_mtlShader *pixelShader; // Objective-C Selectors void* classNSString; void* selAlloc; void* selContents; void* selInitWithUTF8String; void* selLength; void* selLocalizedDescription; void* selNewBufferWithLength; void* selNewFunctionWithName; void* selNewLibraryWithSource; void* selRelease; void* selUTF8String; } MOJOSHADER_mtlContext; static MOJOSHADER_mtlContext *ctx = NULL; /* Uniform buffer utilities */ static inline int next_highest_alignment(int n) { #if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_SIMULATOR int align = 16; #else // !!! 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 update_uniform_buffer(MOJOSHADER_mtlShader *shader) { 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 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: src = ®F[4 * idx]; size *= 16; break; case MOJOSHADER_UNIFORM_INT: src = ®I[4 * idx]; size *= 16; break; case MOJOSHADER_UNIFORM_BOOL: src = ®B[idx]; break; default: assert(0); // This should never happen. 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 */ MOJOSHADER_mtlContext *MOJOSHADER_mtlCreateContext(void* mtlDevice, MOJOSHADER_malloc m, MOJOSHADER_free f, void *malloc_d) { 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); if (ctx == NULL) { out_of_memory(); goto init_fail; } // if 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; // 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->selLength = sel_registerName("length"); 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->selUTF8String = sel_registerName("UTF8String"); // 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); ctx = current_ctx; return NULL; } // MOJOSHADER_mtlCreateContext void MOJOSHADER_mtlMakeContextCurrent(MOJOSHADER_mtlContext *_ctx) { ctx = _ctx; } // MOJOSHADER_mtlMakeContextCurrent 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, *fnname; // Count the number of shaders before allocating src_len = 0; 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; 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 // Handle texcoord0 -> point_coord conversion if (strstr(shader_source, "[[point_size]]")) { // !!! FIXME: This assumes all texcoord0 attributes in the effect are // !!! FIXME: actually point coords! It ain't necessarily so! -caleb repl = "[[ point_coord ]]"; while ((ptr = strstr(shader_source, "[[user(texcoord0)]]"))) { memcpy(ptr, repl, strlen(repl)); // "float4" -> "float2" int spaces = 0; while (spaces < 2) if (*(ptr--) == ' ') spaces++; memcpy(ptr, "2", sizeof(char)); } // while } // if // 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) { compileError = msg(compileError, ctx->selLocalizedDescription); set_error((char*) msg(compileError, ctx->selUTF8String)); return NULL; } // if // Run through the shaders again, getting the function handles 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) continue; 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 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->handle = NULL; // populated by MOJOSHADER_mtlCompileLibrary return retval; compile_shader_fail: MOJOSHADER_freeParseData(retval->parseData); f(retval, d); return NULL; } // MOJOSHADER_mtlCompileShader void MOJOSHADER_mtlShaderAddRef(MOJOSHADER_mtlShader *shader) { if (shader != NULL) shader->refcount++; } // MOJOSHADER_mtlShaderAddRef const MOJOSHADER_parseData *MOJOSHADER_mtlGetShaderParseData( MOJOSHADER_mtlShader *shader) { return (shader != NULL) ? shader->parseData : NULL; } // MOJOSHADER_mtlGetParseData void MOJOSHADER_mtlBindShaders(MOJOSHADER_mtlShader *vshader, MOJOSHADER_mtlShader *pshader) { // Use the last bound shaders in case of NULL if (vshader != NULL) ctx->vertexShader = vshader; if (pshader != NULL) ctx->pixelShader = pshader; } // MOJOSHADER_mtlBindShaders void MOJOSHADER_mtlGetBoundShaders(MOJOSHADER_mtlShader **vshader, MOJOSHADER_mtlShader **pshader) { *vshader = ctx->vertexShader; *pshader = ctx->pixelShader; } // MOJOSHADER_mtlGetBoundShaders 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 void MOJOSHADER_mtlUnmapUniformBufferMemory() { /* This has nothing to do with unmapping memory * and everything to do with updating uniform * buffers with the latest parameter contents. */ update_uniform_buffer(ctx->vertexShader); update_uniform_buffer(ctx->pixelShader); } // MOJOSHADER_mtlUnmapUniformBufferMemory void MOJOSHADER_mtlGetUniformData(void **buf, int *voff, int *poff) { *buf = ctx->ubo; *voff = ctx->vertexUniformOffset; *poff = ctx->pixelUniformOffset; } // MOJOSHADER_mtlGetUniformBuffers void *MOJOSHADER_mtlGetFunctionHandle(MOJOSHADER_mtlShader *shader) { if (shader == NULL) return NULL; return shader->handle; } // MOJOSHADER_mtlGetFunctionHandle void MOJOSHADER_mtlEndFrame() { ctx->totalUniformOffset = 0; ctx->vertexUniformOffset = 0; ctx->pixelUniformOffset = 0; } // MOJOSHADER_mtlEndFrame int MOJOSHADER_mtlGetVertexAttribLocation(MOJOSHADER_mtlShader *vert, MOJOSHADER_usage usage, int index) { if (vert == NULL) return -1; for (int i = 0; i < vert->parseData->attribute_count; i++) { if (vert->parseData->attributes[i].usage == usage && vert->parseData->attributes[i].index == index) { return i; } // if } // for // failure, couldn't find requested attribute return -1; } // MOJOSHADER_mtlGetVertexAttribLocation const char *MOJOSHADER_mtlGetError(void) { return error_buffer; } // MOJOSHADER_mtlGetError 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 */ // end of mojoshader_metal.c ...