Show More
Commit Description:
Various UI improvements.
Commit Description:
Various UI improvements.
References:
File last commit:
Show/Diff file:
Action:
FNA/lib/MojoShader/mojoshader_assembler.c
1776 lines | 51.6 KiB | text/x-c | CLexer
1776 lines | 51.6 KiB | text/x-c | CLexer
r0 | /** | |||
* 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. | ||||
*/ | ||||
// !!! FIXME: this should probably use a formal grammar and not a hand-written | ||||
// !!! FIXME: pile of C code. | ||||
#define __MOJOSHADER_INTERNAL__ 1 | ||||
#include "mojoshader_internal.h" | ||||
#if !SUPPORT_PROFILE_BYTECODE | ||||
#error Shader assembler needs bytecode profile. Fix your build. | ||||
#endif | ||||
#if DEBUG_ASSEMBLER_PARSER | ||||
#define print_debug_token(token, len, val) \ | ||||
MOJOSHADER_print_debug_token("ASSEMBLER", token, len, val) | ||||
#else | ||||
#define print_debug_token(token, len, val) | ||||
#endif | ||||
typedef struct SourcePos | ||||
{ | ||||
const char *filename; | ||||
uint32 line; | ||||
} SourcePos; | ||||
// Context...this is state that changes as we assemble a shader... | ||||
typedef struct Context | ||||
{ | ||||
int isfail; | ||||
int out_of_memory; | ||||
MOJOSHADER_malloc malloc; | ||||
MOJOSHADER_free free; | ||||
void *malloc_data; | ||||
const char *current_file; | ||||
int current_position; | ||||
ErrorList *errors; | ||||
Preprocessor *preprocessor; | ||||
MOJOSHADER_shaderType shader_type; | ||||
uint8 major_ver; | ||||
uint8 minor_ver; | ||||
int pushedback; | ||||
const char *token; // assembler token! | ||||
unsigned int tokenlen; // assembler token! | ||||
Token tokenval; // assembler token! | ||||
uint32 version_token; // bytecode token! | ||||
uint32 tokenbuf[16]; // bytecode tokens! | ||||
int tokenbufpos; // bytecode tokens! | ||||
DestArgInfo dest_arg; | ||||
Buffer *output; | ||||
Buffer *token_to_source; | ||||
Buffer *ctab; | ||||
} Context; | ||||
// !!! FIXME: cut and paste between every damned source file follows... | ||||
// !!! FIXME: We need to make some sort of ContextBase that applies to all | ||||
// !!! FIXME: files and move this stuff to mojoshader_common.c ... | ||||
// Convenience functions for allocators... | ||||
static inline void out_of_memory(Context *ctx) | ||||
{ | ||||
ctx->isfail = ctx->out_of_memory = 1; | ||||
} // out_of_memory | ||||
static inline void *Malloc(Context *ctx, const size_t len) | ||||
{ | ||||
void *retval = ctx->malloc((int) len, ctx->malloc_data); | ||||
if (retval == NULL) | ||||
out_of_memory(ctx); | ||||
return retval; | ||||
} // Malloc | ||||
static inline char *StrDup(Context *ctx, const char *str) | ||||
{ | ||||
char *retval = (char *) Malloc(ctx, strlen(str) + 1); | ||||
if (retval != NULL) | ||||
strcpy(retval, str); | ||||
return retval; | ||||
} // StrDup | ||||
static inline void Free(Context *ctx, void *ptr) | ||||
{ | ||||
ctx->free(ptr, ctx->malloc_data); | ||||
} // Free | ||||
static void *MallocBridge(int bytes, void *data) | ||||
{ | ||||
return Malloc((Context *) data, (size_t) bytes); | ||||
} // MallocBridge | ||||
static void FreeBridge(void *ptr, void *data) | ||||
{ | ||||
Free((Context *) data, ptr); | ||||
} // FreeBridge | ||||
static void failf(Context *ctx, const char *fmt, ...) ISPRINTF(2,3); | ||||
static void failf(Context *ctx, const char *fmt, ...) | ||||
{ | ||||
ctx->isfail = 1; | ||||
if (ctx->out_of_memory) | ||||
return; | ||||
va_list ap; | ||||
va_start(ap, fmt); | ||||
errorlist_add_va(ctx->errors, ctx->current_file, ctx->current_position, fmt, ap); | ||||
va_end(ap); | ||||
} // failf | ||||
static inline void fail(Context *ctx, const char *reason) | ||||
{ | ||||
failf(ctx, "%s", reason); | ||||
} // fail | ||||
static inline int isfail(const Context *ctx) | ||||
{ | ||||
return ctx->isfail; | ||||
} // isfail | ||||
// Shader model version magic... | ||||
static inline uint32 ver_ui32(const uint8 major, const uint8 minor) | ||||
{ | ||||
return ( (((uint32) major) << 16) | (((minor) == 0xFF) ? 0 : (minor)) ); | ||||
} // version_ui32 | ||||
static inline int shader_version_atleast(const Context *ctx, const uint8 maj, | ||||
const uint8 min) | ||||
{ | ||||
return (ver_ui32(ctx->major_ver, ctx->minor_ver) >= ver_ui32(maj, min)); | ||||
} // shader_version_atleast | ||||
static inline int shader_is_pixel(const Context *ctx) | ||||
{ | ||||
return (ctx->shader_type == MOJOSHADER_TYPE_PIXEL); | ||||
} // shader_is_pixel | ||||
static inline int shader_is_vertex(const Context *ctx) | ||||
{ | ||||
return (ctx->shader_type == MOJOSHADER_TYPE_VERTEX); | ||||
} // shader_is_vertex | ||||
static inline void pushback(Context *ctx) | ||||
{ | ||||
#if DEBUG_ASSEMBLER_PARSER | ||||
printf("ASSEMBLER PUSHBACK\n"); | ||||
#endif | ||||
assert(!ctx->pushedback); | ||||
ctx->pushedback = 1; | ||||
} // pushback | ||||
static Token nexttoken(Context *ctx) | ||||
{ | ||||
if (ctx->pushedback) | ||||
ctx->pushedback = 0; | ||||
else | ||||
{ | ||||
while (1) | ||||
{ | ||||
ctx->token = preprocessor_nexttoken(ctx->preprocessor, | ||||
&ctx->tokenlen, | ||||
&ctx->tokenval); | ||||
if (preprocessor_outofmemory(ctx->preprocessor)) | ||||
{ | ||||
ctx->tokenval = TOKEN_EOI; | ||||
ctx->token = NULL; | ||||
ctx->tokenlen = 0; | ||||
break; | ||||
} // if | ||||
unsigned int line; | ||||
ctx->current_file = preprocessor_sourcepos(ctx->preprocessor,&line); | ||||
ctx->current_position = (int) line; | ||||
if (ctx->tokenval == TOKEN_BAD_CHARS) | ||||
{ | ||||
fail(ctx, "Bad characters in source file"); | ||||
continue; | ||||
} // else if | ||||
else if (ctx->tokenval == TOKEN_PREPROCESSING_ERROR) | ||||
{ | ||||
fail(ctx, ctx->token); | ||||
continue; | ||||
} // else if | ||||
break; | ||||
} // while | ||||
} // else | ||||
print_debug_token(ctx->token, ctx->tokenlen, ctx->tokenval); | ||||
return ctx->tokenval; | ||||
} // nexttoken | ||||
static void output_token_noswap(Context *ctx, const uint32 token) | ||||
{ | ||||
if (!isfail(ctx)) | ||||
{ | ||||
buffer_append(ctx->output, &token, sizeof (token)); | ||||
// We only need a list of these that grows throughout processing, and | ||||
// is flattened for reference at the end of the run, so we use a | ||||
// Buffer. It's sneaky! | ||||
unsigned int pos = 0; | ||||
const char *fname = preprocessor_sourcepos(ctx->preprocessor, &pos); | ||||
SourcePos srcpos; | ||||
memset(&srcpos, '\0', sizeof (SourcePos)); | ||||
srcpos.line = pos; | ||||
srcpos.filename = fname; // cached in preprocessor! | ||||
buffer_append(ctx->token_to_source, &srcpos, sizeof (SourcePos)); | ||||
} // if | ||||
} // output_token_noswap | ||||
static inline void output_token(Context *ctx, const uint32 token) | ||||
{ | ||||
output_token_noswap(ctx, SWAP32(token)); | ||||
} // output_token | ||||
static void output_comment_bytes(Context *ctx, const uint8 *buf, size_t len) | ||||
{ | ||||
if (len > (0xFFFF * 4)) // length is stored as token count, in 16 bits. | ||||
fail(ctx, "Comment field is too big"); | ||||
else if (!isfail(ctx)) | ||||
{ | ||||
const uint32 tokencount = (len / 4) + ((len % 4) ? 1 : 0); | ||||
output_token(ctx, 0xFFFE | (tokencount << 16)); | ||||
while (len >= 4) | ||||
{ | ||||
output_token_noswap(ctx, *((const uint32 *) buf)); | ||||
len -= 4; | ||||
buf += 4; | ||||
} // while | ||||
if (len > 0) // handle spillover... | ||||
{ | ||||
union { uint8 ui8[4]; uint32 ui32; } overflow; | ||||
overflow.ui32 = 0; | ||||
memcpy(overflow.ui8, buf, len); | ||||
output_token_noswap(ctx, overflow.ui32); | ||||
} // if | ||||
} // else if | ||||
} // output_comment_bytes | ||||
static inline void output_comment_string(Context *ctx, const char *str) | ||||
{ | ||||
output_comment_bytes(ctx, (const uint8 *) str, strlen(str)); | ||||
} // output_comment_string | ||||
static int require_comma(Context *ctx) | ||||
{ | ||||
const Token token = nexttoken(ctx); | ||||
if (token != ((Token) ',')) | ||||
{ | ||||
fail(ctx, "Comma expected"); | ||||
return 0; | ||||
} // if | ||||
return 1; | ||||
} // require_comma | ||||
static int check_token_segment(Context *ctx, const char *str) | ||||
{ | ||||
// !!! FIXME: these are case-insensitive, right? | ||||
const size_t len = strlen(str); | ||||
if ( (ctx->tokenlen < len) || (strncasecmp(ctx->token, str, len) != 0) ) | ||||
return 0; | ||||
ctx->token += len; | ||||
ctx->tokenlen -= len; | ||||
return 1; | ||||
} // check_token_segment | ||||
static int check_token(Context *ctx, const char *str) | ||||
{ | ||||
const size_t len = strlen(str); | ||||
if ( (ctx->tokenlen != len) || (strncasecmp(ctx->token, str, len) != 0) ) | ||||
return 0; | ||||
ctx->token += len; | ||||
ctx->tokenlen = 0; | ||||
return 1; | ||||
} // check_token | ||||
static int ui32fromtoken(Context *ctx, uint32 *_val) | ||||
{ | ||||
unsigned int i; | ||||
for (i = 0; i < ctx->tokenlen; i++) | ||||
{ | ||||
if ((ctx->token[i] < '0') || (ctx->token[i] > '9')) | ||||
break; | ||||
} // for | ||||
if (i == 0) | ||||
{ | ||||
*_val = 0; | ||||
return 0; | ||||
} // if | ||||
const unsigned int len = i; | ||||
uint32 val = 0; | ||||
uint32 mult = 1; | ||||
while (i--) | ||||
{ | ||||
val += ((uint32) (ctx->token[i] - '0')) * mult; | ||||
mult *= 10; | ||||
} // while | ||||
ctx->token += len; | ||||
ctx->tokenlen -= len; | ||||
*_val = val; | ||||
return 1; | ||||
} // ui32fromtoken | ||||
static int parse_register_name(Context *ctx, RegisterType *rtype, int *rnum) | ||||
{ | ||||
if (nexttoken(ctx) != TOKEN_IDENTIFIER) | ||||
{ | ||||
fail(ctx, "Expected register"); | ||||
return 0; | ||||
} // if | ||||
int neednum = 1; | ||||
int regnum = 0; | ||||
RegisterType regtype = REG_TYPE_TEMP; | ||||
// Watch out for substrings! oDepth must be checked before oD, since | ||||
// the latter will match either case. | ||||
if (check_token_segment(ctx, "oDepth")) | ||||
{ | ||||
regtype = REG_TYPE_DEPTHOUT; | ||||
neednum = 0; | ||||
} // else if | ||||
else if (check_token_segment(ctx, "vFace")) | ||||
{ | ||||
regtype = REG_TYPE_MISCTYPE; | ||||
regnum = (int) MISCTYPE_TYPE_FACE; | ||||
neednum = 0; | ||||
} // else if | ||||
else if (check_token_segment(ctx, "vPos")) | ||||
{ | ||||
regtype = REG_TYPE_MISCTYPE; | ||||
regnum = (int) MISCTYPE_TYPE_POSITION; | ||||
neednum = 0; | ||||
} // else if | ||||
else if (check_token_segment(ctx, "oPos")) | ||||
{ | ||||
regtype = REG_TYPE_RASTOUT; | ||||
regnum = (int) RASTOUT_TYPE_POSITION; | ||||
neednum = 0; | ||||
} // else if | ||||
else if (check_token_segment(ctx, "oFog")) | ||||
{ | ||||
regtype = REG_TYPE_RASTOUT; | ||||
regnum = (int) RASTOUT_TYPE_FOG; | ||||
neednum = 0; | ||||
} // else if | ||||
else if (check_token_segment(ctx, "oPts")) | ||||
{ | ||||
regtype = REG_TYPE_RASTOUT; | ||||
regnum = (int) RASTOUT_TYPE_POINT_SIZE; | ||||
neednum = 0; | ||||
} // else if | ||||
else if (check_token_segment(ctx, "aL")) | ||||
{ | ||||
regtype = REG_TYPE_LOOP; | ||||
neednum = 0; | ||||
} // else if | ||||
else if (check_token_segment(ctx, "oC")) | ||||
regtype = REG_TYPE_COLOROUT; | ||||
else if (check_token_segment(ctx, "oT")) | ||||
regtype = REG_TYPE_OUTPUT; | ||||
else if (check_token_segment(ctx, "oD")) | ||||
regtype = REG_TYPE_ATTROUT; | ||||
else if (check_token_segment(ctx, "r")) | ||||
regtype = REG_TYPE_TEMP; | ||||
else if (check_token_segment(ctx, "v")) | ||||
regtype = REG_TYPE_INPUT; | ||||
else if (check_token_segment(ctx, "c")) | ||||
regtype = REG_TYPE_CONST; | ||||
else if (check_token_segment(ctx, "i")) | ||||
regtype = REG_TYPE_CONSTINT; | ||||
else if (check_token_segment(ctx, "b")) | ||||
regtype = REG_TYPE_CONSTBOOL; | ||||
else if (check_token_segment(ctx, "s")) | ||||
regtype = REG_TYPE_SAMPLER; | ||||
else if (check_token_segment(ctx, "l")) | ||||
regtype = REG_TYPE_LABEL; | ||||
else if (check_token_segment(ctx, "p")) | ||||
regtype = REG_TYPE_PREDICATE; | ||||
else if (check_token_segment(ctx, "o")) | ||||
regtype = REG_TYPE_OUTPUT; | ||||
else if (check_token_segment(ctx, "a")) | ||||
regtype = REG_TYPE_ADDRESS; | ||||
else if (check_token_segment(ctx, "t")) | ||||
regtype = REG_TYPE_ADDRESS; | ||||
//case REG_TYPE_TEMPFLOAT16: // !!! FIXME: don't know this asm string | ||||
else | ||||
{ | ||||
fail(ctx, "expected register type"); | ||||
regtype = REG_TYPE_CONST; | ||||
regnum = 0; | ||||
neednum = 0; | ||||
} // else | ||||
// "c[5]" is the same as "c5", so if the token is done, see if next is '['. | ||||
if ((neednum) && (ctx->tokenlen == 0)) | ||||
{ | ||||
const int tlen = ctx->tokenlen; // we need to protect this for later. | ||||
if (nexttoken(ctx) == ((Token) '[')) | ||||
neednum = 0; // don't need a number on register name itself. | ||||
pushback(ctx); | ||||
ctx->tokenlen = tlen; | ||||
} // if | ||||
if (neednum) | ||||
{ | ||||
uint32 ui32 = 0; | ||||
if (!ui32fromtoken(ctx, &ui32)) | ||||
fail(ctx, "Invalid register index"); | ||||
regnum = (int) ui32; | ||||
} // if | ||||
// split up REG_TYPE_CONST | ||||
if (regtype == REG_TYPE_CONST) | ||||
{ | ||||
if (regnum < 2048) | ||||
{ | ||||
regtype = REG_TYPE_CONST; | ||||
regnum -= 0; | ||||
} // if | ||||
else if (regnum < 4096) | ||||
{ | ||||
regtype = REG_TYPE_CONST2; | ||||
regnum -= 2048; | ||||
} // if | ||||
else if (regnum < 6144) | ||||
{ | ||||
regtype = REG_TYPE_CONST3; | ||||
regnum -= 4096; | ||||
} // if | ||||
else if (regnum < 8192) | ||||
{ | ||||
regtype = REG_TYPE_CONST4; | ||||
regnum -= 6144; | ||||
} // if | ||||
else | ||||
{ | ||||
fail(ctx, "Invalid const register index"); | ||||
} // else | ||||
} // if | ||||
*rtype = regtype; | ||||
*rnum = regnum; | ||||
return 1; | ||||
} // parse_register_name | ||||
static void set_result_shift(Context *ctx, DestArgInfo *info, const int val) | ||||
{ | ||||
if (info->result_shift != 0) | ||||
fail(ctx, "Multiple result shift modifiers"); | ||||
info->result_shift = val; | ||||
} // set_result_shift | ||||
static inline int tokenbuf_overflow(Context *ctx) | ||||
{ | ||||
if ( ctx->tokenbufpos >= ((int) (STATICARRAYLEN(ctx->tokenbuf))) ) | ||||
{ | ||||
fail(ctx, "Too many tokens"); | ||||
return 1; | ||||
} // if | ||||
return 0; | ||||
} // tokenbuf_overflow | ||||
static int parse_destination_token(Context *ctx) | ||||
{ | ||||
DestArgInfo *info = &ctx->dest_arg; | ||||
memset(info, '\0', sizeof (DestArgInfo)); | ||||
// parse_instruction_token() sets ctx->token to the end of the instruction | ||||
// so we can see if there are destination modifiers on the instruction | ||||
// itself... | ||||
int invalid_modifier = 0; | ||||
while ((ctx->tokenlen > 0) && (!invalid_modifier)) | ||||
{ | ||||
if (check_token_segment(ctx, "_x2")) | ||||
set_result_shift(ctx, info, 0x1); | ||||
else if (check_token_segment(ctx, "_x4")) | ||||
set_result_shift(ctx, info, 0x2); | ||||
else if (check_token_segment(ctx, "_x8")) | ||||
set_result_shift(ctx, info, 0x3); | ||||
else if (check_token_segment(ctx, "_d8")) | ||||
set_result_shift(ctx, info, 0xD); | ||||
else if (check_token_segment(ctx, "_d4")) | ||||
set_result_shift(ctx, info, 0xE); | ||||
else if (check_token_segment(ctx, "_d2")) | ||||
set_result_shift(ctx, info, 0xF); | ||||
else if (check_token_segment(ctx, "_sat")) | ||||
info->result_mod |= MOD_SATURATE; | ||||
else if (check_token_segment(ctx, "_pp")) | ||||
info->result_mod |= MOD_PP; | ||||
else if (check_token_segment(ctx, "_centroid")) | ||||
info->result_mod |= MOD_CENTROID; | ||||
else | ||||
invalid_modifier = 1; | ||||
} // while | ||||
if (invalid_modifier) | ||||
fail(ctx, "Invalid destination modifier"); | ||||
// !!! FIXME: predicates. | ||||
if (nexttoken(ctx) == ((Token) '(')) | ||||
fail(ctx, "Predicates unsupported at this time"); // !!! FIXME: ... | ||||
pushback(ctx); // parse_register_name calls nexttoken(). | ||||
parse_register_name(ctx, &info->regtype, &info->regnum); | ||||
// parse_register_name() can't check this: dest regs might have modifiers. | ||||
if (ctx->tokenlen > 0) | ||||
fail(ctx, "invalid register name"); | ||||
// !!! FIXME: can dest registers do relative addressing? | ||||
int invalid_writemask = 0; | ||||
int implicit_writemask = 0; | ||||
if (nexttoken(ctx) != ((Token) '.')) | ||||
{ | ||||
implicit_writemask = 1; | ||||
info->writemask = 0xF; | ||||
info->writemask0 = info->writemask1 = info->writemask2 = info->writemask3 = 1; | ||||
pushback(ctx); // no explicit writemask; do full mask. | ||||
} // if | ||||
// !!! FIXME: Cg generates code with oDepth.z ... this is a bug, I think. | ||||
//else if (scalar_register(ctx->shader_type, info->regtype, info->regnum)) | ||||
else if ( (scalar_register(ctx->shader_type, info->regtype, info->regnum)) && (info->regtype != REG_TYPE_DEPTHOUT) ) | ||||
fail(ctx, "Writemask specified for scalar register"); | ||||
else if (nexttoken(ctx) != TOKEN_IDENTIFIER) | ||||
invalid_writemask = 1; | ||||
else | ||||
{ | ||||
char tokenbytes[5] = { '\0', '\0', '\0', '\0', '\0' }; | ||||
const unsigned int tokenlen = ctx->tokenlen; | ||||
memcpy(tokenbytes, ctx->token, ((tokenlen < 4) ? tokenlen : 4)); | ||||
char *ptr = tokenbytes; | ||||
if ((*ptr == 'r') || (*ptr == 'x')) { info->writemask0 = 1; ptr++; } | ||||
if ((*ptr == 'g') || (*ptr == 'y')) { info->writemask1 = 1; ptr++; } | ||||
if ((*ptr == 'b') || (*ptr == 'z')) { info->writemask2 = 1; ptr++; } | ||||
if ((*ptr == 'a') || (*ptr == 'w')) { info->writemask3 = 1; ptr++; } | ||||
if (*ptr != '\0') | ||||
invalid_writemask = 1; | ||||
info->writemask = ( ((info->writemask0 & 0x1) << 0) | | ||||
((info->writemask1 & 0x1) << 1) | | ||||
((info->writemask2 & 0x1) << 2) | | ||||
((info->writemask3 & 0x1) << 3) ); | ||||
} // else | ||||
if (invalid_writemask) | ||||
fail(ctx, "Invalid writemask"); | ||||
// !!! FIXME: Cg generates code with oDepth.z ... this is a bug, I think. | ||||
if (info->regtype == REG_TYPE_DEPTHOUT) | ||||
{ | ||||
if ( (!implicit_writemask) && ((info->writemask0 + info->writemask1 + | ||||
info->writemask2 + info->writemask3) > 1) ) | ||||
fail(ctx, "Writemask specified for scalar register"); | ||||
} // if | ||||
info->orig_writemask = info->writemask; | ||||
if (tokenbuf_overflow(ctx)) | ||||
return 1; | ||||
ctx->tokenbuf[ctx->tokenbufpos++] = | ||||
( ((((uint32) 1)) << 31) | | ||||
((((uint32) info->regnum) & 0x7ff) << 0) | | ||||
((((uint32) info->relative) & 0x1) << 13) | | ||||
((((uint32) info->result_mod) & 0xF) << 20) | | ||||
((((uint32) info->result_shift) & 0xF) << 24) | | ||||
((((uint32) info->writemask) & 0xF) << 16) | | ||||
((((uint32) info->regtype) & 0x7) << 28) | | ||||
((((uint32) info->regtype) & 0x18) << 8) ); | ||||
return 1; | ||||
} // parse_destination_token | ||||
static void set_source_mod(Context *ctx, const int negate, | ||||
const SourceMod norm, const SourceMod negated, | ||||
SourceMod *srcmod) | ||||
{ | ||||
if ( (*srcmod != SRCMOD_NONE) || (negate && (negated == SRCMOD_NONE)) ) | ||||
fail(ctx, "Incompatible source modifiers"); | ||||
else | ||||
*srcmod = ((negate) ? negated : norm); | ||||
} // set_source_mod | ||||
static int parse_source_token_maybe_relative(Context *ctx, const int relok) | ||||
{ | ||||
int retval = 1; | ||||
if (tokenbuf_overflow(ctx)) | ||||
return 0; | ||||
// mark this now, so optional relative addressing token is placed second. | ||||
uint32 *outtoken = &ctx->tokenbuf[ctx->tokenbufpos++]; | ||||
*outtoken = 0; | ||||
SourceMod srcmod = SRCMOD_NONE; | ||||
int negate = 0; | ||||
Token token = nexttoken(ctx); | ||||
if (token == ((Token) '!')) | ||||
srcmod = SRCMOD_NOT; | ||||
else if (token == ((Token) '-')) | ||||
negate = 1; | ||||
else if ( (token == TOKEN_INT_LITERAL) && (check_token(ctx, "1")) ) | ||||
{ | ||||
if (nexttoken(ctx) != ((Token) '-')) | ||||
fail(ctx, "Unexpected token"); | ||||
else | ||||
srcmod = SRCMOD_COMPLEMENT; | ||||
} // else | ||||
else | ||||
{ | ||||
pushback(ctx); | ||||
} // else | ||||
RegisterType regtype; | ||||
int regnum; | ||||
parse_register_name(ctx, ®type, ®num); | ||||
if (ctx->tokenlen == 0) | ||||
{ | ||||
if (negate) | ||||
set_source_mod(ctx, negate, SRCMOD_NONE, SRCMOD_NEGATE, &srcmod); | ||||
} // if | ||||
else | ||||
{ | ||||
assert(ctx->tokenlen > 0); | ||||
if (check_token_segment(ctx, "_bias")) | ||||
set_source_mod(ctx, negate, SRCMOD_BIAS, SRCMOD_BIASNEGATE, &srcmod); | ||||
else if (check_token_segment(ctx, "_bx2")) | ||||
set_source_mod(ctx, negate, SRCMOD_SIGN, SRCMOD_SIGNNEGATE, &srcmod); | ||||
else if (check_token_segment(ctx, "_x2")) | ||||
set_source_mod(ctx, negate, SRCMOD_X2, SRCMOD_X2NEGATE, &srcmod); | ||||
else if (check_token_segment(ctx, "_dz")) | ||||
set_source_mod(ctx, negate, SRCMOD_DZ, SRCMOD_NONE, &srcmod); | ||||
else if (check_token_segment(ctx, "_db")) | ||||
set_source_mod(ctx, negate, SRCMOD_DZ, SRCMOD_NONE, &srcmod); | ||||
else if (check_token_segment(ctx, "_dw")) | ||||
set_source_mod(ctx, negate, SRCMOD_DW, SRCMOD_NONE, &srcmod); | ||||
else if (check_token_segment(ctx, "_da")) | ||||
set_source_mod(ctx, negate, SRCMOD_DW, SRCMOD_NONE, &srcmod); | ||||
else if (check_token_segment(ctx, "_abs")) | ||||
set_source_mod(ctx, negate, SRCMOD_ABS, SRCMOD_ABSNEGATE, &srcmod); | ||||
else | ||||
fail(ctx, "Invalid source modifier"); | ||||
} // else | ||||
uint32 relative = 0; | ||||
if (nexttoken(ctx) != ((Token) '[')) | ||||
pushback(ctx); // not relative addressing? | ||||
else | ||||
{ | ||||
if (!relok) | ||||
fail(ctx, "Relative addressing not permitted here."); | ||||
else | ||||
retval++; | ||||
parse_source_token_maybe_relative(ctx, 0); | ||||
relative = 1; | ||||
if (nexttoken(ctx) != ((Token) '+')) | ||||
pushback(ctx); | ||||
else | ||||
{ | ||||
// !!! FIXME: maybe c3[a0.x + 5] is legal and becomes c[a0.x + 8] ? | ||||
if (regnum != 0) | ||||
fail(ctx, "Relative addressing with explicit register number."); | ||||
uint32 ui32 = 0; | ||||
if ( (nexttoken(ctx) != TOKEN_INT_LITERAL) || | ||||
(!ui32fromtoken(ctx, &ui32)) || | ||||
(ctx->tokenlen != 0) ) | ||||
{ | ||||
fail(ctx, "Invalid relative addressing offset"); | ||||
} // if | ||||
regnum += (int) ui32; | ||||
} // else | ||||
if (nexttoken(ctx) != ((Token) ']')) | ||||
fail(ctx, "Expected ']'"); | ||||
} // else | ||||
int invalid_swizzle = 0; | ||||
uint32 swizzle = 0; | ||||
if (nexttoken(ctx) != ((Token) '.')) | ||||
{ | ||||
swizzle = 0xE4; // 0xE4 == 11100100 ... 0 1 2 3. No swizzle. | ||||
pushback(ctx); // no explicit writemask; do full mask. | ||||
} // if | ||||
else if (scalar_register(ctx->shader_type, regtype, regnum)) | ||||
fail(ctx, "Swizzle specified for scalar register"); | ||||
else if (nexttoken(ctx) != TOKEN_IDENTIFIER) | ||||
invalid_swizzle = 1; | ||||
else | ||||
{ | ||||
char tokenbytes[5] = { '\0', '\0', '\0', '\0', '\0' }; | ||||
const unsigned int tokenlen = ctx->tokenlen; | ||||
memcpy(tokenbytes, ctx->token, ((tokenlen < 4) ? tokenlen : 4)); | ||||
// deal with shortened form (.x = .xxxx, etc). | ||||
if (tokenlen == 1) | ||||
tokenbytes[1] = tokenbytes[2] = tokenbytes[3] = tokenbytes[0]; | ||||
else if (tokenlen == 2) | ||||
tokenbytes[2] = tokenbytes[3] = tokenbytes[1]; | ||||
else if (tokenlen == 3) | ||||
tokenbytes[3] = tokenbytes[2]; | ||||
else if (tokenlen != 4) | ||||
invalid_swizzle = 1; | ||||
tokenbytes[4] = '\0'; | ||||
uint32 val = 0; | ||||
int i; | ||||
for (i = 0; i < 4; i++) | ||||
{ | ||||
const int component = (int) tokenbytes[i]; | ||||
switch (component) | ||||
{ | ||||
case 'r': case 'x': val = 0; break; | ||||
case 'g': case 'y': val = 1; break; | ||||
case 'b': case 'z': val = 2; break; | ||||
case 'a': case 'w': val = 3; break; | ||||
default: invalid_swizzle = 1; break; | ||||
} // switch | ||||
swizzle |= (val << (i * 2)); | ||||
} // for | ||||
} // else | ||||
if (invalid_swizzle) | ||||
fail(ctx, "Invalid swizzle"); | ||||
*outtoken = ( ((((uint32) 1)) << 31) | | ||||
((((uint32) regnum) & 0x7ff) << 0) | | ||||
((((uint32) relative) & 0x1) << 13) | | ||||
((((uint32) swizzle) & 0xFF) << 16) | | ||||
((((uint32) srcmod) & 0xF) << 24) | | ||||
((((uint32) regtype) & 0x7) << 28) | | ||||
((((uint32) regtype) & 0x18) << 8) ); | ||||
return retval; | ||||
} // parse_source_token_maybe_relative | ||||
static inline int parse_source_token(Context *ctx) | ||||
{ | ||||
return parse_source_token_maybe_relative(ctx, 1); | ||||
} // parse_source_token | ||||
static int parse_args_NULL(Context *ctx) | ||||
{ | ||||
return 1; | ||||
} // parse_args_NULL | ||||
static int parse_num(Context *ctx, const int floatok, uint32 *value) | ||||
{ | ||||
union { float f; int32 si32; uint32 ui32; } cvt; | ||||
int negative = 0; | ||||
Token token = nexttoken(ctx); | ||||
if (token == ((Token) '-')) | ||||
{ | ||||
negative = 1; | ||||
token = nexttoken(ctx); | ||||
} // if | ||||
if (token == TOKEN_INT_LITERAL) | ||||
{ | ||||
int d = 0; | ||||
sscanf(ctx->token, "%d", &d); | ||||
if (floatok) | ||||
cvt.f = (float) ((negative) ? -d : d); | ||||
else | ||||
cvt.si32 = (int32) ((negative) ? -d : d); | ||||
} // if | ||||
else if (token == TOKEN_FLOAT_LITERAL) | ||||
{ | ||||
if (!floatok) | ||||
{ | ||||
fail(ctx, "Expected whole number"); | ||||
*value = 0; | ||||
return 0; | ||||
} // if | ||||
sscanf(ctx->token, "%f", &cvt.f); | ||||
if (negative) | ||||
cvt.f = -cvt.f; | ||||
} // if | ||||
else | ||||
{ | ||||
fail(ctx, "Expected number"); | ||||
*value = 0; | ||||
return 0; | ||||
} // else | ||||
*value = cvt.ui32; | ||||
return 1; | ||||
} // parse_num | ||||
static int parse_args_DEFx(Context *ctx, const int isflt) | ||||
{ | ||||
parse_destination_token(ctx); | ||||
require_comma(ctx); | ||||
parse_num(ctx, isflt, &ctx->tokenbuf[ctx->tokenbufpos++]); | ||||
require_comma(ctx); | ||||
parse_num(ctx, isflt, &ctx->tokenbuf[ctx->tokenbufpos++]); | ||||
require_comma(ctx); | ||||
parse_num(ctx, isflt, &ctx->tokenbuf[ctx->tokenbufpos++]); | ||||
require_comma(ctx); | ||||
parse_num(ctx, isflt, &ctx->tokenbuf[ctx->tokenbufpos++]); | ||||
return 6; | ||||
} // parse_args_DEFx | ||||
static int parse_args_DEF(Context *ctx) | ||||
{ | ||||
return parse_args_DEFx(ctx, 1); | ||||
} // parse_args_DEF | ||||
static int parse_args_DEFI(Context *ctx) | ||||
{ | ||||
return parse_args_DEFx(ctx, 0); | ||||
} // parse_args_DEFI | ||||
static int parse_args_DEFB(Context *ctx) | ||||
{ | ||||
parse_destination_token(ctx); | ||||
require_comma(ctx); | ||||
// !!! FIXME: do a TOKEN_TRUE and TOKEN_FALSE? Is this case-sensitive? | ||||
const Token token = nexttoken(ctx); | ||||
int bad = 0; | ||||
if (token != TOKEN_IDENTIFIER) | ||||
bad = 1; | ||||
else if (check_token_segment(ctx, "true")) | ||||
ctx->tokenbuf[ctx->tokenbufpos++] = 1; | ||||
else if (check_token_segment(ctx, "false")) | ||||
ctx->tokenbuf[ctx->tokenbufpos++] = 0; | ||||
else | ||||
bad = 1; | ||||
if (ctx->tokenlen != 0) | ||||
bad = 1; | ||||
if (bad) | ||||
fail(ctx, "Expected 'true' or 'false'"); | ||||
return 3; | ||||
} // parse_args_DEFB | ||||
static int parse_dcl_usage(Context *ctx, uint32 *val, int *issampler) | ||||
{ | ||||
size_t i; | ||||
static const char *samplerusagestrs[] = { "_2d", "_cube", "_volume" }; | ||||
static const char *usagestrs[] = { | ||||
"_position", "_blendweight", "_blendindices", "_normal", "_psize", | ||||
"_texcoord", "_tangent", "_binormal", "_tessfactor", "_positiont", | ||||
"_color", "_fog", "_depth", "_sample" | ||||
}; | ||||
for (i = 0; i < STATICARRAYLEN(usagestrs); i++) | ||||
{ | ||||
if (check_token_segment(ctx, usagestrs[i])) | ||||
{ | ||||
*issampler = 0; | ||||
*val = i; | ||||
return 1; | ||||
} // if | ||||
} // for | ||||
for (i = 0; i < STATICARRAYLEN(samplerusagestrs); i++) | ||||
{ | ||||
if (check_token_segment(ctx, samplerusagestrs[i])) | ||||
{ | ||||
*issampler = 1; | ||||
*val = i + 2; | ||||
return 1; | ||||
} // if | ||||
} // for | ||||
*issampler = 0; | ||||
*val = 0; | ||||
return 0; | ||||
} // parse_dcl_usage | ||||
static int parse_args_DCL(Context *ctx) | ||||
{ | ||||
int issampler = 0; | ||||
uint32 usage = 0; | ||||
uint32 index = 0; | ||||
ctx->tokenbufpos++; // save a spot for the usage/index token. | ||||
ctx->tokenbuf[0] = 0; | ||||
// parse_instruction_token() sets ctx->token to the end of the instruction | ||||
// so we can see if there are destination modifiers on the instruction | ||||
// itself... | ||||
if (parse_dcl_usage(ctx, &usage, &issampler)) | ||||
{ | ||||
if ((ctx->tokenlen > 0) && (*ctx->token != '_')) | ||||
{ | ||||
if (!ui32fromtoken(ctx, &index)) | ||||
fail(ctx, "Expected usage index"); | ||||
} // if | ||||
} // if | ||||
parse_destination_token(ctx); | ||||
const int samplerreg = (ctx->dest_arg.regtype == REG_TYPE_SAMPLER); | ||||
if (issampler != samplerreg) | ||||
fail(ctx, "Invalid usage"); | ||||
else if (samplerreg) | ||||
ctx->tokenbuf[0] = (usage << 27) | 0x80000000; | ||||
else if (shader_is_pixel(ctx)) // all other pixel shader types are zero'd. | ||||
ctx->tokenbuf[0] = 0x80000000; | ||||
else | ||||
ctx->tokenbuf[0] = usage | (index << 16) | 0x80000000; | ||||
return 3; | ||||
} // parse_args_DCL | ||||
static int parse_args_D(Context *ctx) | ||||
{ | ||||
int retval = 1; | ||||
retval += parse_destination_token(ctx); | ||||
return retval; | ||||
} // parse_args_D | ||||
static int parse_args_S(Context *ctx) | ||||
{ | ||||
int retval = 1; | ||||
retval += parse_source_token(ctx); | ||||
return retval; | ||||
} // parse_args_S | ||||
static int parse_args_SS(Context *ctx) | ||||
{ | ||||
int retval = 1; | ||||
retval += parse_source_token(ctx); | ||||
require_comma(ctx); | ||||
retval += parse_source_token(ctx); | ||||
return retval; | ||||
} // parse_args_SS | ||||
static int parse_args_DS(Context *ctx) | ||||
{ | ||||
int retval = 1; | ||||
retval += parse_destination_token(ctx); | ||||
require_comma(ctx); | ||||
retval += parse_source_token(ctx); | ||||
return retval; | ||||
} // parse_args_DS | ||||
static int parse_args_DSS(Context *ctx) | ||||
{ | ||||
int retval = 1; | ||||
retval += parse_destination_token(ctx); | ||||
require_comma(ctx); | ||||
retval += parse_source_token(ctx); | ||||
require_comma(ctx); | ||||
retval += parse_source_token(ctx); | ||||
return retval; | ||||
} // parse_args_DSS | ||||
static int parse_args_DSSS(Context *ctx) | ||||
{ | ||||
int retval = 1; | ||||
retval += parse_destination_token(ctx); | ||||
require_comma(ctx); | ||||
retval += parse_source_token(ctx); | ||||
require_comma(ctx); | ||||
retval += parse_source_token(ctx); | ||||
require_comma(ctx); | ||||
retval += parse_source_token(ctx); | ||||
return retval; | ||||
} // parse_args_DSSS | ||||
static int parse_args_DSSSS(Context *ctx) | ||||
{ | ||||
int retval = 1; | ||||
retval += parse_destination_token(ctx); | ||||
require_comma(ctx); | ||||
retval += parse_source_token(ctx); | ||||
require_comma(ctx); | ||||
retval += parse_source_token(ctx); | ||||
require_comma(ctx); | ||||
retval += parse_source_token(ctx); | ||||
require_comma(ctx); | ||||
retval += parse_source_token(ctx); | ||||
return retval; | ||||
} // parse_args_DSSSS | ||||
static int parse_args_SINCOS(Context *ctx) | ||||
{ | ||||
// this opcode needs extra registers for sm2 and lower. | ||||
if (!shader_version_atleast(ctx, 3, 0)) | ||||
return parse_args_DSSS(ctx); | ||||
return parse_args_DS(ctx); | ||||
} // parse_args_SINCOS | ||||
static int parse_args_TEXCRD(Context *ctx) | ||||
{ | ||||
// added extra register in ps_1_4. | ||||
if (shader_version_atleast(ctx, 1, 4)) | ||||
return parse_args_DS(ctx); | ||||
return parse_args_D(ctx); | ||||
} // parse_args_TEXCRD | ||||
static int parse_args_TEXLD(Context *ctx) | ||||
{ | ||||
// different registers in px_1_3, ps_1_4, and ps_2_0! | ||||
if (shader_version_atleast(ctx, 2, 0)) | ||||
return parse_args_DSS(ctx); | ||||
else if (shader_version_atleast(ctx, 1, 4)) | ||||
return parse_args_DS(ctx); | ||||
return parse_args_D(ctx); | ||||
} // parse_args_TEXLD | ||||
// one args function for each possible sequence of opcode arguments. | ||||
typedef int (*args_function)(Context *ctx); | ||||
// Lookup table for instruction opcodes... | ||||
typedef struct | ||||
{ | ||||
const char *opcode_string; | ||||
args_function parse_args; | ||||
} Instruction; | ||||
static const Instruction instructions[] = | ||||
{ | ||||
#define INSTRUCTION_STATE(op, opstr, s, a, t) { opstr, parse_args_##a }, | ||||
#define INSTRUCTION(op, opstr, slots, a, t) { opstr, parse_args_##a }, | ||||
#define MOJOSHADER_DO_INSTRUCTION_TABLE 1 | ||||
#include "mojoshader_internal.h" | ||||
#undef MOJOSHADER_DO_INSTRUCTION_TABLE | ||||
#undef INSTRUCTION | ||||
#undef INSTRUCTION_STATE | ||||
}; | ||||
static int parse_condition(Context *ctx, uint32 *controls) | ||||
{ | ||||
static const char *comps[] = { "_gt", "_eq", "_ge", "_lt", "_ne", "_le" }; | ||||
size_t i; | ||||
if (ctx->tokenlen >= 3) | ||||
{ | ||||
for (i = 0; i < STATICARRAYLEN(comps); i++) | ||||
{ | ||||
if (check_token_segment(ctx, comps[i])) | ||||
{ | ||||
*controls = (uint32) (i + 1); | ||||
return 1; | ||||
} // if | ||||
} // for | ||||
} // if | ||||
return 0; | ||||
} // parse_condition | ||||
static int parse_instruction_token(Context *ctx, Token token) | ||||
{ | ||||
int coissue = 0; | ||||
int predicated = 0; | ||||
if (token == ((Token) '+')) | ||||
{ | ||||
coissue = 1; | ||||
token = nexttoken(ctx); | ||||
} // if | ||||
if (token != TOKEN_IDENTIFIER) | ||||
{ | ||||
fail(ctx, "Expected instruction"); | ||||
return 0; | ||||
} // if | ||||
uint32 controls = 0; | ||||
uint32 opcode = OPCODE_TEXLD; | ||||
const char *origtoken = ctx->token; | ||||
const unsigned int origtokenlen = ctx->tokenlen; | ||||
// "TEX" is only meaningful in ps_1_1. | ||||
if ((!shader_version_atleast(ctx, 1, 4)) && (check_token_segment(ctx, "TEX"))) | ||||
controls = 0; | ||||
// This might need to be TEXLD instead of TEXLDP. | ||||
else if (check_token_segment(ctx, "TEXLDP")) | ||||
controls = CONTROL_TEXLDP; | ||||
// This might need to be TEXLD instead of TEXLDB. | ||||
else if (check_token_segment(ctx, "TEXLDB")) | ||||
controls = CONTROL_TEXLDB; | ||||
else // find the instruction. | ||||
{ | ||||
size_t i; | ||||
for (i = 0; i < STATICARRAYLEN(instructions); i++) | ||||
{ | ||||
const char *opcode_string = instructions[i].opcode_string; | ||||
if (opcode_string == NULL) | ||||
continue; // skip this. | ||||
else if (!check_token_segment(ctx, opcode_string)) | ||||
continue; // not us. | ||||
else if ((ctx->tokenlen > 0) && (*ctx->token != '_')) | ||||
{ | ||||
ctx->token = origtoken; | ||||
ctx->tokenlen = origtokenlen; | ||||
continue; // not the match: TEXLD when we wanted TEXLDL, etc. | ||||
} // if | ||||
break; // found it! | ||||
} // for | ||||
opcode = (uint32) i; | ||||
// This might need to be IFC instead of IF. | ||||
if (opcode == OPCODE_IF) | ||||
{ | ||||
if (parse_condition(ctx, &controls)) | ||||
opcode = OPCODE_IFC; | ||||
} // if | ||||
// This might need to be BREAKC instead of BREAK. | ||||
else if (opcode == OPCODE_BREAK) | ||||
{ | ||||
if (parse_condition(ctx, &controls)) | ||||
opcode = OPCODE_BREAKC; | ||||
} // else if | ||||
// SETP has a conditional code, always. | ||||
else if (opcode == OPCODE_SETP) | ||||
{ | ||||
if (!parse_condition(ctx, &controls)) | ||||
fail(ctx, "SETP requires a condition"); | ||||
} // else if | ||||
} // else | ||||
if ( (opcode == STATICARRAYLEN(instructions)) || | ||||
((ctx->tokenlen > 0) && (ctx->token[0] != '_')) ) | ||||
{ | ||||
char opstr[32]; | ||||
const int len = Min(sizeof (opstr) - 1, origtokenlen); | ||||
memcpy(opstr, origtoken, len); | ||||
opstr[len] = '\0'; | ||||
failf(ctx, "Unknown instruction '%s'", opstr); | ||||
return 0; | ||||
} // if | ||||
const Instruction *instruction = &instructions[opcode]; | ||||
// !!! FIXME: predicated instructions | ||||
ctx->tokenbufpos = 0; | ||||
const int tokcount = instruction->parse_args(ctx); | ||||
// insttoks bits are reserved and should be zero if < SM2. | ||||
const uint32 insttoks = shader_version_atleast(ctx, 2, 0) ? tokcount-1 : 0; | ||||
// write out the instruction token. | ||||
output_token(ctx, ((opcode & 0xFFFF) << 0) | | ||||
((controls & 0xFF) << 16) | | ||||
((insttoks & 0xF) << 24) | | ||||
((coissue) ? 0x40000000 : 0x00000000) | | ||||
((predicated) ? 0x10000000 : 0x00000000) ); | ||||
// write out the argument tokens. | ||||
int i; | ||||
for (i = 0; i < (tokcount-1); i++) | ||||
output_token(ctx, ctx->tokenbuf[i]); | ||||
return 1; | ||||
} // parse_instruction_token | ||||
static void parse_version_token(Context *ctx) | ||||
{ | ||||
int bad = 0; | ||||
int dot_form = 0; | ||||
uint32 shader_type = 0; | ||||
if (nexttoken(ctx) != TOKEN_IDENTIFIER) | ||||
bad = 1; | ||||
else if (check_token_segment(ctx, "vs")) | ||||
{ | ||||
ctx->shader_type = MOJOSHADER_TYPE_VERTEX; | ||||
shader_type = 0xFFFE; | ||||
} // if | ||||
else if (check_token_segment(ctx, "ps")) | ||||
{ | ||||
ctx->shader_type = MOJOSHADER_TYPE_PIXEL; | ||||
shader_type = 0xFFFF; | ||||
} // if | ||||
else | ||||
{ | ||||
// !!! FIXME: geometry shaders? | ||||
bad = 1; | ||||
} // else | ||||
dot_form = ((!bad) && (ctx->tokenlen == 0)); // it's in xs.x.x form? | ||||
uint32 major = 0; | ||||
uint32 minor = 0; | ||||
if (dot_form) | ||||
{ | ||||
Token t = TOKEN_UNKNOWN; | ||||
if (!bad) | ||||
{ | ||||
t = nexttoken(ctx); | ||||
// stupid lexer sees "vs.2.0" and makes the ".2" into a float. | ||||
if (t == ((Token) '.')) | ||||
t = nexttoken(ctx); | ||||
else | ||||
{ | ||||
if ((t != TOKEN_FLOAT_LITERAL) || (ctx->token[0] != '.')) | ||||
bad = 1; | ||||
else | ||||
{ | ||||
ctx->tokenval = t = TOKEN_INT_LITERAL; | ||||
ctx->token++; | ||||
ctx->tokenlen--; | ||||
} // else | ||||
} // else | ||||
} // if | ||||
if (!bad) | ||||
{ | ||||
if (t != TOKEN_INT_LITERAL) | ||||
bad = 1; | ||||
else if (!ui32fromtoken(ctx, &major)) | ||||
bad = 1; | ||||
} // if | ||||
if (!bad) | ||||
{ | ||||
t = nexttoken(ctx); | ||||
// stupid lexer sees "vs.2.0" and makes the ".2" into a float. | ||||
if (t == ((Token) '.')) | ||||
t = nexttoken(ctx); | ||||
else | ||||
{ | ||||
if ((t != TOKEN_FLOAT_LITERAL) || (ctx->token[0] != '.')) | ||||
bad = 1; | ||||
else | ||||
{ | ||||
ctx->tokenval = t = TOKEN_INT_LITERAL; | ||||
ctx->token++; | ||||
ctx->tokenlen--; | ||||
} // else | ||||
} // else | ||||
} // if | ||||
if (!bad) | ||||
{ | ||||
if ((t == TOKEN_INT_LITERAL) && (ui32fromtoken(ctx, &minor))) | ||||
; // good to go. | ||||
else if ((t == TOKEN_IDENTIFIER) && (check_token_segment(ctx, "x"))) | ||||
minor = 1; | ||||
else if ((t == TOKEN_IDENTIFIER) && (check_token_segment(ctx, "sw"))) | ||||
minor = 255; | ||||
else | ||||
bad = 1; | ||||
} // if | ||||
} // if | ||||
else | ||||
{ | ||||
if (!check_token_segment(ctx, "_")) | ||||
bad = 1; | ||||
else if (!ui32fromtoken(ctx, &major)) | ||||
bad = 1; | ||||
else if (!check_token_segment(ctx, "_")) | ||||
bad = 1; | ||||
else if (check_token_segment(ctx, "x")) | ||||
minor = 1; | ||||
else if (check_token_segment(ctx, "sw")) | ||||
minor = 255; | ||||
else if (!ui32fromtoken(ctx, &minor)) | ||||
bad = 1; | ||||
} // else | ||||
if ((!bad) && (ctx->tokenlen != 0)) | ||||
bad = 1; | ||||
if (bad) | ||||
fail(ctx, "Expected valid version string"); | ||||
ctx->major_ver = major; | ||||
ctx->minor_ver = minor; | ||||
ctx->version_token = (shader_type << 16) | (major << 8) | (minor << 0); | ||||
output_token(ctx, ctx->version_token); | ||||
} // parse_version_token | ||||
static void parse_phase_token(Context *ctx) | ||||
{ | ||||
output_token(ctx, 0x0000FFFD); // phase token always 0x0000FFFD. | ||||
} // parse_phase_token | ||||
static void parse_end_token(Context *ctx) | ||||
{ | ||||
// We don't emit the end token bits here, since it's valid for a shader | ||||
// to not specify an "end" string at all; it's implicit, in that case. | ||||
// Instead, we make sure if we see "end" that it's the last thing we see. | ||||
if (nexttoken(ctx) != TOKEN_EOI) | ||||
fail(ctx, "Content after END"); | ||||
} // parse_end_token | ||||
static void parse_token(Context *ctx, const Token token) | ||||
{ | ||||
if (token != TOKEN_IDENTIFIER) | ||||
parse_instruction_token(ctx, token); // might be a coissue '+', etc. | ||||
else | ||||
{ | ||||
if (check_token(ctx, "end")) | ||||
parse_end_token(ctx); | ||||
else if (check_token(ctx, "phase")) | ||||
parse_phase_token(ctx); | ||||
else | ||||
parse_instruction_token(ctx, token); | ||||
} // if | ||||
} // parse_token | ||||
static void destroy_context(Context *ctx) | ||||
{ | ||||
if (ctx != NULL) | ||||
{ | ||||
MOJOSHADER_free f = ((ctx->free != NULL) ? ctx->free : MOJOSHADER_internal_free); | ||||
void *d = ctx->malloc_data; | ||||
preprocessor_end(ctx->preprocessor); | ||||
errorlist_destroy(ctx->errors); | ||||
buffer_destroy(ctx->ctab); | ||||
buffer_destroy(ctx->token_to_source); | ||||
buffer_destroy(ctx->output); | ||||
f(ctx, d); | ||||
} // if | ||||
} // destroy_context | ||||
static Context *build_context(const char *filename, | ||||
const char *source, unsigned int sourcelen, | ||||
const MOJOSHADER_preprocessorDefine *defines, | ||||
unsigned int define_count, | ||||
MOJOSHADER_includeOpen include_open, | ||||
MOJOSHADER_includeClose include_close, | ||||
MOJOSHADER_malloc m, MOJOSHADER_free f, void *d) | ||||
{ | ||||
if (!m) m = MOJOSHADER_internal_malloc; | ||||
if (!f) f = MOJOSHADER_internal_free; | ||||
if (!include_open) include_open = MOJOSHADER_internal_include_open; | ||||
if (!include_close) include_close = MOJOSHADER_internal_include_close; | ||||
Context *ctx = (Context *) m(sizeof (Context), d); | ||||
if (ctx == NULL) | ||||
return NULL; | ||||
memset(ctx, '\0', sizeof (Context)); | ||||
ctx->malloc = m; | ||||
ctx->free = f; | ||||
ctx->malloc_data = d; | ||||
ctx->current_position = MOJOSHADER_POSITION_BEFORE; | ||||
const size_t outblk = sizeof (uint32) * 4 * 64; // 64 4-token instrs. | ||||
ctx->output = buffer_create(outblk, MallocBridge, FreeBridge, ctx); | ||||
if (ctx->output == NULL) | ||||
goto build_context_failed; | ||||
const size_t mapblk = sizeof (SourcePos) * 4 * 64; // 64 * 4-tokens. | ||||
ctx->token_to_source = buffer_create(mapblk, MallocBridge, FreeBridge, ctx); | ||||
if (ctx->token_to_source == NULL) | ||||
goto build_context_failed; | ||||
ctx->errors = errorlist_create(MallocBridge, FreeBridge, ctx); | ||||
if (ctx->errors == NULL) | ||||
goto build_context_failed; | ||||
ctx->preprocessor = preprocessor_start(filename, source, sourcelen, | ||||
include_open, include_close, | ||||
defines, define_count, 1, | ||||
MallocBridge, FreeBridge, ctx); | ||||
if (ctx->preprocessor == NULL) | ||||
goto build_context_failed; | ||||
return ctx; | ||||
build_context_failed: // ctx is allocated and zeroed before this is called. | ||||
destroy_context(ctx); | ||||
return NULL; | ||||
} // build_context | ||||
static const MOJOSHADER_parseData *build_failed_assembly(Context *ctx) | ||||
{ | ||||
assert(isfail(ctx)); | ||||
if (ctx->out_of_memory) | ||||
return &MOJOSHADER_out_of_mem_data; | ||||
MOJOSHADER_parseData *retval = NULL; | ||||
retval = (MOJOSHADER_parseData*) Malloc(ctx, sizeof(MOJOSHADER_parseData)); | ||||
if (retval == NULL) | ||||
return &MOJOSHADER_out_of_mem_data; | ||||
memset(retval, '\0', sizeof (MOJOSHADER_parseData)); | ||||
retval->malloc = (ctx->malloc == MOJOSHADER_internal_malloc) ? NULL : ctx->malloc; | ||||
retval->free = (ctx->free == MOJOSHADER_internal_free) ? NULL : ctx->free; | ||||
retval->malloc_data = ctx->malloc_data; | ||||
retval->error_count = errorlist_count(ctx->errors); | ||||
retval->errors = errorlist_flatten(ctx->errors); | ||||
if (ctx->out_of_memory) | ||||
{ | ||||
Free(ctx, retval->errors); | ||||
Free(ctx, retval); | ||||
return &MOJOSHADER_out_of_mem_data; | ||||
} // if | ||||
return retval; | ||||
} // build_failed_assembly | ||||
static uint32 add_ctab_bytes(Context *ctx, const uint8 *bytes, const size_t len) | ||||
{ | ||||
if (isfail(ctx)) | ||||
return 0; | ||||
const size_t extra = CTAB_SIZE + sizeof (uint32); | ||||
const ssize_t pos = buffer_find(ctx->ctab, extra, bytes, len); | ||||
if (pos >= 0) // blob is already in here. | ||||
return ((uint32) pos) - sizeof (uint32); | ||||
// add it to the byte pile... | ||||
const uint32 retval = ((uint32) buffer_size(ctx->ctab)) - sizeof (uint32); | ||||
buffer_append(ctx->ctab, bytes, len); | ||||
return retval; | ||||
} // add_ctab_bytes | ||||
static inline uint32 add_ctab_string(Context *ctx, const char *str) | ||||
{ | ||||
return add_ctab_bytes(ctx, (const uint8 *) str, strlen(str) + 1); | ||||
} // add_ctab_string | ||||
static uint32 add_ctab_typeinfo(Context *ctx, const MOJOSHADER_symbolTypeInfo *info); | ||||
static uint32 add_ctab_members(Context *ctx, const MOJOSHADER_symbolTypeInfo *info) | ||||
{ | ||||
unsigned int i; | ||||
const size_t len = info->member_count * CMEMBERINFO_SIZE; | ||||
uint8 *bytes = (uint8 *) Malloc(ctx, len); | ||||
if (bytes == NULL) | ||||
return 0; | ||||
union { uint8 *ui8; uint16 *ui16; uint32 *ui32; } ptr; | ||||
ptr.ui8 = bytes; | ||||
for (i = 0; i < info->member_count; i++) | ||||
{ | ||||
const MOJOSHADER_symbolStructMember *member = &info->members[i]; | ||||
*(ptr.ui32++) = SWAP32(add_ctab_string(ctx, member->name)); | ||||
*(ptr.ui32++) = SWAP32(add_ctab_typeinfo(ctx, &member->info)); | ||||
} // for | ||||
const uint32 retval = add_ctab_bytes(ctx, bytes, len); | ||||
Free(ctx, bytes); | ||||
return retval; | ||||
} // add_ctab_members | ||||
static uint32 add_ctab_typeinfo(Context *ctx, const MOJOSHADER_symbolTypeInfo *info) | ||||
{ | ||||
uint8 bytes[CTYPEINFO_SIZE]; | ||||
union { uint8 *ui8; uint16 *ui16; uint32 *ui32; } ptr; | ||||
ptr.ui8 = bytes; | ||||
*(ptr.ui16++) = SWAP16((uint16) info->parameter_class); | ||||
*(ptr.ui16++) = SWAP16((uint16) info->parameter_type); | ||||
*(ptr.ui16++) = SWAP16((uint16) info->rows); | ||||
*(ptr.ui16++) = SWAP16((uint16) info->columns); | ||||
*(ptr.ui16++) = SWAP16((uint16) info->elements); | ||||
*(ptr.ui16++) = SWAP16((uint16) info->member_count); | ||||
*(ptr.ui32++) = SWAP32(add_ctab_members(ctx, info)); | ||||
return add_ctab_bytes(ctx, bytes, sizeof (bytes)); | ||||
} // add_ctab_typeinfo | ||||
static uint32 add_ctab_info(Context *ctx, const MOJOSHADER_symbol *symbols, | ||||
const unsigned int symbol_count) | ||||
{ | ||||
unsigned int i; | ||||
const size_t len = symbol_count * CINFO_SIZE; | ||||
uint8 *bytes = (uint8 *) Malloc(ctx, len); | ||||
if (bytes == NULL) | ||||
return 0; | ||||
union { uint8 *ui8; uint16 *ui16; uint32 *ui32; } ptr; | ||||
ptr.ui8 = bytes; | ||||
for (i = 0; i < symbol_count; i++) | ||||
{ | ||||
const MOJOSHADER_symbol *sym = &symbols[i]; | ||||
*(ptr.ui32++) = SWAP32(add_ctab_string(ctx, sym->name)); | ||||
*(ptr.ui16++) = SWAP16((uint16) sym->register_set); | ||||
*(ptr.ui16++) = SWAP16((uint16) sym->register_index); | ||||
*(ptr.ui16++) = SWAP16((uint16) sym->register_count); | ||||
*(ptr.ui16++) = SWAP16(0); // reserved | ||||
*(ptr.ui32++) = SWAP32(add_ctab_typeinfo(ctx, &sym->info)); | ||||
*(ptr.ui32++) = SWAP32(0); // !!! FIXME: default value. | ||||
} // for | ||||
const uint32 retval = add_ctab_bytes(ctx, bytes, len); | ||||
Free(ctx, bytes); | ||||
return retval; | ||||
} // add_ctab_info | ||||
static void output_ctab(Context *ctx, const MOJOSHADER_symbol *symbols, | ||||
unsigned int symbol_count, const char *creator) | ||||
{ | ||||
const size_t tablelen = CTAB_SIZE + sizeof (uint32); | ||||
ctx->ctab = buffer_create(256, MallocBridge, FreeBridge, ctx); | ||||
if (ctx->ctab == NULL) | ||||
return; // out of memory. | ||||
uint32 *table = (uint32 *) buffer_reserve(ctx->ctab, tablelen); | ||||
if (table == NULL) | ||||
{ | ||||
buffer_destroy(ctx->ctab); | ||||
ctx->ctab = NULL; | ||||
return; // out of memory. | ||||
} // if | ||||
*(table++) = SWAP32(CTAB_ID); | ||||
*(table++) = SWAP32(CTAB_SIZE); | ||||
*(table++) = SWAP32(add_ctab_string(ctx, creator)); | ||||
*(table++) = SWAP32(ctx->version_token); | ||||
*(table++) = SWAP32(((uint32) symbol_count)); | ||||
*(table++) = SWAP32(add_ctab_info(ctx, symbols, symbol_count)); | ||||
*(table++) = SWAP32(0); // build flags. | ||||
*(table++) = SWAP32(add_ctab_string(ctx, "")); // !!! FIXME: target? | ||||
const size_t ctablen = buffer_size(ctx->ctab); | ||||
uint8 *buf = (uint8 *) buffer_flatten(ctx->ctab); | ||||
if (buf != NULL) | ||||
{ | ||||
output_comment_bytes(ctx, buf, ctablen); | ||||
Free(ctx, buf); | ||||
} // if | ||||
buffer_destroy(ctx->ctab); | ||||
ctx->ctab = NULL; | ||||
} // output_ctab | ||||
static void output_comments(Context *ctx, const char **comments, | ||||
unsigned int comment_count, | ||||
const MOJOSHADER_symbol *symbols, | ||||
unsigned int symbol_count) | ||||
{ | ||||
if (isfail(ctx)) | ||||
return; | ||||
// make error messages sane if CTAB fails, etc. | ||||
const char *prev_fname = ctx->current_file; | ||||
const int prev_position = ctx->current_position; | ||||
ctx->current_file = NULL; | ||||
ctx->current_position = MOJOSHADER_POSITION_BEFORE; | ||||
const char *creator = "MojoShader revision " MOJOSHADER_CHANGESET; | ||||
if (symbol_count > 0) | ||||
output_ctab(ctx, symbols, symbol_count, creator); | ||||
else | ||||
output_comment_string(ctx, creator); | ||||
unsigned int i; | ||||
for (i = 0; i < comment_count; i++) | ||||
output_comment_string(ctx, comments[i]); | ||||
ctx->current_file = prev_fname; | ||||
ctx->current_position = prev_position; | ||||
} // output_comments | ||||
static const MOJOSHADER_parseData *build_final_assembly(Context *ctx) | ||||
{ | ||||
if (isfail(ctx)) | ||||
return build_failed_assembly(ctx); | ||||
// get the final bytecode! | ||||
const unsigned int output_len = (unsigned int) buffer_size(ctx->output); | ||||
unsigned char *bytecode = (unsigned char *) buffer_flatten(ctx->output); | ||||
buffer_destroy(ctx->output); | ||||
ctx->output = NULL; | ||||
if (bytecode == NULL) | ||||
return build_failed_assembly(ctx); | ||||
// This validates the shader; there are lots of things that are | ||||
// invalid, but will successfully parse in the assembler, | ||||
// generating bad bytecode; this will catch them without us | ||||
// having to duplicate most of the validation here. | ||||
// It also saves us the trouble of duplicating all the other work, | ||||
// like setting up the uniforms list, etc. | ||||
MOJOSHADER_parseData *retval = (MOJOSHADER_parseData *) | ||||
MOJOSHADER_parse(MOJOSHADER_PROFILE_BYTECODE, NULL, | ||||
bytecode, output_len, NULL, 0, NULL, 0, | ||||
ctx->malloc, ctx->free, ctx->malloc_data); | ||||
Free(ctx, bytecode); | ||||
SourcePos *token_to_src = NULL; | ||||
if (retval->error_count > 0) | ||||
token_to_src = (SourcePos *) buffer_flatten(ctx->token_to_source); | ||||
buffer_destroy(ctx->token_to_source); | ||||
ctx->token_to_source = NULL; | ||||
if (retval->error_count > 0) | ||||
{ | ||||
if (token_to_src == NULL) | ||||
{ | ||||
assert(ctx->out_of_memory); | ||||
MOJOSHADER_freeParseData(retval); | ||||
return build_failed_assembly(ctx); | ||||
} // if | ||||
// on error, map the bytecode back to a line number. | ||||
int i; | ||||
for (i = 0; i < retval->error_count; i++) | ||||
{ | ||||
MOJOSHADER_error *error = &retval->errors[i]; | ||||
if (error->error_position >= 0) | ||||
{ | ||||
assert(retval != &MOJOSHADER_out_of_mem_data); | ||||
assert((error->error_position % sizeof (uint32)) == 0); | ||||
const size_t pos = error->error_position / sizeof(uint32); | ||||
if (pos >= output_len) | ||||
error->error_position = -1; // oh well. | ||||
else | ||||
{ | ||||
const SourcePos *srcpos = &token_to_src[pos]; | ||||
Free(ctx, (void *) error->filename); | ||||
char *fname = NULL; | ||||
if (srcpos->filename != NULL) | ||||
fname = StrDup(ctx, srcpos->filename); | ||||
error->error_position = srcpos->line; | ||||
error->filename = fname; // may be NULL, that's okay. | ||||
} // else | ||||
} // if | ||||
} // for | ||||
Free(ctx, token_to_src); | ||||
} // if | ||||
return retval; | ||||
} // build_final_assembly | ||||
// API entry point... | ||||
const MOJOSHADER_parseData *MOJOSHADER_assemble(const char *filename, | ||||
const char *source, unsigned int sourcelen, | ||||
const char **comments, unsigned int comment_count, | ||||
const MOJOSHADER_symbol *symbols, | ||||
unsigned int symbol_count, | ||||
const MOJOSHADER_preprocessorDefine *defines, | ||||
unsigned int define_count, | ||||
MOJOSHADER_includeOpen include_open, | ||||
MOJOSHADER_includeClose include_close, | ||||
MOJOSHADER_malloc m, MOJOSHADER_free f, void *d) | ||||
{ | ||||
const MOJOSHADER_parseData *retval = NULL; | ||||
Context *ctx = NULL; | ||||
if ( ((m == NULL) && (f != NULL)) || ((m != NULL) && (f == NULL)) ) | ||||
return &MOJOSHADER_out_of_mem_data; // supply both or neither. | ||||
ctx = build_context(filename, source, sourcelen, defines, define_count, | ||||
include_open, include_close, m, f, d); | ||||
if (ctx == NULL) | ||||
return &MOJOSHADER_out_of_mem_data; | ||||
// Version token always comes first. | ||||
parse_version_token(ctx); | ||||
output_comments(ctx, comments, comment_count, symbols, symbol_count); | ||||
// parse out the rest of the tokens after the version token... | ||||
Token token; | ||||
while ((token = nexttoken(ctx)) != TOKEN_EOI) | ||||
parse_token(ctx, token); | ||||
ctx->current_file = NULL; | ||||
ctx->current_position = MOJOSHADER_POSITION_AFTER; | ||||
output_token(ctx, 0x0000FFFF); // end token always 0x0000FFFF. | ||||
retval = build_final_assembly(ctx); | ||||
destroy_context(ctx); | ||||
return retval; | ||||
} // MOJOSHADER_assemble | ||||
// end of mojoshader_assembler.c ... | ||||