Using Pixel Buffers with ATI Fire GL graphics boards under Linux/XFree86 4.X
============================================================================


                             ATI Research GmbH
                                Markus Neff


                            September 13, 2001

Latest Update: 2002-Oct-10

IMPORTANT NOTE:
  This is a preliminary document which might differ in several cases from
  the true capabilities and specific behaviour of the fglrx drivers.
  It is provided as it is in order to give the developer a minimum guideline.


1. Introduction / disclaimer

This document describes and specifies the API for using Pixel Buffers 
(pbuffers) with the ATI Fire GL graphics boards under Linux/XFree86.
Please note that pbuffer support is still in an early beta stage. It may 
contain bugs and some limitations that we - hopefully - can clear with the 
next releases of the driver.
This pre-release represents the current direction of our pbuffer support 
development under Linux/XFree86. To find out if we are on the right way, we 
provide this "snapshot version" of our driver (and especially the pbuffer 
support) "as is" - without any warranty of fitness for usage in a "production 
environment".


2. Description of the "pbuffer API"

Pbuffer support was officially introduced in GLX 1.3 but Silicon Graphics, 
Inc. has publicly released only the source code of GLX 1.2 so far. This is 
included in XFree4.X. To provide pbuffer support to our customers, we decided 
to partially emulate the behavior of GLX 1.3 on top of GLX 1.2. As soon as 
the "real" GLX 1.3 API is available in XFree86, you can use the "genuine" 
pbuffers of GLX 1.3 with our drivers. This will not require any changes in 
your application code because the programmer has to use the same function 
calls.
To use pbuffers, you need a custom version of "libGL.so.1.2" installed - 
including our GLX 1.3 emulation code. Our driver installation tool will 
automatically do that for you.

NOTE: Your application will report GLX version 1.2 on glXQueryVersion. 
However, it is safe to call the GLX 1.3 functions as described below.

