|
|
/**
|
|
|
* 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.
|
|
|
*/
|
|
|
|
|
|
#if (defined(__APPLE__) && defined(__MACH__))
|
|
|
#define PLATFORM_APPLE 1
|
|
|
#include "TargetConditionals.h"
|
|
|
#define OBJC_OLD_DISPATCH_PROTOTYPES 1
|
|
|
#include <objc/message.h>
|
|
|
#define objc_msgSend_STR ((void* (*)(void*, void*, const char*))objc_msgSend)
|
|
|
#define objc_msgSend_PTR ((void* (*)(void*, void*, void*))objc_msgSend)
|
|
|
#define objc_msgSend_INT_PTR ((void* (*)(void*, void*, int, void*))objc_msgSend)
|
|
|
#define objc_msgSend_PTR_PTR_PTR ((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;
|
|
|
void *library; // MTLLibrary*
|
|
|
int numInternalBuffers;
|
|
|
} MOJOSHADER_mtlShader;
|
|
|
|
|
|
// 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
|
|
|
|
|
|
// profile-specific implementations...
|
|
|
|
|
|
#if SUPPORT_PROFILE_METAL && PLATFORM_APPLE
|
|
|
#ifdef MOJOSHADER_EFFECT_SUPPORT
|
|
|
|
|
|
/* 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;
|
|
|
} MOJOSHADER_mtlUniformBuffer;
|
|
|
|
|
|
typedef struct MOJOSHADER_mtlShaderState
|
|
|
{
|
|
|
MOJOSHADER_mtlShader *vertexShader;
|
|
|
MOJOSHADER_mtlShader *fragmentShader;
|
|
|
void *vertexUniformBuffer; // MTLBuffer*
|
|
|
void *fragmentUniformBuffer; // MTLBuffer*
|
|
|
int vertexUniformOffset;
|
|
|
int fragmentUniformOffset;
|
|
|
} MOJOSHADER_mtlShaderState;
|
|
|
|
|
|
/* 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 objc_msgSend_STR(
|
|
|
objc_msgSend(classNSString, selAlloc),
|
|
|
selInitWithUTF8String,
|
|
|
str
|
|
|
);
|
|
|
} // cstr_to_nsstr
|
|
|
|
|
|
static const char *nsstr_to_cstr(void *str)
|
|
|
{
|
|
|
return (char *) objc_msgSend(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];
|
|
|
|
|
|
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 = ¶ms[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
|
|
|
|
|
|
/* 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
|
|
|
int align = 256;
|
|
|
#endif
|
|
|
|
|
|
return align * ((n + align - 1) / align);
|
|
|
} // next_highest_alignment
|
|
|
|
|
|
static int UBO_buffer_length(void *buffer)
|
|
|
{
|
|
|
return (int) objc_msgSend(buffer, selLength);
|
|
|
} // UBO_buffer_length
|
|
|
|
|
|
static void *UBO_buffer_contents(void *buffer)
|
|
|
{
|
|
|
return (void *) objc_msgSend(buffer, selContents);
|
|
|
} // UBO_buffer_contents
|
|
|
|
|
|
static void *UBO_create_backing_buffer(MOJOSHADER_mtlUniformBuffer *ubo, int f)
|
|
|
{
|
|
|
void *oldBuffer = ubo->internalBuffers[f];
|
|
|
void *newBuffer = objc_msgSend_INT_PTR(
|
|
|
ubo->device,
|
|
|
selNewBufferWithLength,
|
|
|
ubo->internalBufferSize,
|
|
|
NULL
|
|
|
);
|
|
|
if (oldBuffer != NULL)
|
|
|
{
|
|
|
// Copy over data from old buffer
|
|
|
memcpy(
|
|
|
UBO_buffer_contents(newBuffer),
|
|
|
UBO_buffer_contents(oldBuffer),
|
|
|
UBO_buffer_length(oldBuffer)
|
|
|
);
|
|
|
|
|
|
// Free the old buffer
|
|
|
objc_msgSend(oldBuffer, selRelease);
|
|
|
} //if
|
|
|
|
|
|
return newBuffer;
|
|
|
} // UBO_create_backing_buffer
|
|
|
|
|
|
static void UBO_predraw(MOJOSHADER_mtlUniformBuffer *ubo)
|
|
|
{
|
|
|
if (!ubo->alreadyWritten)
|
|
|
{
|
|
|
ubo->alreadyWritten = 1;
|
|
|
return;
|
|
|
} // if
|
|
|
|
|
|
ubo->internalOffset += ubo->bufferSize;
|
|
|
|
|
|
int buflen = UBO_buffer_length(ubo->internalBuffers[ubo->currentFrame]);
|
|
|
if (ubo->internalOffset >= buflen)
|
|
|
{
|
|
|
// Double capacity when we're out of room
|
|
|
if (ubo->internalOffset >= ubo->internalBufferSize)
|
|
|
ubo->internalBufferSize *= 2;
|
|
|
|
|
|
ubo->internalBuffers[ubo->currentFrame] =
|
|
|
UBO_create_backing_buffer(ubo, ubo->currentFrame);
|
|
|
} //if
|
|
|
} // UBO_predraw
|
|
|
|
|
|
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)
|
|
|
{
|
|
|
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
|
|
|
|
|
|
// 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++)
|
|
|
{
|
|
|
ubo->internalBuffers[i] = NULL;
|
|
|
ubo->internalBuffers[i] = UBO_create_backing_buffer(ubo, 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;
|
|
|
} // create_ubo
|
|
|
|
|
|
static void dealloc_ubo(MOJOSHADER_mtlShader *shader,
|
|
|
MOJOSHADER_free f,
|
|
|
void* d)
|
|
|
{
|
|
|
if (shader->ubo == NULL)
|
|
|
return;
|
|
|
|
|
|
LL_remove_node(&ubos, shader->ubo, f, d);
|
|
|
for (int i = 0; i < shader->ubo->numInternalBuffers; i++)
|
|
|
{
|
|
|
objc_msgSend(shader->ubo->internalBuffers[i], 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)
|
|
|
return;
|
|
|
|
|
|
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;
|
|
|
} // if
|
|
|
else
|
|
|
{
|
|
|
regF = ps_reg_file_f;
|
|
|
regI = ps_reg_file_i;
|
|
|
regB = ps_reg_file_b;
|
|
|
} // else
|
|
|
|
|
|
UBO_predraw(shader->ubo);
|
|
|
void *buf = shader->ubo->internalBuffers[shader->ubo->currentFrame];
|
|
|
void *contents = UBO_buffer_contents(buf) + shader->ubo->internalOffset;
|
|
|
|
|
|
int offset = 0;
|
|
|
for (int i = 0; i < shader->parseData->uniform_count; i++)
|
|
|
{
|
|
|
int idx = shader->parseData->uniforms[i].index;
|
|
|
int arrayCount = shader->parseData->uniforms[i].array_count;
|
|
|
int size = arrayCount ? arrayCount : 1;
|
|
|
|
|
|
switch (shader->parseData->uniforms[i].type)
|
|
|
{
|
|
|
case MOJOSHADER_UNIFORM_FLOAT:
|
|
|
memcpy(
|
|
|
contents + (offset * 16),
|
|
|
®F[4 * idx],
|
|
|
size * 16
|
|
|
);
|
|
|
break;
|
|
|
|
|
|
case MOJOSHADER_UNIFORM_INT:
|
|
|
// !!! FIXME: Need a test case
|
|
|
memcpy(
|
|
|
contents + (offset * 16),
|
|
|
®I[4 * idx],
|
|
|
size * 16
|
|
|
);
|
|
|
break;
|
|
|
|
|
|
case MOJOSHADER_UNIFORM_BOOL:
|
|
|
// !!! FIXME: Need a test case
|
|
|
memcpy(
|
|
|
contents + offset,
|
|
|
®B[idx],
|
|
|
size
|
|
|
);
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
assert(0); // This should never happen.
|
|
|
break;
|
|
|
} // switch
|
|
|
|
|
|
offset += size;
|
|
|
} // for
|
|
|
} // update_uniform_buffer
|
|
|
|
|
|
/* Public API */
|
|
|
|
|
|
MOJOSHADER_mtlEffect *MOJOSHADER_mtlCompileEffect(MOJOSHADER_effect *effect,
|
|
|
void *mtlDevice,
|
|
|
int numBackingBuffers)
|
|
|
{
|
|
|
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;
|
|
|
|
|
|
// Make sure the Objective-C selectors have been initialized...
|
|
|
if (selAlloc == NULL)
|
|
|
initSelectors();
|
|
|
|
|
|
MOJOSHADER_mtlEffect *retval = (MOJOSHADER_mtlEffect *) m(sizeof (MOJOSHADER_mtlEffect), d);
|
|
|
if (retval == NULL)
|
|
|
{
|
|
|
out_of_memory();
|
|
|
return NULL;
|
|
|
} // if
|
|
|
memset(retval, '\0', sizeof (MOJOSHADER_mtlEffect));
|
|
|
|
|
|
// 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++;
|
|
|
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];
|
|
|
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;
|
|
|
} // 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
|
|
|
const char *repl = "[[ point_coord ]]";
|
|
|
char *ptr;
|
|
|
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
|
|
|
|
|
|
// Alloc shader information
|
|
|
retval->shaders = (MOJOSHADER_mtlShader *) m(retval->num_shaders * sizeof (MOJOSHADER_mtlShader), d);
|
|
|
if (retval->shaders == 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();
|
|
|
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 = objc_msgSend_PTR_PTR_PTR(
|
|
|
mtlDevice,
|
|
|
selNewLibraryWithSource,
|
|
|
shader_source_ns,
|
|
|
NULL,
|
|
|
&compileError
|
|
|
);
|
|
|
retval->library = library;
|
|
|
f(shader_source, d);
|
|
|
objc_msgSend(shader_source_ns, selRelease);
|
|
|
|
|
|
if (library == NULL)
|
|
|
{
|
|
|
// Set the error
|
|
|
void *error_nsstr = objc_msgSend(compileError, selLocalizedDescription);
|
|
|
set_error(nsstr_to_cstr(error_nsstr));
|
|
|
|
|
|
goto compile_shader_fail;
|
|
|
} // if
|
|
|
|
|
|
// Run through the shaders again, 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
|
|
|
|
|
|
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++;
|
|
|
} // if
|
|
|
} // for
|
|
|
|
|
|
retval->effect = effect;
|
|
|
return retval;
|
|
|
|
|
|
compile_shader_fail:
|
|
|
f(retval->shader_indices, d);
|
|
|
f(retval->shaders, d);
|
|
|
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
|
|
|
|
|
|
/* Release the library */
|
|
|
objc_msgSend(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)
|
|
|
{
|
|
|
*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);
|
|
|
|
|
|
void MOJOSHADER_mtlEffectBeginPass(MOJOSHADER_mtlEffect *mtlEffect,
|
|
|
unsigned int pass,
|
|
|
MOJOSHADER_mtlShaderState *shState)
|
|
|
{
|
|
|
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++)
|
|
|
{
|
|
|
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 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
|
|
|
|
|
|
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_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;
|
|
|
|
|
|
/* 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;
|
|
|
|
|
|
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
|
|
|
|
|
|
/* 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(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(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)
|
|
|
{
|
|
|
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
|
|
|
|
|
|
void *MOJOSHADER_mtlGetFunctionHandle(MOJOSHADER_mtlShader *shader)
|
|
|
{
|
|
|
if (shader == NULL)
|
|
|
return NULL;
|
|
|
|
|
|
void *fnname = cstr_to_nsstr(shader->parseData->mainfn);
|
|
|
void *ret = objc_msgSend_PTR(
|
|
|
shader->library,
|
|
|
selNewFunctionWithName,
|
|
|
fnname
|
|
|
);
|
|
|
objc_msgSend(fnname, selRelease);
|
|
|
objc_msgSend(ret, selRetain);
|
|
|
|
|
|
return ret;
|
|
|
} // MOJOSHADER_mtlGetFunctionHandle
|
|
|
|
|
|
void MOJOSHADER_mtlEndFrame()
|
|
|
{
|
|
|
LLNODE *node = ubos;
|
|
|
while (node != NULL)
|
|
|
{
|
|
|
UBO_end_frame((MOJOSHADER_mtlUniformBuffer *) node->data);
|
|
|
node = node->next;
|
|
|
} // while
|
|
|
} // 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
|
|
|
|
|
|
#endif /* MOJOSHADER_EFFECT_SUPPORT */
|
|
|
#endif /* SUPPORT_PROFILE_METAL && PLATFORM_APPLE */
|
|
|
|
|
|
// end of mojoshader_metal.c ...
|
|
|
|