mojoshader_opengl.c
author Ryan C. Gordon <icculus@icculus.org>
Mon, 02 Jun 2008 00:14:02 -0400
branchtrunk
changeset 347 f8d9d0ae6ba8
parent 340 04865e4df2f9
child 349 78d4cf1ecf0c
permissions -rw-r--r--
Bunch more work (ARB1 profile, OpenGL glue, and general fixes). ARB1 OpenGL glue should be good to go now, and all the generalization of the code is taken care of (including some long standing FIXMEs). Still need a little fragment program work to be done with ARB1, though.

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

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <assert.h>

#ifdef _MSC_VER
#define WIN32_LEAN_AND_MEAN 1
#include <windows.h>  // GL headers need this for WINGDIAPI definition.
#endif

#include "mojoshader.h"
#define GL_GLEXT_LEGACY 1
#include "GL/gl.h"
#include "GL/glext.h"

// Get basic wankery out of the way here...

typedef unsigned int uint;  // this is a printf() helper. don't use for code.

#ifdef _MSC_VER
#define snprintf _snprintf
typedef unsigned __int8 uint8;
typedef unsigned __int32 uint32;
typedef unsigned __int32 int32;
// Warning Level 4 considered harmful.  :)
#pragma warning(disable: 4100)  // "unreferenced formal parameter"
#pragma warning(disable: 4389)  // "signed/unsigned mismatch"
#else
#include <stdint.h>
typedef uint8_t uint8;
typedef uint32_t uint32;
typedef int32_t int32;
#endif

#define STATICARRAYLEN(x) ( (sizeof ((x))) / (sizeof ((x)[0])) )

#ifndef SUPPORT_PROFILE_GLSL
#define SUPPORT_PROFILE_GLSL 1
#endif

#ifndef SUPPORT_PROFILE_ARB1
#define SUPPORT_PROFILE_ARB1 1
#endif

struct MOJOSHADER_glShader
{
    const MOJOSHADER_parseData *parseData;
    GLuint handle;
    uint32 refcount;
};

typedef struct
{
    MOJOSHADER_shaderType shader_type;
    const MOJOSHADER_uniform *uniform;
    GLuint location;
} UniformMap;

typedef struct
{
    MOJOSHADER_shaderType shader_type;
    const MOJOSHADER_sampler *sampler;
    GLuint location;
} SamplerMap;

typedef struct
{
    const MOJOSHADER_attribute *attribute;
    GLuint location;
} AttributeMap;

struct MOJOSHADER_glProgram
{
    MOJOSHADER_glShader *vertex;
    MOJOSHADER_glShader *fragment;
    GLuint handle;
    uint32 constant_count;  // !!! FIXME: misnamed.
    GLfloat *constants;  // !!! FIXME: misnamed.
    uint32 uniform_count;
    UniformMap *uniforms;
    uint32 sampler_count;
    SamplerMap *samplers;
    uint32 attribute_count;
    AttributeMap *attributes;
    uint32 refcount;
};

#ifndef WINGDIAPI
#define WINGDIAPI
#endif

// Entry points in base OpenGL that lack function pointer prototypes...
typedef WINGDIAPI void (APIENTRYP PFNGLGETINTEGERVPROC) (GLenum pname, GLint *params);
typedef WINGDIAPI const GLubyte * (APIENTRYP PFNGLGETSTRINGPROC) (GLenum name);
typedef WINGDIAPI GLenum (APIENTRYP PFNGLGETERRORPROC) (void);

struct MOJOSHADER_glContext
{
    // Allocators...
    MOJOSHADER_malloc malloc_fn;
    MOJOSHADER_free free_fn;
    void *malloc_data;

    // The constant register files...
    // Man, it kills me how much memory this takes...
    GLfloat vs_reg_file_f[8192 * 4];
    GLint vs_reg_file_i[2047 * 4];
    GLint vs_reg_file_b[2047];
    GLfloat ps_reg_file_f[8192 * 4];
    GLint ps_reg_file_i[2047 * 4];
    GLint ps_reg_file_b[2047];
    GLuint sampler_reg_file[16];

    // GL stuff...
    int opengl_major;
    int opengl_minor;
    MOJOSHADER_glProgram *bound_program;
    char profile[16];

    // Extensions...
    int have_base_opengl;
    int have_GL_ARB_vertex_program;
    int have_GL_ARB_fragment_program;
    int have_GL_ARB_shader_objects;
    int have_GL_ARB_vertex_shader;
    int have_GL_ARB_fragment_shader;
    int have_GL_ARB_shading_language_100;
    int have_GL_NV_half_float;

    // Entry points...
    PFNGLGETSTRINGPROC glGetString;
    PFNGLGETERRORPROC glGetError;
    PFNGLGETINTEGERVPROC glGetIntegerv;
    PFNGLDELETEOBJECTARBPROC glDeleteObject;
    PFNGLATTACHOBJECTARBPROC glAttachObject;
    PFNGLCOMPILESHADERARBPROC glCompileShader;
    PFNGLCREATEPROGRAMOBJECTARBPROC glCreateProgramObject;
    PFNGLCREATESHADEROBJECTARBPROC glCreateShaderObject;
    PFNGLDISABLEVERTEXATTRIBARRAYARBPROC glDisableVertexAttribArray;
    PFNGLENABLEVERTEXATTRIBARRAYARBPROC glEnableVertexAttribArray;
    PFNGLGETATTRIBLOCATIONARBPROC glGetAttribLocation;
    PFNGLGETINFOLOGARBPROC glGetInfoLog;
    PFNGLGETOBJECTPARAMETERIVARBPROC glGetObjectParameteriv;
    PFNGLGETUNIFORMLOCATIONARBPROC glGetUniformLocation;
    PFNGLLINKPROGRAMARBPROC glLinkProgram;
    PFNGLSHADERSOURCEARBPROC glShaderSource;
    PFNGLUNIFORM1IARBPROC glUniform1i;
    PFNGLUNIFORM4FVARBPROC glUniform4fv;
    PFNGLUNIFORM4IVARBPROC glUniform4iv;
    PFNGLUSEPROGRAMOBJECTARBPROC glUseProgramObject;
    PFNGLVERTEXATTRIBPOINTERARBPROC glVertexAttribPointer;
    PFNGLGETPROGRAMIVARBPROC glGetProgramivARB;
    PFNGLGETPROGRAMSTRINGARBPROC glGetProgramStringARB;
    PFNGLPROGRAMENVPARAMETER4FVARBPROC glProgramEnvParameter4fvARB;
    PFNGLDELETEPROGRAMSARBPROC glDeleteProgramsARB;
    PFNGLGENPROGRAMSARBPROC glGenProgramsARB;
    PFNGLBINDPROGRAMARBPROC glBindProgramARB;
    PFNGLPROGRAMSTRINGARBPROC glProgramStringARB;