Now let's talk about the emulated functions of GLX 1.3. First, I'll name all 
functions that are present in our emulation layer. Then I'll describe where 
they differ from the original functions. The pdf file "glx1.3.pdf"
(ftp://ftp.sgi.com/opengl/doc/opengl1.2/glx1.3.pdf) contains the original 
GLX 1.3 specs. For general questions please refer to the related chapters 
of that document.


2.1 Function specification

GLX 1.3 replaces the use of X Visuals for configuration management with the 
use of GLXFBConfigs. For handling GLXFBConfigs, the following functions are 
provided:
    - glXGetFBConfigs
    - glXChooseFBConfig
    - glXGetFBConfigAttrib
    - glXGetVisualFromFBConfig

All of these functions are implemented in our emulation. However, please use 
these functions only to manage configurations for pbuffers. For example, if 
you want to create a window with a certain GLXFBConfig, you can use 
glXGetVisualFromFBConfig to get the respective XVisualInfo.
Creating contexts for use with pbuffers is similar: You get the XVisualInfo 
by calling glXGetVisualFromFBConfig. Then you create the context with 
glXCreateContext using exactly this XVisualInfo.

To create / destroy pbuffers, the following functions exist:
    - glXCreatePbuffer
    - glXDestroyPbuffer

In future releases, we may provide the calls glXCreateNewContext, 
glXQueryDrawable and glXMakeContextCurrent. The latter would allow to specify 
a different source for pixel data when assigning a context to be the current 
context. Then you can, for example, transfer images from a pbuffer to an X 
window calling glCopyPixles. As for now, you can only read the pixels from 
one drawable to host memory (glReadPixels) and draw them to another drawable 
(glDrawPixels).


2.2 Current limitations / deviations from GLX 1.3 specs

First, let me mention some general limitations:

    - You should expect that you can create only a limited number of pbuffers.
      If you try to create more pbuffers than the adapter is capable then 
      the call to glXCreatePbuffer will not succeed but raise an error.
    - When you create a pbuffer, other applications can also make use of this 
      pbuffer, if they know its XID. But please note the following: Unlike 
      stated in GLX 1.3 spec, if one client destroys a pbuffer that is 
      current to any client, undefined behavior occurs. That's why the 
      programmer has to make sure that only unbound pbuffers are destroyed.
    - If you use error handlers (XSetErrorHandler) to catch GLX protocol 
      errors, you need to change the error handler code to look for different 
      major/minor opcodes. This is because the pbuffer requests don't use the 
      GLX protocol.
    - Some error codes are different from the error codes specified in the 
      GLX 1.3 specs. We try to fix this and the other issues.

Function glXChooseFBConfig:

    - The following attributes can be specified in the attribute list but 
      they will be ignored: GLX_MAX_PUBUFFER_*


3. Sample application

For a better understanding and for demonstration, a simple pbuffer example 
program is provided with the drivers. If you start the program, you will see 
a rotating cube. On each face of the cube, you can observe a changing texture 
showing three rotating gear wheels. These texture images are generated by 
rendering to a pbuffer and a subsequent call to glCopyTexImage2D.
By pressing the arrow keys, the position of the gear wheels relative to the 
viewer can be changed.

The following command will compile the example:

gcc -o fgl_glxgears -Wall -I/usr/X11R6/include -L/usr/X11R6/lib -lGL -lGLU -lX11 -lm fgl_glxgears.c


================================ code start =================================

/*
 * Copyright (C) 1999-2001  Brian Paul   All Rights Reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
 * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
/* $XFree86: xc/programs/glxgears/glxgears.c,v 1.2 2001/04/03 15:56:26 dawes 
Exp $ */

/*
 * This is a port of the infamous "gears" demo to straight GLX (i.e. no GLUT)
 * Port by Brian Paul  23 March 2001
 *
 * Command line options:
 *    -info      print GL implementation information
 *    -help      print usage information
 */

/*
 * Modifing person:      Markus Neff, ATI Research GmbH
 * Date of modification: 2001/11/09
 *
 * This is a modified version of the "glxgears" demo, which demonstrates 
 * the usage of pixel buffers (pbuffers) whith an ATI Fire GL graphics board 
 * and a patched version of libGL.so.
 * Some other code in this example (the cube stuff) is taken from an OGL 
 * tutorial by Jeff Molofee.
 *
 * The three gear wheels are rendered to a pbuffer instead of to a window. 
 * These images are loaded to texture memory to be mapped on the four faces 
 * of a cube.
 *
 * The user can press the arrow keys to change his position relative to the
 * gears.
 */

#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <GL/glu.h>
#include <GL/gl.h>
#include <GL/glx.h>

#include <sys/time.h>
#include <unistd.h>

/* return current time (in seconds) */
static int
current_time(void)
{
   struct timeval tv;
   struct timezone tz;
   (void) gettimeofday(&tv, &tz);
   return (int) tv.tv_sec;
}

#ifndef M_PI
#define M_PI 3.14159265
#endif


/* dimensions of texture and pbuffer */
#define TEXTURE_SIZE_XY       256
#define TEXTURE_BORDER_WIDTH  3
#define PBUFFER_WIDTH_HEIGHT  TEXTURE_SIZE_XY - 2*TEXTURE_BORDER_WIDTH

/* for simplicity, i take global var. for X and GLX resources */
static Display *dpy        = NULL;
static Window win          = 0;
static GLXPbuffer pbuf     = 0;
static GLXContext ctx_win  = NULL,
                  ctx_pbuf = NULL;

/* gears stuff */
static GLfloat view_rotx = 20.0,
               view_roty = 30.0,
               view_rotz = 0.0;
static GLint   gear1     = 0,
               gear2     = 0,
               gear3     = 0;
static GLfloat angle     = 0.0;

/* cube stuff */
static GLfloat rotX = 0;    /* X Rotation */
static GLfloat rotY = 0;    /* Y Rotation */
static GLfloat rotZ = 0;    /* Z Rotation */
static GLuint  texture[1];  /* Storage For One Texture */


/*
 *  Draw a gear wheel. You'll probably want to call this function when
 *  building a display list since we do a lot of trig here.
 * 
 *  Input:  inner_radius - radius of hole at center
 *          outer_radius - radius at center of teeth
 *          width        - width of gear
 *          teeth        - number of teeth
 *          tooth_depth  - depth of tooth
 */
static void
gear(GLfloat inner_radius, GLfloat outer_radius, GLfloat width,
     GLint teeth, GLfloat tooth_depth)
{
   GLint i;
   GLfloat r0, r1, r2;
   GLfloat angle, da;
   GLfloat u, v, len;

   r0 = inner_radius;
   r1 = outer_radius - tooth_depth / 2.0;
   r2 = outer_radius + tooth_depth / 2.0;

   da = 2.0 * M_PI / teeth / 4.0;

   glShadeModel(GL_FLAT);

   glNormal3f(0.0, 0.0, 1.0);

   /* draw front face */
   glBegin(GL_QUAD_STRIP);
   for (i = 0; i <= teeth; i++) {
      angle = i * 2.0 * M_PI / teeth;
      glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5);
      glVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5);
      if (i < teeth) {
         glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5);
	     glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
             width * 0.5);
      }
   }
   glEnd();

   /* draw front sides of teeth */
   glBegin(GL_QUADS);
   da = 2.0 * M_PI / teeth / 4.0;
   for (i = 0; i < teeth; i++) {
      angle = i * 2.0 * M_PI / teeth;

      glVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5);
      glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), width * 0.5);
      glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
		 width * 0.5);
      glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
		 width * 0.5);
   }
   glEnd();

   glNormal3f(0.0, 0.0, -1.0);

   /* draw back face */
   glBegin(GL_QUAD_STRIP);
   for (i = 0; i <= teeth; i++) {
      angle = i * 2.0 * M_PI / teeth;
      glVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5);
      glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
      if (i < teeth) {
          glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), 
              -width * 0.5);
          glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
      }
   }
   glEnd();

   /* draw back sides of teeth */
   glBegin(GL_QUADS);
   da = 2.0 * M_PI / teeth / 4.0;
   for (i = 0; i < teeth; i++) {
      angle = i * 2.0 * M_PI / teeth;

      glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
          -width * 0.5);
      glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
          -width * 0.5);
      glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -width * 0.5);
      glVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5);
   }
   glEnd();

   /* draw outward faces of teeth */
   glBegin(GL_QUAD_STRIP);
   for (i = 0; i < teeth; i++) {
      angle = i * 2.0 * M_PI / teeth;

      glVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5);
      glVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5);
      u = r2 * cos(angle + da) - r1 * cos(angle);
      v = r2 * sin(angle + da) - r1 * sin(angle);
      len = sqrt(u * u + v * v);
      u /= len;
      v /= len;
      glNormal3f(v, -u, 0.0);
      glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), width * 0.5);
      glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -width * 0.5);
      glNormal3f(cos(angle), sin(angle), 0.0);
      glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
          width * 0.5);
      glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da),
          -width * 0.5);
      u = r1 * cos(angle + 3 * da) - r2 * cos(angle + 2 * da);
      v = r1 * sin(angle + 3 * da) - r2 * sin(angle + 2 * da);
      glNormal3f(v, -u, 0.0);
      glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
          width * 0.5);
      glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da),
          -width * 0.5);
      glNormal3f(cos(angle), sin(angle), 0.0);
   }

   glVertex3f(r1 * cos(0), r1 * sin(0), width * 0.5);
   glVertex3f(r1 * cos(0), r1 * sin(0), -width * 0.5);

   glEnd();

   glShadeModel(GL_SMOOTH);

   /* draw inside radius cylinder */
   glBegin(GL_QUAD_STRIP);
   for (i = 0; i <= teeth; i++) {
      angle = i * 2.0 * M_PI / teeth;
      glNormal3f(-cos(angle), -sin(angle), 0.0);
      glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5);
      glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5);
   }
   glEnd();
}


