struct GlobalShaderParamState
{
    const char *name;
    union
    {
        float fval[32];
        int ival[32];
        uint uval[32];
        uchar buf[32*sizeof(float)];
    };
    int version;

    static int nextversion;

    void resetversions();

    void changed()
    {
        if(++nextversion < 0) resetversions();
        version = nextversion;
    }
};

struct ShaderParamBinding
{
    int loc, size;
    GLenum format;
};

struct GlobalShaderParamUse : ShaderParamBinding
{

    GlobalShaderParamState *param;
    int version;

    void flush()
    {
        if(version == param->version) return;
        switch(format)
        {
            case GL_BOOL:
            case GL_FLOAT:      glUniform1fv_(loc, size, param->fval); break;
            case GL_BOOL_VEC2:
            case GL_FLOAT_VEC2: glUniform2fv_(loc, size, param->fval); break;
            case GL_BOOL_VEC3:
            case GL_FLOAT_VEC3: glUniform3fv_(loc, size, param->fval); break;
            case GL_BOOL_VEC4:
            case GL_FLOAT_VEC4: glUniform4fv_(loc, size, param->fval); break;
            case GL_INT:        glUniform1iv_(loc, size, param->ival); break;
            case GL_INT_VEC2:   glUniform2iv_(loc, size, param->ival); break;
            case GL_INT_VEC3:   glUniform3iv_(loc, size, param->ival); break;
            case GL_INT_VEC4:   glUniform4iv_(loc, size, param->ival); break;
            case GL_UNSIGNED_INT:      glUniform1uiv_(loc, size, param->uval); break;
            case GL_UNSIGNED_INT_VEC2: glUniform2uiv_(loc, size, param->uval); break;
            case GL_UNSIGNED_INT_VEC3: glUniform3uiv_(loc, size, param->uval); break;
            case GL_UNSIGNED_INT_VEC4: glUniform4uiv_(loc, size, param->uval); break;
            case GL_FLOAT_MAT2: glUniformMatrix2fv_(loc, 1, GL_FALSE, param->fval); break;
            case GL_FLOAT_MAT3: glUniformMatrix3fv_(loc, 1, GL_FALSE, param->fval); break;
            case GL_FLOAT_MAT4: glUniformMatrix4fv_(loc, 1, GL_FALSE, param->fval); break;
        }
        version = param->version;
    }
};

struct LocalShaderParamState : ShaderParamBinding
{
    const char *name;
};

struct SlotShaderParam
{
    enum
    {
        REUSE = 1<<0
    };

    const char *name;
    int loc, flags;
    float val[4];
};

struct SlotShaderParamState : LocalShaderParamState
{
    int flags;
    float val[4];

    SlotShaderParamState() {}
    SlotShaderParamState(const SlotShaderParam &p)
    {
        name = p.name;
        loc = -1;
        size = 1;
        format = GL_FLOAT_VEC4;
        flags = p.flags;
        memcpy(val, p.val, sizeof(val));
    }
};

enum
{
    SHADER_DEFAULT    = 0,
    SHADER_WORLD      = 1<<0,
    SHADER_ENVMAP     = 1<<1,
    SHADER_REFRACT    = 1<<2,
    SHADER_OPTION     = 1<<3,
    SHADER_DYNAMIC    = 1<<4,
    SHADER_TRIPLANAR  = 1<<5,

    SHADER_INVALID    = 1<<8,
    SHADER_DEFERRED   = 1<<9
};

#define MAXVARIANTROWS 32

struct Slot;
struct VSlot;

struct UniformLoc
{
    const char *name, *blockname;
    int loc, version, binding, stride, offset, size;
    void *data;
    UniformLoc(const char *name = NULL, const char *blockname = NULL, int binding = -1, int stride = -1) : name(name), blockname(blockname), loc(-1), version(-1), binding(binding), stride(stride), offset(-1), size(-1), data(NULL) {}
};

struct AttribLoc
{
    const char *name;
    int loc;
    AttribLoc(const char *name = NULL, int loc = -1) : name(name), loc(loc) {}
};

struct FragDataLoc
{
    const char *name;
    int loc;
    GLenum format;
    int index;
    FragDataLoc(const char *name = NULL, int loc = -1, GLenum format = GL_FALSE, int index = 0) : name(name), loc(loc), format(format), index(index) {}
};

