|
|
/**
|
|
|
* 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 ...
|
|
|
|