// Filename: glGraphicsStateGuardian_src.cxx
// Created by:  drose (02Feb99)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
//
// All use of this software is subject to the terms of the Panda 3d
// Software license.  You should have received a copy of this license
// along with this source code; you will also find a current copy of
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
//
// To contact the maintainers of this program write to
// panda3d-general@lists.sourceforge.net .
//
////////////////////////////////////////////////////////////////////

#include "config_util.h"
#include "displayRegion.h"
#include "renderBuffer.h"
#include "geom.h"
#include "geomIssuer.h"
#include "graphicsWindow.h"
#include "lens.h"
#include "perspectiveLens.h"
#include "directionalLight.h"
#include "pointLight.h"
#include "spotlight.h"
#include "planeNode.h"
#include "textureAttrib.h"
#include "lightAttrib.h"
#include "cullFaceAttrib.h"
#include "transparencyAttrib.h"
#include "alphaTestAttrib.h"
#include "depthTestAttrib.h"
#include "depthWriteAttrib.h"
#include "colorWriteAttrib.h"
#include "texMatrixAttrib.h"
#include "texGenAttrib.h"
#include "materialAttrib.h"
#include "renderModeAttrib.h"
#include "rescaleNormalAttrib.h"
#include "fogAttrib.h"
#include "depthOffsetAttrib.h"
#include "fog.h"
#include "clockObject.h"
#include "string_utils.h"
#include "nodePath.h"
#include "dcast.h"
#include "pvector.h"
#include "vector_string.h"
#include "string_utils.h"
#include "pnmImage.h"
#ifdef DO_PSTATS
#include "pStatTimer.h"
#endif
#ifdef HAVE_CGGL
#include "cgShaderAttrib.h"
#endif

#include <algorithm>

TypeHandle CLP(GraphicsStateGuardian)::_type_handle;

#ifdef DO_PSTATS
PStatCollector CLP(GraphicsStateGuardian)::_vertices_display_list_pcollector("Vertices:Display lists");
#endif

static void
issue_vertex_gl(const Geom *geom, Geom::VertexIterator &viterator, 
                GraphicsStateGuardianBase *) {
  const Vertexf &vertex = geom->get_next_vertex(viterator);
  // GLCAT.spam() << "Issuing vertex " << vertex << "\n";
  GLP(Vertex3fv)(vertex.get_data());
}

static void
issue_normal_gl(const Geom *geom, Geom::NormalIterator &niterator, 
                GraphicsStateGuardianBase *) {
  const Normalf &normal = geom->get_next_normal(niterator);
  // GLCAT.spam() << "Issuing normal " << normal << "\n";
  GLP(Normal3fv)(normal.get_data());
}

static void
issue_texcoord_single_gl(const Geom *geom, 
                         Geom::MultiTexCoordIterator &tciterator, 
                         GraphicsStateGuardianBase *) {
  const TexCoordf &texcoord = geom->get_next_multitexcoord(tciterator, 0);
  // GLCAT.spam() << "Issuing texcoord " << texcoord << " on unit 0 (single-texture mode)\n";
  GLP(TexCoord2fv)(texcoord.get_data());
}

static void
issue_texcoord_multi_gl(const Geom *geom, 
                        Geom::MultiTexCoordIterator &tciterator, 
                        GraphicsStateGuardianBase *gsgbase) {
  // We avoid DCAST here because we don't really need it, and it's
  // nice not to have to pay that overhead on each vertex.
  CLP(GraphicsStateGuardian) *gsg = (CLP(GraphicsStateGuardian) *)gsgbase;
  for (int i = 0; i < tciterator._num_stages; i++) {
    const TexCoordf &texcoord = geom->get_next_multitexcoord(tciterator, i);
    int stage_index = tciterator._stage_index[i];
    // GLCAT.spam() << "Issuing texcoord " << texcoord << " on unit " << stage_index << "\n";
    gsg->_glMultiTexCoord2fv(GL_TEXTURE0 + stage_index, texcoord.get_data());
  }
}

static void
issue_color_gl(const Geom *geom, Geom::ColorIterator &citerator,
               GraphicsStateGuardianBase *) {
  const Colorf &color = geom->get_next_color(citerator);
  //  GLCAT.spam() << "Issuing color " << color << "\n";
  GLP(Color4fv)(color.get_data());
}

static void
issue_scaled_color_gl(const Geom *geom, Geom::ColorIterator &citerator,
                           GraphicsStateGuardianBase *gsg) {
  const CLP(GraphicsStateGuardian) *glgsg = DCAST(CLP(GraphicsStateGuardian), gsg);
  const Colorf &color = geom->get_next_color(citerator);
  glgsg->issue_scaled_color(color);
}

// The following noop functions are assigned to the corresponding
// glext function pointers in the class, in case the functions are not
// defined by the GL, just so it will always be safe to call the
// extension functions.

static void APIENTRY
null_glActiveTexture(GLenum gl_texture_stage) {
  // If we don't support multitexture, we'd better not try to request
  // a texture beyond the first texture stage.
  nassertv(gl_texture_stage == GL_TEXTURE0);
}

static void APIENTRY
null_glBlendEquation(GLenum) {
}

static void APIENTRY
null_glBlendColor(GLclampf, GLclampf, GLclampf, GLclampf) {
}

