Merge commit 'origin/gallium-master-merge'
[mesa.git] / src / mesa / main / fbobject.c
index ad4965550a49a5c0165c90fad064eb96d915c055..c3cdc110379c5a882a03b7d34f1c45fcc31ff454 100644 (file)
@@ -125,9 +125,23 @@ _mesa_lookup_framebuffer(GLcontext *ctx, GLuint id)
 }
 
 
+/**
+ * Mark the given framebuffer as invalid.  This will force the
+ * test for framebuffer completeness to be done before the framebuffer
+ * is used.
+ */
+static void
+invalidate_framebuffer(struct gl_framebuffer *fb)
+{
+   fb->_Status = 0; /* "indeterminate" */
+}
+
+
 /**
  * Given a GL_*_ATTACHMENTn token, return a pointer to the corresponding
  * gl_renderbuffer_attachment object.
+ * If \p attachment is GL_DEPTH_STENCIL_ATTACHMENT, return a pointer to
+ * the depth buffer attachment point.
  */
 struct gl_renderbuffer_attachment *
 _mesa_get_attachment(GLcontext *ctx, struct gl_framebuffer *fb,
@@ -157,6 +171,8 @@ _mesa_get_attachment(GLcontext *ctx, struct gl_framebuffer *fb,
         return NULL;
       }
       return &fb->Attachment[BUFFER_COLOR0 + i];
+   case GL_DEPTH_STENCIL_ATTACHMENT:
+      /* fall-through */
    case GL_DEPTH_ATTACHMENT_EXT:
       return &fb->Attachment[BUFFER_DEPTH];
    case GL_STENCIL_ATTACHMENT_EXT:
@@ -177,7 +193,7 @@ _mesa_remove_attachment(GLcontext *ctx, struct gl_renderbuffer_attachment *att)
    if (att->Type == GL_TEXTURE) {
       ASSERT(att->Texture);
       if (ctx->Driver.FinishRenderTexture) {
-         /* tell driver we're done rendering to this texobj */
+         /* tell driver that we're done rendering to this texture. */
          ctx->Driver.FinishRenderTexture(ctx, att);
       }
       _mesa_reference_texobj(&att->Texture, NULL); /* unbind */
@@ -230,6 +246,8 @@ _mesa_set_texture_attachment(GLcontext *ctx,
    if (att->Texture->Image[att->CubeMapFace][att->TextureLevel]) {
       ctx->Driver.RenderTexture(ctx, fb, att);
    }
+
+   invalidate_framebuffer(fb);
 }
 
 
@@ -267,11 +285,19 @@ _mesa_framebuffer_renderbuffer(GLcontext *ctx, struct gl_framebuffer *fb,
    ASSERT(att);
    if (rb) {
       _mesa_set_renderbuffer_attachment(ctx, att, rb);
+      if (attachment == GL_DEPTH_STENCIL_ATTACHMENT) {
+         /* do stencil attachment here (depth already done above) */
+         att = _mesa_get_attachment(ctx, fb, GL_STENCIL_ATTACHMENT_EXT);
+         assert(att);
+         _mesa_set_renderbuffer_attachment(ctx, att, rb);
+      }
    }
    else {
       _mesa_remove_attachment(ctx, att);
    }
 
+   invalidate_framebuffer(fb);
+
    _glthread_UNLOCK_MUTEX(fb->Mutex);
 }
 
@@ -425,6 +451,7 @@ _mesa_test_framebuffer_completeness(GLcontext *ctx, struct gl_framebuffer *fb)
    GLuint numImages;
    GLenum intFormat = GL_NONE; /* color buffers' internal format */
    GLuint minWidth = ~0, minHeight = ~0, maxWidth = 0, maxHeight = 0;
+   GLint numSamples = -1;
    GLint i;
    GLuint j;
 
@@ -509,12 +536,18 @@ _mesa_test_framebuffer_completeness(GLcontext *ctx, struct gl_framebuffer *fb)
          continue;
       }
 
-      /* Error-check width, height, format
+      if (numSamples < 0) {
+         /* first buffer */
+         numSamples = att->Renderbuffer->NumSamples;
+      }
+
+      /* Error-check width, height, format, samples
        */
       if (numImages == 1) {
-         /* save format */
-         if (i >= 0)
+         /* save format, num samples */
+         if (i >= 0) {
             intFormat = f;
+         }
       }
       else {
          if (!ctx->Extensions.ARB_framebuffer_object) {
@@ -531,6 +564,13 @@ _mesa_test_framebuffer_completeness(GLcontext *ctx, struct gl_framebuffer *fb)
                return;
             }
          }
