mojoshader_effects.c
author Ryan C. Gordon <icculus@icculus.org>
Tue, 31 May 2011 03:23:39 -0400
changeset 1036 8a3597b44ff7
parent 1021 cad933999680
child 1037 b102a563d9cb
permissions -rw-r--r--
First shot at preshader interpreter. Completely untested!

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

#include <math.h>

void run_preshader(const MOJOSHADER_preshader *preshader, float *regs)
{
    // this is fairly straightforward, as there aren't any branching
    //  opcodes in the preshader instruction set (at the moment, at least).
    const int scalarstart = (int) MOJOSHADER_PRESHADEROP_SCALAR_OPS;

    double *temps = (double *) alloca(sizeof (double) * preshader->temp_count);
    memset(temps, '\0', sizeof (double) * preshader->temp_count);

    double dst[4];
    double src[3][4];
    const double *src0 = src[0];
    const double *src1 = src[1];
    const double *src2 = src[2];

    MOJOSHADER_preshaderInstruction *inst = preshader->instructions;
    int instit;

    for (instit = 0; instit < preshader->instruction_count; instit++, inst++)
    {
        const MOJOSHADER_preshaderOperand *operand = inst->operands;
        const int isscalar = (inst->opcode >= scalarstart);
        const int elems = inst->element_count;
        const int elemsbytes = sizeof (double) * elems;

        // load up our operands...
        int opiter, elemiter;
        for (opiter = 1; opiter < inst->operand_count; opiter++, operand++)
        {
            const unsigned int index = operand->index;
            switch (operand->type)
            {
                case MOJOSHADER_PRESHADEROPERAND_LITERAL:
                {
                    const double *lit = &preshader->literals[index];
                    if (!isscalar)
                        memcpy(src[opiter], lit, elemsbytes);
                    else
                    {
                        const double val = *lit;
                        for (elemiter = 0; elemiter < elems; elemiter++)
                            src[opiter][elemiter] = val;
                    } // else
                    break;
                } // case

                case MOJOSHADER_PRESHADEROPERAND_INPUT:
                case MOJOSHADER_PRESHADEROPERAND_OUTPUT:
                    if (isscalar)
                        src[opiter][0] = regs[index];
                    else
                    {
                        int cpy;
                        for (cpy = 0; cpy < elems; cpy++)
                            src[opiter][cpy] = regs[index+cpy];
                    } // else
                    break;

                case MOJOSHADER_PRESHADEROPERAND_TEMP:
                    if (isscalar)
                        src[opiter][0] = temps[index];
                    else
                        memcpy(src[opiter], temps + index, elemsbytes);
                    break;

                default:
                    assert(0 && "unexpected preshader operand type.");
                    break;
            } // switch
        } // for

        // run the actual instruction, store result to dst.
        int i;
        switch (inst->opcode)
        {
            #define OPCODE_CASE(op, val) \
                case MOJOSHADER_PRESHADEROP_##op: \
                    for (i = 0; i < elems; i++) { dst[i] = val; } \
                    break;

            //OPCODE_CASE(NOP, 0.0)  // not a real instruction.
            OPCODE_CASE(MOV, src0[i])
            OPCODE_CASE(NEG, -src0[i])
            OPCODE_CASE(RCP, 1.0 / src0[i])
            OPCODE_CASE(FRC, src0[i] - floor(src0[i]))
            OPCODE_CASE(EXP, exp(src0[i]))
            OPCODE_CASE(LOG, log(src0[i]))
            OPCODE_CASE(RSQ, 1.0 / sqrt(src0[i]))
            OPCODE_CASE(SIN, sin(src0[i]))
            OPCODE_CASE(COS, cos(src0[i]))
            OPCODE_CASE(ASIN, asin(src0[i]))
            OPCODE_CASE(ACOS, acos(src0[i]))
            OPCODE_CASE(ATAN, atan(src0[i]))
            OPCODE_CASE(MIN, (src0[i] < src1[i]) ? src0[i] : src1[i])
            OPCODE_CASE(MAX, (src0[i] > src1[i]) ? src0[i] : src1[i])
            OPCODE_CASE(LT, (src0[i] < src1[i]) ? 1.0 : 0.0)
            OPCODE_CASE(GE, (src0[i] >= src1[i]) ? 1.0 : 0.0)
            OPCODE_CASE(ADD, src0[i] + src1[i])
            OPCODE_CASE(MUL,  src0[i] * src1[i])
            OPCODE_CASE(ATAN2, atan2(src0[i], src1[i]))
            OPCODE_CASE(DIV, src0[i] / src1[i])
            OPCODE_CASE(CMP, (src0[i] >= 0.0) ? src1[i] : src2[i])
            //OPCODE_CASE(NOISE, ???)  // !!! FIXME: don't know what this does
            //OPCODE_CASE(MOVC, ???)  // !!! FIXME: don't know what this does
            #undef OPCODE_CASE

            case MOJOSHADER_PRESHADEROP_DOT:
            {
                double final = 0.0;
                for (i = 0; i < elems; i++)
                    final += src0[i] * src1[i];
                for (i = 0; i < elems; i++)
                    dst[i] = final;  // !!! FIXME: is this right?
            } // case

            #define OPCODE_CASE_SCALAR(op, val) \
                case MOJOSHADER_PRESHADEROP_##op##_SCALAR: { \
                    const double final = val; \
                    for (i = 0; i < elems; i++) { dst[i] = final; } \
                    break; \
                }

            OPCODE_CASE_SCALAR(MIN, (src0[0] < src1[0]) ? src0[0] : src1[0])
            OPCODE_CASE_SCALAR(MAX, (src0[0] > src1[0]) ? src0[0] : src1[0])
            OPCODE_CASE_SCALAR(LT, (src0[0] < src1[0]) ? 1.0 : 0.0)
            OPCODE_CASE_SCALAR(GE, (src0[0] >= src1[0]) ? 1.0 : 0.0)
            OPCODE_CASE_SCALAR(ADD, src0[0] + src1[0])
            OPCODE_CASE_SCALAR(MUL, src0[0] * src1[0])
            OPCODE_CASE_SCALAR(ATAN2, atan2(src0[0], src1[0]))
            OPCODE_CASE_SCALAR(DIV, src0[0] / src1[0])
            //OPCODE_CASE_SCALAR(DOT)  // !!! FIXME: isn't this just a MUL?
            //OPCODE_CASE_SCALAR(NOISE, ???)  // !!! FIXME: ?
            #undef OPCODE_CASE_SCALAR

            default:
                assert(0 && "Unhandled preshader opcode!");
                break;
        } // switch

        // Figure out where dst wants to be stored.
        operand = inst->operands;
        if (operand->type == MOJOSHADER_PRESHADEROPERAND_TEMP)
            memcpy(temps + operand->index, dst, elemsbytes);
        else
        {
            assert(operand->type == MOJOSHADER_PRESHADEROPERAND_OUTPUT);
            for (i = 0; i < elems; i++)
                regs[operand->index + i] = (float) dst[i];
        } // else
    } // for
} // run_preshader