struct Shader
{
    static Shader *lastshader;

    char *name, *vsstr, *psstr, *defer;
    int type;
    GLuint program, vsobj, psobj;
    vector<SlotShaderParamState> defaultparams;
    vector<GlobalShaderParamUse> globalparams;
    vector<LocalShaderParamState> localparams;
    vector<uchar> localparamremap;
    Shader *variantshader;
    vector<Shader *> variants;
    ushort *variantrows;
    bool standard, forced, used;
    Shader *reusevs, *reuseps;
    vector<UniformLoc> uniformlocs;
    vector<AttribLoc> attriblocs;
    vector<FragDataLoc> fragdatalocs;
    const void *owner;

    Shader() : name(NULL), vsstr(NULL), psstr(NULL), defer(NULL), type(SHADER_DEFAULT), program(0), vsobj(0), psobj(0), variantshader(NULL), variantrows(NULL), standard(false), forced(false), used(false), reusevs(NULL), reuseps(NULL), owner(NULL)
    {
    }

    ~Shader()
    {
        DELETEA(name);
        DELETEA(vsstr);
        DELETEA(psstr);
        DELETEA(defer);
        DELETEA(variantrows);
    }

    void allocparams(Slot *slot = NULL);
    void setslotparams(Slot &slot);
    void setslotparams(Slot &slot, VSlot &vslot);
    void bindprograms();

    void flushparams(Slot *slot = NULL)
    {
        if(!used) { allocparams(slot); used = true; }
        loopv(globalparams) globalparams[i].flush();
    }

    void force();

    bool invalid() const { return (type&SHADER_INVALID)!=0; }
    bool deferred() const { return (type&SHADER_DEFERRED)!=0; }
    bool loaded() const { return !(type&(SHADER_DEFERRED|SHADER_INVALID)); }

    bool hasoption() const { return (type&SHADER_OPTION)!=0; }

    bool isdynamic() const { return (type&SHADER_DYNAMIC)!=0; }

    static inline bool isnull(const Shader *s) { return !s; }

    bool isnull() const { return isnull(this); }

    int numvariants(int row) const
    {
        if(row < 0 || row >= MAXVARIANTROWS || !variantrows) return 0;
        return variantrows[row+1] - variantrows[row];
    }

    Shader *getvariant(int col, int row) const
    {
        if(row < 0 || row >= MAXVARIANTROWS || col < 0 || !variantrows) return NULL;
        int start = variantrows[row], end = variantrows[row+1];
        return col < end - start ? variants[start + col] : NULL;
    }

    void addvariant(int row, Shader *s)
    {
        if(row < 0 || row >= MAXVARIANTROWS || variants.length() >= USHRT_MAX) return;
        if(!variantrows) { variantrows = new ushort[MAXVARIANTROWS+1]; memset(variantrows, 0, (MAXVARIANTROWS+1)*sizeof(ushort)); }
        variants.insert(variantrows[row+1], s);
        for(int i = row+1; i <= MAXVARIANTROWS; ++i) ++variantrows[i];
    }

    void setvariant_(int col, int row)
    {
        Shader *s = this;
        if(variantrows)
        {
            int start = variantrows[row], end = variantrows[row+1];
            for(col = min(start + col, end-1); col >= start; --col) if(!variants[col]->invalid()) { s = variants[col]; break; }
        }
        if(lastshader!=s) s->bindprograms();
    }

    void setvariant(int col, int row)
    {
        if(isnull() || !loaded()) return;
        setvariant_(col, row);
        lastshader->flushparams();
    }

    void setvariant(int col, int row, Slot &slot)
    {
        if(isnull() || !loaded()) return;
        setvariant_(col, row);
        lastshader->flushparams(&slot);
        lastshader->setslotparams(slot);
    }

    void setvariant(int col, int row, Slot &slot, VSlot &vslot)
    {
        if(isnull() || !loaded()) return;
        setvariant_(col, row);
        lastshader->flushparams(&slot);
        lastshader->setslotparams(slot, vslot);
    }

    void set_()
    {
        if(lastshader!=this) bindprograms();
    }

    void set()
    {
        if(isnull() || !loaded()) return;
        set_();
        lastshader->flushparams();
    }

