Show More
Commit Description:
Tweak calculations....
Commit Description:
Tweak calculations.
Prevent a negative number of visitors and separate visitors from donors.
References:
File last commit:
Show/Diff file:
Action:
FNA/lib/FNA3D/MojoShader/mojoshader_metal.c
553 lines | 16.3 KiB | text/x-c | CLexer
553 lines | 16.3 KiB | text/x-c | CLexer
r690 | /** | |||
* 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 <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_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 ... | ||||