/*=============================================================================
    Name    : glcompat.c
    Purpose : GL compatibility layer - for fast GL frontends, DrawPixels -> texture, &c

    Created 7/14/1999 by khent
    Copyright Relic Entertainment, Inc.  All rights reserved.
=============================================================================*/

#include <stdlib.h>
#include "glinc.h"
#include "main.h"
#include "render.h"
#include "texreg.h"
#include "memory.h"
#include "glcompat.h"
#include "gldll.h"
#include "debug.h"
#include "mouse.h"
#include "glcaps.h"
#include "FEColour.h"
#include "shipview.h"
#include "launchmgr.h"
#include "consmgr.h"
#include "researchgui.h"
#include "scenpick.h"
#include "randy.h"


//magic 2D coordinate adjustment factor
#define GLC_MAGIC 0.05f

#ifdef khentschel
#define VISIBLE_TEXTURES 0
#define VISIBLE_EXTENTS  0
#define GLC_FULLSCREEN   0
#else
#define VISIBLE_TEXTURES 0
#define VISIBLE_EXTENTS  0
#define GLC_FULLSCREEN   0
#endif

#define GLC_MAX_BUFFERS 3

#define TRANSFORM_POINT(Q, M, P) \
    Q[0] = M[0] * P[0] + M[4] * P[1] + M[8]  * P[2] + M[12] * P[3]; \
    Q[1] = M[1] * P[0] + M[5] * P[1] + M[9]  * P[2] + M[13] * P[3]; \
    Q[2] = M[2] * P[0] + M[6] * P[1] + M[10] * P[2] + M[14] * P[3]; \
    Q[3] = M[3] * P[0] + M[7] * P[1] + M[11] * P[2] + M[15] * P[3];

#if VISIBLE_EXTENTS
static bool int3 = FALSE;
#endif

bool glcLinear = FALSE;                 //whether to use bilinear filtering for quilts

//dimensions of the scratch buffer
static sdword _width, _height, _pitch;
static sdword _heightMinusOne;
static sdword xDiff, yDiff;             //offsets from main window dimensions

static sdword glcForceUpdates;          //number of buffers to update completely
static bool glcXReuse;
static bool glcLaggy;                   //TRUE if the fast front end is laggy

static sdword glcMaxHandles, glcHighestHandle;
static GLuint* handle;                  //handles for GL texture objects

static sdword glcNumBuffers;
static sdword glcActiveBuffer;

//dirty extents
static rectangle glcRect[GLC_MAX_BUFFERS];
//last mouse rectangle
static rectangle glcMouse[GLC_MAX_BUFFERS];

//render entire screen n times
static sdword glcForceUpdate[GLC_MAX_BUFFERS];
static bool glcDrawingMouse;            //don't update dirty extents if TRUE

static ubyte* glTex = NULL;             //to contain a single wedge of the quilt
static ubyte* glScratch = NULL;         //our scratch framebuffer
static sdword* glcScrMult = NULL;       //scratch buffer precomputed y pitch

static bool glcActiveStatus = FALSE;    //whether glcompat is active or not
static bool glcFullscreenStatus = FALSE; //whether we're running a fullscreen in-game gui

//the GL's function pointers
static DRAWPIXELSproc _glcDrawPixels;
static RASTERPOS2Fproc _glcRasterPos2f;
static COLOR3UBproc _glcColor3ub;
static CLEARproc _glcClear;
static ENABLEproc _glcEnable;
static DISABLEproc _glcDisable;

static sdword glcRasterPos[2];          //2D raster position
static sdword glcColor[3];              //current color

static GLuint glcBackgroundHandle = 0;  //texture object for background blue wedge
static GLuint glcFullscreenHandle = 0;  //texture object for in-game background wedge (black)

//state of GL caps
static bool glcBlend, glcAlphaTest, glcScissorTest;

#if VISIBLE_TEXTURES
#define GLC_RANTABLE_SIZE 16384
static bool8  glcRanTable[GLC_RANTABLE_SIZE];
static sdword glcRanIndex;
#endif


/*-----------------------------------------------------------------------------
    Name        : glcStartup
    Description : startup the glcompat module, allocating glTex memory
    Inputs      :
    Outputs     :
    Return      : TRUE or FALSE (success or failure)
----------------------------------------------------------------------------*/
bool glcStartup(void)
{
    sdword i;

#if VISIBLE_TEXTURES
    for (i = 0; i < GLC_RANTABLE_SIZE; i++)
    {
        glcRanTable[i] = ranRandom(RAN_ParticleStream) & 0xFF;
    }
    glcRanIndex = 0;
#endif

    glcNumBuffers = glCapNumBuffers();
    glcForceUpdates = glcNumBuffers;

    if (glTex != NULL)
    {
        memFree(glTex);
    }
    glTex = (ubyte*)memAlloc(4*64*64, "glTex", NonVolatile);

    glcLoadTextures();

    for (i = 0; i < GLC_MAX_BUFFERS; i++)
    {
        glcForceUpdate[i] = glcForceUpdates;
    }

    return TRUE;
}

/*-----------------------------------------------------------------------------
    Name        : glcShutdown
    Description : shutdown the glcompat module, freeing glTex and scratch buffer memory
    Inputs      :
    Outputs     :
    Return      : TRUE or FALSE (success or failure)
----------------------------------------------------------------------------*/
bool glcShutdown(void)
{
    if (glcActive())
    {
        (void)glcActivate(FALSE);
    }

    glcFreeTextures();

    if (glTex != NULL)
    {
        memFree(glTex);
        glTex = NULL;
    }

    if (handle != NULL)
    {
        memFree(handle);
        handle = NULL;
    }
    glcMaxHandles = 0;

    return glcAllocateScratch(FALSE);
}

/*-----------------------------------------------------------------------------
    Name        : glcLoadTextures
    Description : generate necessary texture handles and contents
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void glcLoadTextures(void)
{
    ubyte  backgroundData[3*4*4];
    sdword i;

    //free textures first
    glcFreeTextures();

    //create texture objects for quilting
    if (handle != NULL)
    {
        memFree(handle);
    }
    glcXReuse = !glNT;
    if (glcXReuse)
    {
        glcMaxHandles = 2 * (MAIN_WindowWidth / 32);
    }
    else
    {
        glcMaxHandles = ((MAIN_WindowWidth / 64) * (MAIN_WindowHeight / 64)) + 2 * (MAIN_WindowWidth / 64);
    }
    handle = (GLuint*)memAlloc(glcMaxHandles * sizeof(GLuint), "glcompat handles", NonVolatile);
    memset(handle, 0, glcMaxHandles * sizeof(GLuint));
    glGenTextures(glcMaxHandles, handle);

    //generate blue background wedge
    trClearCurrent();
    glGenTextures(1, &glcBackgroundHandle);
    glBindTexture(GL_TEXTURE_2D, glcBackgroundHandle);
    for (i = 0; i < 3*4*4; i += 3)
    {
        backgroundData[i + 0] = colRed(FEC_Background);
        backgroundData[i + 1] = colGreen(FEC_Background);
        backgroundData[i + 2] = colBlue(FEC_Background);
    }
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
                 4, 4,
                 0, GL_RGB, GL_UNSIGNED_BYTE,
                 backgroundData);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

    //generate black background wedge
    glGenTextures(1, &glcFullscreenHandle);
    glBindTexture(GL_TEXTURE_2D, glcFullscreenHandle);
    memset(backgroundData, 0, 3*4*4);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
                 4, 4,
                 0, GL_RGB, GL_UNSIGNED_BYTE,
                 backgroundData);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
}

/*-----------------------------------------------------------------------------
    Name        : glcFreeTextures
    Description : free generated texture objects
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void glcFreeTextures(void)
{
    sdword i;

    //free quilt texture objects
    for (i = 0; i < glcMaxHandles; i++)
    {
        if (handle[i] != 0)
        {
            glDeleteTextures(1, &handle[i]);
            handle[i] = 0;
        }
    }

    //free background texture objects
    if (glcBackgroundHandle != 0)
    {
        glDeleteTextures(1, &glcBackgroundHandle);
        glcBackgroundHandle = 0;
    }
    if (glcFullscreenHandle != 0)
    {
        glDeleteTextures(1, &glcFullscreenHandle);
        glcFullscreenHandle = 0;
    }
}

/*-----------------------------------------------------------------------------
    Name        : glcAllocateScratch
    Description : allocate the GL scratch buffer (RGBA, 640x480)
    Inputs      : allocate - TRUE or FALSE (allocate or deallocate)
    Outputs     :
    Return      : TRUE or FALSE (success or failure)
----------------------------------------------------------------------------*/
bool glcAllocateScratch(bool allocate)
{
    sdword y;

    if (allocate)
    {
        _width  = MAIN_WindowWidth;
        _height = MAIN_WindowHeight;
        _pitch  = 4*_width;
        _heightMinusOne = _height - 1;

        xDiff = 0;
        yDiff = 0;

        if (glScratch != NULL)
        {
            free(glScratch);
        }
        if (glcScrMult != NULL)
        {
            memFree(glcScrMult);
        }

        glScratch = (ubyte*)malloc(_pitch * _height);

        glcScrMult = (sdword*)memAlloc(sizeof(sdword) * _height, "glcScrMult", NonVolatile);
        for (y = 0; y < _height; y++)
        {
            glcScrMult[y] = _pitch * y;
        }
    }
    else
    {
        if (glScratch != NULL)
        {
            free(glScratch);
            glScratch = NULL;
        }
        if (glcScrMult != NULL)
        {
            memFree(glcScrMult);
            glcScrMult = NULL;
        }
    }

    return TRUE;
}

