Bug 707460 - Fix WebGL framebuffer statuses and errors - r=jgilbert

This commit is contained in:
Benoit Jacob 2012-01-24 16:12:31 -05:00
parent 249106d9ba
commit 0f5ca361a9
4 changed files with 121 additions and 87 deletions

View File

@ -1104,6 +1104,17 @@ WebGLContext::EnsureBackbufferClearedAsNeeded()
Invalidate();
}
nsresult
WebGLContext::DummyFramebufferOperation(const char *info)
{
WebGLenum status;
CheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER, &status);
if (status == LOCAL_GL_FRAMEBUFFER_COMPLETE)
return NS_OK;
else
return ErrorInvalidFramebufferOperation("%s: incomplete framebuffer", info);
}
// We use this timer for many things. Here are the things that it is activated for:
// 1) If a script is using the MOZ_WEBGL_lose_context extension.
// 2) If we are using EGL and _NOT ANGLE_, we query periodically to see if the

View File

@ -566,13 +566,16 @@ public:
nsresult ErrorInvalidEnum(const char *fmt = 0, ...);
nsresult ErrorInvalidOperation(const char *fmt = 0, ...);
nsresult ErrorInvalidValue(const char *fmt = 0, ...);
nsresult ErrorInvalidFramebufferOperation(const char *fmt = 0, ...);
nsresult ErrorInvalidEnumInfo(const char *info, PRUint32 enumvalue) {
return ErrorInvalidEnum("%s: invalid enum value 0x%x", info, enumvalue);
}
nsresult ErrorOutOfMemory(const char *fmt = 0, ...);
const char *ErrorName(GLenum error);
nsresult DummyFramebufferOperation(const char *info);
WebGLTexture *activeBoundTextureForTarget(WebGLenum target) {
return target == LOCAL_GL_TEXTURE_2D ? mBound2DTextures[mActiveTexture]
: mBoundCubeMapTextures[mActiveTexture];
@ -1939,31 +1942,6 @@ public:
return mTextureCubeMapFace;
}
bool IsIncompatibleWithAttachmentPoint() const
{
// textures can only be color textures in WebGL
if (mTexturePtr)
return mAttachmentPoint != LOCAL_GL_COLOR_ATTACHMENT0;
if (mRenderbufferPtr) {
WebGLenum format = mRenderbufferPtr->InternalFormat();
switch (mAttachmentPoint) {
case LOCAL_GL_COLOR_ATTACHMENT0:
return format != LOCAL_GL_RGB565 &&
format != LOCAL_GL_RGB5_A1 &&
format != LOCAL_GL_RGBA4;
case LOCAL_GL_DEPTH_ATTACHMENT:
return format != LOCAL_GL_DEPTH_COMPONENT16;
case LOCAL_GL_STENCIL_ATTACHMENT:
return format != LOCAL_GL_STENCIL_INDEX8;
case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT:
return format != LOCAL_GL_DEPTH_STENCIL;
}
}
return false; // no attachment at all, so no incompatibility
}
bool HasUninitializedRenderbuffer() const {
return mRenderbufferPtr && !mRenderbufferPtr->Initialized();
}
@ -1988,6 +1966,39 @@ public:
otherRect &&
thisRect->HasSameDimensionsAs(*otherRect);
}
bool IsComplete() const {
const WebGLRectangleObject *thisRect = RectangleObject();
if (!thisRect ||
!thisRect->Width() ||
!thisRect->Height())
return false;
if (mTexturePtr)
return mAttachmentPoint == LOCAL_GL_COLOR_ATTACHMENT0;
if (mRenderbufferPtr) {
WebGLenum format = mRenderbufferPtr->InternalFormat();
switch (mAttachmentPoint) {
case LOCAL_GL_COLOR_ATTACHMENT0:
return format == LOCAL_GL_RGB565 ||
format == LOCAL_GL_RGB5_A1 ||
format == LOCAL_GL_RGBA4;
case LOCAL_GL_DEPTH_ATTACHMENT:
return format == LOCAL_GL_DEPTH_COMPONENT16;
case LOCAL_GL_STENCIL_ATTACHMENT:
return format == LOCAL_GL_STENCIL_INDEX8;
case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT:
return format == LOCAL_GL_DEPTH_STENCIL;
default:
NS_ABORT(); // should have been validated earlier
}
}
NS_ABORT(); // should never get there
return false;
}
};
class WebGLFramebuffer
@ -2135,50 +2146,23 @@ public:
return NS_OK;
}
bool CheckAndInitializeRenderbuffers()
{
if (HasBadAttachments()) {
mContext->SynthesizeGLError(LOCAL_GL_INVALID_FRAMEBUFFER_OPERATION);
return false;
}
if (mColorAttachment.HasUninitializedRenderbuffer() ||
mDepthAttachment.HasUninitializedRenderbuffer() ||
mStencilAttachment.HasUninitializedRenderbuffer() ||
mDepthStencilAttachment.HasUninitializedRenderbuffer())
{
InitializeRenderbuffers();
}
return true;
bool HasIncompleteAttachment() const {
return (mColorAttachment.IsDefined() && !mColorAttachment.IsComplete()) ||
(mDepthAttachment.IsDefined() && !mDepthAttachment.IsComplete()) ||
(mStencilAttachment.IsDefined() && !mStencilAttachment.IsComplete()) ||
(mDepthStencilAttachment.IsDefined() && !mDepthStencilAttachment.IsComplete());
}
bool HasBadAttachments() const {
if (mColorAttachment.IsIncompatibleWithAttachmentPoint() ||
mDepthAttachment.IsIncompatibleWithAttachmentPoint() ||
mStencilAttachment.IsIncompatibleWithAttachmentPoint() ||
mDepthStencilAttachment.IsIncompatibleWithAttachmentPoint())
{
// some attachment is incompatible with its attachment point
return true;
}
bool HasDepthStencilConflict() const {
return int(mDepthAttachment.IsDefined()) +
int(mStencilAttachment.IsDefined()) +
int(mDepthStencilAttachment.IsDefined()) >= 2;
}
if (int(mDepthAttachment.IsDefined()) +
int(mStencilAttachment.IsDefined()) +
int(mDepthStencilAttachment.IsDefined()) >= 2)
{
// has at least two among Depth, Stencil, DepthStencil
return true;
}
if (mDepthAttachment.IsDefined() && !mDepthAttachment.HasSameDimensionsAs(mColorAttachment))
return true;
if (mStencilAttachment.IsDefined() && !mStencilAttachment.HasSameDimensionsAs(mColorAttachment))
return true;
if (mDepthStencilAttachment.IsDefined() && !mDepthStencilAttachment.HasSameDimensionsAs(mColorAttachment))
return true;
return false;
bool HasAttachmentsOfMismatchedDimensions() const {
return (mDepthAttachment.IsDefined() && !mDepthAttachment.HasSameDimensionsAs(mColorAttachment)) ||
(mStencilAttachment.IsDefined() && !mStencilAttachment.HasSameDimensionsAs(mColorAttachment)) ||
(mDepthStencilAttachment.IsDefined() && !mDepthStencilAttachment.HasSameDimensionsAs(mColorAttachment));
}
const WebGLFramebufferAttachment& ColorAttachment() const {
@ -2238,15 +2222,32 @@ public:
NS_DECL_ISUPPORTS
NS_DECL_NSIWEBGLFRAMEBUFFER
protected:
// protected because WebGLContext should only call InitializeRenderbuffers
void InitializeRenderbuffers()
bool CheckAndInitializeRenderbuffers()
{
// enforce WebGL section 6.5 which is WebGL-specific, hence OpenGL itself would not
// generate the INVALID_FRAMEBUFFER_OPERATION that we need here
if (HasDepthStencilConflict())
return false;
if (!mColorAttachment.HasUninitializedRenderbuffer() &&
!mDepthAttachment.HasUninitializedRenderbuffer() &&
!mStencilAttachment.HasUninitializedRenderbuffer() &&
!mDepthStencilAttachment.HasUninitializedRenderbuffer())
return true;
// ensure INVALID_FRAMEBUFFER_OPERATION in zero-size case
const WebGLRectangleObject *rect = mColorAttachment.RectangleObject();
if (!rect ||
!rect->Width() ||
!rect->Height())
return false;
mContext->MakeContextCurrent();
if (mContext->gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER) != LOCAL_GL_FRAMEBUFFER_COMPLETE)
return;
WebGLenum status;
mContext->CheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER, &status);
if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE)
return false;
PRUint32 mask = 0;
@ -2265,7 +2266,6 @@ protected:
mask |= LOCAL_GL_STENCIL_BUFFER_BIT;
}
const WebGLRectangleObject *rect = mColorAttachment.RectangleObject();
mContext->ForceClearFramebufferWithDefaultValues(mask, nsIntRect(0, 0, rect->Width(), rect->Height()));
if (mColorAttachment.HasUninitializedRenderbuffer())
@ -2279,6 +2279,8 @@ protected:
if (mDepthStencilAttachment.HasUninitializedRenderbuffer())
mDepthStencilAttachment.Renderbuffer()->SetInitialized(true);
return true;
}
WebGLuint mGLName;

