const Tk_ClassProcs C3dCanvasProcs = {
    sizeof(Tk_ClassProcs),	/* size */
    C3d_Canvas_WorldChanged,	/* worldChangedProc */
    C3d_Native_CreateCanvas3d,  /* createProc */
    NULL		        /* modalProc */
};

static void
C3d_Native_ResizeContext(C3dWidget *pCanvas,int w,int h)
{
    XResizeWindow(pCanvas->display, Tk_WindowId(pCanvas->tkwin), w, h);
    pCanvas->options.width = w;
    pCanvas->options.height = h;
}

/*
 * cd3X11Main.c --
 *
 *     This file contains the implementation of the Tcl interface of the
 *     canvas3d widget in X11 windowing environments.
 *
 *     This file is called by an include directive in c3dmain.c
 *-------------------------------------------------------------------------
 */
static void
C3d_Native_DeleteContext(C3dWidget *pCanvas) {
    glXDestroyContext(pCanvas->display, pCanvas->context);
}

/*
 *---------------------------------------------------------------------------
 *
 * C3d_Native_FreePixMaps --
 *
 *     This function frees all currently allocated pixmaps used by the
 *     -saveunder option. This function is called in the following three
 *     circumstances:
 *
 *         * When the widget is being deleted,
 *         * When the -saveunder mode changes,
 *         * When the window size changes.
 *
 *     The pixmaps are allocated within C3d_Native_Select3dContext().
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static void
C3d_Native_FreePixMaps(C3dWidget *pCanvas)
{
    pCanvas->pixmapheight = 0;
    pCanvas->pixmapwidth = 0;
    if (pCanvas->pixmap) {
        C3dFreePixmap(pCanvas->display, pCanvas->pixmap);
        pCanvas->pixmap = 0;
    }
    if (pCanvas->glxpixmap) {
	glXDestroyGLXPixmap(pCanvas->display, pCanvas->glxpixmap);
	pCanvas->glxpixmap = 0;
    }
    if (pCanvas->pixcontext) {
	glXDestroyContext(pCanvas->display, pCanvas->pixcontext);
	pCanvas->pixcontext = 0;
    }
    if (pCanvas->pixmap3d) {
	C3dFreePixmap(pCanvas->display, pCanvas->pixmap3d);
	pCanvas->pixmap3d = 0;
    }
}

/*
 *---------------------------------------------------------------------------
 *
 * C3d_Native_CreateCanvas3d --
 *
 *     This function is invoked when Tk_MakeWindowExist() is called to
 *     create the 3d-canvas window (it is registered using
 *     Tk_SetClassProcs() in function C3d_CanvasObjCmd()). This is where we do
 *     platform specific OpenGL initialisation.
 *
 * Results:
 *     X-window Window identifier to be wrapped into a Tk_Window by Tk.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static Window
C3d_Native_CreateCanvas3d(
    Tk_Window tkwin,
    Window parent,                /* X-windows wrapper around parent HWND */
    ClientData clientData
) {
    /* The rest of this function is the X11 version. */
    C3dWidget *pCanvas = (C3dWidget *)clientData;
    Tcl_Interp *interp = pCanvas->interp;
    Display *dpy = Tk_Display(tkwin); /* Application display */
    int screen = Tk_ScreenNumber(tkwin);   /* Application screen number */
    Window xwindow;                      /* X-window created (return value) */
    int width;                 /* Width of created window in pixels */
    int height;                /* Height of created window in pixels */
    int dummy;
    XVisualInfo *pInfo;
    XSetWindowAttributes swa;

    /* Attributes to request of the X-windows visual context. */
    static int attributes[] = {
        GLX_RGBA,
        GLX_DOUBLEBUFFER,
        GLX_DEPTH_SIZE, 4,
        None
    };

    /* Ensure the X-server supports Open-GL GLX. */
    if (!glXQueryExtension(dpy, &dummy, &dummy)) {
        Tcl_AppendResult(interp, "Canvas3d: No OpenGL GLX support detected", 0);
        return 0;
    }

    pInfo = glXChooseVisual(dpy, screen, attributes);
    assert(pInfo);
    swa.colormap = XCreateColormap(
            dpy,
            RootWindow(dpy, pInfo->screen),
            pInfo->visual,
            AllocNone
    );

    /* This catches the case where GLX uses a 32bpp visual on
     * 15/16 bpp diplays. If omitted BadVisual errors may be
     * reported later e.g. when text is drawn.
     */
    Tk_SetWindowVisual(tkwin, pInfo->visual, pInfo->depth, swa.colormap);

    swa.border_pixel = 0;
    swa.event_mask = ALL_EVENTS_MASK;
    /* swa.save_under = 1; */

    /* Create the X-window. Use the width and height specified as widget
     * options to size the window.
     */
    width = pCanvas->options.width;
    height = pCanvas->options.height;
    xwindow = XCreateWindow(
        dpy,                   /* Display to create new window for */
        parent,                /* Parent X-window */
        0, 0,                  /* Initial X and Y coords of new window */
        width, height,         /* Initial width and height of new window */
        0,                     /* Border width */
        pInfo->depth,          /* Depth of window */
        InputOutput,           /* Type of window */
        pInfo->visual,                             /* Visual class */
        CWBorderPixel | CWColormap | CWEventMask,  /* | CWSaveUnder */
        &swa                                       /* Window attributes */
    );

    /* Create an Open-GL rendering context to use. */
    pCanvas->context = glXCreateContext(dpy, pInfo, 0, GL_TRUE);

    return xwindow;
}