/*-----------------------------------------------------------------------------
    Name        : glcGetScratch
    Description : return the scratch buffer & pitch, if it exists
    Inputs      : pitch - storage for pitch, or NULL if you don't care
    Outputs     :
    Return      : glScratch
----------------------------------------------------------------------------*/
ubyte* glcGetScratch(sdword* pitch)
{
    if (pitch != NULL)
    {
        if (glScratch == NULL)
        {
            *pitch = 0;
        }
        else
        {
            *pitch = _pitch;
        }
    }
    return glScratch;
}

/*-----------------------------------------------------------------------------
    Name        : glcGetScratchMaybeAllocate
    Description : will allocate the scratch buffer if not already so, then
                  return glcGetScratch
    Inputs      : pitch - see glcGetScratch
    Outputs     :
    Return      : see glcGetScratch
----------------------------------------------------------------------------*/
ubyte* glcGetScratchMaybeAllocate(sdword* pitch)
{
    if (glScratch == NULL)
    {
        if (!glcAllocateScratch(TRUE))
        {
            if (pitch != NULL)
            {
                *pitch = 0;
            }
            return NULL;
        }
    }
    return glcGetScratch(pitch);
}

/*-----------------------------------------------------------------------------
    Name        : glcNextBuffer
    Description : return the incremented buffer index (for tracking page flips)
    Inputs      :
    Outputs     :
    Return      : next buffer index
----------------------------------------------------------------------------*/
sdword glcNextBuffer(void)
{
    return ((glcActiveBuffer + 1) % glcNumBuffers);
}

/*-----------------------------------------------------------------------------
    Name        : glcPrevBuffer
    Description : return the decremented buffer index (for tracking page flips)
    Inputs      : numPrevs - amount to decrement buffer index by
    Outputs     :
    Return      : previous buffer index
----------------------------------------------------------------------------*/
sdword glcPrevBuffer(sdword numPrevs)
{
    sdword index, buffer;

    buffer = glcActiveBuffer;
    for (index = 0; index < numPrevs; index++)
    {
        if (buffer == 0)
        {
            buffer = glcNumBuffers - 1;
        }
        else
        {
            buffer--;
        }
    }
    return buffer;
}

/*-----------------------------------------------------------------------------
    Name        : glcUpdateExtents
    Description : update dirty rectangle extents
    Inputs      : x0, x1, y0, y1 - rectangle coordinates
    Outputs     : glcRect may be modified
    Return      :
----------------------------------------------------------------------------*/
static void glcUpdateExtents(sdword x0, sdword x1, sdword y0, sdword y1)
{
    rectangle* rect;
    sdword temp;

    if (glcDrawingMouse)
    {
        return;
    }

    rect = &glcRect[glcActiveBuffer];

    if (x0 + x1 + y0 + y1 == -4)
    {
        rect->x0 = -1;
        rect->x1 = -1;
        rect->y0 = -1;
        rect->y1 = -1;
    }
    else
    {
        x0 -= 1;
        x1 += 2;
        if (y0 < y1)
        {
            y0 -= 1;
            y1 += 2;
        }
        else
        {
            y1 -= 1;
            y0 += 2;
        }

        if (x0 < 0) x0 = 0;
        if (x1 >= _width) x1 = _width - 1;
        if (y0 < 0) y0 = 0;
        if (y1 >= _height) y1 = _height - 1;

        y0 = _heightMinusOne - y0;
        y1 = _heightMinusOne - y1;
        temp = y0;
        y0 = y1;
        y1 = temp;

        if (rect->x0 == -1)
        {
            rect->x0 = x0;
        }
        else if (x0 < rect->x0)
        {
            rect->x0 = x0;
        }

        if (rect->x1 == -1)
        {
            rect->x1 = x1;
        }
        else if (x1 > rect->x1)
        {
            rect->x1 = x1;
        }

        if (rect->y0 == -1)
        {
            rect->y0 = y0;
        }
        else if (y0 < rect->y0)
        {
            rect->y0 = y0;
        }

        if (rect->y1 == -1)
        {
            rect->y1 = y1;
        }
        else if (y1 > rect->y1)
        {
            rect->y1 = y1;
        }
    }
#if VISIBLE_EXTENTS
    if ((rect->x1 - rect->x0) > 640 ||
        (rect->y1 - rect->y0) > 480)
    {
        if (int3)
        {
            __asm int 3 ;
        }
    }
#endif
}

/*-----------------------------------------------------------------------------
    Name        : glcCreateTexture
    Description : create a subtexture of source RGBA 640x480 screen
    Inputs      : buf - source RGBA image
                  xofs, yofs - offset inside the source image
                  xend, yend - extents of the source image to create texture from
    Outputs     : currently bound texture is updated
    Return      :
----------------------------------------------------------------------------*/
static void glcCreateTexture(ubyte* buf, sdword xofs, sdword yofs, sdword xend, sdword yend)
{
    sdword y, x;
    sdword swidth, sheight;
    ubyte* ptex;
    ubyte* pbuf;
    ubyte  r, g, b;

    if (xend < 0)
    {
        xend = -xend;
        swidth  = 640;
        sheight = 480;
    }
    else
    {
        swidth  = _width;
        sheight = _height;
    }

    r = 0;//colRed(FEC_Background);
    g = 0;//colGreen(FEC_Background);
    b = 0;//colBlue(FEC_Background);

    for (y = 0; y < yend; y++)
    {
        if (y+yofs < 0 || y+yofs >= sheight)
        {
            if (RGLtype == D3Dtype)
            {
                //Direct3D optimization - avoid redundant byte shuffling
                ptex = glTex + 4*xend*y;
                for (x = 0; x < xend; x++)
                {
                    ptex[4*x + 0] = r;
                    ptex[4*x + 1] = g;
                    ptex[4*x + 2] = b;
                    ptex[4*x + 3] = 0;
                }
            }
            else
            {
                ptex = glTex + 3*xend*y;
                for (x = 0; x < xend; x++, ptex += 3)
                {
                    ptex[0] = r;
                    ptex[1] = g;
                    ptex[2] = b;
                }
            }
            continue;
        }
        pbuf = buf + 4*swidth*(y+yofs) + 4*xofs;
        if (RGLtype == D3Dtype)
        {
            //Direct3D optimization - avoid redundant byte shuffling
            ptex = glTex + 4*xend*y;
            for (x = 0; x < xend; x++)
            {
                if (xofs+x < 0 || xofs+x >= swidth)
                {
                    ptex[4*x + 0] = r;
                    ptex[4*x + 1] = g;
                    ptex[4*x + 2] = b;
                    ptex[4*x + 3] = 0;
                    continue;
                }
#if VISIBLE_TEXTURES
                if (glcRanTable[(glcRanIndex++) & (GLC_RANTABLE_SIZE - 1)])
#endif
                {
                    ptex[4*x + 0] = pbuf[4*x + 0];
                    ptex[4*x + 1] = pbuf[4*x + 1];
                    ptex[4*x + 2] = pbuf[4*x + 2];
                    ptex[4*x + 3] = 255;
                }
#if VISIBLE_TEXTURES
                else
                {
                    ptex[4*x + 0] = 127;
                    ptex[4*x + 1] = 127;
                    ptex[4*x + 2] = 127;
                    ptex[4*x + 3] = 255;
                }
#endif
            }
        }
        else
        {
            ptex = glTex + 3*xend*y;
            for (x = 0; x < xend; x++, ptex += 3)
            {
                if (xofs+x < 0 || xofs+x >= swidth)
                {
                    ptex[0] = r;
                    ptex[1] = g;
                    ptex[2] = b;
                    continue;
                }
#if VISIBLE_TEXTURES
                if (glcRanTable[(glcRanIndex++) & (GLC_RANTABLE_SIZE - 1)])
#endif
                {
                    ptex[0] = pbuf[4*x + 0];
                    ptex[1] = pbuf[4*x + 1];
                    ptex[2] = pbuf[4*x + 2];
                }
#if VISIBLE_TEXTURES
                else
                {
                    ptex[0] = 127;
                    ptex[1] = 127;
                    ptex[2] = 127;
                }
#endif
            }
        }
    }

    if (RGLtype == D3Dtype)
    {
        //avoid redundant byte shuffling w/ source format of RGBA
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
                     xend, xend,
                     0, GL_RGBA, GL_UNSIGNED_BYTE,
                     glTex);
    }
    else
    {
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
                     xend, xend,
                     0, GL_RGB, GL_UNSIGNED_BYTE,
                     glTex);
    }

    if (glcLinear)
    {
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    }
    else
    {
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    }
}

