If you have comments or questions concerning this source file, discuss them in the forum.
/*
Copyright (c) 2002 Nicolai Haehnle

See the license.txt for details. If that file was not included in the
source distributions, please email <prefect@rtts.org>
*/
// hal_sdlgl.c

#include "library.h"
#include "hal.h"
#include "hal_sdlgl.h"

#include <SDL.h>
#include <SDL_opengl.h>

#include "glfns.h"


// bit dodgy 'design', but I can't see a reason why one would need multiple
// HAL objects at a time
SDL_Surface *sdl_screen;

bool    hal_subpixel;
bool    hal_noborder;

LRect   orthorect;
LRect   drawrect;
int     drawofsx, drawofsy;
bool    drawclip;


/*
==============================================================================

GL HELPER FUNCTIONS

==============================================================================
*/

int gl_format_maskonly;
int gl_format_alpha;
int gl_format_opaque;

int gl_max_texture_size = 256;

bool gl_state_texenable = false;
unsigned gl_state_texture = 0;

/*
==============
GL_TexEnable

Enable or disable texturing
==============
*/
static inline void GL_TexEnable(bool enable)
{
    if (enable == gl_state_texenable)
        return;

    if (enable)
        pglEnable(GL_TEXTURE_2D);
    else
        pglDisable(GL_TEXTURE_2D);

    gl_state_texenable = enable;
}

/*
==============
GL_Bind

Bind a texture (TMU is currently ignored)
==============
*/
static inline void GL_Bind(int tmu, unsigned texture)
{
    if (texture == gl_state_texture)
        return;

    pglBindTexture(GL_TEXTURE_2D, texture);

    gl_state_texture = texture;
}

/*
==============
GL_GenTexture
==============
*/
static inline unsigned GL_GenTexture()
{
    unsigned texture;

    pglGenTextures(1, &texture);

    return texture;
}

/*
==============
GL_DeleteTexture

Delete the texture, fix bindings if necessary
==============
*/
static inline void GL_DeleteTexture(unsigned texture)
{
    if (texture == gl_state_texture) {
        pglBindTexture(GL_TEXTURE_2D, 0);
        gl_state_texture = 0;
    }

    pglDeleteTextures(1, &texture);
}

/*
==============================================================================

SINGLE BITMAP

==============================================================================
*/

class HBitmapImpl : public HPictureImpl {
public:
    unsigned    m_uTexture;
    int         m_iUploadSize[2];

public:
    HBitmapImpl(HPicture *pic, const int *size, int type, byte *pixels);
    virtual ~HBitmapImpl();

    virtual void Draw(float dstx, float dsty, float srcx, float srcy, float w, float h,
                int r, int g, int b, int a);
};

/*
==============
HBitmapImpl::HBitmapImpl

Create a texture and upload the pixels to it
==============
*/
HBitmapImpl::HBitmapImpl(HPicture *pic, const int *size, int type, byte *pixels)
    : HPictureImpl(pic)
{
    bool bFree;
    int i;
    int format;
    int filter;

    bFree = false;

    // Scale the dimensions to powers of two
    for(i = 0; i < 2; i++) {
        m_iUploadSize[i] = 1;
        while(m_iUploadSize[i] < size[i])
            m_iUploadSize[i] <<= 1;

        if (m_iUploadSize[i] > gl_max_texture_size)
            m_iUploadSize[i] = gl_max_texture_size;
    }

    // Actually scale the image, if necessary
    if (size[0] != m_iUploadSize[0] || size[1] != m_iUploadSize[1])
    {
        byte *scaled;

        scaled = (byte *)L_Malloc(4 * m_iUploadSize[0]*m_iUploadSize[1], TAG_HAL);
        L_ScaleImage(scaled, m_iUploadSize[0], m_iUploadSize[1],
                     pixels, size[0], size[1]);

        pixels = scaled;
        bFree = true;
    }

    // Determine upload parameters
    if (type & __pic_onlyalpha)
    {
        if (type & __pic_maskonly)
            format = GL_ALPHA4;
        else
            format = GL_ALPHA8;
    }
    else if (type & __pic_alpha)
    {
        if (type & __pic_maskonly)
            format = gl_format_maskonly;
        else
            format = gl_format_alpha;
    }
    else
        format = gl_format_opaque;

    filter = GL_LINEAR;

    // Upload
    m_uTexture = GL_GenTexture();
    GL_Bind(0, m_uTexture);
    pglTexImage2D(GL_TEXTURE_2D, 0, format, m_iUploadSize[0], m_iUploadSize[1], 0,
                    GL_RGBA, GL_UNSIGNED_BYTE, pixels);

    pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
    pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);

    if (bFree)
        L_Free(pixels);
}