/*
 *---------------------------------------------------------------------------
 *
 * C3d_Native_Select3dContext --
 *
 *     This function selects the correct OpenGL context to draw the 3d scene
 *     using either glXMakeCurrent() or wglMakeCurrent(), depending on the
 *     platform (X11 or win32).
 *
 *     The context selected draws either to the back-buffer of the
 *     double-buffered window, or to a platform specific bitmap (a Pixmap for
 *     X11, a DIB for win32).
 *
 *     If required, this function may allocate and/or deallocate bitmaps.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static void
C3d_Native_Select3dContext(C3dWidget *pCanvas)
{
    Tk_Window win = pCanvas->tkwin;
    Display *dpy = pCanvas->display;
    int saveunder = pCanvas->options.saveunder;
    int w = Tk_Width(win);
    int h = Tk_Height(win);

    /* If the -saveunder mode is not "none", then we need at least
     * C3dWidget.pixmap.
     */
    if (saveunder != SAVEUNDER_NONE && !pCanvas->pixmap) {
        Pixmap pixmap;
        pixmap = C3dGetPixmap(dpy, Tk_WindowId(win), w, h, Tk_Depth(win));
        assert(pixmap);
        pCanvas->pixmap = pixmap;
        pCanvas->pixmapwidth = w;
        pCanvas->pixmapheight = h;
	{
            GLXPixmap glxpixmap;
            XVisualInfo *pInfo;
            static int attr[] = {
                GLX_RGBA,
                GLX_DEPTH_SIZE, 4,
                None
            };
            pInfo = glXChooseVisual(dpy, Tk_ScreenNumber(win), attr);
            glxpixmap = glXCreateGLXPixmap(dpy, pInfo, pixmap);
            pCanvas->glxpixmap = glxpixmap;

            if (!pCanvas->pixcontext) {
                pCanvas->pixcontext = glXCreateContext(dpy, pInfo, 0, GL_TRUE);
            }
            assert(pCanvas->glxpixmap);
	}
    }

    assert(saveunder == SAVEUNDER_NONE || w == pCanvas->pixmapwidth);
    assert(saveunder == SAVEUNDER_NONE || h == pCanvas->pixmapheight);

    /* If the -saveunder mode is "3d", then we also need C3dWidget.pixmap3d */
    if (saveunder == SAVEUNDER_3D && !pCanvas->pixmap3d) {
        Pixmap pixmap;
        pixmap = C3dGetPixmap(dpy, Tk_WindowId(win), w, h, Tk_Depth(win));
        assert(pixmap);
        pCanvas->pixmap3d = pixmap;
    }

    /* Finally, select the appropriate context. */
    if (saveunder) {
        glXMakeCurrent(dpy, pCanvas->glxpixmap,pCanvas->pixcontext);
    } else {
        glXMakeCurrent(dpy, Tk_WindowId(pCanvas->tkwin), pCanvas->context);
    }
    pCanvas->options.width = w;
    pCanvas->options.height = h;
    glViewport(0, 0, w, h);
}

