Merge branch 'llvm-cliptest-viewport'
[mesa.git] / src / mesa / main / bufferobj.c
index 1f2070ef4735548fe71ed1ecb25c8c8176fc6940..0a68008a1003fba28671f42a0f83517b54f68d87 100644 (file)
@@ -1,9 +1,9 @@
 /*
  * Mesa 3-D graphics library
- * Version:  7.5
+ * Version:  7.6
  *
  * Copyright (C) 1999-2008  Brian Paul   All Rights Reserved.
- * Copyright (C) 1999-2009  VMware, Inc.  All Rights Reserved.
+ * Copyright (C) 2009  VMware, Inc.  All Rights Reserved.
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
  * copy of this software and associated documentation files (the "Software"),
 
 
 #include "glheader.h"
+#include "enums.h"
 #include "hash.h"
 #include "imports.h"
 #include "image.h"
 #include "context.h"
 #include "bufferobj.h"
+#include "fbobject.h"
+#include "texobj.h"
 
 
-#ifdef FEATURE_OES_mapbuffer
-#define DEFAULT_ACCESS GL_WRITE_ONLY;
+/* Debug flags */
+/*#define VBO_DEBUG*/
+/*#define BOUNDS_CHECK*/
+
+
+#if FEATURE_OES_mapbuffer
+#define DEFAULT_ACCESS GL_MAP_WRITE_BIT
 #else
-#define DEFAULT_ACCESS GL_READ_WRITE;
+#define DEFAULT_ACCESS (GL_MAP_READ_BIT | GL_MAP_WRITE_BIT)
 #endif
 
 
 /**
- * Get the buffer object bound to the specified target in a GL context.
- *
- * \param ctx     GL context
- * \param target  Buffer object target to be retrieved.  Currently this must
- *                be either \c GL_ARRAY_BUFFER or \c GL_ELEMENT_ARRAY_BUFFER.
- * \return   A pointer to the buffer object bound to \c target in the
+ * Return pointer to address of a buffer object target.
+ * \param ctx  the GL context
+ * \param target  the buffer object target to be retrieved.
+ * \return   pointer to pointer to the buffer object bound to \c target in the
  *           specified context or \c NULL if \c target is invalid.
  */
-static INLINE struct gl_buffer_object *
-get_buffer(GLcontext *ctx, GLenum target)
+static INLINE struct gl_buffer_object **
+get_buffer_target(struct gl_context *ctx, GLenum target)
 {
-   struct gl_buffer_object * bufObj = NULL;
-
    switch (target) {
-      case GL_ARRAY_BUFFER_ARB:
-         bufObj = ctx->Array.ArrayBufferObj;
-         break;
-      case GL_ELEMENT_ARRAY_BUFFER_ARB:
-         bufObj = ctx->Array.ElementArrayBufferObj;
-         break;
-      case GL_PIXEL_PACK_BUFFER_EXT:
-         bufObj = ctx->Pack.BufferObj;
-         break;
-      case GL_PIXEL_UNPACK_BUFFER_EXT:
-         bufObj = ctx->Unpack.BufferObj;
-         break;
-      default:
-         /* error must be recorded by caller */
-         return NULL;
+   case GL_ARRAY_BUFFER_ARB:
+      return &ctx->Array.ArrayBufferObj;
+   case GL_ELEMENT_ARRAY_BUFFER_ARB:
+      return &ctx->Array.ElementArrayBufferObj;
+   case GL_PIXEL_PACK_BUFFER_EXT:
+      return &ctx->Pack.BufferObj;
+   case GL_PIXEL_UNPACK_BUFFER_EXT:
+      return &ctx->Unpack.BufferObj;
+   case GL_COPY_READ_BUFFER:
+      return &ctx->CopyReadBuffer;
+   case GL_COPY_WRITE_BUFFER:
+      return &ctx->CopyWriteBuffer;
+#if FEATURE_EXT_transform_feedback
+   case GL_TRANSFORM_FEEDBACK_BUFFER:
+      if (ctx->Extensions.EXT_transform_feedback) {
+         return &ctx->TransformFeedback.CurrentBuffer;
+      }
+      break;
+#endif
+   default:
+      return NULL;
    }
+   return NULL;
+}
 
-   /* bufObj should point to NullBufferObj or a user-created buffer object */
-   ASSERT(bufObj);
 
-   return bufObj;
+/**
+ * Get the buffer object bound to the specified target in a GL context.
+ * \param ctx  the GL context
+ * \param target  the buffer object target to be retrieved.
+ * \return   pointer to the buffer object bound to \c target in the
+ *           specified context or \c NULL if \c target is invalid.
+ */
+static INLINE struct gl_buffer_object *
+get_buffer(struct gl_context *ctx, GLenum target)
+{
+   struct gl_buffer_object **bufObj = get_buffer_target(ctx, target);
+   if (bufObj)
+      return *bufObj;
+   return NULL;
+}
+
+
+/**
+ * Convert a GLbitfield describing the mapped buffer access flags
+ * into one of GL_READ_WRITE, GL_READ_ONLY, or GL_WRITE_ONLY.
+ */
+static GLenum
+simplified_access_mode(GLbitfield access)
+{
+   const GLbitfield rwFlags = GL_MAP_READ_BIT | GL_MAP_WRITE_BIT;
+   if ((access & rwFlags) == rwFlags)
+      return GL_READ_WRITE;
+   if ((access & GL_MAP_READ_BIT) == GL_MAP_READ_BIT)
+      return GL_READ_ONLY;
+   if ((access & GL_MAP_WRITE_BIT) == GL_MAP_WRITE_BIT)
+      return GL_WRITE_ONLY;
+   return GL_READ_WRITE; /* this should never happen, but no big deal */
 }
 
 
@@ -102,7 +143,7 @@ get_buffer(GLcontext *ctx, GLenum target)
  * \sa glBufferSubDataARB, glGetBufferSubDataARB
  */
 static struct gl_buffer_object *