+         if (att->Renderbuffer &&
+             att->Renderbuffer->NumSamples != numSamples) {
+            fb->_Status = GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE;
+            fbo_incomplete("inconsistant number of samples", i);
+            return;
+         }            
+
       }
    }
 
@@ -571,9 +611,15 @@ _mesa_test_framebuffer_completeness(GLcontext *ctx, struct gl_framebuffer *fb)
    /* Provisionally set status = COMPLETE ... */
    fb->_Status = GL_FRAMEBUFFER_COMPLETE_EXT;
 
-   /* ... but the driver may say the FB is incomplete: */
+   /* ... but the driver may say the FB is incomplete.
+    * Drivers will most likely set the status to GL_FRAMEBUFFER_UNSUPPORTED
+    * if anything.
+    */
    if (ctx->Driver.ValidateFramebuffer) {
       ctx->Driver.ValidateFramebuffer(ctx, fb);
+      if (fb->_Status != GL_FRAMEBUFFER_COMPLETE_EXT) {
+         fbo_incomplete("driver marked FBO as incomplete", -1);
+      }
    }
 
    if (fb->_Status == GL_FRAMEBUFFER_COMPLETE_EXT) {
@@ -584,6 +630,9 @@ _mesa_test_framebuffer_completeness(GLcontext *ctx, struct gl_framebuffer *fb)
        */
       fb->Width = minWidth;
       fb->Height = minHeight;
+
+      /* finally, update the visual info for the framebuffer */
+      _mesa_update_framebuffer_visual(fb);
    }
 }
 
@@ -658,6 +707,27 @@ _mesa_BindRenderbufferEXT(GLenum target, GLuint renderbuffer)
 }
 
 
+/**
+ * If the given renderbuffer is anywhere attached to the framebuffer, detach
+ * the renderbuffer.
+ * This is used when a renderbuffer object is deleted.
+ * The spec calls for unbinding.
+ */
+static void
+detach_renderbuffer(GLcontext *ctx,
+                    struct gl_framebuffer *fb,
+                    struct gl_renderbuffer *rb)
+{
+   GLuint i;
+   for (i = 0; i < BUFFER_COUNT; i++) {
+      if (fb->Attachment[i].Renderbuffer == rb) {
+         _mesa_remove_attachment(ctx, &fb->Attachment[i]);
+      }
+   }
+   invalidate_framebuffer(fb);
+}
+
+
 void GLAPIENTRY
 _mesa_DeleteRenderbuffersEXT(GLsizei n, const GLuint *renderbuffers)
 {
@@ -679,6 +749,13 @@ _mesa_DeleteRenderbuffersEXT(GLsizei n, const GLuint *renderbuffers)
                _mesa_BindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
             }
 
