/******************************Module*Header*******************************\
* Module Name: metasup.c
*
* OpenGL metafile support
*
* History:
*  Thu Feb 23 15:27:47 1995	-by-	Drew Bliss [drewb]
*   Created
*
* Copyright (c) 1995 Microsoft Corporation
\**************************************************************************/

#include "precomp.h"
#pragma hdrstop

#include <ntpsapi.h>
#include <wingdip.h>

#include "global.h"
#include <glgenwin.h>

#include "metasup.h"

#if defined(GL_METAFILE)

#include <glmf.h>
#include <encoding.h>

GLCLTPROCTABLE gcptGlsProcTable;
GLEXTPROCTABLE geptGlsExtProcTable;

// Functions in GL which we will do device coordinate translation for
typedef struct _GLDEVICEPROCS
{
    void (APIENTRY *glBitmap)(GLsizei width, GLsizei height,
                              GLfloat xorig, GLfloat yorig,
                              GLfloat xmove, GLfloat ymove,
                              const GLubyte *bitmap);
    void (APIENTRY *glCopyPixels)(GLint x, GLint y,
                                  GLsizei width, GLsizei height,
                                  GLenum type);
    void (APIENTRY *glCopyTexImage1D)(GLenum target, GLint level,
                                      GLenum internalformat,
                                      GLint x, GLint y,
                                      GLsizei width, GLint border);
    void (APIENTRY *glCopyTexImage2D)(GLenum target, GLint level,
                                      GLenum internalformat,
                                      GLint x, GLint y,
                                      GLsizei width, GLsizei height,
                                      GLint border);
    void (APIENTRY *glCopyTexSubImage1D)(GLenum target, GLint level,
                                         GLint xoffset, GLint x, GLint y,
                                         GLsizei width);
    void (APIENTRY *glCopyTexSubImage2D)(GLenum target, GLint level,
                                         GLint xoffset, GLint yoffset,
                                         GLint x, GLint y,
                                         GLsizei width, GLsizei height);
    void (APIENTRY *glDrawPixels)(GLsizei width, GLsizei height,
                                  GLenum format, GLenum type,
                                  const GLvoid *pixels);
    void (APIENTRY *glLineWidth)(GLfloat width);
    void (APIENTRY *glPointSize)(GLfloat size);
    void (APIENTRY *glScissor)(GLint x, GLint y,
                               GLsizei width, GLsizei height);
    void (APIENTRY *glViewport)(GLint x, GLint y,
                                GLsizei w, GLsizei h);
} GLDEVICEPROCS;
#define GL_DEVICE_PROCS (sizeof(GLDEVICEPROCS)/sizeof(PROC))

// Opcode for device procs
static GLSopcode glsopDeviceProcs[GL_DEVICE_PROCS] =
{
    GLS_OP_glBitmap,
    GLS_OP_glCopyPixels,
    GLS_OP_glCopyTexImage1D,
    GLS_OP_glCopyTexImage2D,
    GLS_OP_glCopyTexSubImage1D,
    GLS_OP_glCopyTexSubImage2D,
    GLS_OP_glDrawPixels,
    GLS_OP_glLineWidth,
    GLS_OP_glPointSize,
    GLS_OP_glScissor,
    GLS_OP_glViewport
};

static GLDEVICEPROCS gdpGlsActual;