static MOJOSHADER_effect MOJOSHADER_out_of_mem_effect = {
    1, &MOJOSHADER_out_of_mem_error, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

static uint32 readui32(const uint8 **_ptr, uint32 *_len)
{
    uint32 retval = 0;
    if (*_len < sizeof (retval))
        *_len = 0;
    else
    {
        const uint32 *ptr = (const uint32 *) *_ptr;
        retval = SWAP32(*ptr);
        *_ptr += sizeof (retval);
        *_len -= sizeof (retval);
    } // else
    return retval;
} // readui32

// !!! FIXME: this is sort of a big, ugly function.
const MOJOSHADER_effect *MOJOSHADER_parseEffect(const char *profile,
                                                const unsigned char *buf,
                                                const unsigned int _len,
                                                const MOJOSHADER_swizzle *swiz,
                                                const unsigned int swizcount,
                                                MOJOSHADER_malloc m,
                                                MOJOSHADER_free f,
                                                void *d)
{
    if ( ((m == NULL) && (f != NULL)) || ((m != NULL) && (f == NULL)) )
        return &MOJOSHADER_out_of_mem_effect;  // supply both or neither.

    if (m == NULL) m = MOJOSHADER_internal_malloc;
    if (f == NULL) f = MOJOSHADER_internal_free;

    MOJOSHADER_effect *retval = m(sizeof (MOJOSHADER_effect), d);
    if (retval == NULL)
        return &MOJOSHADER_out_of_mem_effect;  // supply both or neither.
    memset(retval, '\0', sizeof (*retval));

    retval->malloc = m;
    retval->free = f;
    retval->malloc_data = d;

    const uint8 *ptr = (const uint8 *) buf;
    uint32 len = (uint32) _len;
    size_t siz = 0;
    int i, j, k;

    if (len < 8)
        goto parseEffect_unexpectedEOF;

    if (readui32(&ptr, &len) != 0xFEFF0901) // !!! FIXME: is this always magic?
        goto parseEffect_notAnEffectsFile;
    else
    {
        const uint32 offset = readui32(&ptr, &len);
        if (offset > len)
            goto parseEffect_unexpectedEOF;
        ptr += offset;
        len -= offset;
    } // else

    // params...

    if (len < 16)
        goto parseEffect_unexpectedEOF;

    const uint32 numparams = readui32(&ptr, &len);
    const uint32 numtechniques = readui32(&ptr, &len);

    readui32(&ptr, &len); // !!! FIXME: there are 8 unknown bytes here.
    readui32(&ptr, &len);

    for (i = 0; i < numparams; i++)
    {
        if (len < 16)
            goto parseEffect_unexpectedEOF;

        /*const uint32 startoffset = */ readui32(&ptr, &len);
        /*const uint32 endoffset = */ readui32(&ptr, &len);
        readui32(&ptr, &len);  // !!! FIXME: don't know what this is.
        const uint32 numannos = readui32(&ptr, &len);
        for (j = 0; j < numannos; j++)
        {
            if (len < 8)
                goto parseEffect_unexpectedEOF;
            // !!! FIXME: parse annotations.
            readui32(&ptr, &len);
            readui32(&ptr, &len);
        } // for
    } // for

    uint32 numshaders = 0;  // we'll calculate this later.

    // techniques...

    if (numtechniques > 0)
    {
        siz = sizeof (MOJOSHADER_effectTechnique) * numtechniques;
        retval->techniques = (MOJOSHADER_effectTechnique *) m(siz, d);
        if (retval->techniques == NULL)
            goto parseEffect_outOfMemory;
        memset(retval->techniques, '\0', siz);

        retval->technique_count = numtechniques;

        for (i = 0; i < numtechniques; i++)
        {
            if (len < 12)
                goto parseEffect_unexpectedEOF;
            
            MOJOSHADER_effectTechnique *technique = &retval->techniques[i];

            // !!! FIXME: is this always 12?
            const uint32 nameoffset = readui32(&ptr, &len) + 12;
            readui32(&ptr, &len);  // !!! FIXME: don't know what this field does.
            const uint32 numpasses = readui32(&ptr, &len);

            if (nameoffset >= _len)
                goto parseEffect_unexpectedEOF;

            if (numpasses > 0)
            {
                // !!! FIXME: verify this doesn't go past EOF looking for a null.
                siz = strlen(((const char *) buf) + nameoffset) + 1;
                technique->name = (char *) m(siz, d);
                if (technique->name == NULL)
                    goto parseEffect_outOfMemory;
                strcpy((char *) technique->name, ((const char *) buf) + nameoffset);

                technique->pass_count = numpasses;

                siz = sizeof (MOJOSHADER_effectPass) * numpasses;
                technique->passes = (MOJOSHADER_effectPass *) m(siz, d);
                if (technique->passes == NULL)
                    goto parseEffect_outOfMemory;
                memset(technique->passes, '\0', siz);

                for (j = 0; j < numpasses; j++)
                {
                    if (len < 12)
                        goto parseEffect_unexpectedEOF;

                    MOJOSHADER_effectPass *pass = &technique->passes[j];

                    // !!! FIXME: is this always 12?
                    const uint32 passnameoffset = readui32(&ptr, &len) + 12;
                    readui32(&ptr, &len);  // !!! FIXME: don't know what this field does.
                    const uint32 numstates = readui32(&ptr, &len);

                    if (passnameoffset >= _len)
                        goto parseEffect_unexpectedEOF;

                    // !!! FIXME: verify this doesn't go past EOF looking for a null.
                    siz = strlen(((const char *) buf) + passnameoffset) + 1;
                    pass->name = (char *) m(siz, d);
                    if (pass->name == NULL)
                        goto parseEffect_outOfMemory;
                    strcpy((char *) pass->name, ((const char *) buf) + passnameoffset);

                    if (numstates > 0)
                    {
                        pass->state_count = numstates;

                        siz = sizeof (MOJOSHADER_effectState) * numstates;
                        pass->states = (MOJOSHADER_effectState *) m(siz, d);
                        if (pass->states == NULL)
                            goto parseEffect_outOfMemory;
                        memset(pass->states, '\0', siz);

                        for (k = 0; k < numstates; k++)
                        {
                            if (len < 16)
                                goto parseEffect_unexpectedEOF;

                            MOJOSHADER_effectState *state = &pass->states[k];
                            const uint32 type = readui32(&ptr, &len);
                            readui32(&ptr, &len);  // !!! FIXME: don't know what this field does.
                            /*const uint32 offsetend = */ readui32(&ptr, &len);
                            /*const uint32 offsetstart = */ readui32(&ptr, &len);
                            state->type = type;

                            if ((type == 0x92) || (type == 0x93))
                                numshaders++;
                        } // for
                    } // if
                } // for
            } // if
        } // for
    } // if

    // textures...

    if (len < 8)
        goto parseEffect_unexpectedEOF;

    const int numtextures = readui32(&ptr, &len);
    const int numobjects = readui32(&ptr, &len);  // !!! FIXME: "objects" for lack of a better word.

    if (numtextures > 0)
    {
        siz = sizeof (MOJOSHADER_effectTexture) * numtextures;
        retval->textures = m(siz, d);
        if (retval->textures == NULL)
            goto parseEffect_outOfMemory;
        memset(retval->textures, '\0', siz);

        for (i = 0; i < numtextures; i++)
        {
            if (len < 8)
                goto parseEffect_unexpectedEOF;

            MOJOSHADER_effectTexture *texture = &retval->textures[i];
            const uint32 texparam = readui32(&ptr, &len);
            const uint32 texsize = readui32(&ptr, &len);
            // apparently texsize will pad out to 32 bits.
            const uint32 readsize = (((texsize + 3) / 4) * 4);
            if (len < readsize)
                goto parseEffect_unexpectedEOF;

            texture->param = texparam;
            char *str = m(texsize + 1, d);
            if (str == NULL)
                goto parseEffect_outOfMemory;
            memcpy(str, ptr, texsize);
            str[texsize] = '\0';
            texture->name = str;

            ptr += readsize;
            len -= readsize;
        } // for
    } // if

    // shaders...

    if (numshaders > 0)
    {
        siz = sizeof (MOJOSHADER_effectShader) * numshaders;
        retval->shaders = (MOJOSHADER_effectShader *) m(siz, d);
        if (retval->shaders == NULL)
            goto parseEffect_outOfMemory;
        memset(retval->shaders, '\0', siz);

        retval->shader_count = numshaders;

        // !!! FIXME: I wonder if we should pull these from offsets and not
        // !!! FIXME:  count on them all being in a line like this.
        for (i = 0; i < numshaders; i++)
        {
            if (len < 24)
                goto parseEffect_unexpectedEOF;

            MOJOSHADER_effectShader *shader = &retval->shaders[i];
            const uint32 technique = readui32(&ptr, &len);
            const uint32 pass = readui32(&ptr, &len);
            readui32(&ptr, &len);  // !!! FIXME: don't know what this does.
            readui32(&ptr, &len);  // !!! FIXME: don't know what this does (vertex/pixel/geometry?)
            readui32(&ptr, &len);  // !!! FIXME: don't know what this does.
            const uint32 shadersize = readui32(&ptr, &len);

            if (len < shadersize)
                goto parseEffect_unexpectedEOF;

            shader->technique = technique;
            shader->pass = pass;
            shader->shader = MOJOSHADER_parse(profile, ptr, shadersize,
                                              swiz, swizcount, m, f, d);

            // !!! FIXME: check for errors.

            ptr += shadersize;
            len -= shadersize;
        } // for
    } // if

    // !!! FIXME: we parse this, but don't expose the data, yet.
    // mappings ...
    assert(numshaders <= numobjects);
    const uint32 nummappings = numobjects - numshaders;
    if (nummappings > 0)
    {
        for (i = 0; i < nummappings; i++)
        {
            if (len < 24)
                goto parseEffect_unexpectedEOF;

            /*const uint32 magic = */ readui32(&ptr, &len);
            /*const uint32 index = */ readui32(&ptr, &len);
            readui32(&ptr, &len);  // !!! FIXME: what is this field?
            readui32(&ptr, &len);  // !!! FIXME: what is this field?
            /*const uint32 type = */ readui32(&ptr, &len);
            const uint32 mapsize = readui32(&ptr, &len);
            if (mapsize > 0)
            {
                const uint32 readsize = (((mapsize + 3) / 4) * 4);
                if (len < readsize)
                    goto parseEffect_unexpectedEOF;
            } // if
        } // for
    } // if

    retval->profile = (char *) m(strlen(profile) + 1, d);
    if (retval->profile == NULL)
        goto parseEffect_outOfMemory;
    strcpy((char *) retval->profile, profile);

    return retval;


// !!! FIXME: do something with this.
parseEffect_notAnEffectsFile:
parseEffect_unexpectedEOF:
parseEffect_outOfMemory:
    MOJOSHADER_freeEffect(retval);
    return &MOJOSHADER_out_of_mem_effect;
} // MOJOSHADER_parseEffect


void MOJOSHADER_freeEffect(const MOJOSHADER_effect *_effect)
{
    MOJOSHADER_effect *effect = (MOJOSHADER_effect *) _effect;
    if ((effect == NULL) || (effect == &MOJOSHADER_out_of_mem_effect))
        return;  // no-op.

    MOJOSHADER_free f = effect->free;
    void *d = effect->malloc_data;
    int i, j;

    for (i = 0; i < effect->error_count; i++)
    {
        f((void *) effect->errors[i].error, d);
        f((void *) effect->errors[i].filename, d);
    } // for
    f((void *) effect->errors, d);

    f((void *) effect->profile, d);

    for (i = 0; i < effect->technique_count; i++)
    {
        MOJOSHADER_effectTechnique *technique = &effect->techniques[i];
        f((void *) technique->name, d);
        for (j = 0; j < technique->pass_count; j++)
        {
            f((void *) technique->passes[j].name, d);
            f(technique->passes[j].states, d);
        } // for
        f(technique->passes, d);
    } // for

    f(effect->techniques, d);

    for (i = 0; i < effect->texture_count; i++)
        f((void *) effect->textures[i].name, d);
    f(effect->textures, d);

    for (i = 0; i < effect->shader_count; i++)
        MOJOSHADER_freeParseData(effect->shaders[i].shader);
    f(effect->shaders, d);

    f(effect, d);
} // MOJOSHADER_freeEffect

// end of mojoshader_effects.c ...