    // interface for profile-specific things.
    int (*profileMaxUniforms)(MOJOSHADER_shaderType shader_type);
    int (*profileCompileShader)(const MOJOSHADER_parseData *pd, GLuint *s);
    void (*profileDeleteShader)(const GLuint shader);
    void (*profileDeleteProgram)(const GLuint program);
    GLint (*profileGetAttribLocation)(MOJOSHADER_glProgram *program, int idx);
    GLint (*profileGetUniformLocation)(MOJOSHADER_glProgram *, MOJOSHADER_glShader *, int);
    GLint (*profileGetSamplerLocation)(MOJOSHADER_glProgram *, MOJOSHADER_glShader *, int);
    GLuint (*profileLinkProgram)(MOJOSHADER_glShader *, MOJOSHADER_glShader *);
    void (*profileUseProgramObject)(MOJOSHADER_glProgram *program);
    void (*profileUniform4fv)(const MOJOSHADER_parseData *, GLint, GLsizei, GLfloat *);
    void (*profileUniform4iv)(const MOJOSHADER_parseData *, GLint, GLsizei, GLint *);
    void (*profileUniform1i)(const MOJOSHADER_parseData *, GLint, GLint);
    void (*profileSetSampler)(GLint loc, GLuint sampler);
};

// predeclare some profile implementation stuff...
static int impl_GLSL_MaxUniforms(MOJOSHADER_shaderType shader_type);
static int impl_GLSL_CompileShader(const MOJOSHADER_parseData *pd, GLuint *s);
static void impl_GLSL_DeleteShader(const GLuint shader);
static void impl_GLSL_DeleteProgram(const GLuint program);
static GLint impl_GLSL_GetAttribLocation(MOJOSHADER_glProgram *, int);
static GLint impl_GLSL_GetUniformLocation(MOJOSHADER_glProgram *, MOJOSHADER_glShader *, int);
static GLint impl_GLSL_GetSamplerLocation(MOJOSHADER_glProgram *, MOJOSHADER_glShader *, int);
static GLuint impl_GLSL_LinkProgram(MOJOSHADER_glShader *, MOJOSHADER_glShader *);
static void impl_GLSL_UseProgramObject(MOJOSHADER_glProgram *program);
static void impl_GLSL_Uniform4fv(const MOJOSHADER_parseData *pd, GLint loc, GLsizei siz, GLfloat *v);
static void impl_GLSL_Uniform4iv(const MOJOSHADER_parseData *pd, GLint loc, GLsizei siz, GLint *v);
static void impl_GLSL_Uniform1i(const MOJOSHADER_parseData *pd, GLint loc, GLint v);
static void impl_GLSL_SetSampler(GLint loc, GLuint sampler);

static int impl_ARB1_MaxUniforms(MOJOSHADER_shaderType shader_type);
static int impl_ARB1_CompileShader(const MOJOSHADER_parseData *pd, GLuint *s);
static void impl_ARB1_DeleteShader(const GLuint shader);
static void impl_ARB1_DeleteProgram(const GLuint program);
static GLint impl_ARB1_GetAttribLocation(MOJOSHADER_glProgram *, int);
static GLint impl_ARB1_GetUniformLocation(MOJOSHADER_glProgram *, MOJOSHADER_glShader *, int);
static GLint impl_ARB1_GetSamplerLocation(MOJOSHADER_glProgram *, MOJOSHADER_glShader *, int);
static GLuint impl_ARB1_LinkProgram(MOJOSHADER_glShader *, MOJOSHADER_glShader *);
static void impl_ARB1_UseProgramObject(MOJOSHADER_glProgram *program);
static void impl_ARB1_Uniform4fv(const MOJOSHADER_parseData *pd, GLint loc, GLsizei siz, GLfloat *v);
static void impl_ARB1_Uniform4iv(const MOJOSHADER_parseData *pd, GLint loc, GLsizei siz, GLint *v);
static void impl_ARB1_Uniform1i(const MOJOSHADER_parseData *pd, GLint loc, GLint v);
static void impl_ARB1_SetSampler(GLint loc, GLuint sampler);


static MOJOSHADER_glContext *ctx = NULL;

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


// #define this to force app to supply an allocator, so there's no reference
//  to the C runtime's malloc() and free()...
#if MOJOSHADER_FORCE_ALLOCATOR
#define internal_malloc NULL
#define internal_free NULL
#else
static void *internal_malloc(int bytes, void *d) { return malloc(bytes); }
static void internal_free(void *ptr, void *d) { free(ptr); }
#endif

static inline void *Malloc(const size_t len)
{
    void *retval = ctx->malloc_fn((int) len, ctx->malloc_data);
    if (retval == NULL)
        set_error("out of memory");
    return retval;
} // Malloc

static inline void Free(void *ptr)
{
    if (ptr != NULL)
        ctx->free_fn(ptr, ctx->malloc_data);
} // Free


const char *MOJOSHADER_glGetError(void)
{
    return error_buffer;
} // MOJOSHADER_glGetError


static void *loadsym(void *(*lookup)(const char *fn), const char *fn, int *ext)
{
    void *retval = NULL;
    if (lookup != NULL)
    {
        retval = lookup(fn);
        if (retval == NULL)
        {
            char arbfn[64];
            snprintf(arbfn, sizeof (arbfn), "%sARB", fn);
            retval = lookup(arbfn);
        } // if
    } // if

    if (retval == NULL)
        *ext = 0;

    return retval;
} // loadsym

static void lookup_entry_points(void *(*lookup)(const char *fnname))
{
    #define DO_LOOKUP(ext, typ, fn) ctx->fn = (typ) loadsym(lookup, #fn, &ctx->have_##ext)
    DO_LOOKUP(base_opengl, PFNGLGETSTRINGPROC, glGetString);
    DO_LOOKUP(base_opengl, PFNGLGETERRORPROC, glGetError);
    DO_LOOKUP(base_opengl, PFNGLGETINTEGERVPROC, glGetIntegerv);
    DO_LOOKUP(GL_ARB_shader_objects, PFNGLDELETEOBJECTARBPROC, glDeleteObject);
    DO_LOOKUP(GL_ARB_shader_objects, PFNGLATTACHOBJECTARBPROC, glAttachObject);
    DO_LOOKUP(GL_ARB_shader_objects, PFNGLCOMPILESHADERARBPROC, glCompileShader);
    DO_LOOKUP(GL_ARB_shader_objects, PFNGLCREATEPROGRAMOBJECTARBPROC, glCreateProgramObject);
    DO_LOOKUP(GL_ARB_shader_objects, PFNGLCREATESHADEROBJECTARBPROC, glCreateShaderObject);
    DO_LOOKUP(GL_ARB_shader_objects, PFNGLGETINFOLOGARBPROC, glGetInfoLog);
    DO_LOOKUP(GL_ARB_shader_objects, PFNGLGETOBJECTPARAMETERIVARBPROC, glGetObjectParameteriv);
    DO_LOOKUP(GL_ARB_shader_objects, PFNGLGETUNIFORMLOCATIONARBPROC, glGetUniformLocation);
    DO_LOOKUP(GL_ARB_shader_objects, PFNGLLINKPROGRAMARBPROC, glLinkProgram);
    DO_LOOKUP(GL_ARB_shader_objects, PFNGLSHADERSOURCEARBPROC, glShaderSource);
    DO_LOOKUP(GL_ARB_shader_objects, PFNGLUNIFORM1IARBPROC, glUniform1i);
    DO_LOOKUP(GL_ARB_shader_objects, PFNGLUNIFORM4FVARBPROC, glUniform4fv);
    DO_LOOKUP(GL_ARB_shader_objects, PFNGLUNIFORM4IVARBPROC, glUniform4iv);
    DO_LOOKUP(GL_ARB_shader_objects, PFNGLUSEPROGRAMOBJECTARBPROC, glUseProgramObject);
    DO_LOOKUP(GL_ARB_vertex_shader, PFNGLDISABLEVERTEXATTRIBARRAYARBPROC, glDisableVertexAttribArray);
    DO_LOOKUP(GL_ARB_vertex_shader, PFNGLENABLEVERTEXATTRIBARRAYARBPROC, glEnableVertexAttribArray);
    DO_LOOKUP(GL_ARB_vertex_shader, PFNGLGETATTRIBLOCATIONARBPROC, glGetAttribLocation);
    DO_LOOKUP(GL_ARB_vertex_shader, PFNGLVERTEXATTRIBPOINTERARBPROC, glVertexAttribPointer);
    DO_LOOKUP(GL_ARB_vertex_program, PFNGLUSEPROGRAMOBJECTARBPROC, glUseProgramObject);
    DO_LOOKUP(GL_ARB_vertex_program, PFNGLVERTEXATTRIBPOINTERARBPROC, glVertexAttribPointer);
    DO_LOOKUP(GL_ARB_vertex_program, PFNGLGETPROGRAMIVARBPROC, glGetProgramivARB);
    DO_LOOKUP(GL_ARB_vertex_program, PFNGLGETPROGRAMSTRINGARBPROC, glGetProgramStringARB);
    DO_LOOKUP(GL_ARB_vertex_program, PFNGLPROGRAMENVPARAMETER4FVARBPROC, glProgramEnvParameter4fvARB);
    DO_LOOKUP(GL_ARB_vertex_program, PFNGLDELETEPROGRAMSARBPROC, glDeleteProgramsARB);
    DO_LOOKUP(GL_ARB_vertex_program, PFNGLGENPROGRAMSARBPROC, glGenProgramsARB);
    DO_LOOKUP(GL_ARB_vertex_program, PFNGLBINDPROGRAMARBPROC, glBindProgramARB);
    DO_LOOKUP(GL_ARB_vertex_program, PFNGLPROGRAMSTRINGARBPROC, glProgramStringARB);

    #undef DO_LOOKUP
} // lookup_entry_points