static void drawGears(void)
{
   glXMakeCurrent(dpy, pbuf, ctx_pbuf);

   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   glPushMatrix();
   glRotatef(view_rotx, 1.0, 0.0, 0.0);
   glRotatef(view_roty, 0.0, 1.0, 0.0);
   glRotatef(view_rotz, 0.0, 0.0, 1.0);

   glPushMatrix();
   glTranslatef(-3.0, -2.0, 0.0);
   glRotatef(angle, 0.0, 0.0, 1.0);
   glCallList(gear1);
   glPopMatrix();

   glPushMatrix();
   glTranslatef(3.1, -2.0, 0.0);
   glRotatef(-2.0 * angle - 9.0, 0.0, 0.0, 1.0);
   glCallList(gear2);
   glPopMatrix();

   glPushMatrix();
   glTranslatef(-3.1, 4.2, 0.0);
   glRotatef(-2.0 * angle - 25.0, 0.0, 0.0, 1.0);
   glCallList(gear3);
   glPopMatrix();

   glPopMatrix();
   glFlush();

   glBindTexture(GL_TEXTURE_2D, texture[0]);
   glCopyTexSubImage2D(GL_TEXTURE_2D, 0,
                       TEXTURE_BORDER_WIDTH, TEXTURE_BORDER_WIDTH, 
                       0, 0, PBUFFER_WIDTH_HEIGHT, PBUFFER_WIDTH_HEIGHT);
}


