Render Buffer Interface Code

This is a quick and dirty explanation of how interfacing with the GLX/X
protocol buffers works in the glx client code. This is intended for people
who need to write or modify code that sends and receives requests to a GLX
server, via these buffers. Examples would be debugging current protocol errors,
and adding new protocol requests, such as extensions.

Most basic protocol requests are auto-generated from the libGL.tcl 
specification. However, any request that needs a reply or sends varying amount
of data (esp. pixel data) is hand coded. Although it wouldn't be entirely
difficult to auto-generate all of this code, it would require some changes to
how the spec file is parsed and code is auto-generated.

The specification for how GLX protocol packets are defined is contained in the
GLX protocol spec, available from ftp://sgigate.sgi.com, in the 
/pub/opengl/doc/ directory, in the file specs.tar.Z. This includes the OpenGL
and GLU specs as well. More information about the X Protocol spec (upon which
the GLX protocol is based) is available from the X tree itself, in 
xc/doc/hardcopy/XProtocol/.

Put simply, creating a GLX packet is two main steps: requesting a buffer, and
filling that buffer. The new interface code simplifies all of the details down
to this. No longer do you need to worry about regular or large requests when
adding a new function call.


Requesting a Buffer
A buffer is requested with the following macro:
__GLX_GET_RENDER_BUFFER(buffer, opcode, request_size, extra_data_size)

buffer should be a simple char * pointer, that will be set to point into the
allocated packet, to the next available byte.

opcode should be the opcode of the request. It will be placed into the packet
for you.

request_size is the size of the request packet itself. This is how much space
will be allocated by the macro.

extra_data_size is for potentially large requests. While the non-varying 
request size should be given above, this is the variable data size (for 
example, a texture size). For a regular request, this should be 0.


Filling a Buffer
A buffer is filled with macros of the form:
__GLX_PUT_<data_type>(buffer, data)

examples are:
__GLX_PUT_int(), __GLX_PUT_enum(), __GLX_PUT_float(), etc.

buffer is the current buffer pointer, passed to and set by 
__GLX_GET_RENDER_BUFFER above.

data is the data to be inserted into the buffer.

The data will be placed into the buffer, and the buffer pointer will be 
appropriately incremented.


Example of Use.
Here is an example of how the above are used in a simple request:

__glx_Vertex3f( GLfloat x,  GLfloat y,  GLfloat z) {
   char* buffer=NULL;
   __GLX_GET_RENDER_BUFFER(buffer, 70, 16, 0);
   __GLX_PUT_float(buffer, x);
   __GLX_PUT_float(buffer, y);
   __GLX_PUT_float(buffer, z);
}


Large Requests.
There a few more details for sending data specific to potentially large 
requests. Specifically, there are two more macros to make life easier:

__GLX_PUT_PIXEL_DATA_ARGUMENTS
Pixel data requests (such as DrawPixels, TexImage, etc.) pass more information
than is included in the function call. Specifically, they pass information 
about how the pixel data is stored. This is because a client may do some 
unpacking of pixel data, or may rely on the server to do the unpacking. This
information is intended to communicate to the server what the client expects
the server to do.

In this specific GLX implementation, much unpacking is done on the client side.
This means that some of this information to the server is always the same,
since the client has already done the work for the server. Using this macro
hides these details when implementing a new function call as well as simplifies
making the packet.

__GLX_PUT_buffer(buffer, data, width, height, format, type, size)
This is used to send the extra data, similarly to the above macros to send
simple data types. This macro completely hides any unpacking that is performed
on the data, as well as sending the data on multiple packets in the case of a
large render request. The arguements are fairly straightforward,

buffer is, as above, a pointer into the current packet.

data is a pointer to the data to be sent.

width, height, format, type are the basic arguements to the function.

size is the size of the data, as calculated by GLX_image_size. Note that the
image size could be calculated inside of the macro, based on the data passed
to the macro (format, type, etc). This was not done, as your function needs to
pass this size to the initial GET_RENDER_BUFFER macro anyways, so this allows
you to reuse that calculation.

Example of Use:
Here's a sample from pixel.c:

void __glx_DrawPixels(GLsizei width, GLsizei height,
                  GLenum format, GLenum type, const GLvoid*pixels)
{
    char* buffer = NULL;
    int s=GLX_image_size(width, height, format, type);
    __GLX_GET_RENDER_BUFFER(buffer, 173, 40, s);
    __GLX_PUT_PIXEL_DATA_ARGUMENTS;
    __GLX_PUT_sizei(buffer, width);
    __GLX_PUT_sizei(buffer, height);
    __GLX_PUT_enum(buffer, format);
    __GLX_PUT_enum(buffer, type);
    __GLX_PUT_buffer(buffer, pixels, width, height, format, type, s);
}


NOTE about 'unused' bytes in packet:
These *must* be filled (most likely with 0, but it doesn't matter) like so:
__GLX_PUT_byte(buffer, 0)
since buffer needs to be incremented past the empty location. If this is not
done, the packet will be created wrong, and the library will not work 
properly.


Terence Ripperda
ripperda@sgi.com