    void set(Slot &slot)
    {
        if(isnull() || !loaded()) return;
        set_();
        lastshader->flushparams(&slot);
        lastshader->setslotparams(slot);
    }

    void set(Slot &slot, VSlot &vslot)
    {
        if(isnull() || !loaded()) return;
        set_();
        lastshader->flushparams(&slot);
        lastshader->setslotparams(slot, vslot);
    }

    bool compile();
    void cleanup(bool full = false);

    static int uniformlocversion();
};

struct GlobalShaderParam
{
    const char *name;
    GlobalShaderParamState *param;

    GlobalShaderParam(const char *name) : name(name), param(NULL) {}

    GlobalShaderParamState *resolve()
    {
        extern GlobalShaderParamState *getglobalparam(const char *name);
        if(!param) param = getglobalparam(name);
        param->changed();
        return param;
    }

    void setf(float x = 0, float y = 0, float z = 0, float w = 0)
    {
        GlobalShaderParamState *g = resolve();
        g->fval[0] = x;
        g->fval[1] = y;
        g->fval[2] = z;
        g->fval[3] = w;
    }
    void set(const vec &v, float w = 0) { setf(v.x, v.y, v.z, w); }
    void set(const vec2 &v, float z = 0, float w = 0) { setf(v.x, v.y, z, w); }
    void set(const vec4 &v) { setf(v.x, v.y, v.z, v.w); }
    void set(const plane &p) { setf(p.x, p.y, p.z, p.offset); }
    void set(const matrix2 &m) { memcpy(resolve()->fval, m.a.v, sizeof(m)); }
    void set(const matrix3 &m) { memcpy(resolve()->fval, m.a.v, sizeof(m)); }
    void set(const matrix4 &m) { memcpy(resolve()->fval, m.a.v, sizeof(m)); }

    template<class T>
    void setv(const T *v, int n = 1) { memcpy(resolve()->buf, v, n*sizeof(T)); }

    void seti(int x = 0, int y = 0, int z = 0, int w = 0)
    {
        GlobalShaderParamState *g = resolve();
        g->ival[0] = x;
        g->ival[1] = y;
        g->ival[2] = z;
        g->ival[3] = w;
    }
    void set(const ivec &v, int w = 0) { seti(v.x, v.y, v.z, w); }
    void set(const ivec2 &v, int z = 0, int w = 0) { seti(v.x, v.y, z, w); }
    void set(const ivec4 &v) { seti(v.x, v.y, v.z, v.w); }

    void setu(uint x = 0, uint y = 0, uint z = 0, uint w = 0)
    {
        GlobalShaderParamState *g = resolve();
        g->uval[0] = x;
        g->uval[1] = y;
        g->uval[2] = z;
        g->uval[3] = w;
    }

    template<class T>
    T *reserve(int n = 1) { return (T *)resolve()->buf; }
};

struct LocalShaderParam
{
    const char *name;
    int loc;

    LocalShaderParam(const char *name) : name(name), loc(-1) {}

    LocalShaderParamState *resolve()
    {
        Shader *s = Shader::lastshader;
        if(!s) return NULL;
        if(!s->localparamremap.inrange(loc))
        {
            extern int getlocalparam(const char *name);
            if(loc == -1) loc = getlocalparam(name);
            if(!s->localparamremap.inrange(loc)) return NULL;
        }
        uchar remap = s->localparamremap[loc];
        return s->localparams.inrange(remap) ? &s->localparams[remap] : NULL;
    }