/*-----------------------------------------------------------------------------
    Name        : glcQuadAt
    Description : displays a textured quad at given location
    Inputs      : x, y - position for display
                  xend, yend - subtexture dimensions
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
static void glcQuadAt(sdword x, sdword y, sdword xend, sdword yend)
{
#define VERT(S,T,X,Y) \
    glTexCoord2f((real32)(S),(real32)(T));\
    glVertex2f((real32)(X) + GLC_MAGIC,(real32)(Y) + GLC_MAGIC);

    real32 xfrac, yfrac;

    xfrac = (real32)xend / (real32)xend;
    yfrac = (real32)yend / (real32)xend;

    glBegin(GL_QUADS);
    VERT(0, yfrac, x, y);
    VERT(xfrac, yfrac, x + xend, y);
    VERT(xfrac, 0, x + xend, y + yend);
    VERT(0, 0, x, y + yend);
    glEnd();
#undef VERT
}

/*-----------------------------------------------------------------------------
    Name        : glcScaledQuadAt
    Description : displays a textured quad at given location, scaled to simulate
                  640x480 in any display mode
    Inputs      : x, y - position for display
                  xend, yend - subtexture dimensions
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
static void glcScaledQuadAt(sdword x, sdword y, sdword xend, sdword yend)
{
#define VERT(S,T,X,Y) \
    glTexCoord2f((real32)(S),(real32)(T));\
    glVertex2f(((real32)(X))*xscale,((real32)(Y))*yscale);

    real32 xfrac, yfrac;
    real32 xscale, yscale;

    xfrac = (real32)xend / (real32)xend;
    yfrac = (real32)yend / (real32)xend;

    xscale = (real32)MAIN_WindowWidth  / 640.0f;
    yscale = (real32)MAIN_WindowHeight / 480.0f;
 
    glBegin(GL_QUADS);
    VERT(0, yfrac, x, y);
    VERT(xfrac, yfrac, x + xend, y);
    VERT(xfrac, 0, x + xend, y + yend);
    VERT(0, 0, x, y + yend);
    glEnd();
#undef VERT
}

/*-----------------------------------------------------------------------------
    Name        : glcDisplayRGBABackgroundScaled
    Description : display a RGBA 640x480 image as a series of quilted textures,
                  scaled to current display resolution
    Inputs      : surface - source image
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void glcDisplayRGBABackgroundScaled(ubyte* surface)
{
    sdword handles;
    sdword y, x, yend;

    rndTextureEnvironment(RTE_Replace);
    rndTextureEnable(TRUE);
    rndLightingEnable(FALSE);
    glDisable(GL_DEPTH_TEST);
    glColor4ub(255,255,255,255);

    trClearCurrent();

    if (!glcXReuse)
    {
        handles = 0;
    }

    for (y = 0; y < 480; y += 64)
    {
        if (glcXReuse)
        {
            handles = 0;
        }
        yend = (y+64 > 480) ? 32 : 64;
        for (x = 0; x < 640; x += 64)
        {
            glBindTexture(GL_TEXTURE_2D, handle[handles]);
            handles++;
            dbgAssert(handles < glcMaxHandles);
            glcCreateTexture(surface, x, y, -64, yend);
            glcScaledQuadAt(x, 479 - y + (64 - yend) - 64, 64, yend);
        }
        Sleep(0);
    }

    rndTextureEnable(FALSE);

    if (handles > glcHighestHandle)
    {
        glcHighestHandle = handles;
    }
}

/*-----------------------------------------------------------------------------
    Name        : glcDisplayRGBABackgroundWithoutScaling
    Description : display a RGBA 640x480 image as a series of quilted textures
    Inputs      : surface - source image
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void glcDisplayRGBABackgroundWithoutScaling(ubyte* surface)
{
    sdword handles;
    sdword y, x, yend;
    sdword xOfs, yOfs;

    xOfs = (MAIN_WindowWidth  - 640) / 2;
    yOfs = (MAIN_WindowHeight - 480) / 2;

    rndTextureEnvironment(RTE_Replace);
    rndTextureEnable(TRUE);
    rndLightingEnable(FALSE);
    glDisable(GL_DEPTH_TEST);
    glColor4ub(255,255,255,255);

    trClearCurrent();

    if (!glcXReuse)
    {
        handles = 0;
    }

    for (y = 0; y < 480; y += 64)
    {
        if (glcXReuse)
        {
            handles = 0;
        }
        yend = (y+64 > 480) ? 32 : 64;
        for (x = 0; x < 640; x += 64)
        {
            glBindTexture(GL_TEXTURE_2D, handle[handles]);
            handles++;
            dbgAssert(handles < glcMaxHandles);
            glcCreateTexture(surface, x, y, -64, yend);
            glcQuadAt(xOfs + x, yOfs + 479 - y + (64 - yend) - 64, 64, yend);
        }
        Sleep(0);
    }

    rndTextureEnable(FALSE);

    if (handles > glcHighestHandle)
    {
        glcHighestHandle = handles;
    }
}

/*-----------------------------------------------------------------------------
    Name        : glcMatrixSetup
    Description : setup a 2D orthographic projection
    Inputs      : on - TRUE or FALSE, setup or takedown
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void glcMatrixSetup(bool on)
{
    static bool cull;
    static GLint matrixMode;
    static GLfloat projection[16];

    if (on)
    {
        cull = (bool)glIsEnabled(GL_CULL_FACE);
        if (cull)
        {
            glDisable(GL_CULL_FACE);
        }

        glGetIntegerv(GL_MATRIX_MODE, &matrixMode);
        glGetFloatv(GL_PROJECTION_MATRIX, projection);

        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        rgluOrtho2D(0.0, (GLdouble)MAIN_WindowWidth, (GLdouble)MAIN_WindowHeight, 0.0);

        glMatrixMode(GL_MODELVIEW);
        glPushMatrix();
        glLoadIdentity();
    }
    else
    {
        glMatrixMode(GL_MODELVIEW);
        glPopMatrix();

        glMatrixMode(GL_PROJECTION);
        glLoadMatrixf(projection);

        glMatrixMode(matrixMode);

        if (cull)
        {
            glEnable(GL_CULL_FACE);
        }
    }
}

/*-----------------------------------------------------------------------------
    Name        : glcDisplayRGBABackground
    Description : display a RGBA 640x480 image as a series of quilted textures
    Inputs      : surface - source image
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void glcDisplayRGBABackground(ubyte* surface)
{
    sdword handles;
    sdword xOfs, yOfs;
    sdword y, x, yend;

    xOfs = xDiff;
    yOfs = yDiff;

    glcMatrixSetup(TRUE);

    rndTextureEnvironment(RTE_Replace);
    rndTextureEnable(TRUE);
    rndLightingEnable(FALSE);
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_BLEND);
    glColor4ub(255,255,255,255);

    trClearCurrent();

    if (!glcXReuse)
    {
        handles = 0;
    }

    for (y = 0; y < _height; y += 64)
    {
        if (glcXReuse)
        {
            handles = 0;
        }
        yend = (y+64 > _height) ? ((y+64) - _height) : 64;
        for (x = 0; x < _width; x += 64)
        {
            glBindTexture(GL_TEXTURE_2D, handle[handles]);
            handles++;
            dbgAssert(handles < glcMaxHandles);
            glcCreateTexture(surface, x, y, 64, yend);
            glcQuadAt(xOfs + x, yOfs + _heightMinusOne - y + (64 - yend) - 64, 64, yend);
        }
    }

    rndTextureEnable(FALSE);

    glcMatrixSetup(FALSE);

    if (handles > glcHighestHandle)
    {
        glcHighestHandle = handles;
    }
}

static void glcWedgeAt(sdword x, sdword y, sdword width, sdword height)
{
#define VERT(S,T,X,Y) \
    glTexCoord2f((real32)(S),(real32)(T));\
    glVertex2f((real32)(X),(real32)(Y));

    glBegin(GL_QUADS);
    VERT(0, 0, x, y);
    VERT(1, 0, x + width, y);
    VERT(1, 1, x + width, y + height);
    VERT(0, 1, x, y + height);
    glEnd();
#undef VERT
}

/*-----------------------------------------------------------------------------
    Name        : glcDisplayBackground
    Description : "clear" the screen to "blue" or "black"
    Inputs      : wedges - whether to cover only outer bounds of screen or not
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
static void glcDisplayBackground(bool wedges)
{
    bool blue;

    if (glScratch[0] == colRed(FEC_Background) &&
        glScratch[1] == colGreen(FEC_Background) &&
        glScratch[2] == colBlue(FEC_Background))
    {
        blue = TRUE;
    }
    else
    {
        blue = FALSE;
    }

    glcMatrixSetup(TRUE);

    rndTextureEnvironment(RTE_Replace);
    rndTextureEnable(TRUE);
    rndLightingEnable(FALSE);
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_BLEND);
    glColor4ub(255,255,255,255);

    trClearCurrent();

    if (wedges)
    {
        glBindTexture(GL_TEXTURE_2D, glcFullscreenHandle);
        glcWedgeAt(-1, -1, MAIN_WindowWidth + 1, 3);
        glcWedgeAt(MAIN_WindowWidth - 1, -1, 3, MAIN_WindowHeight + 1);
        glcWedgeAt(-1, MAIN_WindowHeight - 1, MAIN_WindowWidth + 1, 3);
        glcWedgeAt(-1, -1, 3, MAIN_WindowHeight + 1);
    }
    else
    {
        glBindTexture(GL_TEXTURE_2D, blue ? glcBackgroundHandle : glcFullscreenHandle);
        //draw a single big-ass quad covering the whole screen
        glcWedgeAt(-1, -1, MAIN_WindowWidth + 1, MAIN_WindowHeight + 1);
    }

    rndTextureEnable(FALSE);

    glcMatrixSetup(FALSE);
}

/*-----------------------------------------------------------------------------
    Name        : glcDisplayRGBABackgroundPortion
    Description : ...
    Inputs      :
    Outputs     :
    Return      : TRUE or FALSE (could or couldn't display the portion)
----------------------------------------------------------------------------*/
bool glcDisplayRGBABackgroundPortion(ubyte* surface, rectangle* rect, bool mouse)
{
    sdword handles;
    sdword xOfs, yOfs;
    sdword y, x, iy;

    if (mouse)
    {
        mouse = FALSE;
    }

    handles = 0;
    if (glcXReuse)
    {
        for (x = rect->x0; x < rect->x1; x += 32)
        {
            handles++;
        }
    }
    else
    {
        for (y = rect->y0; y < rect->y1; y += 32)
        {
            for (x = rect->x0; x < rect->x1; x += 32)
            {
                handles++;
            }
        }
    }
    if (handles >= glcMaxHandles)
    {
        return FALSE;
    }

    glcMatrixSetup(TRUE);

    xOfs = xDiff;
    yOfs = yDiff;

    rndTextureEnvironment(RTE_Replace);
    rndTextureEnable(TRUE);
    rndLightingEnable(FALSE);
    glDisable(GL_DEPTH_TEST);
    if (mouse)
    {
        rndAdditiveBlends(FALSE);
        glEnable(GL_BLEND);
    }
    else
    {
        glDisable(GL_BLEND);
    }
    glColor4ub(255,255,255,255);

    trClearCurrent();

    if (!glcXReuse)
    {
        handles = 0;
    }

    for (y = rect->y0; y < rect->y1; y += 32)
    {
        if (glcXReuse)
        {
            handles = 0;
        }
        iy = _heightMinusOne - y;
        for (x = rect->x0; x < rect->x1; x += 32)
        {
            glBindTexture(GL_TEXTURE_2D, handle[handles]);
            handles++;
            dbgAssert(handles < glcMaxHandles);
            glcCreateTexture(surface, x, iy-32, 32, 32);
            glcQuadAt(xOfs + x, yOfs + _heightMinusOne - iy, 32, 32);
        }
    }

    if (mouse)
    {
        glDisable(GL_BLEND);
    }
    rndTextureEnable(FALSE);

    glcMatrixSetup(FALSE);

    if (handles > glcHighestHandle)
    {
        glcHighestHandle = handles;
    }

    return TRUE;
}