+            if (ctx->DrawBuffer->Name) {
+               detach_renderbuffer(ctx, ctx->DrawBuffer, rb);
+            }
+            if (ctx->ReadBuffer->Name && ctx->ReadBuffer != ctx->DrawBuffer) {
+               detach_renderbuffer(ctx, ctx->ReadBuffer, rb);
+            }
+
            /* Remove from hash table immediately, to free the ID.
              * But the object will not be freed until it's no longer
              * referenced anywhere else.
@@ -780,10 +857,21 @@ _mesa_base_fbo_format(GLcontext *ctx, GLenum internalFormat)
 }
 
 
-void GLAPIENTRY
-_mesa_RenderbufferStorageEXT(GLenum target, GLenum internalFormat,
-                             GLsizei width, GLsizei height)
+/** sentinal value, see below */
+#define NO_SAMPLES 1000
+
+
+/**
+ * Helper function used by _mesa_RenderbufferStorageEXT() and 
+ * _mesa_RenderbufferStorageMultisample().
+ * samples will be NO_SAMPLES if called by _mesa_RenderbufferStorageEXT().
+ */
+static void
+renderbuffer_storage(GLenum target, GLenum internalFormat,
+                     GLsizei width, GLsizei height, GLsizei samples)
 {
+   const char *func = samples == NO_SAMPLES ?
+      "glRenderbufferStorage" : "RenderbufferStorageMultisample";
    struct gl_renderbuffer *rb;
    GLenum baseFormat;
    GET_CURRENT_CONTEXT(ctx);
@@ -791,31 +879,39 @@ _mesa_RenderbufferStorageEXT(GLenum target, GLenum internalFormat,
    ASSERT_OUTSIDE_BEGIN_END(ctx);
 
    if (target != GL_RENDERBUFFER_EXT) {
-      _mesa_error(ctx, GL_INVALID_ENUM, "glRenderbufferStorageEXT(target)");
+      _mesa_error(ctx, GL_INVALID_ENUM, "%s(target)", func);
       return;
    }
 
    baseFormat = _mesa_base_fbo_format(ctx, internalFormat);
    if (baseFormat == 0) {
-      _mesa_error(ctx, GL_INVALID_ENUM,
-                  "glRenderbufferStorageEXT(internalFormat)");
+      _mesa_error(ctx, GL_INVALID_ENUM, "%s(internalFormat)", func);
       return;
    }
 
    if (width < 1 || width > (GLsizei) ctx->Const.MaxRenderbufferSize) {
-      _mesa_error(ctx, GL_INVALID_VALUE, "glRenderbufferStorageEXT(width)");
+      _mesa_error(ctx, GL_INVALID_VALUE, "%s(width)", func);
       return;
    }
 
    if (height < 1 || height > (GLsizei) ctx->Const.MaxRenderbufferSize) {
-      _mesa_error(ctx, GL_INVALID_VALUE, "glRenderbufferStorageEXT(height)");
+      _mesa_error(ctx, GL_INVALID_VALUE, "%s(height)", func);
       return;
    }
 
-   rb = ctx->CurrentRenderbuffer;
+   if (samples == NO_SAMPLES) {
+      /* NumSamples == 0 indicates non-multisampling */
+      samples = 0;
+   }
+   else if (samples > ctx->Const.MaxSamples) {
+      /* note: driver may choose to use more samples than what's requested */
+      _mesa_error(ctx, GL_INVALID_VALUE, "%s(samples)", func);
+      return;
+   }
 
+   rb = ctx->CurrentRenderbuffer;
    if (!rb) {
-      _mesa_error(ctx, GL_INVALID_OPERATION, "glRenderbufferStorageEXT");
+      _mesa_error(ctx, GL_INVALID_OPERATION, func);
       return;
    }
 
@@ -837,6 +933,7 @@ _mesa_RenderbufferStorageEXT(GLenum target, GLenum internalFormat,
    rb->IndexBits =
    rb->DepthBits =
    rb->StencilBits = 0;
+   rb->NumSamples = samples;
 
    /* Now allocate the storage */
    ASSERT(rb->AllocStorage);
@@ -863,7 +960,8 @@ _mesa_RenderbufferStorageEXT(GLenum target, GLenum internalFormat,
       rb->AlphaBits =
       rb->IndexBits =
       rb->DepthBits =
-      rb->StencilBits = 0;
+      rb->StencilBits =
+      rb->NumSamples = 0;
    }
 
    /*
@@ -875,9 +973,32 @@ _mesa_RenderbufferStorageEXT(GLenum target, GLenum internalFormat,
 }
 
 
+void GLAPIENTRY
+_mesa_RenderbufferStorageEXT(GLenum target, GLenum internalFormat,
+                             GLsizei width, GLsizei height)
+{
+   /* GL_ARB_fbo says calling this function is equivalent to calling
+    * glRenderbufferStorageMultisample() with samples=0.  We pass in
+    * a token value here just for error reporting purposes.
+    */
+   renderbuffer_storage(target, internalFormat, width, height, NO_SAMPLES);
+}
+
+
+void GLAPIENTRY
+_mesa_RenderbufferStorageMultisample(GLenum target, GLsizei samples,
+                                     GLenum internalFormat,
+                                     GLsizei width, GLsizei height)
+{
+   renderbuffer_storage(target, internalFormat, width, height, samples);
+}
+
+
+
 void GLAPIENTRY
 _mesa_GetRenderbufferParameterivEXT(GLenum target, GLenum pname, GLint *params)
 {
+   struct gl_renderbuffer *rb;
    GET_CURRENT_CONTEXT(ctx);
 
    ASSERT_OUTSIDE_BEGIN_END(ctx);
@@ -888,7 +1009,8 @@ _mesa_GetRenderbufferParameterivEXT(GLenum target, GLenum pname, GLint *params)
       return;
    }
 
-   if (!ctx->CurrentRenderbuffer) {
+   rb = ctx->CurrentRenderbuffer;
+   if (!rb) {
       _mesa_error(ctx, GL_INVALID_OPERATION,
                   "glGetRenderbufferParameterivEXT");
       return;
@@ -898,32 +1020,38 @@ _mesa_GetRenderbufferParameterivEXT(GLenum target, GLenum pname, GLint *params)
 
    switch (pname) {
    case GL_RENDERBUFFER_WIDTH_EXT:
-      *params = ctx->CurrentRenderbuffer->Width;
+      *params = rb->Width;
       return;
    case GL_RENDERBUFFER_HEIGHT_EXT:
-      *params = ctx->CurrentRenderbuffer->Height;
+      *params = rb->Height;
       return;
    case GL_RENDERBUFFER_INTERNAL_FORMAT_EXT:
-      *params = ctx->CurrentRenderbuffer->InternalFormat;
+      *params = rb->InternalFormat;
       return;
    case GL_RENDERBUFFER_RED_SIZE_EXT:
-      *params = ctx->CurrentRenderbuffer->RedBits;
+      *params = rb->RedBits;
       break;
    case GL_RENDERBUFFER_GREEN_SIZE_EXT:
-      *params = ctx->CurrentRenderbuffer->GreenBits;
+      *params = rb->GreenBits;
       break;
    case GL_RENDERBUFFER_BLUE_SIZE_EXT:
-      *params = ctx->CurrentRenderbuffer->BlueBits;
+      *params = rb->BlueBits;
       break;
    case GL_RENDERBUFFER_ALPHA_SIZE_EXT:
-      *params = ctx->CurrentRenderbuffer->AlphaBits;
+      *params = rb->AlphaBits;
       break;
    case GL_RENDERBUFFER_DEPTH_SIZE_EXT:
-      *params = ctx->CurrentRenderbuffer->DepthBits;
+      *params = rb->DepthBits;
       break;
    case GL_RENDERBUFFER_STENCIL_SIZE_EXT:
-      *params = ctx->CurrentRenderbuffer->StencilBits;
+      *params = rb->StencilBits;
       break;
+   case GL_RENDERBUFFER_SAMPLES:
+      if (ctx->Extensions.ARB_framebuffer_object) {
+         *params = rb->NumSamples;
+         break;
+      }
+      /* fallthrough */
    default:
       _mesa_error(ctx, GL_INVALID_ENUM,
                   "glGetRenderbufferParameterivEXT(target)");
@@ -1125,7 +1253,12 @@ _mesa_DeleteFramebuffersEXT(GLsizei n, const GLuint *framebuffers)
             if (fb == ctx->DrawBuffer) {
                /* bind default */
                ASSERT(fb->RefCount >= 2);
-               _mesa_BindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+               _mesa_BindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, 0);
+            }
+            if (fb == ctx->ReadBuffer) {
+               /* bind default */
+               ASSERT(fb->RefCount >= 2);
+               _mesa_BindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0);
             }
 
            /* remove from hash table immediately, to free the ID */
@@ -1135,7 +1268,7 @@ _mesa_DeleteFramebuffersEXT(GLsizei n, const GLuint *framebuffers)
                /* But the object will not be freed until it's no longer
                 * bound in any context.
                 */
-               _mesa_unreference_framebuffer(&fb);
+               _mesa_reference_framebuffer(&fb, NULL);
            }
         }
       }