/*
==============
HBitmapImpl::~HBitmapImpl

Free allocated resources
==============
*/
HBitmapImpl::~HBitmapImpl()
{
    GL_DeleteTexture(m_uTexture);
}

/*
==============
HBitmapImpl::Draw

Render (parts of) the picture onto the screen
==============
*/
void HBitmapImpl::Draw(float dstx, float dsty, float srcx, float srcy, float w, float h,
                int r, int g, int b, int a)
{
    LFloatRect rc(dstx, dsty, w, h);
    float bs, bt, ds, dt;
    float s0, s1, t0, t1;

    // Border and subpixel adjustments
    if (!hal_subpixel) {
        rc.pos[0] = (int)rc.pos[0];
        rc.pos[1] = (int)rc.pos[1];
    }
    rc.pos[0] -= drawofsx;
    rc.pos[1] -= drawofsy;

    // Perform clipping
    if (drawclip)
        drawrect.ClipLocalOffset(&rc, &srcx, &srcy);

    if (rc.size[0] <= 0 || rc.size[1] <= 0)
        return;

    drawrect.RectToWorld(&rc.pos[0], &rc.pos[1]);

    // Actually render the quad
    GL_TexEnable(true);
    GL_Bind(0, m_uTexture);

    bs = 0.5 / m_iUploadSize[0];
    bt = 0.5 / m_iUploadSize[1];

    ds = 1.0 / m_pic->size[0];
    dt = 1.0 / m_pic->size[1];

    s0 = bs + ds * srcx;
    s1 = s0 + ds * (rc.size[0] - 1);
    t0 = bs + dt * srcy;
    t1 = t0 + dt * (rc.size[1] - 1);

    pglColor4ub((byte)r, (byte)g, (byte)b, (byte)a);

    pglBegin(GL_QUADS);
        pglTexCoord2f(s0, t0); pglVertex2f(rc.pos[0], rc.pos[1]);
        pglTexCoord2f(s1, t0); pglVertex2f(rc.pos[0]+rc.size[0], rc.pos[1]);
        pglTexCoord2f(s1, t1); pglVertex2f(rc.pos[0]+rc.size[0], rc.pos[1]+rc.size[1]);
        pglTexCoord2f(s0, t1); pglVertex2f(rc.pos[0], rc.pos[1]+rc.size[1]);
    pglEnd();
}

/*
==============================================================================

PICLETS

==============================================================================
*/

#define PICLUMP_SIZE        256

typedef struct piclump_s {
    struct piclump_s **pprev, *next;
    int             used;
    int             type;
    unsigned int    texture;

    int             height;
    int             texels, effective_texels;
    unsigned short  usage[PICLUMP_SIZE];
} piclump_t;

piclump_t   *gl_piclumps = 0;

#ifdef DEBUG
/*
==============
PL_Statistics

Gather statistics about piclets (for debugging)
==============
*/
static void PL_Statistics()
{
    piclump_t *lump;
    int numlumps;
    int numtexels, numefftexels;
    int totaltexels;

    numlumps = 0;
    numtexels = 0;
    numefftexels = 0;

    for(lump = gl_piclumps; lump; lump = lump->next) {
        numlumps++;
        numtexels += lump->texels;
        numefftexels += lump->effective_texels;
    }

    totaltexels = numlumps * PICLUMP_SIZE * PICLUMP_SIZE;
    if (!totaltexels)
        totaltexels = 1;

    L_Printf("Lump statistics: %i lumps\n", numlumps);
    L_Printf(" %7i texels -- %3.2f%%\n", numtexels, 100.0 * (float)numtexels / totaltexels);
    L_Printf(" %7i effective texels -- %3.2f%%\n", numefftexels,
        100.0 * (float)numefftexels / totaltexels);
}
#endif