/*-----------------------------------------------------------------------------
    Name        : glcMouseDraw
    Description : call mouseDraw after setting flags to not update dirty extents
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void glcMouseDraw(void)
{
#if 0
    glcDrawingMouse = TRUE;
    mouseDraw();
    glcDrawingMouse = FALSE;
#endif
}

/*-----------------------------------------------------------------------------
    Name        : glcPurgeHandles
    Description : ensure that the quilts are minimally affecting the GL's
                  texture cache management (create small textures)
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
static void glcPurgeHandles(void)
{
    sdword i;
    ubyte  data[3*2*2];

    memset(data, 0, 3*2*2);

    for (i = 0; i < glcHighestHandle; i++)
    {
        if (handle[i] != 0)
        {
            glBindTexture(GL_TEXTURE_2D, handle[i]);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
                         2, 2,
                         0, GL_RGB, GL_UNSIGNED_BYTE,
                         data);
        }
    }

    glcHighestHandle = 0;
}

/*-----------------------------------------------------------------------------
    Name        : glcGetDirect3DMouse
    Description : gets the mouse rectangle, adjusted for Direct3D-ness
    Inputs      : r - where to put the mouse rectangle
                  index - current framebuffer page
    Outputs     : r is filled
    Return      :
----------------------------------------------------------------------------*/
static void glcGetDirect3DMouse(rectangle* r, sdword index)
{
    sdword x0, x1;
    sdword y0, y1;
    sdword height;

    x0 = glcMouse[index].x0;
    x1 = glcMouse[index].x1;
    y0 = glcMouse[index].y0;
    y1 = glcMouse[index].y1;

    r->x0 = x0 - 2;
    r->x1 = x1 + 1;

    height = abs(y1 - y0);
    y1 += height;
    if (y0 < y1)
    {
        r->y0 = y0;
        r->y1 = y1;
    }
    else
    {
        r->y0 = y1;
        r->y1 = y0;
    }
}