static int verify_extension(const char *ext, int have, const char *extlist,
                            int major, int minor)
{
    if (have == 0)
        return 0;  // don't bother checking, we're missing an entry point.

    else if (!ctx->have_base_opengl)
        return 0;  // don't bother checking, we're missing basic functionality.

    // See if it's in the spec for this GL implementation's version.
    if (major >= 0)
    {
        if ( ((ctx->opengl_major << 16) | (ctx->opengl_minor & 0xFFFF)) >=
             ((major << 16) | (minor & 0xFFFF)) )
            return 1;
    } // if

    // Not available in the GL version, check the extension list.
    const char *ptr = strstr(extlist, ext);
    if (ptr == NULL)
        return 0;

    const char endchar = ptr[strlen(ext)];
    if ((endchar == '\0') || (endchar == ' '))
        return 1;  // extension is in the list.

    return 0;  // just not supported, fail.
} // verify_extension


static void parse_opengl_version(const char *verstr)
{
    if (verstr == NULL)
        ctx->opengl_major = ctx->opengl_minor = 0;
    else
        sscanf(verstr, "%d.%d", &ctx->opengl_major, &ctx->opengl_minor);
} // parse_opengl_version


static void load_extensions(void *(*lookup)(const char *fnname))
{
    const char *extlist = NULL;

    ctx->have_base_opengl = 1;
    ctx->have_GL_ARB_vertex_program = 1;
    ctx->have_GL_ARB_fragment_program = 1;
    ctx->have_GL_ARB_shader_objects = 1;
    ctx->have_GL_ARB_vertex_shader = 1;
    ctx->have_GL_ARB_fragment_shader = 1;
    ctx->have_GL_ARB_shading_language_100 = 1;
    ctx->have_GL_NV_half_float = 1;

    lookup_entry_points(lookup);

    if (!ctx->have_base_opengl)
        set_error("missing basic OpenGL entry points");
    else
    {
        parse_opengl_version((const char *) ctx->glGetString(GL_VERSION));
        extlist = (const char *) ctx->glGetString(GL_EXTENSIONS);
    } // else

    if (extlist == NULL)
        extlist = "";  // just in case.

    #define VERIFY_EXT(ext, major, minor) \
        ctx->have_##ext = verify_extension(#ext, ctx->have_##ext, extlist, major, minor)

    VERIFY_EXT(GL_ARB_vertex_program, -1, -1);
    VERIFY_EXT(GL_ARB_fragment_program, -1, -1);
    VERIFY_EXT(GL_ARB_shader_objects, 2, 0);
    VERIFY_EXT(GL_ARB_vertex_shader, 2, 0);
    VERIFY_EXT(GL_ARB_fragment_shader, 2, 0);
    VERIFY_EXT(GL_ARB_shading_language_100, 2, 0);
    VERIFY_EXT(GL_NV_half_float, -1, -1);

    #undef VERIFY_EXT
} // load_extensions