static void drawCube(void)
{
    glXMakeCurrent(dpy, win, ctx_win);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glLoadIdentity();
    glTranslatef(0.0f, 0.0f, -5.0f);
    glRotatef(rotX, 1.0f, 0.0f, 0.0f);  /* rotate on the X axis */
    glRotatef(rotY, 0.0f, 1.0f, 0.0f);  /* rotate on the Y axis */
    glRotatef(rotZ, 0.0f, 0.0f, 1.0f);  /* rotate on the Z axis */

    glBindTexture(GL_TEXTURE_2D, texture[0]);   /* select our  texture */

    glBegin(GL_QUADS);
        /* front face */
        glTexCoord2f(0.0f, 0.0f);
        glVertex3f(-1.0f, -1.0f, 1.0f); 
        glTexCoord2f(1.0f, 0.0f);
        glVertex3f(1.0f, -1.0f, 1.0f);
        glTexCoord2f(1.0f, 1.0f);
        glVertex3f(1.0f, 1.0f, 1.0f);
        glTexCoord2f(0.0f, 1.0f);
        glVertex3f(-1.0f, 1.0f, 1.0f);
        /* back face */
        glTexCoord2f(1.0f, 0.0f);
        glVertex3f(-1.0f, -1.0f, -1.0f); 
        glTexCoord2f(1.0f, 1.0f);
        glVertex3f(-1.0f, 1.0f, -1.0f);
        glTexCoord2f(0.0f, 1.0f);
        glVertex3f(1.0f, 1.0f, -1.0f);
        glTexCoord2f(0.0f, 0.0f);
        glVertex3f(1.0f, -1.0f, -1.0f);
        /* right face */
        glTexCoord2f(1.0f, 0.0f);
        glVertex3f(1.0f, -1.0f, -1.0f); 
        glTexCoord2f(1.0f, 1.0f);
        glVertex3f(1.0f, 1.0f, -1.0f);
        glTexCoord2f(0.0f, 1.0f);
        glVertex3f(1.0f, 1.0f, 1.0f);
        glTexCoord2f(0.0f, 0.0f);
        glVertex3f(1.0f, -1.0f, 1.0f);
        /* left face */
        glTexCoord2f(1.0f, 0.0f);
        glVertex3f(-1.0f, -1.0f, 1.0f); 
        glTexCoord2f(1.0f, 1.0f);
        glVertex3f(-1.0f, 1.0f, 1.0f);
        glTexCoord2f(0.0f, 1.0f);
        glVertex3f(-1.0f, 1.0f, -1.0f);
        glTexCoord2f(0.0f, 0.0f);
        glVertex3f(-1.0f, -1.0f, -1.0f);
        /* top face */
        glTexCoord2f(1.0f, 0.0f);
        glVertex3f(1.0f, 1.0f, 1.0f); 
        glTexCoord2f(1.0f, 1.0f);
        glVertex3f(1.0f, 1.0f, -1.0f);
        glTexCoord2f(0.0f, 1.0f);
        glVertex3f(-1.0f, 1.0f, -1.0f);
        glTexCoord2f(0.0f, 0.0f);
        glVertex3f(-1.0f, 1.0f, 1.0f);
        /* bottom face */
        glTexCoord2f(1.0f, 0.0f);
        glVertex3f(1.0f, -1.0f, -1.0f); 
        glTexCoord2f(1.0f, 1.0f);
        glVertex3f(1.0f, -1.0f, 1.0f);
        glTexCoord2f(0.0f, 1.0f);
        glVertex3f(-1.0f, -1.0f, 1.0f);
        glTexCoord2f(0.0f, 0.0f);
        glVertex3f(-1.0f, -1.0f, -1.0f);
    glEnd();

    /* change the rotation angles */
    rotX += 0.3f;
    rotY += 0.2f;
    rotZ += 0.4f;

    glXSwapBuffers(dpy, win);
}