    void setf(float x = 0, float y = 0, float z = 0, float w = 0)
    {
        ShaderParamBinding *b = resolve();
        if(b) switch(b->format)
        {
            case GL_BOOL:
            case GL_FLOAT:      glUniform1f_(b->loc, x); break;
            case GL_BOOL_VEC2:
            case GL_FLOAT_VEC2: glUniform2f_(b->loc, x, y); break;
            case GL_BOOL_VEC3:
            case GL_FLOAT_VEC3: glUniform3f_(b->loc, x, y, z); break;
            case GL_BOOL_VEC4:
            case GL_FLOAT_VEC4: glUniform4f_(b->loc, x, y, z, w); break;
            case GL_INT:        glUniform1i_(b->loc, int(x)); break;
            case GL_INT_VEC2:   glUniform2i_(b->loc, int(x), int(y)); break;
            case GL_INT_VEC3:   glUniform3i_(b->loc, int(x), int(y), int(z)); break;
            case GL_INT_VEC4:   glUniform4i_(b->loc, int(x), int(y), int(z), int(w)); break;
            case GL_UNSIGNED_INT:      glUniform1ui_(b->loc, uint(x)); break;
            case GL_UNSIGNED_INT_VEC2: glUniform2ui_(b->loc, uint(x), uint(y)); break;
            case GL_UNSIGNED_INT_VEC3: glUniform3ui_(b->loc, uint(x), uint(y), uint(z)); break;
            case GL_UNSIGNED_INT_VEC4: glUniform4ui_(b->loc, uint(x), uint(y), uint(z), uint(w)); break;
        }
    }
    void set(const vec &v, float w = 0) { setf(v.x, v.y, v.z, w); }
    void set(const vec2 &v, float z = 0, float w = 0) { setf(v.x, v.y, z, w); }
    void set(const vec4 &v) { setf(v.x, v.y, v.z, v.w); }
    void set(const plane &p) { setf(p.x, p.y, p.z, p.offset); }
    void setv(const float *f, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniform1fv_(b->loc, n, f); }
    void setv(const vec *v, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniform3fv_(b->loc, n, v->v); }
    void setv(const vec2 *v, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniform2fv_(b->loc, n, v->v); }
    void setv(const vec4 *v, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniform4fv_(b->loc, n, v->v); }
    void setv(const plane *p, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniform4fv_(b->loc, n, p->v); }
    void setv(const matrix2 *m, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniformMatrix2fv_(b->loc, n, GL_FALSE, m->a.v); }
    void setv(const matrix3 *m, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniformMatrix3fv_(b->loc, n, GL_FALSE, m->a.v); }
    void setv(const matrix4 *m, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniformMatrix4fv_(b->loc, n, GL_FALSE, m->a.v); }
    void set(const matrix2 &m) { setv(&m); }
    void set(const matrix3 &m) { setv(&m); }
    void set(const matrix4 &m) { setv(&m); }

    template<class T>
    void sett(T x, T y, T z, T w)
    {
        ShaderParamBinding *b = resolve();
        if(b) switch(b->format)
        {
            case GL_FLOAT:      glUniform1f_(b->loc, x); break;
            case GL_FLOAT_VEC2: glUniform2f_(b->loc, x, y); break;
            case GL_FLOAT_VEC3: glUniform3f_(b->loc, x, y, z); break;
            case GL_FLOAT_VEC4: glUniform4f_(b->loc, x, y, z, w); break;
            case GL_BOOL:
            case GL_INT:        glUniform1i_(b->loc, x); break;
            case GL_BOOL_VEC2:
            case GL_INT_VEC2:   glUniform2i_(b->loc, x, y); break;
            case GL_BOOL_VEC3:
            case GL_INT_VEC3:   glUniform3i_(b->loc, x, y, z); break;
            case GL_BOOL_VEC4:
            case GL_INT_VEC4:   glUniform4i_(b->loc, x, y, z, w); break;
            case GL_UNSIGNED_INT:      glUniform1ui_(b->loc, x); break;
            case GL_UNSIGNED_INT_VEC2: glUniform2ui_(b->loc, x, y); break;
            case GL_UNSIGNED_INT_VEC3: glUniform3ui_(b->loc, x, y, z); break;
            case GL_UNSIGNED_INT_VEC4: glUniform4ui_(b->loc, x, y, z, w); break;
        }
    }
    void seti(int x = 0, int y = 0, int z = 0, int w = 0) { sett<int>(x, y, z, w); }
    void set(const ivec &v, int w = 0) { seti(v.x, v.y, v.z, w); }
    void set(const ivec2 &v, int z = 0, int w = 0) { seti(v.x, v.y, z, w); }
    void set(const ivec4 &v) { seti(v.x, v.y, v.z, v.w); }
    void setv(const int *i, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniform1iv_(b->loc, n, i); }
    void setv(const ivec *v, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniform3iv_(b->loc, n, v->v); }
    void setv(const ivec2 *v, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniform2iv_(b->loc, n, v->v); }
    void setv(const ivec4 *v, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniform4iv_(b->loc, n, v->v); }