static int valid_profile(const char *profile)
{
    if (!ctx->have_base_opengl)
        return 0;

    #define MUST_HAVE(p, x) \
        if (!ctx->have_##x) { set_error(#p " profile needs " #x); return 0; }

    if (profile == NULL)
    {
        set_error("NULL profile");
        return 0;
    } // if

    #if SUPPORT_PROFILE_ARB1
    else if (strcmp(profile, MOJOSHADER_PROFILE_ARB1) == 0)
    {
        MUST_HAVE(MOJOSHADER_PROFILE_ARB1, GL_ARB_vertex_program);
        MUST_HAVE(MOJOSHADER_PROFILE_ARB1, GL_ARB_fragment_program);
    } // else if
    #endif

    #if SUPPORT_PROFILE_GLSL
    else if (strcmp(profile, MOJOSHADER_PROFILE_GLSL) == 0)
    {
        MUST_HAVE(MOJOSHADER_PROFILE_GLSL, GL_ARB_shader_objects);
        MUST_HAVE(MOJOSHADER_PROFILE_GLSL, GL_ARB_vertex_shader);
        MUST_HAVE(MOJOSHADER_PROFILE_GLSL, GL_ARB_fragment_shader);
        MUST_HAVE(MOJOSHADER_PROFILE_GLSL, GL_ARB_shading_language_100);
    } // else if
    #endif

    else
    {
        set_error("unknown or unsupported profile");
        return 0;
    } // else

    #undef MUST_HAVE

    return 1;
} // valid_profile


const char *MOJOSHADER_glBestProfile(void *(*lookup)(const char *fnname))
{
    const char *retval = NULL;
    MOJOSHADER_glContext _ctx;
    MOJOSHADER_glContext *current_ctx = ctx;

    ctx = &_ctx;
    memset(ctx, '\0', sizeof (MOJOSHADER_glContext));
    load_extensions(lookup);

    if (ctx->have_base_opengl)
    {
        static const char *priority[] = {
            MOJOSHADER_PROFILE_GLSL,
            MOJOSHADER_PROFILE_ARB1,
        };

        int i;
        for (i = 0; i < STATICARRAYLEN(priority); i++)
        {
            // !!! FIXME: if Mac OS X <= 10.4, don't ever pick GLSL, even if
            // !!! FIXME:  the system claims it is available.
            if (valid_profile(priority[i]))
            {
                retval = priority[i];
                break;
            } // if
        } // for

        if (retval == NULL)
            set_error("no profiles available");
    } // if

    ctx = current_ctx;
    return retval;
} // MOJOSHADER_glBestProfile


MOJOSHADER_glContext *MOJOSHADER_glCreateContext(const char *profile,
                                        void *(*lookup)(const char *fnname),
                                        MOJOSHADER_malloc m, MOJOSHADER_free f,
                                        void *d)
{
    MOJOSHADER_glContext *retval = NULL;
    MOJOSHADER_glContext *current_ctx = ctx;
    ctx = NULL;

    if (m == NULL) m = internal_malloc;
    if (f == NULL) f = internal_free;

    ctx = (MOJOSHADER_glContext *) m(sizeof (MOJOSHADER_glContext), d);
    if (ctx == NULL)
    {
        set_error("out of memory");
        goto init_fail;
    } // if

    memset(ctx, '\0', sizeof (MOJOSHADER_glContext));
    ctx->malloc_fn = m;
    ctx->free_fn = f;
    ctx->malloc_data = d;
    snprintf(ctx->profile, sizeof (ctx->profile), "%s", profile);

    load_extensions(lookup);
    if (!valid_profile(profile))
        goto init_fail;

    MOJOSHADER_glBindProgram(NULL);

    // !!! FIXME: generalize this part.
    if (profile == NULL) {}

#if SUPPORT_PROFILE_GLSL
    else if (strcmp(profile, MOJOSHADER_PROFILE_GLSL) == 0)
    {
        ctx->profileMaxUniforms = impl_GLSL_MaxUniforms;
        ctx->profileCompileShader = impl_GLSL_CompileShader;
        ctx->profileDeleteShader = impl_GLSL_DeleteShader;
        ctx->profileDeleteProgram = impl_GLSL_DeleteProgram;
        ctx->profileGetAttribLocation = impl_GLSL_GetAttribLocation;
        ctx->profileGetUniformLocation = impl_GLSL_GetUniformLocation;
        ctx->profileGetSamplerLocation = impl_GLSL_GetSamplerLocation;
        ctx->profileLinkProgram = impl_GLSL_LinkProgram;
        ctx->profileUseProgramObject = impl_GLSL_UseProgramObject;
        ctx->profileUniform4fv = impl_GLSL_Uniform4fv;
        ctx->profileUniform4iv = impl_GLSL_Uniform4iv;
        ctx->profileUniform1i = impl_GLSL_Uniform1i;
        ctx->profileSetSampler = impl_GLSL_SetSampler;
    } // if
#endif

#if SUPPORT_PROFILE_ARB1
    else if (strcmp(profile, MOJOSHADER_PROFILE_ARB1) == 0)
    {
        ctx->profileMaxUniforms = impl_ARB1_MaxUniforms;
        ctx->profileCompileShader = impl_ARB1_CompileShader;
        ctx->profileDeleteShader = impl_ARB1_DeleteShader;
        ctx->profileDeleteProgram = impl_ARB1_DeleteProgram;
        ctx->profileGetAttribLocation = impl_ARB1_GetAttribLocation;
        ctx->profileGetUniformLocation = impl_ARB1_GetUniformLocation;
        ctx->profileGetSamplerLocation = impl_ARB1_GetSamplerLocation;
        ctx->profileLinkProgram = impl_ARB1_LinkProgram;
        ctx->profileUseProgramObject = impl_ARB1_UseProgramObject;
        ctx->profileUniform4fv = impl_ARB1_Uniform4fv;
        ctx->profileUniform4iv = impl_ARB1_Uniform4iv;
        ctx->profileUniform1i = impl_ARB1_Uniform1i;
        ctx->profileSetSampler = impl_ARB1_SetSampler;
    } // if
#endif

    assert(ctx->profileMaxUniforms != NULL);
    assert(ctx->profileCompileShader != NULL);
    assert(ctx->profileDeleteShader != NULL);
    assert(ctx->profileDeleteProgram != NULL);
    assert(ctx->profileMaxUniforms != NULL);
    assert(ctx->profileCompileShader != NULL);
    assert(ctx->profileDeleteShader != NULL);
    assert(ctx->profileDeleteProgram != NULL);
    assert(ctx->profileGetAttribLocation != NULL);
    assert(ctx->profileGetUniformLocation != NULL);
    assert(ctx->profileGetSamplerLocation != NULL);
    assert(ctx->profileLinkProgram != NULL);
    assert(ctx->profileUseProgramObject != NULL);
    assert(ctx->profileUniform4fv != NULL);
    assert(ctx->profileUniform4iv != NULL);
    assert(ctx->profileUniform1i != NULL);
    assert(ctx->profileSetSampler != NULL);

    retval = ctx;
    ctx = current_ctx;
    return retval;

init_fail:
    if (ctx != NULL)
        f(ctx, d);
    ctx = current_ctx;
    return NULL;
} // MOJOSHADER_glCreateContext


void MOJOSHADER_glMakeContextCurrent(MOJOSHADER_glContext *_ctx)
{
    ctx = _ctx;
} // MOJOSHADER_glMakeContextCurrent


static int impl_GLSL_MaxUniforms(MOJOSHADER_shaderType shader_type)
{
    GLenum pname = GL_NONE;
    GLint val = 0;
    if (shader_type == MOJOSHADER_TYPE_VERTEX)
        pname = GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB;
    else if (shader_type == MOJOSHADER_TYPE_PIXEL)
        pname = GL_MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB;
    else
        return -1;

    ctx->glGetIntegerv(pname, &val);
    return (int) val;
} // impl_GLSL_MaxUniforms


static int impl_ARB1_MaxUniforms(MOJOSHADER_shaderType shader_type)
{
    GLint retval = 0;
    GLenum program_type = GL_NONE;
    if (shader_type == MOJOSHADER_TYPE_VERTEX)
        program_type = GL_VERTEX_PROGRAM_ARB;
    else if (shader_type == MOJOSHADER_TYPE_PIXEL)
        program_type = GL_FRAGMENT_PROGRAM_ARB;
    else
        return -1;

    ctx->glGetProgramivARB(program_type, GL_MAX_PROGRAM_PARAMETERS_ARB, &retval);
    return (int) retval;
} // impl_ARB1_MaxUniforms


int MOJOSHADER_glMaxUniforms(MOJOSHADER_shaderType shader_type)
{
    return ctx->profileMaxUniforms(shader_type);
} // MOJOSHADER_glMaxUniforms


static int impl_GLSL_CompileShader(const MOJOSHADER_parseData *pd, GLuint *s)
{
    GLint ok = 0;
    GLint shaderlen = (GLint) pd->output_len;
    const GLenum shader_type = (pd->shader_type == MOJOSHADER_TYPE_PIXEL) ? GL_FRAGMENT_SHADER : GL_VERTEX_SHADER;
    GLuint shader = ctx->glCreateShaderObject(shader_type);

    ctx->glShaderSource(shader, 1, (const GLchar **) &pd->output, &shaderlen);
    ctx->glCompileShader(shader);
    ctx->glGetObjectParameteriv(shader, GL_OBJECT_COMPILE_STATUS_ARB, &ok);

    if (!ok)
    {
        GLsizei len = 0;
        ctx->glGetInfoLog(shader, sizeof (error_buffer), &len,
                          (GLchar *) error_buffer);
        *s = 0;
        return 0;
    } // if

    *s = shader;
    return 1;
} // impl_GLSL_CompileShader


static int impl_ARB1_CompileShader(const MOJOSHADER_parseData *pd, GLuint *s)
{
    GLint shaderlen = (GLint) pd->output_len;
    const GLenum shader_type = (pd->shader_type == MOJOSHADER_TYPE_PIXEL) ? GL_FRAGMENT_PROGRAM_ARB : GL_VERTEX_PROGRAM_ARB;
    GLuint shader = 0;
    ctx->glGenProgramsARB(1, &shader);

    ctx->glGetError();  // flush any existing error state.
    ctx->glBindProgramARB(shader_type, shader);
    ctx->glProgramStringARB(shader_type, GL_PROGRAM_FORMAT_ASCII_ARB,
                            shaderlen, pd->output);

    if (ctx->glGetError() == GL_INVALID_OPERATION)
    { 
        GLint pos = 0;
        ctx->glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB, &pos);
        const GLubyte *errstr = ctx->glGetString(GL_PROGRAM_ERROR_STRING_ARB);
        snprintf(error_buffer, sizeof (error_buffer),
                  "ARB1 compile error at position %d: %s",
                  (int) pos, (const char *) errstr);
        ctx->glBindProgramARB(shader_type, 0);
        ctx->glDeleteProgramsARB(1, &shader);
        *s = 0;
        return 0;
    } // if

    *s = shader;
    return 1;
} // impl_ARB1_CompileShader