/* function called when pbuffer is created to setup scene */
static void reshapeGears(unsigned int width, unsigned int height)
{
   GLfloat h = (GLfloat) height / (GLfloat) width;

   glViewport(0, 0, (GLint) width, (GLint) height);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   glFrustum(-1.0, 1.0, -h, h, 5.0, 60.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   glTranslatef(0.0, 0.0, -40.0);
}


/* function called when our window is resized */
static void reshapeCube(unsigned int width, unsigned int height)
{
    /* Prevent A Divide By Zero If The Window Is Too Small */
    if (height == 0)
        height = 1;
    /* Reset The Current Viewport And Perspective Transformation */
    glViewport(0, 0, width, height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0f, (GLfloat)width / (GLfloat)height, 0.1f, 100.0f);
    glMatrixMode(GL_MODELVIEW);
}


static void init(void)
{
   static GLfloat pos[4]   = { 5.0, 5.0, 10.0, 0.0 };
   static GLfloat red[4]   = { 0.8, 0.1, 0.0, 1.0 };
   static GLfloat green[4] = { 0.0, 0.8, 0.2, 1.0 };
   static GLfloat blue[4]  = { 0.2, 0.2, 1.0, 1.0 };
   unsigned char *whiteImage = NULL;
   
   
   /* create a "white" RGBA image as initial texture */
   whiteImage = malloc(TEXTURE_SIZE_XY*TEXTURE_SIZE_XY*4);
   if (NULL == whiteImage) {
       fprintf(stderr, "Error: not enough memory => bye ...\n");
       exit(1);
   }
   memset(whiteImage, 255, TEXTURE_SIZE_XY*TEXTURE_SIZE_XY*4);

   /* stuff for window */
   glXMakeCurrent(dpy, win, ctx_win);

   glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
   glClearDepth(1.0f);
   glEnable(GL_DEPTH_TEST);
   glEnable(GL_TEXTURE_2D);
   glShadeModel(GL_SMOOTH);
   glDepthFunc(GL_LEQUAL);
   glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
   glPixelStorei(GL_UNPACK_ALIGNMENT,1);

   /* create the texture */
   glGenTextures(1, &texture[0]);
   glBindTexture(GL_TEXTURE_2D, texture[0]);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
   glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 
                TEXTURE_SIZE_XY, TEXTURE_SIZE_XY,
                0, GL_RGBA, GL_UNSIGNED_BYTE, whiteImage);
   free(whiteImage);
   

   /* stuff for pbuffer */
   glXMakeCurrent(dpy, pbuf, ctx_pbuf);

   glPixelStorei(GL_UNPACK_ALIGNMENT,1);
   glClearColor(0.2f, 0.2f, 0.2f, 0.2f);

   glLightfv(GL_LIGHT0, GL_POSITION, pos);
   glEnable(GL_CULL_FACE);
   glEnable(GL_LIGHTING);
   glEnable(GL_LIGHT0);
   glEnable(GL_DEPTH_TEST);
   reshapeGears(PBUFFER_WIDTH_HEIGHT, PBUFFER_WIDTH_HEIGHT);

   /* make the gears */
   gear1 = glGenLists(1);
   glNewList(gear1, GL_COMPILE);
   glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, red);
   gear(1.0, 4.0, 1.0, 20, 0.7);
   glEndList();

   gear2 = glGenLists(1);
   glNewList(gear2, GL_COMPILE);
   glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, green);
   gear(0.5, 2.0, 2.0, 10, 0.7);
   glEndList();

   gear3 = glGenLists(1);
   glNewList(gear3, GL_COMPILE);
   glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, blue);
   gear(1.3, 2.0, 0.5, 10, 0.7);
   glEndList();

   glEnable(GL_NORMALIZE);
}