/*
==============
PL_Alloc

Allocate some space in a piclump
==============
*/
static piclump_t *PL_Alloc(int type, const int *size, int *offset)
{
    piclump_t *lump;
    unsigned dummy[PICLUMP_SIZE * PICLUMP_SIZE];
    int format;
    int bestx, besty;
    int curx, cury, x, y;
    int filter;

    type &= __pic_formatmask;

    // Check for space in existing lumps
    for(lump = gl_piclumps; lump; lump = lump->next)
    {
        if (lump->type != type)
            continue;

        if (lump != gl_piclumps) {
            if (lump->height > PICLUMP_SIZE-size[1])
                continue;
        }

        besty = PICLUMP_SIZE-size[1]+1;

        for(curx = 0; curx+size[0] <= PICLUMP_SIZE; curx++)
        {
            cury = 0;
            for(x = curx; x < curx+size[0]; x++) {
                y = lump->usage[x];
                if (y >= cury) {
                    cury = y;
                    if (y >= besty) {
                        curx = x;
                        goto skip;
                    }
                }
            }

            lassert(cury < besty);

            bestx = curx;
            besty = cury;

            if (!besty)
                break;
    skip: ;
        }

        if (besty+size[1] <= PICLUMP_SIZE)
        {
            lump->height = besty;
            y = besty+size[1];
            for(x = bestx; x < bestx+size[0]; x++)
                lump->usage[x] = y;
            lump->texels += size[0]*size[1];
            lump->effective_texels += size[0]*size[1];
            lump->used++;

            offset[0] = bestx;
            offset[1] = besty;
            return lump;
        }
    }

#ifdef DEBUG
    PL_Statistics();
#endif

    // Create a new lump
    lump = (piclump_t *)L_Malloc(sizeof(piclump_t), TAG_HAL);
    memset(lump, 0, sizeof(piclump_t));
    lump->used = 1;
    lump->pprev = &gl_piclumps;
    lump->next = gl_piclumps;
    if (lump->next)
        lump->next->pprev = &lump->next;
    gl_piclumps = lump;

    lump->type = type;

    // Fake initial upload
    if (type & __pic_onlyalpha)
    {
        if (type & __pic_maskonly)
            format = GL_ALPHA4;
        else
            format = GL_ALPHA8;
    }
    else if (type & __pic_alpha)
    {
        if (type & __pic_maskonly)
            format = gl_format_maskonly;
        else
            format = gl_format_alpha;
    }
    else
        format = gl_format_opaque;

    filter = GL_LINEAR;

    lump->texture = GL_GenTexture();
    GL_Bind(0, lump->texture);
    pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
    pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
    pglTexImage2D(GL_TEXTURE_2D, 0, format, PICLUMP_SIZE, PICLUMP_SIZE, 0,
        GL_RGBA, GL_UNSIGNED_BYTE, dummy);

    // Store information
    lump->height = 0;
    for(x = 0; x < size[0]; x++)
        lump->usage[x] = size[1];
    lump->effective_texels = lump->texels = size[0]*size[1];

    offset[0] = 0;
    offset[1] = 0;
    return lump;
}

/*
==============
PL_Free

Free an area inside a piclump
==============
*/
void PL_Free(piclump_t *lump, const int *offset, const int *size)
{
    lump->effective_texels -= size[0]*size[1];
    lump->used--;

    if (lump->used > 0)
        return;

    lassert(!lump->effective_texels);

    if (lump->next)
        lump->next->pprev = lump->pprev;
    *lump->pprev = lump->next;

    GL_DeleteTexture(lump->texture);
    L_Free(lump);
}

class HPicletImpl : public HPictureImpl {
public:
    piclump_t   *m_pLump;
    int         m_iOffset[2];