@@ -1214,7 +1347,10 @@ _mesa_CheckFramebufferStatusEXT(GLenum target)
 
    FLUSH_VERTICES(ctx, _NEW_BUFFERS);
 
-   _mesa_test_framebuffer_completeness(ctx, buffer);
+   if (buffer->_Status != GL_FRAMEBUFFER_COMPLETE) {
+      _mesa_test_framebuffer_completeness(ctx, buffer);
+   }
+
    return buffer->_Status;
 }
 
@@ -1231,16 +1367,31 @@ framebuffer_texture(GLcontext *ctx, const char *caller, GLenum target,
    struct gl_renderbuffer_attachment *att;
    struct gl_texture_object *texObj = NULL;
    struct gl_framebuffer *fb;
+   GLboolean error = GL_FALSE;
 
    ASSERT_OUTSIDE_BEGIN_END(ctx);
 
-   if (target != GL_FRAMEBUFFER_EXT) {
+   switch (target) {
+   case GL_READ_FRAMEBUFFER_EXT:
+      error = !ctx->Extensions.EXT_framebuffer_blit;
+      fb = ctx->ReadBuffer;
+      break;
+   case GL_DRAW_FRAMEBUFFER_EXT:
+      error = !ctx->Extensions.EXT_framebuffer_blit;
+      /* fall-through */
+   case GL_FRAMEBUFFER_EXT:
+      fb = ctx->DrawBuffer;
+      break;
+   default:
+      error = GL_TRUE;
+   }
+
+   if (error) {
       _mesa_error(ctx, GL_INVALID_ENUM,
-                  "glFramebufferTexture%sEXT(target)", caller);
+                  "glFramebufferTexture%sEXT(target=0x%x)", caller, target);
       return;
    }
 
-   fb = ctx->DrawBuffer;
    ASSERT(fb);
 
    /* check framebuffer binding */
@@ -1295,7 +1446,6 @@ framebuffer_texture(GLcontext *ctx, const char *caller, GLenum target,
          }
       }
 