/*-----------------------------------------------------------------------------
    Name        : glcPageFlipHelper
    Description : convenient fn to render subsidiary frontend elements if necessary
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
static void glcPageFlipHelper(void)
{
    //make the screen borders sane
    glcDisplayBackground(TRUE);

#if 0
    //render the shipview mesh
    if (_svRender)
    {
        svShipViewRender(NULL, NULL);
    }
#endif

    //render the mouse atop everything
    mouseDraw();
}

static void glcDrawPitchedPixels(sdword x0, sdword y0, sdword x1, sdword y1,
                                 sdword width, sdword height, sdword pitch,
                                 ubyte* data)
{
    if (RGLtype == D3Dtype)
    {
        rglDrawPitchedPixels(x0, y0, x1, y1, width, height, pitch, data);
    }
    else
    {
        sdword height = y1 - y0;
        y0 += height;
        glcMatrixSetup(TRUE);
        _glcRasterPos2f((GLfloat)x0, (GLfloat)y0);
        glPixelStorei(GL_UNPACK_ROW_LENGTH, _width);
        _glcDrawPixels(x1 - x0, height,
                       GL_RGBA, GL_UNSIGNED_BYTE,
                       data + pitch*(_heightMinusOne - y0) + 4*x0);
        glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
        glcMatrixSetup(FALSE);
    }
}

#if VISIBLE_EXTENTS
static void glcDisplayExtents(void)
{
    rectangle rect;
    sdword    width, height;

    rect = glcRect[glcActiveBuffer];
    width  = rect.x1 - rect.x0;
    height = rect.y1 - rect.y0;
    while (width & 31)
    {
        rect.x1++;
        width++;
    }
    if (height < 0)
    {
        height = -height;
        while (height & 31)
        {
            rect.y0++;
            height++;
        }
    }
    else
    {
        while (height & 31)
        {
            rect.y1++;
            height++;
        }
    }

    glcMatrixSetup(TRUE);
    glBegin(GL_LINE_LOOP);
    glVertex2f((real32)rect.x0, (real32)rect.y0);
    glVertex2f((real32)rect.x1, (real32)rect.y0);
    glVertex2f((real32)rect.x1, (real32)rect.y1);
    glVertex2f((real32)rect.x0, (real32)rect.y1);
    glEnd();
    glcMatrixSetup(FALSE);
}
#endif

/*-----------------------------------------------------------------------------
    Name        : glcPageFlip
    Description : displays the scratch buffer as a texture
    Inputs      : scaled - TRUE or FALSE, scale to current res or not
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void glcPageFlip(bool scaled)
{
    rectangle r;
    sdword i, index;

    if (scaled)
    {
        glcMatrixSetup(TRUE);
        glcDisplayRGBABackgroundScaled(glScratch);
        glcMatrixSetup(FALSE);
    }
    else if (RGLtype == D3Dtype)
    {
        for (i = 0; i < glcNumBuffers; i++)
        {
            index = glcPrevBuffer(i);
#if VISIBLE_EXTENTS
            if (1)
#else
            if (glcForceUpdate[index])
#endif
            {
                if (glcForceUpdate[index] > 0)
                {
                    glcForceUpdate[index]--;
                }
                glcDrawPitchedPixels(0, 0, _width, _height,
                                     _width, _height, _pitch,
                                     glScratch);
            }
            else if ((glcRect[index].x0 + glcRect[index].x1 +
                      glcRect[index].y0 + glcRect[index].y1) != -4)
            {
                r = glcRect[index];
                glcDrawPitchedPixels(r.x0, r.y0, r.x1, r.y1,
                                     _width, _height, _pitch,
                                     glScratch);
                glcGetDirect3DMouse(&r, index);
                glcDrawPitchedPixels(r.x0, r.y0, r.x1, r.y1,
                                     _width, _height, _pitch,
                                     glScratch);
            }
            else
            {
                glcGetDirect3DMouse(&r, index);
                glcDrawPitchedPixels(r.x0, r.y0, r.x1, r.y1,
                                     _width, _height, _pitch,
                                     glScratch);
            }
            glcPageFlipHelper();
        }
    }
    else
    {
        for (i = 0; i < glcNumBuffers; i++)
        {
            index = glcPrevBuffer(i);
#if VISIBLE_EXTENTS
            if (1)
#else
            if (glcForceUpdate[index])
#endif
            {
                glcForceUpdate[index]--;
                glcDisplayBackground(FALSE);
                r.x0 = (MAIN_WindowWidth  - 640) >> 1;
                r.y0 = (MAIN_WindowHeight - 480) >> 1;
                r.x1 = r.x0 + 640;
                r.y1 = r.y0 + 480;
                if (!glcDisplayRGBABackgroundPortion(glScratch, &r, FALSE))
                {
                    glcDisplayRGBABackground(glScratch);
                }
                glcDisplayRGBABackgroundPortion(glScratch, &glcMouse[index], TRUE);
                glcPageFlipHelper();
            }
            else if ((glcRect[index].x0 + glcRect[index].x1 +
                      glcRect[index].y0 + glcRect[index].y1) != -4)
            {
                if (!glcDisplayRGBABackgroundPortion(glScratch, &glcRect[index], FALSE))
                {
                    glcDisplayRGBABackground(glScratch);
                }
                glcDisplayRGBABackgroundPortion(glScratch, &glcMouse[index], TRUE);
                glcPageFlipHelper();
            }
            else
            {
                if (glcLaggy && i == 0)
                {
                    Sleep(30);
                }
                glcDisplayRGBABackgroundPortion(glScratch, &glcMouse[index], TRUE);
                glcPageFlipHelper();
            }
        }
    }

#if VISIBLE_EXTENTS
    glcDisplayExtents();
#endif

    glcMouse[glcActiveBuffer].x0 = mouseCursorXPosition - xDiff;
    glcMouse[glcActiveBuffer].y1 = mouseCursorYPosition - yDiff;
    glcMouse[glcActiveBuffer].x1 = glcMouse[glcActiveBuffer].x0 + lastUnderWidth;
    glcMouse[glcActiveBuffer].y0 = glcMouse[glcActiveBuffer].y1 - lastUnderHeight;

    glcMouse[glcActiveBuffer].x0 -= 2;

    glcActiveBuffer = glcNextBuffer();

    glcUpdateExtents(-1, -1, -1, -1);
}

/*-----------------------------------------------------------------------------
    Name        : glcActive
    Description : returns glcompat module active status
    Inputs      :
    Outputs     :
    Return      : TRUE or FALSE
----------------------------------------------------------------------------*/
bool glcActive(void)
{
    return glcActiveStatus;
}

static void glcRectangle(rectangle* rect, color c, bool blend)
{
    ubyte* db;
    sdword y, x;
    sdword y0, y1;
    sdword x0, x1;
    sdword r, g, b;

    if (rect->y0 >= rect->y1 || rect->x0 >= rect->x1)
    {
        return;
    }

    y0 = _heightMinusOne - (rect->y0 - yDiff);
    y1 = _heightMinusOne - (rect->y1 - yDiff);
    if (y0 > y1)
    {
        y = y0;
        y0 = y1;
        y1 = y;
    }

    x0 = rect->x0;
    x1 = rect->x1;
    x0 -= xDiff;
    x1 -= xDiff;
    if (x0 < 0) x0 = 0;
    if (x0 >= _width) return;
    if (x1 < 0) return;
    if (x1 >= _width) x1 = _width;
    if ((x1 - x0) <= 0)
    {
        return;
    }

    glcUpdateExtents(x0, x1, y0, y1);

    r = colRed(c);
    g = colGreen(c);
    b = colBlue(c);

    if (!blend)
    {
        //ensure 255 alpha
        c |= 0xFF000000;
    }

    for (y = y0; y < y1; y++)
    {
        if (y < 0 || y >= _height)
        {
            continue;
        }
        db = glScratch + glcScrMult[y];
        if (blend)
        {
            sdword s, t;
            t = colAlpha(c);
            s = 256 - t;
            for (x = x0; x < x1; x++)
            {
                db[4*x + 0] = (t*r + s*db[4*x + 0]) >> 8;
                db[4*x + 1] = (t*g + s*db[4*x + 1]) >> 8;
                db[4*x + 2] = (t*b + s*db[4*x + 2]) >> 8;
                db[4*x + 3] = 255;
            }
        }
        else
        {
            udword* udb = (udword*)db;
            for (x = x0; x < x1; x++)
            {
                udb[x] = c;
            }
        }
    }
}

static void glcRectangleShaded(rectangle* rect, color* c)
{
    udword* udb;
    sdword y, x;
    sdword y0, y1;
    sdword x0, x1;
    sdword xwidth;
    sdword r, g, b;
    sdword rMax, gMax, bMax;
    sdword rDiff, gDiff, bDiff;

    if (rect->y0 >= rect->y1 || rect->x0 >= rect->x1)
    {
        return;
    }

    y0 = _heightMinusOne - (rect->y0 - yDiff);
    y1 = _heightMinusOne - (rect->y1 - yDiff);
    if (y0 > y1)
    {
        y = y0;
        y0 = y1;
        y1 = y;
    }
    else if (y0 == y1)
    {
        return;
    }

    x0 = rect->x0;
    x1 = rect->x1;
    x0 -= xDiff;
    x1 -= xDiff;
    if (x0 < 0) x0 = 0;
    if (x0 >= _width) return;
    if (x1 < 0) return;
    if (x1 >= _width) x1 = _width;
    xwidth = x1 - x0;
    if (xwidth <= 0)
    {
        return;
    }

    glcUpdateExtents(x0, x1, y0, y1);

    r = colRed(c[0]);
    g = colGreen(c[0]);
    b = colBlue(c[0]);
    rMax = colRed(c[2]);
    gMax = colGreen(c[2]);
    bMax = colBlue(c[2]);
    rDiff = (rMax - r) / xwidth;
    gDiff = (gMax - g) / xwidth;
    bDiff = (bMax - b) / xwidth;

    for (y = y0; y < y1; y++)
    {
        if (y < 0 || y >= _height)
        {
            continue;
        }

        r = colRed(c[0]);
        g = colGreen(c[0]);
        b = colBlue(c[0]);

        udb = (udword*)(glScratch + glcScrMult[y]);
        //this is so slow ... lotsa compares
        for (x = x0; x < x1; x++)
        {
            udb[x] = colRGB(r, g, b);
            r += rDiff;
            g += gDiff;
            b += bDiff;
            if (r > rMax) r = rMax;
            else if (r < 0) r = 0;
            if (g > gMax) g = gMax;
            else if (g < 0) g = 0;
            if (b > bMax) b = bMax;
            else if (b < 0) b = 0;
        }
    }
}