static void impl_GLSL_DeleteShader(const GLuint shader)
{
    ctx->glDeleteObject(shader);
} // impl_GLSL_DeleteShader


static void impl_ARB1_DeleteShader(const GLuint _shader)
{
    GLuint shader = _shader;  // const removal.
    ctx->glDeleteProgramsARB(1, &shader);
} // impl_ARB1_DeleteShader


static void impl_GLSL_DeleteProgram(const GLuint program)
{
    ctx->glDeleteObject(program);
} // impl_GLSL_DeleteProgram


static void impl_ARB1_DeleteProgram(const GLuint program)
{
    // no-op. ARB1 doesn't have real linked programs.
} // impl_GLSL_DeleteProgram


MOJOSHADER_glShader *MOJOSHADER_glCompileShader(const unsigned char *tokenbuf,
                                                const unsigned int bufsize)
{
    MOJOSHADER_glShader *retval = NULL;
    GLuint shader = 0;
    const MOJOSHADER_parseData *pd = MOJOSHADER_parse(ctx->profile, tokenbuf,
                                                      bufsize, ctx->malloc_fn,
                                                      ctx->free_fn,
                                                      ctx->malloc_data);
    if (pd->error != NULL)
    {
        set_error(pd->error);
        goto compile_shader_fail;
    } // if

    retval = (MOJOSHADER_glShader *) Malloc(sizeof (MOJOSHADER_glShader));
    if (retval == NULL)
        goto compile_shader_fail;

    if (!ctx->profileCompileShader(pd, &shader))
        goto compile_shader_fail;

    retval->parseData = pd;
    retval->handle = shader;
    retval->refcount = 1;
    return retval;

compile_shader_fail:
    MOJOSHADER_freeParseData(pd);
    Free(retval);
    if (shader != 0)
        ctx->profileDeleteShader(shader);
    return NULL;
} // MOJOSHADER_glCompileShader


const MOJOSHADER_parseData *MOJOSHADER_glGetShaderParseData(
                                                MOJOSHADER_glShader *shader)
{
    return (shader != NULL) ? shader->parseData : NULL;
} // MOJOSHADER_glGetShaderParseData


static void shader_unref(MOJOSHADER_glShader *shader)
{
    if (shader != NULL)
    {
        const uint32 refcount = shader->refcount;
        if (refcount > 1)
            shader->refcount--;
        else
        {
            ctx->profileDeleteShader(shader->handle);
            MOJOSHADER_freeParseData(shader->parseData);
            Free(shader);
        } // else
    } // if
} // shader_unref


static void program_unref(MOJOSHADER_glProgram *program)
{
    if (program != NULL)
    {
        const uint32 refcount = program->refcount;
        if (refcount > 1)
            program->refcount--;
        else
        {
            ctx->profileDeleteProgram(program->handle);
            shader_unref(program->vertex);
            shader_unref(program->fragment);
            Free(program->constants);
            Free(program->samplers);
            Free(program->uniforms);
            Free(program->attributes);
            Free(program);
        } // else
    } // if
} // program_unref


static GLint impl_GLSL_GetUniformLocation(MOJOSHADER_glProgram *program,
                                          MOJOSHADER_glShader *shader, int idx)
{
    return ctx->glGetUniformLocation(program->handle,
                                     shader->parseData->uniforms[idx].name);
} // impl_GLSL_GetUniformLocation


static GLint impl_ARB1_GetUniformLocation(MOJOSHADER_glProgram *program,
                                          MOJOSHADER_glShader *shader, int idx)
{
    assert(shader->parseData->uniforms[idx].type == MOJOSHADER_UNIFORM_FLOAT);
    return shader->parseData->uniforms[idx].index;  // !!! FIXME: doesn't work if there are int or bool uniforms!
} // impl_ARB1_GetUniformLocation


static void lookup_uniforms(MOJOSHADER_glProgram *program,
                            MOJOSHADER_glShader *shader)
{
    const MOJOSHADER_parseData *pd = shader->parseData;
    const MOJOSHADER_uniform *u = pd->uniforms;
    const MOJOSHADER_shaderType shader_type = pd->shader_type;
    int i;

    for (i = 0; i < pd->uniform_count; i++)
    {
        const GLint loc = ctx->profileGetUniformLocation(program, shader, i);
        if (loc != -1)  // maybe the Uniform was optimized out?
        {
            UniformMap *map = &program->uniforms[program->uniform_count];
            map->shader_type = shader_type;
            map->uniform = &u[i];
            map->location = (GLuint) loc;
            program->uniform_count++;
        } // if
    } // for
} // lookup_uniforms


static GLint impl_GLSL_GetSamplerLocation(MOJOSHADER_glProgram *program,
                                          MOJOSHADER_glShader *shader, int idx)
{
    return ctx->glGetUniformLocation(program->handle,
                                     shader->parseData->samplers[idx].name);
} // impl_GLSL_GetSamplerLocation


static GLint impl_ARB1_GetSamplerLocation(MOJOSHADER_glProgram *program,
                                          MOJOSHADER_glShader *shader, int idx)
{
    return shader->parseData->samplers[idx].index;
} // impl_ARB1_GetSamplerLocation