////////////////////////////////////////////////////////////////////
//     Function: uchar_bgr_to_rgb
//  Description: Recopies the given array of pixels, converting from
//               BGR to RGB arrangement.
////////////////////////////////////////////////////////////////////
static void
uchar_bgr_to_rgb(unsigned char *dest, const unsigned char *source, 
                 int num_pixels) {
  for (int i = 0; i < num_pixels; i++) {
    dest[0] = source[2];
    dest[1] = source[1];
    dest[2] = source[0];
    dest += 3;
    source += 3;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: uchar_bgra_to_rgba
//  Description: Recopies the given array of pixels, converting from
//               BGRA to RGBA arrangement.
////////////////////////////////////////////////////////////////////
static void
uchar_bgra_to_rgba(unsigned char *dest, const unsigned char *source, 
                   int num_pixels) {
  for (int i = 0; i < num_pixels; i++) {
    dest[0] = source[2];
    dest[1] = source[1];
    dest[2] = source[0];
    dest[3] = source[3];
    dest += 4;
    source += 4;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: ushort_bgr_to_rgb
//  Description: Recopies the given array of pixels, converting from
//               BGR to RGB arrangement.
////////////////////////////////////////////////////////////////////
static void
ushort_bgr_to_rgb(unsigned short *dest, const unsigned short *source, 
                  int num_pixels) {
  for (int i = 0; i < num_pixels; i++) {
    dest[0] = source[2];
    dest[1] = source[1];
    dest[2] = source[0];
    dest += 3;
    source += 3;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: ushort_bgra_to_rgba
//  Description: Recopies the given array of pixels, converting from
//               BGRA to RGBA arrangement.
////////////////////////////////////////////////////////////////////
static void
ushort_bgra_to_rgba(unsigned short *dest, const unsigned short *source, 
                    int num_pixels) {
  for (int i = 0; i < num_pixels; i++) {
    dest[0] = source[2];
    dest[1] = source[1];
    dest[2] = source[0];
    dest[3] = source[3];
    dest += 4;
    source += 4;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: reduce_image
//  Description: Reduces an image to an acceptable size by sampling
//               pixels.  This is just a cheap and dirty trick to load
//               an image that the GL says is too large.
//
//               The implementation copies byte_chunk bytes every
//               byte_chunk * stride bytes.
////////////////////////////////////////////////////////////////////
static PTA_uchar
reduce_image(CPTA_uchar orig_image, int byte_chunk, int stride) {
  size_t orig_image_size = orig_image.size();
  size_t new_image_size = orig_image_size / stride;
  PTA_uchar new_image = PTA_uchar::empty_array(new_image_size);
  const unsigned char *from = orig_image.p();
  unsigned char *to = new_image.p();

  const unsigned char *from_end = from + orig_image_size;
  const unsigned char *to_end = to + new_image_size;

  while (from + byte_chunk <= from_end) {
    nassertr(to + byte_chunk <= to_end, new_image);
    memcpy(to, from, byte_chunk);
    from += stride * byte_chunk;
    to += byte_chunk;
  }

  return new_image;
}

////////////////////////////////////////////////////////////////////
//     Function: fix_component_ordering
//  Description: Reverses the order of the components within the
//               image, to convert (for instance) GL_BGR to GL_RGB.
//               Returns the CPTA_uchar representing the converted
//               image, or the original image if it is unchanged.
////////////////////////////////////////////////////////////////////
static PTA_uchar
fix_component_ordering(CPTA_uchar orig_image, GLenum external_format, 
                       Texture *tex) {
  size_t orig_image_size = orig_image.size();
  PTA_uchar new_image = (PTA_uchar &)orig_image;

  switch (external_format) {
  case GL_RGB:
    switch (tex->get_component_type()) {
    case Texture::T_unsigned_byte:
      new_image = PTA_uchar::empty_array(orig_image_size);
      uchar_bgr_to_rgb(new_image, orig_image, orig_image_size / 3);
      break;

    case Texture::T_unsigned_short:
      new_image = PTA_uchar::empty_array(orig_image_size);
      ushort_bgr_to_rgb((unsigned short *)new_image.p(), 
                        (const unsigned short *)orig_image.p(), 
                        orig_image_size / 6);
      break;

    default:
      break;
    }
    break;

  case GL_RGBA:
    switch (tex->get_component_type()) {
    case Texture::T_unsigned_byte:
      new_image = PTA_uchar::empty_array(orig_image_size);
      uchar_bgra_to_rgba(new_image, orig_image, orig_image_size / 4);
      break;

    case Texture::T_unsigned_short:
      new_image = PTA_uchar::empty_array(orig_image_size);
      ushort_bgra_to_rgba((unsigned short *)new_image.p(), 
                          (const unsigned short *)orig_image.p(), 
                          orig_image_size / 8);
      break;

    default:
      break;
    }
    break;

  default:
    break;
  }

  return new_image;
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::Constructor
//       Access: Public
//  Description:
////////////////////////////////////////////////////////////////////
CLP(GraphicsStateGuardian)::
CLP(GraphicsStateGuardian)(const FrameBufferProperties &properties) :
  GraphicsStateGuardian(properties, CS_yup_right) 
{
  _error_count = 0;
#ifdef HAVE_CGGL
  _cg_shader = (CgShader *)NULL;
#endif
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::Destructor
//       Access: Public
//  Description:
////////////////////////////////////////////////////////////////////
CLP(GraphicsStateGuardian)::
~CLP(GraphicsStateGuardian)() {
  close_gsg();
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::reset
//       Access: Public, Virtual
//  Description: Resets all internal state as if the gsg were newly
//               created.
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
reset() {
  free_pointers();
  GraphicsStateGuardian::reset();

  // Output the vendor and version strings.
  get_gl_version();

  // Save the extensions tokens.
  save_extensions((const char *)GLP(GetString)(GL_EXTENSIONS));
  get_extra_extensions();
  report_extensions();

  _supports_3d_texture = 
    has_extension("GL_EXT_texture3D") || is_at_least_version(1, 2);

  if (is_at_least_version(1, 2)) {
    _supports_3d_texture = true;

    _glTexImage3D = (PFNGLTEXIMAGE3DPROC)
      get_extension_func(GLPREFIX_QUOTED, "TexImage3D");
    _glTexSubImage3D = (PFNGLTEXSUBIMAGE3DPROC)
      get_extension_func(GLPREFIX_QUOTED, "TexSubImage3D");

  } else if (has_extension("GL_EXT_texture3D")) {
    _supports_3d_texture = true;

    _glTexImage3D = (PFNGLTEXIMAGE3DPROC)
      get_extension_func(GLPREFIX_QUOTED, "TexImage3DEXT");
    _glTexSubImage3D = (PFNGLTEXSUBIMAGE3DPROC)
      get_extension_func(GLPREFIX_QUOTED, "TexSubImage3DEXT");
  }

  if (_supports_3d_texture) {
    if (_glTexImage3D == NULL || _glTexSubImage3D == NULL) {
      GLCAT.warning()
        << "3-D textures advertised as supported by OpenGL runtime, but could not get pointers to extension functions.\n";
      _supports_3d_texture = false;
    }
  }

  _supports_cube_map = 
    has_extension("GL_ARB_texture_cube_map") || is_at_least_version(1, 3);

  _supports_rescale_normal = 
    has_extension("GL_EXT_rescale_normal") || is_at_least_version(1, 2);

  _supports_bgr = 
    has_extension("GL_EXT_bgra") || is_at_least_version(1, 2);
  _supports_rescale_normal = 
    has_extension("GL_EXT_rescale_normal") || is_at_least_version(1, 2);

  _supports_multisample = 
    has_extension("GL_ARB_multisample");

  _supports_generate_mipmap = 
    has_extension("GL_SGIS_generate_mipmap") || is_at_least_version(1, 4);

  _supports_multitexture = false;
    
  if (is_at_least_version(1, 3)) {
    _supports_multitexture = true;

    _glActiveTexture = (PFNGLACTIVETEXTUREPROC)
      get_extension_func(GLPREFIX_QUOTED, "ActiveTexture");
    _glMultiTexCoord2fv = (PFNGLMULTITEXCOORD2FVPROC)
      get_extension_func(GLPREFIX_QUOTED, "MultiTexCoord2fv");

  } else if (has_extension("GL_ARB_multitexture")) {
    _supports_multitexture = true;

    _glActiveTexture = (PFNGLACTIVETEXTUREPROC)
      get_extension_func(GLPREFIX_QUOTED, "ActiveTextureARB");
    _glMultiTexCoord2fv = (PFNGLMULTITEXCOORD2FVPROC)
      get_extension_func(GLPREFIX_QUOTED, "MultiTexCoord2fvARB");
  }

  if (_supports_multitexture) {
    if (_glActiveTexture == NULL || _glMultiTexCoord2fv == NULL) {
      GLCAT.warning()
        << "Multitexture advertised as supported by OpenGL runtime, but could not get pointers to extension functions.\n";
      _supports_multitexture = false;
    }
  }
  if (!_supports_multitexture) {
    _glActiveTexture = null_glActiveTexture;
  }

  _glBlendEquation = NULL;
  if (has_extension("GL_EXT_blend_minmax") || is_at_least_version(1, 2)) {
    _glBlendEquation = (PFNGLBLENDEQUATIONPROC)
      get_extension_func(GLPREFIX_QUOTED, "BlendEquationEXT");
    if (_glBlendEquation == NULL) {
      GLCAT.warning()
        << "BlendEquation advertised as supported by OpenGL runtime, but could not get pointers to extension function.\n";
    }
  }
  if (_glBlendEquation == NULL) {
    _glBlendEquation = null_glBlendEquation;
  }

  _glBlendColor = NULL;
  if (has_extension("GL_EXT_blend_color") || is_at_least_version(1, 2)) {
    _glBlendColor = (PFNGLBLENDCOLORPROC)
      get_extension_func(GLPREFIX_QUOTED, "BlendColorEXT");
    if (_glBlendColor == NULL) {
      GLCAT.warning()
        << "BlendColor advertised as supported by OpenGL runtime, but could not get pointers to extension function.\n";
    }
  }
  if (_glBlendColor == NULL) {
    _glBlendColor = null_glBlendColor;
  }

  _edge_clamp = GL_CLAMP;
  if (has_extension("GL_SGIS_texture_edge_clamp") ||
      is_at_least_version(1, 2)) {
    _edge_clamp = GL_CLAMP_TO_EDGE;
  }

  _border_clamp = GL_CLAMP;
  if (has_extension("GL_ARB_texture_border_clamp") ||
      is_at_least_version(1, 3)) {
    _border_clamp = GL_CLAMP_TO_BORDER;
  }

  _mirror_repeat = GL_REPEAT;
  if (has_extension("GL_ARB_texture_mirrored_repeat") ||
      is_at_least_version(1, 4)) {
    _mirror_repeat = GL_MIRRORED_REPEAT;
  }

  _mirror_clamp = GL_CLAMP;
  _mirror_edge_clamp = _edge_clamp;
  _mirror_border_clamp = _border_clamp;
  if (has_extension("GL_EXT_texture_mirror_clamp")) {
    _mirror_clamp = GL_MIRROR_CLAMP_EXT;
    _mirror_edge_clamp = GL_MIRROR_CLAMP_TO_EDGE_EXT;
    _mirror_border_clamp = GL_MIRROR_CLAMP_TO_BORDER_EXT;
  }

  if (_supports_multisample) {
    GLint sample_buffers;
    GLP(GetIntegerv)(GL_SAMPLE_BUFFERS, &sample_buffers);
    if (sample_buffers != 1) {
      // Even if the API supports multisample, we might have ended up
      // with a framebuffer that doesn't have any multisample bits.
      // (It's also possible the graphics card doesn't provide any
      // framebuffers with multisample.)  In this case, we don't
      // really support the multisample API's, since they won't do
      // anything.
      _supports_multisample = false;
    }
  }

  GLint max_texture_size;
  GLint max_3d_texture_size;
  GLint max_cube_map_size;
  
  GLP(GetIntegerv)(GL_MAX_TEXTURE_SIZE, &max_texture_size);
  _max_texture_dimension = max_texture_size;

  if (_supports_3d_texture) {
    GLP(GetIntegerv)(GL_MAX_3D_TEXTURE_SIZE, &max_3d_texture_size);
    _max_3d_texture_dimension = max_3d_texture_size;
  } else {
    _max_3d_texture_dimension = 0;
  }

  if (_supports_cube_map) {
    GLP(GetIntegerv)(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &max_cube_map_size);
    _max_cube_map_dimension = max_cube_map_size;
  } else {
    _max_cube_map_dimension = 0;
  }

  if (GLCAT.is_debug()) {
    GLCAT.debug()
      << "max texture dimension = " << _max_texture_dimension
      << ", max 3d texture = " << _max_3d_texture_dimension
      << ", max cube map = " << _max_cube_map_dimension << "\n";
  }

  report_my_gl_errors();

  _auto_rescale_normal = false;

  // All GL implementations have the following buffers.
  _buffer_mask = (RenderBuffer::T_color |
                  RenderBuffer::T_depth |
                  RenderBuffer::T_stencil |
                  RenderBuffer::T_accum);

  // If we don't have double-buffering, don't attempt to write to the
  // back buffer.
  GLboolean has_back;
  GLP(GetBooleanv)(GL_DOUBLEBUFFER, &has_back);
  if (!has_back) {
    _buffer_mask &= ~RenderBuffer::T_back;
  }

  // Ensure the initial state is what we say it should be (in some
  // cases, we don't want the GL default settings; in others, we have
  // to force the point with some drivers that aren't strictly
  // compliant w.r.t. initial settings).
  GLP(FrontFace)(GL_CCW);
  GLP(Disable)(GL_LINE_SMOOTH);
  GLP(Disable)(GL_POINT_SMOOTH);
  GLP(Disable)(GL_POLYGON_SMOOTH);

  if (_supports_multisample) {
    GLP(Disable)(GL_MULTISAMPLE);
  }


  // Set up all the enabled/disabled flags to GL's known initial
  // values: everything off.
  _multisample_mode = 0;
  _line_smooth_enabled = false;
  _point_smooth_enabled = false;
  _polygon_smooth_enabled = false;
  _scissor_enabled = false;
  _stencil_test_enabled = false;
  _blend_enabled = false;
  _depth_test_enabled = false;
  _fog_enabled = false;
  _alpha_test_enabled = false;
  _polygon_offset_enabled = false;
  _decal_level = 0;

  // Dither is on by default in GL; let's turn it off
  GLP(Disable)(GL_DITHER);
  _dithering_enabled = false;

  _texgen_forced_normal = false;

#ifdef HAVE_CGGL
  _cg_shader = (CgShader *)NULL;
#endif

  // Count the max number of lights
  GLint max_lights;
  GLP(GetIntegerv)(GL_MAX_LIGHTS, &max_lights);
  _max_lights = max_lights;

  if (GLCAT.is_debug()) {
    GLCAT.debug()
      << "max lights = " << _max_lights << "\n";
  }

  // Count the max number of clipping planes
  GLint max_clip_planes;
  GLP(GetIntegerv)(GL_MAX_CLIP_PLANES, &max_clip_planes);
  _max_clip_planes = max_clip_planes;

  if (GLCAT.is_debug()) {
    GLCAT.debug()
      << "max clip planes = " << _max_clip_planes << "\n";
  }

  _current_projection_mat = LMatrix4f::ident_mat();
  _projection_mat_stack_count = 0;

  if (_supports_multitexture) {
    GLint max_texture_stages;
    GLP(GetIntegerv)(GL_MAX_TEXTURE_UNITS, &max_texture_stages);
    _max_texture_stages = max_texture_stages;

    if (GLCAT.is_debug()) {
      GLCAT.debug()
        << "max texture stages = " << _max_texture_stages << "\n";
    }
  }
  _current_texture = DCAST(TextureAttrib, TextureAttrib::make_all_off());
  _current_tex_mat = DCAST(TexMatrixAttrib, TexMatrixAttrib::make());
  _needs_tex_mat = false;
  _current_tex_gen = DCAST(TexGenAttrib, TexGenAttrib::make());
  _needs_tex_gen = false;
  _tex_gen_modifies_mat = false;
  _auto_antialias_mode = false;
  _render_mode = RenderModeAttrib::M_filled;

  report_my_gl_errors();

  // Make sure the GL state matches all of our initial attribute
  // states.
  CPT(RenderAttrib) dta = DepthTestAttrib::make(DepthTestAttrib::M_less);
  CPT(RenderAttrib) dwa = DepthWriteAttrib::make(DepthWriteAttrib::M_on);
  CPT(RenderAttrib) cfa = CullFaceAttrib::make(CullFaceAttrib::M_cull_clockwise);
  CPT(RenderAttrib) ta = TextureAttrib::make_off();

  dta->issue(this);
  dwa->issue(this);
  cfa->issue(this);
  ta->issue(this);

  Material empty;
  apply_material(&empty);

  if (CLP(cheap_textures)) {
    GLCAT.info()
      << "Setting GLP(Hint)() for fastest textures.\n";
    GLP(Hint)(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
  }

  // use per-vertex fog if per-pixel fog requires SW renderer
  GLP(Hint)(GL_FOG_HINT, GL_DONT_CARE);

  GLint num_red_bits;
  GLP(GetIntegerv)(GL_RED_BITS, &num_red_bits);
  if (num_red_bits < 8) {
    GLP(Enable)(GL_DITHER);
    _dithering_enabled = true;
    if (GLCAT.is_debug()) {
      GLCAT.debug() 
        << "frame buffer depth = " << num_red_bits
        << " bits/channel, enabling dithering\n";
    }
  }

  _error_count = 0;

  report_my_gl_errors();
}


////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::clear
//       Access: Public, Virtual
//  Description: Clears all of the indicated buffers to their assigned
//               colors.
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
do_clear(const RenderBuffer &buffer) {
  nassertv(buffer._gsg == this);
  int buffer_type = buffer._buffer_type;
  GLbitfield mask = 0;
  CPT(RenderState) state = RenderState::make_empty();

  if (buffer_type & RenderBuffer::T_color) {
    GLP(ClearColor)(_color_clear_value[0],
                    _color_clear_value[1],
                    _color_clear_value[2],
                    _color_clear_value[3]);
    state = state->add_attrib(ColorWriteAttrib::make(ColorWriteAttrib::M_on));
    mask |= GL_COLOR_BUFFER_BIT;

    set_draw_buffer(buffer);
  }

  if (buffer_type & RenderBuffer::T_depth) {
    GLP(ClearDepth)(_depth_clear_value);
    mask |= GL_DEPTH_BUFFER_BIT;

    // In order to clear the depth buffer, the depth mask must enable
    // writing to the depth buffer.
    state = state->add_attrib(DepthWriteAttrib::make(DepthWriteAttrib::M_on));
  }

  if (buffer_type & RenderBuffer::T_stencil) {
    GLP(ClearStencil)(_stencil_clear_value != false);
    mask |= GL_STENCIL_BUFFER_BIT;
  }

  if (buffer_type & RenderBuffer::T_accum) {
    GLP(ClearAccum)(_accum_clear_value[0],
                    _accum_clear_value[1],
                    _accum_clear_value[2],
                    _accum_clear_value[3]);
    mask |= GL_ACCUM_BUFFER_BIT;
  }

#ifdef GSG_VERBOSE
  GLCAT.spam() << "glClear(";
  if (mask & GL_COLOR_BUFFER_BIT) {
    GLCAT.spam(false) << "GL_COLOR_BUFFER_BIT|";
  }
  if (mask & GL_DEPTH_BUFFER_BIT) {
    GLCAT.spam(false) << "GL_DEPTH_BUFFER_BIT|";
  }
  if (mask & GL_STENCIL_BUFFER_BIT) {
    GLCAT.spam(false) << "GL_STENCIL_BUFFER_BIT|";
  }
  if (mask & GL_ACCUM_BUFFER_BIT) {
    GLCAT.spam(false) << "GL_ACCUM_BUFFER_BIT|";
  }
  GLCAT.spam(false) << ")" << endl;
#endif

  modify_state(state);

  GLP(Clear)(mask);
  report_my_gl_errors();
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::prepare_display_region
//       Access: Public, Virtual
//  Description: Prepare a display region for rendering (set up
//               scissor region and viewport)
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
prepare_display_region() {
  if (_current_display_region == (DisplayRegion*)0L) {
    GLCAT.error()
      << "Invalid NULL display region in prepare_display_region()\n";
    enable_scissor(false);

  } else if (_current_display_region != _actual_display_region) {
    _actual_display_region = _current_display_region;

    int l, b, w, h;
    _actual_display_region->get_region_pixels(l, b, w, h);
    GLint x = GLint(l);
    GLint y = GLint(b);
    GLsizei width = GLsizei(w);
    GLsizei height = GLsizei(h);

    enable_scissor( true );
    GLP(Scissor)( x, y, width, height );
    GLP(Viewport)( x, y, width, height );
  }
  report_my_gl_errors();
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::prepare_lens
//       Access: Public, Virtual
//  Description: Makes the current lens (whichever lens was most
//               recently specified with set_scene()) active, so
//               that it will transform future rendered geometry.
//               Normally this is only called from the draw process,
//               and usually it is called by set_scene().
//
//               The return value is true if the lens is acceptable,
//               false if it is not.
////////////////////////////////////////////////////////////////////
bool CLP(GraphicsStateGuardian)::
prepare_lens() {
  if (_current_lens == (Lens *)NULL) {
    return false;
  }

  if (!_current_lens->is_linear()) {
    return false;
  }

  const LMatrix4f &projection_mat = _current_lens->get_projection_mat();

  // The projection matrix must always be right-handed Y-up, even if
  // our coordinate system of choice is otherwise, because certain GL
  // calls (specifically glTexGen(GL_SPHERE_MAP)) assume this kind of
  // a coordinate system.  Sigh.  In order to implement a Z-up (or
  // other arbitrary) coordinate system, we'll use a Y-up projection
  // matrix, and store the conversion to our coordinate system of
  // choice in the modelview matrix.
  LMatrix4f new_projection_mat =
    LMatrix4f::convert_mat(CS_yup_right, _current_lens->get_coordinate_system()) *
    projection_mat;

  if (_scene_setup->get_inverted()) {
    // If the scene is supposed to be inverted, then invert the
    // projection matrix.
    static LMatrix4f invert_mat = LMatrix4f::scale_mat(1.0f, -1.0f, 1.0f);
    new_projection_mat *= invert_mat;
  }

#ifdef GSG_VERBOSE
  GLCAT.spam()
    << "glMatrixMode(GL_PROJECTION): " << new_projection_mat << endl;
#endif
  GLP(MatrixMode)(GL_PROJECTION);
  GLP(LoadMatrixf)(new_projection_mat.get_data());
  report_my_gl_errors();

  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: GraphicsStateGuardian::begin_frame
//       Access: Public, Virtual
//  Description: Called before each frame is rendered, to allow the
//               GSG a chance to do any internal cleanup before
//               beginning the frame.
//
//               The return value is true if successful (in which case
//               the frame will be drawn and end_frame() will be
//               called later), or false if unsuccessful (in which
//               case nothing will be drawn and end_frame() will not
//               be called).
////////////////////////////////////////////////////////////////////
bool CLP(GraphicsStateGuardian)::
begin_frame() {
  if (!GraphicsStateGuardian::begin_frame()) {
    return false;
  }

#ifdef DO_PSTATS
  _vertices_display_list_pcollector.clear_level();
#endif

  _actual_display_region = NULL;

  report_my_gl_errors();
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::end_frame
//       Access: Public, Virtual
//  Description: Called after each frame is rendered, to allow the
//               GSG a chance to do any internal cleanup after
//               rendering the frame, and before the window flips.
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
end_frame() {
  GraphicsStateGuardian::end_frame();

  {
#ifdef DO_PSTATS
    PStatTimer timer(_flush_pcollector);
#endif
    // Calling glFlush() at the end of the frame is particularly
    // necessary if this is a single-buffered visual, so that the frame
    // will be finished drawing before we return to the application.
    // It's not clear what effect this has on our total frame time.
    GLP(Flush)();
  }

  report_my_gl_errors();
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::draw_point
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
draw_point(GeomPoint *geom, GeomContext *gc) {
#ifdef GSG_VERBOSE
  GLCAT.spam() << "draw_point()" << endl;
#endif

  setup_antialias_point();

  if (draw_display_list(gc)) {
    return;
  }


#ifdef DO_PSTATS
  PStatTimer timer(_draw_primitive_pcollector);
  _vertices_other_pcollector.add_level(geom->get_num_vertices());
#endif

  issue_scene_graph_color();

  int nprims = geom->get_num_prims();
  Geom::VertexIterator vi = geom->make_vertex_iterator();
  Geom::NormalIterator ni = geom->make_normal_iterator();
  Geom::MultiTexCoordIterator ti;
  geom->setup_multitexcoord_iterator(ti, _current_texture->get_on_stages(),
                                     _current_tex_gen->get_no_texcoords());
  Geom::ColorIterator ci = geom->make_color_iterator();

  GeomIssuer::IssueColor *issue_color;

  if (_color_blend_involves_color_scale || !_color_scale_enabled) {
    issue_color = issue_color_gl;
  } else {
    issue_color = issue_scaled_color_gl;
  }

  GeomIssuer issuer(geom, this,
                    issue_vertex_gl,
                    issue_normal_gl,
                    issue_color,
                    issue_texcoord_single_gl,
                    issue_texcoord_multi_gl,
                    ti);

  // Draw overall
  issuer.issue_color(G_OVERALL, ci);
  issuer.issue_normal(G_OVERALL, ni);

  GLP(Begin)(GL_POINTS);

  for (int i = 0; i < nprims; i++) {
    // Draw per primitive
    issuer.issue_color(G_PER_PRIM, ci);
    issuer.issue_normal(G_PER_PRIM, ni);

    // Draw per vertex, same thing.
    issuer.issue_color(G_PER_VERTEX, ci);
    issuer.issue_normal(G_PER_VERTEX, ni);
    issuer.issue_texcoord(G_PER_VERTEX, ti);
    issuer.issue_vertex(G_PER_VERTEX, vi);
  }

  GLP(End)();
  report_my_gl_errors();
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::draw_line
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
draw_line(GeomLine *geom, GeomContext *gc) {
#ifdef GSG_VERBOSE
  GLCAT.spam() << "draw_line()" << endl;
#endif

  setup_antialias_line();

  if (draw_display_list(gc)) {
    return;
  }

#ifdef DO_PSTATS
  PStatTimer timer(_draw_primitive_pcollector);
  _vertices_other_pcollector.add_level(geom->get_num_vertices());
#endif

  issue_scene_graph_color();

  int nprims = geom->get_num_prims();
  Geom::VertexIterator vi = geom->make_vertex_iterator();
  Geom::NormalIterator ni = geom->make_normal_iterator();
  Geom::MultiTexCoordIterator ti;
  geom->setup_multitexcoord_iterator(ti, _current_texture->get_on_stages(),
                                     _current_tex_gen->get_no_texcoords());
  Geom::ColorIterator ci = geom->make_color_iterator();

  GeomIssuer::IssueColor *issue_color;

  if (_color_blend_involves_color_scale || !_color_scale_enabled) {
    issue_color = issue_color_gl;
  } else {
    issue_color = issue_scaled_color_gl;
  }

  GeomIssuer issuer(geom, this,
                    issue_vertex_gl,
                    issue_normal_gl,
                    issue_color,
                    issue_texcoord_single_gl,
                    issue_texcoord_multi_gl,
                    ti);

  // If we have per-vertex colors or normals, we need smooth shading.
  // Otherwise we want flat shading for performance reasons.
  if ((geom->get_binding(G_COLOR) == G_PER_VERTEX && wants_colors()) ||
      (geom->get_binding(G_NORMAL) == G_PER_VERTEX && wants_normals())) {
    GLP(ShadeModel)(GL_SMOOTH);
  } else {
    GLP(ShadeModel)(GL_FLAT);
  }

  // Draw overall
  issuer.issue_color(G_OVERALL, ci);
  issuer.issue_normal(G_OVERALL, ni);

  GLP(Begin)(GL_LINES);

  for (int i = 0; i < nprims; i++) {
    // Draw per primitive
    issuer.issue_color(G_PER_PRIM, ci);
    issuer.issue_normal(G_PER_PRIM, ni);

    for (int j = 0; j < 2; j++) {
      // Draw per vertex
      issuer.issue_color(G_PER_VERTEX, ci);
      issuer.issue_normal(G_PER_VERTEX, ni);
      issuer.issue_texcoord(G_PER_VERTEX, ti);
      issuer.issue_vertex(G_PER_VERTEX, vi);
    }
  }

  GLP(End)();
  report_my_gl_errors();
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::draw_linestrip
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
draw_linestrip(GeomLinestrip *geom, GeomContext *gc) {
#ifdef GSG_VERBOSE
  GLCAT.spam() << "draw_linestrip()" << endl;
#endif

  setup_antialias_line();

  if (draw_display_list(gc)) {
    return;
  }

#ifdef DO_PSTATS
  //  PStatTimer timer(_draw_primitive_pcollector);
  // Using PStatTimer may cause a compiler crash.
  _draw_primitive_pcollector.start();
  _vertices_other_pcollector.add_level(geom->get_num_vertices());
#endif

  issue_scene_graph_color();

  int nprims = geom->get_num_prims();
  const int *plen = geom->get_lengths();
  Geom::VertexIterator vi = geom->make_vertex_iterator();
  Geom::NormalIterator ni = geom->make_normal_iterator();
  Geom::MultiTexCoordIterator ti;
  geom->setup_multitexcoord_iterator(ti, _current_texture->get_on_stages(),
                                     _current_tex_gen->get_no_texcoords());
  Geom::ColorIterator ci = geom->make_color_iterator();

  GeomIssuer::IssueColor *issue_color;

  if (_color_blend_involves_color_scale || !_color_scale_enabled) {
    issue_color = issue_color_gl;
  } else {
    issue_color = issue_scaled_color_gl;
  }

  GeomIssuer issuer(geom, this,
                    issue_vertex_gl,
                    issue_normal_gl,
                    issue_color,
                    issue_texcoord_single_gl,
                    issue_texcoord_multi_gl,
                    ti);

  // If we have per-vertex colors or normals, we need smooth shading.
  // Otherwise we want flat shading for performance reasons.
  if ((geom->get_binding(G_COLOR) == G_PER_VERTEX && wants_colors()) ||
      (geom->get_binding(G_NORMAL) == G_PER_VERTEX && wants_normals())) {
    GLP(ShadeModel)(GL_SMOOTH);
  } else {
    GLP(ShadeModel)(GL_FLAT);
  }

  // Draw overall
  issuer.issue_color(G_OVERALL, ci);
  issuer.issue_normal(G_OVERALL, ni);

  for (int i = 0; i < nprims; i++) {
    // Draw per primitive
    issuer.issue_color(G_PER_PRIM, ci);
    issuer.issue_normal(G_PER_PRIM, ni);

    int num_verts = *(plen++);
    nassertv(num_verts >= 2);

    GLP(Begin)(GL_LINE_STRIP);

    // Per-component attributes for the first line segment?
    issuer.issue_color(G_PER_COMPONENT, ci);
    issuer.issue_normal(G_PER_COMPONENT, ni);

    // Draw the first 2 vertices
    int v;
    for (v = 0; v < 2; v++) {
      issuer.issue_color(G_PER_VERTEX, ci);
      issuer.issue_normal(G_PER_VERTEX, ni);
      issuer.issue_texcoord(G_PER_VERTEX, ti);
      issuer.issue_vertex(G_PER_VERTEX, vi);
    }

    // Now draw each of the remaining vertices.  Each vertex from
    // this point on defines a new line segment.
    for (v = 2; v < num_verts; v++) {
      // Per-component attributes?
      issuer.issue_color(G_PER_COMPONENT, ci);
      issuer.issue_normal(G_PER_COMPONENT, ni);

      // Per-vertex attributes
      issuer.issue_color(G_PER_VERTEX, ci);
      issuer.issue_normal(G_PER_VERTEX, ni);
      issuer.issue_texcoord(G_PER_VERTEX, ti);
      issuer.issue_vertex(G_PER_VERTEX, vi);
    }
    GLP(End)();
  }
  report_my_gl_errors();
  DO_PSTATS_STUFF(_draw_primitive_pcollector.stop());
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::draw_sprite
//       Access: Public, Virtual
//  Description: CSN, 7/11/00
////////////////////////////////////////////////////////////////////

// this class exists because an alpha sort is necessary for correct
// sprite rendering, and we can't simply sort the vertex arrays as
// each vertex may or may not have corresponding information in the
// x/y texel-world-ratio and rotation arrays.
class WrappedSprite {
public:
  Vertexf _v;
  Colorf _c;
  float _x_ratio;
  float _y_ratio;
  float _theta;
};

// this struct exists because the STL can sort faster than i can.
struct draw_sprite_vertex_less {
  INLINE bool operator ()(const WrappedSprite& v0,
                          const WrappedSprite& v1) const {
    return v0._v[2] < v1._v[2]; }
};

void CLP(GraphicsStateGuardian)::
draw_sprite(GeomSprite *geom, GeomContext *) {
  // this is a little bit of a mess, but it's ok.  Here's the deal:
  // we want to draw, and draw quickly, an arbitrarily large number
  // of sprites all facing the screen.  Performing the billboard math
  // for ~1000 sprites is way too slow.  Ideally, we want one
  // matrix transformation that will handle everything, and this is
  // just about what ends up happening. We're getting the front-facing
  // effect by setting up a new frustum (of the same z-depth as the
  // current one) that is very small in x and y.  This way regularly
  // rendered triangles that might not be EXACTLY facing the camera
  // will certainly look close enough.  Then, we transform to camera-space
  // by hand and apply the inverse frustum to the transformed point.
  // For some cracked out reason, this actually works.
#ifdef GSG_VERBOSE
  GLCAT.spam() << "draw_sprite()" << endl;
#endif

  setup_antialias_polygon();

  // get the array traversal set up.
  int nprims = geom->get_num_prims();
  if (nprims==0) {
      return;
  }

#ifdef DO_PSTATS
  //  PStatTimer timer(_draw_primitive_pcollector);
  // Using PStatTimer may cause a compiler crash.
  _draw_primitive_pcollector.start();
  _vertices_other_pcollector.add_level(geom->get_num_vertices());
#endif

  Geom::VertexIterator vi = geom->make_vertex_iterator();
  Geom::ColorIterator ci = geom->make_color_iterator();

  // need some interface so user can set 2d dimensions if no texture specified
  float tex_x_size = 1.0f;  
  float tex_y_size = 1.0f;

  Texture *tex = geom->get_texture();
  if(tex != NULL) {
    // set up the texture-rendering state
    modify_state(RenderState::make
                 (TextureAttrib::make(tex),
                  TextureApplyAttrib::make(TextureApplyAttrib::M_modulate)));
    tex_x_size = tex->get_x_size();
    tex_y_size = tex->get_y_size();
  }

  // save the modelview matrix
  const LMatrix4f &modelview_mat = _transform->get_mat();

  // We don't need to mess with the aspect ratio, since we are now
  // using the default projection matrix, which has the right aspect
  // ratio built in.

  // load up our own matrices
  GLP(MatrixMode)(GL_MODELVIEW);
  GLP(LoadIdentity)();

  // precomputation stuff
  float tex_left = geom->get_ll_uv()[0];
  float tex_right = geom->get_ur_uv()[0];
  float tex_bottom = geom->get_ll_uv()[1];
  float tex_top = geom->get_ur_uv()[1];

  float half_width =  0.5f * tex_x_size * fabs(tex_right - tex_left);
  float half_height = 0.5f * tex_y_size * fabs(tex_top - tex_bottom);
  float scaled_width = 0.0f;
  float scaled_height = 0.0f;

  // the user can override alpha sorting if they want
  bool alpha = false;

  if (!geom->get_alpha_disable()) {
    // figure out if alpha's enabled (if not, no reason to sort)
    const TransparencyAttrib *trans = _state->get_transparency();
    if (trans != (const TransparencyAttrib *)NULL) {
      alpha = (trans->get_mode() != TransparencyAttrib::M_none);
    }
  }

  // sort container and iterator
  pvector< WrappedSprite > cameraspace_vector;
  pvector< WrappedSprite >::iterator vec_iter;

  // inner loop vars
  int i;
  Vertexf source_vert, cameraspace_vert;
  float *x_walk = (float *)NULL;
  float *y_walk = (float *)NULL;
  float *theta_walk = (float *)NULL;
  float theta = 0.0f;

  nassertv(geom->get_x_bind_type() != G_PER_VERTEX);
  nassertv(geom->get_y_bind_type() != G_PER_VERTEX);

  // set up the non-built-in bindings
  bool x_overall = (geom->get_x_bind_type() == G_OVERALL);
  bool y_overall = (geom->get_y_bind_type() == G_OVERALL);
  bool theta_overall = (geom->get_theta_bind_type() == G_OVERALL);
  bool color_overall = (geom->get_binding(G_COLOR) == G_OVERALL);
  bool theta_on = !(geom->get_theta_bind_type() == G_OFF);

  // x direction
  if (x_overall)
    scaled_width = geom->_x_texel_ratio[0] * half_width;
  else {
    nassertv(((int)geom->_x_texel_ratio.size() >= geom->get_num_prims()));
    x_walk = &geom->_x_texel_ratio[0];
  }

  // y direction
  if (y_overall)
    scaled_height = geom->_y_texel_ratio[0] * half_height;
  else {
    nassertv(((int)geom->_y_texel_ratio.size() >= geom->get_num_prims()));
    y_walk = &geom->_y_texel_ratio[0];
  }

  // theta
  if (theta_on) {
    if (theta_overall)
      theta = geom->_theta[0];
    else {
      nassertv(((int)geom->_theta.size() >= geom->get_num_prims()));
      theta_walk = &geom->_theta[0];
    }
  }

  /////////////////////////////////////////////////////////////////////
  // INNER LOOP PART 1 STARTS HERE
  // Here we transform each point to cameraspace and fill our sort
  // vector with the final geometric information.
  /////////////////////////////////////////////////////////////////////

  cameraspace_vector.reserve(nprims);   //pre-alloc space for nprims

  // the state is set, start running the prims
  for (i = 0; i < nprims; i++) {
    WrappedSprite ws;

    source_vert = geom->get_next_vertex(vi);

    // this mult converts to y-up cameraspace.
    cameraspace_vert = source_vert * modelview_mat;
    // build the final object that will go into the vector.
    ws._v.set(cameraspace_vert[0],cameraspace_vert[1],cameraspace_vert[2]);

    if (!color_overall)
      ws._c = geom->get_next_color(ci);
    if (!x_overall)
      ws._x_ratio = *x_walk++;
    if (!y_overall)
      ws._y_ratio = *y_walk++;
    if (theta_on) {
      if (!theta_overall)
        ws._theta = *theta_walk++;
    }

    cameraspace_vector.push_back(ws);
  }

  // now the verts are properly sorted by alpha (if necessary).  Of course,
  // the sort is only local, not scene-global, so if you look closely you'll
  // notice that alphas may be screwy.  It's ok though, because this is fast.
  // if you want accuracy, use billboards and take the speed hit.
  if (alpha) {
    sort(cameraspace_vector.begin(), cameraspace_vector.end(),
         draw_sprite_vertex_less());

     if (_dithering_enabled)
         GLP(Disable)(GL_DITHER);
  }

  Vertexf ul, ur, ll, lr;

  if (color_overall)
    GLP(Color4fv)(geom->get_next_color(ci).get_data());

  ////////////////////////////////////////////////////////////////////////////
  // INNER LOOP PART 2 STARTS HERE
  // Now we run through the cameraspace vector and compute the geometry for each
  // tristrip.  This includes scaling as per the ratio arrays, as well as
  // rotating in the z.
  ////////////////////////////////////////////////////////////////////////////

  vec_iter = cameraspace_vector.begin();
  for (; vec_iter != cameraspace_vector.end(); vec_iter++) {
    WrappedSprite& cur_image = *vec_iter;

    // if not G_OVERALL, calculate the scale factors
    if (x_overall == false)
      scaled_width = cur_image._x_ratio * half_width;

    if (y_overall == false)
      scaled_height = cur_image._y_ratio * half_height;

    // if not G_OVERALL, do some trig for this z rotate
    if (theta_on) {
      if (theta_overall == false)
        theta = cur_image._theta;

      // create the rotated points
      LMatrix3f xform_mat = LMatrix3f::rotate_mat(theta) * LMatrix3f::scale_mat(scaled_width, scaled_height);

      ur = (LVector3f( 1,  1, 0) * xform_mat) + cur_image._v;
      ul = (LVector3f(-1,  1, 0) * xform_mat) + cur_image._v;
      lr = (LVector3f( 1, -1, 0) * xform_mat) + cur_image._v;
      ll = (LVector3f(-1, -1, 0) * xform_mat) + cur_image._v;
    }
    else {
      // create the normal points
      ur.set(scaled_width, scaled_height, 0);
      ul.set(-scaled_width, scaled_height, 0);
      lr.set(scaled_width, -scaled_height, 0);
      ll.set(-scaled_width, -scaled_height, 0);

      ur += cur_image._v;
      ul += cur_image._v;
      lr += cur_image._v;
      ll += cur_image._v;
    }

    // set the color
    if (color_overall == false)
      GLP(Color4fv)(cur_image._c.get_data());

    // draw each one as a 2-element tri-strip
    GLP(Begin)(GL_TRIANGLE_STRIP);
    GLP(Normal3f)(0.0f, 0.0f, 1.0f);
    GLP(TexCoord2f)(tex_left, tex_bottom);  GLP(Vertex3fv)(ll.get_data());
    GLP(TexCoord2f)(tex_right, tex_bottom); GLP(Vertex3fv)(lr.get_data());
    GLP(TexCoord2f)(tex_left, tex_top);     GLP(Vertex3fv)(ul.get_data());
    GLP(TexCoord2f)(tex_right, tex_top);    GLP(Vertex3fv)(ur.get_data());
    GLP(End)();
  }

  // restore the matrices
  GLP(LoadMatrixf)(modelview_mat.get_data());

  if(alpha && _dithering_enabled)
     GLP(Enable)(GL_DITHER);

  report_my_gl_errors();
  DO_PSTATS_STUFF(_draw_primitive_pcollector.stop());
}


////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::draw_polygon
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
draw_polygon(GeomPolygon *geom, GeomContext *gc) {
#ifdef GSG_VERBOSE
  GLCAT.spam() << "draw_polygon()" << endl;
#endif

  setup_antialias_polygon();

  if (draw_display_list(gc)) {
    return;
  }

#ifdef DO_PSTATS
  //  PStatTimer timer(_draw_primitive_pcollector);
  // Using PStatTimer may cause a compiler crash.
  _draw_primitive_pcollector.start();
  _vertices_other_pcollector.add_level(geom->get_num_vertices());
#endif

  issue_scene_graph_color();

  int nprims = geom->get_num_prims();
  const int *plen = geom->get_lengths();
  Geom::VertexIterator vi = geom->make_vertex_iterator();
  Geom::NormalIterator ni = geom->make_normal_iterator();
  Geom::MultiTexCoordIterator ti;
  geom->setup_multitexcoord_iterator(ti, _current_texture->get_on_stages(),
                                     _current_tex_gen->get_no_texcoords());
  Geom::ColorIterator ci = geom->make_color_iterator();

  GeomIssuer::IssueColor *issue_color;

  if (_color_blend_involves_color_scale || !_color_scale_enabled) {
    issue_color = issue_color_gl;
  } else {
    issue_color = issue_scaled_color_gl;
  }

  GeomIssuer issuer(geom, this,
                    issue_vertex_gl,
                    issue_normal_gl,
                    issue_color,
                    issue_texcoord_single_gl,
                    issue_texcoord_multi_gl,
                    ti);

  // If we have per-vertex colors or normals, we need smooth shading.
  // Otherwise we want flat shading for performance reasons.
  if ((geom->get_binding(G_COLOR) == G_PER_VERTEX && wants_colors()) ||
      (geom->get_binding(G_NORMAL) == G_PER_VERTEX && wants_normals())) {
    GLP(ShadeModel)(GL_SMOOTH);
  } else {
    GLP(ShadeModel)(GL_FLAT);
  }

  // Draw overall
  issuer.issue_color(G_OVERALL, ci);
  issuer.issue_normal(G_OVERALL, ni);

  for (int i = 0; i < nprims; i++) {
    // Draw per primitive
    issuer.issue_color(G_PER_PRIM, ci);
    issuer.issue_normal(G_PER_PRIM, ni);

    int num_verts = *(plen++);
    nassertv(num_verts >= 3);

    GLP(Begin)(GL_POLYGON);

    // Draw the vertices.
    int v;
    for (v = 0; v < num_verts; v++) {
      // Per-vertex attributes.
      issuer.issue_color(G_PER_VERTEX, ci);
      issuer.issue_normal(G_PER_VERTEX, ni);
      issuer.issue_texcoord(G_PER_VERTEX, ti);
      issuer.issue_vertex(G_PER_VERTEX, vi);
    }
    GLP(End)();
  }
  report_my_gl_errors();
  DO_PSTATS_STUFF(_draw_primitive_pcollector.stop());
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::draw_tri
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
draw_tri(GeomTri *geom, GeomContext *gc) {
#ifdef GSG_VERBOSE
  GLCAT.spam() << "draw_tri()" << endl;
#endif

  setup_antialias_polygon();

  if (draw_display_list(gc)) {
    return;
  }

#ifdef DO_PSTATS
  //  PStatTimer timer(_draw_primitive_pcollector);
  // Using PStatTimer may cause a compiler crash.
  _draw_primitive_pcollector.start();
  _vertices_tri_pcollector.add_level(geom->get_num_vertices());
#endif

  issue_scene_graph_color();

  int nprims = geom->get_num_prims();
  Geom::VertexIterator vi = geom->make_vertex_iterator();
  Geom::NormalIterator ni = geom->make_normal_iterator();
  Geom::MultiTexCoordIterator ti;
  geom->setup_multitexcoord_iterator(ti, _current_texture->get_on_stages(),
                                     _current_tex_gen->get_no_texcoords());
  Geom::ColorIterator ci = geom->make_color_iterator();

  GeomIssuer::IssueColor *issue_color;

  if (_color_blend_involves_color_scale || !_color_scale_enabled) {
    issue_color = issue_color_gl;
  } else {
    issue_color = issue_scaled_color_gl;
  }

  GeomIssuer issuer(geom, this,
                    issue_vertex_gl,
                    issue_normal_gl,
                    issue_color,
                    issue_texcoord_single_gl,
                    issue_texcoord_multi_gl,
                    ti);

  // If we have per-vertex colors or normals, we need smooth shading.
  // Otherwise we want flat shading for performance reasons.
  if ((geom->get_binding(G_COLOR) == G_PER_VERTEX && wants_colors()) ||
      (geom->get_binding(G_NORMAL) == G_PER_VERTEX && wants_normals())) {
    GLP(ShadeModel)(GL_SMOOTH);
  } else {
    GLP(ShadeModel)(GL_FLAT);
  }

  // Draw overall
  issuer.issue_color(G_OVERALL, ci);
  issuer.issue_normal(G_OVERALL, ni);

  GLP(Begin)(GL_TRIANGLES);

  for (int i = 0; i < nprims; i++) {
    // Draw per primitive
    issuer.issue_color(G_PER_PRIM, ci);
    issuer.issue_normal(G_PER_PRIM, ni);

    for (int j = 0; j < 3; j++) {
      // Draw per vertex
      issuer.issue_color(G_PER_VERTEX, ci);
      issuer.issue_normal(G_PER_VERTEX, ni);
      issuer.issue_texcoord(G_PER_VERTEX, ti);
      issuer.issue_vertex(G_PER_VERTEX, vi);
    }
  }

  GLP(End)();
  report_my_gl_errors();
#ifdef DO_PSTATS
  _draw_primitive_pcollector.stop();
#endif
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::draw_quad
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
draw_quad(GeomQuad *geom, GeomContext *gc) {
#ifdef GSG_VERBOSE
  GLCAT.spam() << "draw_quad()" << endl;
#endif

  setup_antialias_polygon();

  if (draw_display_list(gc)) {
    return;
  }

#ifdef DO_PSTATS
  //  PStatTimer timer(_draw_primitive_pcollector);
  // Using PStatTimer may cause a compiler crash.
  _draw_primitive_pcollector.start();
  _vertices_other_pcollector.add_level(geom->get_num_vertices());
#endif

  issue_scene_graph_color();

  int nprims = geom->get_num_prims();
  Geom::VertexIterator vi = geom->make_vertex_iterator();
  Geom::NormalIterator ni = geom->make_normal_iterator();
  Geom::MultiTexCoordIterator ti;
  geom->setup_multitexcoord_iterator(ti, _current_texture->get_on_stages(),
                                     _current_tex_gen->get_no_texcoords());
  Geom::ColorIterator ci = geom->make_color_iterator();

  GeomIssuer::IssueColor *issue_color;

  if (_color_blend_involves_color_scale || !_color_scale_enabled) {
    issue_color = issue_color_gl;
  } else {
    issue_color = issue_scaled_color_gl;
  }

  GeomIssuer issuer(geom, this,
                    issue_vertex_gl,
                    issue_normal_gl,
                    issue_color,
                    issue_texcoord_single_gl,
                    issue_texcoord_multi_gl,
                    ti);

  // If we have per-vertex colors or normals, we need smooth shading.
  // Otherwise we want flat shading for performance reasons.
  if ((geom->get_binding(G_COLOR) == G_PER_VERTEX && wants_colors()) ||
      (geom->get_binding(G_NORMAL) == G_PER_VERTEX && wants_normals())) {
    GLP(ShadeModel)(GL_SMOOTH);
  } else {
    GLP(ShadeModel)(GL_FLAT);
  }

  // Draw overall
  issuer.issue_color(G_OVERALL, ci);
  issuer.issue_normal(G_OVERALL, ni);

  GLP(Begin)(GL_QUADS);

  for (int i = 0; i < nprims; i++) {
    // Draw per primitive
    issuer.issue_color(G_PER_PRIM, ci);
    issuer.issue_normal(G_PER_PRIM, ni);

    for (int j = 0; j < 4; j++) {
      // Draw per vertex
      issuer.issue_color(G_PER_VERTEX, ci);
      issuer.issue_normal(G_PER_VERTEX, ni);
      issuer.issue_texcoord(G_PER_VERTEX, ti);
      issuer.issue_vertex(G_PER_VERTEX, vi);
    }
  }

  GLP(End)();
  report_my_gl_errors();
  DO_PSTATS_STUFF(_draw_primitive_pcollector.stop());
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::draw_tristrip
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
draw_tristrip(GeomTristrip *geom, GeomContext *gc) {
  
#ifdef GSG_VERBOSE
  GLCAT.spam() << "draw_tristrip()" << endl;
#endif

  setup_antialias_polygon();

  if (draw_display_list(gc)) {
    return;
  }

#ifdef DO_PSTATS
  //  PStatTimer timer(_draw_primitive_pcollector);
  // Using PStatTimer may cause a compiler crash.
  _draw_primitive_pcollector.start();
  if (geom->get_coords_index().is_null()) {
    _vertices_tristrip_pcollector.add_level(geom->get_num_vertices());
  } else {
    _vertices_indexed_tristrip_pcollector.add_level(geom->get_num_vertices());
  }
#endif

  issue_scene_graph_color();

  int nprims = geom->get_num_prims();
  const int *plen = geom->get_lengths();
  Geom::VertexIterator vi = geom->make_vertex_iterator();
  Geom::NormalIterator ni = geom->make_normal_iterator();
  Geom::MultiTexCoordIterator ti;
  geom->setup_multitexcoord_iterator(ti, _current_texture->get_on_stages(),
                                     _current_tex_gen->get_no_texcoords());
  Geom::ColorIterator ci = geom->make_color_iterator();

  GeomIssuer::IssueColor *issue_color;

  if (_color_blend_involves_color_scale || !_color_scale_enabled) {
    issue_color = issue_color_gl;
  } else {
    issue_color = issue_scaled_color_gl;
  }

  GeomIssuer issuer(geom, this,
                    issue_vertex_gl,
                    issue_normal_gl,
                    issue_color,
                    issue_texcoord_single_gl,
                    issue_texcoord_multi_gl,
                    ti);

  // If we have per-vertex colors or normals, we need smooth shading.
  // Otherwise we want flat shading for performance reasons.
  if ((geom->get_binding(G_COLOR) == G_PER_VERTEX && wants_colors()) ||
      (geom->get_binding(G_NORMAL) == G_PER_VERTEX && wants_normals())) {
    GLP(ShadeModel)(GL_SMOOTH);
  } else {
    GLP(ShadeModel)(GL_FLAT);
  }

  // Draw overall
  issuer.issue_color(G_OVERALL, ci);
  issuer.issue_normal(G_OVERALL, ni);

  for (int i = 0; i < nprims; i++) {
    // Draw per primitive
    issuer.issue_color(G_PER_PRIM, ci);
    issuer.issue_normal(G_PER_PRIM, ni);

    int num_verts = *(plen++);
    nassertv(num_verts >= 3);

    GLP(Begin)(GL_TRIANGLE_STRIP);

    // Per-component attributes for the first triangle?
    issuer.issue_color(G_PER_COMPONENT, ci);
    issuer.issue_normal(G_PER_COMPONENT, ni);

    // Draw the first three vertices.
    int v;
    for (v = 0; v < 3; v++) {
      issuer.issue_color(G_PER_VERTEX, ci);
      issuer.issue_normal(G_PER_VERTEX, ni);
      issuer.issue_texcoord(G_PER_VERTEX, ti);
      issuer.issue_vertex(G_PER_VERTEX, vi);
    }

    // Now draw each of the remaining vertices.  Each vertex from
    // this point on defines a new triangle.
    for (v = 3; v < num_verts; v++) {
      // Per-component attributes?
      issuer.issue_color(G_PER_COMPONENT, ci);
      issuer.issue_normal(G_PER_COMPONENT, ni);

      // Per-vertex attributes.
      issuer.issue_color(G_PER_VERTEX, ci);
      issuer.issue_normal(G_PER_VERTEX, ni);
      issuer.issue_texcoord(G_PER_VERTEX, ti);
      issuer.issue_vertex(G_PER_VERTEX, vi);
    }
    GLP(End)();
  }

  report_my_gl_errors();
  DO_PSTATS_STUFF(_draw_primitive_pcollector.stop());
  
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::draw_trifan
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
draw_trifan(GeomTrifan *geom, GeomContext *gc) {
#ifdef GSG_VERBOSE
  GLCAT.spam() << "draw_trifan()" << endl;
#endif

  setup_antialias_polygon();

  if (draw_display_list(gc)) {
    return;
  }

#ifdef DO_PSTATS
  //  PStatTimer timer(_draw_primitive_pcollector);
  // Using PStatTimer may cause a compiler crash.
  _draw_primitive_pcollector.start();
  _vertices_trifan_pcollector.add_level(geom->get_num_vertices());
#endif

  issue_scene_graph_color();

  int nprims = geom->get_num_prims();
  const int *plen = geom->get_lengths();
  Geom::VertexIterator vi = geom->make_vertex_iterator();
  Geom::NormalIterator ni = geom->make_normal_iterator();
  Geom::MultiTexCoordIterator ti;
  geom->setup_multitexcoord_iterator(ti, _current_texture->get_on_stages(),
                                     _current_tex_gen->get_no_texcoords());
  Geom::ColorIterator ci = geom->make_color_iterator();

  GeomIssuer::IssueColor *issue_color;

  if (_color_blend_involves_color_scale || !_color_scale_enabled) {
    issue_color = issue_color_gl;
  } else {
    issue_color = issue_scaled_color_gl;
  }

  GeomIssuer issuer(geom, this,
                    issue_vertex_gl,
                    issue_normal_gl,
                    issue_color,
                    issue_texcoord_single_gl,
                    issue_texcoord_multi_gl,
                    ti);

  // If we have per-vertex colors or normals, we need smooth shading.
  // Otherwise we want flat shading for performance reasons.
  if ((geom->get_binding(G_COLOR) == G_PER_VERTEX && wants_colors()) ||
      (geom->get_binding(G_NORMAL) == G_PER_VERTEX && wants_normals())) {
    GLP(ShadeModel)(GL_SMOOTH);
  } else {
    GLP(ShadeModel)(GL_FLAT);
  }

  // Draw overall
  issuer.issue_color(G_OVERALL, ci);
  issuer.issue_normal(G_OVERALL, ni);

  for (int i = 0; i < nprims; i++) {
    // Draw per primitive
    issuer.issue_color(G_PER_PRIM, ci);
    issuer.issue_normal(G_PER_PRIM, ni);

    int num_verts = *(plen++);
    nassertv(num_verts >= 3);

    GLP(Begin)(GL_TRIANGLE_FAN);

    // Per-component attributes for the first triangle?
    issuer.issue_color(G_PER_COMPONENT, ci);
    issuer.issue_normal(G_PER_COMPONENT, ni);

    // Draw the first three vertices.
    int v;
    for (v = 0; v < 3; v++) {
      issuer.issue_color(G_PER_VERTEX, ci);
      issuer.issue_normal(G_PER_VERTEX, ni);
      issuer.issue_texcoord(G_PER_VERTEX, ti);
      issuer.issue_vertex(G_PER_VERTEX, vi);
    }

    // Now draw each of the remaining vertices.  Each vertex from
    // this point on defines a new triangle.
    for (v = 3; v < num_verts; v++) {
      // Per-component attributes?
      issuer.issue_color(G_PER_COMPONENT, ci);
      issuer.issue_normal(G_PER_COMPONENT, ni);

      // Per-vertex attributes.
      issuer.issue_color(G_PER_VERTEX, ci);
      issuer.issue_normal(G_PER_VERTEX, ni);
      issuer.issue_texcoord(G_PER_VERTEX, ti);
      issuer.issue_vertex(G_PER_VERTEX, vi);
    }
    GLP(End)();
  }
  report_my_gl_errors();
  DO_PSTATS_STUFF(_draw_primitive_pcollector.stop());
}


////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::draw_sphere
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
draw_sphere(GeomSphere *geom, GeomContext *gc) {
#ifdef GSG_VERBOSE
  GLCAT.spam() << "draw_sphere()" << endl;
#endif

  setup_antialias_polygon();

  if (draw_display_list(gc)) {
    return;
  }

#ifdef DO_PSTATS
  //  PStatTimer timer(_draw_primitive_pcollector);
  // Using PStatTimer may cause a compiler crash.
  _draw_primitive_pcollector.start();
  _vertices_other_pcollector.add_level(geom->get_num_vertices());
#endif

  issue_scene_graph_color();

  int nprims = geom->get_num_prims();
  Geom::VertexIterator vi = geom->make_vertex_iterator();
  Geom::MultiTexCoordIterator ti;
  geom->setup_multitexcoord_iterator(ti, _current_texture->get_on_stages(),
                                     _current_tex_gen->get_no_texcoords());
  Geom::ColorIterator ci = geom->make_color_iterator();

  GeomIssuer::IssueColor *issue_color;

  if (_color_blend_involves_color_scale || !_color_scale_enabled) {
    issue_color = issue_color_gl;
  } else {
    issue_color = issue_scaled_color_gl;
  }

  GeomIssuer issuer(geom, this,
                    issue_vertex_gl,
                    issue_normal_gl,
                    issue_color,
                    issue_texcoord_single_gl,
                    issue_texcoord_multi_gl,
                    ti);

  if (wants_normals()) {
    GLP(ShadeModel)(GL_SMOOTH);
  } else {
    GLP(ShadeModel)(GL_FLAT);
  }

  // Draw overall
  issuer.issue_color(G_OVERALL, ci);

  GLUquadricObj *sph = GLUP(NewQuadric)();
  GLUP(QuadricNormals)(sph, wants_normals() ? (GLenum)GLU_SMOOTH : (GLenum)GLU_NONE);
  GLUP(QuadricTexture)(sph, wants_texcoords() ? (GLenum)GL_TRUE : (GLenum)GL_FALSE);
  GLUP(QuadricOrientation)(sph, (GLenum)GLU_OUTSIDE);
  GLUP(QuadricDrawStyle)(sph, (GLenum)GLU_FILL);
  //GLUP(QuadricDrawStyle)(sph, (GLenum)GLU_LINE);

  for (int i = 0; i < nprims; i++) {
    // Draw per primitive
    issuer.issue_color(G_PER_PRIM, ci);

    for (int j = 0; j < 2; j++) {
      // Draw per vertex
      issuer.issue_color(G_PER_VERTEX, ci);
    }
    Vertexf center = geom->get_next_vertex(vi);
    Vertexf edge = geom->get_next_vertex(vi);
    LVector3f v = edge - center;
    float r = sqrt(dot(v, v));

    // Since GLUP(Sphere) doesn't have a center parameter, we have to use
    // a matrix transform.

    GLP(MatrixMode)(GL_MODELVIEW);
    GLP(PushMatrix)();
    GLP(MultMatrixf)(LMatrix4f::translate_mat(center).get_data());

    // Now render the sphere using GLU calls.
    GLUP(Sphere)(sph, r, 16, 10);

    GLP(MatrixMode)(GL_MODELVIEW);
    GLP(PopMatrix)();
  }

  GLUP(DeleteQuadric)(sph);
  report_my_gl_errors();
  DO_PSTATS_STUFF(_draw_primitive_pcollector.stop());
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::prepare_texture
//       Access: Public, Virtual
//  Description: Creates a new retained-mode representation of the
//               given texture, and returns a newly-allocated
//               TextureContext pointer to reference it.  It is the
//               responsibility of the calling function to later
//               call release_texture() with this same pointer (which
//               will also delete the pointer).
//
//               This function should not be called directly to
//               prepare a texture.  Instead, call Texture::prepare().
////////////////////////////////////////////////////////////////////
TextureContext *CLP(GraphicsStateGuardian)::
prepare_texture(Texture *tex) {
  CLP(TextureContext) *gtc = new CLP(TextureContext)(tex);
  GLP(GenTextures)(1, &gtc->_index);

  bind_texture(gtc);
  GLP(PrioritizeTextures)(1, &gtc->_index, &gtc->_priority);
  specify_texture(tex);
  apply_texture_immediate(gtc, tex);

  report_my_gl_errors();
  return gtc;
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::apply_texture
//       Access: Public, Virtual
//  Description: Makes the texture the currently available texture for
//               rendering.
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
apply_texture(TextureContext *tc) {
  CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);

  add_to_texture_record(gtc);
  bind_texture(gtc);

  int dirty = gtc->get_dirty_flags();
  if ((dirty & (Texture::DF_wrap | Texture::DF_filter | Texture::DF_border)) != 0) {
    // We need to re-specify the texture properties.
    specify_texture(gtc->_texture);
  }
  if ((dirty & (Texture::DF_image | Texture::DF_mipmap | Texture::DF_border)) != 0) {
    // We need to re-apply the image.
    apply_texture_immediate(gtc, gtc->_texture);
  }

  gtc->clear_dirty_flags();

  report_my_gl_errors();
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::release_texture
//       Access: Public, Virtual
//  Description: Frees the GL resources previously allocated for the
//               texture.  This function should never be called
//               directly; instead, call Texture::release() (or simply
//               let the Texture destruct).
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
release_texture(TextureContext *tc) {
  CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);

  GLP(DeleteTextures)(1, &gtc->_index);
  report_my_gl_errors();

  gtc->_index = 0;
  delete gtc;
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::prepare_geom
//       Access: Public, Virtual
//  Description: Creates a new retained-mode representation of the
//               given geom, and returns a newly-allocated
//               GeomContext pointer to reference it.  It is the
//               responsibility of the calling function to later
//               call release_geom() with this same pointer (which
//               will also delete the pointer).
//
//               This function should not be called directly to
//               prepare a geom.  Instead, call Geom::prepare().
////////////////////////////////////////////////////////////////////
GeomContext *CLP(GraphicsStateGuardian)::
prepare_geom(Geom *geom) {
  if (!_vertex_colors_enabled) {
    // We can't build a display list (or play back a display list) if
    // its color is overridden with a scene graph color.  Maybe if we
    // take advantage of the OpenGL color matrix we can do this, but
    // for now we'll just ignore it.
    return NULL;
  }

  if (geom->is_dynamic()) {
    // If the Geom is dynamic in some way, we shouldn't try to
    // display-list it.
    return NULL;
  }

  CLP(GeomContext) *ggc = new CLP(GeomContext)(geom);
  ggc->_index = GLP(GenLists)(1);
  if (GLCAT.is_debug()) {
    GLCAT.debug()
      << "preparing " << *geom << ", index " << ggc->_index << "\n";
  }
  if (ggc->_index == 0) {
    GLCAT.error()
      << "Ran out of display list indices.\n";
    delete ggc;
    return NULL;
  }

  // We need to temporarily force normals and UV's on, so the display
  // list will have them built in.
  //force_texcoords(); 
  force_normals();

#ifdef DO_PSTATS
  // Count up the number of vertices we're about to render, by
  // checking the PStats vertex counters now, and at the end.  This is
  // kind of hacky, but this is debug code.
  float num_verts_before =
    _vertices_tristrip_pcollector.get_level() +
    _vertices_trifan_pcollector.get_level() +
    _vertices_tri_pcollector.get_level() +
    _vertices_other_pcollector.get_level();
#endif

  // Now define the display list.
  GLP(NewList)(ggc->_index, GL_COMPILE);
  geom->draw_immediate(this, NULL);
  GLP(EndList)();

#ifdef DO_PSTATS
  float num_verts_after =
    _vertices_tristrip_pcollector.get_level() +
    _vertices_trifan_pcollector.get_level() +
    _vertices_tri_pcollector.get_level() +
    _vertices_other_pcollector.get_level();
  float num_verts = num_verts_after - num_verts_before;
  ggc->_num_verts = (int)(num_verts + 0.5);
#endif

  undo_force_normals();
  //undo_force_texcoords();

  report_my_gl_errors();
  return ggc;
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::release_geom
//       Access: Public, Virtual
//  Description: Frees the GL resources previously allocated for the
//               geom.  This function should never be called
//               directly; instead, call Geom::release() (or simply
//               let the Geom destruct).
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
release_geom(GeomContext *gc) {
  CLP(GeomContext) *ggc = DCAST(CLP(GeomContext), gc);
  if (GLCAT.is_debug()) {
    GLCAT.debug()
      << "releasing index " << ggc->_index << "\n";
  }

  GLP(DeleteLists)(ggc->_index, 1);
  report_my_gl_errors();

  ggc->_index = 0;
  delete ggc;
}

#if 0
static int logs[] = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048,
                      4096, 0 };

// This function returns the smallest power of two greater than or
// equal to x.
static int binary_log_cap(const int x) {
  int i = 0;
  for (; (x > logs[i]) && (logs[i] != 0); ++i);
  if (logs[i] == 0)
    return 4096;
  return logs[i];
}
#endif

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::framebuffer_copy_to_texture
//       Access: Public, Virtual
//  Description: Copy the pixels within the indicated display
//               region from the framebuffer into texture memory.
//
//               If z > -1, it is the cube map index into which to
//               copy.
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr,
                            const RenderBuffer &rb) {
  nassertv(tex != NULL && dr != NULL);
  set_read_buffer(rb);

  int xo, yo, w, h;
  dr->get_region_pixels(xo, yo, w, h);

  tex->set_x_size(w);
  tex->set_y_size(h);

  TextureContext *tc = tex->prepare_now(get_prepared_objects(), this);
  nassertv(tc != (TextureContext *)NULL);
  bind_texture(tc);

  if (z >= 0) {
    // Copy to a cube map face.
    nassertv(z < 6);
    nassertv(tex->get_texture_type() == Texture::TT_cube_map);

    if (_supports_cube_map) {
      // We cleverly defined the cube map faces to fall in the same
      // order as the GL constants are defined, so we can just make this
      // simple addition to get to the right GL constant.
      GLP(CopyTexImage2D)(GL_TEXTURE_CUBE_MAP_POSITIVE_X + z, 0,
                          get_internal_image_format(tex->get_format()),
                          xo, yo, w, h, 0);
    }

  } else {
    // Copy to a regular texture.
    nassertv(tex->get_texture_type() == Texture::TT_2d_texture);
    GLP(CopyTexImage2D)(GL_TEXTURE_2D, 0,
                        get_internal_image_format(tex->get_format()),
                        xo, yo, w, h, 0);
  }

  // Clear the internal texture state, since we've just monkeyed with it.
  modify_state(get_untextured_state());
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::framebuffer_copy_to_ram
//       Access: Public, Virtual
//  Description: Copy the pixels within the indicated display region
//               from the framebuffer into system memory, not texture
//               memory.  Returns true on success, false on failure.
//
//               This completely redefines the ram image of the
//               indicated texture.
////////////////////////////////////////////////////////////////////
bool CLP(GraphicsStateGuardian)::
framebuffer_copy_to_ram(Texture *tex, int z, const DisplayRegion *dr,
                        const RenderBuffer &rb) {
  nassertr(tex != NULL && dr != NULL, false);
  set_read_buffer(rb);
  GLP(PixelStorei)(GL_PACK_ALIGNMENT, 1);

  // Bug fix for RE, RE2, and VTX - need to disable texturing in order
  // for GLP(ReadPixels)() to work
  // NOTE: reading the depth buffer is *much* slower than reading the
  // color buffer
  modify_state(get_untextured_state());

  int xo, yo, w, h;
  dr->get_region_pixels(xo, yo, w, h);

  const FrameBufferProperties &properties = get_properties();

  Texture::ComponentType component_type;
  if (properties.get_color_bits() <= 24) {
    component_type = Texture::T_unsigned_byte;
  } else {
    component_type = Texture::T_unsigned_short;
  }

  Texture::Format format;
  if (properties.get_frame_buffer_mode() & FrameBufferProperties::FM_alpha) {
    format = Texture::F_rgba;
  } else {
    format = Texture::F_rgb;
  }

  Texture::TextureType texture_type;
  if (z >= 0) {
    texture_type = Texture::TT_cube_map;
  } else {
    texture_type = Texture::TT_2d_texture;
  }

  if (tex->get_x_size() != w || tex->get_y_size() != h ||
      tex->get_component_type() != component_type ||
      tex->get_format() != format ||
      tex->get_texture_type() != texture_type) {
    // Re-setup the texture; its properties have changed.
    tex->setup_texture(texture_type, w, h, 1, component_type, format);
  }

  GLenum external_format = get_external_image_format(format);

#ifdef GSG_VERBOSE
  GLCAT.debug()
    << "glReadPixels(" << xo << ", " << yo << ", " << w << ", " << h << ", ";
  switch (external_format) {
  case GL_DEPTH_COMPONENT:
    GLCAT.debug(false) << "GL_DEPTH_COMPONENT, ";
    break;
  case GL_RGB:
    GLCAT.debug(false) << "GL_RGB, ";
    break;
  case GL_RGBA:
    GLCAT.debug(false) << "GL_RGBA, ";
    break;
  case GL_BGR:
    GLCAT.debug(false) << "GL_BGR, ";
    break;
  case GL_BGRA:
    GLCAT.debug(false) << "GL_BGRA, ";
    break;
  default:
    GLCAT.debug(false) << "unknown, ";
    break;
  }
  switch (get_component_type(component_type)) {
  case GL_UNSIGNED_BYTE:
    GLCAT.debug(false) << "GL_UNSIGNED_BYTE, ";
    break;
  case GL_UNSIGNED_SHORT:
    GLCAT.debug(false) << "GL_UNSIGNED_SHORT, ";
    break;
  case GL_FLOAT:
    GLCAT.debug(false) << "GL_FLOAT, ";
    break;
  default:
    GLCAT.debug(false) << "unknown, ";
    break;
  }
  GLCAT.debug(false)
    << ")" << endl;
#endif

  unsigned char *image = tex->modify_ram_image();
  if (z >= 0) {
    nassertr(z < tex->get_z_size(), false);
    image += z * tex->get_expected_ram_page_size();
  }

  GLP(ReadPixels)(xo, yo, w, h,
                  external_format, get_component_type(component_type),
                  image);

  // We may have to reverse the byte ordering of the image if GL
  // didn't do it for us.  This assumes we render out the six faces of
  // a cube map in ascending order, since we can't do this until we
  // have rendered the last face.
  if (!_supports_bgr && (z == -1 || z == 5)) {
    tex->set_ram_image(fix_component_ordering(tex->get_ram_image(), 
                                              external_format, tex));
  }

  report_my_gl_errors();
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::apply_material
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::apply_material(const Material *material) {
  GLenum face = material->get_twoside() ? GL_FRONT_AND_BACK : GL_FRONT;

  GLP(Materialfv)(face, GL_SPECULAR, material->get_specular().get_data());
  GLP(Materialfv)(face, GL_EMISSION, material->get_emission().get_data());
  GLP(Materialf)(face, GL_SHININESS, material->get_shininess());

  if (material->has_ambient() && material->has_diffuse()) {
    // The material has both an ambient and diffuse specified.  This
    // means we do not need glMaterialColor().
    GLP(Disable)(GL_COLOR_MATERIAL);
    GLP(Materialfv)(face, GL_AMBIENT, material->get_ambient().get_data());
    GLP(Materialfv)(face, GL_DIFFUSE, material->get_diffuse().get_data());

  } else if (material->has_ambient()) {
    // The material specifies an ambient, but not a diffuse component.
    // The diffuse component comes from the object's color.
    GLP(Materialfv)(face, GL_AMBIENT, material->get_ambient().get_data());
    GLP(ColorMaterial)(face, GL_DIFFUSE);
    GLP(Enable)(GL_COLOR_MATERIAL);

  } else if (material->has_diffuse()) {
    // The material specifies a diffuse, but not an ambient component.
    // The ambient component comes from the object's color.
    GLP(Materialfv)(face, GL_DIFFUSE, material->get_diffuse().get_data());
    GLP(ColorMaterial)(face, GL_AMBIENT);
    GLP(Enable)(GL_COLOR_MATERIAL);

  } else {
    // The material specifies neither a diffuse nor an ambient
    // component.  Both components come from the object's color.
    GLP(ColorMaterial)(face, GL_AMBIENT_AND_DIFFUSE);
    GLP(Enable)(GL_COLOR_MATERIAL);
  }

  GLP(LightModeli)(GL_LIGHT_MODEL_LOCAL_VIEWER, material->get_local());
  GLP(LightModeli)(GL_LIGHT_MODEL_TWO_SIDE, material->get_twoside());
  report_my_gl_errors();
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::apply_fog
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
apply_fog(Fog *fog) {
  Fog::Mode fmode = fog->get_mode();
  GLP(Fogi)(GL_FOG_MODE, get_fog_mode_type(fmode));

  if (fmode == Fog::M_linear) {
    float onset, opaque;
    fog->get_linear_range(onset, opaque);
    GLP(Fogf)(GL_FOG_START, onset);
    GLP(Fogf)(GL_FOG_END, opaque);

  } else {
    // Exponential fog is always camera-relative.
    GLP(Fogf)(GL_FOG_DENSITY, fog->get_exp_density());
  }

  GLP(Fogfv)(GL_FOG_COLOR, fog->get_color().get_data());
  report_my_gl_errors();
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::issue_transform
//       Access: Public, Virtual
//  Description: Sends the indicated transform matrix to the graphics
//               API to be applied to future vertices.
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
issue_transform(const TransformState *transform) {
#ifdef GSG_VERBOSE
  GLCAT.spam()
    << "glLoadMatrix(GL_MODELVIEW): " << transform->get_mat() << endl;
#endif
  DO_PSTATS_STUFF(_transform_state_pcollector.add_level(1));
  GLP(MatrixMode)(GL_MODELVIEW);
  GLP(LoadMatrixf)(transform->get_mat().get_data());

  _transform = transform;
  if (_auto_rescale_normal) {
    do_auto_rescale_normal();
  }

  report_my_gl_errors();
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::issue_tex_matrix
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
issue_tex_matrix(const TexMatrixAttrib *attrib) {
  // We don't apply the texture matrix right away, since we might yet
  // get a TextureAttrib that changes the set of TextureStages we have
  // active.  Instead, we simply set a flag that indicates we need to
  // re-issue the texture matrix after all of the other attribs are
  // done being issued.
  _current_tex_mat = attrib;
  _needs_tex_mat = true;
}




////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::issue_cg_shader_bind
//       Access: Public, Virtual
//  Description: Bind shader of current node
//               and unbind the shader of the previous node
//               Create a new GLCgShaderContext if this shader
//               object is coming in for the first time
//               Also maintain the map of CgShader objects to
//               respective GLCgShaderContexts
////////////////////////////////////////////////////////////////////
#ifdef HAVE_CGGL
void CLP(GraphicsStateGuardian)::
issue_cg_shader_bind(const CgShaderAttrib *attrib) {

  if (attrib->is_off()) { //Current node has no shaders
    if (_cg_shader != (CgShader *) NULL) {
      _gl_cg_shader_contexts[_cg_shader]->un_bind();// Prev node had shaders
    }    
    _cg_shader = attrib->get_cg_shader();//Store current node.. here NULL 
  } else {// Current node has shaders
    if (_cg_shader != (CgShader *) NULL) {
      _gl_cg_shader_contexts[_cg_shader]->un_bind();// Prev node had shaders
    }
    _cg_shader = attrib->get_cg_shader();//Store current node  
    CGSHADERCONTEXTS::const_iterator csci;
    csci = _gl_cg_shader_contexts.find(_cg_shader);
    if (csci != _gl_cg_shader_contexts.end()) { // Already have context?
      (*csci).second->bind(this); // Bind the current shader
    } else {// First time CgShader object...need to make a new GLCgShaderContext
      PT(CLP(CgShaderContext)) csc = new CLP(CgShaderContext)(_cg_shader);
      _cg_shader->load_shaders(); // Profiles created lets load from HD
      csc->load_shaders(); // Programs loaded, compile and download to GPU
      CGSHADERCONTEXTS::value_type shader_and_context(_cg_shader, csc);
      _gl_cg_shader_contexts.insert(shader_and_context);
      csc->bind(this);// Bind the new shader
    }
  }
}
#endif

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::issue_tex_gen
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
issue_tex_gen(const TexGenAttrib *attrib) {
  // We don't apply the texture coordinate generation commands right
  // away, since we might yet get a TextureAttrib that changes the set
  // of TextureStages we have active.  Instead, we simply set a flag
  // that indicates we need to re-issue the TexGenAttrib after all of
  // the other attribs are done being issued.
  _current_tex_gen = attrib;
  _needs_tex_gen = true;
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::issue_material
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
issue_material(const MaterialAttrib *attrib) {
  const Material *material = attrib->get_material();
  if (material != (const Material *)NULL) {
    apply_material(material);
  } else {
    // Apply a default material when materials are turned off.
    Material empty;
    apply_material(&empty);
  }
  report_my_gl_errors();
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::issue_render_mode
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
issue_render_mode(const RenderModeAttrib *attrib) {
  _render_mode = attrib->get_mode();

  switch (_render_mode) {
  case RenderModeAttrib::M_unchanged:
  case RenderModeAttrib::M_filled:
    GLP(PolygonMode)(GL_FRONT_AND_BACK, GL_FILL);
    break;

  case RenderModeAttrib::M_wireframe:
    GLP(PolygonMode)(GL_FRONT_AND_BACK, GL_LINE);
    break;

  case RenderModeAttrib::M_point:
    GLP(PolygonMode)(GL_FRONT_AND_BACK, GL_POINT);
    break;

  default:
    GLCAT.error()
      << "Unknown render mode " << (int)_render_mode << endl;
  }

  // The thickness affects both the line width and the point size.
  GLP(LineWidth)(attrib->get_thickness());
  GLP(PointSize)(attrib->get_thickness());

  report_my_gl_errors();
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::issue_antialias
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
issue_antialias(const AntialiasAttrib *attrib) {
  if (attrib->get_mode_type() == AntialiasAttrib::M_auto) {
    // In this special mode, we must enable antialiasing on a
    // case-by-case basis, because we enable it differently for
    // polygons and for points and lines.
    _auto_antialias_mode = true;

  } else {
    // Otherwise, explicitly enable or disable according to the bits
    // that are set.  But if multisample is requested and supported,
    // don't use the other bits at all (they will be ignored by GL
    // anyway).
    _auto_antialias_mode = false;
    unsigned short mode = attrib->get_mode();

    if (_supports_multisample &&
        (mode & AntialiasAttrib::M_multisample) != 0) {
      enable_multisample_antialias(true);

    } else {
      enable_multisample_antialias(false);
      enable_line_smooth((mode & AntialiasAttrib::M_line) != 0);
      enable_point_smooth((mode & AntialiasAttrib::M_point) != 0);
      enable_polygon_smooth((mode & AntialiasAttrib::M_polygon) != 0);
    }
  }

  switch (attrib->get_mode_quality()) {
  case AntialiasAttrib::M_faster:
    GLP(Hint)(GL_LINE_SMOOTH_HINT, GL_FASTEST);
    GLP(Hint)(GL_POINT_SMOOTH_HINT, GL_FASTEST);
    GLP(Hint)(GL_POLYGON_SMOOTH_HINT, GL_FASTEST);
    break;

  case AntialiasAttrib::M_better:
    GLP(Hint)(GL_LINE_SMOOTH_HINT, GL_NICEST);
    GLP(Hint)(GL_POINT_SMOOTH_HINT, GL_NICEST);
    GLP(Hint)(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
    break;

  default:
    GLP(Hint)(GL_LINE_SMOOTH_HINT, GL_DONT_CARE);
    GLP(Hint)(GL_POINT_SMOOTH_HINT, GL_DONT_CARE);
    GLP(Hint)(GL_POLYGON_SMOOTH_HINT, GL_DONT_CARE);
    break;
  }

  report_my_gl_errors();
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::issue_rescale_normal
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
issue_rescale_normal(const RescaleNormalAttrib *attrib) {
  RescaleNormalAttrib::Mode mode = attrib->get_mode();

  _auto_rescale_normal = false;

  switch (mode) {
  case RescaleNormalAttrib::M_none:
    GLP(Disable)(GL_NORMALIZE);
    if (_supports_rescale_normal) {
      GLP(Disable)(GL_RESCALE_NORMAL);
    }
    break;

  case RescaleNormalAttrib::M_rescale:
    if (_supports_rescale_normal) {
      GLP(Enable)(GL_RESCALE_NORMAL);
      GLP(Disable)(GL_NORMALIZE);
    } else {
      GLP(Enable)(GL_NORMALIZE);
    }
    break;

  case RescaleNormalAttrib::M_normalize:
    GLP(Enable)(GL_NORMALIZE);
    if (_supports_rescale_normal) {
      GLP(Disable)(GL_RESCALE_NORMAL);
    }
    break;

  case RescaleNormalAttrib::M_auto:
    _auto_rescale_normal = true;
    do_auto_rescale_normal();
    break;

  default:
    GLCAT.error()
      << "Unknown rescale_normal mode " << (int)mode << endl;
  }
  report_my_gl_errors();
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::issue_texture_apply
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
issue_texture_apply(const TextureApplyAttrib *) {
  // This attrib is no longer used; it is replaced by the parameters
  // within TextureStage.
  return;
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::issue_color_write
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
issue_color_write(const ColorWriteAttrib *attrib) {
  // If we did not override this function, the default implementation
  // would achieve turning off color writes by changing the blend mode
  // in set_blend_mode().  However, since GL does support an easy way
  // to disable writes to the color buffer, we can take advantage of
  // it here.
  if (CLP(color_mask)) {
    ColorWriteAttrib::Mode mode = attrib->get_mode();
    if (mode == ColorWriteAttrib::M_off) {
      GLP(ColorMask)(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
    } else {
      GLP(ColorMask)(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    }
    report_my_gl_errors();

  } else {
    // Some implementations don't seem to handle GLP(ColorMask)() very
    // robustly, however, so we provide this fallback.
    GraphicsStateGuardian::issue_color_write(attrib);
  }
}

// PandaCompareFunc - 1 + 0x200 === GL_NEVER, etc.  order is sequential
#define PANDA_TO_GL_COMPAREFUNC(PANDACMPFUNC) (PANDACMPFUNC-1 +0x200)

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::issue_depth_test
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
issue_depth_test(const DepthTestAttrib *attrib) {
  DepthTestAttrib::PandaCompareFunc mode = attrib->get_mode();
  if (mode == DepthTestAttrib::M_none) {
    enable_depth_test(false);
  } else {
    enable_depth_test(true);
    GLP(DepthFunc)(PANDA_TO_GL_COMPAREFUNC(mode));
  }
  report_my_gl_errors();
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::issue_alpha_test
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
issue_alpha_test(const AlphaTestAttrib *attrib) {
  AlphaTestAttrib::PandaCompareFunc mode = attrib->get_mode();
  if (mode == AlphaTestAttrib::M_none) {
    enable_alpha_test(false);
  } else {
    assert(GL_NEVER==(AlphaTestAttrib::M_never-1+0x200));
    GLP(AlphaFunc)(PANDA_TO_GL_COMPAREFUNC(mode), attrib->get_reference_alpha());
    enable_alpha_test(true);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::issue_depth_write
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
issue_depth_write(const DepthWriteAttrib *attrib) {
  DepthWriteAttrib::Mode mode = attrib->get_mode();
  if (mode == DepthWriteAttrib::M_off) {
    GLP(DepthMask)(GL_FALSE);
  } else {
    GLP(DepthMask)(GL_TRUE);
  }
  report_my_gl_errors();
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::issue_cull_face
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
issue_cull_face(const CullFaceAttrib *attrib) {
  CullFaceAttrib::Mode mode = attrib->get_effective_mode();

  switch (mode) {
  case CullFaceAttrib::M_cull_none:
    GLP(Disable)(GL_CULL_FACE);
    break;
  case CullFaceAttrib::M_cull_clockwise:
    GLP(Enable)(GL_CULL_FACE);
    GLP(CullFace)(GL_BACK);
    break;
  case CullFaceAttrib::M_cull_counter_clockwise:
    GLP(Enable)(GL_CULL_FACE);
    GLP(CullFace)(GL_FRONT);
    break;
  default:
    GLCAT.error()
      << "invalid cull face mode " << (int)mode << endl;
    break;
  }
  report_my_gl_errors();
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::issue_fog
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
issue_fog(const FogAttrib *attrib) {
  if (!attrib->is_off()) {
    enable_fog(true);
    Fog *fog = attrib->get_fog();
    nassertv(fog != (Fog *)NULL);
    apply_fog(fog);
  } else {
    enable_fog(false);
  }
  report_my_gl_errors();
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::issue_depth_offset
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
issue_depth_offset(const DepthOffsetAttrib *attrib) {
  int offset = attrib->get_offset();

  if (offset != 0) {
    // The relationship between these two parameters is a little
    // unclear and poorly explained in the GL man pages.
    GLP(PolygonOffset)((GLfloat) -offset, (GLfloat) -offset);
    enable_polygon_offset(true);

  } else {
    enable_polygon_offset(false);
  }

  report_my_gl_errors();
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::bind_light
//       Access: Public, Virtual
//  Description: Called the first time a particular light has been
//               bound to a given id within a frame, this should set
//               up the associated hardware light with the light's
//               properties.
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
bind_light(PointLight *light_obj, const NodePath &light, int light_id) {
  GLenum id = get_light_id(light_id);
  static const Colorf black(0.0f, 0.0f, 0.0f, 1.0f);
  GLP(Lightfv)(id, GL_AMBIENT, black.get_data());
  GLP(Lightfv)(id, GL_DIFFUSE, light_obj->get_color().get_data());
  GLP(Lightfv)(id, GL_SPECULAR, light_obj->get_specular_color().get_data());

  // Position needs to specify x, y, z, and w
  // w == 1 implies non-infinite position
  const LMatrix4f &light_mat = light.get_mat(_scene_setup->get_scene_root());
  LPoint3f pos = light_obj->get_point() * light_mat;

  LPoint4f fpos(pos[0], pos[1], pos[2], 1.0f);
  GLP(Lightfv)(id, GL_POSITION, fpos.get_data());

  // GL_SPOT_DIRECTION is not significant when cutoff == 180

  // Exponent == 0 implies uniform light distribution
  GLP(Lightf)(id, GL_SPOT_EXPONENT, 0.0f);

  // Cutoff == 180 means uniform point light source
  GLP(Lightf)(id, GL_SPOT_CUTOFF, 180.0f);

  const LVecBase3f &att = light_obj->get_attenuation();
  GLP(Lightf)(id, GL_CONSTANT_ATTENUATION, att[0]);
  GLP(Lightf)(id, GL_LINEAR_ATTENUATION, att[1]);
  GLP(Lightf)(id, GL_QUADRATIC_ATTENUATION, att[2]);

  report_my_gl_errors();
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::bind_light
//       Access: Public, Virtual
//  Description: Called the first time a particular light has been
//               bound to a given id within a frame, this should set
//               up the associated hardware light with the light's
//               properties.
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
bind_light(DirectionalLight *light_obj, const NodePath &light, int light_id) {
  GLenum id = get_light_id( light_id );
  static const Colorf black(0.0f, 0.0f, 0.0f, 1.0f);
  GLP(Lightfv)(id, GL_AMBIENT, black.get_data());
  GLP(Lightfv)(id, GL_DIFFUSE, light_obj->get_color().get_data());
  GLP(Lightfv)(id, GL_SPECULAR, light_obj->get_specular_color().get_data());

  // Position needs to specify x, y, z, and w.
  // w == 0 implies light is at infinity
  const LMatrix4f &light_mat = light.get_mat(_scene_setup->get_scene_root());
  LVector3f dir = light_obj->get_direction() * light_mat;
  LPoint4f fdir(-dir[0], -dir[1], -dir[2], 0);
  GLP(Lightfv)(id, GL_POSITION, fdir.get_data());

  // GL_SPOT_DIRECTION is not significant when cutoff == 180
  // In this case, position x, y, z specifies direction

  // Exponent == 0 implies uniform light distribution
  GLP(Lightf)(id, GL_SPOT_EXPONENT, 0.0f);

  // Cutoff == 180 means uniform point light source
  GLP(Lightf)(id, GL_SPOT_CUTOFF, 180.0f);

  // Default attenuation values (only spotlight and point light can
  // modify these)
  GLP(Lightf)(id, GL_CONSTANT_ATTENUATION, 1.0f);
  GLP(Lightf)(id, GL_LINEAR_ATTENUATION, 0.0f);
  GLP(Lightf)(id, GL_QUADRATIC_ATTENUATION, 0.0f);

  report_my_gl_errors();
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::bind_light
//       Access: Public, Virtual
//  Description: Called the first time a particular light has been
//               bound to a given id within a frame, this should set
//               up the associated hardware light with the light's
//               properties.
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
bind_light(Spotlight *light_obj, const NodePath &light, int light_id) {
  Lens *lens = light_obj->get_lens();
  nassertv(lens != (Lens *)NULL);

  GLenum id = get_light_id(light_id);
  static const Colorf black(0.0f, 0.0f, 0.0f, 1.0f);
  GLP(Lightfv)(id, GL_AMBIENT, black.get_data());
  GLP(Lightfv)(id, GL_DIFFUSE, light_obj->get_color().get_data());
  GLP(Lightfv)(id, GL_SPECULAR, light_obj->get_specular_color().get_data());

  // Position needs to specify x, y, z, and w
  // w == 1 implies non-infinite position
  const LMatrix4f &light_mat = light.get_mat(_scene_setup->get_scene_root());
  LPoint3f pos = lens->get_nodal_point() * light_mat;
  LVector3f dir = lens->get_view_vector() * light_mat;

  LPoint4f fpos(pos[0], pos[1], pos[2], 1.0f);
  GLP(Lightfv)(id, GL_POSITION, fpos.get_data());
  GLP(Lightfv)(id, GL_SPOT_DIRECTION, dir.get_data());

  GLP(Lightf)(id, GL_SPOT_EXPONENT, light_obj->get_exponent());
  GLP(Lightf)(id, GL_SPOT_CUTOFF, lens->get_hfov());

  const LVecBase3f &att = light_obj->get_attenuation();
  GLP(Lightf)(id, GL_CONSTANT_ATTENUATION, att[0]);
  GLP(Lightf)(id, GL_LINEAR_ATTENUATION, att[1]);
  GLP(Lightf)(id, GL_QUADRATIC_ATTENUATION, att[2]);

  report_my_gl_errors();
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::wants_texcoords
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
bool CLP(GraphicsStateGuardian)::
wants_texcoords() const {
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::compute_distance_to
//       Access: Public, Virtual
//  Description: This function may only be called during a render
//               traversal; it will compute the distance to the
//               indicated point, assumed to be in modelview
//               coordinates, from the camera plane.
////////////////////////////////////////////////////////////////////
float CLP(GraphicsStateGuardian)::
compute_distance_to(const LPoint3f &point) const {
  // In the case of a CLP(GraphicsStateGuardian), we know that the
  // modelview matrix already includes the relative transform from the
  // camera, as well as a to-y-up conversion.  Thus, the distance to
  // the camera plane is simply the -z distance.

  return -point[2];
}

////////////////////////////////////////////////////////////////////
//     Function: report_errors_loop
//       Access: Protected, Static
//  Description: The internal implementation of report_errors().
//               Don't call this function; use report_errors()
//               instead.  The return value is true if everything is
//               ok, or false if we should shut down.
////////////////////////////////////////////////////////////////////
bool CLP(GraphicsStateGuardian)::
report_errors_loop(int line, const char *source_file, GLenum error_code,
                   int &error_count) {
#ifndef NDEBUG
  static const int max_gl_errors_reported = 20;

  while ((error_count < max_gl_errors_reported) && 
         (error_code != GL_NO_ERROR)) {
    const GLubyte *error_string = GLUP(ErrorString)(error_code);
    if (error_string != (const GLubyte *)NULL) {
      GLCAT.error()
        << "at " << line << " of " << source_file << ": " 
        << error_string << "\n";
    } else {
      GLCAT.error()
        << "at " << line << " of " << source_file << ": " 
        << "GL error " << (int)error_code << "\n";
    }
    error_code = GLP(GetError)();
    error_count++;
  }

#endif
  return (error_code == GL_NO_ERROR);
}

////////////////////////////////////////////////////////////////////
//     Function: GLGraphicsStateGuardian::show_gl_string
//       Access: Protected
//  Description: Outputs the result of glGetString() on the indicated
//               tag.
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
show_gl_string(const string &name, GLenum id) {
  if (GLCAT.is_debug()) {
    const GLubyte *text = GLP(GetString)(id);
    if (text == (const GLubyte *)NULL) {
      GLCAT.debug()
        << "Unable to query " << name << "\n";
    } else {
      GLCAT.debug()
        << name << " = " << (const char *)text << "\n";
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: GLGraphicsStateGuardian::get_gl_version
//       Access: Protected, Virtual
//  Description: Queries the runtime version of OpenGL in use.
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
get_gl_version() {
  show_gl_string("GL_VENDOR", GL_VENDOR);
  show_gl_string("GL_RENDERER", GL_RENDERER);

  _gl_version_major = 0;
  _gl_version_minor = 0;
  _gl_version_release = 0;

  const GLubyte *text = GLP(GetString)(GL_VERSION);
  if (text == (const GLubyte *)NULL) {
    GLCAT.debug()
      << "Unable to query GL_VERSION\n";
  } else {
    string input((const char *)text);
    size_t space = input.find(' ');
    if (space != string::npos) {
      input = input.substr(0, space);
    }

    vector_string components;
    tokenize(input, components, ".");
    if (components.size() >= 1) {
      string_to_int(components[0], _gl_version_major);
    }
    if (components.size() >= 2) {
      string_to_int(components[1], _gl_version_minor);
    }
    if (components.size() >= 3) {
      string_to_int(components[2], _gl_version_release);
    }

    GLCAT.debug()
      << "GL_VERSION = " << (const char *)text << ", decoded to "
      << _gl_version_major << "." << _gl_version_minor 
      << "." << _gl_version_release << "\n";
  }
}

////////////////////////////////////////////////////////////////////
//     Function: GLGraphicsStateGuardian::save_extensions
//       Access: Protected
//  Description: Separates the string returned by GL_EXTENSIONS (or
//               glx or wgl extensions) into its individual tokens
//               and saves them in the _extensions member.
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
save_extensions(const char *extensions) {
  if (extensions != (const char *)NULL) {
    vector_string tokens;
    extract_words(extensions, tokens);
    
    vector_string::iterator ti;
    for (ti = tokens.begin(); ti != tokens.end(); ++ti) {
      _extensions.insert(*ti);
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: GLGraphicsStateGuardian::get_extra_extensions
//       Access: Protected, Virtual
//  Description: This may be redefined by a derived class (e.g. glx or
//               wgl) to get whatever further extensions strings may
//               be appropriate to that interface, in addition to the
//               GL extension strings return by glGetString().
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
get_extra_extensions() {
}

////////////////////////////////////////////////////////////////////
//     Function: GLGraphicsStateGuardian::report_extensions
//       Access: Protected
//  Description: Outputs the list of GL extensions to notify, if debug
//               mode is enabled.
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
report_extensions() const {
  if (GLCAT.is_debug()) {
    GLCAT.debug()
      << "GL Extensions:\n";
    pset<string>::const_iterator ei;
    for (ei = _extensions.begin(); ei != _extensions.end(); ++ei) {
      GLCAT.debug() << (*ei) << "\n";
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: GLGraphicsStateGuardian::has_extension
//       Access: Protected
//  Description: Returns true if the indicated extension is reported
//               by the GL system, false otherwise.  The extension
//               name is case-sensitive.
////////////////////////////////////////////////////////////////////
bool CLP(GraphicsStateGuardian)::
has_extension(const string &extension) const {
  return (_extensions.find(extension) != _extensions.end());
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::is_at_least_version
//       Access: Public
//  Description: Returns true if the runtime GL version number is at
//               least the indicated value, false otherwise.
////////////////////////////////////////////////////////////////////
bool CLP(GraphicsStateGuardian)::
is_at_least_version(int major_version, int minor_version, 
                    int release_version) const {
  if (_gl_version_major < major_version) {
    return false;
  }
  if (_gl_version_minor < minor_version) {
    return false;
  }
  if (_gl_version_release < release_version) {
    return false;
  }
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::get_extension_func
//       Access: Public, Virtual
//  Description: Returns the pointer to the GL extension function with
//               the indicated name.  It is the responsibility of the
//               caller to ensure that the required extension is
//               defined in the OpenGL runtime prior to calling this;
//               it is an error to call this for a function that is
//               not defined.
////////////////////////////////////////////////////////////////////
void *CLP(GraphicsStateGuardian)::
get_extension_func(const char *, const char *) {
  return NULL;
}


////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::set_draw_buffer
//       Access: Protected
//  Description: Sets up the GLP(DrawBuffer) to render into the buffer
//               indicated by the RenderBuffer object.  This only sets
//               up the color bits; it does not affect the depth,
//               stencil, accum layers.
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
set_draw_buffer(const RenderBuffer &rb) {
  switch (rb._buffer_type & RenderBuffer::T_color) {
  case RenderBuffer::T_front:
    GLP(DrawBuffer)(GL_FRONT);
    break;

  case RenderBuffer::T_back:
    GLP(DrawBuffer)(GL_BACK);
    break;

  case RenderBuffer::T_right:
    GLP(DrawBuffer)(GL_RIGHT);
    break;

  case RenderBuffer::T_left:
    GLP(DrawBuffer)(GL_LEFT);
    break;

  case RenderBuffer::T_front_right:
    GLP(DrawBuffer)(GL_FRONT_RIGHT);
    break;

  case RenderBuffer::T_front_left:
    GLP(DrawBuffer)(GL_FRONT_LEFT);
    break;

  case RenderBuffer::T_back_right:
    GLP(DrawBuffer)(GL_BACK_RIGHT);
    break;

  case RenderBuffer::T_back_left:
    GLP(DrawBuffer)(GL_BACK_LEFT);
    break;

  default:
    GLP(DrawBuffer)(GL_FRONT_AND_BACK);
  }
  report_my_gl_errors();
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::set_read_buffer
//       Access: Protected
//  Description: Sets up the GLP(ReadBuffer) to render into the buffer
//               indicated by the RenderBuffer object.  This only sets
//               up the color bits; it does not affect the depth,
//               stencil, accum layers.
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
set_read_buffer(const RenderBuffer &rb) {
  switch (rb._buffer_type & RenderBuffer::T_color) {
  case RenderBuffer::T_front:
    GLP(ReadBuffer)(GL_FRONT);
    break;

  case RenderBuffer::T_back:
    GLP(ReadBuffer)(GL_BACK);
    break;

  case RenderBuffer::T_right:
    GLP(ReadBuffer)(GL_RIGHT);
    break;

  case RenderBuffer::T_left:
    GLP(ReadBuffer)(GL_LEFT);
    break;

  case RenderBuffer::T_front_right:
    GLP(ReadBuffer)(GL_FRONT_RIGHT);
    break;

  case RenderBuffer::T_front_left:
    GLP(ReadBuffer)(GL_FRONT_LEFT);
    break;

  case RenderBuffer::T_back_right:
    GLP(ReadBuffer)(GL_BACK_RIGHT);
    break;

  case RenderBuffer::T_back_left:
    GLP(ReadBuffer)(GL_BACK_LEFT);
    break;

  default:
    GLP(ReadBuffer)(GL_FRONT_AND_BACK);
  }
  report_my_gl_errors();
}


////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::bind_texture
//       Access: Protected
//  Description:
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
bind_texture(TextureContext *tc) {
  CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
  report_my_gl_errors();

#ifdef GSG_VERBOSE
  Texture *tex = tc->_texture;
  GLCAT.spam()
    << "glBindTexture(): " << tex->get_name() << "(" << (int)gtc->_index
    << ")" << endl;
#endif

  GLenum target = get_texture_target(tc->_texture->get_texture_type());
  if (target != GL_NONE) {
    GLP(BindTexture)(target, gtc->_index);
  }

  report_my_gl_errors();
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::specify_texture
//       Access: Protected
//  Description:
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
specify_texture(Texture *tex) {
  GLenum target = get_texture_target(tex->get_texture_type());
  if (target == GL_NONE) {
    // Unsupported target (e.g. 3-d texturing on GL 1.1).
    return;
  }

  GLP(TexParameteri)(target, GL_TEXTURE_WRAP_S,
                     get_texture_wrap_mode(tex->get_wrap_u()));
  if (target != GL_TEXTURE_1D) {
    GLP(TexParameteri)(target, GL_TEXTURE_WRAP_T,
                       get_texture_wrap_mode(tex->get_wrap_v()));
  }
  if (target == GL_TEXTURE_3D) {
    GLP(TexParameteri)(target, GL_TEXTURE_WRAP_R,
                       get_texture_wrap_mode(tex->get_wrap_w()));
  }

  Colorf border_color = tex->get_border_color();
  GLP(TexParameterfv)(target, GL_TEXTURE_BORDER_COLOR,
                      border_color.get_data());

  Texture::FilterType minfilter = tex->get_minfilter();
  Texture::FilterType magfilter = tex->get_magfilter();
  bool uses_mipmaps = tex->uses_mipmaps() && !CLP(ignore_mipmaps);

#ifndef NDEBUG
  if (CLP(force_mipmaps)) {
    minfilter = Texture::FT_linear_mipmap_linear;
    magfilter = Texture::FT_linear;
    uses_mipmaps = true;
  }
#endif

  if (_supports_generate_mipmap && 
      (auto_generate_mipmaps || !tex->might_have_ram_image())) {
    // If the hardware can automatically generate mipmaps, ask it to
    // do so now, but only if the texture requires them.
    GLP(TexParameteri)(target, GL_GENERATE_MIPMAP, uses_mipmaps);

  } else if (!tex->might_have_ram_image()) {
    // If the hardware can't automatically generate mipmaps, but it's
    // a dynamically generated texture (that is, the RAM image isn't
    // available so it didn't pass through the CPU), then we'd better
    // not try to enable mipmap filtering, since we can't generate
    // mipmaps.
    uses_mipmaps = false;
  }
 
  GLP(TexParameteri)(target, GL_TEXTURE_MIN_FILTER,
                     get_texture_filter_type(minfilter, !uses_mipmaps));
  GLP(TexParameteri)(target, GL_TEXTURE_MAG_FILTER,
                     get_texture_filter_type(magfilter, true));

  report_my_gl_errors();
}

#ifndef NDEBUG
////////////////////////////////////////////////////////////////////
//     Function: compute_gl_image_size
//  Description: Calculates how many bytes GL will expect to read for
//               a texture image, based on the number of pixels and
//               the GL format and type.  This is only used for
//               debugging.
////////////////////////////////////////////////////////////////////
static int
compute_gl_image_size(int x_size, int y_size, int z_size, 
                      int external_format, int type) {
  int num_components = 0;
  switch (external_format) {
  case GL_COLOR_INDEX:
  case GL_STENCIL_INDEX:
  case GL_DEPTH_COMPONENT:
  case GL_RED:
  case GL_GREEN:
  case GL_BLUE:
  case GL_ALPHA:
  case GL_LUMINANCE:
    num_components = 1;
    break;

  case GL_LUMINANCE_ALPHA:
    num_components = 2;
    break;

  case GL_BGR:
  case GL_RGB:
    num_components = 3;
    break;

  case GL_BGRA:
  case GL_RGBA:
    num_components = 4;
    break;
  }

  int pixel_width = 0;
  switch (type) {
  case GL_UNSIGNED_BYTE:
    pixel_width = 1 * num_components;
    break;

  case GL_UNSIGNED_SHORT:
    pixel_width = 2 * num_components;
    break;

  case GL_UNSIGNED_BYTE_3_3_2:
    nassertr(num_components == 3, 0);
    pixel_width = 1;
    break;

  case GL_FLOAT:
    pixel_width = 4 * num_components;
    break;
  }

  return x_size * y_size * z_size * pixel_width;
}
#endif  // NDEBUG

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::apply_texture_immediate
//       Access: Protected
//  Description: Sends the texture image to GL.  This can be used to
//               render a texture in immediate mode, or as part of the
//               process of creating a GL texture object.
//
//               The return value is true if successful, or false if
//               the texture has no image.
////////////////////////////////////////////////////////////////////
bool CLP(GraphicsStateGuardian)::
apply_texture_immediate(CLP(TextureContext) *gtc, Texture *tex) {
  CPTA_uchar image = tex->get_ram_image();
  if (image.is_null()) {
    return false;
  }

  int width = tex->get_x_size();
  int height = tex->get_y_size();
  int depth = tex->get_z_size();

  GLint internal_format = get_internal_image_format(tex->get_format());
  GLint external_format = get_external_image_format(tex->get_format());
  GLenum component_type = get_component_type(tex->get_component_type());

  // Ensure that the texture fits within the GL's specified limits.
  int max_dimension;
  switch (tex->get_texture_type()) {
  case Texture::TT_3d_texture:
    max_dimension = _max_3d_texture_dimension;
    break;

  case Texture::TT_cube_map:
    max_dimension = _max_cube_map_dimension;
    break;

  default:
    max_dimension = _max_texture_dimension;
  }

  if (max_dimension == 0) {
    // Guess this GL doesn't support cube mapping/3d textures.
    report_my_gl_errors();
    return false;
  }

  int texel_size = tex->get_num_components() * tex->get_component_width();

  // If it doesn't fit, we have to reduce it on-the-fly.  This is kind
  // of expensive and it doesn't look great; it would have been better
  // if the user had specified max-texture-dimension to reduce the
  // texture at load time instead.  Of course, the user doesn't always
  // know ahead of time what the hardware limits are.
  if (max_dimension > 0) {
    if (width > max_dimension) {
      int byte_chunk = texel_size;
      int stride = 1;
      int new_width = width;
      while (new_width > max_dimension) {
        stride <<= 1;
        new_width >>= 1;
      }
      GLCAT.info()
        << "Reducing width of " << tex->get_name()
        << " from " << width << " to " << new_width << "\n";
      image = reduce_image(image, byte_chunk, stride);
      width = new_width;
    }
    if (height > max_dimension) {
      int byte_chunk = width * texel_size;
      int stride = 1;
      int new_height = height;
      while (new_height > max_dimension) {
        stride <<= 1;
        new_height >>= 1;
      }
      GLCAT.info()
        << "Reducing height of " << tex->get_name()
        << " from " << height << " to " << new_height << "\n";
      image = reduce_image(image, byte_chunk, stride);
      height = new_height;
    }
    if (depth > max_dimension) {
      int byte_chunk = height * width * texel_size;
      int stride = 1;
      int new_depth = depth;
      while (new_depth > max_dimension) {
        stride <<= 1;
        new_depth >>= 1;
      }
      GLCAT.info()
        << "Reducing depth of " << tex->get_name()
        << " from " << depth << " to " << new_depth << "\n";
      image = reduce_image(image, byte_chunk, stride);
      depth = new_depth;
    }
  }

  if (!_supports_bgr) {
    // If the GL doesn't claim to support BGR, we may have to reverse
    // the component ordering of the image.
    image = fix_component_ordering(image, external_format, tex);
  }

#ifndef NDEBUG
  int wanted_size = 
    compute_gl_image_size(width, height, depth, external_format, component_type);
  nassertr(wanted_size == (int)image.size(), false);
#endif  // NDEBUG

  GLP(PixelStorei)(GL_UNPACK_ALIGNMENT, 1);

  bool uses_mipmaps = (tex->uses_mipmaps() && !CLP(ignore_mipmaps)) || CLP(force_mipmaps);

#ifndef NDEBUG
  if (CLP(force_mipmaps)) {
    uses_mipmaps = true;
  }
#endif

  bool success = true;

  if (tex->get_texture_type() == Texture::TT_cube_map) {
    // A cube map must load six different 2-d images (which are stored
    // as the six pages of the system ram image).
    if (!_supports_cube_map) {
      report_my_gl_errors();
      return false;
    }

    size_t page_size = height * width * texel_size;
    const unsigned char *image_base = image;
    
    success = success && upload_texture_image
      (gtc, uses_mipmaps, GL_TEXTURE_CUBE_MAP_POSITIVE_X,
       internal_format, width, height, depth, external_format, component_type,
       image_base);
    image_base += page_size;
    
    success = success && upload_texture_image
      (gtc, uses_mipmaps, GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
       internal_format, width, height, depth, external_format, component_type,
       image_base);
    image_base += page_size;
    
    success = success && upload_texture_image
      (gtc, uses_mipmaps, GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
       internal_format, width, height, depth, external_format, component_type,
       image_base);
    image_base += page_size;
    
    success = success && upload_texture_image
      (gtc, uses_mipmaps, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
       internal_format, width, height, depth, external_format, component_type,
       image_base);
    image_base += page_size;
    
    success = success && upload_texture_image
      (gtc, uses_mipmaps, GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
       internal_format, width, height, depth, external_format, component_type,
       image_base);
    image_base += page_size;
    
    success = success && upload_texture_image
      (gtc, uses_mipmaps, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
       internal_format, width, height, depth, external_format, component_type,
       image_base);
    image_base += page_size;
    
    nassertr((size_t)(image_base - image) == image.size(), false);

  } else {
    // Any other kind of texture can be loaded all at once.
    success = upload_texture_image
      (gtc, uses_mipmaps, get_texture_target(tex->get_texture_type()),
       internal_format, width, height, depth, external_format, component_type,
       image);
  }

  if (success) {
    gtc->_already_applied = true;
    gtc->_internal_format = internal_format;
    gtc->_width = width;
    gtc->_height = height;
    gtc->_depth = depth;

#ifndef NDEBUG
    if (uses_mipmaps && CLP(save_mipmaps)) {
      save_mipmap_images(tex);
    }
#endif

    report_my_gl_errors();
    return true;
  }

  report_my_gl_errors();
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::upload_texture_image
//       Access: Protected
//  Description: Loads a texture image, or one page of a cube map
//               image, from system RAM to texture memory.
////////////////////////////////////////////////////////////////////
bool CLP(GraphicsStateGuardian)::
upload_texture_image(CLP(TextureContext) *gtc, 
                     bool uses_mipmaps, 
                     GLenum target, GLint internal_format, 
                     int width, int height, int depth,
                     GLint external_format, GLenum component_type, 
                     const unsigned char *image) {
  if (target == GL_NONE) {
    // Unsupported target (e.g. 3-d texturing on GL 1.1).
    return false;
  }

  if (uses_mipmaps) {
#ifndef NDEBUG
    if (CLP(show_mipmaps) && target == GL_TEXTURE_2D) {
      build_phony_mipmaps(gtc->_texture);
      report_my_gl_errors();
      return true;
      
    } else 
#endif 
      if (!_supports_generate_mipmap || !auto_generate_mipmaps) {
        // We only need to build the mipmaps by hand if the GL
        // doesn't support generating them automatically.
        bool success = true;
        switch (target) {
        case GL_TEXTURE_1D:
          GLUP(Build1DMipmaps)(target, internal_format, width,
                               external_format, component_type, image);
          break;

        case GL_TEXTURE_3D:
#ifdef GLU_VERSION_1_3
          GLUP(Build3DMipmaps)(target, internal_format,
                               width, height, depth,
                               external_format, component_type, image);
#else  // GLU_VERSION_1_3
          // Prior to GLU 1.3, there was no gluBuild3DMipmaps() call.
          // Just fall through and load the texture without mipmaps.
          GLP(TexParameteri)(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
          success = false;
#endif  // GLU_VERSION_1_3
          break;

        default:
          GLUP(Build2DMipmaps)(target, internal_format,
                               width, height,
                               external_format, component_type, image);
        }

        report_my_gl_errors();
        if (success) {
          return true;
        }
      }
  }

  if (!gtc->_already_applied || 
      gtc->_internal_format != internal_format ||
      gtc->_width != width ||
      gtc->_height != height ||
      gtc->_depth != depth) {
    // We need to reload a new image.
    switch (target) {
    case GL_TEXTURE_1D:
      GLP(TexImage1D)(target, 0, internal_format,
                      width, 0,
                      external_format, component_type, image);
      break;

    case GL_TEXTURE_3D:
      if (_supports_3d_texture) {
        _glTexImage3D(target, 0, internal_format,
                      width, height, depth, 0,
                      external_format, component_type, image);
      } else {
        report_my_gl_errors();
        return false;
      }
      break;

    default:
      GLP(TexImage2D)(target, 0, internal_format,
                      width, height, 0,
                      external_format, component_type, image);
    }

  } else {
    // We can reload the image over the previous image, possibly
    // saving on texture memory fragmentation.
    switch (target) {
    case GL_TEXTURE_1D:
      GLP(TexSubImage1D)(target, 0, 0, width, 
                         external_format, component_type, image);
      break;

    case GL_TEXTURE_3D:
      if (_supports_3d_texture) {
        _glTexSubImage3D(target, 0, 0, 0, 0, width, height, depth,
                         external_format, component_type, image);
      } else {
        report_my_gl_errors();
        return false;
      }
      break;

    default:
      GLP(TexSubImage2D)(target, 0, 0, 0, width, height,
                         external_format, component_type, image);
      break;
    }
  }

  // Report the error message explicitly if the GL texture creation
  // failed.
  GLenum error_code = GLP(GetError)();
  if (error_code != GL_NO_ERROR) {
    const GLubyte *error_string = GLUP(ErrorString)(error_code);
    GLCAT.error()
      << "GL texture creation failed for " << gtc->_texture->get_name();
    if (error_string != (const GLubyte *)NULL) {
      GLCAT.error(false)
        << " : " << error_string;
    }
    GLCAT.error(false)
      << "\n";
  }

  return true;
}


////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::get_texture_target
//       Access: Protected
//  Description: Maps from the Texture's texture type symbols to
//               GL's.
////////////////////////////////////////////////////////////////////
GLenum CLP(GraphicsStateGuardian)::
get_texture_target(Texture::TextureType texture_type) const {
  switch (texture_type) {
  case Texture::TT_1d_texture:
    return GL_TEXTURE_1D;

  case Texture::TT_2d_texture:
    return GL_TEXTURE_2D;

  case Texture::TT_3d_texture:
    if (_supports_3d_texture) {
      return GL_TEXTURE_3D;
    } else {
      return GL_NONE;
    }

  case Texture::TT_cube_map:
    if (_supports_cube_map) {
      return GL_TEXTURE_CUBE_MAP;
    } else {
      return GL_NONE;
    }
  }

  GLCAT.error() << "Invalid Texture::TextureType value!\n";
  return GL_TEXTURE_2D;
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::get_texture_wrap_mode
//       Access: Protected, Static
//  Description: Maps from the Texture's internal wrap mode symbols to
//               GL's.
////////////////////////////////////////////////////////////////////
GLenum CLP(GraphicsStateGuardian)::
get_texture_wrap_mode(Texture::WrapMode wm) {
  if (CLP(ignore_clamp)) {
    return GL_REPEAT;
  }
  switch (wm) {
  case Texture::WM_clamp:
    return _edge_clamp;

  case Texture::WM_repeat:
    return GL_REPEAT;

  case Texture::WM_mirror:
    return _mirror_repeat;

  case Texture::WM_mirror_once:
    return _mirror_border_clamp;

  case Texture::WM_border_color:
    return _border_clamp;

  case Texture::WM_invalid:
    break;
  }
  GLCAT.error() << "Invalid Texture::WrapMode value!\n";
  return _edge_clamp;
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::get_texture_filter_type
//       Access: Protected, Static
//  Description: Maps from the Texture's internal filter type symbols
//               to GL's.
////////////////////////////////////////////////////////////////////
GLenum CLP(GraphicsStateGuardian)::
get_texture_filter_type(Texture::FilterType ft, bool ignore_mipmaps) {
  if (CLP(ignore_filters)) {
    return GL_NEAREST;

  } else if (ignore_mipmaps) {
    switch (ft) {
    case Texture::FT_nearest_mipmap_nearest:
    case Texture::FT_nearest:
      return GL_NEAREST;
    case Texture::FT_linear:
    case Texture::FT_linear_mipmap_nearest:
    case Texture::FT_nearest_mipmap_linear:
    case Texture::FT_linear_mipmap_linear:
      return GL_LINEAR;
    case Texture::FT_invalid:
      break;
    }

  } else {
    switch (ft) {
    case Texture::FT_nearest:
      return GL_NEAREST;
    case Texture::FT_linear:
      return GL_LINEAR;
    case Texture::FT_nearest_mipmap_nearest:
      return GL_NEAREST_MIPMAP_NEAREST;
    case Texture::FT_linear_mipmap_nearest:
      return GL_LINEAR_MIPMAP_NEAREST;
    case Texture::FT_nearest_mipmap_linear:
      return GL_NEAREST_MIPMAP_LINEAR;
    case Texture::FT_linear_mipmap_linear:
      return GL_LINEAR_MIPMAP_LINEAR;
    case Texture::FT_invalid:
      break;
    }
  }
  GLCAT.error() << "Invalid Texture::FilterType value!\n";
  return GL_NEAREST;
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::get_component_type
//       Access: Protected, Static
//  Description: Maps from the Texture's internal ComponentType symbols
//               to GL's.
////////////////////////////////////////////////////////////////////
GLenum CLP(GraphicsStateGuardian)::
get_component_type(Texture::ComponentType component_type) {
  switch (component_type) {
  case Texture::T_unsigned_byte:
    return GL_UNSIGNED_BYTE;
  case Texture::T_unsigned_short:
    return GL_UNSIGNED_SHORT;
  case Texture::T_float:
    return GL_FLOAT;

  default:
    GLCAT.error() << "Invalid Texture::Type value!\n";
    return GL_UNSIGNED_BYTE;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::get_external_image_format
//       Access: Protected
//  Description: Maps from the Texture's Format symbols
//               to GL's.
////////////////////////////////////////////////////////////////////
GLint CLP(GraphicsStateGuardian)::
get_external_image_format(Texture::Format format) const {
  switch (format) {
  case Texture::F_color_index:
    return GL_COLOR_INDEX;
  case Texture::F_stencil_index:
    return GL_STENCIL_INDEX;
  case Texture::F_depth_component:
    return GL_DEPTH_COMPONENT;
  case Texture::F_red:
    return GL_RED;
  case Texture::F_green:
    return GL_GREEN;
  case Texture::F_blue:
    return GL_BLUE;
  case Texture::F_alpha:
    return GL_ALPHA;
  case Texture::F_rgb:
  case Texture::F_rgb5:
  case Texture::F_rgb8:
  case Texture::F_rgb12:
  case Texture::F_rgb332:
    return _supports_bgr ? GL_BGR : GL_RGB;
  case Texture::F_rgba:
  case Texture::F_rgbm:
  case Texture::F_rgba4:
  case Texture::F_rgba5:
  case Texture::F_rgba8:
  case Texture::F_rgba12:
    return _supports_bgr ? GL_BGRA : GL_RGBA;
  case Texture::F_luminance:
    return GL_LUMINANCE;
  case Texture::F_luminance_alphamask:
  case Texture::F_luminance_alpha:
    return GL_LUMINANCE_ALPHA;
  }
  GLCAT.error()
    << "Invalid Texture::Format value in get_external_image_format(): "
    << (int)format << "\n";
  return GL_RGB;
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::get_internal_image_format
//       Access: Protected, Static
//  Description: Maps from the Texture's Format symbols to a
//               suitable internal format for GL textures.
////////////////////////////////////////////////////////////////////
GLint CLP(GraphicsStateGuardian)::
get_internal_image_format(Texture::Format format) {
  switch (format) {
  case Texture::F_rgba:
  case Texture::F_rgbm:
    return GL_RGBA;
  case Texture::F_rgba4:
    return GL_RGBA4;
  case Texture::F_rgba8:
    return GL_RGBA8;
  case Texture::F_rgba12:
    return GL_RGBA12;

  case Texture::F_rgb:
    return GL_RGB;
  case Texture::F_rgb5:
    return GL_RGB5;
  case Texture::F_rgba5:
    return GL_RGB5_A1;
  case Texture::F_rgb8:
    return GL_RGB8;
  case Texture::F_rgb12:
    return GL_RGB12;
  case Texture::F_rgb332:
    return GL_R3_G3_B2;

  case Texture::F_alpha:
    return GL_ALPHA;

  case Texture::F_red:
  case Texture::F_green:
  case Texture::F_blue:
  case Texture::F_luminance:
    return GL_LUMINANCE;
  case Texture::F_luminance_alpha:
  case Texture::F_luminance_alphamask:
    return GL_LUMINANCE_ALPHA;

  default:
    GLCAT.error()
      << "Invalid image format in get_internal_image_format(): "
      << (int)format << "\n";
    return GL_RGB;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::get_texture_apply_mode_type
//       Access: Protected, Static
//  Description: Maps from the texture stage's mode types
//               to the corresponding OpenGL ids
////////////////////////////////////////////////////////////////////
GLint CLP(GraphicsStateGuardian)::
get_texture_apply_mode_type(TextureStage::Mode am) {
  switch (am) {
  case TextureStage::M_modulate: return GL_MODULATE;
  case TextureStage::M_decal: return GL_DECAL;
  case TextureStage::M_blend: return GL_BLEND;
  case TextureStage::M_replace: return GL_REPLACE;
  case TextureStage::M_add: return GL_ADD;
  case TextureStage::M_combine: return GL_COMBINE;
  case TextureStage::M_blend_color_scale: return GL_BLEND;
  }

  GLCAT.error()
    << "Invalid TextureStage::Mode value" << endl;
  return GL_MODULATE;
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::get_texture_combine_type
//       Access: Protected, Static
//  Description: Maps from the texture stage's CombineMode types
//               to the corresponding OpenGL ids
////////////////////////////////////////////////////////////////////
GLint CLP(GraphicsStateGuardian)::
get_texture_combine_type(TextureStage::CombineMode cm) {
  switch (cm) {
  case TextureStage::CM_undefined: // fall through
  case TextureStage::CM_replace: return GL_REPLACE;
  case TextureStage::CM_modulate: return GL_MODULATE;
  case TextureStage::CM_add: return GL_ADD;
  case TextureStage::CM_add_signed: return GL_ADD_SIGNED;
  case TextureStage::CM_interpolate: return GL_INTERPOLATE;
  case TextureStage::CM_subtract: return GL_SUBTRACT;
  case TextureStage::CM_dot3_rgb: return GL_DOT3_RGB;
  case TextureStage::CM_dot3_rgba: return GL_DOT3_RGBA;
  }
  GLCAT.error()
    << "Invalid TextureStage::CombineMode value" << endl;
  return GL_REPLACE;
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::get_texture_src_type
//       Access: Protected, Static
//  Description: Maps from the texture stage's CombineSource types
//               to the corresponding OpenGL ids
////////////////////////////////////////////////////////////////////
GLint CLP(GraphicsStateGuardian)::
get_texture_src_type(TextureStage::CombineSource cs) {
  switch (cs) {
  case TextureStage::CS_undefined: // fall through
  case TextureStage::CS_texture: return GL_TEXTURE;
  case TextureStage::CS_constant: return GL_CONSTANT;
  case TextureStage::CS_primary_color: return GL_PRIMARY_COLOR;
  case TextureStage::CS_previous: return GL_PREVIOUS;
  case TextureStage::CS_constant_color_scale: return GL_CONSTANT;
  }

  GLCAT.error()
    << "Invalid TextureStage::CombineSource value" << endl;
  return GL_TEXTURE;
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::get_texture_operand_type
//       Access: Protected, Static
//  Description: Maps from the texture stage's CombineOperand types
//               to the corresponding OpenGL ids
////////////////////////////////////////////////////////////////////
GLint CLP(GraphicsStateGuardian)::
get_texture_operand_type(TextureStage::CombineOperand co) {
  switch (co) {
  case TextureStage::CO_undefined: // fall through
  case TextureStage::CO_src_alpha: return GL_SRC_ALPHA;
  case TextureStage::CO_one_minus_src_alpha: return GL_ONE_MINUS_SRC_ALPHA;
  case TextureStage::CO_src_color: return GL_SRC_COLOR;
  case TextureStage::CO_one_minus_src_color: return GL_ONE_MINUS_SRC_COLOR;
  }

  GLCAT.error()
    << "Invalid TextureStage::CombineOperand value" << endl;
  return GL_SRC_COLOR;
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::get_fog_mode_type
//       Access: Protected, Static
//  Description: Maps from the fog types to gl version
////////////////////////////////////////////////////////////////////
GLenum CLP(GraphicsStateGuardian)::
get_fog_mode_type(Fog::Mode m) {
  switch(m) {
  case Fog::M_linear: return GL_LINEAR;
  case Fog::M_exponential: return GL_EXP;
  case Fog::M_exponential_squared: return GL_EXP2;
    /*
      case Fog::M_spline: return GL_FOG_FUNC_SGIS;
    */

  default:
    GLCAT.error() << "Invalid Fog::Mode value" << endl;
    return GL_EXP;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::get_blend_equation_type
//       Access: Protected, Static
//  Description: Maps from ColorBlendAttrib::Mode to glBlendEquation
//               value.
////////////////////////////////////////////////////////////////////
GLenum CLP(GraphicsStateGuardian)::
get_blend_equation_type(ColorBlendAttrib::Mode mode) {
  switch (mode) {
  case ColorBlendAttrib::M_none:
  case ColorBlendAttrib::M_add:
    return GL_FUNC_ADD;
    
  case ColorBlendAttrib::M_subtract:
    return GL_FUNC_SUBTRACT;
    
  case ColorBlendAttrib::M_inv_subtract:
    return GL_FUNC_REVERSE_SUBTRACT;
    
  case ColorBlendAttrib::M_min:
    return GL_MIN;
    
  case ColorBlendAttrib::M_max:
    return GL_MAX;
  }    

  GLCAT.error()
    << "Unknown color blend mode " << (int)mode << endl;
  return GL_FUNC_ADD;
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::get_blend_func
//       Access: Protected, Static
//  Description: Maps from ColorBlendAttrib::Operand to glBlendFunc
//               value.
////////////////////////////////////////////////////////////////////
GLenum CLP(GraphicsStateGuardian)::
get_blend_func(ColorBlendAttrib::Operand operand) {
  switch (operand) {
  case ColorBlendAttrib::O_zero:
    return GL_ZERO;

  case ColorBlendAttrib::O_one:
    return GL_ONE;

  case ColorBlendAttrib::O_incoming_color:
    return GL_SRC_COLOR;

  case ColorBlendAttrib::O_one_minus_incoming_color:
    return GL_ONE_MINUS_SRC_COLOR;

  case ColorBlendAttrib::O_fbuffer_color:
    return GL_DST_COLOR;

  case ColorBlendAttrib::O_one_minus_fbuffer_color:
    return GL_ONE_MINUS_DST_COLOR;

  case ColorBlendAttrib::O_incoming_alpha:
    return GL_SRC_ALPHA;

  case ColorBlendAttrib::O_one_minus_incoming_alpha:
    return GL_ONE_MINUS_SRC_ALPHA;

  case ColorBlendAttrib::O_fbuffer_alpha:
    return GL_DST_ALPHA;

  case ColorBlendAttrib::O_one_minus_fbuffer_alpha:
    return GL_ONE_MINUS_DST_ALPHA;

  case ColorBlendAttrib::O_constant_color:
  case ColorBlendAttrib::O_color_scale:
    return GL_CONSTANT_COLOR;

  case ColorBlendAttrib::O_one_minus_constant_color:
  case ColorBlendAttrib::O_one_minus_color_scale:
    return GL_ONE_MINUS_CONSTANT_COLOR;

  case ColorBlendAttrib::O_constant_alpha:
  case ColorBlendAttrib::O_alpha_scale:
    return GL_CONSTANT_ALPHA;

  case ColorBlendAttrib::O_one_minus_constant_alpha:
  case ColorBlendAttrib::O_one_minus_alpha_scale:
    return GL_ONE_MINUS_CONSTANT_ALPHA;

  case ColorBlendAttrib::O_incoming_color_saturate:
    return GL_SRC_ALPHA_SATURATE;
  }

  GLCAT.error()
    << "Unknown color blend operand " << (int)operand << endl;
  return GL_ZERO;
}


////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::print_gfx_visual
//       Access: Public
//  Description: Prints a description of the current visual selected.
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
print_gfx_visual() {
  GLint i;
  GLboolean j;
  cout << "Graphics Visual Info (# bits of each):" << endl;

  cout << "RGBA: ";
  GLP(GetIntegerv)( GL_RED_BITS, &i ); cout << i << " ";
  GLP(GetIntegerv)( GL_GREEN_BITS, &i ); cout << i << " ";
  GLP(GetIntegerv)( GL_BLUE_BITS, &i ); cout << i << " ";
  GLP(GetIntegerv)( GL_ALPHA_BITS, &i ); cout << i << endl;

  cout << "Accum RGBA: ";
  GLP(GetIntegerv)( GL_ACCUM_RED_BITS, &i ); cout << i << " ";
  GLP(GetIntegerv)( GL_ACCUM_GREEN_BITS, &i ); cout << i << " ";
  GLP(GetIntegerv)( GL_ACCUM_BLUE_BITS, &i ); cout << i << " ";
  GLP(GetIntegerv)( GL_ACCUM_ALPHA_BITS, &i ); cout << i << endl;

  GLP(GetIntegerv)( GL_INDEX_BITS, &i ); cout << "Color Index: " << i << endl;

  GLP(GetIntegerv)( GL_DEPTH_BITS, &i ); cout << "Depth: " << i << endl;
  GLP(GetIntegerv)( GL_ALPHA_BITS, &i ); cout << "Alpha: " << i << endl;
  GLP(GetIntegerv)( GL_STENCIL_BITS, &i ); cout << "Stencil: " << i << endl;

  GLP(GetBooleanv)( GL_DOUBLEBUFFER, &j ); cout << "DoubleBuffer? "
                                             << (int)j << endl;

  GLP(GetBooleanv)( GL_STEREO, &j ); cout << "Stereo? " << (int)j << endl;

  if (_supports_multisample) {
    GLP(GetBooleanv)( GL_MULTISAMPLE, &j ); cout << "Multisample? " << (int)j << endl;
    GLP(GetIntegerv)( GL_SAMPLES, &i ); cout << "Samples: " << i << endl;
  }

  GLP(GetBooleanv)( GL_BLEND, &j ); cout << "Blend? " << (int)j << endl;
  GLP(GetBooleanv)( GL_POINT_SMOOTH, &j ); cout << "Point Smooth? "
                                             << (int)j << endl;
  GLP(GetBooleanv)( GL_LINE_SMOOTH, &j ); cout << "Line Smooth? "
                                            << (int)j << endl;

  GLP(GetIntegerv)( GL_AUX_BUFFERS, &i ); cout << "Aux Buffers: " << i << endl;
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::issue_scaled_color
//       Access: Public
//  Description: Transform the color by the current color matrix, and
//               calls the appropriate glColor function.
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
issue_scaled_color(const Colorf &color) const {
  Colorf transformed
    (color[0] * _current_color_scale[0],
     color[1] * _current_color_scale[1],
     color[2] * _current_color_scale[2],
     color[3] * _current_color_scale[3]);

  GLP(Color4fv)(transformed.get_data());
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::slot_new_light
//       Access: Protected, Virtual
//  Description: This will be called by the base class before a
//               particular light id will be used for the first time.
//               It is intended to allow the derived class to reserve
//               any additional resources, if required, for the new
//               light; and also to indicate whether the hardware
//               supports this many simultaneous lights.
//
//               The return value should be true if the additional
//               light is supported, or false if it is not.
////////////////////////////////////////////////////////////////////
bool CLP(GraphicsStateGuardian)::
slot_new_light(int light_id) {
  return (light_id < _max_lights);
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::enable_lighting
//       Access: Protected, Virtual
//  Description: Intended to be overridden by a derived class to
//               enable or disable the use of lighting overall.  This
//               is called by issue_light() according to whether any
//               lights are in use or not.
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
enable_lighting(bool enable) {
  if (enable) {
    GLP(Enable)(GL_LIGHTING);
  } else {
    GLP(Disable)(GL_LIGHTING);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::set_ambient_light
//       Access: Protected, Virtual
//  Description: Intended to be overridden by a derived class to
//               indicate the color of the ambient light that should
//               be in effect.  This is called by issue_light() after
//               all other lights have been enabled or disabled.
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
set_ambient_light(const Colorf &color) {
  GLP(LightModelfv)(GL_LIGHT_MODEL_AMBIENT, color.get_data());
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::enable_light
//       Access: Protected, Virtual
//  Description: Intended to be overridden by a derived class to
//               enable the indicated light id.  A specific Light will
//               already have been bound to this id via bind_light().
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
enable_light(int light_id, bool enable) {
  if (enable) {
    GLP(Enable)(get_light_id(light_id));
  } else {
    GLP(Disable)(get_light_id(light_id));
  }
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::begin_bind_lights
//       Access: Protected, Virtual
//  Description: Called immediately before bind_light() is called,
//               this is intended to provide the derived class a hook
//               in which to set up some state (like transform) that
//               might apply to several lights.
//
//               The sequence is: begin_bind_lights() will be called,
//               then one or more bind_light() calls, then
//               end_bind_lights().
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
begin_bind_lights() {
  // We need to temporarily load a new matrix so we can define the
  // light in a known coordinate system.  We pick the transform of the
  // root.  (Alternatively, we could leave the current transform where
  // it is and compute the light position relative to that transform
  // instead of relative to the root, by composing with the matrix
  // computed by _transform->invert_compose(render_transform).  But I
  // think loading a completely new matrix is simpler.)
  GLP(MatrixMode)(GL_MODELVIEW);
  GLP(PushMatrix)();
  GLP(LoadMatrixf)(_scene_setup->get_render_transform()->get_mat().get_data());
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::end_bind_lights
//       Access: Protected, Virtual
//  Description: Called after before bind_light() has been called one
//               or more times (but before any geometry is issued or
//               additional state is changed), this is intended to
//               clean up any temporary changes to the state that may
//               have been made by begin_bind_lights().
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
end_bind_lights() {
  GLP(MatrixMode)(GL_MODELVIEW);
  GLP(PopMatrix)();
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::slot_new_clip_plane
//       Access: Protected, Virtual
//  Description: This will be called by the base class before a
//               particular clip plane id will be used for the first
//               time.  It is intended to allow the derived class to
//               reserve any additional resources, if required, for
//               the new clip plane; and also to indicate whether the
//               hardware supports this many simultaneous clipping
//               planes.
//
//               The return value should be true if the additional
//               plane is supported, or false if it is not.
////////////////////////////////////////////////////////////////////
bool CLP(GraphicsStateGuardian)::
slot_new_clip_plane(int plane_id) {
  return (plane_id < _max_clip_planes);
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::enable_clip_plane
//       Access: Protected, Virtual
//  Description: Intended to be overridden by a derived class to
//               enable the indicated clip_plane id.  A specific
//               PlaneNode will already have been bound to this id via
//               bind_clip_plane().
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
enable_clip_plane(int plane_id, bool enable) {
  if (enable) {
    GLP(Enable)(get_clip_plane_id(plane_id));
  } else {
    GLP(Disable)(get_clip_plane_id(plane_id));
  }
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::begin_bind_clip_planes
//       Access: Protected, Virtual
//  Description: Called immediately before bind_clip_plane() is called,
//               this is intended to provide the derived class a hook
//               in which to set up some state (like transform) that
//               might apply to several clip_planes.
//
//               The sequence is: begin_bind_clip_planes() will be called,
//               then one or more bind_clip_plane() calls, then
//               end_bind_clip_planes().
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
begin_bind_clip_planes() {
  // We need to temporarily load a new matrix so we can define the
  // clip_plane in a known coordinate system.  We pick the transform of the
  // root.  (Alternatively, we could leave the current transform where
  // it is and compute the clip_plane position relative to that transform
  // instead of relative to the root, by composing with the matrix
  // computed by _transform->invert_compose(render_transform).  But I
  // think loading a completely new matrix is simpler.)
  GLP(MatrixMode)(GL_MODELVIEW);
  GLP(PushMatrix)();
  GLP(LoadMatrixf)(_scene_setup->get_render_transform()->get_mat().get_data());
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::bind_clip_plane
//       Access: Protected, Virtual
//  Description: Called the first time a particular clip_plane has been
//               bound to a given id within a frame, this should set
//               up the associated hardware clip_plane with the clip_plane's
//               properties.
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
bind_clip_plane(PlaneNode *plane, int plane_id) {
  GLenum id = get_clip_plane_id(plane_id);

  NodePath plane_np(plane);
  const LMatrix4f &plane_mat = plane_np.get_mat(_scene_setup->get_scene_root());
  Planef xformed_plane = plane->get_plane() * plane_mat;

  Planed double_plane(LCAST(double, xformed_plane));
  GLP(ClipPlane)(id, double_plane.get_data());

  report_my_gl_errors();
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::end_bind_clip_planes
//       Access: Protected, Virtual
//  Description: Called after before bind_clip_plane() has been called one
//               or more times (but before any geometry is issued or
//               additional state is changed), this is intended to
//               clean up any temporary changes to the state that may
//               have been made by begin_bind_clip_planes().
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
end_bind_clip_planes() {
  GLP(MatrixMode)(GL_MODELVIEW);
  GLP(PopMatrix)();
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::set_blend_mode
//       Access: Protected, Virtual
//  Description: Called after any of the things that might change
//               blending state have changed, this function is
//               responsible for setting the appropriate color
//               blending mode based on the current properties.
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
set_blend_mode() {
  // If color_write_mode is off, we disable writing to the color using
  // blending.  This case is only used if we can't use GLP(ColorMask) to
  // disable the color writing for some reason (usually a driver
  // problem).
  if (_color_write_mode == ColorWriteAttrib::M_off) {
    enable_multisample_alpha_one(false);
    enable_multisample_alpha_mask(false);
    enable_blend(true);
    _glBlendEquation(GL_FUNC_ADD);
    GLP(BlendFunc)(GL_ZERO, GL_ONE);
   return;
  }

  // Is there a color blend set?
  if (_color_blend_mode != ColorBlendAttrib::M_none) {
    enable_multisample_alpha_one(false);
    enable_multisample_alpha_mask(false);
    enable_blend(true);
    _glBlendEquation(get_blend_equation_type(_color_blend_mode));
    GLP(BlendFunc)(get_blend_func(_color_blend->get_operand_a()),
                   get_blend_func(_color_blend->get_operand_b()));

    if (_color_blend_involves_color_scale) {
      // Apply the current color scale to the blend mode.
      _glBlendColor(_current_color_scale[0], _current_color_scale[1], 
                    _current_color_scale[2], _current_color_scale[3]);
      
    } else {
      Colorf c = _color_blend->get_color();
      _glBlendColor(c[0], c[1], c[2], c[3]);
    }
    return;
  }

  // No color blend; is there a transparency set?
  switch (_transparency_mode) {
  case TransparencyAttrib::M_none:
  case TransparencyAttrib::M_binary:
    break;
    
  case TransparencyAttrib::M_alpha:
  case TransparencyAttrib::M_dual:
    enable_multisample_alpha_one(false);
    enable_multisample_alpha_mask(false);
    enable_blend(true);
    _glBlendEquation(GL_FUNC_ADD);
    GLP(BlendFunc)(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    return;
    
  case TransparencyAttrib::M_multisample:
    // We need to enable *both* of these in M_multisample case.
    enable_multisample_alpha_one(true);
    enable_multisample_alpha_mask(true);
    enable_blend(false);
    return;
    
  case TransparencyAttrib::M_multisample_mask:
    enable_multisample_alpha_one(false);
    enable_multisample_alpha_mask(true);
    enable_blend(false);
    return;
    
  default:
    GLCAT.error()
      << "invalid transparency mode " << (int)_transparency_mode << endl;
    break;
  }

  if (_line_smooth_enabled || _point_smooth_enabled) {
    // If we have either of these turned on, we also need to have
    // blend mode enabled in order to see it.
    enable_multisample_alpha_one(false);
    enable_multisample_alpha_mask(false);
    enable_blend(true);
    _glBlendEquation(GL_FUNC_ADD);
    GLP(BlendFunc)(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    return;
  }

  // For best polygon smoothing, we need:
  // (1) a frame buffer that supports alpha
  // (2) sort polygons front-to-back
  // (3) glBlendFunc(GL_SRC_ALPHA_SATURATE, GL_ONE);
  //
  // Since these modes have other implications for the application, we
  // don't attempt to do this by default.  If you really want good
  // polygon smoothing (and you don't have multisample support), do
  // all this yourself.

  // Nothing's set, so disable blending.
  enable_multisample_alpha_one(false);
  enable_multisample_alpha_mask(false);
  enable_blend(false);
}

////////////////////////////////////////////////////////////////////
//     Function: GLGraphicsStateGuardian::finish_modify_state
//       Access: Protected, Virtual
//  Description: Called after the GSG state has been modified via
//               modify_state() or set_state(), this hook is provided
//               for the derived class to do any further state setup
//               work.
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
finish_modify_state() {
  GraphicsStateGuardian::finish_modify_state();

  // Apply the texture, if it needs to be reapplied.
  if (_texture_stale) {
    _texture_stale = false;
    do_issue_texture();
  }

  // If one of the previously-loaded TexGen modes modified the texture
  // matrix, then if either state changed, we have to change both of
  // them now.
  if (_tex_gen_modifies_mat &&
      (_needs_tex_mat || _needs_tex_gen)) {
    _needs_tex_mat = true;
    _needs_tex_gen = true;
  }

  // Apply the texture matrix, if needed.
  if (_needs_tex_mat) {
    _needs_tex_mat = false;

    int num_stages = _current_texture->get_num_on_stages();
    nassertv(num_stages <= _max_texture_stages);
    
    for (int i = 0; i < num_stages; i++) {
      TextureStage *stage = _current_texture->get_on_stage(i);
      _glActiveTexture(GL_TEXTURE0 + i);
      
      GLP(MatrixMode)(GL_TEXTURE);
      if (_current_tex_mat->has_stage(stage)) {
        GLP(LoadMatrixf)(_current_tex_mat->get_mat(stage).get_data());
      } else {
        GLP(LoadIdentity)();

        // For some reason, the glLoadIdentity() call doesn't work on
        // my Dell laptop's IBM OpenGL driver, when used in
        // conjunction with glTexGen(), below.  But explicitly loading
        // an identity matrix does work.  But this buggy-driver
        // workaround might have other performance implications, so I
        // leave it out.
        //GLP(LoadMatrixf)(LMatrix4f::ident_mat().get_data());
      }
    }
    report_my_gl_errors();
  }

  if (_needs_tex_gen) {
    _needs_tex_gen = false;
    bool force_normal = false;

    int num_stages = _current_texture->get_num_on_stages();
    nassertv(num_stages <= _max_texture_stages);
    
    // These are passed in for the four OBJECT_PLANE or EYE_PLANE
    // values; they effectively define an identity matrix that maps
    // the spatial coordinates one-for-one to UV's.  If you want a
    // mapping other than identity, use a TexMatrixAttrib (or a
    // TexProjectorEffect).
    static const float s_data[4] = { 1, 0, 0, 0 };
    static const float t_data[4] = { 0, 1, 0, 0 };
    static const float r_data[4] = { 0, 0, 1, 0 };
    static const float q_data[4] = { 0, 0, 0, 1 };

    _tex_gen_modifies_mat = false;
    
    for (int i = 0; i < num_stages; i++) {
      TextureStage *stage = _current_texture->get_on_stage(i);
      _glActiveTexture(GL_TEXTURE0 + i);

      TexGenAttrib::Mode mode = _current_tex_gen->get_mode(stage);
      switch (mode) {
      case TexGenAttrib::M_off:
        GLP(Disable)(GL_TEXTURE_GEN_S);
        GLP(Disable)(GL_TEXTURE_GEN_T);
        GLP(Disable)(GL_TEXTURE_GEN_R);
        GLP(Disable)(GL_TEXTURE_GEN_Q);
        break;
        
      case TexGenAttrib::M_eye_sphere_map:
        GLP(TexGeni)(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
        GLP(TexGeni)(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
        GLP(Enable)(GL_TEXTURE_GEN_S);
        GLP(Enable)(GL_TEXTURE_GEN_T);
        GLP(Disable)(GL_TEXTURE_GEN_R);
        GLP(Disable)(GL_TEXTURE_GEN_Q);
        force_normal = true;
        break;
        
      case TexGenAttrib::M_eye_cube_map:
      case TexGenAttrib::M_world_cube_map:
        if (_supports_cube_map) {
          if (mode != TexGenAttrib::M_eye_cube_map) {
            // We dynamically transform normals from eye space to
            // world space by applying the appropriate rotation
            // transform to the current texture matrix.  Although it's
            // tempting to try, we can't safely convert to object
            // space, since this method doesn't get called with each
            // different object.
            CPT(TransformState) transform = _scene_setup->get_render_transform();
            transform = transform->invert_compose(TransformState::make_identity());
            LMatrix4f mat = transform->get_mat();
            mat.set_row(3, LVecBase3f(0.0f, 0.0f, 0.0f));
            GLP(MatrixMode)(GL_TEXTURE);
            GLP(MultMatrixf)(mat.get_data());

            // Now we need to reset the texture matrix next time
            // around to undo this.
            _tex_gen_modifies_mat = true;
          }

          GLP(TexGeni)(GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
          GLP(TexGeni)(GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
          GLP(TexGeni)(GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
          GLP(Enable)(GL_TEXTURE_GEN_S);
          GLP(Enable)(GL_TEXTURE_GEN_T);
          GLP(Enable)(GL_TEXTURE_GEN_R);
          GLP(Disable)(GL_TEXTURE_GEN_Q);
          force_normal = true;
        } else {
          GLP(Disable)(GL_TEXTURE_GEN_S);
          GLP(Disable)(GL_TEXTURE_GEN_T);
          GLP(Disable)(GL_TEXTURE_GEN_R);
          GLP(Disable)(GL_TEXTURE_GEN_Q);
        }
        break;
        
      case TexGenAttrib::M_eye_normal:
      case TexGenAttrib::M_world_normal:
        if (_supports_cube_map) {
          if (mode != TexGenAttrib::M_eye_normal) {
            // We dynamically transform normals from eye space to
            // world space by applying the appropriate rotation
            // transform to the current texture matrix.  Although it's
            // tempting to try, we can't safely convert to object
            // space, since this method doesn't get called with each
            // different object.
            CPT(TransformState) transform = _scene_setup->get_render_transform();
            transform = transform->invert_compose(TransformState::make_identity());
            LMatrix4f mat = transform->get_mat();
            mat.set_row(3, LVecBase3f(0.0f, 0.0f, 0.0f));
            GLP(MatrixMode)(GL_TEXTURE);
            GLP(MultMatrixf)(mat.get_data());

            // Now we need to reset the texture matrix next time
            // around to undo this.
            _tex_gen_modifies_mat = true;
          }

          GLP(TexGeni)(GL_S, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP);
          GLP(TexGeni)(GL_T, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP);
          GLP(TexGeni)(GL_R, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP);
          GLP(Enable)(GL_TEXTURE_GEN_S);
          GLP(Enable)(GL_TEXTURE_GEN_T);
          GLP(Enable)(GL_TEXTURE_GEN_R);
          GLP(Disable)(GL_TEXTURE_GEN_Q);
          force_normal = true;
        } else {
          GLP(Disable)(GL_TEXTURE_GEN_S);
          GLP(Disable)(GL_TEXTURE_GEN_T);
          GLP(Disable)(GL_TEXTURE_GEN_R);
          GLP(Disable)(GL_TEXTURE_GEN_Q);
        }
        break;

      case TexGenAttrib::M_object_position:
        GLP(TexGeni)(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
        GLP(TexGeni)(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
        GLP(TexGeni)(GL_R, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
        GLP(TexGeni)(GL_Q, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
        
        GLP(TexGenfv)(GL_S, GL_OBJECT_PLANE, s_data);
        GLP(TexGenfv)(GL_T, GL_OBJECT_PLANE, t_data);
        GLP(TexGenfv)(GL_R, GL_OBJECT_PLANE, r_data);
        GLP(TexGenfv)(GL_Q, GL_OBJECT_PLANE, q_data);
        
        GLP(Enable)(GL_TEXTURE_GEN_S);
        GLP(Enable)(GL_TEXTURE_GEN_T);
        GLP(Enable)(GL_TEXTURE_GEN_R);
        GLP(Enable)(GL_TEXTURE_GEN_Q);
        break;

      case TexGenAttrib::M_eye_position:
        // To represent eye position correctly, we need to temporarily
        // load the coordinate-system transform.
        GLP(MatrixMode)(GL_MODELVIEW);
        GLP(PushMatrix)();
        GLP(LoadMatrixf)(_scene_setup->get_cs_transform()->get_mat().get_data());

        GLP(TexGeni)(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
        GLP(TexGeni)(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
        GLP(TexGeni)(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
        GLP(TexGeni)(GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
        
        GLP(TexGenfv)(GL_S, GL_EYE_PLANE, s_data);
        GLP(TexGenfv)(GL_T, GL_EYE_PLANE, t_data);
        GLP(TexGenfv)(GL_R, GL_EYE_PLANE, r_data);
        GLP(TexGenfv)(GL_Q, GL_EYE_PLANE, q_data);
        
        GLP(Enable)(GL_TEXTURE_GEN_S);
        GLP(Enable)(GL_TEXTURE_GEN_T);
        GLP(Enable)(GL_TEXTURE_GEN_R);
        GLP(Enable)(GL_TEXTURE_GEN_Q);

        GLP(MatrixMode)(GL_MODELVIEW);
        GLP(PopMatrix)();
        break;

      case TexGenAttrib::M_world_position:
        // We achieve world position coordinates by using the eye
        // position mode, and loading the transform of the root
        // node--thus putting the "eye" at the root.
        {
          GLP(MatrixMode)(GL_MODELVIEW);
          GLP(PushMatrix)();
          CPT(TransformState) root_transform = _scene_setup->get_render_transform();
          GLP(LoadMatrixf)(root_transform->get_mat().get_data());
          GLP(TexGeni)(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
          GLP(TexGeni)(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
          GLP(TexGeni)(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
          GLP(TexGeni)(GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
        
          GLP(TexGenfv)(GL_S, GL_EYE_PLANE, s_data);
          GLP(TexGenfv)(GL_T, GL_EYE_PLANE, t_data);
          GLP(TexGenfv)(GL_R, GL_EYE_PLANE, r_data);
          GLP(TexGenfv)(GL_Q, GL_EYE_PLANE, q_data);
          
          GLP(Enable)(GL_TEXTURE_GEN_S);
          GLP(Enable)(GL_TEXTURE_GEN_T);
          GLP(Enable)(GL_TEXTURE_GEN_R);
          GLP(Enable)(GL_TEXTURE_GEN_Q);
          
          GLP(MatrixMode)(GL_MODELVIEW);
          GLP(PopMatrix)();
        }
        break;
      }
    }

    // Certain texgen modes (sphere_map, cube_map) require forcing the
    // normal to be sent to the GL while the texgen mode is in effect.
    if (force_normal != _texgen_forced_normal) {
      if (force_normal) {
        force_normals();
      } else  {
        undo_force_normals();
      }
      _texgen_forced_normal = force_normal;
    }

    report_my_gl_errors();
  }
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::free_pointers
//       Access: Protected, Virtual
//  Description: Frees some memory that was explicitly allocated
//               within the glgsg.
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
free_pointers() {
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::get_untextured_state
//       Access: Protected, Static
//  Description: Returns a RenderState object that represents
//               texturing off.
////////////////////////////////////////////////////////////////////
CPT(RenderState) CLP(GraphicsStateGuardian)::
get_untextured_state() {
  static CPT(RenderState) state;
  if (state == (RenderState *)NULL) {
    state = RenderState::make(TextureAttrib::make_off());
  }
  return state;
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::do_auto_rescale_normal
//       Access: Protected
//  Description: Issues the appropriate GL commands to either rescale
//               or normalize the normals according to the current
//               transform.
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
do_auto_rescale_normal() {
  if (_transform->has_uniform_scale()) {
    if (IS_NEARLY_EQUAL(_transform->get_uniform_scale(), 1.0f)) {
      // If there's no scale at all, don't do anything.
      GLP(Disable)(GL_NORMALIZE);
      if (_supports_rescale_normal) {
        GLP(Disable)(GL_RESCALE_NORMAL);
      }
      
    } else {
      // There's a uniform scale; use the rescale feature if available.
      if (_supports_rescale_normal) {
        GLP(Enable)(GL_RESCALE_NORMAL);
        GLP(Disable)(GL_NORMALIZE);
      } else {
        GLP(Enable)(GL_NORMALIZE);
      }
    }

  } else {
    // If there's a non-uniform scale, normalize everything.
    GLP(Enable)(GL_NORMALIZE);
    if (_supports_rescale_normal) {
      GLP(Disable)(GL_RESCALE_NORMAL);
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::do_issue_texture
//       Access: Protected
//  Description: This is called by finish_modify_state() when the
//               texture state has changed.
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
do_issue_texture() {
  DO_PSTATS_STUFF(_texture_state_pcollector.add_level(1));

  CPT(TextureAttrib) new_texture = _pending_texture->filter_to_max(_max_texture_stages);
  
  int num_stages = new_texture->get_num_on_stages();
  int num_old_stages = _current_texture->get_num_on_stages();

  nassertv(num_stages <= _max_texture_stages && 
           num_old_stages <= _max_texture_stages);

  _texture_involves_color_scale = false;

  int i;
  for (i = 0; i < num_stages; i++) {
    TextureStage *stage = new_texture->get_on_stage(i);
    Texture *texture = new_texture->get_on_texture(stage);
    nassertv(texture != (Texture *)NULL);
    
    if (i >= num_old_stages ||
        stage != _current_texture->get_on_stage(i) ||
        texture != _current_texture->get_on_texture(stage) ||
        stage->involves_color_scale()) {
      // Stage i has changed.  Issue the texture on this stage.
      _glActiveTexture(GL_TEXTURE0 + i);

      GLenum target = get_texture_target(texture->get_texture_type());

      // First, turn off the previous texture mode.
      GLP(Disable)(GL_TEXTURE_1D);
      GLP(Disable)(GL_TEXTURE_2D);
      if (_supports_3d_texture) {
        GLP(Disable)(GL_TEXTURE_3D);
      }
      if (_supports_cube_map) {
        GLP(Disable)(GL_TEXTURE_CUBE_MAP);
      }

      // Then, turn on the current texture mode.
      if (target == GL_NONE) {
        // Unsupported texture mode.
        break;
      }
      GLP(Enable)(target);
      
      TextureContext *tc = texture->prepare_now(_prepared_objects, this);
      apply_texture(tc);

      if (stage->involves_color_scale() && _color_scale_enabled) {
        Colorf color = stage->get_color();
        color.set(color[0] * _current_color_scale[0],
                  color[1] * _current_color_scale[1],
                  color[2] * _current_color_scale[2],
                  color[3] * _current_color_scale[3]);
        _texture_involves_color_scale = true;
        GLP(TexEnvfv)(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color.get_data());
      } else {
        GLP(TexEnvfv)(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, stage->get_color().get_data());
      }

      if (stage->get_mode() == TextureStage::M_decal) {
        if (texture->get_num_components() < 3) {
          // Make a special case for 1- and 2-channel decal textures.
          // OpenGL does not define their use with GL_DECAL for some
          // reason, so implement them using the combiner instead.
          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE);
          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_RGB_SCALE, 1);
          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_ALPHA_SCALE, 1);
          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE);
          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_PREVIOUS);
          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_SRC2_RGB, GL_TEXTURE);
          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_ALPHA);
          
        } else {
          // Normal 3- and 4-channel decal textures.
          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
        }

      } else if (stage->get_mode() == TextureStage::M_combine) {
        GLP(TexEnvi)(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
        GLP(TexEnvi)(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE);
        GLP(TexEnvi)(GL_TEXTURE_ENV, GL_RGB_SCALE, stage->get_rgb_scale());
        GLP(TexEnvi)(GL_TEXTURE_ENV, GL_ALPHA_SCALE, stage->get_alpha_scale());
        GLP(TexEnvi)(GL_TEXTURE_ENV, GL_COMBINE_RGB, 
                     get_texture_combine_type(stage->get_combine_rgb_mode()));

        switch (stage->get_num_combine_rgb_operands()) {
        case 3:
          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_SRC2_RGB, 
                       get_texture_src_type(stage->get_combine_rgb_source2()));
          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_OPERAND2_RGB, 
                       get_texture_operand_type(stage->get_combine_rgb_operand2()));
          // fall through

        case 2:
          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_SRC1_RGB, 
                       get_texture_src_type(stage->get_combine_rgb_source1()));
          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_OPERAND1_RGB, 
                       get_texture_operand_type(stage->get_combine_rgb_operand1()));
          // fall through

        case 1:
          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_SRC0_RGB, 
                       get_texture_src_type(stage->get_combine_rgb_source0()));
          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_OPERAND0_RGB, 
                       get_texture_operand_type(stage->get_combine_rgb_operand0()));
          // fall through

        default:
          break;
        }
        GLP(TexEnvi)(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, 
                     get_texture_combine_type(stage->get_combine_alpha_mode()));

        switch (stage->get_num_combine_alpha_operands()) {
        case 3:
          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_SRC2_ALPHA, 
                       get_texture_src_type(stage->get_combine_alpha_source2()));
          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_OPERAND2_ALPHA, 
                       get_texture_operand_type(stage->get_combine_alpha_operand2()));
          // fall through

        case 2:
          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_SRC1_ALPHA, 
                       get_texture_src_type(stage->get_combine_alpha_source1()));
          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, 
                       get_texture_operand_type(stage->get_combine_alpha_operand1()));
          // fall through

        case 1:
          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_SRC0_ALPHA, 
                       get_texture_src_type(stage->get_combine_alpha_source0()));
          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, 
                       get_texture_operand_type(stage->get_combine_alpha_operand0()));
          // fall through

        default:
          break;
        }

      } else {
        GLint glmode = get_texture_apply_mode_type(stage->get_mode());
        GLP(TexEnvi)(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, glmode);
      }

      GLP(MatrixMode)(GL_TEXTURE);
      if (_current_tex_mat->has_stage(stage)) {
        GLP(LoadMatrixf)(_current_tex_mat->get_mat(stage).get_data());
      } else {
        GLP(LoadIdentity)();
      }
    }
  }
    
  // Disable the texture stages that are no longer used.
  for (i = num_stages; i < num_old_stages; i++) {
    _glActiveTexture(GL_TEXTURE0 + i);
    GLP(Disable)(GL_TEXTURE_1D);
    GLP(Disable)(GL_TEXTURE_2D);
    if (_supports_3d_texture) {
      GLP(Disable)(GL_TEXTURE_3D);
    }
    if (_supports_cube_map) {
      GLP(Disable)(GL_TEXTURE_CUBE_MAP);
    }
  }

  _current_texture = new_texture;

  // Changing the set of texture stages will require us to reissue the
  // texgen and texmat attribs.
  _needs_tex_gen = true;
  _needs_tex_mat = true;

  report_my_gl_errors();
}

#ifndef NDEBUG
////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::build_phony_mipmaps
//       Access: Protected
//  Description: Generates a series of colored mipmap levels to aid in
//               visualizing the mipmap levels as the hardware applies
//               them.
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
build_phony_mipmaps(Texture *tex) {
  int x_size = tex->get_x_size();
  int y_size = tex->get_y_size();

  GLCAT.info()
    << "Building phony mipmap levels for " << tex->get_name() << "\n";
  int level = 0;
  while (x_size > 0 && y_size > 0) {
    GLCAT.info(false)
      << "  level " << level << " is " << x_size << " by " << y_size << "\n";
    build_phony_mipmap_level(level, x_size, y_size);

    x_size >>= 1;
    y_size >>= 1;
    level++;
  }

  while (x_size > 0) {
    GLCAT.info(false)
      << "  level " << level << " is " << x_size << " by 1\n";
    build_phony_mipmap_level(level, x_size, 1);

    x_size >>= 1;
    level++;
  }

  while (y_size > 0) {
    GLCAT.info(false)
      << "  level " << level << " is 1 by " << y_size << "\n";
    build_phony_mipmap_level(level, 1, y_size);

    y_size >>= 1;
    level++;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::build_phony_mipmap_level
//       Access: Protected
//  Description: Generates a single colored mipmap level.
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
build_phony_mipmap_level(int level, int x_size, int y_size) {
  static const int num_levels = 10;
  static const char *level_filenames[num_levels] = {
    "mipmap_level_0.rgb",
    "mipmap_level_1.rgb",
    "mipmap_level_2.rgb",
    "mipmap_level_3.rgb",
    "mipmap_level_4.rgb",
    "mipmap_level_5.rgb",
    "mipmap_level_6.rgb",
    "mipmap_level_7.rgb",
    "mipmap_level_8.rgb",
    "mipmap_level_9.rgb"
  };
  static const RGBColorf level_colors[num_levels] = {
    RGBColorf(1.0f, 1.0f, 1.0f),
    RGBColorf(1.0f, 0.0f, 0.0f),
    RGBColorf(0.0f, 1.0f, 0.0f),
    RGBColorf(0.0f, 0.0f, 1.0f),
    RGBColorf(1.0f, 1.0f, 0.0f),
    RGBColorf(0.0f, 1.0f, 1.0f),
    RGBColorf(1.0f, 0.0f, 1.0f),
    RGBColorf(1.0f, 0.5, 0.0f),
    RGBColorf(0.0f, 1.0f, 0.5),
    RGBColorf(0.83, 0.71, 1.0f)
  };

  level = level % num_levels;
  Filename filename(level_filenames[level]);

  PNMImage image_sized(x_size, y_size);
  PNMImage image_source;
  if (filename.resolve_filename(get_texture_path()) ||
      filename.resolve_filename(get_model_path())) {
    image_source.read(filename);
  }

  if (image_source.is_valid()) {
    image_sized.quick_filter_from(image_source);

  } else {
    GLCAT.info(false)
      << "    " << filename << " cannot be read, making solid color mipmap.\n";
    image_sized.fill(level_colors[level][0],
                     level_colors[level][1],
                     level_colors[level][2]);
  }

  PT(Texture) tex = new Texture;
  if (!tex->load(image_sized)) {
    GLCAT.warning()
      << "Unable to load phony mipmap image.\n";
  } else {
    GLenum internal_format = get_internal_image_format(tex->get_format());
    GLenum external_format = get_external_image_format(tex->get_format());
    GLenum type = get_component_type(tex->get_component_type());
    
    GLP(TexImage2D)(GL_TEXTURE_2D, level, internal_format,
                    tex->get_x_size(), tex->get_y_size(), 0,
                    external_format, type, tex->get_ram_image());
  }
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GraphicsStateGuardian)::save_mipmap_images
//       Access: Protected
//  Description: Saves out each mipmap level of the indicated texture
//               (which must also be the currently active texture in
//               the GL state) as a separate image file to disk.
////////////////////////////////////////////////////////////////////
void CLP(GraphicsStateGuardian)::
save_mipmap_images(Texture *tex) {
  if (tex->get_texture_type() != Texture::TT_2d_texture) {
    // Never mind on unusual texture formats.
    return;
  }

  Filename filename = tex->get_name();
  string name;
  if (filename.empty()) {
    static int index = 0;
    name = "texture" + format_string(index);
    index++;
  } else {
    name = filename.get_basename_wo_extension();
  }

  GLenum external_format = get_external_image_format(tex->get_format());
  GLenum type = get_component_type(tex->get_component_type());

  int x_size = tex->get_x_size();
  int y_size = tex->get_y_size();

  // Specify byte-alignment for the pixels on output.
  GLP(PixelStorei)(GL_PACK_ALIGNMENT, 1);

  int mipmap_level = 0;
  do {
    x_size = max(x_size, 1);
    y_size = max(y_size, 1);

    PT(Texture) mtex = new Texture;
    mtex->setup_2d_texture(x_size, y_size, tex->get_component_type(), 
                           tex->get_format());
    GLP(GetTexImage)(GL_TEXTURE_2D, mipmap_level, external_format, 
                     type, mtex->make_ram_image());
    Filename mipmap_filename = name + "_" + format_string(mipmap_level) + ".rgb";
    nout << "Writing mipmap level " << mipmap_level
         << " (" << x_size << " by " << y_size << ") " 
         << mipmap_filename << "\n";
    mtex->write(mipmap_filename);

    x_size >>= 1;
    y_size >>= 1;
    mipmap_level++;
  } while (x_size > 0 || y_size > 0);
}
#endif  // NDEBUG

TypeHandle CLP(GraphicsStateGuardian)::get_type(void) const {
  return get_class_type();
}

TypeHandle CLP(GraphicsStateGuardian)::get_class_type(void) {
  return _type_handle;
}

void CLP(GraphicsStateGuardian)::init_type(void) {
  GraphicsStateGuardian::init_type();
  register_type(_type_handle, CLASSPREFIX_QUOTED "GraphicsStateGuardian",
                GraphicsStateGuardian::get_class_type());
}


#ifdef GSG_VERBOSE

void CLP(GraphicsStateGuardian)::
dump_state(void)
{
  if (GLCAT.is_debug())
    {
      ostream &dump = GLCAT.debug(false);
      GLCAT.debug() << "Dumping GL State" << endl;

      dump << "\t\t" << "GL_LINE_SMOOTH " << _line_smooth_enabled << " " << (bool)GLP(IsEnabled)(GL_LINE_SMOOTH) << "\n";
      dump << "\t\t" << "GL_POINT_SMOOTH " << _point_smooth_enabled << " " << (bool)GLP(IsEnabled)(GL_POINT_SMOOTH) << "\n";
      dump << "\t\t" << "GL_LIGHTING " << _lighting_enabled << " " << (bool)GLP(IsEnabled)(GL_LIGHTING) << "\n";
      dump << "\t\t" << "GL_SCISSOR_TEST " << _scissor_enabled << " " << (bool)GLP(IsEnabled)(GL_SCISSOR_TEST) << "\n";
      dump << "\t\t" << "GL_STENCIL_TEST " << " " << (bool)GLP(IsEnabled)(GL_STENCIL_TEST) << "\n";
      dump << "\t\t" << "GL_BLEND " << _blend_enabled << " " << (bool)GLP(IsEnabled)(GL_BLEND) << "\n";
      dump << "\t\t" << "GL_DEPTH_TEST " << _depth_test_enabled << " " << (bool)GLP(IsEnabled)(GL_DEPTH_TEST) << "\n";
      dump << "\t\t" << "GL_FOG " << _fog_enabled << " " << (bool)GLP(IsEnabled)(GL_FOG) << "\n";
      dump << "\t\t" << "GL_ALPHA_TEST " << _alpha_test_enabled << " " << (bool)GLP(IsEnabled)(GL_ALPHA_TEST) << "\n";
      dump << "\t\t" << "GL_POLYGON_OFFSET_FILL " << _polygon_offset_enabled << " " << (bool)GLP(IsEnabled)(GL_POLYGON_OFFSET_FILL) << "\n";

      dump << endl;
    }
}

#else  // GSG_VERBOSE

// This function does nothing unless GSG_VERBOSE is compiled in.
void CLP(GraphicsStateGuardian)::
dump_state(void)
{
}

#endif  // GSG_VERBOSE


#ifdef GSG_VERBOSE

// This is a handy function to output a GLenum value as a string, for
// debugging.
ostream &output_gl_enum(ostream &out, GLenum v) {
  switch (v) {
  case GL_FALSE:
    return out << "GL_FALSE";
  case GL_TRUE:
    return out << "GL_TRUE";

    /* Data types */
  case GL_BYTE:
    return out << "GL_BYTE";
  case GL_UNSIGNED_BYTE:
    return out << "GL_UNSIGNED_BYTE";
  case GL_SHORT:
    return out << "GL_SHORT";
  case GL_UNSIGNED_SHORT:
    return out << "GL_UNSIGNED_SHORT";
  case GL_INT:
    return out << "GL_INT";
  case GL_UNSIGNED_INT:
    return out << "GL_UNSIGNED_INT";
  case GL_FLOAT:
    return out << "GL_FLOAT";
  case GL_DOUBLE:
    return out << "GL_DOUBLE";
  case GL_2_BYTES:
    return out << "GL_2_BYTES";
  case GL_3_BYTES:
    return out << "GL_3_BYTES";
  case GL_4_BYTES:
    return out << "GL_4_BYTES";

    /* Primitives */
    /*
      case GL_LINES:
      return out << "GL_LINES";
      case GL_POINTS:
      return out << "GL_POINTS";
    */
  case GL_LINE_STRIP:
    return out << "GL_LINE_STRIP";
  case GL_LINE_LOOP:
    return out << "GL_LINE_LOOP";
  case GL_TRIANGLES:
    return out << "GL_TRIANGLES";
  case GL_TRIANGLE_STRIP:
    return out << "GL_TRIANGLE_STRIP";
  case GL_TRIANGLE_FAN:
    return out << "GL_TRIANGLE_FAN";
  case GL_QUADS:
    return out << "GL_QUADS";
  case GL_QUAD_STRIP:
    return out << "GL_QUAD_STRIP";
  case GL_POLYGON:
    return out << "GL_POLYGON";
  case GL_EDGE_FLAG:
    return out << "GL_EDGE_FLAG";

    /* Vertex Arrays */
  case GL_VERTEX_ARRAY:
    return out << "GL_VERTEX_ARRAY";
  case GL_NORMAL_ARRAY:
    return out << "GL_NORMAL_ARRAY";
  case GL_COLOR_ARRAY:
    return out << "GL_COLOR_ARRAY";
  case GL_INDEX_ARRAY:
    return out << "GL_INDEX_ARRAY";
  case GL_TEXTURE_COORD_ARRAY:
    return out << "GL_TEXTURE_COORD_ARRAY";
  case GL_EDGE_FLAG_ARRAY:
    return out << "GL_EDGE_FLAG_ARRAY";
  case GL_VERTEX_ARRAY_SIZE:
    return out << "GL_VERTEX_ARRAY_SIZE";
  case GL_VERTEX_ARRAY_TYPE:
    return out << "GL_VERTEX_ARRAY_TYPE";
  case GL_VERTEX_ARRAY_STRIDE:
    return out << "GL_VERTEX_ARRAY_STRIDE";
  case GL_NORMAL_ARRAY_TYPE:
    return out << "GL_NORMAL_ARRAY_TYPE";
  case GL_NORMAL_ARRAY_STRIDE:
    return out << "GL_NORMAL_ARRAY_STRIDE";
  case GL_COLOR_ARRAY_SIZE:
    return out << "GL_COLOR_ARRAY_SIZE";
  case GL_COLOR_ARRAY_TYPE:
    return out << "GL_COLOR_ARRAY_TYPE";
  case GL_COLOR_ARRAY_STRIDE:
    return out << "GL_COLOR_ARRAY_STRIDE";
  case GL_INDEX_ARRAY_TYPE:
    return out << "GL_INDEX_ARRAY_TYPE";
  case GL_INDEX_ARRAY_STRIDE:
    return out << "GL_INDEX_ARRAY_STRIDE";
  case GL_TEXTURE_COORD_ARRAY_SIZE:
    return out << "GL_TEXTURE_COORD_ARRAY_SIZE";
  case GL_TEXTURE_COORD_ARRAY_TYPE:
    return out << "GL_TEXTURE_COORD_ARRAY_TYPE";
  case GL_TEXTURE_COORD_ARRAY_STRIDE:
    return out << "GL_TEXTURE_COORD_ARRAY_STRIDE";
  case GL_EDGE_FLAG_ARRAY_STRIDE:
    return out << "GL_EDGE_FLAG_ARRAY_STRIDE";
  case GL_VERTEX_ARRAY_POINTER:
    return out << "GL_VERTEX_ARRAY_POINTER";
  case GL_NORMAL_ARRAY_POINTER:
    return out << "GL_NORMAL_ARRAY_POINTER";
  case GL_COLOR_ARRAY_POINTER:
    return out << "GL_COLOR_ARRAY_POINTER";
  case GL_INDEX_ARRAY_POINTER:
    return out << "GL_INDEX_ARRAY_POINTER";
  case GL_TEXTURE_COORD_ARRAY_POINTER:
    return out << "GL_TEXTURE_COORD_ARRAY_POINTER";
  case GL_EDGE_FLAG_ARRAY_POINTER:
    return out << "GL_EDGE_FLAG_ARRAY_POINTER";
  case GL_V2F:
    return out << "GL_V2F";
  case GL_V3F:
    return out << "GL_V3F";
  case GL_C4UB_V2F:
    return out << "GL_C4UB_V2F";
  case GL_C4UB_V3F:
    return out << "GL_C4UB_V3F";
  case GL_C3F_V3F:
    return out << "GL_C3F_V3F";
  case GL_N3F_V3F:
    return out << "GL_N3F_V3F";
  case GL_C4F_N3F_V3F:
    return out << "GL_C4F_N3F_V3F";
  case GL_T2F_V3F:
    return out << "GL_T2F_V3F";
  case GL_T4F_V4F:
    return out << "GL_T4F_V4F";
  case GL_T2F_C4UB_V3F:
    return out << "GL_T2F_C4UB_V3F";
  case GL_T2F_C3F_V3F:
    return out << "GL_T2F_C3F_V3F";
  case GL_T2F_N3F_V3F:
    return out << "GL_T2F_N3F_V3F";
  case GL_T2F_C4F_N3F_V3F:
    return out << "GL_T2F_C4F_N3F_V3F";
  case GL_T4F_C4F_N3F_V4F:
    return out << "GL_T4F_C4F_N3F_V4F";

    /* Matrix Mode */
  case GL_MATRIX_MODE:
    return out << "GL_MATRIX_MODE";
  case GL_MODELVIEW:
    return out << "GL_MODELVIEW";
  case GL_PROJECTION:
    return out << "GL_PROJECTION";
  case GL_TEXTURE:
    return out << "GL_TEXTURE";

    /* Points */
  case GL_POINT_SMOOTH:
    return out << "GL_POINT_SMOOTH";
  case GL_POINT_SIZE:
    return out << "GL_POINT_SIZE";
  case GL_POINT_SIZE_GRANULARITY:
    return out << "GL_POINT_SIZE_GRANULARITY";
  case GL_POINT_SIZE_RANGE:
    return out << "GL_POINT_SIZE_RANGE";

    /* Lines */
  case GL_LINE_SMOOTH:
    return out << "GL_LINE_SMOOTH";
  case GL_LINE_STIPPLE:
    return out << "GL_LINE_STIPPLE";
  case GL_LINE_STIPPLE_PATTERN:
    return out << "GL_LINE_STIPPLE_PATTERN";
  case GL_LINE_STIPPLE_REPEAT:
    return out << "GL_LINE_STIPPLE_REPEAT";
  case GL_LINE_WIDTH:
    return out << "GL_LINE_WIDTH";
  case GL_LINE_WIDTH_GRANULARITY:
    return out << "GL_LINE_WIDTH_GRANULARITY";
  case GL_LINE_WIDTH_RANGE:
    return out << "GL_LINE_WIDTH_RANGE";

    /* Polygons */
  case GL_POINT:
    return out << "GL_POINT";
  case GL_LINE:
    return out << "GL_LINE";
  case GL_FILL:
    return out << "GL_FILL";
  case GL_CCW:
    return out << "GL_CCW";
  case GL_CW:
    return out << "GL_CW";
  case GL_FRONT:
    return out << "GL_FRONT";
  case GL_BACK:
    return out << "GL_BACK";
  case GL_CULL_FACE:
    return out << "GL_CULL_FACE";
  case GL_CULL_FACE_MODE:
    return out << "GL_CULL_FACE_MODE";
  case GL_POLYGON_SMOOTH:
    return out << "GL_POLYGON_SMOOTH";
  case GL_POLYGON_STIPPLE:
    return out << "GL_POLYGON_STIPPLE";
  case GL_FRONT_FACE:
    return out << "GL_FRONT_FACE";
  case GL_POLYGON_MODE:
    return out << "GL_POLYGON_MODE";
  case GL_POLYGON_OFFSET_FACTOR:
    return out << "GL_POLYGON_OFFSET_FACTOR";
  case GL_POLYGON_OFFSET_UNITS:
    return out << "GL_POLYGON_OFFSET_UNITS";
  case GL_POLYGON_OFFSET_POINT:
    return out << "GL_POLYGON_OFFSET_POINT";
  case GL_POLYGON_OFFSET_LINE:
    return out << "GL_POLYGON_OFFSET_LINE";
  case GL_POLYGON_OFFSET_FILL:
    return out << "GL_POLYGON_OFFSET_FILL";

    /* Display Lists */
  case GL_COMPILE:
    return out << "GL_COMPILE";
  case GL_COMPILE_AND_EXECUTE:
    return out << "GL_COMPILE_AND_EXECUTE";
  case GL_LIST_BASE:
    return out << "GL_LIST_BASE";
  case GL_LIST_INDEX:
    return out << "GL_LIST_INDEX";
  case GL_LIST_MODE:
    return out << "GL_LIST_MODE";

    /* Depth buffer */
  case GL_NEVER:
    return out << "GL_NEVER";
  case GL_LESS:
    return out << "GL_LESS";
  case GL_GEQUAL:
    return out << "GL_GEQUAL";
  case GL_LEQUAL:
    return out << "GL_LEQUAL";
  case GL_GREATER:
    return out << "GL_GREATER";
  case GL_NOTEQUAL:
    return out << "GL_NOTEQUAL";
  case GL_EQUAL:
    return out << "GL_EQUAL";
  case GL_ALWAYS:
    return out << "GL_ALWAYS";
  case GL_DEPTH_TEST:
    return out << "GL_DEPTH_TEST";
  case GL_DEPTH_BITS:
    return out << "GL_DEPTH_BITS";
  case GL_DEPTH_CLEAR_VALUE:
    return out << "GL_DEPTH_CLEAR_VALUE";
  case GL_DEPTH_FUNC:
    return out << "GL_DEPTH_FUNC";
  case GL_DEPTH_RANGE:
    return out << "GL_DEPTH_RANGE";
  case GL_DEPTH_WRITEMASK:
    return out << "GL_DEPTH_WRITEMASK";
  case GL_DEPTH_COMPONENT:
    return out << "GL_DEPTH_COMPONENT";

    /* Lighting */
  case GL_LIGHTING:
    return out << "GL_LIGHTING";
  case GL_LIGHT0:
    return out << "GL_LIGHT0";
  case GL_LIGHT1:
    return out << "GL_LIGHT1";
  case GL_LIGHT2:
    return out << "GL_LIGHT2";
  case GL_LIGHT3:
    return out << "GL_LIGHT3";
  case GL_LIGHT4:
    return out << "GL_LIGHT4";
  case GL_LIGHT5:
    return out << "GL_LIGHT5";
  case GL_LIGHT6:
    return out << "GL_LIGHT6";
  case GL_LIGHT7:
    return out << "GL_LIGHT7";
  case GL_SPOT_EXPONENT:
    return out << "GL_SPOT_EXPONENT";
  case GL_SPOT_CUTOFF:
    return out << "GL_SPOT_CUTOFF";
  case GL_CONSTANT_ATTENUATION:
    return out << "GL_CONSTANT_ATTENUATION";
  case GL_LINEAR_ATTENUATION:
    return out << "GL_LINEAR_ATTENUATION";
  case GL_QUADRATIC_ATTENUATION:
    return out << "GL_QUADRATIC_ATTENUATION";
  case GL_AMBIENT:
    return out << "GL_AMBIENT";
  case GL_DIFFUSE:
    return out << "GL_DIFFUSE";
  case GL_SPECULAR:
    return out << "GL_SPECULAR";
  case GL_SHININESS:
    return out << "GL_SHININESS";
  case GL_EMISSION:
    return out << "GL_EMISSION";
  case GL_POSITION:
    return out << "GL_POSITION";
  case GL_SPOT_DIRECTION:
    return out << "GL_SPOT_DIRECTION";
  case GL_AMBIENT_AND_DIFFUSE:
    return out << "GL_AMBIENT_AND_DIFFUSE";
  case GL_COLOR_INDEXES:
    return out << "GL_COLOR_INDEXES";
  case GL_LIGHT_MODEL_TWO_SIDE:
    return out << "GL_LIGHT_MODEL_TWO_SIDE";
  case GL_LIGHT_MODEL_LOCAL_VIEWER:
    return out << "GL_LIGHT_MODEL_LOCAL_VIEWER";
  case GL_LIGHT_MODEL_AMBIENT:
    return out << "GL_LIGHT_MODEL_AMBIENT";
  case GL_FRONT_AND_BACK:
    return out << "GL_FRONT_AND_BACK";
  case GL_SHADE_MODEL:
    return out << "GL_SHADE_MODEL";
  case GL_FLAT:
    return out << "GL_FLAT";
  case GL_SMOOTH:
    return out << "GL_SMOOTH";
  case GL_COLOR_MATERIAL:
    return out << "GL_COLOR_MATERIAL";
  case GL_COLOR_MATERIAL_FACE:
    return out << "GL_COLOR_MATERIAL_FACE";
  case GL_COLOR_MATERIAL_PARAMETER:
    return out << "GL_COLOR_MATERIAL_PARAMETER";
  case GL_NORMALIZE:
    return out << "GL_NORMALIZE";

    /* User clipping planes */
  case GL_CLIP_PLANE0:
    return out << "GL_CLIP_PLANE0";
  case GL_CLIP_PLANE1:
    return out << "GL_CLIP_PLANE1";
  case GL_CLIP_PLANE2:
    return out << "GL_CLIP_PLANE2";
  case GL_CLIP_PLANE3:
    return out << "GL_CLIP_PLANE3";
  case GL_CLIP_PLANE4:
    return out << "GL_CLIP_PLANE4";
  case GL_CLIP_PLANE5:
    return out << "GL_CLIP_PLANE5";

    /* Accumulation buffer */
  case GL_ACCUM_RED_BITS:
    return out << "GL_ACCUM_RED_BITS";
  case GL_ACCUM_GREEN_BITS:
    return out << "GL_ACCUM_GREEN_BITS";
  case GL_ACCUM_BLUE_BITS:
    return out << "GL_ACCUM_BLUE_BITS";
  case GL_ACCUM_ALPHA_BITS:
    return out << "GL_ACCUM_ALPHA_BITS";
  case GL_ACCUM_CLEAR_VALUE:
    return out << "GL_ACCUM_CLEAR_VALUE";
  case GL_ACCUM:
    return out << "GL_ACCUM";
  case GL_ADD:
    return out << "GL_ADD";
  case GL_LOAD:
    return out << "GL_LOAD";
  case GL_MULT:
    return out << "GL_MULT";

    /* Alpha testing */
  case GL_ALPHA_TEST:
    return out << "GL_ALPHA_TEST";
  case GL_ALPHA_TEST_REF:
    return out << "GL_ALPHA_TEST_REF";
  case GL_ALPHA_TEST_FUNC:
    return out << "GL_ALPHA_TEST_FUNC";

    /* Blending */
  case GL_BLEND:
    return out << "GL_BLEND";
  case GL_BLEND_SRC:
    return out << "GL_BLEND_SRC";
  case GL_BLEND_DST:
    return out << "GL_BLEND_DST";
    /*
      case GL_ZERO:
      return out << "GL_ZERO";
      case GL_ONE:
      return out << "GL_ONE";
    */
  case GL_SRC_COLOR:
    return out << "GL_SRC_COLOR";
  case GL_ONE_MINUS_SRC_COLOR:
    return out << "GL_ONE_MINUS_SRC_COLOR";
  case GL_DST_COLOR:
    return out << "GL_DST_COLOR";
  case GL_ONE_MINUS_DST_COLOR:
    return out << "GL_ONE_MINUS_DST_COLOR";
  case GL_SRC_ALPHA:
    return out << "GL_SRC_ALPHA";
  case GL_ONE_MINUS_SRC_ALPHA:
    return out << "GL_ONE_MINUS_SRC_ALPHA";
  case GL_DST_ALPHA:
    return out << "GL_DST_ALPHA";
  case GL_ONE_MINUS_DST_ALPHA:
    return out << "GL_ONE_MINUS_DST_ALPHA";
  case GL_SRC_ALPHA_SATURATE:
    return out << "GL_SRC_ALPHA_SATURATE";
  case GL_CONSTANT_COLOR:
    return out << "GL_CONSTANT_COLOR";
  case GL_ONE_MINUS_CONSTANT_COLOR:
    return out << "GL_ONE_MINUS_CONSTANT_COLOR";
  case GL_CONSTANT_ALPHA:
    return out << "GL_CONSTANT_ALPHA";
  case GL_ONE_MINUS_CONSTANT_ALPHA:
    return out << "GL_ONE_MINUS_CONSTANT_ALPHA";

    /* Render Mode */
  case GL_FEEDBACK:
    return out << "GL_FEEDBACK";
  case GL_RENDER:
    return out << "GL_RENDER";
  case GL_SELECT:
    return out << "GL_SELECT";

    /* Feedback */
  case GL_2D:
    return out << "GL_2D";
  case GL_3D:
    return out << "GL_3D";
  case GL_3D_COLOR:
    return out << "GL_3D_COLOR";
  case GL_3D_COLOR_TEXTURE:
    return out << "GL_3D_COLOR_TEXTURE";
  case GL_4D_COLOR_TEXTURE:
    return out << "GL_4D_COLOR_TEXTURE";
  case GL_POINT_TOKEN:
    return out << "GL_POINT_TOKEN";
  case GL_LINE_TOKEN:
    return out << "GL_LINE_TOKEN";
  case GL_LINE_RESET_TOKEN:
    return out << "GL_LINE_RESET_TOKEN";
  case GL_POLYGON_TOKEN:
    return out << "GL_POLYGON_TOKEN";
  case GL_BITMAP_TOKEN:
    return out << "GL_BITMAP_TOKEN";
  case GL_DRAW_PIXEL_TOKEN:
    return out << "GL_DRAW_PIXEL_TOKEN";
  case GL_COPY_PIXEL_TOKEN:
    return out << "GL_COPY_PIXEL_TOKEN";
  case GL_PASS_THROUGH_TOKEN:
    return out << "GL_PASS_THROUGH_TOKEN";
  case GL_FEEDBACK_BUFFER_POINTER:
    return out << "GL_FEEDBACK_BUFFER_POINTER";
  case GL_FEEDBACK_BUFFER_SIZE:
    return out << "GL_FEEDBACK_BUFFER_SIZE";
  case GL_FEEDBACK_BUFFER_TYPE:
    return out << "GL_FEEDBACK_BUFFER_TYPE";

    /* Selection */
  case GL_SELECTION_BUFFER_POINTER:
    return out << "GL_SELECTION_BUFFER_POINTER";
  case GL_SELECTION_BUFFER_SIZE:
    return out << "GL_SELECTION_BUFFER_SIZE";

    /* Fog */
  case GL_FOG:
    return out << "GL_FOG";
  case GL_FOG_MODE:
    return out << "GL_FOG_MODE";
  case GL_FOG_DENSITY:
    return out << "GL_FOG_DENSITY";
  case GL_FOG_COLOR:
    return out << "GL_FOG_COLOR";
  case GL_FOG_INDEX:
    return out << "GL_FOG_INDEX";
  case GL_FOG_START:
    return out << "GL_FOG_START";
  case GL_FOG_END:
    return out << "GL_FOG_END";
  case GL_LINEAR:
    return out << "GL_LINEAR";
  case GL_EXP:
    return out << "GL_EXP";
  case GL_EXP2:
    return out << "GL_EXP2";

    /* Logic Ops */
  case GL_LOGIC_OP:
    return out << "GL_LOGIC_OP";
    /*
      case GL_INDEX_LOGIC_OP:
      return out << "GL_INDEX_LOGIC_OP";
    */
  case GL_COLOR_LOGIC_OP:
    return out << "GL_COLOR_LOGIC_OP";
  case GL_LOGIC_OP_MODE:
    return out << "GL_LOGIC_OP_MODE";
  case GL_CLEAR:
    return out << "GL_CLEAR";
  case GL_SET:
    return out << "GL_SET";
  case GL_COPY:
    return out << "GL_COPY";
  case GL_COPY_INVERTED:
    return out << "GL_COPY_INVERTED";
  case GL_NOOP:
    return out << "GL_NOOP";
  case GL_INVERT:
    return out << "GL_INVERT";
  case GL_AND:
    return out << "GL_AND";
  case GL_NAND:
    return out << "GL_NAND";
  case GL_OR:
    return out << "GL_OR";
  case GL_NOR:
    return out << "GL_NOR";
  case GL_XOR:
    return out << "GL_XOR";
  case GL_EQUIV:
    return out << "GL_EQUIV";
  case GL_AND_REVERSE:
    return out << "GL_AND_REVERSE";
  case GL_AND_INVERTED:
    return out << "GL_AND_INVERTED";
  case GL_OR_REVERSE:
    return out << "GL_OR_REVERSE";
  case GL_OR_INVERTED:
    return out << "GL_OR_INVERTED";

    /* Stencil */
  case GL_STENCIL_TEST:
    return out << "GL_STENCIL_TEST";
  case GL_STENCIL_WRITEMASK:
    return out << "GL_STENCIL_WRITEMASK";
  case GL_STENCIL_BITS:
    return out << "GL_STENCIL_BITS";
  case GL_STENCIL_FUNC:
    return out << "GL_STENCIL_FUNC";
  case GL_STENCIL_VALUE_MASK:
    return out << "GL_STENCIL_VALUE_MASK";
  case GL_STENCIL_REF:
    return out << "GL_STENCIL_REF";
  case GL_STENCIL_FAIL:
    return out << "GL_STENCIL_FAIL";
  case GL_STENCIL_PASS_DEPTH_PASS:
    return out << "GL_STENCIL_PASS_DEPTH_PASS";
  case GL_STENCIL_PASS_DEPTH_FAIL:
    return out << "GL_STENCIL_PASS_DEPTH_FAIL";
  case GL_STENCIL_CLEAR_VALUE:
    return out << "GL_STENCIL_CLEAR_VALUE";
  case GL_STENCIL_INDEX:
    return out << "GL_STENCIL_INDEX";
  case GL_KEEP:
    return out << "GL_KEEP";
  case GL_REPLACE:
    return out << "GL_REPLACE";
  case GL_INCR:
    return out << "GL_INCR";
  case GL_DECR:
    return out << "GL_DECR";

    /* Buffers, Pixel Drawing/Reading */
    /*
      case GL_NONE:
      return out << "GL_NONE";
    */
  case GL_LEFT:
    return out << "GL_LEFT";
  case GL_RIGHT:
    return out << "GL_RIGHT";
  case GL_FRONT_LEFT:
    return out << "GL_FRONT_LEFT";
  case GL_FRONT_RIGHT:
    return out << "GL_FRONT_RIGHT";
  case GL_BACK_LEFT:
    return out << "GL_BACK_LEFT";
  case GL_BACK_RIGHT:
    return out << "GL_BACK_RIGHT";
  case GL_AUX0:
    return out << "GL_AUX0";
  case GL_AUX1:
    return out << "GL_AUX1";
  case GL_AUX2:
    return out << "GL_AUX2";
  case GL_AUX3:
    return out << "GL_AUX3";
  case GL_COLOR_INDEX:
    return out << "GL_COLOR_INDEX";
  case GL_RED:
    return out << "GL_RED";
  case GL_GREEN:
    return out << "GL_GREEN";
  case GL_BLUE:
    return out << "GL_BLUE";
  case GL_ALPHA:
    return out << "GL_ALPHA";
  case GL_LUMINANCE:
    return out << "GL_LUMINANCE";
  case GL_LUMINANCE_ALPHA:
    return out << "GL_LUMINANCE_ALPHA";
  case GL_ALPHA_BITS:
    return out << "GL_ALPHA_BITS";
  case GL_RED_BITS:
    return out << "GL_RED_BITS";
  case GL_GREEN_BITS:
    return out << "GL_GREEN_BITS";
  case GL_BLUE_BITS:
    return out << "GL_BLUE_BITS";
  case GL_INDEX_BITS:
    return out << "GL_INDEX_BITS";
  case GL_SUBPIXEL_BITS:
    return out << "GL_SUBPIXEL_BITS";
  case GL_AUX_BUFFERS:
    return out << "GL_AUX_BUFFERS";
  case GL_READ_BUFFER:
    return out << "GL_READ_BUFFER";
  case GL_DRAW_BUFFER:
    return out << "GL_DRAW_BUFFER";
  case GL_DOUBLEBUFFER:
    return out << "GL_DOUBLEBUFFER";
  case GL_STEREO:
    return out << "GL_STEREO";
  case GL_BITMAP:
    return out << "GL_BITMAP";
  case GL_COLOR:
    return out << "GL_COLOR";
  case GL_DEPTH:
    return out << "GL_DEPTH";
  case GL_STENCIL:
    return out << "GL_STENCIL";
  case GL_DITHER:
    return out << "GL_DITHER";
  case GL_RGB:
    return out << "GL_RGB";
  case GL_RGBA:
    return out << "GL_RGBA";

    /* Implementation limits */
  case GL_MAX_LIST_NESTING:
    return out << "GL_MAX_LIST_NESTING";
  case GL_MAX_ATTRIB_STACK_DEPTH:
    return out << "GL_MAX_ATTRIB_STACK_DEPTH";
  case GL_MAX_MODELVIEW_STACK_DEPTH:
    return out << "GL_MAX_MODELVIEW_STACK_DEPTH";
  case GL_MAX_NAME_STACK_DEPTH:
    return out << "GL_MAX_NAME_STACK_DEPTH";
  case GL_MAX_PROJECTION_STACK_DEPTH:
    return out << "GL_MAX_PROJECTION_STACK_DEPTH";
  case GL_MAX_TEXTURE_STACK_DEPTH:
    return out << "GL_MAX_TEXTURE_STACK_DEPTH";
  case GL_MAX_EVAL_ORDER:
    return out << "GL_MAX_EVAL_ORDER";
  case GL_MAX_LIGHTS:
    return out << "GL_MAX_LIGHTS";
  case GL_MAX_CLIP_PLANES:
    return out << "GL_MAX_CLIP_PLANES";
  case GL_MAX_TEXTURE_SIZE:
    return out << "GL_MAX_TEXTURE_SIZE";
  case GL_MAX_PIXEL_MAP_TABLE:
    return out << "GL_MAX_PIXEL_MAP_TABLE";
  case GL_MAX_VIEWPORT_DIMS:
    return out << "GL_MAX_VIEWPORT_DIMS";
  case GL_MAX_CLIENT_ATTRIB_STACK_DEPTH:
    return out << "GL_MAX_CLIENT_ATTRIB_STACK_DEPTH";

    /* Gets */
  case GL_ATTRIB_STACK_DEPTH:
    return out << "GL_ATTRIB_STACK_DEPTH";
  case GL_CLIENT_ATTRIB_STACK_DEPTH:
    return out << "GL_CLIENT_ATTRIB_STACK_DEPTH";
  case GL_COLOR_CLEAR_VALUE:
    return out << "GL_COLOR_CLEAR_VALUE";
  case GL_COLOR_WRITEMASK:
    return out << "GL_COLOR_WRITEMASK";
  case GL_CURRENT_INDEX:
    return out << "GL_CURRENT_INDEX";
  case GL_CURRENT_COLOR:
    return out << "GL_CURRENT_COLOR";
  case GL_CURRENT_NORMAL:
    return out << "GL_CURRENT_NORMAL";
  case GL_CURRENT_RASTER_COLOR:
    return out << "GL_CURRENT_RASTER_COLOR";
  case GL_CURRENT_RASTER_DISTANCE:
    return out << "GL_CURRENT_RASTER_DISTANCE";
  case GL_CURRENT_RASTER_INDEX:
    return out << "GL_CURRENT_RASTER_INDEX";
  case GL_CURRENT_RASTER_POSITION:
    return out << "GL_CURRENT_RASTER_POSITION";
  case GL_CURRENT_RASTER_TEXTURE_COORDS:
    return out << "GL_CURRENT_RASTER_TEXTURE_COORDS";
  case GL_CURRENT_RASTER_POSITION_VALID:
    return out << "GL_CURRENT_RASTER_POSITION_VALID";
  case GL_CURRENT_TEXTURE_COORDS:
    return out << "GL_CURRENT_TEXTURE_COORDS";
  case GL_INDEX_CLEAR_VALUE:
    return out << "GL_INDEX_CLEAR_VALUE";
  case GL_INDEX_MODE:
    return out << "GL_INDEX_MODE";
  case GL_INDEX_WRITEMASK:
    return out << "GL_INDEX_WRITEMASK";
  case GL_MODELVIEW_MATRIX:
    return out << "GL_MODELVIEW_MATRIX";
  case GL_MODELVIEW_STACK_DEPTH:
    return out << "GL_MODELVIEW_STACK_DEPTH";
  case GL_NAME_STACK_DEPTH:
    return out << "GL_NAME_STACK_DEPTH";
  case GL_PROJECTION_MATRIX:
    return out << "GL_PROJECTION_MATRIX";
  case GL_PROJECTION_STACK_DEPTH:
    return out << "GL_PROJECTION_STACK_DEPTH";
  case GL_RENDER_MODE:
    return out << "GL_RENDER_MODE";
  case GL_RGBA_MODE:
    return out << "GL_RGBA_MODE";
  case GL_TEXTURE_MATRIX:
    return out << "GL_TEXTURE_MATRIX";
  case GL_TEXTURE_STACK_DEPTH:
    return out << "GL_TEXTURE_STACK_DEPTH";
  case GL_VIEWPORT:
    return out << "GL_VIEWPORT";


    /* Evaluators */
  case GL_AUTO_NORMAL:
    return out << "GL_AUTO_NORMAL";
  case GL_MAP1_COLOR_4:
    return out << "GL_MAP1_COLOR_4";
  case GL_MAP1_GRID_DOMAIN:
    return out << "GL_MAP1_GRID_DOMAIN";
  case GL_MAP1_GRID_SEGMENTS:
    return out << "GL_MAP1_GRID_SEGMENTS";
  case GL_MAP1_INDEX:
    return out << "GL_MAP1_INDEX";
  case GL_MAP1_NORMAL:
    return out << "GL_MAP1_NORMAL";
  case GL_MAP1_TEXTURE_COORD_1:
    return out << "GL_MAP1_TEXTURE_COORD_1";
  case GL_MAP1_TEXTURE_COORD_2:
    return out << "GL_MAP1_TEXTURE_COORD_2";
  case GL_MAP1_TEXTURE_COORD_3:
    return out << "GL_MAP1_TEXTURE_COORD_3";
  case GL_MAP1_TEXTURE_COORD_4:
    return out << "GL_MAP1_TEXTURE_COORD_4";
  case GL_MAP1_VERTEX_3:
    return out << "GL_MAP1_VERTEX_3";
  case GL_MAP1_VERTEX_4:
    return out << "GL_MAP1_VERTEX_4";
  case GL_MAP2_COLOR_4:
    return out << "GL_MAP2_COLOR_4";
  case GL_MAP2_GRID_DOMAIN:
    return out << "GL_MAP2_GRID_DOMAIN";
  case GL_MAP2_GRID_SEGMENTS:
    return out << "GL_MAP2_GRID_SEGMENTS";
  case GL_MAP2_INDEX:
    return out << "GL_MAP2_INDEX";
  case GL_MAP2_NORMAL:
    return out << "GL_MAP2_NORMAL";
  case GL_MAP2_TEXTURE_COORD_1:
    return out << "GL_MAP2_TEXTURE_COORD_1";
  case GL_MAP2_TEXTURE_COORD_2:
    return out << "GL_MAP2_TEXTURE_COORD_2";
  case GL_MAP2_TEXTURE_COORD_3:
    return out << "GL_MAP2_TEXTURE_COORD_3";
  case GL_MAP2_TEXTURE_COORD_4:
    return out << "GL_MAP2_TEXTURE_COORD_4";
  case GL_MAP2_VERTEX_3:
    return out << "GL_MAP2_VERTEX_3";
  case GL_MAP2_VERTEX_4:
    return out << "GL_MAP2_VERTEX_4";
  case GL_COEFF:
    return out << "GL_COEFF";
  case GL_DOMAIN:
    return out << "GL_DOMAIN";
  case GL_ORDER:
    return out << "GL_ORDER";

    /* Hints */
  case GL_FOG_HINT:
    return out << "GL_FOG_HINT";
  case GL_LINE_SMOOTH_HINT:
    return out << "GL_LINE_SMOOTH_HINT";
  case GL_PERSPECTIVE_CORRECTION_HINT:
    return out << "GL_PERSPECTIVE_CORRECTION_HINT";
  case GL_POINT_SMOOTH_HINT:
    return out << "GL_POINT_SMOOTH_HINT";
  case GL_POLYGON_SMOOTH_HINT:
    return out << "GL_POLYGON_SMOOTH_HINT";
  case GL_DONT_CARE:
    return out << "GL_DONT_CARE";
  case GL_FASTEST:
    return out << "GL_FASTEST";
  case GL_NICEST:
    return out << "GL_NICEST";

    /* Scissor box */
  case GL_SCISSOR_TEST:
    return out << "GL_SCISSOR_TEST";
  case GL_SCISSOR_BOX:
    return out << "GL_SCISSOR_BOX";

    /* Pixel Mode / Transfer */
  case GL_MAP_COLOR:
    return out << "GL_MAP_COLOR";
  case GL_MAP_STENCIL:
    return out << "GL_MAP_STENCIL";
  case GL_INDEX_SHIFT:
    return out << "GL_INDEX_SHIFT";
  case GL_INDEX_OFFSET:
    return out << "GL_INDEX_OFFSET";
  case GL_RED_SCALE:
    return out << "GL_RED_SCALE";
  case GL_RED_BIAS:
    return out << "GL_RED_BIAS";
  case GL_GREEN_SCALE:
    return out << "GL_GREEN_SCALE";
  case GL_GREEN_BIAS:
    return out << "GL_GREEN_BIAS";
  case GL_BLUE_SCALE:
    return out << "GL_BLUE_SCALE";
  case GL_BLUE_BIAS:
    return out << "GL_BLUE_BIAS";
  case GL_ALPHA_SCALE:
    return out << "GL_ALPHA_SCALE";
  case GL_ALPHA_BIAS:
    return out << "GL_ALPHA_BIAS";
  case GL_DEPTH_SCALE:
    return out << "GL_DEPTH_SCALE";
  case GL_DEPTH_BIAS:
    return out << "GL_DEPTH_BIAS";
  case GL_PIXEL_MAP_S_TO_S_SIZE:
    return out << "GL_PIXEL_MAP_S_TO_S_SIZE";
  case GL_PIXEL_MAP_I_TO_I_SIZE:
    return out << "GL_PIXEL_MAP_I_TO_I_SIZE";
  case GL_PIXEL_MAP_I_TO_R_SIZE:
    return out << "GL_PIXEL_MAP_I_TO_R_SIZE";
  case GL_PIXEL_MAP_I_TO_G_SIZE:
    return out << "GL_PIXEL_MAP_I_TO_G_SIZE";
  case GL_PIXEL_MAP_I_TO_B_SIZE:
    return out << "GL_PIXEL_MAP_I_TO_B_SIZE";
  case GL_PIXEL_MAP_I_TO_A_SIZE:
    return out << "GL_PIXEL_MAP_I_TO_A_SIZE";
  case GL_PIXEL_MAP_R_TO_R_SIZE:
    return out << "GL_PIXEL_MAP_R_TO_R_SIZE";
  case GL_PIXEL_MAP_G_TO_G_SIZE:
    return out << "GL_PIXEL_MAP_G_TO_G_SIZE";
  case GL_PIXEL_MAP_B_TO_B_SIZE:
    return out << "GL_PIXEL_MAP_B_TO_B_SIZE";
  case GL_PIXEL_MAP_A_TO_A_SIZE:
    return out << "GL_PIXEL_MAP_A_TO_A_SIZE";
  case GL_PIXEL_MAP_S_TO_S:
    return out << "GL_PIXEL_MAP_S_TO_S";
  case GL_PIXEL_MAP_I_TO_I:
    return out << "GL_PIXEL_MAP_I_TO_I";
  case GL_PIXEL_MAP_I_TO_R:
    return out << "GL_PIXEL_MAP_I_TO_R";
  case GL_PIXEL_MAP_I_TO_G:
    return out << "GL_PIXEL_MAP_I_TO_G";
  case GL_PIXEL_MAP_I_TO_B:
    return out << "GL_PIXEL_MAP_I_TO_B";
  case GL_PIXEL_MAP_I_TO_A:
    return out << "GL_PIXEL_MAP_I_TO_A";
  case GL_PIXEL_MAP_R_TO_R:
    return out << "GL_PIXEL_MAP_R_TO_R";
  case GL_PIXEL_MAP_G_TO_G:
    return out << "GL_PIXEL_MAP_G_TO_G";
  case GL_PIXEL_MAP_B_TO_B:
    return out << "GL_PIXEL_MAP_B_TO_B";
  case GL_PIXEL_MAP_A_TO_A:
    return out << "GL_PIXEL_MAP_A_TO_A";
  case GL_PACK_ALIGNMENT:
    return out << "GL_PACK_ALIGNMENT";
  case GL_PACK_LSB_FIRST:
    return out << "GL_PACK_LSB_FIRST";
  case GL_PACK_ROW_LENGTH:
    return out << "GL_PACK_ROW_LENGTH";
  case GL_PACK_SKIP_PIXELS:
    return out << "GL_PACK_SKIP_PIXELS";
  case GL_PACK_SKIP_ROWS:
    return out << "GL_PACK_SKIP_ROWS";
  case GL_PACK_SWAP_BYTES:
    return out << "GL_PACK_SWAP_BYTES";
  case GL_UNPACK_ALIGNMENT:
    return out << "GL_UNPACK_ALIGNMENT";
  case GL_UNPACK_LSB_FIRST:
    return out << "GL_UNPACK_LSB_FIRST";
  case GL_UNPACK_ROW_LENGTH:
    return out << "GL_UNPACK_ROW_LENGTH";
  case GL_UNPACK_SKIP_PIXELS:
    return out << "GL_UNPACK_SKIP_PIXELS";
  case GL_UNPACK_SKIP_ROWS:
    return out << "GL_UNPACK_SKIP_ROWS";
  case GL_UNPACK_SWAP_BYTES:
    return out << "GL_UNPACK_SWAP_BYTES";
  case GL_ZOOM_X:
    return out << "GL_ZOOM_X";
  case GL_ZOOM_Y:
    return out << "GL_ZOOM_Y";

    /* Texture mapping */
  case GL_TEXTURE_ENV:
    return out << "GL_TEXTURE_ENV";
  case GL_TEXTURE_ENV_MODE:
    return out << "GL_TEXTURE_ENV_MODE";
  case GL_TEXTURE_1D:
    return out << "GL_TEXTURE_1D";
  case GL_TEXTURE_2D:
    return out << "GL_TEXTURE_2D";
  case GL_TEXTURE_WRAP_S:
    return out << "GL_TEXTURE_WRAP_S";
  case GL_TEXTURE_WRAP_T:
    return out << "GL_TEXTURE_WRAP_T";
  case GL_TEXTURE_MAG_FILTER:
    return out << "GL_TEXTURE_MAG_FILTER";
  case GL_TEXTURE_MIN_FILTER:
    return out << "GL_TEXTURE_MIN_FILTER";
  case GL_TEXTURE_ENV_COLOR:
    return out << "GL_TEXTURE_ENV_COLOR";
  case GL_TEXTURE_GEN_S:
    return out << "GL_TEXTURE_GEN_S";
  case GL_TEXTURE_GEN_T:
    return out << "GL_TEXTURE_GEN_T";
  case GL_TEXTURE_GEN_MODE:
    return out << "GL_TEXTURE_GEN_MODE";
  case GL_TEXTURE_BORDER_COLOR:
    return out << "GL_TEXTURE_BORDER_COLOR";
  case GL_TEXTURE_WIDTH:
    return out << "GL_TEXTURE_WIDTH";
  case GL_TEXTURE_HEIGHT:
    return out << "GL_TEXTURE_HEIGHT";
  case GL_TEXTURE_BORDER:
    return out << "GL_TEXTURE_BORDER";
  case GL_TEXTURE_COMPONENTS:
    return out << "GL_TEXTURE_COMPONENTS";
  case GL_TEXTURE_RED_SIZE:
    return out << "GL_TEXTURE_RED_SIZE";
  case GL_TEXTURE_GREEN_SIZE:
    return out << "GL_TEXTURE_GREEN_SIZE";
  case GL_TEXTURE_BLUE_SIZE:
    return out << "GL_TEXTURE_BLUE_SIZE";
  case GL_TEXTURE_ALPHA_SIZE:
    return out << "GL_TEXTURE_ALPHA_SIZE";
  case GL_TEXTURE_LUMINANCE_SIZE:
    return out << "GL_TEXTURE_LUMINANCE_SIZE";
  case GL_TEXTURE_INTENSITY_SIZE:
    return out << "GL_TEXTURE_INTENSITY_SIZE";
  case GL_NEAREST_MIPMAP_NEAREST:
    return out << "GL_NEAREST_MIPMAP_NEAREST";
  case GL_NEAREST_MIPMAP_LINEAR:
    return out << "GL_NEAREST_MIPMAP_LINEAR";
  case GL_LINEAR_MIPMAP_NEAREST:
    return out << "GL_LINEAR_MIPMAP_NEAREST";
  case GL_LINEAR_MIPMAP_LINEAR:
    return out << "GL_LINEAR_MIPMAP_LINEAR";
  case GL_OBJECT_LINEAR:
    return out << "GL_OBJECT_LINEAR";
  case GL_OBJECT_PLANE:
    return out << "GL_OBJECT_PLANE";
  case GL_EYE_LINEAR:
    return out << "GL_EYE_LINEAR";
  case GL_EYE_PLANE:
    return out << "GL_EYE_PLANE";
  case GL_SPHERE_MAP:
    return out << "GL_SPHERE_MAP";
  case GL_DECAL:
    return out << "GL_DECAL";
  case GL_MODULATE:
    return out << "GL_MODULATE";
  case GL_NEAREST:
    return out << "GL_NEAREST";
  case GL_REPEAT:
    return out << "GL_REPEAT";
  case GL_CLAMP:
    return out << "GL_CLAMP";
  case GL_S:
    return out << "GL_S";
  case GL_T:
    return out << "GL_T";
  case GL_R:
    return out << "GL_R";
  case GL_Q:
    return out << "GL_Q";
  case GL_TEXTURE_GEN_R:
    return out << "GL_TEXTURE_GEN_R";
  case GL_TEXTURE_GEN_Q:
    return out << "GL_TEXTURE_GEN_Q";

    /* GL 1.1 texturing */
  case GL_PROXY_TEXTURE_1D:
    return out << "GL_PROXY_TEXTURE_1D";
  case GL_PROXY_TEXTURE_2D:
    return out << "GL_PROXY_TEXTURE_2D";
  case GL_TEXTURE_PRIORITY:
    return out << "GL_TEXTURE_PRIORITY";
  case GL_TEXTURE_RESIDENT:
    return out << "GL_TEXTURE_RESIDENT";
  case GL_TEXTURE_BINDING_1D:
    return out << "GL_TEXTURE_BINDING_1D";
  case GL_TEXTURE_BINDING_2D:
    return out << "GL_TEXTURE_BINDING_2D";
    /*
      case GL_TEXTURE_INTERNAL_FORMAT:
      return out << "GL_TEXTURE_INTERNAL_FORMAT";
    */

    /* GL 1.2 texturing */
  case GL_PACK_SKIP_IMAGES:
    return out << "GL_PACK_SKIP_IMAGES";
  case GL_PACK_IMAGE_HEIGHT:
    return out << "GL_PACK_IMAGE_HEIGHT";
  case GL_UNPACK_SKIP_IMAGES:
    return out << "GL_UNPACK_SKIP_IMAGES";
  case GL_UNPACK_IMAGE_HEIGHT:
    return out << "GL_UNPACK_IMAGE_HEIGHT";
  case GL_TEXTURE_3D:
    return out << "GL_TEXTURE_3D";
  case GL_PROXY_TEXTURE_3D:
    return out << "GL_PROXY_TEXTURE_3D";
  case GL_TEXTURE_DEPTH:
    return out << "GL_TEXTURE_DEPTH";
  case GL_TEXTURE_WRAP_R:
    return out << "GL_TEXTURE_WRAP_R";
  case GL_MAX_3D_TEXTURE_SIZE:
    return out << "GL_MAX_3D_TEXTURE_SIZE";
  case GL_TEXTURE_BINDING_3D:
    return out << "GL_TEXTURE_BINDING_3D";

    /* Internal texture formats (GL 1.1) */
  case GL_ALPHA4:
    return out << "GL_ALPHA4";
  case GL_ALPHA8:
    return out << "GL_ALPHA8";
  case GL_ALPHA12:
    return out << "GL_ALPHA12";
  case GL_ALPHA16:
    return out << "GL_ALPHA16";
  case GL_LUMINANCE4:
    return out << "GL_LUMINANCE4";
  case GL_LUMINANCE8:
    return out << "GL_LUMINANCE8";
  case GL_LUMINANCE12:
    return out << "GL_LUMINANCE12";
  case GL_LUMINANCE16:
    return out << "GL_LUMINANCE16";
  case GL_LUMINANCE4_ALPHA4:
    return out << "GL_LUMINANCE4_ALPHA4";
  case GL_LUMINANCE6_ALPHA2:
    return out << "GL_LUMINANCE6_ALPHA2";
  case GL_LUMINANCE8_ALPHA8:
    return out << "GL_LUMINANCE8_ALPHA8";
  case GL_LUMINANCE12_ALPHA4:
    return out << "GL_LUMINANCE12_ALPHA4";
  case GL_LUMINANCE12_ALPHA12:
    return out << "GL_LUMINANCE12_ALPHA12";
  case GL_LUMINANCE16_ALPHA16:
    return out << "GL_LUMINANCE16_ALPHA16";
  case GL_INTENSITY:
    return out << "GL_INTENSITY";
  case GL_INTENSITY4:
    return out << "GL_INTENSITY4";
  case GL_INTENSITY8:
    return out << "GL_INTENSITY8";
  case GL_INTENSITY12:
    return out << "GL_INTENSITY12";
  case GL_INTENSITY16:
    return out << "GL_INTENSITY16";
  case GL_R3_G3_B2:
    return out << "GL_R3_G3_B2";
  case GL_RGB4:
    return out << "GL_RGB4";
  case GL_RGB5:
    return out << "GL_RGB5";
  case GL_RGB8:
    return out << "GL_RGB8";
  case GL_RGB10:
    return out << "GL_RGB10";
  case GL_RGB12:
    return out << "GL_RGB12";
  case GL_RGB16:
    return out << "GL_RGB16";
  case GL_RGBA2:
    return out << "GL_RGBA2";
  case GL_RGBA4:
    return out << "GL_RGBA4";
  case GL_RGB5_A1:
    return out << "GL_RGB5_A1";
  case GL_RGBA8:
    return out << "GL_RGBA8";
  case GL_RGB10_A2:
    return out << "GL_RGB10_A2";
  case GL_RGBA12:
    return out << "GL_RGBA12";
  case GL_RGBA16:
    return out << "GL_RGBA16";

    /* Utility */
  case GL_VENDOR:
    return out << "GL_VENDOR";
  case GL_RENDERER:
    return out << "GL_RENDERER";
  case GL_VERSION:
    return out << "GL_VERSION";
  case GL_EXTENSIONS:
    return out << "GL_EXTENSIONS";

    /* Errors */
  case GL_INVALID_VALUE:
    return out << "GL_INVALID_VALUE";
  case GL_INVALID_ENUM:
    return out << "GL_INVALID_ENUM";
  case GL_INVALID_OPERATION:
    return out << "GL_INVALID_OPERATION";
  case GL_STACK_OVERFLOW:
    return out << "GL_STACK_OVERFLOW";
  case GL_STACK_UNDERFLOW:
    return out << "GL_STACK_UNDERFLOW";
  case GL_OUT_OF_MEMORY:
    return out << "GL_OUT_OF_MEMORY";

    /* OpenGL 1.2 */
  case GL_RESCALE_NORMAL:
    return out << "GL_RESCALE_NORMAL";
  case GL_CLAMP_TO_EDGE:
    return out << "GL_CLAMP_TO_EDGE";
  case GL_MAX_ELEMENTS_VERTICES:
    return out << "GL_MAX_ELEMENTS_VERTICES";
  case GL_MAX_ELEMENTS_INDICES:
    return out << "GL_MAX_ELEMENTS_INDICES";
  case GL_BGR:
    return out << "GL_BGR";
  case GL_BGRA:
    return out << "GL_BGRA";
  case GL_UNSIGNED_BYTE_3_3_2:
    return out << "GL_UNSIGNED_BYTE_3_3_2";
  case GL_UNSIGNED_BYTE_2_3_3_REV:
    return out << "GL_UNSIGNED_BYTE_2_3_3_REV";
  case GL_UNSIGNED_SHORT_5_6_5:
    return out << "GL_UNSIGNED_SHORT_5_6_5";
  case GL_UNSIGNED_SHORT_5_6_5_REV:
    return out << "GL_UNSIGNED_SHORT_5_6_5_REV";
  case GL_UNSIGNED_SHORT_4_4_4_4:
    return out << "GL_UNSIGNED_SHORT_4_4_4_4";
  case GL_UNSIGNED_SHORT_4_4_4_4_REV:
    return out << "GL_UNSIGNED_SHORT_4_4_4_4_REV";
  case GL_UNSIGNED_SHORT_5_5_5_1:
    return out << "GL_UNSIGNED_SHORT_5_5_5_1";
  case GL_UNSIGNED_SHORT_1_5_5_5_REV:
    return out << "GL_UNSIGNED_SHORT_1_5_5_5_REV";
  case GL_UNSIGNED_INT_8_8_8_8:
    return out << "GL_UNSIGNED_INT_8_8_8_8";
  case GL_UNSIGNED_INT_8_8_8_8_REV:
    return out << "GL_UNSIGNED_INT_8_8_8_8_REV";
  case GL_UNSIGNED_INT_10_10_10_2:
    return out << "GL_UNSIGNED_INT_10_10_10_2";
  case GL_UNSIGNED_INT_2_10_10_10_REV:
    return out << "GL_UNSIGNED_INT_2_10_10_10_REV";
  case GL_LIGHT_MODEL_COLOR_CONTROL:
    return out << "GL_LIGHT_MODEL_COLOR_CONTROL";
  case GL_SINGLE_COLOR:
    return out << "GL_SINGLE_COLOR";
  case GL_SEPARATE_SPECULAR_COLOR:
    return out << "GL_SEPARATE_SPECULAR_COLOR";
  case GL_TEXTURE_MIN_LOD:
    return out << "GL_TEXTURE_MIN_LOD";
  case GL_TEXTURE_MAX_LOD:
    return out << "GL_TEXTURE_MAX_LOD";
  case GL_TEXTURE_BASE_LEVEL:
    return out << "GL_TEXTURE_BASE_LEVEL";
  case GL_TEXTURE_MAX_LEVEL:
    return out << "GL_TEXTURE_MAX_LEVEL";
  }

  return out << (int)v;
}
#endif