/*****************************Private*Routine******************************\
*
* GLS recording callbacks
*
* Perfoms any necessary work when capturing a call
*
* History:
*  Mon Mar 27 14:21:09 1995	-by-	Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

void GlsBitmapIn(GLsizei width, GLsizei height,
                 GLfloat xorig, GLfloat yorig,
                 GLfloat xmove, GLfloat ymove, const GLubyte *bitmap)
{
    PLRC plrc;
    RECTL rcl;

    plrc = GLTEB_CLTCURRENTRC();
    ASSERTOPENGL(plrc != NULL, "GlsBitmapIn: No current RC!\n");

    // Record bounds for the bitmap
    rcl.left = 0;
    rcl.top = 0;
    rcl.right = width;
    rcl.bottom = height;
    plrc->prclGlsBounds = &rcl;

    gdpGlsActual.glBitmap(width, height, xorig, yorig, xmove, ymove, bitmap);

    plrc->prclGlsBounds = NULL;
}

void GlsDrawPixelsIn(GLsizei width, GLsizei height,
                     GLenum format, GLenum type,
                     const GLvoid *pixels)
{
    PLRC plrc;
    RECTL rcl;

    plrc = GLTEB_CLTCURRENTRC();
    ASSERTOPENGL(plrc != NULL, "GlsBitmapIn: No current RC!\n");

    // Record bounds for the bitmap
    rcl.left = 0;
    rcl.top = 0;
    rcl.right = width;
    rcl.bottom = height;
    plrc->prclGlsBounds = &rcl;

    gdpGlsActual.glDrawPixels(width, height, format, type, pixels);

    plrc->prclGlsBounds = NULL;
}

void GlsViewportIn(GLint x, GLint y, GLsizei width, GLsizei height)
{
    RECTL rcl;
    PLRC plrc;
    
    plrc = GLTEB_CLTCURRENTRC();
    ASSERTOPENGL(plrc != NULL, "GlsViewportIn: No current RC!\n");
    
    // Send the bounds on
    // The rect is inclusive-exclusive while the incoming parameters
    // are inclusive-inclusive
    rcl.left = x;
    rcl.right = x+width+1;
    rcl.top = y;
    rcl.bottom = y+height+1;
    if (!GlGdiAddGlsBounds(plrc->gwidCreate.hdc, &rcl))
    {
        ASSERTOPENGL(FALSE, "GdiAddGlsBounds failed");
    }

    gdpGlsActual.glViewport(x, y, width, height);
}

// glViewport is the only device-dependent function that we need to
// do work for on input
static GLDEVICEPROCS gdpInput =
{
    GlsBitmapIn,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    GlsDrawPixelsIn,
    NULL,
    NULL,
    NULL,
    GlsViewportIn
};

/*****************************Private*Routine******************************\
*
* MetaLoadGls
*
* Loads glmf32.dll for metafile use
*
* History:
*  Thu Feb 23 17:40:59 1995	-by-	Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

static char *pszGlsEntryPoints[] =
{
    "glsBeginCapture",
    "glsBinary",
    "glsCallArrayInContext",
    "glsCaptureFlags",
    "glsCaptureFunc",
    "glsCommandFunc",
    "glsContext",
    "glsDeleteContext",
    "glsEndCapture",
    "glsFlush",
    "glsGenContext",
    "glsGetCaptureDispatchTable",
    "glsGetCaptureExecTable",
    "glsGetCommandAlignment",
    "glsGetCurrentContext",
    "glsUpdateCaptureExecTable",
    "glsWriteFunc",
    "glsBeginGLS",
    "glsBlock",
    "glsCallStream",
    "glsEndGLS",
    "glsError",
    "glsGLRC",
    "glsGLRCLayer",
    "glsHeaderGLRCi",
    "glsHeaderLayerf",
    "glsHeaderLayeri",
    "glsHeaderf",
    "glsHeaderfv",
    "glsHeaderi",
    "glsHeaderiv",
    "glsHeaderubz",
    "glsRequireExtension",
    "glsUnsupportedCommand",
    "glsAppRef",
    "glsBeginObj",
    "glsCharubz",
    "glsComment",
    "glsDisplayMapfv",
    "glsEndObj",
    "glsNumb",
    "glsNumbv",
    "glsNumd",
    "glsNumdv",
    "glsNumf",
    "glsNumfv",
    "glsNumi",
    "glsNumiv",
    "glsNuml",
    "glsNumlv",
    "glsNums",
    "glsNumsv",
    "glsNumub",
    "glsNumubv",
    "glsNumui",
    "glsNumuiv",
    "glsNumul",
    "glsNumulv",
    "glsNumus",
    "glsNumusv",
    "glsPad",
    "glsSwapBuffers"
};
#define GLS_ENTRY_POINT_STRINGS (sizeof(pszGlsEntryPoints)/sizeof(char *))

typedef struct _GLSENTRYPOINTS
{
    GLboolean (APIENTRY *glsBeginCapture)(const GLubyte *, GLSenum,
                                          GLbitfield);
    GLSenum   (APIENTRY *glsBinary)(GLboolean);
    void      (APIENTRY *glsCallArrayInContext)(GLuint, GLSenum, size_t,
                                                const GLubyte *);
    void      (APIENTRY *glsCaptureFlags)(GLSopcode, GLbitfield);
    void      (APIENTRY *glsCaptureFunc)(GLSenum, GLScaptureFunc);
    void      (APIENTRY *glsCommandFunc)(GLSopcode, GLSfunc);
    void      (APIENTRY *glsContext)(GLuint);
    void      (APIENTRY *glsDeleteContext)(GLuint);
    void      (APIENTRY *glsEndCapture)(void);
    void      (APIENTRY *glsFlush)(GLSenum);
    GLuint    (APIENTRY *glsGenContext)(void);
    void      (APIENTRY *glsGetCaptureDispatchTable)(GLCLTPROCTABLE *,
                                                     GLEXTPROCTABLE *);
    void      (APIENTRY *glsGetCaptureExecTable)(GLCLTPROCTABLE *,
                                                 GLEXTPROCTABLE *);
    GLScommandAlignment *
              (APIENTRY *glsGetCommandAlignment)(GLSopcode, GLSenum,
                                                 GLScommandAlignment *);
    GLuint    (APIENTRY *glsGetCurrentContext)(void);
    void      (APIENTRY *glsUpdateCaptureExecTable)(GLCLTPROCTABLE *,
                                                    GLEXTPROCTABLE *);
    void      (APIENTRY *glsWriteFunc)(GLSwriteFunc);

    // The following are only used in glsCommandFunc and so don't
    // require real prototypes
    GLSfunc glsBeginGLS;
    GLSfunc glsBlock;
    GLSfunc glsCallStream;
    GLSfunc glsEndGLS;
    GLSfunc glsError;
    GLSfunc glsGLRC;
    GLSfunc glsGLRCLayer;
    GLSfunc glsHeaderGLRCi;
    GLSfunc glsHeaderLayerf;
    GLSfunc glsHeaderLayeri;
    GLSfunc glsHeaderf;
    GLSfunc glsHeaderfv;
    GLSfunc glsHeaderi;
    GLSfunc glsHeaderiv;
    GLSfunc glsHeaderubz;
    GLSfunc glsRequireExtension;
    GLSfunc glsUnsupportedCommand;
    GLSfunc glsAppRef;
    GLSfunc glsBeginObj;
    GLSfunc glsCharubz;
    GLSfunc glsComment;
    GLSfunc glsDisplayMapfv;
    GLSfunc glsEndObj;
    GLSfunc glsNumb;
    GLSfunc glsNumbv;
    GLSfunc glsNumd;
    GLSfunc glsNumdv;
    GLSfunc glsNumf;
    GLSfunc glsNumfv;
    GLSfunc glsNumi;
    GLSfunc glsNumiv;
    GLSfunc glsNuml;
    GLSfunc glsNumlv;
    GLSfunc glsNums;
    GLSfunc glsNumsv;
    GLSfunc glsNumub;
    GLSfunc glsNumubv;
    GLSfunc glsNumui;
    GLSfunc glsNumuiv;
    GLSfunc glsNumul;
    GLSfunc glsNumulv;
    GLSfunc glsNumus;
    GLSfunc glsNumusv;
    GLSfunc glsPad;
    GLSfunc glsSwapBuffers;
} GLSENTRYPOINTS;
#define GLS_ENTRY_POINTS (sizeof(GLSENTRYPOINTS)/sizeof(void *))

static GLSENTRYPOINTS gepGlsFuncs = {NULL};
static HMODULE hGlsDll = NULL;

BOOL MetaLoadGls(void)
{
    HMODULE hdll;
    BOOL bRet = FALSE;
    GLSENTRYPOINTS gep;
    PROC *ppfn, *ppfnActual, *ppfnInput;
    GLSfunc *pgfnNormal, *pgfnExt;
    int i;
    
    ASSERTOPENGL(GLS_ENTRY_POINT_STRINGS == GLS_ENTRY_POINTS,
                 "GLS entry point strings/pointers mismatch\n");
    
    ENTERCRITICALSECTION(&semLocal);

    if (hGlsDll != NULL)
    {
        bRet = TRUE;
        goto Exit;
    }

    hdll = LoadLibrary(__TEXT("glmf32.dll"));
    if (hdll == NULL)
    {
        WARNING1("Unable to load glmf32.dll, %d\n", GetLastError());
        goto Exit;
    }
    
    ppfn = (PROC *)&gep;
    for (i = 0; i < GLS_ENTRY_POINTS; i++)
    {
        if (!(*ppfn = GetProcAddress(hdll, pszGlsEntryPoints[i])))
        {
            WARNING1("glmf32.dll is missing '%s'\n", pszGlsEntryPoints[i]);
            FreeLibrary(hdll);
            goto Exit;
        }
        
        ppfn++;
    }

    // The table copied out is constant as long as the DLL is loaded
    gep.glsGetCaptureDispatchTable(&gcptGlsProcTable, &geptGlsExtProcTable);
    
    // Patch the table for certain functions to allow us to
    // do some coordinate conversion and bounds accumlation
    ppfnActual = (PROC *)&gdpGlsActual;
    ppfnInput = (PROC *)&gdpInput;
    for (i = 0; i < GL_DEVICE_PROCS; i++)
    {
        if (*ppfnInput != NULL)
        {
            ppfn = ((PROC *)&gcptGlsProcTable.glDispatchTable)+
                glsopDeviceProcs[i]-GLS_OP_glNewList;
            *ppfnActual = *ppfn;
            *ppfn = *ppfnInput;
        }

        ppfnActual++;
        ppfnInput++;
    }
    
    gepGlsFuncs = gep;
    hGlsDll = hdll;
    bRet = TRUE;
    
 Exit:
    LEAVECRITICALSECTION(&semLocal);
    return bRet;
}

/*****************************Private*Routine******************************\
*
* MetaGlProcTables
*
* Returns the GL dispatch tables to use for metafile RC's
*
* History:
*  Thu Feb 23 17:40:25 1995	-by-	Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

void MetaGlProcTables(PGLCLTPROCTABLE *ppgcpt, PGLEXTPROCTABLE *ppgept)
{
    ASSERTOPENGL(hGlsDll != NULL, "MetaGlProcTables: GLS not loaded\n");
    *ppgcpt = &gcptGlsProcTable;
    *ppgept = &geptGlsExtProcTable;
}

/******************************Public*Routine******************************\
*
* MetaSetCltProcTable
*
* Update GLS's generic dispatch tables
*
* History:
*  Fri Jan 05 16:40:31 1996	-by-	Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

void MetaSetCltProcTable(GLCLTPROCTABLE *pgcpt, GLEXTPROCTABLE *pgept)
{
    gepGlsFuncs.glsUpdateCaptureExecTable(pgcpt, pgept);
}

/******************************Public*Routine******************************\
*
* MetaGetCltProcTable
*
* Retrieves GLS's generic dispatch tables
*
* History:
*  Fri Jan 05 19:14:18 1996	-by-	Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

void MetaGetCltProcTable(GLCLTPROCTABLE *pgcpt, GLEXTPROCTABLE *pgept)
{
    gepGlsFuncs.glsGetCaptureExecTable(pgcpt, pgept);
}

/*****************************Private*Routine******************************\
*
* GlsWriter
*
* GLS write function for metafile support
*
* History:
*  Thu Feb 23 15:49:03 1995	-by-	Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

size_t GlsWriter(size_t cb, CONST BYTE *pb)
{
    PLRC plrc;

#if 0
    DbgPrint("GlsWriter(%d)\n", cb);
#endif
    
    plrc = GLTEB_CLTCURRENTRC();
    if( plrc == NULL ) 
    {
        DBGERROR( "GlsWriter: No current RC!\n");
        return 0;
    }
    
    ASSERTOPENGL(plrc->gwidCreate.hdc != NULL,
                 "GlsWriter: hdcCreate is NULL\n");
    ASSERTOPENGL(gepGlsFuncs.glsGetCurrentContext() ==
                 plrc->uiGlsCaptureContext,
                 "GlsWriter: Wrong GLS context\n");
    ASSERTOPENGL(plrc->fCapturing == TRUE,
                 "GlsWriter: Not capturing\n");

    if (GlGdiAddGlsRecord(plrc->gwidCreate.hdc,
                          cb, (BYTE *)pb, plrc->prclGlsBounds))
    {
        return cb;
    }
    else
    {
        return 0;
    }
}

/*****************************Private*Routine******************************\
*
* GlsFlush
*
* Post-command GLS callback to flush the GLS stream
*
* History:
*  Fri Feb 24 10:12:49 1995	-by-	Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

void GlsFlush(GLSopcode op)
{
    gepGlsFuncs.glsFlush(GLS_LAST);
}

/*****************************Private*Routine******************************\
*
* MetaRcBegin
*
* Start capturing on a metafile
*
* History:
*  Thu Feb 23 18:35:32 1995	-by-	Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

BOOL MetaRcBegin(PLRC plrc, HDC hdc)
{
    PLRC plrcOld;

    // The GLS commands here will cause data to be written, which
    // requires a current RC.  The RC is only used for data storage
    // so we don't need to set the proc table
    plrcOld = GLTEB_CLTCURRENTRC();
    GLTEB_SET_CLTCURRENTRC(plrc);
    
    // Set capturing first because the block commands will cause
    // GlsWriter calls
    plrc->fCapturing = TRUE;

    // Start recording
    if (!gepGlsFuncs.glsBeginCapture("", gepGlsFuncs.glsBinary(GL_FALSE),
                                     GLS_NONE))
    {
        plrc->fCapturing = FALSE;
        SetLastError(ERROR_NOT_ENOUGH_MEMORY);
        GLTEB_SET_CLTCURRENTRC(plrcOld);
        return FALSE;
    }

    GLTEB_SET_CLTCURRENTRC(plrcOld);
    
    return TRUE;
}

/*****************************Private*Routine******************************\
*
* MetaRcEnd
*
* Stop capturing on a metafile RC
*
* History:
*  Thu Feb 23 17:13:48 1995	-by-	Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

void MetaRcEnd(PLRC plrc)
{
    PLRC plrcOld;
    
    // The GLS commands here will cause data to be written, which
    // requires a current RC.  The RC is only used for data storage
    // so we don't need to set the proc table
    plrcOld = GLTEB_CLTCURRENTRC();
    GLTEB_SET_CLTCURRENTRC(plrc);
    
    gepGlsFuncs.glsEndCapture();
    
    plrc->fCapturing = FALSE;
    
    GLTEB_SET_CLTCURRENTRC(plrcOld);
}

// Table of operations which GLS should execute when capturing
// in order to return information
// Currently all of them are in the list
//          Should we attempt to do only the critical calls?
static GLSopcode opExecuteBits[] =
{
    GLS_OP_glAccum,
    GLS_OP_glAlphaFunc,
    GLS_OP_glAreTexturesResidentEXT,
    GLS_OP_glArrayElementEXT,
    GLS_OP_glBegin,
    GLS_OP_glBindTextureEXT,
    GLS_OP_glBitmap,
    GLS_OP_glBlendColorEXT,
    GLS_OP_glBlendEquationEXT,
    GLS_OP_glBlendFunc,
    GLS_OP_glCallList,
    GLS_OP_glCallLists,
    GLS_OP_glClear,
    GLS_OP_glClearAccum,
    GLS_OP_glClearColor,
    GLS_OP_glClearDepth,
    GLS_OP_glClearIndex,
    GLS_OP_glClearStencil,
    GLS_OP_glClipPlane,
    GLS_OP_glColor3b,
    GLS_OP_glColor3bv,
    GLS_OP_glColor3d,
    GLS_OP_glColor3dv,
    GLS_OP_glColor3f,
    GLS_OP_glColor3fv,
    GLS_OP_glColor3i,
    GLS_OP_glColor3iv,
    GLS_OP_glColor3s,
    GLS_OP_glColor3sv,
    GLS_OP_glColor3ub,
    GLS_OP_glColor3ubv,
    GLS_OP_glColor3ui,
    GLS_OP_glColor3uiv,
    GLS_OP_glColor3us,
    GLS_OP_glColor3usv,
    GLS_OP_glColor4b,
    GLS_OP_glColor4bv,
    GLS_OP_glColor4d,
    GLS_OP_glColor4dv,
    GLS_OP_glColor4f,
    GLS_OP_glColor4fv,
    GLS_OP_glColor4i,
    GLS_OP_glColor4iv,
    GLS_OP_glColor4s,
    GLS_OP_glColor4sv,
    GLS_OP_glColor4ub,
    GLS_OP_glColor4ubv,
    GLS_OP_glColor4ui,
    GLS_OP_glColor4uiv,
    GLS_OP_glColor4us,
    GLS_OP_glColor4usv,
    GLS_OP_glColorMask,
    GLS_OP_glColorMaterial,
    GLS_OP_glColorPointerEXT,
    GLS_OP_glColorSubTableEXT,
    GLS_OP_glDrawRangeElementsWIN,
    GLS_OP_glColorTableParameterfvSGI,
    GLS_OP_glColorTableParameterivSGI,
    GLS_OP_glColorTableEXT,
    GLS_OP_glConvolutionFilter1DEXT,
    GLS_OP_glConvolutionFilter2DEXT,
    GLS_OP_glConvolutionParameterfEXT,
    GLS_OP_glConvolutionParameterfvEXT,
    GLS_OP_glConvolutionParameteriEXT,
    GLS_OP_glConvolutionParameterivEXT,
    GLS_OP_glCopyColorTableSGI,
    GLS_OP_glCopyConvolutionFilter1DEXT,
    GLS_OP_glCopyConvolutionFilter2DEXT,
    GLS_OP_glCopyPixels,
    GLS_OP_glCopyTexImage1DEXT,
    GLS_OP_glCopyTexImage2DEXT,
    GLS_OP_glCopyTexSubImage1DEXT,
    GLS_OP_glCopyTexSubImage2DEXT,
    GLS_OP_glCopyTexSubImage3DEXT,
    GLS_OP_glCullFace,
    GLS_OP_glDeleteLists,
    GLS_OP_glDeleteTexturesEXT,
    GLS_OP_glDepthFunc,
    GLS_OP_glDepthMask,
    GLS_OP_glDepthRange,
    GLS_OP_glDetailTexFuncSGIS,
    GLS_OP_glDisable,
    GLS_OP_glDrawArraysEXT,
    GLS_OP_glDrawBuffer,
    GLS_OP_glDrawPixels,
    GLS_OP_glEdgeFlag,
    GLS_OP_glEdgeFlagPointerEXT,
    GLS_OP_glEdgeFlagv,
    GLS_OP_glEnable,
    GLS_OP_glEnd,
    GLS_OP_glEndList,
    GLS_OP_glEvalCoord1d,
    GLS_OP_glEvalCoord1dv,
    GLS_OP_glEvalCoord1f,
    GLS_OP_glEvalCoord1fv,
    GLS_OP_glEvalCoord2d,
    GLS_OP_glEvalCoord2dv,
    GLS_OP_glEvalCoord2f,
    GLS_OP_glEvalCoord2fv,
    GLS_OP_glEvalMesh1,
    GLS_OP_glEvalMesh2,
    GLS_OP_glEvalPoint1,
    GLS_OP_glEvalPoint2,
    GLS_OP_glFeedbackBuffer,
    GLS_OP_glFinish,
    GLS_OP_glFlush,
    GLS_OP_glFogf,
    GLS_OP_glFogfv,
    GLS_OP_glFogi,
    GLS_OP_glFogiv,
    GLS_OP_glFrontFace,
    GLS_OP_glFrustum,
    GLS_OP_glGenLists,
    GLS_OP_glGenTexturesEXT,
    GLS_OP_glGetBooleanv,
    GLS_OP_glGetClipPlane,
    GLS_OP_glGetColorTableParameterfvEXT,
    GLS_OP_glGetColorTableParameterivEXT,
    GLS_OP_glGetColorTableEXT,
    GLS_OP_glGetConvolutionFilterEXT,
    GLS_OP_glGetConvolutionParameterfvEXT,
    GLS_OP_glGetConvolutionParameterivEXT,
    GLS_OP_glGetDetailTexFuncSGIS,
    GLS_OP_glGetDoublev,
    GLS_OP_glGetError,
    GLS_OP_glGetFloatv,
    GLS_OP_glGetHistogramEXT,
    GLS_OP_glGetHistogramParameterfvEXT,
    GLS_OP_glGetHistogramParameterivEXT,
    GLS_OP_glGetIntegerv,
    GLS_OP_glGetLightfv,
    GLS_OP_glGetLightiv,
    GLS_OP_glGetMapdv,
    GLS_OP_glGetMapfv,
    GLS_OP_glGetMapiv,
    GLS_OP_glGetMaterialfv,
    GLS_OP_glGetMaterialiv,
    GLS_OP_glGetMinmaxEXT,
    GLS_OP_glGetMinmaxParameterfvEXT,
    GLS_OP_glGetMinmaxParameterivEXT,
    GLS_OP_glGetPixelMapfv,
    GLS_OP_glGetPixelMapuiv,
    GLS_OP_glGetPixelMapusv,
    GLS_OP_glGetPointervEXT,
    GLS_OP_glGetPolygonStipple,
    GLS_OP_glGetSeparableFilterEXT,
    GLS_OP_glGetSharpenTexFuncSGIS,
    GLS_OP_glGetString,
    GLS_OP_glGetTexColorTableParameterfvSGI,
    GLS_OP_glGetTexColorTableParameterivSGI,
    GLS_OP_glGetTexEnvfv,
    GLS_OP_glGetTexEnviv,
    GLS_OP_glGetTexGendv,
    GLS_OP_glGetTexGenfv,
    GLS_OP_glGetTexGeniv,
    GLS_OP_glGetTexImage,
    GLS_OP_glGetTexLevelParameterfv,
    GLS_OP_glGetTexLevelParameteriv,
    GLS_OP_glGetTexParameterfv,
    GLS_OP_glGetTexParameteriv,
    GLS_OP_glHint,
    GLS_OP_glHistogramEXT,
    GLS_OP_glIndexMask,
    GLS_OP_glIndexPointerEXT,
    GLS_OP_glIndexd,
    GLS_OP_glIndexdv,
    GLS_OP_glIndexf,
    GLS_OP_glIndexfv,
    GLS_OP_glIndexi,
    GLS_OP_glIndexiv,
    GLS_OP_glIndexs,
    GLS_OP_glIndexsv,
    GLS_OP_glInitNames,
    GLS_OP_glIsEnabled,
    GLS_OP_glIsList,
    GLS_OP_glIsTextureEXT,
    GLS_OP_glLightModelf,
    GLS_OP_glLightModelfv,
    GLS_OP_glLightModeli,
    GLS_OP_glLightModeliv,
    GLS_OP_glLightf,
    GLS_OP_glLightfv,
    GLS_OP_glLighti,
    GLS_OP_glLightiv,
    GLS_OP_glLineStipple,
    GLS_OP_glLineWidth,
    GLS_OP_glListBase,
    GLS_OP_glLoadIdentity,
    GLS_OP_glLoadMatrixd,
    GLS_OP_glLoadMatrixf,
    GLS_OP_glLoadName,
    GLS_OP_glLogicOp,
    GLS_OP_glMap1d,
    GLS_OP_glMap1f,
    GLS_OP_glMap2d,
    GLS_OP_glMap2f,
    GLS_OP_glMapGrid1d,
    GLS_OP_glMapGrid1f,
    GLS_OP_glMapGrid2d,
    GLS_OP_glMapGrid2f,
    GLS_OP_glMaterialf,
    GLS_OP_glMaterialfv,
    GLS_OP_glMateriali,
    GLS_OP_glMaterialiv,
    GLS_OP_glMatrixMode,
    GLS_OP_glMinmaxEXT,
    GLS_OP_glMultMatrixd,
    GLS_OP_glMultMatrixf,
    GLS_OP_glNewList,
    GLS_OP_glNormal3b,
    GLS_OP_glNormal3bv,
    GLS_OP_glNormal3d,
    GLS_OP_glNormal3dv,
    GLS_OP_glNormal3f,
    GLS_OP_glNormal3fv,
    GLS_OP_glNormal3i,
    GLS_OP_glNormal3iv,
    GLS_OP_glNormal3s,
    GLS_OP_glNormal3sv,
    GLS_OP_glNormalPointerEXT,
    GLS_OP_glOrtho,
    GLS_OP_glPassThrough,
    GLS_OP_glPixelMapfv,
    GLS_OP_glPixelMapuiv,
    GLS_OP_glPixelMapusv,
    GLS_OP_glPixelStoref,
    GLS_OP_glPixelStorei,
    GLS_OP_glPixelTexGenSGIX,
    GLS_OP_glPixelTransferf,
    GLS_OP_glPixelTransferi,
    GLS_OP_glPixelZoom,
    GLS_OP_glPointSize,
    GLS_OP_glPolygonMode,
    GLS_OP_glPolygonOffsetEXT,
    GLS_OP_glPolygonStipple,
    GLS_OP_glPopAttrib,
    GLS_OP_glPopMatrix,
    GLS_OP_glPopName,
    GLS_OP_glPrioritizeTexturesEXT,
    GLS_OP_glPushAttrib,
    GLS_OP_glPushMatrix,
    GLS_OP_glPushName,
    GLS_OP_glRasterPos2d,
    GLS_OP_glRasterPos2dv,
    GLS_OP_glRasterPos2f,
    GLS_OP_glRasterPos2fv,
    GLS_OP_glRasterPos2i,
    GLS_OP_glRasterPos2iv,
    GLS_OP_glRasterPos2s,
    GLS_OP_glRasterPos2sv,
    GLS_OP_glRasterPos3d,
    GLS_OP_glRasterPos3dv,
    GLS_OP_glRasterPos3f,
    GLS_OP_glRasterPos3fv,
    GLS_OP_glRasterPos3i,
    GLS_OP_glRasterPos3iv,
    GLS_OP_glRasterPos3s,
    GLS_OP_glRasterPos3sv,
    GLS_OP_glRasterPos4d,
    GLS_OP_glRasterPos4dv,
    GLS_OP_glRasterPos4f,
    GLS_OP_glRasterPos4fv,
    GLS_OP_glRasterPos4i,
    GLS_OP_glRasterPos4iv,
    GLS_OP_glRasterPos4s,
    GLS_OP_glRasterPos4sv,
    GLS_OP_glReadBuffer,
    GLS_OP_glReadPixels,
    GLS_OP_glRectd,
    GLS_OP_glRectdv,
    GLS_OP_glRectf,
    GLS_OP_glRectfv,
    GLS_OP_glRecti,
    GLS_OP_glRectiv,
    GLS_OP_glRects,
    GLS_OP_glRectsv,
    GLS_OP_glRenderMode,
    GLS_OP_glResetHistogramEXT,
    GLS_OP_glResetMinmaxEXT,
    GLS_OP_glRotated,
    GLS_OP_glRotatef,
    GLS_OP_glSampleMaskSGIS,
    GLS_OP_glSamplePatternSGIS,
    GLS_OP_glScaled,
    GLS_OP_glScalef,
    GLS_OP_glScissor,
    GLS_OP_glSelectBuffer,
    GLS_OP_glSeparableFilter2DEXT,
    GLS_OP_glShadeModel,
    GLS_OP_glSharpenTexFuncSGIS,
    GLS_OP_glStencilFunc,
    GLS_OP_glStencilMask,
    GLS_OP_glStencilOp,
    GLS_OP_glTagSampleBufferSGIX,
    GLS_OP_glTexColorTableParameterfvSGI,
    GLS_OP_glTexColorTableParameterivSGI,
    GLS_OP_glTexCoord1d,
    GLS_OP_glTexCoord1dv,
    GLS_OP_glTexCoord1f,
    GLS_OP_glTexCoord1fv,
    GLS_OP_glTexCoord1i,
    GLS_OP_glTexCoord1iv,
    GLS_OP_glTexCoord1s,
    GLS_OP_glTexCoord1sv,
    GLS_OP_glTexCoord2d,
    GLS_OP_glTexCoord2dv,
    GLS_OP_glTexCoord2f,
    GLS_OP_glTexCoord2fv,
    GLS_OP_glTexCoord2i,
    GLS_OP_glTexCoord2iv,
    GLS_OP_glTexCoord2s,
    GLS_OP_glTexCoord2sv,
    GLS_OP_glTexCoord3d,
    GLS_OP_glTexCoord3dv,
    GLS_OP_glTexCoord3f,
    GLS_OP_glTexCoord3fv,
    GLS_OP_glTexCoord3i,
    GLS_OP_glTexCoord3iv,
    GLS_OP_glTexCoord3s,
    GLS_OP_glTexCoord3sv,
    GLS_OP_glTexCoord4d,
    GLS_OP_glTexCoord4dv,
    GLS_OP_glTexCoord4f,
    GLS_OP_glTexCoord4fv,
    GLS_OP_glTexCoord4i,
    GLS_OP_glTexCoord4iv,
    GLS_OP_glTexCoord4s,
    GLS_OP_glTexCoord4sv,
    GLS_OP_glTexCoordPointerEXT,
    GLS_OP_glTexEnvf,
    GLS_OP_glTexEnvfv,
    GLS_OP_glTexEnvi,
    GLS_OP_glTexEnviv,
    GLS_OP_glTexGend,
    GLS_OP_glTexGendv,
    GLS_OP_glTexGenf,
    GLS_OP_glTexGenfv,
    GLS_OP_glTexGeni,
    GLS_OP_glTexGeniv,
    GLS_OP_glTexImage1D,
    GLS_OP_glTexImage2D,
    GLS_OP_glTexImage3DEXT,
    GLS_OP_glTexImage4DSGIS,
    GLS_OP_glTexParameterf,
    GLS_OP_glTexParameterfv,
    GLS_OP_glTexParameteri,
    GLS_OP_glTexParameteriv,
    GLS_OP_glTexSubImage1DEXT,
    GLS_OP_glTexSubImage2DEXT,
    GLS_OP_glTexSubImage3DEXT,
    GLS_OP_glTexSubImage4DSGIS,
    GLS_OP_glTranslated,
    GLS_OP_glTranslatef,
    GLS_OP_glVertex2d,
    GLS_OP_glVertex2dv,
    GLS_OP_glVertex2f,
    GLS_OP_glVertex2fv,
    GLS_OP_glVertex2i,
    GLS_OP_glVertex2iv,
    GLS_OP_glVertex2s,
    GLS_OP_glVertex2sv,
    GLS_OP_glVertex3d,
    GLS_OP_glVertex3dv,
    GLS_OP_glVertex3f,
    GLS_OP_glVertex3fv,
    GLS_OP_glVertex3i,
    GLS_OP_glVertex3iv,
    GLS_OP_glVertex3s,
    GLS_OP_glVertex3sv,
    GLS_OP_glVertex4d,
    GLS_OP_glVertex4dv,
    GLS_OP_glVertex4f,
    GLS_OP_glVertex4fv,
    GLS_OP_glVertex4i,
    GLS_OP_glVertex4iv,
    GLS_OP_glVertex4s,
    GLS_OP_glVertex4sv,
    GLS_OP_glVertexPointerEXT,
    GLS_OP_glViewport,
    GLS_OP_glArrayElement,
    GLS_OP_glBindTexture,
    GLS_OP_glColorPointer,
    GLS_OP_glDisableClientState,
    GLS_OP_glDrawArrays,
    GLS_OP_glDrawElements,
    GLS_OP_glEdgeFlagPointer,
    GLS_OP_glEnableClientState,
    GLS_OP_glIndexPointer,
    GLS_OP_glIndexub,
    GLS_OP_glIndexubv,
    GLS_OP_glInterleavedArrays,
    GLS_OP_glNormalPointer,
    GLS_OP_glPolygonOffset,
    GLS_OP_glTexCoordPointer,
    GLS_OP_glVertexPointer,
    GLS_OP_glAreTexturesResident,
    GLS_OP_glCopyTexImage1D,
    GLS_OP_glCopyTexImage2D,
    GLS_OP_glCopyTexSubImage1D,
    GLS_OP_glCopyTexSubImage2D,
    GLS_OP_glDeleteTextures,
    GLS_OP_glGenTextures,
    GLS_OP_glGetPointerv,
    GLS_OP_glIsTexture,
    GLS_OP_glPrioritizeTextures,
    GLS_OP_glTexSubImage1D,
    GLS_OP_glTexSubImage2D,
    GLS_OP_glPushClientAttrib,
    GLS_OP_glPopClientAttrib,
};
#define EXECUTE_BITS (sizeof(opExecuteBits)/sizeof(opExecuteBits[0]))

/*****************************Private*Routine******************************\
*
* CreateMetaRc
*
* Creates a rendering context for a metafile DC
*
* History:
*  Thu Feb 23 15:27:47 1995	-by-	Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

BOOL CreateMetaRc(HDC hdc, PLRC plrc)
{
    int i;
    BOOL fSuccess;
    
    if (!MetaLoadGls())
    {
        return FALSE;
    }
    
    // If there's currently a GLS context active we can't record
    // because we require our own context to be current for recording
    if (gepGlsFuncs.glsGetCurrentContext() != 0)
    {
        SetLastError(ERROR_BUSY);
        return FALSE;
    }
    
    // Create a GLS context to record into
    plrc->uiGlsCaptureContext = gepGlsFuncs.glsGenContext();
    if (plrc->uiGlsCaptureContext == 0)
    {
        SetLastError(ERROR_NOT_ENOUGH_MEMORY);
        goto EH_NoContext;
    }

    // No bounds by default
    plrc->prclGlsBounds = NULL;
    
    // Set current GLS context
    gepGlsFuncs.glsContext(plrc->uiGlsCaptureContext);

    // Point to our writer function
    gepGlsFuncs.glsWriteFunc(GlsWriter);

    // Set up a callback to flush after every command
    // This lets every GL command form its own separate record in the
    // metafile
    gepGlsFuncs.glsCaptureFunc(GLS_CAPTURE_EXIT_FUNC, GlsFlush);

    // Set execute bits on commands which retrieve state
    // This allows accurate results to come back for retrieval functions
    for (i = 0; i < EXECUTE_BITS; i++)
    {
        gepGlsFuncs.glsCaptureFlags(opExecuteBits[i],
                                    GLS_CAPTURE_EXECUTE_BIT |
                                    GLS_CAPTURE_WRITE_BIT);
    }
    
    fSuccess = MetaRcBegin(plrc, hdc);
    
    // Remove context to avoid inadvertent GLS calls
    // Also needed in failure case
    gepGlsFuncs.glsContext(0);

    if (fSuccess)
    {
        return TRUE;
    }

    gepGlsFuncs.glsDeleteContext(plrc->uiGlsCaptureContext);
    plrc->uiGlsCaptureContext = 0;
 EH_NoContext:
    DBGERROR("CreateMetaRc failed\n");
    return FALSE;
}

/*****************************Private*Routine******************************\
*
* DeleteMetaRc
*
* Cleans up a metafile RC
*
* History:
*  Thu Feb 23 16:35:19 1995	-by-	Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

void DeleteMetaRc(PLRC plrc)
{
    GLuint uiGlsCurrent;
    
    if (plrc->uiGlsCaptureContext != 0)
    {
        // Make the GLS context current just in case
        // A different GLS context can be active at this time, though,
        // because a different metafile RC could be current to this
        // thread at the time of the delete, so we have to preserve
        // any current context
        uiGlsCurrent = gepGlsFuncs.glsGetCurrentContext();
    
        gepGlsFuncs.glsContext(plrc->uiGlsCaptureContext);

        // If we're still capturing, stop
        if (plrc->fCapturing)
        {
            MetaRcEnd(plrc);
        }

        // Restore old context
        gepGlsFuncs.glsContext(uiGlsCurrent);
    
        // Clean up dying context
        gepGlsFuncs.glsDeleteContext(plrc->uiGlsCaptureContext);
        plrc->uiGlsCaptureContext = 0;
    }

    // Clean up playback context if necessary
    // This can happen when metafile playback crashes or an
    // application crashes while enumerating
    if (plrc->uiGlsPlaybackContext != 0)
    {
        gepGlsFuncs.glsDeleteContext(plrc->uiGlsPlaybackContext);
        plrc->uiGlsPlaybackContext = 0;
    }
    
    // LRC and handle will be cleaned up elsewhere
}

/*****************************Private*Routine******************************\
*
* ActivateMetaRc
*
* Make a metafile RC current
*
* History:
*  Thu Feb 23 16:50:31 1995	-by-	Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

void ActivateMetaRc(PLRC plrc, HDC hdc)
{
    ASSERTOPENGL(plrc->uiGlsCaptureContext != 0,
                 "ActivateMetaRc: No GLS context\n");
    ASSERTOPENGL(gepGlsFuncs.glsGetCurrentContext() == 0,
                 "ActivateMetaRc: Already a current GLS context\n");
    
    gepGlsFuncs.glsContext(plrc->uiGlsCaptureContext);
}

/*****************************Private*Routine******************************\
*
* DeactivateMetaRc
*
* Make a metafile RC non-current
*
* History:
*  Thu Feb 23 16:49:51 1995	-by-	Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

void DeactivateMetaRc(PLRC plrc)
{
    // The current GLS context may not be this RC's capturing context
    // in the case where the RC has been made current after a
    // CloseEnhMetaFile has stopped capturing
    if (gepGlsFuncs.glsGetCurrentContext() == plrc->uiGlsCaptureContext)
    {
        gepGlsFuncs.glsContext(0);
    }
}

/*****************************Private*Routine******************************\
*
* GlmfSave
*
* Save all current GL state
*
* History:
*  Fri Feb 24 15:15:50 1995	-by-	Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

void GlmfSave(void)
{
    // What is the exact list of state to be saved?
    // What about overflowing the projection and textures stacks?
    // They're small
    
    glPushAttrib(GL_ALL_ATTRIB_BITS);
    
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();

    glMatrixMode(GL_PROJECTION);
    glPushMatrix();

    glMatrixMode(GL_TEXTURE);
    glPushMatrix();
}

/*****************************Private*Routine******************************\
*
* GlmfRestore
*
* Restores saved state
*
* History:
*  Fri Feb 24 15:16:14 1995	-by-	Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

void GlmfRestore(void)
{
    glMatrixMode(GL_TEXTURE);
    glPopMatrix();

    glMatrixMode(GL_PROJECTION);
    glPopMatrix();

    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();

    glPopAttrib();
}

#define ScaleLongX(plrc, l) \
    MulDiv(l, plrc->iGlsNumeratorX, plrc->iGlsDenominatorX)
#define ScaleLongY(plrc, l) \
    MulDiv(l, plrc->iGlsNumeratorY, plrc->iGlsDenominatorY)
#define ScaleFloatX(plrc, f) ((f)*(plrc)->fGlsScaleX)
#define ScaleFloatY(plrc, f) ((f)*(plrc)->fGlsScaleY)

/*****************************Private*Routine******************************\
*
* TransformLongPt
*
* Transform an integer point
*
* History:
*  Fri Feb 24 15:27:37 1995	-by-	Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

void TransformLongPt(PLRC plrc, POINT *ppt)
{
    ppt->x = MulDiv(ppt->x-plrc->iGlsSubtractX, plrc->iGlsNumeratorX,
                    plrc->iGlsDenominatorX)+plrc->iGlsAddX;
    ppt->y = MulDiv(ppt->y-plrc->iGlsSubtractY, plrc->iGlsNumeratorY,
                    plrc->iGlsDenominatorY)+plrc->iGlsAddY;
}

/*****************************Private*Routine******************************\
*
* ScaleLongPt
*
* Scale an integer point, no translation, for values rather than coordinates
*
* History:
*  Fri Feb 24 15:27:52 1995	-by-	Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

void ScaleLongPt(PLRC plrc, POINT *ppt)
{
    ppt->x = MulDiv(ppt->x, plrc->iGlsNumeratorX, plrc->iGlsDenominatorX);
    ppt->y = MulDiv(ppt->y, plrc->iGlsNumeratorY, plrc->iGlsDenominatorY);
}

/*****************************Private*Routine******************************\
*
* TransformFloatPt
*
* Transform a float point
*
* History:
*  Fri Feb 24 15:27:37 1995	-by-	Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

void TransformFloatPt(PLRC plrc, POINTFLOAT *pptf)
{
    pptf->x = (pptf->x-plrc->iGlsSubtractX)*plrc->iGlsNumeratorX/
        plrc->iGlsDenominatorX+plrc->iGlsAddX;
    pptf->y = (pptf->y-plrc->iGlsSubtractY)*plrc->iGlsNumeratorY/
        plrc->iGlsDenominatorY+plrc->iGlsAddY;
}

/*****************************Private*Routine******************************\
*
* ScaleFloatPt
*
* Scale a float point
*
* History:
*  Fri Feb 24 15:27:37 1995	-by-	Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

void ScaleFloatPt(PLRC plrc, POINTFLOAT *pptf)
{
    pptf->x = pptf->x*plrc->iGlsNumeratorX/plrc->iGlsDenominatorX;
    pptf->y = pptf->y*plrc->iGlsNumeratorY/plrc->iGlsDenominatorY;
}

/*****************************Private*Routine******************************\
*
* GLS output scaling callbacks
*
* The following functions are used as GLS command functions for
* intercepting device coordinates and scaling them appropriately
*
* Bitmap contents are not scaled, but the current raster position is
* correctly maintained so that they are positioned appropriately
*
* Stipples are not scaled
*
* History:
*  Fri Feb 24 15:28:23 1995	-by-	Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

void GlsBitmapOut(GLsizei width, GLsizei height,
                  GLfloat xorig, GLfloat yorig,
                  GLfloat xmove, GLfloat ymove, const GLubyte *bitmap)
{
    PLRC plrc;
    POINTFLOAT ptf;

    plrc = GLTEB_CLTCURRENTRC();
    
    ptf.x = xmove;
    ptf.y = ymove;
    ScaleFloatPt(plrc, &ptf);
    
    glBitmap(width, height, xorig, yorig, ptf.x, ptf.y, bitmap);
}

void GlsCopyPixelsOut(GLint x, GLint y, GLsizei width, GLsizei height,
                      GLenum type)
{
    POINT ptXY, ptWH;
    PLRC plrc;

    plrc = GLTEB_CLTCURRENTRC();
    
    ptXY.x = x;
    ptXY.y = y;
    TransformLongPt(plrc, &ptXY);
    
    ptWH.x = (LONG)width;
    ptWH.y = (LONG)height;
    ScaleLongPt(plrc, &ptWH);

    glCopyPixels(ptXY.x, ptXY.y, ptWH.x, ptWH.y, type);
}

void GlsCopyTexImage1DOut(GLenum target, GLint level,
                          GLenum internalformat,
                          GLint x, GLint y,
                          GLsizei width, GLint border)
{
    POINT ptXY;
    PLRC plrc;

    plrc = GLTEB_CLTCURRENTRC();

    ptXY.x = x;
    ptXY.y = y;
    TransformLongPt(plrc, &ptXY);
    
    glCopyTexImage1D(target, level, internalformat,
                     ptXY.x, ptXY.y, ScaleLongX(plrc, width), border);
}

    
void GlsCopyTexImage2DOut(GLenum target, GLint level,
                          GLenum internalformat,
                          GLint x, GLint y,
                          GLsizei width, GLsizei height,
                          GLint border)
{
    POINT ptXY, ptWH;
    PLRC plrc;

    plrc = GLTEB_CLTCURRENTRC();

    ptXY.x = x;
    ptXY.y = y;
    TransformLongPt(plrc, &ptXY);
    
    ptWH.x = (LONG)width;
    ptWH.y = (LONG)height;
    ScaleLongPt(plrc, &ptWH);
    
    glCopyTexImage2D(target, level, internalformat,
                     ptXY.x, ptXY.y, ptWH.x, ptWH.y, border);
}

void GlsCopyTexSubImage1DOut(GLenum target, GLint level,
                             GLint xoffset, GLint x, GLint y,
                             GLsizei width)
{
    POINT ptXY;
    PLRC plrc;

    plrc = GLTEB_CLTCURRENTRC();

    ptXY.x = x;
    ptXY.y = y;
    TransformLongPt(plrc, &ptXY);
    
    glCopyTexSubImage1D(target, level, xoffset,
                        ptXY.x, ptXY.y, ScaleLongX(plrc, width));
}

void GlsCopyTexSubImage2DOut(GLenum target, GLint level,
                             GLint xoffset, GLint yoffset,
                             GLint x, GLint y,
                             GLsizei width, GLsizei height)
{
    POINT ptXY, ptWH;
    PLRC plrc;

    plrc = GLTEB_CLTCURRENTRC();

    ptXY.x = x;
    ptXY.y = y;
    TransformLongPt(plrc, &ptXY);
    
    ptWH.x = (LONG)width;
    ptWH.y = (LONG)height;
    ScaleLongPt(plrc, &ptWH);
    
    glCopyTexSubImage2D(target, level, xoffset, yoffset,
                        ptXY.x, ptXY.y, ptWH.x, ptWH.y);
}

void GlsLineWidthOut(GLfloat width)
{
    PLRC plrc;

    plrc = GLTEB_CLTCURRENTRC();

    // Use X scaling here
    glLineWidth(ScaleFloatX(plrc, width));
}

void GlsPointSizeOut(GLfloat size)
{
    PLRC plrc;

    plrc = GLTEB_CLTCURRENTRC();
    
    // Use X scaling here
    glPointSize(ScaleFloatX(plrc, size));
}

void GlsScissorOut(GLint x, GLint y, GLsizei width, GLsizei height)
{
    POINT ptXY, ptWH;
    PLRC plrc;

    plrc = GLTEB_CLTCURRENTRC();

    ptXY.x = x;
    ptXY.y = y;
    TransformLongPt(plrc, &ptXY);
    
    ptWH.x = (LONG)width;
    ptWH.y = (LONG)height;
    ScaleLongPt(plrc, &ptWH);

    glScissor(ptXY.x, ptXY.y, ptWH.x, ptWH.y);
}

void GlsViewportOut(GLint x, GLint y, GLsizei width, GLsizei height)
{
    POINT ptXY, ptWH;
    PLRC plrc;

    plrc = GLTEB_CLTCURRENTRC();

    ptXY.x = x;
    ptXY.y = y;
    TransformLongPt(plrc, &ptXY);
    
    ptWH.x = (LONG)width;
    ptWH.y = (LONG)height;
    ScaleLongPt(plrc, &ptWH);

#if 0
    DbgPrint("glViewport(%d, %d, %d, %d)\n", ptXY.x, ptXY.y,
             ptWH.x, ptWH.y);
#endif

    glViewport(ptXY.x, ptXY.y, ptWH.x, ptWH.y);
}

static GLDEVICEPROCS gdpOutput =
{
    GlsBitmapOut,
    GlsCopyPixelsOut,
    GlsCopyTexImage1DOut,
    GlsCopyTexImage2DOut,
    GlsCopyTexSubImage1DOut,
    GlsCopyTexSubImage2DOut,
    NULL, // glDrawPixels
    GlsLineWidthOut,
    GlsPointSizeOut,
    GlsScissorOut,
    GlsViewportOut
};

/*****************************Private*Routine******************************\
*
* GlmfHookDeviceFns
*
* Hook all functions that deal with device units
*
* History:
*  Fri Feb 24 15:30:45 1995	-by-	Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

void GlmfHookDeviceFns(void)
{
    int i;
    PROC *ppfn;

    ppfn = (PROC *)&gdpOutput;
    for (i = 0; i < GL_DEVICE_PROCS; i++)
    {
        if (*ppfn != NULL)
        {
            gepGlsFuncs.glsCommandFunc(glsopDeviceProcs[i], *ppfn);
        }
        
        ppfn++;
    }
}

/*****************************Private*Routine******************************\
*
* GlmfInitTransform
*
* Compute 2D playback transform from source and destination rectangles
* Hook GLS with scaling functions
*
* History:
*  Fri Feb 24 15:31:24 1995	-by-	Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

void GlmfInitTransform(LPRECTL prclFrom, LPRECTL prclTo)
{
    PLRC plrc;

    plrc = GLTEB_CLTCURRENTRC();
    
    // Rectangles are inclusive-inclusive
    
    plrc->iGlsSubtractX = prclFrom->left;
    plrc->iGlsSubtractY = prclFrom->top;
    plrc->iGlsNumeratorX = prclTo->right-prclTo->left+1;
    plrc->iGlsNumeratorY = prclTo->bottom-prclTo->top+1;
    plrc->iGlsDenominatorX = prclFrom->right-prclFrom->left+1;
    plrc->iGlsDenominatorY = prclFrom->bottom-prclFrom->top+1;
    plrc->iGlsAddX = prclTo->left;
    plrc->iGlsAddY = prclTo->top;

#if 0
    DbgPrint("- %d,%d * %d,%d / %d,%d + %d,%d\n",
             plrc->iGlsSubtractX, plrc->iGlsSubtractY,
             plrc->iGlsNumeratorX, plrc->iGlsNumeratorY,
             plrc->iGlsDenominatorX, plrc->iGlsDenominatorY,
             plrc->iGlsAddX, plrc->iGlsAddY);
#endif
    
    // Only install hooks if the transform is not identity
    if (plrc->iGlsSubtractX != plrc->iGlsAddX ||
        plrc->iGlsSubtractY != plrc->iGlsAddY ||
        plrc->iGlsNumeratorX != plrc->iGlsDenominatorX ||
        plrc->iGlsNumeratorY != plrc->iGlsDenominatorY)
    {
        plrc->fGlsScaleX = (GLfloat)plrc->iGlsNumeratorX/
            plrc->iGlsDenominatorX;
        plrc->fGlsScaleY = (GLfloat)plrc->iGlsNumeratorY/
            plrc->iGlsDenominatorY;

        GlmfHookDeviceFns();
    }
}

// Table of functions which need to have their command funcs
// reset for playback virtualization
static GLSopcode opRecirculate[] =
{
    GLS_OP_glsBeginGLS,
    GLS_OP_glsBlock,
    GLS_OP_glsCallStream,
    GLS_OP_glsEndGLS,
    GLS_OP_glsError,
    GLS_OP_glsGLRC,
    GLS_OP_glsGLRCLayer,
    GLS_OP_glsHeaderGLRCi,
    GLS_OP_glsHeaderLayerf,
    GLS_OP_glsHeaderLayeri,
    GLS_OP_glsHeaderf,
    GLS_OP_glsHeaderfv,
    GLS_OP_glsHeaderi,
    GLS_OP_glsHeaderiv,
    GLS_OP_glsHeaderubz,
    GLS_OP_glsRequireExtension,
    GLS_OP_glsUnsupportedCommand,
    GLS_OP_glsAppRef,
    GLS_OP_glsBeginObj,
    GLS_OP_glsCharubz,
    GLS_OP_glsComment,
    GLS_OP_glsDisplayMapfv,
    GLS_OP_glsEndObj,
    GLS_OP_glsNumb,
    GLS_OP_glsNumbv,
    GLS_OP_glsNumd,
    GLS_OP_glsNumdv,
    GLS_OP_glsNumf,
    GLS_OP_glsNumfv,
    GLS_OP_glsNumi,
    GLS_OP_glsNumiv,
    GLS_OP_glsNuml,
    GLS_OP_glsNumlv,
    GLS_OP_glsNums,
    GLS_OP_glsNumsv,
    GLS_OP_glsNumub,
    GLS_OP_glsNumubv,
    GLS_OP_glsNumui,
    GLS_OP_glsNumuiv,
    GLS_OP_glsNumul,
    GLS_OP_glsNumulv,
    GLS_OP_glsNumus,
    GLS_OP_glsNumusv,
    GLS_OP_glsPad,
    GLS_OP_glsSwapBuffers
};
#define RECIRCULATE_OPS (sizeof(opRecirculate)/sizeof(opRecirculate[0]))

/******************************Public*Routine******************************\
*
* GlmfInitPlayback
*
* Initialize GL metafile playback, called from PlayEnhMetaFile for
* metafiles with GL information in them
*
* History:
*  Fri Feb 24 10:32:29 1995	-by-	Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

BOOL APIENTRY GlmfInitPlayback(HDC hdc, ENHMETAHEADER *pemh, LPRECTL prclDest)
{
    GLuint uiCurrentCtx;
    PLRC plrc;
    RECTL rclSourceDevice;
    int i;

    // If we don't have the appropriate GL context set up,
    // do nothing.  This allows applications to play metafiles containing
    // GL information even if they don't know anything about GL
    plrc = GLTEB_CLTCURRENTRC();
    if (plrc == NULL)
    {
        return TRUE;
    }
    
    if (!MetaLoadGls())
    {
        return FALSE;
    }

    plrc->uiGlsPlaybackContext = gepGlsFuncs.glsGenContext();
    if (plrc->uiGlsPlaybackContext == 0)
    {
        return FALSE;
    }

    GlmfSave();

    // Set an initial viewport just as a default
    glViewport(prclDest->left, prclDest->top,
               prclDest->right-prclDest->left,
               prclDest->bottom-prclDest->top);

    // The frame is in .01mm units.  Convert it to reference
    // device units using the information in the metafile header
    rclSourceDevice.left = MulDiv(pemh->rclFrame.left, pemh->szlDevice.cx,
                                  pemh->szlMillimeters.cx*100);
    rclSourceDevice.right = MulDiv(pemh->rclFrame.right, pemh->szlDevice.cx,
                                   pemh->szlMillimeters.cx*100);
    rclSourceDevice.top = MulDiv(pemh->rclFrame.top, pemh->szlDevice.cy,
                                 pemh->szlMillimeters.cy*100);
    rclSourceDevice.bottom = MulDiv(pemh->rclFrame.bottom, pemh->szlDevice.cy,
                                    pemh->szlMillimeters.cy*100);

    // We are resetting command funcs so we need our playback context
    // to be current.  Another context could be current now, though,
    // so preserve it
    uiCurrentCtx = gepGlsFuncs.glsGetCurrentContext();
    gepGlsFuncs.glsContext(plrc->uiGlsPlaybackContext);
    
    GlmfInitTransform(&rclSourceDevice, prclDest);

    // Reset all GLS command funcs to point to the actual exported
    // routines.  This means that playback on this context will
    // be exactly the same as if all the routines were being called
    // directly, so embedding a metafile into another one works
    // as expected
    //
    // NOTE: This context should not be made current because any
    // GLS commands executed on it when it is current will now
    // cause infinite loops
    for (i = 0; i < RECIRCULATE_OPS; i++)
    {
        gepGlsFuncs.glsCommandFunc(opRecirculate[i],
                                   (&gepGlsFuncs.glsBeginGLS)[i]);
    }

    // Restore preserved context
    gepGlsFuncs.glsContext(uiCurrentCtx);
    
    return TRUE;
}

/******************************Public*Routine******************************\
*
* GlmfBeginGlsBlock
*
* Sets up things for GLS record playback which can only be active during
* GLS records
* Currently this only sets the world transform to identity to avoid
* it interacting with GL drawing
*
* History:
*  Mon Apr 10 11:20:19 1995	-by-	Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

BOOL APIENTRY GlmfBeginGlsBlock(HDC hdc)
{
    PLRC plrc;
    BOOL bRet;
    
    // If we don't have the appropriate GL context set up,
    // do nothing.  This allows applications to play metafiles containing
    // GL information even if they don't know anything about GL
    plrc = GLTEB_CLTCURRENTRC();
    if (plrc == NULL)
    {
        return TRUE;
    }
    
    bRet = GetWorldTransform(hdc, &plrc->xformMeta);
    if (bRet)
    {
        bRet = ModifyWorldTransform(hdc, NULL, MWT_IDENTITY);
    }

    return bRet;
}
    
/******************************Public*Routine******************************\
*
* GlmfPlayGlsRecord
*
* Play a GL metafile record
*
* History:
*  Fri Feb 24 10:33:38 1995	-by-	Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

#define PLAY_STACK_BUFFER 256

BOOL APIENTRY GlmfPlayGlsRecord(HDC hdc, DWORD cb, BYTE *pb,
                                LPRECTL prclBounds)
{
    PLRC plrc;
    LARGE_INTEGER liBuffer[(PLAY_STACK_BUFFER+sizeof(LARGE_INTEGER)-1)/
                           sizeof(LARGE_INTEGER)+1];
    BYTE *pbPlay, *pbAlloc = NULL;
    __GLSbinCommandHead_large *gbch;
    GLSopcode op;
    GLScommandAlignment gca;

#if 0
    DbgPrint("GlmfPlayGlsRecord(%d)\n", cb);
#endif
    
    // If we don't have the appropriate GL and GLS contexts set up,
    // do nothing.  This allows applications to play metafiles containing
    // GL information even if they don't know anything about GL
    plrc = GLTEB_CLTCURRENTRC();
    if (plrc == NULL || plrc->uiGlsPlaybackContext == 0)
    {
        return TRUE;
    }
    
    ASSERTOPENGL(hGlsDll != NULL, "GlmfPlayGlsRecord: GLS not loaded\n");
    
    ASSERTOPENGL(plrc->tidCurrent == GetCurrentThreadId(),
                 "GlmfPlayGlsRecord: "
                 "Current RC does not belong to this thread!\n");
    ASSERTOPENGL(plrc->gwidCurrent.hdc != 0,
                 "GlmfPlayGlsRecord: Current HDC is NULL!\n");

    // pb points to some arbitrary block of memory
    // GLS requires that this block be appropriately aligned for
    // any commands that are executed out of it, so we need to
    // determine which command is in the buffer and then query
    // GLS for its alignment.
    // This is trickier than you would think since GLS doesn't
    // always add padding to commands relative to their beginning; it
    // sometimes adds padding to the end of the previous command.
    // We need to detect the case where padding is added.
    // 
    // NOTE: This definitely works when there is only one command
    // in the buffer.  It should work when there are multiple commands
    // because the following commands are padded according to the 
    // alignment of the initial command.  However, this assumption
    // should probably be validated if blocks start containing
    // multiple commands.

    // Check for an initial pad and skip it if necessary
    gbch = (__GLSbinCommandHead_large *)pb;
    if (gbch->opSmall == GLS_OP_glsPad &&
        gbch->countSmall == 1)
    {
        pb += sizeof(__GLSbinCommandHead_small);
        cb -= sizeof(__GLSbinCommandHead_small);
        gbch = (__GLSbinCommandHead_large *)pb;
    }

    ASSERTOPENGL(gbch->countSmall == 0 ||
                 gbch->opSmall != GLS_OP_glsPad,
                 "Unexpected glsPad in command buffer\n");

    op = gbch->countSmall == 0 ? gbch->opLarge : gbch->opSmall;

    gepGlsFuncs.glsGetCommandAlignment(op, gepGlsFuncs.glsBinary(GL_FALSE),
                                       &gca);
    ASSERTOPENGL(gca.mask <= 7, "Unhandled GLS playback alignment\n");

    if (((ULONG_PTR)pb & gca.mask) != gca.value)
    {
        if (cb <= PLAY_STACK_BUFFER)
        {
            pbPlay = (BYTE *)liBuffer+gca.value;
        }
        else
        {
            pbAlloc = (BYTE *)ALLOC(cb+gca.value);
            if (pbAlloc == NULL)
            {
                return FALSE;
            }

            pbPlay = pbAlloc+gca.value;
        }
        
        RtlCopyMemory(pbPlay, pb, cb);
    }
    else
    {
        pbPlay = pb;
    }
    
    gepGlsFuncs.glsCallArrayInContext(plrc->uiGlsPlaybackContext,
                                      gepGlsFuncs.glsBinary(GL_FALSE),
                                      cb, pbPlay);

    if (pbAlloc != NULL)
    {
        FREE(pbAlloc);
    }
    
    return TRUE;
}

/******************************Public*Routine******************************\
*
* GlmfEndGlsBlock
*
* Resets state changed for GLS record playback
* Currently restores the world transform
*
* History:
*  Mon Apr 10 11:23:06 1995	-by-	Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

BOOL APIENTRY GlmfEndGlsBlock(HDC hdc)
{
    PLRC plrc;
    
    // If we don't have the appropriate GL context set up,
    // do nothing.  This allows applications to play metafiles containing
    // GL information even if they don't know anything about GL
    plrc = GLTEB_CLTCURRENTRC();
    if (plrc == NULL)
    {
        return TRUE;
    }

    // Doesn't matter which side we multiply by since the transform
    // should be identity
    return ModifyWorldTransform(hdc, &plrc->xformMeta, MWT_LEFTMULTIPLY);
}
    
/******************************Public*Routine******************************\
*
* GlmfEndPlayback
*
* End GL metafile playback, called at the end of metafile playback
* Only called if GlmfInitPlayback was successful
*
* History:
*  Fri Feb 24 10:36:36 1995	-by-	Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

BOOL APIENTRY GlmfEndPlayback(HDC hdc)
{
    PLRC plrc;

    // If we don't have the appropriate GL and GLS contexts set up,
    // do nothing.  This allows applications to play metafiles containing
    // GL information even if they don't know anything about GL
    plrc = GLTEB_CLTCURRENTRC();
    if (plrc == NULL || plrc->uiGlsPlaybackContext == 0)
    {
        return TRUE;
    }

    ASSERTOPENGL(hGlsDll != NULL, "GlmfEndPlayback: GLS not loaded\n");

    // Since GlmfInitPlayback completed, we must have saved state
    GlmfRestore();

    ASSERTOPENGL(plrc->uiGlsPlaybackContext != 0,
                 "GlmfEndPlayback: No playback context\n");
    gepGlsFuncs.glsDeleteContext(plrc->uiGlsPlaybackContext);
    plrc->uiGlsPlaybackContext = 0;

    // Request cleanup of windows on the theory that most orphaned
    // windows are produced by DCs created for metafiles and memory
    // DCs used by printing.  Cleaning up during playback will mean
    // that orphaned windows are only around for one extra playback
    wglValidateWindows();

    return TRUE;
}

/******************************Public*Routine******************************\
*
* GlmfCloseMetaFile
*
* Called in CloseEnhMetaFile if GL records are present in the metafile
*
* History:
*  Fri Mar 03 18:05:50 1995	-by-	Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

BOOL APIENTRY GlmfCloseMetaFile(HDC hdc)
{
    PLRC plrc;
    GLGENwindow *pwnd;
    GLWINDOWID gwid;

    // This DC has just gone away so clean up its WNDOBJ if necessary
    WindowIdFromHdc(hdc, &gwid);
    pwnd = pwndGetFromID(&gwid);
    if (pwnd != NULL)
    {
        pwndCleanup(pwnd);
    }
    
    // The app could have called wglDeleteContext before CloseEnhMetaFile,
    // so we're not guaranteed to have a context
    plrc = GLTEB_CLTCURRENTRC();
    if (plrc == NULL ||
        !plrc->fCapturing)
    {
        return TRUE;
    }

    ASSERTOPENGL(hGlsDll != NULL, "GlmfCloseMetaFile: GLS not loaded\n");

    ASSERTOPENGL(plrc->uiGlsCaptureContext != 0,
                 "GlmfCloseMetaFile: GLS context is invalid");
    MetaRcEnd(plrc);

    // Set the proc table to the generic routines because capturing
    // is over.  Metafiling always uses the generic routines because
    // the underlying surface is always faked on top of an info DC.
    {
    // Use RGBA or CI proc table depending on the color mode.

	GLCLTPROCTABLE *pglProcTable;
	__GL_SETUP();

	if (gc->modes.colorIndexMode)
	    pglProcTable = &glCltCIProcTable;
	else
	    pglProcTable = &glCltRGBAProcTable;

	SetCltProcTable(pglProcTable, &glExtProcTable, TRUE);
    }

    return TRUE;
}

/******************************Public*Routine******************************\
*
* GlGdi routines
*
* Thunks to allow the same binary to run on both NT with metafile support
* and Win95 without it
*
* History:
*  Thu Aug 31 15:46:37 1995	-by-	Drew Bliss [drewb]
*   Created
*
\**************************************************************************/