static void lookup_samplers(MOJOSHADER_glProgram *program,
                            MOJOSHADER_glShader *shader)
{
    int i;
    const MOJOSHADER_parseData *pd = shader->parseData;
    const MOJOSHADER_sampler *s = pd->samplers;
    const MOJOSHADER_shaderType shader_type = pd->shader_type;

    for (i = 0; i < pd->sampler_count; i++)
    {
        const GLint loc = ctx->profileGetSamplerLocation(program, shader, i);
        if (loc != -1)  // maybe the Sampler was optimized out?
        {
            SamplerMap *map = &program->samplers[program->sampler_count];
            map->shader_type = shader_type;
            map->sampler = &s[i];
            map->location = (GLuint) loc;
            program->sampler_count++;
        } // if
    } // for
} // lookup_samplers


static GLint impl_GLSL_GetAttribLocation(MOJOSHADER_glProgram *program, int idx)
{
    const MOJOSHADER_parseData *pd = program->vertex->parseData;
    const MOJOSHADER_attribute *a = pd->attributes;
    return ctx->glGetAttribLocation(program->handle, a[idx].name);
} // impl_GLSL_GetAttribLocation

static GLint impl_ARB1_GetAttribLocation(MOJOSHADER_glProgram *program, int idx)
{
    return idx;  // map to vertex arrays in the same order as the parseData.
} // impl_ARB1_GetAttribLocation


static void lookup_attributes(MOJOSHADER_glProgram *program)
{
    int i;
    const MOJOSHADER_parseData *pd = program->vertex->parseData;
    const MOJOSHADER_attribute *a = pd->attributes;

    for (i = 0; i < pd->attribute_count; i++)
    {
        const GLint loc = ctx->profileGetAttribLocation(program, i);
        if (loc != -1)  // maybe the Attribute was optimized out?
        {
            AttributeMap *map = &program->attributes[program->attribute_count];
            map->attribute = &a[i];
            map->location = (GLuint) loc;
            program->attribute_count++;
        } // if
    } // for
} // lookup_attributes


static GLuint impl_GLSL_LinkProgram(MOJOSHADER_glShader *vshader,
                                    MOJOSHADER_glShader *pshader)
{
    const GLuint program = ctx->glCreateProgramObject();

    if (vshader != NULL) ctx->glAttachObject(program, vshader->handle);
    if (pshader != NULL) ctx->glAttachObject(program, pshader->handle);

    ctx->glLinkProgram(program);

    GLint ok = 0;
    ctx->glGetObjectParameteriv(program, GL_OBJECT_LINK_STATUS_ARB, &ok);
    if (!ok)
    {
        GLsizei len = 0;
        ctx->glGetInfoLog(program, sizeof (error_buffer), &len, (GLchar *) error_buffer);
        ctx->glDeleteObject(program);
        return 0;
    } // if

    return program;
} // impl_GLSL_LinkProgram


static GLuint impl_ARB1_LinkProgram(MOJOSHADER_glShader *vshader,
                                    MOJOSHADER_glShader *pshader)
{
    // there is no formal linking in ARB1...just return a unique value.
    static GLuint retval = 1;
    return retval++;
} // impl_ARB1_LinkProgram


MOJOSHADER_glProgram *MOJOSHADER_glLinkProgram(MOJOSHADER_glShader *vshader,
                                               MOJOSHADER_glShader *pshader)
{
    if ((vshader == NULL) && (pshader == NULL))
        return NULL;

    int numregs = 0;
    int consts = 0;
    MOJOSHADER_glProgram *retval = NULL;
    const GLuint program = ctx->profileLinkProgram(vshader, pshader);
    if (program == 0)
        goto link_program_fail;

    retval = (MOJOSHADER_glProgram *) Malloc(sizeof (MOJOSHADER_glProgram));
    if (retval == NULL)
        goto link_program_fail;
    memset(retval, '\0', sizeof (MOJOSHADER_glProgram));

    numregs = 0;
    if (vshader != NULL) numregs += vshader->parseData->uniform_count;
    if (pshader != NULL) numregs += pshader->parseData->uniform_count;
    retval->uniforms = (UniformMap *) Malloc(sizeof (UniformMap) * numregs);
    if (retval->uniforms == NULL)
        goto link_program_fail;
    memset(retval->uniforms, '\0', sizeof (UniformMap) * numregs);

    numregs = 0;
    if (vshader != NULL) numregs += vshader->parseData->sampler_count;
    if (pshader != NULL) numregs += pshader->parseData->sampler_count;
    retval->samplers = (SamplerMap *) Malloc(sizeof (SamplerMap) * numregs);
    if (retval->samplers == NULL)
        goto link_program_fail;
    memset(retval->samplers, '\0', sizeof (SamplerMap) * numregs);

    retval->handle = program;
    retval->vertex = vshader;
    retval->fragment = pshader;
    retval->refcount = 1;

    if (vshader != NULL)
    {
        if (consts < vshader->parseData->constant_count)
            consts = vshader->parseData->constant_count;
        retval->attributes = (AttributeMap *) Malloc(sizeof (AttributeMap) *
                                        vshader->parseData->attribute_count);
        if (retval->attributes == NULL)
            goto link_program_fail;

        lookup_attributes(retval);
        lookup_uniforms(retval, vshader);
        lookup_samplers(retval, vshader);
        vshader->refcount++;
    } // if

    if (pshader != NULL)
    {
        if (consts < pshader->parseData->constant_count)
            consts = pshader->parseData->constant_count;

        lookup_uniforms(retval, pshader);
        lookup_samplers(retval, vshader);
        pshader->refcount++;
    } // if

    if (consts > 0)    
    {
        retval->constants = (GLfloat *) Malloc(sizeof (GLfloat) * consts * 4);
        if (retval->constants == NULL)
            goto link_program_fail;
        retval->constant_count = (uint32) consts;
    } // if

    return retval;

link_program_fail:
    if (retval != NULL)
    {
        Free(retval->constants);
        Free(retval->samplers);
        Free(retval->uniforms);
        Free(retval->attributes);
        Free(retval);
    } // if

    if (program != 0)
        ctx->profileDeleteProgram(program);

    return NULL;
} // MOJOSHADER_glLinkProgram


static void impl_GLSL_UseProgramObject(MOJOSHADER_glProgram *program)
{
    ctx->glUseProgramObject((program != NULL) ? program->handle : 0);
} // impl_GLSL_UseProgramObject


static void impl_ARB1_UseProgramObject(MOJOSHADER_glProgram *program)
{
    GLuint vhandle = 0;
    GLuint phandle = 0;
    if (program != NULL)
    {
        if (program->vertex != NULL)
            vhandle = program->vertex->handle;
        if (program->fragment != NULL)
            phandle = program->fragment->handle;
    } // if

    ctx->glBindProgramARB(GL_VERTEX_PROGRAM_ARB, vhandle);
    ctx->glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, phandle);
} // impl_GLSL_UseProgramObject


void MOJOSHADER_glBindProgram(MOJOSHADER_glProgram *program)
{
    GLuint handle = 0;
    int i;

    if (program == ctx->bound_program)
        return;  // nothing to do.

    // Disable any client-side arrays the current program could have used.
    // !!! FIXME: don't disable yet...see which ones get reused, and disable
    // !!! FIXME:  only what we don't need in MOJOSHADER_glProgramReady().
    if (ctx->bound_program != NULL)
    {
        const int count = ctx->bound_program->attribute_count;
        for (i = 0; i < count; i++)
        {
            const AttributeMap *map = &ctx->bound_program->attributes[i];
            ctx->glDisableVertexAttribArray(map->location);
        } // if
    } // for

    if (program != NULL)
    {
        handle = program->handle;
        program->refcount++;
    } // if

    ctx->profileUseProgramObject(program);
    program_unref(ctx->bound_program);
    ctx->bound_program = program;
} // MOJOSHADER_glBindProgram