    bool        m_bBorder;
    int         m_iEffSize[2];

public:
    HPicletImpl(HPicture *pic, const int *psize, int type, byte *pixels);
    virtual ~HPicletImpl();

    virtual void Draw(float dstx, float dsty, float srcx, float srcy, float w, float h,
                int r, int g, int b, int a);
};

/*
==============
HPicletImpl::HPicletImpl

Allocate some space and upload the pixels to it
==============
*/
HPicletImpl::HPicletImpl(HPicture *pic, const int *psize, int type, byte *pixels)
    : HPictureImpl(pic)
{
    if (hal_noborder || !hal_subpixel || (type & (__pic_onlyalpha|__pic_nosubpixel)))
    {
        m_bBorder = false;
        m_iEffSize[0] = psize[0];
        m_iEffSize[1] = psize[1];
    }
    else
    {
        // we need to fixup the type because we add an alpha component
        if (!(type & __pic_alpha))
            type |= __pic_alpha | __pic_maskonly;

        pixels = (byte *)L_ImageBorder((u32 *)pixels, psize);
        m_bBorder = true;
        m_iEffSize[0] = psize[0]+2;
        m_iEffSize[1] = psize[1]+2;
    }

    m_pLump = PL_Alloc(type, m_iEffSize, m_iOffset);

    GL_Bind(0, m_pLump->texture);
    pglTexSubImage2D(GL_TEXTURE_2D, 0, m_iOffset[0], m_iOffset[1],
        m_iEffSize[0], m_iEffSize[1], GL_RGBA, GL_UNSIGNED_BYTE, pixels);

    if (m_bBorder)
        L_Free(pixels);
}

/*
==============
HPicletImpl::~HPicletImpl

Free the piclet space
==============
*/
HPicletImpl::~HPicletImpl()
{
    PL_Free(m_pLump, m_iOffset, m_iEffSize);
}

/*
==============
HPicletImpl::Draw

Draw only a part of the piclet
==============
*/
void HPicletImpl::Draw(float dstx, float dsty, float srcx, float srcy, float w, float h,
                int r, int g, int b, int a)
{
    LFloatRect dst(dstx, dsty, w, h);
    float ds, dt;
    float s0, s1, t0, t1;

    // Border and subpixel adjustments
    if (!hal_subpixel) {
        dst.pos[0] = (int)dst.pos[0];
        dst.pos[1] = (int)dst.pos[1];
    }
    dst.pos[0] -= drawofsx;
    dst.pos[1] -= drawofsy;

    if (m_bBorder)
    {
        if (srcx+dst.size[0] >= m_pic->size[0])
            dst.size[0] += 1.0;
        if (srcy+dst.size[1] >= m_pic->size[1])
            dst.size[1] += 1.0;

        if (srcx <= 0) {
            dst.pos[0] -= 1.0;
            srcx -= 1.0;
            dst.size[0] += 1.0;
        }
        if (srcy <= 0) {
            dst.pos[1] -= 1.0;
            srcy -= 1.0;
            dst.size[1] += 1.0;
        }

        srcx += 1.0;
        srcy += 1.0;
    }

    // Perform source rectangle clipping to counter caller messups
    if (srcx < 0) {
        dst.pos[0] -= srcx;
        dst.size[0] += srcx;
        srcx = 0;
    }
    if (srcx+dst.size[0] > m_iEffSize[0])
        dst.size[0] = m_iEffSize[0] - srcx;
    if (dst.size[0] <= 0)
        return;

    if (srcy < 0) {
        dst.pos[1] -= srcy;
        dst.size[1] += srcy;
        srcy = 0;
    }
    if (srcy+dst.size[1] > m_iEffSize[1])
        dst.size[1] = m_iEffSize[1] - srcy;
    if (dst.size[1] <= 0)
        return;

    // Perform clipping
    if (drawclip)
    {
        drawrect.ClipLocalOffset(&dst, &srcx, &srcy);

        if (dst.size[0] <= 0 || dst.size[1] <= 0)
            return;
    }

    drawrect.RectToWorld(&dst.pos[0], &dst.pos[1]);

    // Actually render the quad
    GL_TexEnable(true);
    GL_Bind(0, m_pLump->texture);

    pglColor4ub((byte)r, (byte)g, (byte)b, (byte)a);

    ds = 1.0 / PICLUMP_SIZE;
    dt = 1.0 / PICLUMP_SIZE;

    s0 = ds * (m_iOffset[0]+srcx+0.5);
    s1 = s0 + ds * (dst.size[0]-1.0);
    t0 = dt * (m_iOffset[1]+srcy+0.5);
    t1 = t0 + dt * (dst.size[1]-1.0);

    pglBegin(GL_QUADS);
        pglTexCoord2f(s0, t0); pglVertex2f(dst.pos[0], dst.pos[1]);
        pglTexCoord2f(s1, t0); pglVertex2f(dst.pos[0]+dst.size[0], dst.pos[1]);
        pglTexCoord2f(s1, t1); pglVertex2f(dst.pos[0]+dst.size[0], dst.pos[1]+dst.size[1]);
        pglTexCoord2f(s0, t1); pglVertex2f(dst.pos[0], dst.pos[1]+dst.size[1]);
    pglEnd();
}