#if DBG
BOOL APIENTRY GlGdiAddGlsRecord(HDC hdc, DWORD cb, BYTE *pb,
                                LPRECTL prclBounds)
{
    ASSERTOPENGL(pfnGdiAddGlsRecord != NULL,
                 "GdiAddGlsRecord called without support\n");
    return pfnGdiAddGlsRecord(hdc, cb, pb, prclBounds);
}

BOOL APIENTRY GlGdiAddGlsBounds(HDC hdc, LPRECTL prclBounds)
{
    ASSERTOPENGL(pfnGdiAddGlsBounds != NULL,
                 "GdiAddGlsBounds called without support\n");
    return pfnGdiAddGlsBounds(hdc, prclBounds);
}

BOOL APIENTRY GlGdiIsMetaPrintDC(HDC hdc)
{
    ASSERTOPENGL(pfnGdiIsMetaPrintDC != NULL,
                 "GdiIsMetaPrintDC called without support\n");
    return pfnGdiIsMetaPrintDC(hdc);
}
#endif

#else

PROC * APIENTRY wglGetDefaultDispatchTable(void)
{
    return NULL;
}

BOOL APIENTRY GlmfInitPlayback(HDC hdc, ENHMETAHEADER *pemh, LPRECTL prclDest)
{
    return FALSE;
}

BOOL APIENTRY GlmfBeginGlsBlock(HDC hdc)
{
    return FALSE;
}

BOOL APIENTRY GlmfPlayGlsRecord(HDC hdc, DWORD cb, BYTE *pb,
                                LPRECTL prclBounds)
{
    return FALSE;
}

BOOL APIENTRY GlmfEndGlsBlock(HDC hdc)
{
    return FALSE;
}

BOOL APIENTRY GlmfEndPlayback(HDC hdc)
{
    return FALSE;
}

BOOL APIENTRY GlmfCloseMetaFile(HDC hdc)
{
    return FALSE;
}

#endif