static inline uint minuint(const uint a, const uint b)
{
    return ((a < b) ? a : b);
} // minuint


void MOJOSHADER_glSetVertexShaderUniformF(unsigned int idx, const float *data,
                                          unsigned int vec4n)
{
    const uint maxregs = STATICARRAYLEN(ctx->vs_reg_file_f) / 4;
    if (idx < maxregs)
    {
        assert(sizeof (GLfloat) == sizeof (float));
        const uint cpy = (minuint(maxregs - idx, vec4n) * sizeof (*data)) * 4;
        memcpy(ctx->vs_reg_file_f + (idx * 4), data, cpy);
    } // if
} // MOJOSHADER_glSetVertexShaderUniformF


void MOJOSHADER_glSetVertexShaderUniformI(unsigned int idx, const int *data,
                                          unsigned int ivec4n)
{
    const uint maxregs = STATICARRAYLEN(ctx->vs_reg_file_i) / 4;
    if (idx < maxregs)
    {
        assert(sizeof (GLint) == sizeof (int));
        const uint cpy = (minuint(maxregs - idx, ivec4n) * sizeof (*data)) * 4;
        memcpy(ctx->vs_reg_file_i + (idx * 4), data, cpy);
    } // if
} // MOJOSHADER_glSetVertexShaderUniformI


void MOJOSHADER_glSetVertexShaderUniformB(unsigned int idx, const int *data,
                                          unsigned int bcount)
{
    const uint maxregs = STATICARRAYLEN(ctx->vs_reg_file_f) / 4;
    if (idx < maxregs)
    {
        GLint *wptr = ctx->vs_reg_file_b + idx;
        GLint *endptr = wptr + minuint(maxregs - idx, bcount);
        while (wptr != endptr)
            *(wptr++) = *(data++) ? 1 : 0;
    } // if
} // MOJOSHADER_glSetVertexShaderUniformB


void MOJOSHADER_glSetPixelShaderUniformF(unsigned int idx, const float *data,
                                         unsigned int vec4n)
{
    const uint maxregs = STATICARRAYLEN(ctx->ps_reg_file_f) / 4;
    if (idx < maxregs)
    {
        assert(sizeof (GLfloat) == sizeof (float));
        const uint cpy = (minuint(maxregs - idx, vec4n) * sizeof (*data)) * 4;
        memcpy(ctx->ps_reg_file_f + (idx * 4), data, cpy);
    } // if
} // MOJOSHADER_glSetPixelShaderUniformF


void MOJOSHADER_glSetPixelShaderUniformI(unsigned int idx, const int *data,
                                         unsigned int ivec4n)
{
    const uint maxregs = STATICARRAYLEN(ctx->ps_reg_file_i) / 4;
    if (idx < maxregs)
    {
        assert(sizeof (GLint) == sizeof (int));
        const uint cpy = (minuint(maxregs - idx, ivec4n) * sizeof (*data)) * 4;
        memcpy(ctx->ps_reg_file_i + (idx * 4), data, cpy);
    } // if
} // MOJOSHADER_glSetPixelShaderUniformI


void MOJOSHADER_glSetPixelShaderUniformB(unsigned int idx, const int *data,
                                         unsigned int bcount)
{
    const uint maxregs = STATICARRAYLEN(ctx->ps_reg_file_f) / 4;
    if (idx < maxregs)
    {
        GLint *wptr = ctx->ps_reg_file_b + idx;
        GLint *endptr = wptr + minuint(maxregs - idx, bcount);
        while (wptr != endptr)
            *(wptr++) = *(data++) ? 1 : 0;
    } // if
} // MOJOSHADER_glSetPixelShaderUniformB


static inline GLenum opengl_attr_type(const MOJOSHADER_attributeType type)
{
    switch (type)
    {
        case MOJOSHADER_ATTRIBUTE_UNKNOWN: return GL_NONE; // oh well.
        case MOJOSHADER_ATTRIBUTE_BYTE: return GL_BYTE;
        case MOJOSHADER_ATTRIBUTE_UBYTE: return GL_UNSIGNED_BYTE;
        case MOJOSHADER_ATTRIBUTE_SHORT: return GL_SHORT;
        case MOJOSHADER_ATTRIBUTE_USHORT: return GL_UNSIGNED_SHORT;
        case MOJOSHADER_ATTRIBUTE_INT: return GL_INT;
        case MOJOSHADER_ATTRIBUTE_UINT: return GL_UNSIGNED_INT;
        case MOJOSHADER_ATTRIBUTE_FLOAT: return GL_FLOAT;
        case MOJOSHADER_ATTRIBUTE_DOUBLE: return GL_DOUBLE;

        case MOJOSHADER_ATTRIBUTE_HALF_FLOAT:
            if (ctx->have_GL_NV_half_float)
                return GL_HALF_FLOAT_NV;
            break;
    } // switch

    return GL_NONE;  // oh well. Raises a GL error later.
} // opengl_attr_type


// !!! FIXME: shouldn't (index) be unsigned?
void MOJOSHADER_glSetVertexAttribute(MOJOSHADER_usage usage,
                                     int index, unsigned int size,
                                     MOJOSHADER_attributeType type,
                                     int normalized, unsigned int stride,
                                     const void *ptr)
{
    if ((ctx->bound_program == NULL) || (ctx->bound_program->vertex == NULL))
        return;

    const GLenum gl_type = opengl_attr_type(type);
    const GLboolean norm = (normalized) ? GL_TRUE : GL_FALSE;
    GLuint gl_index = 0;

    // We have to map POSITION0 to generic vertex attribute 0; the
    //  GL_ARB_vertex_shader spec says this is equivalent to using
    //  glVertexPointer(), but without the limitations of that entry point.

    if ((usage != MOJOSHADER_USAGE_POSITION) || (index != 0))
    {
        int i;
        const int count = ctx->bound_program->attribute_count;
        for (i = 0; i < count; i++)
        {
            const AttributeMap *map = &ctx->bound_program->attributes[i];
            const MOJOSHADER_attribute *a = map->attribute;

            // !!! FIXME: is this array guaranteed to be sorted by usage?
            // !!! FIXME:  if so, we can break if a->usage > usage.

            if ((a->usage == usage) && (a->index == index))
            {
                gl_index = map->location;
                break;
            } // if
        } // for

        if (gl_index == 0)
            return;  // nothing to do, this shader doesn't use this stream.
    } // if

    // these happen to work in both ARB1 and GLSL, but if something alien
    //  shows up, we'll have to split these into profile*() functions.
    ctx->glVertexAttribPointer(gl_index, size, gl_type, norm, stride, ptr);
    ctx->glEnableVertexAttribArray(gl_index);
} // MOJOSHADER_glSetVertexAttribute


static void impl_GLSL_Uniform4fv(const MOJOSHADER_parseData *pd, GLint loc,
                                 GLsizei siz, GLfloat *v)
{
    ctx->glUniform4fv(loc, siz, v);
} // impl_GLSL_Uniform4fv