void glcRectSolid2(rectangle* rect, color c)
{
    dbgAssert(glScratch != NULL);
    glcRectangle(rect, c, FALSE);
}

void glcBeveledRectSolid2(rectangle* rect, color c, sdword xb, sdword yb)
{
    dbgAssert(glScratch != NULL);
    glcRectangle(rect, c, FALSE);
}

void glcRectTranslucent2(rectangle* rect, color c)
{
    dbgAssert(glScratch != NULL);
    glcRectangle(rect, c, TRUE);
}

void glcRectShaded2(rectangle* rect, color* c)
{
    dbgAssert(glScratch != NULL);
    glcRectangleShaded(rect, c);
}

void glcPoint2(sdword x, sdword y, sdword thickness, color c)
{
    color* db;

    x -= xDiff;
    y = _heightMinusOne - (y - yDiff);

    //ensure 255 alpha
    c |= 0xFF000000;

    if (x < 0 || x >= _width || y < 0 || y >= _height)
    {
        return;
    }

    glcUpdateExtents(x, x, y, y);

    db = (color*)(glScratch + glcScrMult[y] + 4*x);
    *db = c;

    if (thickness > 1)
    {
        db[1] = c;
        db[-_width] = c;
        db[-_width+1] = c;
    }
}

void glcLine2(sdword x0, sdword y0, sdword x1, sdword y1, sdword thickness, color c)
{
    sdword dx, dy;
    sdword xstep, ystep;
    sdword width, min, max;

    width = thickness;
    min = -width / 2;
    max = min + width - 1;

    dx = x1 - x0;
    dy = y1 - y0;
    if (dx == 0 && dy == 0)
    {
        return;
    }

    if (dx < 0)
    {
        dx = -dx;   /* make positive */
        xstep = -1;
    }
    else
    {
        xstep = 1;
    }

    if (dy < 0)
    {
        dy = -dy;   /* make positive */
        ystep = -1;
    }
    else
    {
        ystep = 1;
    }

    if (dx > dy)
    {
        /*
         * X-major line
         */
        sdword i;
        sdword errorInc = dy + dy;
        sdword error = errorInc - dx;
        sdword errorDec = error - dx;

        for (i = 0; i < dx; i++)
        {
            sdword yy;
            sdword ymin = y0 + min;
            sdword ymax = y0 + max;
            for (yy = ymin; yy <= ymax; yy++)
            {
                glcPoint2(x0, yy, 1, c);
            }
            x0 += xstep;
            if (error < 0)
            {
                error += errorInc;
            }
            else
            {
                error += errorDec;
                y0 += ystep;
            }
        }
    }
    else
    {
        /*
         * Y-major line
         */
        sdword i;
        sdword errorInc = dx + dx;
        sdword error = errorInc - dy;
        sdword errorDec = error - dy;

        for (i = 0; i < dy; i++)
        {
            sdword xx;
            sdword xmin = x0 + min;
            sdword xmax = x0 + max;
            for (xx = xmin; xx <= xmax; xx++)
            {
                glcPoint2(xx, y0, 1, c);
            }
            y0 += ystep;
            if (error < 0)
            {
                error += errorInc;
            }
            else
            {
                error += errorDec;
                x0 += xstep;
            }
        }
    }
}

//x raster table for triangle scanconversion
static real32 scan[1200][2];

/*-----------------------------------------------------------------------------
    Name        : scan_convert
    Description : rasterizes a triangle
    Inputs      : x0, y0; x1, y1 - edge
    Outputs     : scan (y-table) is filled w/ x coords
    Return      :
----------------------------------------------------------------------------*/
static void scan_convert(sdword x0, sdword y0, sdword x1, sdword y1)
{
    sdword right, y, ey;
    real32 dx, x;

    if (y0 == y1)
    {
        //horizontal edge
        return;
    }

    if (y0 < y1)
    {
        //left edge
        right = 0;
    }
    else
    {
        //right edge
        swapInt(x0, x1);
        swapInt(y0, y1);
        right = 1;
    }

    //dYdX
    dx = (real32)(x1 - x0) / (real32)(y1 - y0);

    //setup
    x = (real32)x0;
    y = y0;
    ey = y1;

    //scan
    while (y < ey)
    {
        scan[y][right] = x;
        y++;
        x += dx;
    }
}