/*
==============================================================================

HAL_SdlGl IMPLEMENTATION

==============================================================================
*/

/*
==============
HAL_SdlGl::HAL_SdlGl

Latch settings from the config object
==============
*/
HAL_SdlGl::HAL_SdlGl(LConfig *cfg)
    : HAL()
{
    init_res = cfg->GetInt("res");
    init_bpp = cfg->GetInt("bpp");
    init_window = cfg->GetBool("window");

    hal_subpixel = !cfg->GetBool("nosubpixel");
    hal_noborder = cfg->GetBool("noborder");

    showfps = false;
}

/*
==============
HAL_SdlGl::Init

Initialize the video mode etc...
==============
*/
void HAL_SdlGl::Init()
{
    static const int resolutions[][2] = {
        { 640, 480 },
        { 800, 600 },
        { 1024, 768 },
        { 1280, 960 },
        { 0, 0 }
    };
    Uint32 flags;
    int bpp;
    int res, origres;
    int width, height;

    // Init SDL
    if (SDL_Init(SDL_INIT_VIDEO) < 0)
        throw LError("Failed to initialize SDL: %s", SDL_GetError());

    // Load the GL library and <= 1.1 functions
    GL_LoadLibrary();

    // Initialize our video mode
    if (init_bpp == 16)
        bpp = 16;
    else
        bpp = 32;

    for(res = 0; resolutions[res][0]; res++) {
        if (resolutions[res][0] >= init_res)
            break;
    }
    if (!resolutions[res][0])
        res--;

    origres = res;

    for(;;) {
        if (bpp != 32) {
            SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
            SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5);
            SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
            SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

            gl_format_maskonly = GL_RGB5_A1;
            gl_format_alpha = GL_RGBA8;
            gl_format_opaque = GL_RGB5;
        } else {
            SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
            SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
            SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
            SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

            gl_format_maskonly = GL_RGBA8;
            gl_format_alpha = GL_RGBA8;
            gl_format_opaque = GL_RGB8;
        }

        flags = SDL_OPENGL;
        if (!init_window)
            flags |= SDL_FULLSCREEN;

        width = resolutions[res][0];
        height = resolutions[res][1];

        sdl_screen = SDL_SetVideoMode(width, height, bpp, flags);
        if (sdl_screen)
            break;

        L_Printf("Failed to set %ix%ix%i GL mode: %s\n", width, height, bpp,
            SDL_GetError());

        if (res > 0)
            res--;
        else if (bpp == 32) {
            bpp = 16;
            res = origres;
        } else
            throw LError("Couldn't find a suitable videomode");
    }

    SDL_WM_SetCaption("Return to the Shadows", "Return to the Shadows");

    // Load post-1.1 GL functions (TODO: extensions)
    GL_PostLoadLibrary();

    // Setup input system
    SDL_EnableUNICODE(1);
    SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);

    SDL_ShowCursor(SDL_DISABLE);

    framestart = SDL_GetTicks();
    frametime = 0;
}