    void setu(uint x = 0, uint y = 0, uint z = 0, uint w = 0) { sett<uint>(x, y, z, w); }
    void setv(const uint *u, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniform1uiv_(b->loc, n, u); }
};

#define LOCALPARAM(name, vals) do { static LocalShaderParam param( #name ); param.set(vals); } while(0)
#define LOCALPARAMF(name, ...) do { static LocalShaderParam param( #name ); param.setf(__VA_ARGS__); } while(0)
#define LOCALPARAMI(name, ...) do { static LocalShaderParam param( #name ); param.seti(__VA_ARGS__); } while(0)
#define LOCALPARAMV(name, vals, num) do { static LocalShaderParam param( #name ); param.setv(vals, num); } while(0)
#define GLOBALPARAM(name, vals) do { static GlobalShaderParam param( #name ); param.set(vals); } while(0)
#define GLOBALPARAMF(name, ...) do { static GlobalShaderParam param( #name ); param.setf(__VA_ARGS__); } while(0)
#define GLOBALPARAMI(name, ...) do { static GlobalShaderParam param( #name ); param.seti(__VA_ARGS__); } while(0)
#define GLOBALPARAMV(name, vals, num) do { static GlobalShaderParam param( #name ); param.setv(vals, num); } while(0)

#define SETSHADER(name, ...) \
    do { \
        static Shader *name##shader = NULL; \
        if(!name##shader) name##shader = lookupshaderbyname(#name); \
        name##shader->set(__VA_ARGS__); \
    } while(0)
#define SETVARIANT(name, ...) \
    do { \
        static Shader *name##shader = NULL; \
        if(!name##shader) name##shader = lookupshaderbyname(#name); \
        name##shader->setvariant(__VA_ARGS__); \
    } while(0)

struct ImageData
{
    int w, h, bpp, levels, align, pitch;
    GLenum compressed;
    uchar *data;
    void *owner;
    void (*freefunc)(void *);

    ImageData()
        : data(NULL), owner(NULL), freefunc(NULL)
    {}


    ImageData(int nw, int nh, int nbpp, int nlevels = 1, int nalign = 0, GLenum ncompressed = GL_FALSE)
    {
        setdata(NULL, nw, nh, nbpp, nlevels, nalign, ncompressed);
    }

    ImageData(int nw, int nh, int nbpp, uchar *data)
        : owner(NULL), freefunc(NULL)
    {
        setdata(data, nw, nh, nbpp);
    }

    ImageData(SDL_Surface *s) { wrap(s); }
    ~ImageData() { cleanup(); }

    void setdata(uchar *ndata, int nw, int nh, int nbpp, int nlevels = 1, int nalign = 0, GLenum ncompressed = GL_FALSE)
    {
        w = nw;
        h = nh;
        bpp = nbpp;
        levels = nlevels;
        align = nalign;
        pitch = align ? 0 : w*bpp;
        compressed = ncompressed;
        data = ndata ? ndata : new uchar[calcsize()];
        if(!ndata) { owner = this; freefunc = NULL; }
    }

    int calclevelsize(int level) const { return ((max(w>>level, 1)+align-1)/align)*((max(h>>level, 1)+align-1)/align)*bpp; }

    int calcsize() const
    {
        if(!align) return w*h*bpp;
        int lw = w, lh = h,
            size = 0;
        loopi(levels)
        {
            if(lw<=0) lw = 1;
            if(lh<=0) lh = 1;
            size += ((lw+align-1)/align)*((lh+align-1)/align)*bpp;
            if(lw*lh==1) break;
            lw >>= 1;
            lh >>= 1;
        }
        return size;
    }

    void disown()
    {
        data = NULL;
        owner = NULL;
        freefunc = NULL;
    }

    void cleanup()
    {
        if(owner==this) delete[] data;
        else if(freefunc) (*freefunc)(owner);
        disown();
    }

    void replace(ImageData &d)
    {
        cleanup();
        *this = d;
        if(owner == &d) owner = this;
        d.disown();
    }

    void wrap(SDL_Surface *s)
    {
        setdata((uchar *)s->pixels, s->w, s->h, s->format->BytesPerPixel);
        pitch = s->pitch;
        owner = s;
        freefunc = (void (*)(void *))SDL_FreeSurface;
    }
};

// management of texture slots
// each texture slot can have multiple texture frames, of which currently only the first is used
// additional frames can be used for various shaders

struct Texture
{
    enum
    {
        IMAGE      = 0,
        CUBEMAP    = 1,
        TYPE       = 0xFF,

        STUB       = 1<<8,
        TRANSIENT  = 1<<9,
        COMPRESSED = 1<<10,
        ALPHA      = 1<<11,
        MIRROR     = 1<<12,
        FLAGS      = 0xFF00
    };

    char *name;
    int type, w, h, xs, ys, bpp, clamp;
    bool mipmap, canreduce;
    GLuint id;
    uchar *alphamask;

    Texture() : alphamask(NULL) {}

    int swizzle() const { extern bool hasTRG, hasTSW; return hasTRG && !hasTSW ? (bpp==1 ? 0 : (bpp==2 ? 1 : -1)) : -1; }
};

#define SETSWIZZLE(name, tex) SETVARIANT(name, (tex) ? (tex)->swizzle() : -1, 0)
#define SETVARIANTSWIZZLE(name, tex, row) SETVARIANT(name, ((row) > 0 ? 1 : 0) + ((tex) ? (tex)->swizzle() : -1), row)

enum
{
    TEX_DIFFUSE = 0,
    TEX_NORMAL,
    TEX_GLOW,
    TEX_ENVMAP,

    TEX_SPEC,
    TEX_DEPTH,
    TEX_UNKNOWN,

    TEX_DETAIL = TEX_SPEC
};

enum
{
    VSLOT_SHPARAM = 0,
    VSLOT_SCALE,
    VSLOT_ROTATION,
    VSLOT_OFFSET,
    VSLOT_SCROLL,
    VSLOT_LAYER,
    VSLOT_ALPHA,
    VSLOT_COLOR,
    VSLOT_RESERVED, // used by RE
    VSLOT_REFRACT,
    VSLOT_DETAIL,
    VSLOT_NUM
};

struct VSlot
{
    Slot *slot;
    VSlot *next;
    int index, changed;
    vector<SlotShaderParam> params;
    bool linked;
    float scale;
    int rotation;
    ivec2 offset;
    vec2 scroll;
    int layer, detail;
    float alphafront, alphaback;
    vec colorscale;
    vec glowcolor;
    float refractscale;
    vec refractcolor;

    VSlot(Slot *slot = NULL, int index = -1) : slot(slot), next(NULL), index(index), changed(0)
    {
        reset();
        if(slot) addvariant(slot);
    }

    void addvariant(Slot *slot);

    void reset()
    {
        params.setsize(0);
        linked = false;
        scale = 1;
        rotation = 0;
        offset = ivec2(0, 0);
        scroll = vec2(0, 0);
        layer = detail = 0;
        alphafront = 0.5f;
        alphaback = 0;
        colorscale = vec(1, 1, 1);
        glowcolor = vec(1, 1, 1);
        refractscale = 0;
        refractcolor = vec(1, 1, 1);
    }

    void cleanup()
    {
        linked = false;
    }

    bool isdynamic() const;
};

struct Slot
{
    enum { OCTA, MATERIAL, DECAL };

    struct Tex
    {
        int type;
        Texture *t;
        string name;
        int combined;

        Tex() : t(NULL), combined(-1) {}
    };

    int index, smooth, texw, texh;
    vector<Tex> sts;
    Shader *shader;
    vector<SlotShaderParam> params;
    VSlot *variants;
    bool loaded;
    uint texmask;
    char *grass;
    const char *group; /* OF */
    Texture *grasstex, *thumbnail;

    Slot(int index = -1) : index(index), variants(NULL), grass(NULL), group(NULL) { reset(); }
    virtual ~Slot() {}

    virtual int type() const { return OCTA; }
    virtual const char *name() const;
    virtual const char *texturedir() const { return "media/texture"; }

    virtual VSlot &emptyvslot();

    virtual int cancombine(int type) const;

    int findtextype(int type, int last = -1) const;

    void load(int index, Slot::Tex &t);
    void load();

    Texture *loadthumbnail();

    void reset()
    {
        smooth = -1;
        sts.setsize(0);
        shader = NULL;
        params.setsize(0);
        loaded = false;
        texmask = 0;
        DELETEA(grass);
        group = NULL;
        grasstex = NULL;
        thumbnail = NULL;
        texw = texh = 0;
    }

    void cleanup()
    {
        loaded = false;
        grasstex = NULL;
        thumbnail = NULL;
        loopv(sts)
        {
            Tex &t = sts[i];
            t.t = NULL;
            t.combined = -1;
        }
    }
};

inline void VSlot::addvariant(Slot *slot)
{
    if(!slot->variants) slot->variants = this;
    else
    {
        VSlot *prev = slot->variants;
        while(prev->next) prev = prev->next;
        prev->next = this;
    }
}

inline bool VSlot::isdynamic() const
{
    return !scroll.iszero() || slot->shader->isdynamic();
}

struct MatSlot : Slot, VSlot
{
    MatSlot();

    int type() const { return MATERIAL; }
    const char *name() const;

    VSlot &emptyvslot() { return *this; }

    int cancombine(int type) const { return -1; }

    void reset()
    {
        Slot::reset();
        VSlot::reset();
    }

    void cleanup()
    {
        Slot::cleanup();
        VSlot::cleanup();
    }
};

struct DecalSlot : Slot, VSlot
{
    float depth, fade;

    DecalSlot(int index = -1) : Slot(index), VSlot(this), depth(1), fade(0.5f) {}

    int type() const { return DECAL; }
    const char *name() const;
    const char *texturedir() const { return "media/decal"; }

    VSlot &emptyvslot() { return *this; }

    int cancombine(int type) const;

    void reset()
    {
        Slot::reset();
        VSlot::reset();
        depth = 1;
        fade = 0.5f;
    }

    void cleanup()
    {
        Slot::cleanup();
        VSlot::cleanup();
    }
};

struct cubemapside
{
    GLenum target;
    const char *name;
    bool flipx, flipy, swapxy;
};

extern const cubemapside cubemapsides[6];
extern Texture *notexture;
extern Shader *nullshader, *hudshader, *hudtextshader, *hudnotextureshader, *nocolorshader, *foggedshader, *foggednotextureshader, *ldrshader, *ldrnotextureshader, *stdworldshader;
extern int maxvsuniforms, maxfsuniforms;

extern Shader *lookupshaderbyname(const char *name);
extern Shader *useshaderbyname(const char *name);
extern Shader *generateshader(const char *name, const char *cmd, ...);
extern void resetslotshader();
extern void setslotshader(Slot &s);
extern void linkslotshader(Slot &s, bool load = true);
extern void linkvslotshader(VSlot &s, bool load = true);
extern void linkslotshaders();
extern const char *getshaderparamname(const char *name, bool insert = true);
extern bool shouldreuseparams(Slot &s, VSlot &p);
extern void setupshaders();
extern void reloadshaders();
extern void cleanupshaders();

/* OF */
extern void setshader(const char *name);
extern void saveslotshader(Shader *&shader, vector<SlotShaderParam> &params);
extern void restoreslotshader(Shader *shader, vector<SlotShaderParam> &params);

#define MAXBLURRADIUS 7
extern float blursigma;
extern void setupblurkernel(int radius, float *weights, float *offsets);
extern void setblurshader(int pass, int size, int radius, float *weights, float *offsets, GLenum target = GL_TEXTURE_2D);

extern void savepng(const char *filename, ImageData &image, bool flip = false);
extern void savetga(const char *filename, ImageData &image, bool flip = false);
extern bool loaddds(const char *filename, ImageData &image, int force = 0);
extern bool loadimage(const char *filename, ImageData &image);

extern MatSlot &lookupmaterialslot(int slot, bool load = true);
extern Slot &lookupslot(int slot, bool load = true);
extern VSlot &lookupvslot(int slot, bool load = true);
extern DecalSlot &lookupdecalslot(int slot, bool load = true);
extern VSlot *findvslot(Slot &slot, const VSlot &src, const VSlot &delta);
extern VSlot *editvslot(const VSlot &src, const VSlot &delta);
extern void mergevslot(VSlot &dst, const VSlot &src, const VSlot &delta);
extern void packvslot(vector<uchar> &buf, const VSlot &src);
extern bool unpackvslot(ucharbuf &buf, VSlot &dst, bool delta);

extern Slot dummyslot;
extern VSlot dummyvslot;
extern vector<Slot *> slots;
extern vector<VSlot *> vslots;