-
       if ((level < 0) || 
           (level >= _mesa_max_texture_levels(ctx, texObj->Target))) {
          _mesa_error(ctx, GL_INVALID_VALUE,
@@ -1311,6 +1461,18 @@ framebuffer_texture(GLcontext *ctx, const char *caller, GLenum target,
       return;
    }
 
+   if (texObj && attachment == GL_DEPTH_STENCIL_ATTACHMENT) {
+      /* the texture format must be depth+stencil */
+      const struct gl_texture_image *texImg;
+      texImg = texObj->Image[0][texObj->BaseLevel];
+      if (!texImg || texImg->_BaseFormat != GL_DEPTH_STENCIL) {
+         _mesa_error(ctx, GL_INVALID_OPERATION,
+                     "glFramebufferTexture%sEXT(texture is not"
+                     " DEPTH_STENCIL format)", caller);
+         return;
+      }
+   }
+
    FLUSH_VERTICES(ctx, _NEW_BUFFERS);
    /* The above doesn't fully flush the drivers in the way that a
     * glFlush does, but that is required here:
@@ -1322,10 +1484,22 @@ framebuffer_texture(GLcontext *ctx, const char *caller, GLenum target,
    if (texObj) {
       _mesa_set_texture_attachment(ctx, fb, att, texObj, textarget,
                                    level, zoffset);
+      /* Set the render-to-texture flag.  We'll check this flag in
+       * glTexImage() and friends to determine if we need to revalidate
+       * any FBOs that might be rendering into this texture.
+       * This flag never gets cleared since it's non-trivial to determine
+       * when all FBOs might be done rendering to this texture.  That's OK
+       * though since it's uncommon to render to a texture then repeatedly
+       * call glTexImage() to change images in the texture.
+       */
+      texObj->_RenderToTexture = GL_TRUE;
    }
    else {
       _mesa_remove_attachment(ctx, att);
    }
+
+   invalidate_framebuffer(fb);
+
    _glthread_UNLOCK_MUTEX(fb->Mutex);
 }
 