/*
 *---------------------------------------------------------------------------
 *
 * C3d_Native_Release3dContext --
 *
 *     This function releases the current OpenGL context.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static void
C3d_Native_Release3dContext(C3dWidget *pCanvas)
{
}

/*
 *---------------------------------------------------------------------------
 *
 * C3d_Native_Pixmap3dToPixmap --
 *
 *     Copy from C3dPixmap.pixmap to C3dPixmap.pixmap3d.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static void
C3d_Native_Pixmap3dToPixmap(C3dWidget *pCanvas)
{
    int w = pCanvas->pixmapwidth;
    int h = pCanvas->pixmapheight;

    Pixmap dest = pCanvas->pixmap;
    Pixmap src = pCanvas->pixmap3d;
    GC gc = C3dGetGC(pCanvas->tkwin, 0, 0);
    assert(src && dest);
    XCopyArea(pCanvas->display, src, dest, gc, 0, 0, w, h, 0, 0);
    C3dFreeGC(pCanvas->display, gc);
}

/*
 *---------------------------------------------------------------------------
 *
 * C3d_Native_PixmapToPixmap3d --
 *
 *     Copy from C3dPixmap.pixmap3d to C3dPixmap.pixmap.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static void
C3d_Native_PixmapToPixmap3d(C3dWidget *pCanvas)
{
    int w = pCanvas->pixmapwidth;
    int h = pCanvas->pixmapheight;

    Pixmap dest = pCanvas->pixmap3d;
    Pixmap src = pCanvas->pixmap;
    GC gc = C3dGetGC(pCanvas->tkwin, 0, 0);
    assert(src && dest);
    XCopyArea(pCanvas->display, src, dest, gc, 0, 0, w, h, 0, 0);
    C3dFreeGC(pCanvas->display, gc);
}

/*
 *---------------------------------------------------------------------------
 *
 * C3d_Native_PixmapToWindow --
 *
 *     If the -saveunder mode is not "none", copy from the pixmap
 *     C3dWidget.pixmap to the main window. If it is "none", then swap the
 *     OpenGL windows buffers.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static void
C3d_Native_PixmapToWindow(C3dWidget *pCanvas)
{
    if (pCanvas->options.saveunder == SAVEUNDER_NONE) {
        glXSwapBuffers(pCanvas->display, Tk_WindowId(pCanvas->tkwin));
    } else {
        int w = pCanvas->pixmapwidth;
        int h = pCanvas->pixmapheight;
        GC gc = C3dGetGC(pCanvas->tkwin, 0, 0);
	assert(pCanvas->pixmap);
        XCopyArea(pCanvas->display, pCanvas->pixmap,
		  Tk_WindowId(pCanvas->tkwin), gc, 0, 0, w, h, 0, 0);
        C3dFreeGC(pCanvas->display, gc);
    }
}

/*
 *---------------------------------------------------------------------------
 *
 * C3d_Native_EventProc --
 *
 *     Widget callback for <Expose> events. In this case we need to redraw
 *     the scene. We don't do the drawing immediately, instead
 *     C3dDrawWhenIdle() is invoked.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static void
C3d_Native_EventProc(ClientData clientData, XEvent *eventPtr)
{
}

static int
C3d_Native_Init(Tcl_Interp *interp)
{
    if (0) {
	/* Squelch compiler warning */
	C3d_Native_ResizeContext(NULL, 0, 0);
    }
    if (0) {
	/* Squelch compiler warning */
	C3d_Native_Pixmap3dToPixmap(NULL);
    }
    return TCL_OK;
}