/*
 * Create an RGBA, double-buffered window.
 */
static void make_window(const char *name, int x, int y, int width, int 
height)
{
   int attrib[] = { GLX_RGBA,
		    GLX_RED_SIZE, 1,
		    GLX_GREEN_SIZE, 1,
		    GLX_BLUE_SIZE, 1,
		    GLX_DOUBLEBUFFER,
		    GLX_DEPTH_SIZE, 1,
		    None };
   int scrnum;
   XSetWindowAttributes attr;
   unsigned long mask;
   Window root;
   XVisualInfo *visinfo;

   scrnum = DefaultScreen( dpy );
   root   = RootWindow( dpy, scrnum );

   visinfo = glXChooseVisual( dpy, scrnum, attrib );
   if (!visinfo) {
      printf("Error: couldn't get an RGBA, Double-buffered visual\n");
      exit(1);
   }

   /* window attributes */
   attr.background_pixel = 0;
   attr.border_pixel = 0;
   attr.colormap = XCreateColormap( dpy, root, visinfo->visual, AllocNone);
   attr.event_mask = StructureNotifyMask | ExposureMask | KeyPressMask;
   mask = CWBackPixel | CWBorderPixel | CWColormap | CWEventMask;

   win = XCreateWindow( dpy, root, 0, 0, width, height,
		        0, visinfo->depth, InputOutput,
		        visinfo->visual, mask, &attr );

   /* set hints and properties */
   {
      XSizeHints sizehints;
      sizehints.x = x;
      sizehints.y = y;
      sizehints.width  = width;
      sizehints.height = height;
      sizehints.flags = USSize | USPosition;
      XSetNormalHints(dpy, win, &sizehints);
      XSetStandardProperties(dpy, win, name, name,
                              None, (char **)NULL, 0, &sizehints);
   }

   ctx_win = glXCreateContext( dpy, visinfo, NULL, GL_TRUE );
   if (!ctx_win) {
      printf("Error: glXCreateContext failed\n");
      exit(1);
   }

   XFree(visinfo);
}


/*
 * Create an RGBA, double-buffered pbuffer.
 */
static void make_pbuffer(int width, int height)
{
   int scrnum;
   GLXFBConfig *fbconfig;
   XVisualInfo *visinfo;
   int nitems;

   int attrib[] = {
      GLX_DOUBLEBUFFER,  False,
      GLX_RED_SIZE,      1,
      GLX_GREEN_SIZE,    1,
      GLX_BLUE_SIZE,     1,
      GLX_DEPTH_SIZE,    1,
      GLX_RENDER_TYPE,   GLX_RGBA_BIT,
      GLX_DRAWABLE_TYPE, GLX_PBUFFER_BIT | GLX_WINDOW_BIT,
      None
   };
   int pbufAttrib[] = {
      GLX_PBUFFER_WIDTH,   width,
      GLX_PBUFFER_HEIGHT,  height,
      GLX_LARGEST_PBUFFER, False,
      None
   };


   scrnum   = DefaultScreen( dpy );

   fbconfig = glXChooseFBConfig(dpy,
                                scrnum,
                                attrib,
                                &nitems);
   if (NULL == fbconfig) {
      fprintf(stderr,"Error: couldn't get fbconfig\n");
      exit(1);
   }

   pbuf = glXCreatePbuffer(dpy, fbconfig[0], pbufAttrib);

   visinfo = glXGetVisualFromFBConfig(dpy, fbconfig[0]);
   if (!visinfo) {
      fprintf(stderr, "Error: couldn't get an RGBA, double-buffered visual\n");
      exit(1);
   }

   ctx_pbuf = glXCreateContext( dpy, visinfo, ctx_win, GL_TRUE );
   if (!ctx_pbuf) {
      fprintf(stderr, "Error: glXCreateContext failed\n");
      exit(1);
   }

   XFree(fbconfig);
   XFree(visinfo);
}