/*
==============
HAL_SdlGl::Shutdown

Shutdown SDL and OpenGL
==============
*/
void HAL_SdlGl::Shutdown()
{
    piclump_t *lump;

    while(gl_piclumps) {
        lump = gl_piclumps;
        gl_piclumps = lump->next;

        L_Printf("piclump leak!\n");

        GL_DeleteTexture(lump->texels);
        L_Free(lump);
    }

    SDL_Quit();
}

/*
==============
HAL_SdlGl::StartFrame

Prepare GL for drawing, get the current time, etc...
==============
*/
void HAL_SdlGl::StartFrame()
{
    int time;

    // Handle timing
    time = SDL_GetTicks();

    frametime = time - framestart;
    if (frametime > 200)
        frametime = 200;
    framestart = time;

    // Setup GL
    pglViewport(0, 0, sdl_screen->w, sdl_screen->h);

    pglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    pglEnable(GL_BLEND);

    pglAlphaFunc(GL_GREATER, 0);
    pglEnable(GL_ALPHA_TEST);

    // for integrity checks

    pglClearColor(0.0, 1.0, 0.0, 0.0);
    pglClear(GL_COLOR_BUFFER_BIT);

    pglColor4f(1.0, 1.0, 1.0, 1.0);

}

/*
==============
HAL_SdlGl::EndFrame

Swap GL buffers to finalize the current frame
==============
*/
void HAL_SdlGl::EndFrame()
{
    static double ftimeaccum = 0;

    ftimeaccum = ftimeaccum * 0.95 + frametime * 0.05;

    if (showfps) {
        HFont *font;
        char buf[64];

        font = HFont::Get("font");
        SetDrawRect(0, 0, 640, 480, 0, 0);

        if (ftimeaccum < 1)
            ftimeaccum = 1;
        sprintf(buf, "%i ms %5.1f fps", frametime, 1000.0 / ftimeaccum);
        font->DrawString(640, 0, buf, halign_right, 0, 0);
        font->Release();
    }

    SDL_GL_SwapBuffers();
}

/*
==============
HAL_SdlGl::Input

Run the message queue
==============
*/
void HAL_SdlGl::Input(const hal_inputcb_t *cb)
{
    SDL_Event ev;
    int c;
    int x, y;

    while(SDL_PollEvent(&ev)) {
        switch(ev.type) {
        case SDL_KEYDOWN:
        case SDL_KEYUP:
            if ((int)ev.key.keysym.sym == KEY_F10) {
                if (ev.type == SDL_KEYDOWN)
                    showfps = !showfps;
                break;
            }
            if ((int)ev.key.keysym.sym == KEY_F9) {
                if (ev.type == SDL_KEYDOWN)
                    HAL_Screenshot("shot");
                break;
            }

            c = ev.key.keysym.unicode;
            if (c < 32 || c >= 128)
                c = 0;

            if (cb->Key)
                cb->Key(ev.key.keysym.sym, (char)c, ev.type == SDL_KEYDOWN);
            break;

        case SDL_MOUSEBUTTONDOWN:
        case SDL_MOUSEBUTTONUP:
            switch(ev.button.button) {
            default:
            case SDL_BUTTON_LEFT: c = 0; break;
            case SDL_BUTTON_RIGHT: c = 1; break;
            case SDL_BUTTON_MIDDLE: c = 2; break;
            }

            if (cb->MouseButton) {
                x = (int)(((float)ev.button.x / sdl_screen->w) * orthorect.size[0]);
                y = (int)(((float)ev.button.y / sdl_screen->h) * orthorect.size[1]);
                cb->MouseButton(c, x, y, ev.type == SDL_MOUSEBUTTONDOWN);
            }
            break;

        case SDL_MOUSEMOTION:
            if (cb->MouseMove) {
                x = (int)(((float)ev.motion.x / sdl_screen->w) * orthorect.size[0]);
                y = (int)(((float)ev.motion.y / sdl_screen->h) * orthorect.size[1]);
                cb->MouseMove(x, y);
            }
            break;

        case SDL_QUIT:
            cb->QuitRequest();
            break;
        }
    }
}