/*-----------------------------------------------------------------------------
    Name        : barrett_triangle
    Description : display a flat-shaded triangle in the given colour.
                  accuracy is not a concern here
    Inputs      : x0, y0, ... triangle vertex locations
                  c - colour to fill
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
static void barrett_triangle(sdword x0, sdword y0, sdword x1, sdword y1, sdword x2, sdword y2, color c)
{
    sdword ymin, ymax;
    sdword y, ey;
    sdword x;

    //find min/max y extent
    ymin = ymax = y0;
    if      (y1 < ymin) ymin = y1;
    else if (y1 > ymax) ymax = y1;
    if      (y2 < ymin) ymin = y2;
    else if (y2 > ymax) ymax = y2;

    //scan edges
    scan_convert(x0, y0, x2, y2);
    scan_convert(x1, y1, x0, y0);
    scan_convert(x2, y2, x1, y1);

    //setup
    y  = ymin;
    ey = ymax;

    while (y < ey)
    {
        sdword n;
        sdword sx = (sdword)scan[y][0];
        sdword ex = (sdword)scan[y][1];

        if (sx < ex)
        {
            //l -> r
            n = ex - sx;
        }
        else
        {
            //r -> l
            n = sx - ex;
            sx = ex;
        }

        if (n == 0)
        {
            //0-width
            y++;
            continue;
        }

        //fill raster
        for (x = 0; x < n; x++)
        {
            glcPoint2(sx+x, y, 1, c);
        }

        //next raster line
        y++;
    }
}

void glcTriSolid2(triangle* tri, color c)
{
    dbgAssert(glScratch != NULL);

    barrett_triangle(tri->x0, tri->y0,
                     tri->x1, tri->y1,
                     tri->x2, tri->y2,
                     c);
}

void glcTriOutline2(triangle* tri, sdword thickness, color c)
{
    dbgAssert(glScratch != NULL);

    glcLine2(tri->x0, tri->y0, tri->x1, tri->y1, thickness, c);
    glcLine2(tri->x1, tri->y1, tri->x2, tri->y2, thickness, c);
    glcLine2(tri->x2, tri->y2, tri->x0, tri->y0, thickness, c);
}

void glcRectSolidTexturedScaled2(rectangle* rect, sdword width, sdword height, ubyte* data, color* palette, bool reverse)
{
    ubyte* newData;
    sdword newHeight, newWidth;
    real32 ystep, ry;
    real32 xstep, rx;
    sdword newy, oldy;
    sdword newx, oldx;
    color* sp;
    color* dp;
    ubyte* sb;
    ubyte* db;

    newHeight = abs(rect->y1 - rect->y0);
    newWidth  = abs(rect->x1 - rect->x0);
    if ((newWidth == width) && (newHeight == height))
    {
        //don't actually need to scale anything
        glcRectSolidTextured2(rect, width, height, data, palette, reverse);
        return;
    }
    newData = (ubyte*)memAlloc(((palette == NULL) ? 4 : 1) * newWidth * newHeight,
                               "scaledGLCTEX", Volatile);

    ystep = (real32)height / (real32)newHeight;
    xstep = (real32)width  / (real32)newWidth;
    ry = 0.0f;
    for (newy = 0; newy < newHeight; newy++, ry += ystep)
    {
        oldy = (sdword)ry;
        rx = 0.0f;
        if (palette == NULL)
        {
            for (newx = 0; newx < newWidth; newx++, rx += xstep)
            {
                oldx = (sdword)rx;
                sp = (color*)(data + 4*width*oldy + 4*oldx);
                dp = (color*)(newData + 4*newWidth*newy + 4*newx);
                *dp = *sp;
            }
        }
        else
        {
            for (newx = 0; newx < newWidth; newx++, rx += xstep)
            {
                oldx = (sdword)rx;
                sb = data + width*oldy + oldx;
                db = newData + newWidth*newy + newx;
                *db = *sb;
            }
        }
    }
    glcRectSolidTextured2(rect, newWidth, newHeight, newData, palette, reverse);
    memFree(newData);
}

void glcRectSolidTextured2(rectangle* rect, sdword width, sdword height, ubyte* data, color* palette, bool reverse)
{
    ubyte* bdp;
    color* cdp;
    color* udb;
    sdword y, x, xwidth;
    sdword y0, y1;
    sdword x0, x1;

    if (rect->y0 >= rect->y1 || rect->x0 >= rect->x1)
    {
        return;
    }

    y0 = _heightMinusOne - (rect->y0 - yDiff);
    y1 = _heightMinusOne - (rect->y1 - yDiff);
    if (y0 > y1)
    {
        y = y0;
        y0 = y1;
        y1 = y;
    }

    x0 = rect->x0 - xDiff;
    x1 = rect->x1 - xDiff;
    if (x0 < 0) x0 = 0;
    if (x0 >= _width) return;
    if (x1 < 0) return;
    if (x1 >= _width) x1 = _width;
    xwidth = x1 - x0;
    if (xwidth <= 0)
    {
        return;
    }

    glcUpdateExtents(x0, x1, y0, y1);

    if (palette == NULL)
    {
        cdp = reverse ? (color*)data + width*(height-1) : (color*)data;
        for (y = y0; y < y1; y++, cdp = reverse ? cdp - width : cdp + width)
        {
            if (y < 0 || y >= _height)
            {
                continue;
            }
            udb = (color*)(glScratch + glcScrMult[y] + 4*x0);
            memcpy(udb, cdp, 4*xwidth);
        }
    }
    else
    {
        bdp = reverse ? data + width*(height-1) : data;
        for (y = y0; y < y1; y++, bdp = reverse ? bdp - width : bdp + width)
        {
            if (y < 0 || y >= _height)
            {
                continue;
            }
            udb = (color*)(glScratch + glcScrMult[y] + 4*x0);
            for (x = 0; x < xwidth; x++)
            {
                udb[x] = palette[bdp[x]];
            }
        }
    }
}

void glcRectOutline2(rectangle* rect, sdword thickness, color c)
{
    glcLine2(rect->x0, rect->y0, rect->x1, rect->y0, thickness, c);
    glcLine2(rect->x1, rect->y0, rect->x1, rect->y1, thickness, c);
    glcLine2(rect->x1, rect->y1, rect->x0, rect->y1, thickness, c);
    glcLine2(rect->x0, rect->y1, rect->x0, rect->y0, thickness, c);
}

void _glcRectOutline2(rectangle* rect, sdword thickness, color c)
{
    udword* db0;
    udword* db1;
    sdword y, x;
    sdword y0, y1;
    sdword x0, x1;

    dbgAssert(glScratch != NULL);

    y0 = _heightMinusOne - (rect->y0 - yDiff);
    y1 = _heightMinusOne - (rect->y1 - yDiff);
    if (y0 > y1)
    {
        y = y0;
        y0 = y1;
        y1 = y;
    }

    x0 = rect->x0 - xDiff;
    x1 = rect->x1 - xDiff;

    glcUpdateExtents(x0, x1, y0, y1);

    //ensure 255 alpha
    c |= 0xFF000000;

    //top & bottom
    db0 = (udword*)(glScratch + glcScrMult[y0]);
    db1 = (udword*)(glScratch + glcScrMult[y1]);
    for (x = x0; x <= x1; x++)
    {
        db0[x] = c;
        db1[x] = c;
    }

    //left & right
    db0 = (udword*)(glScratch + glcScrMult[y0] + 4*x0);
    db1 = (udword*)(glScratch + glcScrMult[y0] + 4*x1);
    for (y = y0; y < y1; y++, db0 += _width, db1 += _width)
    {
        *db0 = c;
        *db1 = c;
    }
}

static void glcDrawPixelsHelper(GLenum format, sdword width, sdword height, ubyte const* data)
{
    ubyte* pb;
    ubyte* db;
    sdword y, x;
    sdword py, px;
    sdword xlead, pitch;
    sdword alpha;
    sdword r, g, b;

    pitch = width;

    px = glcRasterPos[0] - xDiff;
    py = glcRasterPos[1] - yDiff;

    glcUpdateExtents(px, px+width, py, py+height);

    pb = (ubyte*)data;

    r = glcColor[0];
    g = glcColor[1];
    b = glcColor[2];

    if (px < 0)
    {
        xlead = -px;
        width += px;
        px = 0;
        if (width <= 0)
        {
            return;
        }
    }
    else
    {
        xlead = 0;
    }

    if (px+width > _width)
    {
        width -= (px+width) - _width + 1;
        if (width <= 0)
        {
            return;
        }
    }

    if (format == GL_RGBA)
    {
        for (y = 0; y < height; y++, pb += 4*(pitch+xlead))
        {
            if (py+y < 0 || py+y >= _height)
            {
                continue;
            }

            db = glScratch + glcScrMult[py+y] + 4*(px+xlead);

            if (glcBlend || glcAlphaTest)
            {
                for (x = 0; x < width; x++, db += 4)
                {
                    if (pb[4*x + 3] > 0)
                    {
                        db[0] = pb[4*x + 0];
                        db[1] = pb[4*x + 1];
                        db[2] = pb[4*x + 2];
                        db[3] = 255;
                    }
                }
            }
            else
            {
                for (x = 0; x < width; x++, db += 4)
                {
                    db[0] = pb[4*x + 0];
                    db[1] = pb[4*x + 1];
                    db[2] = pb[4*x + 2];
                    db[3] = 255;
                }
            }
        }
    }
    else if (format == GL_RGB8)
    {
        for (y = 0; y < height; y++, pb += pitch + xlead)
        {
            if (py+y < 0 || py+y >= _height)
            {
                continue;
            }

            db = glScratch + glcScrMult[py+y] + 4*(px+xlead);

            for (x = 0; x < width; x++, db += 4)
            {
                if (pb[x] > 0)
                {
                    alpha = pb[x] * 16;
                    db[0] = (r * alpha) >> 8;
                    db[1] = (g * alpha) >> 8;
                    db[2] = (b * alpha) >> 8;
                    db[3] = 255;
                }
            }
        }
    }
}

void __stdcall glcDrawPixels(GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid const* pixels)
{
    dbgAssert(glScratch != NULL);
    dbgAssert(pixels != NULL);

    switch (format)
    {
    case GL_RGBA:
    case GL_RGB8:
        glcDrawPixelsHelper(format, (sdword)width, (sdword)height, (ubyte const*)pixels);
        break;
    default:
        dbgFatalf(DBG_Loc, "glcDrawPixels: unknown format %d", (sdword)format);
    }
}

void __stdcall glcRasterPos2f(GLfloat x, GLfloat y)
{
    real32 v[4], eye[4], clip[4];
    real32 viewport[2][2];
    real32 d, ndc[3];
    real32 modelview[16], projection[16];

    glGetFloatv(GL_MODELVIEW_MATRIX,  (GLfloat*)modelview);
    glGetFloatv(GL_PROJECTION_MATRIX, (GLfloat*)projection);

    v[0] = x;
    v[1] = y;
    v[2] = 0.0f;
    v[3] = 1.0f;
    TRANSFORM_POINT(eye, modelview, v);
    TRANSFORM_POINT(clip, projection, eye);

    viewport[0][0] = (real32)MAIN_WindowWidth  / 2.0f;
    viewport[0][1] = viewport[0][0];
    viewport[1][0] = (real32)MAIN_WindowHeight / 2.0f;
    viewport[1][1] = viewport[1][0];

    d = 1.0f / clip[3];
    ndc[0] = clip[0] * d;
    ndc[1] = clip[1] * d;
    ndc[2] = clip[2] * d;
    glcRasterPos[0] = (sdword)(ndc[0] * viewport[0][0] + viewport[0][1]);
    glcRasterPos[1] = (sdword)(ndc[1] * viewport[1][0] + viewport[1][1]);
}

void __stdcall glcClear(GLbitfield mask)
{
    if ((glScratch != NULL) &&
        (mask & GL_COLOR_BUFFER_BIT))
    {
        memset(glScratch, 0, _pitch*_height);
    }
    _glcClear(mask);
}

void __stdcall glcColor3ub(GLubyte r, GLubyte g, GLubyte b)
{
    glcColor[0] = (sdword)r;
    glcColor[1] = (sdword)g;
    glcColor[2] = (sdword)b;
    _glcColor3ub(r, g, b);
}

void __stdcall glcEnable(GLenum cap)
{
    switch (cap)
    {
    case GL_BLEND:
        glcBlend = TRUE;
        break;
    case GL_ALPHA_TEST:
        glcAlphaTest = TRUE;
        break;
    case GL_SCISSOR_TEST:
        glcScissorTest = TRUE;
        break;
    }
    _glcEnable(cap);
}

void __stdcall glcDisable(GLenum cap)
{
    switch (cap)
    {
    case GL_BLEND:
        glcBlend = FALSE;
        break;
    case GL_ALPHA_TEST:
        glcAlphaTest = FALSE;
        break;
    case GL_SCISSOR_TEST:
        glcScissorTest = FALSE;
        break;
    }
    _glcDisable(cap);
}

/*-----------------------------------------------------------------------------
    Name        : glcCursorUnder
    Description : backing re/store for the mouse pointer
    Inputs      : data - backing storage
                  width, height - dimensions of backing store rectangle
                  x0, y0 - backing store location on screen
                  store - TRUE or FALSE (store or restore)
    Outputs     : store ? data is filled : data is written to glScratch
    Return      :
----------------------------------------------------------------------------*/
void glcCursorUnder(ubyte* data, sdword width, sdword height, sdword x0, sdword y0, bool store)
{
    sdword ybegin, yend;
    sdword xbegin, xend;
    sdword y, x;
    udword* udb;
    udword* ufb;

    if (glScratch == NULL)
    {
        return;
    }

    x0 -= xDiff;
    y0 -= yDiff;

    ybegin = _heightMinusOne - y0;
    yend   = ybegin - height;
    if (ybegin > yend)
    {
        y = ybegin;
        ybegin = yend;
        yend = y;
    }

    xbegin = x0;
    xend   = xbegin + width;

    ybegin--;
    yend++;
    xbegin--;
    xend++;

    if (xbegin >= _width || xend < 0)
    {
        return;
    }

    udb = (udword*)data;

    for (y = ybegin; y < yend; y++)
    {
        if (y < 0 || y >= _height)
        {
            for (x = 0; x < width; x++, udb++)
            {
                *udb = 0;//FEC_Background;
            }
            continue;
        }
        ufb = (udword*)(glScratch + glcScrMult[y]);
        for (x = xbegin; x < xend; x++, udb++)
        {
            if (x >= 0 && x < _width)
            {
                if (store)
                {
                    *udb = ufb[x];
                }
                else
                {
                    ufb[x] = *udb;
                }
            }
            else
            {
                if (store)
                {
                    *udb = 0;//FEC_Background;
                }
            }
        }
    }
}