static void event_loop(void)
{
   while (1) {
      while (XPending(dpy) > 0) {
         XEvent event;
         XNextEvent(dpy, &event);
         switch (event.type) {
         case Expose:
            /* we'll redraw below */
	        break;
         case ConfigureNotify:
            glXMakeCurrent(dpy, win, ctx_win);
	        reshapeCube(event.xconfigure.width, event.xconfigure.height);
	        break;
         case KeyPress:
            {
               char buffer[10];
               int r, code;
               code = XLookupKeysym(&event.xkey, 0);
               if (code == XK_Left) {
                  view_roty += 5.0;
               }
               else if (code == XK_Right) {
                  view_roty -= 5.0;
               }
               else if (code == XK_Up) {
                  view_rotx += 5.0;
               }
               else if (code == XK_Down) {
                  view_rotx -= 5.0;
               }
               else {
                  r = XLookupString(&event.xkey, buffer, sizeof(buffer),
                                    NULL, NULL);
                  if (buffer[0] == 27) {
                     /* escape */
                     return;
                  }
               }
            }
         }
      }

      /* next frame */
      angle += 2.0;

      drawGears();
      drawCube();

      /* calc framerate */
      {
         static int t0 = -1;
         static int frames = 0;
         int t = current_time();

         if (t0 < 0)
            t0 = t;

         frames++;

         if (t - t0 >= 5.0) {
            GLfloat seconds = t - t0;
            GLfloat fps = frames / seconds;
            printf("%d frames in %3.1f seconds = %6.3f FPS\n", frames, seconds,
                   fps);
            t0 = t;
            frames = 0;
         }
      }
   }
}


void my_print_usage(void)
{
    fprintf(stdout,
           "\n"
           "Usage:\n"
           "------\n"
           "\n"
           "-display <dpy>    redirect output to display <dpy>\n"
           "-help             print this information\n"
           "-info             print GLX information (vendor etc.) to
                              stdout\n"
           "\n");
}


int
main(int argc, char *argv[])
{
   int i;
   char *dpyName       = NULL;
   GLboolean printInfo = GL_FALSE;

   /* parse arguments */
   if (argc > 5) {
       fprintf(stdout, "%s: wrong number of arguments specified ...\n",
               argv[0]);
       my_print_usage();
       exit(1);
   }

   for (i = 1; i < argc; i++) {
      if (strcmp(argv[i], "-display") == 0) {
         i++;
         if (i >= argc) {
            my_print_usage();
            exit(1);
         }
         dpyName = argv[i];
      }
      else if (strcmp(argv[i], "-info") == 0) {
         printInfo = GL_TRUE;
      }
      else if (strcmp(argv[i], "-help") == 0) {
         my_print_usage();
         exit(1);
      }
      else {
         my_print_usage();
         exit(1);
      }
   }

   /* open the display */
   dpy = XOpenDisplay(dpyName);
   if (!dpy) {
      printf("Error: couldn't open display %s\n", dpyName);
      return -1;
   }

   if (printInfo) {
      printf("GL_RENDERER   = %s\n", (char *) glGetString(GL_RENDERER));
      printf("GL_VERSION    = %s\n", (char *) glGetString(GL_VERSION));
      printf("GL_VENDOR     = %s\n", (char *) glGetString(GL_VENDOR));
      printf("GL_EXTENSIONS = %s\n", (char *) glGetString(GL_EXTENSIONS));
   }

   /* setup and initial window, pbuffer and texture */
   make_window("PBuffer GLXGears", 0, 0, 600, 600);
   XMapWindow(dpy, win);

   make_pbuffer(PBUFFER_WIDTH_HEIGHT, PBUFFER_WIDTH_HEIGHT);
   
   init();

   /* start processing of X events */
   event_loop();

   /* clean up */
   glXDestroyContext(dpy, ctx_win);
   XDestroyWindow(dpy, win);

   glXDestroyContext(dpy, ctx_pbuf);
   glXDestroyPbuffer(dpy, pbuf);

   XCloseDisplay(dpy);

   return 0;
}

================================= code end ==================================

### EOF ###