/*
==============
HAL_SdlGl::GetKeyState

The SDL version itself is only a return variable, so
we shouldn't have to cache.
==============
*/
byte *HAL_SdlGl::GetKeyState()
{
    return SDL_GetKeyState(0);
}

unsigned HAL_SdlGl::GetMouseState(int *px, int *py)
{
    unsigned resbits;
    byte bits;

    bits = SDL_GetMouseState(px, py);

    *px = (int)(((float)*px / sdl_screen->w) * orthorect.size[0]);
    *py = (int)(((float)*py / sdl_screen->h) * orthorect.size[1]);

    resbits = 0;
    if (bits & SDL_BUTTON_LMASK)
        resbits |= 1;
    if (bits & SDL_BUTTON_RMASK)
        resbits |= 2;
    if (bits & SDL_BUTTON_MMASK)
        resbits |= 4;

    return resbits;
}

/*
==============
HAL_SdlGl::Ortho

Enter ortho drawing mode
==============
*/
void HAL_SdlGl::Ortho(int x, int y, int w, int h)
{
    drawrect.pos[0] = orthorect.pos[0] = x;
    drawrect.pos[1] = orthorect.pos[1] = y;
    drawrect.size[0] = orthorect.size[0] = w;
    drawrect.size[1] = orthorect.size[1] = h;
    drawclip = false;

    pglMatrixMode(GL_PROJECTION);
    pglLoadIdentity();
    pglOrtho(x, x+w, y+h, y, -1, 1);

    pglMatrixMode(GL_MODELVIEW);
    pglLoadIdentity();

}

/*
==============
HAL_SdlGl::SetAlphaOnly

Disable/Enable the color component of source textures (for shadows)
==============
*/
void HAL_SdlGl::SetAlphaOnly(bool on)
{
    if (on)
        pglBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_ALPHA);
    else
        pglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}

/*
==============
HAL_SdlGl::SetDrawRect

Set the drawing/clipping rectangle
==============
*/
void HAL_SdlGl::SetDrawRect(int x, int y, int w, int h, int ofsx, int ofsy)
{
    if (x != orthorect.pos[0] || w != orthorect.size[0] ||
        y != orthorect.pos[1] || h != orthorect.size[1])
        drawclip = true;
    else
        drawclip = false;

    drawrect.pos[0] = x;
    drawrect.pos[1] = y;
    drawrect.size[0] = w;
    drawrect.size[1] = h;

    drawofsx = ofsx;
    drawofsy = ofsy;
}

/*
==============
HAL_SdlGl::FillRect

Draw a filled rectangle
==============
*/
void HAL_SdlGl::FillRect(int x, int y, int w, int h, int r, int g, int b, int a)
{
    LRect rc(x, y, w, h);

    if (drawclip)
        drawrect.ClipLocal(&rc);

    if (rc.size[0] <= 0 || rc.size[1] <= 0)
        return;

    drawrect.RectToWorld(&rc.pos[0], &rc.pos[1]);

    GL_TexEnable(false);

    pglColor4ub((byte)r, (byte)g, (byte)b, (byte)a);

    pglBegin(GL_QUADS);
        pglVertex2i(rc.pos[0], rc.pos[1]);
        pglVertex2i(rc.pos[0]+rc.size[0], rc.pos[1]);
        pglVertex2i(rc.pos[0]+rc.size[0], rc.pos[1]+rc.size[1]);
        pglVertex2i(rc.pos[0], rc.pos[1]+rc.size[1]);
    pglEnd();
}