/*-----------------------------------------------------------------------------
    Name        : glcActivate
    Description : de/activate the glcompat module
    Inputs      : active - TRUE or FALSE
    Outputs     :
    Return      : previous active status
----------------------------------------------------------------------------*/
bool glcActivate(bool active)
{
    bool lastStatus;
    if (!glCapFeatureExists(GL_SWAPFRIENDLY))
    {
        return glcActiveStatus;
    }
    if (glcActiveStatus == active)
    {
        //avoid redundant state change
        return glcActiveStatus;
    }
    lastStatus = glcActiveStatus;
    glcActiveStatus = active;
    if (active)
    {
        if (gl3Dfx)
        {
            glcLaggy = TRUE;
        }
        else if (strstr(GLC_RENDERER, "Voodoo Graphics") != NULL ||
                 strstr(GLC_RENDERER, "Voodoo2") != NULL)
        {
            glcLaggy = TRUE;
        }
        else
        {
            glcLaggy = FALSE;
        }

        glcBlend = (bool)glIsEnabled(GL_BLEND);
        glcAlphaTest = (bool)glIsEnabled(GL_ALPHA_TEST);
        glcScissorTest = (bool)glIsEnabled(GL_SCISSOR_TEST);

        _glcDrawPixels = (DRAWPIXELSproc)glDrawPixels;
        glDrawPixels = (DRAWPIXELSproc)glcDrawPixels;
        dbgAssert(glDrawPixels != _glcDrawPixels);

        _glcRasterPos2f = (RASTERPOS2Fproc)glRasterPos2f;
        glRasterPos2f = (RASTERPOS2Fproc)glcRasterPos2f;
        dbgAssert(glRasterPos2f != _glcRasterPos2f);

        _glcColor3ub = (COLOR3UBproc)glColor3ub;
        glColor3ub = (COLOR3UBproc)glcColor3ub;
        dbgAssert(glColor3ub != _glcColor3ub);

        _glcClear = (CLEARproc)glClear;
        glClear = (CLEARproc)glcClear;
        dbgAssert(glClear != _glcClear);

        _glcEnable = glEnable;
        glEnable = (ENABLEproc)glcEnable;
        dbgAssert(glEnable != _glcEnable);

        _glcDisable = glDisable;
        glDisable = (DISABLEproc)glcDisable;
        dbgAssert(glDisable != _glcDisable);

        (void)glcAllocateScratch(TRUE);

        glcNumBuffers = glCapNumBuffers();
        glcForceUpdates = glcNumBuffers;
        glcActiveBuffer = 0;

        glcDrawingMouse = FALSE;
        glcUpdateExtents(-1, -1, -1, -1);
        glcRenderEverythingALot();
    }
    else
    {
        if (glNT && gl3Dfx)
        {
            ;
        }
        else
        {
            glcPurgeHandles();
        }
        glDLLGetGLCompat();
        (void)glcAllocateScratch(FALSE);
    }
    return lastStatus;
}

/*-----------------------------------------------------------------------------
    Name        : glcIsFullscreen
    Description : returns fullscreen status (in-game glcActive ...)
    Inputs      :
    Outputs     :
    Return      : TRUE or FALSE
----------------------------------------------------------------------------*/
bool glcIsFullscreen(void)
{
    return glcFullscreenStatus;
}

/*-----------------------------------------------------------------------------
    Name        : glcFullscreen
    Description : switches into / outof fullscreen mode (in-game glcActivate ...)
    Inputs      : full - TRUE or FALSE, desired state
    Outputs     :
    Return      : previous state
----------------------------------------------------------------------------*/
bool glcFullscreen(bool full)
{
    bool lastStatus;

#if !GLC_FULLSCREEN
    return FALSE;
#endif

    if (RGLtype == SWtype)
    {
        return FALSE;
    }

    if (!glCapFeatureExists(GL_SWAPFRIENDLY))
    {
        return FALSE;
    }

    if (full == glcFullscreenStatus)
    {
        //avoid redundant state change
        return full;
    }

    lastStatus = glcFullscreenStatus;
    if (full)
    {
        (void)glcActivate(TRUE);
    }
    else
    {
        (void)glcActivate(FALSE);
    }
    glcFullscreenStatus = full;
    return lastStatus;
}

/*-----------------------------------------------------------------------------
    Name        : glcRenderEverything
    Description : force glcompat to re-render the entire screen
    Inputs      :
    Outputs     : glcForceUpdate[..] = glcForceUpdates
    Return      :
----------------------------------------------------------------------------*/
void glcRenderEverything(void)
{
    sdword i;

    for (i = 0; i < GLC_MAX_BUFFERS; i++)
    {
        glcForceUpdate[i] = glcForceUpdates;
    }
}

/*-----------------------------------------------------------------------------
    Name        : glcRenderEverythingALot
    Description : force glcompat to re-render the entire screen 2x the amount
                  that glcRenderEverything would
    Inputs      :
    Outputs     : glcForceUpdate[..] = 2 * glcForceUpdates
    Return      :
----------------------------------------------------------------------------*/
void glcRenderEverythingALot(void)
{
    sdword i;

    for (i = 0; i < GLC_MAX_BUFFERS; i++)
    {
        glcForceUpdate[i] = 2 * glcForceUpdates;
    }
}