-buffer_object_subdata_range_good( GLcontext * ctx, GLenum target, 
+buffer_object_subdata_range_good( struct gl_context * ctx, GLenum target, 
                                   GLintptrARB offset, GLsizeiptrARB size,
                                   const char *caller )
 {
@@ -123,7 +164,7 @@ buffer_object_subdata_range_good( GLcontext * ctx, GLenum target,
       _mesa_error(ctx, GL_INVALID_ENUM, "%s(target)", caller);
       return NULL;
    }
-   if (bufObj->Name == 0) {
+   if (!_mesa_is_bufferobj(bufObj)) {
       _mesa_error(ctx, GL_INVALID_OPERATION, "%s", caller);
       return NULL;
    }
@@ -132,7 +173,7 @@ buffer_object_subdata_range_good( GLcontext * ctx, GLenum target,
                  "%s(size + offset > buffer size)", caller);
       return NULL;
    }
-   if (bufObj->Pointer) {
+   if (_mesa_bufferobj_mapped(bufObj)) {
       /* Buffer is currently mapped */
       _mesa_error(ctx, GL_INVALID_OPERATION, "%s", caller);
       return NULL;
@@ -147,8 +188,8 @@ buffer_object_subdata_range_good( GLcontext * ctx, GLenum target,
  * 
  * Default callback for the \c dd_function_table::NewBufferObject() hook.
  */
-struct gl_buffer_object *
-_mesa_new_buffer_object( GLcontext *ctx, GLuint name, GLenum target )
+static struct gl_buffer_object *
+_mesa_new_buffer_object( struct gl_context *ctx, GLuint name, GLenum target )
 {
    struct gl_buffer_object *obj;
 
@@ -165,19 +206,20 @@ _mesa_new_buffer_object( GLcontext *ctx, GLuint name, GLenum target )
  * 
  * Default callback for the \c dd_function_table::DeleteBuffer() hook.
  */
-void
-_mesa_delete_buffer_object( GLcontext *ctx, struct gl_buffer_object *bufObj )
+static void
+_mesa_delete_buffer_object( struct gl_context *ctx, struct gl_buffer_object *bufObj )
 {
    (void) ctx;
 
    if (bufObj->Data)
-      _mesa_free(bufObj->Data);
+      free(bufObj->Data);
 
    /* assign strange values here to help w/ debugging */
    bufObj->RefCount = -1000;
    bufObj->Name = ~0;
 
-   _mesa_free(bufObj);
+   _glthread_DESTROY_MUTEX(bufObj->Mutex);
+   free(bufObj);
 }
 
 
@@ -186,7 +228,7 @@ _mesa_delete_buffer_object( GLcontext *ctx, struct gl_buffer_object *bufObj )
  * Set ptr to bufObj w/ reference counting.
  */
 void
-_mesa_reference_buffer_object(GLcontext *ctx,
+_mesa_reference_buffer_object(struct gl_context *ctx,
                               struct gl_buffer_object **ptr,
                               struct gl_buffer_object *bufObj)
 {
@@ -198,7 +240,7 @@ _mesa_reference_buffer_object(GLcontext *ctx,
       GLboolean deleteFlag = GL_FALSE;
       struct gl_buffer_object *oldObj = *ptr;
 
-      /*_glthread_LOCK_MUTEX(oldObj->Mutex);*/
+      _glthread_LOCK_MUTEX(oldObj->Mutex);
       ASSERT(oldObj->RefCount > 0);
       oldObj->RefCount--;
 #if 0
@@ -206,7 +248,7 @@ _mesa_reference_buffer_object(GLcontext *ctx,
              (void *) oldObj, oldObj->Name, oldObj->RefCount);
 #endif
       deleteFlag = (oldObj->RefCount == 0);
-      /*_glthread_UNLOCK_MUTEX(oldObj->Mutex);*/
+      _glthread_UNLOCK_MUTEX(oldObj->Mutex);
 
       if (deleteFlag) {
 
@@ -228,7 +270,7 @@ _mesa_reference_buffer_object(GLcontext *ctx,
 
    if (bufObj) {
       /* reference new buffer */
-      /*_glthread_LOCK_MUTEX(tex->Mutex);*/
+      _glthread_LOCK_MUTEX(bufObj->Mutex);
       if (bufObj->RefCount == 0) {
          /* this buffer's being deleted (look just above) */
          /* Not sure this can every really happen.  Warn if it does. */
@@ -243,7 +285,7 @@ _mesa_reference_buffer_object(GLcontext *ctx,
 #endif
          *ptr = bufObj;
       }
-      /*_glthread_UNLOCK_MUTEX(tex->Mutex);*/
+      _glthread_UNLOCK_MUTEX(bufObj->Mutex);
    }
 }
 
@@ -257,11 +299,12 @@ _mesa_initialize_buffer_object( struct gl_buffer_object *obj,
 {
    (void) target;
 
-   _mesa_bzero(obj, sizeof(struct gl_buffer_object));
+   memset(obj, 0, sizeof(struct gl_buffer_object));
+   _glthread_INIT_MUTEX(obj->Mutex);
    obj->RefCount = 1;
    obj->Name = name;
    obj->Usage = GL_STATIC_DRAW_ARB;
-   obj->Access = DEFAULT_ACCESS;
+   obj->AccessFlags = DEFAULT_ACCESS;
 }
 
 
@@ -281,10 +324,11 @@ _mesa_initialize_buffer_object( struct gl_buffer_object *obj,
  * \param usage   Hints about how the data will be used.
  * \param bufObj  Object to be used.
  *
+ * \return GL_TRUE for success, GL_FALSE for failure
  * \sa glBufferDataARB, dd_function_table::BufferData.
  */
-void
-_mesa_buffer_data( GLcontext *ctx, GLenum target, GLsizeiptrARB size,
+static GLboolean
+_mesa_buffer_data( struct gl_context *ctx, GLenum target, GLsizeiptrARB size,
                   const GLvoid * data, GLenum usage,
                   struct gl_buffer_object * bufObj )
 {
@@ -299,8 +343,13 @@ _mesa_buffer_data( GLcontext *ctx, GLenum target, GLsizeiptrARB size,
       bufObj->Usage = usage;
 
       if (data) {
-        _mesa_memcpy( bufObj->Data, data, size );
+        memcpy( bufObj->Data, data, size );
       }
+
+      return GL_TRUE;
+   }
+   else {
+      return GL_FALSE;
    }
 }
 
@@ -322,8 +371,8 @@ _mesa_buffer_data( GLcontext *ctx, GLenum target, GLsizeiptrARB size,
  *
  * \sa glBufferSubDataARB, dd_function_table::BufferSubData.
  */
-void
-_mesa_buffer_subdata( GLcontext *ctx, GLenum target, GLintptrARB offset,
+static void
+_mesa_buffer_subdata( struct gl_context *ctx, GLenum target, GLintptrARB offset,
                      GLsizeiptrARB size, const GLvoid * data,
                      struct gl_buffer_object * bufObj )
 {
@@ -333,7 +382,7 @@ _mesa_buffer_subdata( GLcontext *ctx, GLenum target, GLintptrARB offset,
    ASSERT(size + offset <= bufObj->Size);
 
    if (bufObj->Data) {
-      _mesa_memcpy( (GLubyte *) bufObj->Data + offset, data, size );
+      memcpy( (GLubyte *) bufObj->Data + offset, data, size );
    }
 }
 
@@ -355,15 +404,15 @@ _mesa_buffer_subdata( GLcontext *ctx, GLenum target, GLintptrARB offset,
  *
  * \sa glBufferGetSubDataARB, dd_function_table::GetBufferSubData.
  */
-void
-_mesa_buffer_get_subdata( GLcontext *ctx, GLenum target, GLintptrARB offset,
+static void
+_mesa_buffer_get_subdata( struct gl_context *ctx, GLenum target, GLintptrARB offset,
                          GLsizeiptrARB size, GLvoid * data,
                          struct gl_buffer_object * bufObj )
 {
    (void) ctx; (void) target;
 
    if (bufObj->Data && ((GLsizeiptrARB) (size + offset) <= bufObj->Size)) {
-      _mesa_memcpy( data, (GLubyte *) bufObj->Data + offset, size );
+      memcpy( data, (GLubyte *) bufObj->Data + offset, size );
    }
 }
 
@@ -382,23 +431,64 @@ _mesa_buffer_get_subdata( GLcontext *ctx, GLenum target, GLintptrARB offset,
  *
  * \sa glMapBufferARB, dd_function_table::MapBuffer
  */
-void *
-_mesa_buffer_map( GLcontext *ctx, GLenum target, GLenum access,
+static void *
+_mesa_buffer_map( struct gl_context *ctx, GLenum target, GLenum access,
                  struct gl_buffer_object *bufObj )
 {
    (void) ctx;
    (void) target;
    (void) access;
    /* Just return a direct pointer to the data */
-   if (bufObj->Pointer) {
+   if (_mesa_bufferobj_mapped(bufObj)) {
       /* already mapped! */
       return NULL;
    }
    bufObj->Pointer = bufObj->Data;
+   bufObj->Length = bufObj->Size;
+   bufObj->Offset = 0;
+   return bufObj->Pointer;
+}
+
+
+/**
+ * Default fallback for \c dd_function_table::MapBufferRange().
+ * Called via glMapBufferRange().
+ */
+static void *
+_mesa_buffer_map_range( struct gl_context *ctx, GLenum target, GLintptr offset,
+                        GLsizeiptr length, GLbitfield access,
+                        struct gl_buffer_object *bufObj )
+{
+   (void) ctx;
+   (void) target;
+   assert(!_mesa_bufferobj_mapped(bufObj));
+   /* Just return a direct pointer to the data */
+   bufObj->Pointer = bufObj->Data + offset;
+   bufObj->Length = length;
+   bufObj->Offset = offset;
+   bufObj->AccessFlags = access;
    return bufObj->Pointer;
 }
 
 
+/**
+ * Default fallback for \c dd_function_table::FlushMappedBufferRange().
+ * Called via glFlushMappedBufferRange().
+ */
+static void
+_mesa_buffer_flush_mapped_range( struct gl_context *ctx, GLenum target, 
+                                 GLintptr offset, GLsizeiptr length,
+                                 struct gl_buffer_object *obj )
+{
+   (void) ctx;
+   (void) target;
+   (void) offset;
+   (void) length;
+   (void) obj;
+   /* no-op */
+}
+
+
 /**
  * Default callback for \c dd_function_table::MapBuffer().
  *
@@ -406,59 +496,100 @@ _mesa_buffer_map( GLcontext *ctx, GLenum target, GLenum access,
  *
  * \sa glUnmapBufferARB, dd_function_table::UnmapBuffer
  */
-GLboolean
-_mesa_buffer_unmap( GLcontext *ctx, GLenum target,
+static GLboolean
+_mesa_buffer_unmap( struct gl_context *ctx, GLenum target,
                     struct gl_buffer_object *bufObj )
 {
    (void) ctx;
    (void) target;
    /* XXX we might assert here that bufObj->Pointer is non-null */
    bufObj->Pointer = NULL;
+   bufObj->Length = 0;
+   bufObj->Offset = 0;
+   bufObj->AccessFlags = 0x0;
    return GL_TRUE;
 }
 
 
+/**
+ * Default fallback for \c dd_function_table::CopyBufferSubData().
+ * Called via glCopyBuffserSubData().
+ */
+static void
+_mesa_copy_buffer_subdata(struct gl_context *ctx,
+                          struct gl_buffer_object *src,
+                          struct gl_buffer_object *dst,
+                          GLintptr readOffset, GLintptr writeOffset,
+                          GLsizeiptr size)
+{
+   GLubyte *srcPtr, *dstPtr;
+
+   /* buffer should not already be mapped */
+   assert(!_mesa_bufferobj_mapped(src));
+   assert(!_mesa_bufferobj_mapped(dst));
+
+   srcPtr = (GLubyte *) ctx->Driver.MapBuffer(ctx, GL_COPY_READ_BUFFER,
+                                              GL_READ_ONLY, src);
+   dstPtr = (GLubyte *) ctx->Driver.MapBuffer(ctx, GL_COPY_WRITE_BUFFER,
+                                              GL_WRITE_ONLY, dst);
+
+   if (srcPtr && dstPtr)
+      memcpy(dstPtr + writeOffset, srcPtr + readOffset, size);
+
+   ctx->Driver.UnmapBuffer(ctx, GL_COPY_READ_BUFFER, src);
+   ctx->Driver.UnmapBuffer(ctx, GL_COPY_WRITE_BUFFER, dst);
+}
+
+
+
 /**
  * Initialize the state associated with buffer objects
  */
 void
-_mesa_init_buffer_objects( GLcontext *ctx )
+_mesa_init_buffer_objects( struct gl_context *ctx )
+{
+   _mesa_reference_buffer_object(ctx, &ctx->Array.ArrayBufferObj,
+                                 ctx->Shared->NullBufferObj);
+   _mesa_reference_buffer_object(ctx, &ctx->Array.ElementArrayBufferObj,
+                                 ctx->Shared->NullBufferObj);
+
+   _mesa_reference_buffer_object(ctx, &ctx->CopyReadBuffer,
+                                 ctx->Shared->NullBufferObj);
+   _mesa_reference_buffer_object(ctx, &ctx->CopyWriteBuffer,
+                                 ctx->Shared->NullBufferObj);
+}
+
+
+void
+_mesa_free_buffer_objects( struct gl_context *ctx )
 {
-   ctx->Array.ArrayBufferObj = ctx->Shared->NullBufferObj;
-   ctx->Array.ElementArrayBufferObj = ctx->Shared->NullBufferObj;
+   _mesa_reference_buffer_object(ctx, &ctx->Array.ArrayBufferObj, NULL);
+   _mesa_reference_buffer_object(ctx, &ctx->Array.ElementArrayBufferObj, NULL);
+
+   _mesa_reference_buffer_object(ctx, &ctx->CopyReadBuffer, NULL);
+   _mesa_reference_buffer_object(ctx, &ctx->CopyWriteBuffer, NULL);
 }
 
 
 /**
  * Bind the specified target to buffer for the specified context.
+ * Called by glBindBuffer() and other functions.
  */
 static void
-bind_buffer_object(GLcontext *ctx, GLenum target, GLuint buffer)
+bind_buffer_object(struct gl_context *ctx, GLenum target, GLuint buffer)
 {
    struct gl_buffer_object *oldBufObj;
    struct gl_buffer_object *newBufObj = NULL;
    struct gl_buffer_object **bindTarget = NULL;
 
-   switch (target) {
-   case GL_ARRAY_BUFFER_ARB:
-      bindTarget = &ctx->Array.ArrayBufferObj;
-      break;
-   case GL_ELEMENT_ARRAY_BUFFER_ARB:
-      bindTarget = &ctx->Array.ElementArrayBufferObj;
-      break;
-   case GL_PIXEL_PACK_BUFFER_EXT:
-      bindTarget = &ctx->Pack.BufferObj;
-      break;
-   case GL_PIXEL_UNPACK_BUFFER_EXT:
-      bindTarget = &ctx->Unpack.BufferObj;
-      break;
-   default:
-      _mesa_error(ctx, GL_INVALID_ENUM, "glBindBufferARB(target)");
+   bindTarget = get_buffer_target(ctx, target);
+   if (!bindTarget) {
+      _mesa_error(ctx, GL_INVALID_ENUM, "glBindBufferARB(target 0x%x)", target);
       return;
    }
 
    /* Get pointer to old buffer object (to be unbound) */
-   oldBufObj = get_buffer(ctx, target);
+   oldBufObj = *bindTarget;
    if (oldBufObj && oldBufObj->Name == buffer)
       return;   /* rebinding the same buffer object- no change */
 
@@ -490,7 +621,7 @@ bind_buffer_object(GLcontext *ctx, GLenum target, GLuint buffer)
    _mesa_reference_buffer_object(ctx, bindTarget, newBufObj);
 
    /* Pass BindBuffer call to device driver */
-   if (ctx->Driver.BindBuffer && newBufObj)
+   if (ctx->Driver.BindBuffer)
       ctx->Driver.BindBuffer( ctx, target, newBufObj );
 }
 
@@ -501,7 +632,7 @@ bind_buffer_object(GLcontext *ctx, GLenum target, GLuint buffer)
  * shared state.
  */
 void
-_mesa_update_default_objects_buffer_objects(GLcontext *ctx)
+_mesa_update_default_objects_buffer_objects(struct gl_context *ctx)
 {
    /* Bind the NullBufferObj to remove references to those
     * in the shared context hash table.
@@ -523,6 +654,8 @@ _mesa_update_default_objects_buffer_objects(GLcontext *ctx)
  * currently mapped.  Whoever calls this function should check for that.
  * Remember, we can't use a PBO when it's mapped!
  *
+ * If we're not using a PBO, this is a no-op.
+ *
  * \param width  width of image to read/write
  * \param height  height of image to read/write
  * \param depth  depth of image to read/write
@@ -541,7 +674,8 @@ _mesa_validate_pbo_access(GLuint dimensions,
    GLvoid *start, *end;
    const GLubyte *sizeAddr; /* buffer size, cast to a pointer */
 
-   ASSERT(pack->BufferObj->Name != 0);
+   if (!_mesa_is_bufferobj(pack->BufferObj))
+      return GL_TRUE;  /* no PBO, OK */
 
    if (pack->BufferObj->Size == 0)
       /* no buffer! */
@@ -573,21 +707,22 @@ _mesa_validate_pbo_access(GLuint dimensions,
 
 
 /**
- * If the source of glBitmap data is a PBO, check that we won't read out
- * of buffer bounds, then map the buffer.
- * If not sourcing from a PBO, just return the bitmap pointer.
- * This is a helper function for (some) drivers.
- * Return NULL if error.
- * If non-null return, must call _mesa_unmap_bitmap_pbo() when done.
+ * For commands that read from a PBO (glDrawPixels, glTexImage,
+ * glPolygonStipple, etc), if we're reading from a PBO, map it read-only
+ * and return the pointer into the PBO.  If we're not reading from a
+ * PBO, return \p src as-is.
+ * If non-null return, must call _mesa_unmap_pbo_source() when done.
+ *
+ * \return NULL if error, else pointer to start of data
  */
-const GLubyte *
-_mesa_map_bitmap_pbo(GLcontext *ctx,
+const GLvoid *
+_mesa_map_pbo_source(struct gl_context *ctx,
                      const struct gl_pixelstore_attrib *unpack,
-                     const GLubyte *bitmap)
+                     const GLvoid *src)
 {
    const GLubyte *buf;
 
-   if (unpack->BufferObj->Name) {
+   if (_mesa_is_bufferobj(unpack->BufferObj)) {
       /* unpack from PBO */
       buf = (GLubyte *) ctx->Driver.MapBuffer(ctx, GL_PIXEL_UNPACK_BUFFER_EXT,
                                               GL_READ_ONLY_ARB,
@@ -595,11 +730,11 @@ _mesa_map_bitmap_pbo(GLcontext *ctx,
       if (!buf)
          return NULL;
 
-      buf = ADD_POINTERS(buf, bitmap);
+      buf = ADD_POINTERS(buf, src);
    }
    else {
       /* unpack from normal memory */
-      buf = bitmap;
+      buf = src;
    }
 
    return buf;
@@ -607,57 +742,55 @@ _mesa_map_bitmap_pbo(GLcontext *ctx,
 
 
 /**
- * Counterpart to _mesa_map_bitmap_pbo()
- * This is a helper function for (some) drivers.
- */
-void
-_mesa_unmap_bitmap_pbo(GLcontext *ctx,
-                       const struct gl_pixelstore_attrib *unpack)
-{
-   if (unpack->BufferObj->Name) {
-      ctx->Driver.UnmapBuffer(ctx, GL_PIXEL_UNPACK_BUFFER_EXT,
-                              unpack->BufferObj);
-   }
-}
-
-
-/**
- * \sa _mesa_map_bitmap_pbo
+ * Combine PBO-read validation and mapping.
+ * If any GL errors are detected, they'll be recorded and NULL returned.
+ * \sa _mesa_validate_pbo_access
+ * \sa _mesa_map_pbo_source
+ * A call to this function should have a matching call to
+ * _mesa_unmap_pbo_source().
  */
 const GLvoid *
-_mesa_map_drawpix_pbo(GLcontext *ctx,
-                      const struct gl_pixelstore_attrib *unpack,
-                      const GLvoid *pixels)
+_mesa_map_validate_pbo_source(struct gl_context *ctx,
+                              GLuint dimensions,
+                              const struct gl_pixelstore_attrib *unpack,
+                              GLsizei width, GLsizei height, GLsizei depth,
+                              GLenum format, GLenum type, const GLvoid *ptr,
+                              const char *where)
 {
-   const GLvoid *buf;
+   ASSERT(dimensions == 1 || dimensions == 2 || dimensions == 3);
 
-   if (unpack->BufferObj->Name) {
-      /* unpack from PBO */
-      buf = (GLubyte *) ctx->Driver.MapBuffer(ctx, GL_PIXEL_UNPACK_BUFFER_EXT,
-                                              GL_READ_ONLY_ARB,
-                                              unpack->BufferObj);
-      if (!buf)
-         return NULL;
+   if (!_mesa_is_bufferobj(unpack->BufferObj)) {
+      /* non-PBO access: no validation to be done */
+      return ptr;
+   }
 
-      buf = ADD_POINTERS(buf, pixels);
+   if (!_mesa_validate_pbo_access(dimensions, unpack,
+                                  width, height, depth, format, type, ptr)) {
+      _mesa_error(ctx, GL_INVALID_OPERATION,
+                  "%s(out of bounds PBO access)", where);
+      return NULL;
    }
-   else {
-      /* unpack from normal memory */
-      buf = pixels;
+
+   if (_mesa_bufferobj_mapped(unpack->BufferObj)) {
+      /* buffer is already mapped - that's an error */
+      _mesa_error(ctx, GL_INVALID_OPERATION, "%s(PBO is mapped)", where);
+      return NULL;
    }
 
-   return buf;
+   ptr = _mesa_map_pbo_source(ctx, unpack, ptr);
+   return ptr;
 }
 
 
 /**
- * \sa _mesa_unmap_bitmap_pbo
+ * Counterpart to _mesa_map_pbo_source()
  */
 void
-_mesa_unmap_drawpix_pbo(GLcontext *ctx,
-                        const struct gl_pixelstore_attrib *unpack)
+_mesa_unmap_pbo_source(struct gl_context *ctx,
+                       const struct gl_pixelstore_attrib *unpack)
 {
-   if (unpack->BufferObj->Name) {
+   ASSERT(unpack != &ctx->Pack); /* catch pack/unpack mismatch */
+   if (_mesa_is_bufferobj(unpack->BufferObj)) {
       ctx->Driver.UnmapBuffer(ctx, GL_PIXEL_UNPACK_BUFFER_EXT,
                               unpack->BufferObj);
    }
@@ -665,18 +798,21 @@ _mesa_unmap_drawpix_pbo(GLcontext *ctx,
 
 
 /**
- * If PBO is bound, map the buffer, return dest pointer in mapped buffer.
- * Call _mesa_unmap_readpix_pbo() when finished
- * \return NULL if error
+ * For commands that write to a PBO (glReadPixels, glGetColorTable, etc),
+ * if we're writing to a PBO, map it write-only and return the pointer
+ * into the PBO.  If we're not writing to a PBO, return \p dst as-is.
+ * If non-null return, must call _mesa_unmap_pbo_dest() when done.
+ *
+ * \return NULL if error, else pointer to start of data
  */
 void *
-_mesa_map_readpix_pbo(GLcontext *ctx,
-                      const struct gl_pixelstore_attrib *pack,
-                      GLvoid *dest)
+_mesa_map_pbo_dest(struct gl_context *ctx,
+                   const struct gl_pixelstore_attrib *pack,
+                   GLvoid *dest)
 {
    void *buf;
 
-   if (pack->BufferObj->Name) {
+   if (_mesa_is_bufferobj(pack->BufferObj)) {
       /* pack into PBO */
       buf = (GLubyte *) ctx->Driver.MapBuffer(ctx, GL_PIXEL_PACK_BUFFER_EXT,
                                               GL_WRITE_ONLY_ARB,
@@ -696,13 +832,55 @@ _mesa_map_readpix_pbo(GLcontext *ctx,
 
 
 /**
- * Counterpart to _mesa_map_readpix_pbo()
+ * Combine PBO-write validation and mapping.
+ * If any GL errors are detected, they'll be recorded and NULL returned.
+ * \sa _mesa_validate_pbo_access
+ * \sa _mesa_map_pbo_dest
+ * A call to this function should have a matching call to
+ * _mesa_unmap_pbo_dest().
+ */
+GLvoid *
+_mesa_map_validate_pbo_dest(struct gl_context *ctx,
+                            GLuint dimensions,
+                            const struct gl_pixelstore_attrib *unpack,
+                            GLsizei width, GLsizei height, GLsizei depth,
+                            GLenum format, GLenum type, GLvoid *ptr,
+                            const char *where)
+{
+   ASSERT(dimensions == 1 || dimensions == 2 || dimensions == 3);
+
+   if (!_mesa_is_bufferobj(unpack->BufferObj)) {
+      /* non-PBO access: no validation to be done */
+      return ptr;
+   }
+
+   if (!_mesa_validate_pbo_access(dimensions, unpack,
+                                  width, height, depth, format, type, ptr)) {
+      _mesa_error(ctx, GL_INVALID_OPERATION,
+                  "%s(out of bounds PBO access)", where);
+      return NULL;
+   }
+
+   if (_mesa_bufferobj_mapped(unpack->BufferObj)) {
+      /* buffer is already mapped - that's an error */
+      _mesa_error(ctx, GL_INVALID_OPERATION, "%s(PBO is mapped)", where);
+      return NULL;
+   }
+
+   ptr = _mesa_map_pbo_dest(ctx, unpack, ptr);
+   return ptr;
+}
+
+
+/**
+ * Counterpart to _mesa_map_pbo_dest()
  */
 void
-_mesa_unmap_readpix_pbo(GLcontext *ctx,
-                        const struct gl_pixelstore_attrib *pack)
+_mesa_unmap_pbo_dest(struct gl_context *ctx,
+                     const struct gl_pixelstore_attrib *pack)
 {
-   if (pack->BufferObj->Name) {
+   ASSERT(pack != &ctx->Unpack); /* catch pack/unpack mismatch */
+   if (_mesa_is_bufferobj(pack->BufferObj)) {
       ctx->Driver.UnmapBuffer(ctx, GL_PIXEL_PACK_BUFFER_EXT, pack->BufferObj);
    }
 }
@@ -714,7 +892,7 @@ _mesa_unmap_readpix_pbo(GLcontext *ctx,
  * Always return NULL for ID 0.
  */
 struct gl_buffer_object *
-_mesa_lookup_bufferobj(GLcontext *ctx, GLuint buffer)
+_mesa_lookup_bufferobj(struct gl_context *ctx, GLuint buffer)
 {
    if (buffer == 0)
       return NULL;
@@ -731,7 +909,7 @@ _mesa_lookup_bufferobj(GLcontext *ctx, GLuint buffer)
  * unbound from all arrays in the current context.
  */
 static void
-unbind(GLcontext *ctx,
+unbind(struct gl_context *ctx,
        struct gl_buffer_object **ptr,
        struct gl_buffer_object *obj)
 {
@@ -741,6 +919,32 @@ unbind(GLcontext *ctx,
 }
 
 
+/**
+ * Plug default/fallback buffer object functions into the device
+ * driver hooks.
+ */
+void
+_mesa_init_buffer_object_functions(struct dd_function_table *driver)
+{
+   /* GL_ARB_vertex/pixel_buffer_object */
+   driver->NewBufferObject = _mesa_new_buffer_object;
+   driver->DeleteBuffer = _mesa_delete_buffer_object;
+   driver->BindBuffer = NULL;
+   driver->BufferData = _mesa_buffer_data;
+   driver->BufferSubData = _mesa_buffer_subdata;
+   driver->GetBufferSubData = _mesa_buffer_get_subdata;
+   driver->MapBuffer = _mesa_buffer_map;
+   driver->UnmapBuffer = _mesa_buffer_unmap;
+
+   /* GL_ARB_map_buffer_range */
+   driver->MapBufferRange = _mesa_buffer_map_range;
+   driver->FlushMappedBufferRange = _mesa_buffer_flush_mapped_range;
+
+   /* GL_ARB_copy_buffer */
+   driver->CopyBufferSubData = _mesa_copy_buffer_subdata;
+}
+
+
 
 /**********************************************************************/
 /* API Functions                                                      */
@@ -779,30 +983,32 @@ _mesa_DeleteBuffersARB(GLsizei n, const GLuint *ids)
    for (i = 0; i < n; i++) {
       struct gl_buffer_object *bufObj = _mesa_lookup_bufferobj(ctx, ids[i]);
       if (bufObj) {
+         struct gl_array_object *arrayObj = ctx->Array.ArrayObj;
          GLuint j;
 
          ASSERT(bufObj->Name == ids[i]);
 
-         if (bufObj->Pointer) {
+         if (_mesa_bufferobj_mapped(bufObj)) {
             /* if mapped, unmap it now */
             ctx->Driver.UnmapBuffer(ctx, 0, bufObj);
-            bufObj->Access = DEFAULT_ACCESS;
+            bufObj->AccessFlags = DEFAULT_ACCESS;
             bufObj->Pointer = NULL;
          }
 
          /* unbind any vertex pointers bound to this buffer */
-         unbind(ctx, &ctx->Array.ArrayObj->Vertex.BufferObj, bufObj);
-         unbind(ctx, &ctx->Array.ArrayObj->Normal.BufferObj, bufObj);
-         unbind(ctx, &ctx->Array.ArrayObj->Color.BufferObj, bufObj);
-         unbind(ctx, &ctx->Array.ArrayObj->SecondaryColor.BufferObj, bufObj);
-         unbind(ctx, &ctx->Array.ArrayObj->FogCoord.BufferObj, bufObj);
-         unbind(ctx, &ctx->Array.ArrayObj->Index.BufferObj, bufObj);
-         unbind(ctx, &ctx->Array.ArrayObj->EdgeFlag.BufferObj, bufObj);
-         for (j = 0; j < MAX_TEXTURE_COORD_UNITS; j++) {
-            unbind(ctx, &ctx->Array.ArrayObj->TexCoord[j].BufferObj, bufObj);
+         unbind(ctx, &arrayObj->Vertex.BufferObj, bufObj);
+         unbind(ctx, &arrayObj->Weight.BufferObj, bufObj);
+         unbind(ctx, &arrayObj->Normal.BufferObj, bufObj);
+         unbind(ctx, &arrayObj->Color.BufferObj, bufObj);
+         unbind(ctx, &arrayObj->SecondaryColor.BufferObj, bufObj);
+         unbind(ctx, &arrayObj->FogCoord.BufferObj, bufObj);
+         unbind(ctx, &arrayObj->Index.BufferObj, bufObj);
+         unbind(ctx, &arrayObj->EdgeFlag.BufferObj, bufObj);
+         for (j = 0; j < Elements(arrayObj->TexCoord); j++) {
+            unbind(ctx, &arrayObj->TexCoord[j].BufferObj, bufObj);
          }
-         for (j = 0; j < VERT_ATTRIB_MAX; j++) {
-            unbind(ctx, &ctx->Array.ArrayObj->VertexAttrib[j].BufferObj, bufObj);
+         for (j = 0; j < Elements(arrayObj->VertexAttrib); j++) {
+            unbind(ctx, &arrayObj->VertexAttrib[j].BufferObj, bufObj);
          }
 
          if (ctx->Array.ArrayBufferObj == bufObj) {
@@ -915,20 +1121,20 @@ _mesa_BufferDataARB(GLenum target, GLsizeiptrARB size,
    }
 
    switch (usage) {
-      case GL_STREAM_DRAW_ARB:
-      case GL_STREAM_READ_ARB:
-      case GL_STREAM_COPY_ARB:
-      case GL_STATIC_DRAW_ARB:
-      case GL_STATIC_READ_ARB:
-      case GL_STATIC_COPY_ARB:
-      case GL_DYNAMIC_DRAW_ARB:
-      case GL_DYNAMIC_READ_ARB:
-      case GL_DYNAMIC_COPY_ARB:
-         /* OK */
-         break;
-      default:
-         _mesa_error(ctx, GL_INVALID_ENUM, "glBufferDataARB(usage)");
-         return;
+   case GL_STREAM_DRAW_ARB:
+   case GL_STREAM_READ_ARB:
+   case GL_STREAM_COPY_ARB:
+   case GL_STATIC_DRAW_ARB:
+   case GL_STATIC_READ_ARB:
+   case GL_STATIC_COPY_ARB:
+   case GL_DYNAMIC_DRAW_ARB:
+   case GL_DYNAMIC_READ_ARB:
+   case GL_DYNAMIC_COPY_ARB:
+      /* OK */
+      break;
+   default:
+      _mesa_error(ctx, GL_INVALID_ENUM, "glBufferDataARB(usage)");
+      return;
    }
 
    bufObj = get_buffer(ctx, target);
@@ -936,26 +1142,35 @@ _mesa_BufferDataARB(GLenum target, GLsizeiptrARB size,
       _mesa_error(ctx, GL_INVALID_ENUM, "glBufferDataARB(target)" );
       return;
    }
-   if (bufObj->Name == 0) {
-      _mesa_error(ctx, GL_INVALID_OPERATION, "glBufferDataARB" );
+   if (!_mesa_is_bufferobj(bufObj)) {
+      _mesa_error(ctx, GL_INVALID_OPERATION, "glBufferDataARB(buffer 0)" );
       return;
    }
    
-   if (bufObj->Pointer) {
+   if (_mesa_bufferobj_mapped(bufObj)) {
       /* Unmap the existing buffer.  We'll replace it now.  Not an error. */
       ctx->Driver.UnmapBuffer(ctx, target, bufObj);
-      bufObj->Access = DEFAULT_ACCESS;
-      bufObj->Pointer = NULL;
+      bufObj->AccessFlags = DEFAULT_ACCESS;
+      ASSERT(bufObj->Pointer == NULL);
    }  
 
    FLUSH_VERTICES(ctx, _NEW_BUFFER_OBJECT);
 
-   ASSERT(ctx->Driver.BufferData);
-
    bufObj->Written = GL_TRUE;
 
-   /* Give the buffer object to the driver!  <data> may be null! */
-   ctx->Driver.BufferData( ctx, target, size, data, usage, bufObj );
+#ifdef VBO_DEBUG
+   printf("glBufferDataARB(%u, sz %ld, from %p, usage 0x%x)\n",
+                bufObj->Name, size, data, usage);
+#endif
+
+#ifdef BOUNDS_CHECK
+   size += 100;
+#endif
+
+   ASSERT(ctx->Driver.BufferData);
+   if (!ctx->Driver.BufferData( ctx, target, size, data, usage, bufObj )) {
+      _mesa_error(ctx, GL_OUT_OF_MEMORY, "glBufferDataARB()");
+   }
 }
 
 
@@ -1006,17 +1221,24 @@ _mesa_MapBufferARB(GLenum target, GLenum access)
 {
    GET_CURRENT_CONTEXT(ctx);
    struct gl_buffer_object * bufObj;
+   GLbitfield accessFlags;
+   void *map;
+
    ASSERT_OUTSIDE_BEGIN_END_WITH_RETVAL(ctx, NULL);
 
    switch (access) {
-      case GL_READ_ONLY_ARB:
-      case GL_WRITE_ONLY_ARB:
-      case GL_READ_WRITE_ARB:
-         /* OK */
-         break;
-      default:
-         _mesa_error(ctx, GL_INVALID_ENUM, "glMapBufferARB(access)");
-         return NULL;
+   case GL_READ_ONLY_ARB:
+      accessFlags = GL_MAP_READ_BIT;
+      break;
+   case GL_WRITE_ONLY_ARB:
+      accessFlags = GL_MAP_WRITE_BIT;
+      break;
+   case GL_READ_WRITE_ARB:
+      accessFlags = GL_MAP_READ_BIT | GL_MAP_WRITE_BIT;
+      break;
+   default:
+      _mesa_error(ctx, GL_INVALID_ENUM, "glMapBufferARB(access)");
+      return NULL;
    }
 
    bufObj = get_buffer(ctx, target);
@@ -1024,25 +1246,57 @@ _mesa_MapBufferARB(GLenum target, GLenum access)
       _mesa_error(ctx, GL_INVALID_ENUM, "glMapBufferARB(target)" );
       return NULL;
    }
-   if (bufObj->Name == 0) {
-      _mesa_error(ctx, GL_INVALID_OPERATION, "glMapBufferARB" );
+   if (!_mesa_is_bufferobj(bufObj)) {
+      _mesa_error(ctx, GL_INVALID_OPERATION, "glMapBufferARB(buffer 0)" );
       return NULL;
    }
-   if (bufObj->Pointer) {
+   if (_mesa_bufferobj_mapped(bufObj)) {
       _mesa_error(ctx, GL_INVALID_OPERATION, "glMapBufferARB(already mapped)");
       return NULL;
    }
 
    ASSERT(ctx->Driver.MapBuffer);
-   bufObj->Pointer = ctx->Driver.MapBuffer( ctx, target, access, bufObj );
-   if (!bufObj->Pointer) {
-      _mesa_error(ctx, GL_OUT_OF_MEMORY, "glMapBufferARB(access)");
+   map = ctx->Driver.MapBuffer( ctx, target, access, bufObj );
+   if (!map) {
+      _mesa_error(ctx, GL_OUT_OF_MEMORY, "glMapBufferARB(map failed)");
+      return NULL;
+   }
+   else {
+      /* The driver callback should have set these fields.
+       * This is important because other modules (like VBO) might call
+       * the driver function directly.
+       */
+      ASSERT(bufObj->Pointer == map);
+      ASSERT(bufObj->Length == bufObj->Size);
+      ASSERT(bufObj->Offset == 0);
+      bufObj->AccessFlags = accessFlags;
    }
 
-   bufObj->Access = access;
    if (access == GL_WRITE_ONLY_ARB || access == GL_READ_WRITE_ARB)
       bufObj->Written = GL_TRUE;
 
+#ifdef VBO_DEBUG
+   printf("glMapBufferARB(%u, sz %ld, access 0x%x)\n",
+         bufObj->Name, bufObj->Size, access);
+   if (access == GL_WRITE_ONLY_ARB) {
+      GLuint i;
+      GLubyte *b = (GLubyte *) bufObj->Pointer;
+      for (i = 0; i < bufObj->Size; i++)
+         b[i] = i & 0xff;
+   }
+#endif
+
+#ifdef BOUNDS_CHECK
+   {
+      GLubyte *buf = (GLubyte *) bufObj->Pointer;
+      GLuint i;
+      /* buffer is 100 bytes larger than requested, fill with magic value */
+      for (i = 0; i < 100; i++) {
+         buf[bufObj->Size - i - 1] = 123;
+      }
+   }
+#endif
+
    return bufObj->Pointer;
 }
 
@@ -1060,18 +1314,56 @@ _mesa_UnmapBufferARB(GLenum target)
       _mesa_error(ctx, GL_INVALID_ENUM, "glUnmapBufferARB(target)" );
       return GL_FALSE;
    }
-   if (bufObj->Name == 0) {
+   if (!_mesa_is_bufferobj(bufObj)) {
       _mesa_error(ctx, GL_INVALID_OPERATION, "glUnmapBufferARB" );
       return GL_FALSE;
    }
-   if (!bufObj->Pointer) {
+   if (!_mesa_bufferobj_mapped(bufObj)) {
       _mesa_error(ctx, GL_INVALID_OPERATION, "glUnmapBufferARB");
       return GL_FALSE;
    }
 
+#ifdef BOUNDS_CHECK
+   if (bufObj->Access != GL_READ_ONLY_ARB) {
+      GLubyte *buf = (GLubyte *) bufObj->Pointer;
+      GLuint i;
+      /* check that last 100 bytes are still = magic value */
+      for (i = 0; i < 100; i++) {
+         GLuint pos = bufObj->Size - i - 1;
+         if (buf[pos] != 123) {
+            _mesa_warning(ctx, "Out of bounds buffer object write detected"
+                          " at position %d (value = %u)\n",
+                          pos, buf[pos]);
+         }
+      }
+   }
+#endif
+
+#ifdef VBO_DEBUG
+   if (bufObj->AccessFlags & GL_MAP_WRITE_BIT) {
+      GLuint i, unchanged = 0;
+      GLubyte *b = (GLubyte *) bufObj->Pointer;
+      GLint pos = -1;
+      /* check which bytes changed */
+      for (i = 0; i < bufObj->Size - 1; i++) {
+         if (b[i] == (i & 0xff) && b[i+1] == ((i+1) & 0xff)) {
+            unchanged++;
+            if (pos == -1)
+               pos = i;
+         }
+      }
+      if (unchanged) {
+         printf("glUnmapBufferARB(%u): %u of %ld unchanged, starting at %d\n",
+                      bufObj->Name, unchanged, bufObj->Size, pos);
+      }
+   }
+#endif
+
    status = ctx->Driver.UnmapBuffer( ctx, target, bufObj );
-   bufObj->Access = DEFAULT_ACCESS;
-   bufObj->Pointer = NULL;
+   bufObj->AccessFlags = DEFAULT_ACCESS;
+   ASSERT(bufObj->Pointer == NULL);
+   ASSERT(bufObj->Offset == 0);
+   ASSERT(bufObj->Length == 0);
 
    return status;
 }
@@ -1086,31 +1378,109 @@ _mesa_GetBufferParameterivARB(GLenum target, GLenum pname, GLint *params)
 
    bufObj = get_buffer(ctx, target);
    if (!bufObj) {
-      _mesa_error(ctx, GL_INVALID_ENUM, "GetBufferParameterivARB(target)" );
+      _mesa_error(ctx, GL_INVALID_ENUM, "glGetBufferParameterivARB(target)" );
       return;
    }
-   if (bufObj->Name == 0) {
-      _mesa_error(ctx, GL_INVALID_OPERATION, "GetBufferParameterivARB" );
+   if (!_mesa_is_bufferobj(bufObj)) {
+      _mesa_error(ctx, GL_INVALID_OPERATION, "glGetBufferParameterivARB" );
       return;
    }
 
    switch (pname) {
-      case GL_BUFFER_SIZE_ARB:
-         *params = (GLint) bufObj->Size;
-         break;
-      case GL_BUFFER_USAGE_ARB:
-         *params = bufObj->Usage;
-         break;
-      case GL_BUFFER_ACCESS_ARB:
-         *params = bufObj->Access;
-         break;
-      case GL_BUFFER_MAPPED_ARB:
-         *params = (bufObj->Pointer != NULL);
-         break;
-      default:
-         _mesa_error(ctx, GL_INVALID_ENUM, "glGetBufferParameterivARB(pname)");
-         return;
+   case GL_BUFFER_SIZE_ARB:
+      *params = (GLint) bufObj->Size;
+      return;
+   case GL_BUFFER_USAGE_ARB:
+      *params = bufObj->Usage;
+      return;
+   case GL_BUFFER_ACCESS_ARB:
+      *params = simplified_access_mode(bufObj->AccessFlags);
+      return;
+   case GL_BUFFER_MAPPED_ARB:
+      *params = _mesa_bufferobj_mapped(bufObj);
+      return;
+   case GL_BUFFER_ACCESS_FLAGS:
+      if (ctx->VersionMajor < 3)
+         goto invalid_pname;
+      *params = bufObj->AccessFlags;
+      return;
+   case GL_BUFFER_MAP_OFFSET:
+      if (ctx->VersionMajor < 3)
+         goto invalid_pname;
+      *params = (GLint) bufObj->Offset;
+      return;
+   case GL_BUFFER_MAP_LENGTH:
+      if (ctx->VersionMajor < 3)
+         goto invalid_pname;
+      *params = (GLint) bufObj->Length;
+      return;
+   default:
+      ; /* fall-through */
+   }
+
+invalid_pname:
+   _mesa_error(ctx, GL_INVALID_ENUM, "glGetBufferParameterivARB(pname=%s)",
+               _mesa_lookup_enum_by_nr(pname));
+}
+
+
+/**
+ * New in GL 3.2
+ * This is pretty much a duplicate of GetBufferParameteriv() but the
+ * GL_BUFFER_SIZE_ARB attribute will be 64-bits on a 64-bit system.
+ */
+void GLAPIENTRY
+_mesa_GetBufferParameteri64v(GLenum target, GLenum pname, GLint64 *params)
+{
+   GET_CURRENT_CONTEXT(ctx);
+   struct gl_buffer_object *bufObj;
+   ASSERT_OUTSIDE_BEGIN_END(ctx);
+
+   bufObj = get_buffer(ctx, target);
+   if (!bufObj) {
+      _mesa_error(ctx, GL_INVALID_ENUM, "glGetBufferParameteri64v(target)" );
+      return;
+   }
+   if (!_mesa_is_bufferobj(bufObj)) {
+      _mesa_error(ctx, GL_INVALID_OPERATION, "glGetBufferParameteri64v" );
+      return;
+   }
+
+   switch (pname) {
+   case GL_BUFFER_SIZE_ARB:
+      *params = bufObj->Size;
+      return;
+   case GL_BUFFER_USAGE_ARB:
+      *params = bufObj->Usage;
+      return;
+   case GL_BUFFER_ACCESS_ARB:
+      *params = simplified_access_mode(bufObj->AccessFlags);
+      return;
+   case GL_BUFFER_ACCESS_FLAGS:
+      if (ctx->VersionMajor < 3)
+         goto invalid_pname;
+      *params = bufObj->AccessFlags;
+      return;
+   case GL_BUFFER_MAPPED_ARB:
+      *params = _mesa_bufferobj_mapped(bufObj);
+      return;
+   case GL_BUFFER_MAP_OFFSET:
+      if (ctx->VersionMajor < 3)
+         goto invalid_pname;
+      *params = bufObj->Offset;
+      return;
+   case GL_BUFFER_MAP_LENGTH:
+      if (ctx->VersionMajor < 3)
+         goto invalid_pname;
+      *params = bufObj->Length;
+      return;
+   default:
+      ; /* fall-through */
    }
+
+invalid_pname:
+   _mesa_error(ctx, GL_INVALID_ENUM, "glGetBufferParameteri64v(pname=%s)",
+               _mesa_lookup_enum_by_nr(pname));
 }
 
 
@@ -1131,10 +1501,636 @@ _mesa_GetBufferPointervARB(GLenum target, GLenum pname, GLvoid **params)
       _mesa_error(ctx, GL_INVALID_ENUM, "glGetBufferPointervARB(target)" );
       return;
    }
-   if (bufObj->Name == 0) {
+   if (!_mesa_is_bufferobj(bufObj)) {
       _mesa_error(ctx, GL_INVALID_OPERATION, "glGetBufferPointervARB" );
       return;
    }
 
    *params = bufObj->Pointer;
 }
+
+
+void GLAPIENTRY
+_mesa_CopyBufferSubData(GLenum readTarget, GLenum writeTarget,
+                        GLintptr readOffset, GLintptr writeOffset,
+                        GLsizeiptr size)
+{
+   GET_CURRENT_CONTEXT(ctx);
+   struct gl_buffer_object *src, *dst;
+   ASSERT_OUTSIDE_BEGIN_END(ctx);
+
+   src = get_buffer(ctx, readTarget);
+   if (!src || !_mesa_is_bufferobj(src)) {
+      _mesa_error(ctx, GL_INVALID_ENUM,
+                  "glCopyBuffserSubData(readTarget = 0x%x)", readTarget);
+      return;
+   }
+
+   dst = get_buffer(ctx, writeTarget);
+   if (!dst || !_mesa_is_bufferobj(dst)) {
+      _mesa_error(ctx, GL_INVALID_ENUM,
+                  "glCopyBuffserSubData(writeTarget = 0x%x)", writeTarget);
+      return;
+   }
+
+   if (_mesa_bufferobj_mapped(src)) {
+      _mesa_error(ctx, GL_INVALID_OPERATION,
+                  "glCopyBuffserSubData(readBuffer is mapped)");
+      return;
+   }
+
+   if (_mesa_bufferobj_mapped(dst)) {
+      _mesa_error(ctx, GL_INVALID_OPERATION,
+                  "glCopyBuffserSubData(writeBuffer is mapped)");
+      return;
+   }
+
+   if (readOffset < 0) {
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glCopyBuffserSubData(readOffset = %d)", (int) readOffset);
+      return;
+   }
+
+   if (writeOffset < 0) {
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glCopyBuffserSubData(writeOffset = %d)", (int) writeOffset);
+      return;
+   }
+
+   if (readOffset + size > src->Size) {
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glCopyBuffserSubData(readOffset + size = %d)",
+                  (int) (readOffset + size));
+      return;
+   }
+
+   if (writeOffset + size > dst->Size) {
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glCopyBuffserSubData(writeOffset + size = %d)",
+                  (int) (writeOffset + size));
+      return;
+   }
+
+   if (src == dst) {
+      if (readOffset + size <= writeOffset) {
+         /* OK */
+      }
+      else if (writeOffset + size <= readOffset) {
+         /* OK */
+      }
+      else {
+         /* overlapping src/dst is illegal */
+         _mesa_error(ctx, GL_INVALID_VALUE,
+                     "glCopyBuffserSubData(overlapping src/dst)");
+         return;
+      }
+   }
+
+   ctx->Driver.CopyBufferSubData(ctx, src, dst, readOffset, writeOffset, size);
+}
+
+
+/**
+ * See GL_ARB_map_buffer_range spec
+ */
+void * GLAPIENTRY
+_mesa_MapBufferRange(GLenum target, GLintptr offset, GLsizeiptr length,
+                     GLbitfield access)
+{
+   GET_CURRENT_CONTEXT(ctx);
+   struct gl_buffer_object *bufObj;
+   void *map;
+
+   ASSERT_OUTSIDE_BEGIN_END_WITH_RETVAL(ctx, NULL);
+
+   if (!ctx->Extensions.ARB_map_buffer_range) {
+      _mesa_error(ctx, GL_INVALID_OPERATION,
+                  "glMapBufferRange(extension not supported)");
+      return NULL;
+   }
+
+   if (offset < 0) {
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glMapBufferRange(offset = %ld)", (long)offset);
+      return NULL;
+   }
+
+   if (length < 0) {
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glMapBufferRange(length = %ld)", (long)length);
+      return NULL;
+   }
+
+   if ((access & (GL_MAP_READ_BIT | GL_MAP_WRITE_BIT)) == 0) {
+      _mesa_error(ctx, GL_INVALID_OPERATION,
+                  "glMapBufferRange(access indicates neither read or write)");
+      return NULL;
+   }
+
+   if (access & GL_MAP_READ_BIT) {
+      if ((access & GL_MAP_INVALIDATE_RANGE_BIT) ||
+          (access & GL_MAP_INVALIDATE_BUFFER_BIT) ||
+          (access & GL_MAP_UNSYNCHRONIZED_BIT)) {
+         _mesa_error(ctx, GL_INVALID_OPERATION,
+                     "glMapBufferRange(invalid access flags)");
+         return NULL;
+      }
+   }
+
+   if ((access & GL_MAP_FLUSH_EXPLICIT_BIT) &&
+       ((access & GL_MAP_WRITE_BIT) == 0)) {
+      _mesa_error(ctx, GL_INVALID_OPERATION,
+                  "glMapBufferRange(invalid access flags)");
+      return NULL;
+   }
+
+   bufObj = get_buffer(ctx, target);
+   if (!bufObj || !_mesa_is_bufferobj(bufObj)) {
+      _mesa_error(ctx, GL_INVALID_ENUM,
+                  "glMapBufferRange(target = 0x%x)", target);
+      return NULL;
+   }
+
+   if (offset + length > bufObj->Size) {
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glMapBufferRange(offset + length > size)");
+      return NULL;
+   }
+
+   if (_mesa_bufferobj_mapped(bufObj)) {
+      _mesa_error(ctx, GL_INVALID_OPERATION,
+                  "glMapBufferRange(buffer already mapped)");
+      return NULL;
+   }
+      
+   ASSERT(ctx->Driver.MapBufferRange);
+   map = ctx->Driver.MapBufferRange(ctx, target, offset, length,
+                                    access, bufObj);
+   if (!map) {
+      _mesa_error(ctx, GL_OUT_OF_MEMORY, "glMapBufferARB(map failed)");
+   }
+   else {
+      /* The driver callback should have set all these fields.
+       * This is important because other modules (like VBO) might call
+       * the driver function directly.
+       */
+      ASSERT(bufObj->Pointer == map);
+      ASSERT(bufObj->Length == length);
+      ASSERT(bufObj->Offset == offset);
+      ASSERT(bufObj->AccessFlags == access);
+   }
+
+   return map;
+}
+
+
+/**
+ * See GL_ARB_map_buffer_range spec
+ */
+void GLAPIENTRY
+_mesa_FlushMappedBufferRange(GLenum target, GLintptr offset, GLsizeiptr length)
+{
+   GET_CURRENT_CONTEXT(ctx);
+   struct gl_buffer_object *bufObj;
+   ASSERT_OUTSIDE_BEGIN_END(ctx);
+
+   if (!ctx->Extensions.ARB_map_buffer_range) {
+      _mesa_error(ctx, GL_INVALID_OPERATION,
+                  "glMapBufferRange(extension not supported)");
+      return;
+   }
+
+   if (offset < 0) {
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glMapBufferRange(offset = %ld)", (long)offset);
+      return;
+   }
+
+   if (length < 0) {
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glMapBufferRange(length = %ld)", (long)length);
+      return;
+   }
+
+   bufObj = get_buffer(ctx, target);
+   if (!bufObj) {
+      _mesa_error(ctx, GL_INVALID_ENUM,
+                  "glMapBufferRange(target = 0x%x)", target);
+      return;
+   }
+
+   if (!_mesa_is_bufferobj(bufObj)) {
+      _mesa_error(ctx, GL_INVALID_OPERATION,
+                  "glMapBufferRange(current buffer is 0)");
+      return;
+   }
+
+   if (!_mesa_bufferobj_mapped(bufObj)) {
+      /* buffer is not mapped */
+      _mesa_error(ctx, GL_INVALID_OPERATION,
+                  "glMapBufferRange(buffer is not mapped)");
+      return;
+   }
+
+   if ((bufObj->AccessFlags & GL_MAP_FLUSH_EXPLICIT_BIT) == 0) {
+      _mesa_error(ctx, GL_INVALID_OPERATION,
+                  "glMapBufferRange(GL_MAP_FLUSH_EXPLICIT_BIT not set)");
+      return;
+   }
+
+   if (offset + length > bufObj->Length) {
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                 "glMapBufferRange(offset %ld + length %ld > mapped length %ld)",
+                 (long)offset, (long)length, (long)bufObj->Length);
+      return;
+   }
+
+   ASSERT(bufObj->AccessFlags & GL_MAP_WRITE_BIT);
+
+   if (ctx->Driver.FlushMappedBufferRange)
+      ctx->Driver.FlushMappedBufferRange(ctx, target, offset, length, bufObj);
+}
+
+
+#if FEATURE_APPLE_object_purgeable
+static GLenum
+_mesa_BufferObjectPurgeable(struct gl_context *ctx, GLuint name, GLenum option)
+{
+   struct gl_buffer_object *bufObj;
+   GLenum retval;
+
+   bufObj = _mesa_lookup_bufferobj(ctx, name);
+   if (!bufObj) {
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glObjectPurgeable(name = 0x%x)", name);
+      return 0;
+   }
+   if (!_mesa_is_bufferobj(bufObj)) {
+      _mesa_error(ctx, GL_INVALID_OPERATION, "glObjectPurgeable(buffer 0)" );
+      return 0;
+   }
+
+   if (bufObj->Purgeable) {
+      _mesa_error(ctx, GL_INVALID_OPERATION,
+                  "glObjectPurgeable(name = 0x%x) is already purgeable", name);
+      return GL_VOLATILE_APPLE;
+   }
+
+   bufObj->Purgeable = GL_TRUE;
+
+   retval = GL_VOLATILE_APPLE;
+   if (ctx->Driver.BufferObjectPurgeable)
+      retval = ctx->Driver.BufferObjectPurgeable(ctx, bufObj, option);
+
+   return retval;
+}
+
+
+static GLenum
+_mesa_RenderObjectPurgeable(struct gl_context *ctx, GLuint name, GLenum option)
+{
+   struct gl_renderbuffer *bufObj;
+   GLenum retval;
+
+   bufObj = _mesa_lookup_renderbuffer(ctx, name);
+   if (!bufObj) {
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glObjectUnpurgeable(name = 0x%x)", name);
+      return 0;
+   }
+
+   if (bufObj->Purgeable) {
+      _mesa_error(ctx, GL_INVALID_OPERATION,
+                  "glObjectPurgeable(name = 0x%x) is already purgeable", name);
+      return GL_VOLATILE_APPLE;
+   }
+
+   bufObj->Purgeable = GL_TRUE;
+
+   retval = GL_VOLATILE_APPLE;
+   if (ctx->Driver.RenderObjectPurgeable)
+      retval = ctx->Driver.RenderObjectPurgeable(ctx, bufObj, option);
+
+   return retval;
+}
+
+
+static GLenum
+_mesa_TextureObjectPurgeable(struct gl_context *ctx, GLuint name, GLenum option)
+{
+   struct gl_texture_object *bufObj;
+   GLenum retval;
+
+   bufObj = _mesa_lookup_texture(ctx, name);
+   if (!bufObj) {
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glObjectPurgeable(name = 0x%x)", name);
+      return 0;
+   }
+
+   if (bufObj->Purgeable) {
+      _mesa_error(ctx, GL_INVALID_OPERATION,
+                  "glObjectPurgeable(name = 0x%x) is already purgeable", name);
+      return GL_VOLATILE_APPLE;
+   }
+
+   bufObj->Purgeable = GL_TRUE;
+
+   retval = GL_VOLATILE_APPLE;
+   if (ctx->Driver.TextureObjectPurgeable)
+      retval = ctx->Driver.TextureObjectPurgeable(ctx, bufObj, option);
+
+   return retval;
+}
+
+
+GLenum GLAPIENTRY
+_mesa_ObjectPurgeableAPPLE(GLenum objectType, GLuint name, GLenum option)
+{
+   GLenum retval;
+
+   GET_CURRENT_CONTEXT(ctx);
+   ASSERT_OUTSIDE_BEGIN_END_WITH_RETVAL(ctx, 0);
+
+   if (name == 0) {
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glObjectPurgeable(name = 0x%x)", name);
+      return 0;
+   }
+
+   switch (option) {
+   case GL_VOLATILE_APPLE:
+   case GL_RELEASED_APPLE:
+      /* legal */
+      break;
+   default:
+      _mesa_error(ctx, GL_INVALID_ENUM,
+                  "glObjectPurgeable(name = 0x%x) invalid option: %d",
+                  name, option);
+      return 0;
+   }
+
+   switch (objectType) {
+   case GL_TEXTURE:
+      retval = _mesa_TextureObjectPurgeable (ctx, name, option);
+      break;
+   case GL_RENDERBUFFER_EXT:
+      retval = _mesa_RenderObjectPurgeable (ctx, name, option);
+      break;
+   case GL_BUFFER_OBJECT_APPLE:
+      retval = _mesa_BufferObjectPurgeable (ctx, name, option);
+      break;
+   default:
+      _mesa_error(ctx, GL_INVALID_ENUM,
+                  "glObjectPurgeable(name = 0x%x) invalid type: %d",
+                  name, objectType);
+      return 0;
+   }
+
+   /* In strict conformance to the spec, we must only return VOLATILE when
+    * when passed the VOLATILE option. Madness.
+    *
+    * XXX First fix the spec, then fix me.
+    */
+   return option == GL_VOLATILE_APPLE ? GL_VOLATILE_APPLE : retval;
+}
+
+
+static GLenum
+_mesa_BufferObjectUnpurgeable(struct gl_context *ctx, GLuint name, GLenum option)
+{
+   struct gl_buffer_object *bufObj;
+   GLenum retval;
+
+   bufObj = _mesa_lookup_bufferobj(ctx, name);
+   if (!bufObj) {
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glObjectUnpurgeable(name = 0x%x)", name);
+      return 0;
+   }
+
+   if (! bufObj->Purgeable) {
+      _mesa_error(ctx, GL_INVALID_OPERATION,
+                  "glObjectUnpurgeable(name = 0x%x) object is "
+                  " already \"unpurged\"", name);
+      return 0;
+   }
+
+   bufObj->Purgeable = GL_FALSE;
+
+   retval = option;
+   if (ctx->Driver.BufferObjectUnpurgeable)
+      retval = ctx->Driver.BufferObjectUnpurgeable(ctx, bufObj, option);
+
+   return retval;
+}
+
+
+static GLenum
+_mesa_RenderObjectUnpurgeable(struct gl_context *ctx, GLuint name, GLenum option)
+{
+   struct gl_renderbuffer *bufObj;
+   GLenum retval;
+
+   bufObj = _mesa_lookup_renderbuffer(ctx, name);
+   if (!bufObj) {
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glObjectUnpurgeable(name = 0x%x)", name);
+      return 0;
+   }
+
+   if (! bufObj->Purgeable) {
+      _mesa_error(ctx, GL_INVALID_OPERATION,
+                  "glObjectUnpurgeable(name = 0x%x) object is "
+                  " already \"unpurged\"", name);
+      return 0;
+   }
+
+   bufObj->Purgeable = GL_FALSE;
+
+   retval = option;
+   if (ctx->Driver.RenderObjectUnpurgeable)
+      retval = ctx->Driver.RenderObjectUnpurgeable(ctx, bufObj, option);
+
+   return retval;
+}
+
+
+static GLenum
+_mesa_TextureObjectUnpurgeable(struct gl_context *ctx, GLuint name, GLenum option)
+{
+   struct gl_texture_object *bufObj;
+   GLenum retval;
+
+   bufObj = _mesa_lookup_texture(ctx, name);
+   if (!bufObj) {
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glObjectUnpurgeable(name = 0x%x)", name);
+      return 0;
+   }
+
+   if (! bufObj->Purgeable) {
+      _mesa_error(ctx, GL_INVALID_OPERATION,
+                  "glObjectUnpurgeable(name = 0x%x) object is"
+                  " already \"unpurged\"", name);
+      return 0;
+   }
+
+   bufObj->Purgeable = GL_FALSE;
+
+   retval = option;
+   if (ctx->Driver.TextureObjectUnpurgeable)
+      retval = ctx->Driver.TextureObjectUnpurgeable(ctx, bufObj, option);
+
+   return retval;
+}
+
+
+GLenum GLAPIENTRY
+_mesa_ObjectUnpurgeableAPPLE(GLenum objectType, GLuint name, GLenum option)
+{
+   GET_CURRENT_CONTEXT(ctx);
+   ASSERT_OUTSIDE_BEGIN_END_WITH_RETVAL(ctx, 0);
+
+   if (name == 0) {
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glObjectUnpurgeable(name = 0x%x)", name);
+      return 0;
+   }
+
+   switch (option) {
+   case GL_RETAINED_APPLE:
+   case GL_UNDEFINED_APPLE:
+      /* legal */
+      break;
+   default:
+      _mesa_error(ctx, GL_INVALID_ENUM,
+                  "glObjectUnpurgeable(name = 0x%x) invalid option: %d",
+                  name, option);
+      return 0;
+   }
+
+   switch (objectType) {
+   case GL_BUFFER_OBJECT_APPLE:
+      return _mesa_BufferObjectUnpurgeable(ctx, name, option);
+   case GL_TEXTURE:
+      return _mesa_TextureObjectUnpurgeable(ctx, name, option);
+   case GL_RENDERBUFFER_EXT:
+      return _mesa_RenderObjectUnpurgeable(ctx, name, option);
+   default:
+      _mesa_error(ctx, GL_INVALID_ENUM,
+                  "glObjectUnpurgeable(name = 0x%x) invalid type: %d",
+                  name, objectType);
+      return 0;
+   }
+}
+
+
+static void
+_mesa_GetBufferObjectParameterivAPPLE(struct gl_context *ctx, GLuint name,
+                                      GLenum pname, GLint* params)
+{
+   struct gl_buffer_object *bufObj;
+
+   bufObj = _mesa_lookup_bufferobj(ctx, name);
+   if (!bufObj) {
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glGetObjectParameteriv(name = 0x%x) invalid object", name);
+      return;
+   }
+
+   switch (pname) {
+   case GL_PURGEABLE_APPLE:
+      *params = bufObj->Purgeable;
+      break;
+   default:
+      _mesa_error(ctx, GL_INVALID_ENUM,
+                  "glGetObjectParameteriv(name = 0x%x) invalid enum: %d",
+                  name, pname);
+      break;
+   }
+}
+
+
+static void
+_mesa_GetRenderObjectParameterivAPPLE(struct gl_context *ctx, GLuint name,
+                                      GLenum pname, GLint* params)
+{
+   struct gl_renderbuffer *bufObj;
+
+   bufObj = _mesa_lookup_renderbuffer(ctx, name);
+   if (!bufObj) {
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glObjectUnpurgeable(name = 0x%x)", name);
+      return;
+   }
+
+   switch (pname) {
+   case GL_PURGEABLE_APPLE:
+      *params = bufObj->Purgeable;
+      break;
+   default:
+      _mesa_error(ctx, GL_INVALID_ENUM,
+                  "glGetObjectParameteriv(name = 0x%x) invalid enum: %d",
+                  name, pname);
+      break;
+   }
+}
+
+
+static void
+_mesa_GetTextureObjectParameterivAPPLE(struct gl_context *ctx, GLuint name,
+                                       GLenum pname, GLint* params)
+{
+   struct gl_texture_object *bufObj;
+
+   bufObj = _mesa_lookup_texture(ctx, name);
+   if (!bufObj) {
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glObjectUnpurgeable(name = 0x%x)", name);
+      return;
+   }
+
+   switch (pname) {
+   case GL_PURGEABLE_APPLE:
+      *params = bufObj->Purgeable;
+      break;
+   default:
+      _mesa_error(ctx, GL_INVALID_ENUM,
+                  "glGetObjectParameteriv(name = 0x%x) invalid enum: %d",
+                  name, pname);
+      break;
+   }
+}
+
+
+void GLAPIENTRY
+_mesa_GetObjectParameterivAPPLE(GLenum objectType, GLuint name, GLenum pname,
+                                GLint* params)
+{
+   GET_CURRENT_CONTEXT(ctx);
+
+   if (name == 0) {
+      _mesa_error(ctx, GL_INVALID_VALUE,
+                  "glGetObjectParameteriv(name = 0x%x)", name);
+      return;
+   }
+
+   switch (objectType) {
+   case GL_TEXTURE:
+      _mesa_GetTextureObjectParameterivAPPLE (ctx, name, pname, params);
+      break;
+   case GL_BUFFER_OBJECT_APPLE:
+      _mesa_GetBufferObjectParameterivAPPLE (ctx, name, pname, params);
+      break;
+   case GL_RENDERBUFFER_EXT:
+      _mesa_GetRenderObjectParameterivAPPLE (ctx, name, pname, params);
+      break;
+   default:
+      _mesa_error(ctx, GL_INVALID_ENUM,
+                  "glGetObjectParameteriv(name = 0x%x) invalid type: %d",
+                  name, objectType);
+   }
+}
+
+#endif /* FEATURE_APPLE_object_purgeable */