/*
==============
HAL_SdlGl::DrawLine

Draws a colored line
==============
*/
void HAL_SdlGl::DrawLine(float x1, float y1, float x2, float y2, int r, int g, int b, int a)
{
    float dx, dy, t;

    if (drawclip)
    {
        // X-based clipping
        if (x1 > x2) {
            t = x1; x1 = x2; x2 = t;
            t = y1; y1 = y2; y2 = t;
        }

        if (x1 > drawrect.size[0] || x2 < 0)
            return;

        dx = x2 - x1;
        dy = y2 - y1;
        if (x1 < 0) {
            t = (0 - x1) / dx;
            x1 = 0;
            y1 += t * dy;
        }
        if (x2 > drawrect.size[0]) {
            t = (drawrect.size[0] - x2) / dx;
            x2 = drawrect.size[0];
            y2 += t * dy;
        }

        // Y-based clipping
        if (y1 > y2) {
            t = x1; x1 = x2; x2 = t;
            t = y1; y1 = y2; y2 = t;
        }

        if (y1 > drawrect.size[1] || y2 < 0)
            return;

        dx = x2 - x1;
        dy = y2 - y1;
        if (y1 < 0) {
            t = (0 - y1) / dy;
            y1 = 0;
            x1 += t * dx;
        }
        if (y2 > drawrect.size[1]) {
            t = (drawrect.size[1] - y2) / dy;
            y2 = drawrect.size[1];
            x2 += t * dx;
        }
    }

    drawrect.RectToWorld(&x1, &y1);
    drawrect.RectToWorld(&x2, &y2);

    // Actually render the thing
    GL_TexEnable(false);

    pglColor4ub((byte)r, (byte)g, (byte)b, (byte)a);

    pglBegin(GL_LINES);
        pglVertex2f(x1, y1);
        pglVertex2f(x2, y2);
    pglEnd();
}

/*
==============
HAL_SdlGl::LoadPicture

Perform a canonical bitmap load for the picture, then decide whether
the single-texture or lump implementation should be used.
==============
*/
void HAL_SdlGl::LoadPicture(HPicture *pic)
{
    byte *pixels;
    int size[2];
    int type;
    bool bBitmap;

    // Load the bitmap
    type = pic->m_iType;
    pixels = HAL_CanonicalBitmapLoad(pic->m_szName, size, &type, pic->m_colorkey);

    // Load either as piclet or as full texture
    bBitmap = false;
    if (size[0] >= PICLUMP_SIZE || size[1] >= PICLUMP_SIZE)
        bBitmap = true;

    try
    {
        if (bBitmap)
            pic->m_pImpl = new HBitmapImpl(pic, size, type, pixels);
        else
            pic->m_pImpl = new HPicletImpl(pic, size, type, pixels);
    }
    catch(...)
    {
        L_Free(pixels);
        throw;
    }

    pic->size[0] = size[0];
    pic->size[1] = size[1];

    // Create the bitmask if desired
    if (type & __pic_bitmask)
        pic->bitmask = L_ImageBitmask(pixels, size);

    L_Free(pixels);
}

/*
==============
HAL_SdlGl::UnloadPicture

Free resources allocated by LoadPicture
==============
*/
void HAL_SdlGl::UnloadPicture(HPicture *pic)
{
    lassert(pic->m_pImpl);

    delete pic->m_pImpl;
    if (pic->bitmask)
        L_Free(pic->bitmask);
}

/*
==============
HAL_SdlGl::Screenshot

Make a screenshot; fill in the size, and return the RGBA data in
heap memory (needs to be freed by caller)
==============
*/
byte *HAL_SdlGl::Screenshot(int *size)
{
    byte *data;
    byte *a, *b;
    u32 tmp;
    int x, y;

    size[0] = sdl_screen->w;
    size[1] = sdl_screen->h;

    data = (byte *)L_Malloc(4 * size[0]*size[1], TAG_HAL);

    pglReadBuffer(GL_FRONT);
    pglReadPixels(0, 0, size[0], size[1], GL_RGBA, GL_UNSIGNED_BYTE, data);

    // Need to flip lines
    a = data;
    b = data + 4*size[0]*size[1];
    y = size[1] / 2;
    while(y--) {
        b -= 4*size[0];
        for(x = 0; x < size[0]; x++) {
            tmp = *(u32 *)a;
            *(u32 *)a = *(u32 *)b;
            *(u32 *)b = tmp;
            a += 4;
            b += 4;
        }
        b -= 4*size[0];
    }

    return data;
}