static void impl_GLSL_Uniform4iv(const MOJOSHADER_parseData *pd, GLint loc,
                                 GLsizei siz, GLint *v)
{
    ctx->glUniform4iv(loc, siz, v);
} // impl_GLSL_Uniform4iv

static void impl_GLSL_Uniform1i(const MOJOSHADER_parseData *pd, GLint loc,
                                GLint v)
{
    ctx->glUniform1i(loc, v);
} // impl_GLSL_Uniform1i

static void impl_GLSL_SetSampler(GLint loc, GLuint sampler)
{
    ctx->glUniform1i(loc, sampler);
} // impl_GLSL_SetSampler


static void impl_ARB1_Uniform4fv(const MOJOSHADER_parseData *pd, GLint loc,
                                 GLsizei siz, GLfloat *v)
{
    int i;
    const GLenum shader_type = (pd->shader_type == MOJOSHADER_TYPE_PIXEL) ? GL_FRAGMENT_PROGRAM_ARB : GL_VERTEX_PROGRAM_ARB;
    for (i = 0; i < siz; i++, v += 4)
        ctx->glProgramEnvParameter4fvARB(shader_type, loc + i, v);
} // impl_ARB1_Uniform4fv

static void impl_ARB1_Uniform4iv(const MOJOSHADER_parseData *pd, GLint loc,
                                 GLsizei siz, GLint *v)
{
    int i;
    const GLenum shader_type = (pd->shader_type == MOJOSHADER_TYPE_PIXEL) ? GL_FRAGMENT_PROGRAM_ARB : GL_VERTEX_PROGRAM_ARB;
    for (i = 0; i < siz; i++, v += 4)
    {
        GLfloat f[4] = {
            (GLfloat) v[0], (GLfloat) v[1], (GLfloat) v[2], (GLfloat) v[3]
        };
        ctx->glProgramEnvParameter4fvARB(shader_type, loc + i, f);
    } // for
} // impl_ARB1_Uniform4iv

static void impl_ARB1_Uniform1i(const MOJOSHADER_parseData *pd, GLint loc,
                                GLint _v)
{
    const GLenum shader_type = (pd->shader_type == MOJOSHADER_TYPE_PIXEL) ? GL_FRAGMENT_PROGRAM_ARB : GL_VERTEX_PROGRAM_ARB;
    const GLfloat v = (GLfloat) _v;
    GLfloat f[4] = { v, v, v, v };
    ctx->glProgramEnvParameter4fvARB(shader_type, loc, f);
} // impl_ARB1_Uniform1i

static void impl_ARB1_SetSampler(GLint loc, GLuint sampler)
{
    // no-op in this profile...arb1 uses the texture units as-is.
    assert(loc == (GLint) sampler);
} // impl_ARB1_SetSampler


void MOJOSHADER_glProgramReady(void)
{
    int i;
    int count;

    if (ctx->bound_program == NULL)
        return;  // nothing to do.

    // !!! FIXME: don't push Uniforms/Samplers if they haven't changed.

    // push Uniforms to the program from our register files...
    count = ctx->bound_program->uniform_count;
    for (i = 0; i < count; i++)
    {
        const UniformMap *map = &ctx->bound_program->uniforms[i];
        const MOJOSHADER_uniform *u = map->uniform;
        const MOJOSHADER_uniformType type = u->type;
        const MOJOSHADER_shaderType shader_type = map->shader_type;
        const int index = u->index;
        const int size = u->array_count;
        const GLint location = map->location;
        const MOJOSHADER_parseData *pd;
        const MOJOSHADER_constant *c;
        float *regfilef;
        GLint *regfilei;
        GLint *regfileb;
        int hi;

        if (shader_type == MOJOSHADER_TYPE_VERTEX)
        {
            pd = ctx->bound_program->vertex->parseData;
            regfilef = ctx->vs_reg_file_f;
            regfilei = ctx->vs_reg_file_i;
            regfileb = ctx->vs_reg_file_b;
        } // if
        else
        {
            pd = ctx->bound_program->fragment->parseData;
            regfilef = ctx->ps_reg_file_f;
            regfilei = ctx->ps_reg_file_i;
            regfileb = ctx->ps_reg_file_b;
        } // else

        c = pd->constants;
        hi = pd->constant_count;

        // only use arrays for 'c' registers.
        assert((size == 0) || (type == MOJOSHADER_UNIFORM_FLOAT));

        if (size != 0)  // !!! FIXME: this code sucks.
        {
            // !!! FIXME: calculate this all at link time.
            int j;
            GLfloat *ptr = ctx->bound_program->constants;

            for (j = 0; j < hi; j++)
            {
                if (c[j].type != MOJOSHADER_UNIFORM_FLOAT)
                    continue;

                const int idx = c[j].index;
                if ( (idx >= index) && (idx < (index + size)) )
                {
                    memcpy(ptr, &regfilef[idx * 4], 16);  // !!! FIXME: 16
                    memcpy(&regfilef[idx * 4], &c->value.f, 16);  // !!! FIXME: 16
                    ptr += 4;
                } // if
            } // for

            ctx->profileUniform4fv(pd, location, size, &regfilef[index * 4]);

            ptr = ctx->bound_program->constants;
            for (j = 0; j < hi; j++)
            {
                if (c[j].type != MOJOSHADER_UNIFORM_FLOAT)
                    continue;

                const int idx = c[j].index;
                if ( (idx >= index) && (idx < (index + size)) )
                {
                    memcpy(&regfilef[idx * 4], ptr, 16);  // !!! FIXME: 16
                    ptr += 4;
                } // if
            } // for
        } // if

        else
        {
            if (type == MOJOSHADER_UNIFORM_FLOAT)
                ctx->profileUniform4fv(pd, location, 1, &regfilef[index * 4]);
            else if (type == MOJOSHADER_UNIFORM_INT)
                ctx->profileUniform4iv(pd, location, 1, &regfilei[index * 4]);
            else if (type == MOJOSHADER_UNIFORM_BOOL)
                ctx->profileUniform1i(pd, location, regfileb[index]);
        } // if
    } // for

    // push Samplers to the program from our register files...
    count = ctx->bound_program->sampler_count;
    for (i = 0; i < count; i++)
    {
        const SamplerMap *map = &ctx->bound_program->samplers[i];
        const MOJOSHADER_sampler *s = map->sampler;
        ctx->profileSetSampler(map->location, s->index);
    } // for
} // MOJOSHADER_glProgramReady


void MOJOSHADER_glDeleteProgram(MOJOSHADER_glProgram *program)
{
    program_unref(program);
} // MOJOSHADER_glDeleteProgram


void MOJOSHADER_glDeleteShader(MOJOSHADER_glShader *shader)
{
    shader_unref(shader);
} // MOJOSHADER_glDeleteShader


void MOJOSHADER_glDestroyContext(MOJOSHADER_glContext *_ctx)
{
    MOJOSHADER_glContext *current_ctx = ctx;
    ctx = _ctx;
    MOJOSHADER_glBindProgram(NULL);
    lookup_entry_points(NULL);
    Free(ctx);
    ctx = ((current_ctx == _ctx) ? NULL : current_ctx);
} // MOJOSHADER_glDestroyContext

// end of mojoshader_opengl.c ...