View File

@ -686,8 +686,16 @@ WebGLContext::CheckFramebufferStatus(WebGLenum target, WebGLenum *retval)
if (target != LOCAL_GL_FRAMEBUFFER)
return ErrorInvalidEnum("checkFramebufferStatus: target must be FRAMEBUFFER");
if (mBoundFramebuffer && mBoundFramebuffer->HasBadAttachments())
if (!mBoundFramebuffer)
*retval = LOCAL_GL_FRAMEBUFFER_COMPLETE;
else if(mBoundFramebuffer->HasDepthStencilConflict())
*retval = LOCAL_GL_FRAMEBUFFER_UNSUPPORTED;
else if(!mBoundFramebuffer->ColorAttachment().IsDefined())
*retval = LOCAL_GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT;
else if(mBoundFramebuffer->HasIncompleteAttachment())
*retval = LOCAL_GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
else if(mBoundFramebuffer->HasAttachmentsOfMismatchedDimensions())
*retval = LOCAL_GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS;
else
*retval = gl->fCheckFramebufferStatus(target);
@ -710,7 +718,7 @@ WebGLContext::Clear(PRUint32 mask)
if (mBoundFramebuffer) {
if (!mBoundFramebuffer->CheckAndInitializeRenderbuffers())
return NS_OK;
return ErrorInvalidFramebufferOperation("clear: incomplete framebuffer");
} else {
// no FBO is bound, so we are clearing the backbuffer here
EnsureBackbufferClearedAsNeeded();
@ -861,7 +869,7 @@ WebGLContext::CopyTexSubImage2D_base(WebGLenum target,
|| y+height <= 0)
{
// we are completely outside of range, can exit now with buffer filled with zeros
return NS_OK;
return DummyFramebufferOperation(info);
}
GLint actual_x = clamped(x, 0, framebufferWidth);
@ -946,8 +954,9 @@ WebGLContext::CopyTexImage2D(WebGLenum target,
return ErrorInvalidOperation("copyTexImage2D: texture format requires an alpha channel "
"but the framebuffer doesn't have one");
if (mBoundFramebuffer && !mBoundFramebuffer->CheckAndInitializeRenderbuffers())
return NS_OK;
if (mBoundFramebuffer)
if (!mBoundFramebuffer->CheckAndInitializeRenderbuffers())
return ErrorInvalidFramebufferOperation("copyTexImage2D: incomplete framebuffer");
WebGLTexture *tex = activeBoundTextureForTarget(target);
if (!tex)
@ -1053,8 +1062,9 @@ WebGLContext::CopyTexSubImage2D(WebGLenum target,
return ErrorInvalidOperation("copyTexSubImage2D: texture format requires an alpha channel "
"but the framebuffer doesn't have one");
if (mBoundFramebuffer && !mBoundFramebuffer->CheckAndInitializeRenderbuffers())
return NS_OK;
if (mBoundFramebuffer)
if (!mBoundFramebuffer->CheckAndInitializeRenderbuffers())
return ErrorInvalidFramebufferOperation("copyTexSubImage2D: incomplete framebuffer");
return CopyTexSubImage2D_base(target, level, format, xoffset, yoffset, x, y, width, height, true);
}
@ -1588,7 +1598,7 @@ WebGLContext::DrawArrays(GLenum mode, WebGLint first, WebGLsizei count)
if (mBoundFramebuffer) {
if (!mBoundFramebuffer->CheckAndInitializeRenderbuffers())
return NS_OK;
return ErrorInvalidFramebufferOperation("drawArrays: incomplete framebuffer");
} else {
EnsureBackbufferClearedAsNeeded();
}
@ -1699,7 +1709,7 @@ WebGLContext::DrawElements(WebGLenum mode, WebGLsizei count, WebGLenum type, Web
if (mBoundFramebuffer) {
if (!mBoundFramebuffer->CheckAndInitializeRenderbuffers())
return NS_OK;
return ErrorInvalidFramebufferOperation("drawElements: incomplete framebuffer");
} else {
EnsureBackbufferClearedAsNeeded();
}
@ -3372,7 +3382,7 @@ WebGLContext::ReadPixels_base(WebGLint x, WebGLint y, WebGLsizei width, WebGLsiz
if (mBoundFramebuffer) {
// prevent readback of arbitrary video memory through uninitialized renderbuffers!
if (!mBoundFramebuffer->CheckAndInitializeRenderbuffers())
return NS_OK;
return ErrorInvalidFramebufferOperation("readPixels: incomplete framebuffer");
} else {
EnsureBackbufferClearedAsNeeded();
}
@ -3380,7 +3390,7 @@ WebGLContext::ReadPixels_base(WebGLint x, WebGLint y, WebGLsizei width, WebGLsiz
// If we won't be reading any pixels anyways, just skip the actual reading
if (width == 0 || height == 0)
return NS_OK;
return DummyFramebufferOperation("readPixels");
if (CanvasUtils::CheckSaneSubrectSize(x, y, width, height, framebufferWidth, framebufferHeight)) {
// the easy case: we're not reading out-of-range pixels
@ -3403,7 +3413,7 @@ WebGLContext::ReadPixels_base(WebGLint x, WebGLint y, WebGLsizei width, WebGLsiz
|| y+height <= 0)
{
// we are completely outside of range, can exit now with buffer filled with zeros
return NS_OK;
return DummyFramebufferOperation("readPixels");
}
// compute the parameters of the subrect we're actually going to call glReadPixels on
@ -3568,7 +3578,7 @@ WebGLContext::RenderbufferStorage(WebGLenum target, WebGLenum internalformat, We
GLenum error = LOCAL_GL_NO_ERROR;
UpdateWebGLErrorAndClearGLError(&error);
if (error) {
LogMessageIfVerbose("bufferData generated error %s", ErrorName(error));
LogMessageIfVerbose("renderbufferStorage generated error %s", ErrorName(error));
return NS_OK;
}
} else {

View File

@ -197,6 +197,17 @@ WebGLContext::ErrorInvalidValue(const char *fmt, ...)
return SynthesizeGLError(LOCAL_GL_INVALID_VALUE);
}
nsresult
WebGLContext::ErrorInvalidFramebufferOperation(const char *fmt, ...)
{
va_list va;
va_start(va, fmt);
LogMessageIfVerbose(fmt, va);
va_end(va);
return SynthesizeGLError(LOCAL_GL_INVALID_FRAMEBUFFER_OPERATION);
}
nsresult
WebGLContext::ErrorOutOfMemory(const char *fmt, ...)
{