@@ -1359,7 +1533,7 @@ _mesa_FramebufferTexture2DEXT(GLenum target, GLenum attachment,
        (textarget != GL_TEXTURE_RECTANGLE_ARB) &&
        (!IS_CUBE_FACE(textarget))) {
       _mesa_error(ctx, GL_INVALID_OPERATION,
-                  "glFramebufferTexture2DEXT(textarget)");
+                  "glFramebufferTexture2DEXT(textarget=0x%x)", textarget);
       return;
    }
 
@@ -1469,6 +1643,17 @@ _mesa_FramebufferRenderbufferEXT(GLenum target, GLenum attachment,
       rb = NULL;
    }
 
+   if (attachment == GL_DEPTH_STENCIL_ATTACHMENT) {
+      /* make sure the renderbuffer is a depth/stencil format */
+      if (rb->_BaseFormat != GL_DEPTH_STENCIL) {
+         _mesa_error(ctx, GL_INVALID_OPERATION,
+                     "glFramebufferRenderbufferEXT(renderbuffer"
+                     " is not DEPTH_STENCIL format)");
+         return;
+      }
+   }
+
+
    FLUSH_VERTICES(ctx, _NEW_BUFFERS);
    /* The above doesn't fully flush the drivers in the way that a
     * glFlush does, but that is required here:
@@ -1537,6 +1722,19 @@ _mesa_GetFramebufferAttachmentParameterivEXT(GLenum target, GLenum attachment,
       return;
    }
 
+   if (attachment == GL_DEPTH_STENCIL_ATTACHMENT) {
+      /* the depth and stencil attachments must point to the same buffer */
+      const struct gl_renderbuffer_attachment *depthAtt, *stencilAtt;
+      depthAtt = _mesa_get_attachment(ctx, buffer, GL_DEPTH_ATTACHMENT);
+      stencilAtt = _mesa_get_attachment(ctx, buffer, GL_STENCIL_ATTACHMENT);
+      if (depthAtt->Renderbuffer != stencilAtt->Renderbuffer) {
+         _mesa_error(ctx, GL_INVALID_OPERATION,
+                     "glGetFramebufferAttachmentParameterivEXT(DEPTH/STENCIL"
+                     " attachments differ)");
+         return;
+      }
+   }
+
    FLUSH_VERTICES(ctx, _NEW_BUFFERS);
    /* The above doesn't fully flush the drivers in the way that a
     * glFlush does, but that is required here:
@@ -1719,11 +1917,22 @@ _mesa_GenerateMipmapEXT(GLenum target)
 
 
 #if FEATURE_EXT_framebuffer_blit
+/**
+ * Blit rectangular region, optionally from one framebuffer to another.
+ *
+ * Note, if the src buffer is multisampled and the dest is not, this is
+ * when the samples must be resolved to a single color.
+ */
 void GLAPIENTRY
 _mesa_BlitFramebufferEXT(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
                          GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
                          GLbitfield mask, GLenum filter)
 {
+   const GLbitfield legalMaskBits = (GL_COLOR_BUFFER_BIT |
+                                     GL_DEPTH_BUFFER_BIT |
+                                     GL_STENCIL_BUFFER_BIT);
+   const struct gl_framebuffer *readFb, *drawFb;
+   const struct gl_renderbuffer *colorReadRb, *colorDrawRb;
    GET_CURRENT_CONTEXT(ctx);
 
    ASSERT_OUTSIDE_BEGIN_END(ctx);
@@ -1733,13 +1942,19 @@ _mesa_BlitFramebufferEXT(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
       _mesa_update_state(ctx);
    }
 
-   if (!ctx->ReadBuffer) {
-      /* XXX */
+   readFb = ctx->ReadBuffer;
+   drawFb = ctx->DrawBuffer;
+
+   if (!readFb || !drawFb) {
+      /* This will normally never happen but someday we may want to
+       * support MakeCurrent() with no drawables.
+       */
+      return;
    }
 
    /* check for complete framebuffers */
-   if (ctx->DrawBuffer->_Status != GL_FRAMEBUFFER_COMPLETE_EXT ||
-       ctx->ReadBuffer->_Status != GL_FRAMEBUFFER_COMPLETE_EXT) {
+   if (drawFb->_Status != GL_FRAMEBUFFER_COMPLETE_EXT ||
+       readFb->_Status != GL_FRAMEBUFFER_COMPLETE_EXT) {
       _mesa_error(ctx, GL_INVALID_FRAMEBUFFER_OPERATION_EXT,
                   "glBlitFramebufferEXT(incomplete draw/read buffers)");
       return;
@@ -1750,9 +1965,7 @@ _mesa_BlitFramebufferEXT(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
       return;
    }
 
-   if (mask & ~(GL_COLOR_BUFFER_BIT |
-                GL_DEPTH_BUFFER_BIT |
-                GL_STENCIL_BUFFER_BIT)) {
+   if (mask & ~legalMaskBits) {
       _mesa_error( ctx, GL_INVALID_VALUE, "glBlitFramebufferEXT(mask)");
       return;
    }
@@ -1765,9 +1978,18 @@ _mesa_BlitFramebufferEXT(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
       return;
    }
 
+   /* get color read/draw renderbuffers */
+   if (mask & GL_COLOR_BUFFER_BIT) {
+      colorReadRb = readFb->_ColorReadBuffer;
+      colorDrawRb = drawFb->_ColorDrawBuffers[0];
+   }
+   else {
+      colorReadRb = colorDrawRb = NULL;
+   }
+
    if (mask & GL_STENCIL_BUFFER_BIT) {
-      struct gl_renderbuffer *readRb = ctx->ReadBuffer->_StencilBuffer;
-      struct gl_renderbuffer *drawRb = ctx->DrawBuffer->_StencilBuffer;
+      struct gl_renderbuffer *readRb = readFb->_StencilBuffer;
+      struct gl_renderbuffer *drawRb = drawFb->_StencilBuffer;
       if (readRb->StencilBits != drawRb->StencilBits) {
          _mesa_error(ctx, GL_INVALID_OPERATION,
                      "glBlitFramebufferEXT(stencil buffer size mismatch");
@@ -1776,8 +1998,8 @@ _mesa_BlitFramebufferEXT(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
    }
 
    if (mask & GL_DEPTH_BUFFER_BIT) {
-      struct gl_renderbuffer *readRb = ctx->ReadBuffer->_DepthBuffer;
-      struct gl_renderbuffer *drawRb = ctx->DrawBuffer->_DepthBuffer;
+      struct gl_renderbuffer *readRb = readFb->_DepthBuffer;
+      struct gl_renderbuffer *drawRb = drawFb->_DepthBuffer;
       if (readRb->DepthBits != drawRb->DepthBits) {
          _mesa_error(ctx, GL_INVALID_OPERATION,
                      "glBlitFramebufferEXT(depth buffer size mismatch");
@@ -1785,6 +2007,34 @@ _mesa_BlitFramebufferEXT(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
       }
    }
 
+   if (readFb->Visual.samples > 0 &&
+       drawFb->Visual.samples > 0 &&
+       readFb->Visual.samples != drawFb->Visual.samples) {
+      _mesa_error(ctx, GL_INVALID_OPERATION,
+                  "glBlitFramebufferEXT(mismatched samples");
+      return;
+   }
+
+   /* extra checks for multisample copies... */
+   if (readFb->Visual.samples > 0 || drawFb->Visual.samples > 0) {
+      /* src and dest region sizes must be the same */
+      if (srcX1 - srcX0 != dstX1 - dstX0 ||
+          srcY1 - srcY0 != dstY1 - dstY0) {
+         _mesa_error(ctx, GL_INVALID_OPERATION,
+                "glBlitFramebufferEXT(bad src/dst multisample region sizes");
+         return;
+      }
+
+      /* color formats must match */
+      if (colorReadRb &&
+          colorDrawRb &&
+          colorReadRb->_ActualFormat != colorDrawRb->_ActualFormat) {
+         _mesa_error(ctx, GL_INVALID_OPERATION,
+                "glBlitFramebufferEXT(bad src/dst multisample pixel formats");
+         return;
+      }
+   }
+
    if (!ctx->Extensions.EXT_framebuffer_blit) {
       _mesa_error(ctx, GL_INVALID_OPERATION, "glBlitFramebufferEXT");
       return;