diff --git a/content/canvas/src/WebGLContext.h b/content/canvas/src/WebGLContext.h index fa90db0577f2..03e0653db3a8 100644 --- a/content/canvas/src/WebGLContext.h +++ b/content/canvas/src/WebGLContext.h @@ -221,28 +221,33 @@ struct WebGLVertexAttribData { GLenum type; PRBool enabled; - GLuint actualStride() const { - if (stride) return stride; - GLuint componentSize = 0; + GLuint componentSize() const { switch(type) { case LOCAL_GL_BYTE: - componentSize = sizeof(GLbyte); + return sizeof(GLbyte); break; case LOCAL_GL_UNSIGNED_BYTE: - componentSize = sizeof(GLubyte); + return sizeof(GLubyte); break; case LOCAL_GL_SHORT: - componentSize = sizeof(GLshort); + return sizeof(GLshort); break; case LOCAL_GL_UNSIGNED_SHORT: - componentSize = sizeof(GLushort); + return sizeof(GLushort); break; // XXX case LOCAL_GL_FIXED: case LOCAL_GL_FLOAT: - componentSize = sizeof(GLfloat); + return sizeof(GLfloat); break; + default: + NS_ERROR("Should never get here!"); + return 0; } - return size * componentSize; + } + + GLuint actualStride() const { + if (stride) return stride; + return size * componentSize(); } }; diff --git a/content/canvas/src/WebGLContextGL.cpp b/content/canvas/src/WebGLContextGL.cpp index cc51f14e7530..0e9543f8a7aa 100644 --- a/content/canvas/src/WebGLContextGL.cpp +++ b/content/canvas/src/WebGLContextGL.cpp @@ -803,15 +803,21 @@ WebGLContext::DrawArrays(GLenum mode, WebGLint first, WebGLsizei count) return ErrorInvalidEnum("DrawArrays: invalid mode"); } - if (first < 0 || count < 0 || first+count < first || first+count < count) { - return ErrorInvalidValue("DrawArrays: overflow in first+count"); - } + if (first < 0 || count < 0) + return ErrorInvalidValue("DrawArrays: negative first or count"); + + // If count is 0, there's nothing to do. + if (count == 0) + return NS_OK; // If there is no current program, this is silently ignored. // Any checks below this depend on a program being available. if (!mCurrentProgram) return NS_OK; + if (first+count < first || first+count < count) + return ErrorInvalidOperation("DrawArrays: overflow in first+count"); + if (!ValidateBuffers(first+count)) return ErrorInvalidOperation("DrawArrays: bound vertex attribute buffers do not have sufficient data for given first and count"); @@ -825,10 +831,8 @@ WebGLContext::DrawArrays(GLenum mode, WebGLint first, WebGLsizei count) } NS_IMETHODIMP -WebGLContext::DrawElements(WebGLenum mode, WebGLuint count, WebGLenum type, WebGLuint byteOffset) +WebGLContext::DrawElements(WebGLenum mode, WebGLsizei count, WebGLenum type, WebGLint byteOffset) { - int elementSize = 0; - switch (mode) { case LOCAL_GL_TRIANGLES: case LOCAL_GL_TRIANGLE_STRIP: @@ -842,42 +846,47 @@ WebGLContext::DrawElements(WebGLenum mode, WebGLuint count, WebGLenum type, WebG return ErrorInvalidEnum("DrawElements: invalid mode"); } - switch (type) { - case LOCAL_GL_UNSIGNED_SHORT: - elementSize = 2; - if (byteOffset % 2 != 0) - return ErrorInvalidValue("DrawElements: invalid byteOffset for UNSIGNED_SHORT (must be a multiple of 2)"); - break; + if (count < 0 || byteOffset < 0) + return ErrorInvalidValue("DrawElements: negative count or offset"); - case LOCAL_GL_UNSIGNED_BYTE: - elementSize = 1; - break; + WebGLuint byteCount; + if (type == LOCAL_GL_UNSIGNED_SHORT) { + byteCount = WebGLuint(count) << 1; + if (byteCount >> 1 != WebGLuint(count)) + return ErrorInvalidValue("DrawElements: overflow in byteCount"); - default: - return ErrorInvalidEnum("DrawElements: type must be UNSIGNED_SHORT or UNSIGNED_BYTE"); + if (byteOffset % 2 != 0) + return ErrorInvalidValue("DrawElements: invalid byteOffset for UNSIGNED_SHORT (must be a multiple of 2)"); + } else if (type == LOCAL_GL_UNSIGNED_BYTE) { + byteCount = count; + } else { + return ErrorInvalidEnum("DrawElements: type must be UNSIGNED_SHORT or UNSIGNED_BYTE"); } - if (!mBoundElementArrayBuffer) - return ErrorInvalidOperation("DrawElements: must have element array buffer binding"); - - WebGLuint byteCount = count*elementSize; - - if (count < 0 || byteOffset+byteCount < byteOffset || byteOffset+byteCount < byteCount) - return ErrorInvalidValue("DrawElements: overflow in byteOffset+byteCount"); - - if (byteOffset + byteCount > mBoundElementArrayBuffer->ByteLength()) - return ErrorInvalidOperation("DrawElements: bound element array buffer is too small for given count and offset"); + // If count is 0, there's nothing to do. + if (count == 0) + return NS_OK; // If there is no current program, this is silently ignored. // Any checks below this depend on a program being available. if (!mCurrentProgram) return NS_OK; + if (!mBoundElementArrayBuffer) + return ErrorInvalidOperation("DrawElements: must have element array buffer binding"); + + if (byteOffset+byteCount < byteOffset || byteOffset+byteCount < byteCount) + return ErrorInvalidOperation("DrawElements: overflow in byteOffset+byteCount"); + + if (byteOffset + byteCount > mBoundElementArrayBuffer->ByteLength()) + return ErrorInvalidOperation("DrawElements: bound element array buffer is too small for given count and offset"); + WebGLuint maxIndex = 0; - if (type == LOCAL_GL_UNSIGNED_SHORT) + if (type == LOCAL_GL_UNSIGNED_SHORT) { maxIndex = mBoundElementArrayBuffer->FindMaximum(count, byteOffset); - else if (type == LOCAL_GL_UNSIGNED_BYTE) + } else if (type == LOCAL_GL_UNSIGNED_BYTE) { maxIndex = mBoundElementArrayBuffer->FindMaximum(count, byteOffset); + } // maxIndex+1 because ValidateBuffers expects the number of elements needed if (!ValidateBuffers(maxIndex+1)) { @@ -1032,16 +1041,21 @@ WebGLContext::GetActiveAttrib(nsIWebGLProgram *pobj, PRUint32 index, nsIWebGLAct GLint len = 0; gl->fGetProgramiv(progname, LOCAL_GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &len); - if (len == 0) - return NS_ERROR_FAILURE; // XXX GL error? This really shouldn't happen. + if (len == 0) { + // is this an error? can you have a program with no attributes? + *retval = nsnull; + return NS_OK; + } nsAutoArrayPtr name(new char[len+1]); PRInt32 attrsize = 0; PRUint32 attrtype = 0; gl->fGetActiveAttrib(progname, index, len+1, &len, (GLint*) &attrsize, (WebGLuint*) &attrtype, name); - if (attrsize == 0 || attrtype == 0) - return NS_ERROR_FAILURE; + if (attrsize == 0 || attrtype == 0) { + *retval = nsnull; + return NS_OK; + } JSObjectHelper retobj(&js); retobj.DefineProperty("size", attrsize); diff --git a/content/canvas/src/WebGLContextValidate.cpp b/content/canvas/src/WebGLContextValidate.cpp index dec619fa52d8..5108a6e3c009 100644 --- a/content/canvas/src/WebGLContextValidate.cpp +++ b/content/canvas/src/WebGLContextValidate.cpp @@ -51,6 +51,8 @@ WebGLContext::ValidateBuffers(PRUint32 count) GLint currentProgram = -1; GLint numAttributes = -1; + NS_ENSURE_TRUE(count > 0, PR_TRUE); + MakeContextCurrent(); // XXX cache this per program @@ -93,7 +95,10 @@ WebGLContext::ValidateBuffers(PRUint32 count) return PR_FALSE; } - WebGLuint needed = vd.byteOffset + vd.actualStride() * count; + WebGLuint needed = vd.byteOffset + // the base offset + vd.actualStride() * (count-1) + // to stride to the start of the last element group + vd.componentSize() * vd.size; // and the number of bytes needed for these components + if (vd.buf->ByteLength() < needed) { LogMessage("VBO too small for bound attrib index %d: need at least %d bytes, but have only %d", i, needed, vd.buf->ByteLength()); return PR_FALSE; diff --git a/content/canvas/test/webgl/conformance/draw-arrays-out-of-bounds.html b/content/canvas/test/webgl/conformance/draw-arrays-out-of-bounds.html index c990bf2f08fc..2da92e7a723d 100644 --- a/content/canvas/test/webgl/conformance/draw-arrays-out-of-bounds.html +++ b/content/canvas/test/webgl/conformance/draw-arrays-out-of-bounds.html @@ -50,10 +50,10 @@ shouldGenerateGLError(context, context.INVALID_OPERATION, "context.drawArrays(co shouldGenerateGLError(context, context.INVALID_OPERATION, "context.drawArrays(context.TRIANGLES, 0, 10000)"); shouldGenerateGLError(context, context.INVALID_OPERATION, "context.drawArrays(context.TRIANGLES, 0, 10000000000000)"); shouldGenerateGLError(context, context.INVALID_VALUE, "context.drawArrays(context.TRIANGLES, 0, -1)"); -shouldGenerateGLError(context, context.INVALID_OPERATION, "context.drawArrays(context.TRIANGLES, 1, 0)"); +shouldGenerateGLError(context, context.NO_ERROR, "context.drawArrays(context.TRIANGLES, 1, 0)"); shouldGenerateGLError(context, context.INVALID_VALUE, "context.drawArrays(context.TRIANGLES, -1, 0)"); -shouldGenerateGLError(context, context.INVALID_OPERATION, "context.drawArrays(context.TRIANGLES, 1, -1)"); -shouldGenerateGLError(context, context.INVALID_OPERATION, "context.drawArrays(context.TRIANGLES, -1, 1)"); +shouldGenerateGLError(context, context.INVALID_VALUE, "context.drawArrays(context.TRIANGLES, 1, -1)"); +shouldGenerateGLError(context, context.INVALID_VALUE, "context.drawArrays(context.TRIANGLES, -1, 1)"); debug("") debug("Test buffer with 3 float vectors") @@ -65,8 +65,57 @@ shouldGenerateGLError(context, context.INVALID_OPERATION, "context.drawArrays(co shouldGenerateGLError(context, context.INVALID_OPERATION, "context.drawArrays(context.TRIANGLES, 0, 10000000000000)"); shouldGenerateGLError(context, context.INVALID_VALUE, "context.drawArrays(context.TRIANGLES, 0, -1)"); shouldGenerateGLError(context, context.INVALID_VALUE, "context.drawArrays(context.TRIANGLES, -1, 0)"); -shouldGenerateGLError(context, context.INVALID_OPERATION, "context.drawArrays(context.TRIANGLES, 1, -1)"); -shouldGenerateGLError(context, context.INVALID_OPERATION, "context.drawArrays(context.TRIANGLES, -1, 1)"); +shouldGenerateGLError(context, context.INVALID_VALUE, "context.drawArrays(context.TRIANGLES, 1, -1)"); +shouldGenerateGLError(context, context.INVALID_VALUE, "context.drawArrays(context.TRIANGLES, -1, 1)"); + +debug("") +debug("Test buffer with interleaved (3+2) float vectors") + +var program2 = createProgram(context, + "attribute vec3 aOne;" + + "attribute vec2 aTwo;" + + "void main() { gl_Position = vec4(aOne, 1.0) + vec4(aTwo, 0.0, 1.0); }", + "void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); }", + [ "aOne", "aTwo" ]); +if (!program2) { + testFailed("failed to create test program"); +} + +context.useProgram(program2); + +var vbo = context.createBuffer(); +context.bindBuffer(context.ARRAY_BUFFER, vbo); +// enough for 9 vertices, so 3 triangles +context.bufferData(context.ARRAY_BUFFER, new WebGLFloatArray(9*5), context.STATIC_DRAW); + +// bind first 3 elements, with a stride of 5 float elements +context.vertexAttribPointer(0, 3, context.FLOAT, false, 5*4, 0); +// bind 2 elements, starting after the first 3; same stride of 5 float elements +context.vertexAttribPointer(1, 2, context.FLOAT, false, 5*4, 3*4); + +context.enableVertexAttribArray(0); +context.enableVertexAttribArray(1); + +shouldGenerateGLError(context, context.NO_ERROR, "context.drawArrays(context.TRIANGLES, 0, 9)"); + +// negative values must generate INVALID_VALUE; they can never be valid +shouldGenerateGLError(context, context.INVALID_VALUE, "context.drawArrays(context.TRIANGLES, 0, -500)"); +shouldGenerateGLError(context, context.INVALID_VALUE, "context.drawArrays(context.TRIANGLES, -200, 1)"); +shouldGenerateGLError(context, context.INVALID_VALUE, "context.drawArrays(context.TRIANGLES, -200, -500)"); + +// 0xffffffff needs to convert to a 'long' IDL argument as -1, as per +// WebIDL 4.1.7. JS ToInt32(0xffffffff) == -1, which is the first step +// of the conversion. Thus INVALID_VALUE. +shouldGenerateGLError(context, context.INVALID_VALUE, "context.drawArrays(context.TRIANGLES, 0, 0xffffffff)"); +shouldGenerateGLError(context, context.INVALID_VALUE, "context.drawArrays(context.TRIANGLES, 0xffffffff, 1)"); +shouldGenerateGLError(context, context.INVALID_VALUE, "context.drawArrays(context.TRIANGLES, 0xffffffff, 0xffffffff)"); + +// values that could otherwise be valid but aren't due to bindings generate +// INVALID_OPERATION +shouldGenerateGLError(context, context.INVALID_OPERATION, "context.drawArrays(context.TRIANGLES, 0, 200)"); +shouldGenerateGLError(context, context.INVALID_OPERATION, "context.drawArrays(context.TRIANGLES, 0, 0x7fffffff)"); +shouldGenerateGLError(context, context.INVALID_OPERATION, "context.drawArrays(context.TRIANGLES, 0x7fffffff, 1)"); +shouldGenerateGLError(context, context.INVALID_OPERATION, "context.drawArrays(context.TRIANGLES, 0x7fffffff, 0x7fffffff)"); debug("") successfullyParsed = true; diff --git a/content/canvas/test/webgl/conformance/draw-elements-out-of-bounds.html b/content/canvas/test/webgl/conformance/draw-elements-out-of-bounds.html index abe92afa99f7..f0845473ae5e 100644 --- a/content/canvas/test/webgl/conformance/draw-elements-out-of-bounds.html +++ b/content/canvas/test/webgl/conformance/draw-elements-out-of-bounds.html @@ -54,9 +54,9 @@ shouldGenerateGLError(context, context.INVALID_OPERATION, "context.drawElements( shouldGenerateGLError(context, context.INVALID_OPERATION, "context.drawElements(context.TRIANGLES, 10000, context.UNSIGNED_BYTE, 0)"); shouldGenerateGLError(context, context.INVALID_OPERATION, "context.drawElements(context.TRIANGLES, 10000000000000, context.UNSIGNED_BYTE, 0)"); shouldGenerateGLError(context, context.INVALID_OPERATION, "context.drawElements(context.TRIANGLES, 1, context.UNSIGNED_BYTE, 0)"); -shouldGenerateGLError(context, context.INVALID_OPERATION, "context.drawElements(context.TRIANGLES, 0, context.UNSIGNED_BYTE, -1)"); +shouldGenerateGLError(context, context.INVALID_VALUE, "context.drawElements(context.TRIANGLES, 0, context.UNSIGNED_BYTE, -1)"); shouldGenerateGLError(context, context.INVALID_VALUE, "context.drawElements(context.TRIANGLES, -1, context.UNSIGNED_BYTE, 1)"); -shouldGenerateGLError(context, context.INVALID_OPERATION, "context.drawElements(context.TRIANGLES, 1, context.UNSIGNED_BYTE, -1)"); +shouldGenerateGLError(context, context.INVALID_VALUE, "context.drawElements(context.TRIANGLES, 1, context.UNSIGNED_BYTE, -1)"); debug("") debug("Test buffer with 3 byte indexes") @@ -70,6 +70,61 @@ shouldGenerateGLError(context, context.INVALID_VALUE, "context.drawElements(cont shouldGenerateGLError(context, context.INVALID_VALUE, "context.drawElements(context.TRIANGLES, -1, context.UNSIGNED_BYTE, 1)"); shouldGenerateGLError(context, context.INVALID_VALUE, "context.drawElements(context.TRIANGLES, 1, context.UNSIGNED_BYTE, -1)"); +debug("") +debug("Test buffer with interleaved (3+2) float vectors") + +var program2 = createProgram(context, + "attribute vec3 aOne;" + + "attribute vec2 aTwo;" + + "void main() { gl_Position = vec4(aOne, 1.0) + vec4(aTwo, 0.0, 1.0); }", + "void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); }", + [ "aOne", "aTwo" ]); +if (!program2) { + testFailed("failed to create test program"); +} + +context.useProgram(program2); + +var vbo = context.createBuffer(); +context.bindBuffer(context.ARRAY_BUFFER, vbo); +// enough for 9 vertices, so 3 triangles +context.bufferData(context.ARRAY_BUFFER, new WebGLFloatArray(9*5), context.STATIC_DRAW); + +// bind first 3 elements, with a stride of 5 float elements +context.vertexAttribPointer(0, 3, context.FLOAT, false, 5*4, 0); +// bind 2 elements, starting after the first 3; same stride of 5 float elements +context.vertexAttribPointer(1, 2, context.FLOAT, false, 5*4, 3*4); + +context.enableVertexAttribArray(0); +context.enableVertexAttribArray(1); + +var ebo = context.createBuffer(); +context.bindBuffer(context.ELEMENT_ARRAY_BUFFER, ebo); +context.bufferData(context.ELEMENT_ARRAY_BUFFER, new WebGLUnsignedShortArray([ 0, 1, 2, + 1, 2, 0, + 2, 0, 1, + 200, 200, 200, + 0x7fff, 0x7fff, 0x7fff, + 0xffff, 0xffff, 0xffff ]), + context.STATIC_DRAW); + +shouldGenerateGLError(context, context.NO_ERROR, "context.drawElements(context.TRIANGLES, 9, context.UNSIGNED_SHORT, 0)"); + +// invalid type arguments +shouldGenerateGLError(context, context.INVALID_ENUM, "context.drawElements(context.TRIANGLES, 9, context.FLOAT, 0)"); +shouldGenerateGLError(context, context.INVALID_ENUM, "context.drawElements(context.TRIANGLES, 9, context.SHORT, 0)"); +shouldGenerateGLError(context, context.INVALID_ENUM, "context.drawElements(context.TRIANGLES, 9, context.UNSIGNED_INT, 0)"); + +// invalid operation with indices that would be valid with correct bindings +shouldGenerateGLError(context, context.INVALID_OPERATION, "context.drawElements(context.TRIANGLES, 9, context.UNSIGNED_SHORT, 1000)"); +shouldGenerateGLError(context, context.INVALID_OPERATION, "context.drawElements(context.TRIANGLES, 12, context.UNSIGNED_SHORT, 0)"); +shouldGenerateGLError(context, context.INVALID_OPERATION, "context.drawElements(context.TRIANGLES, 15, context.UNSIGNED_SHORT, 0)"); +shouldGenerateGLError(context, context.INVALID_OPERATION, "context.drawElements(context.TRIANGLES, 18, context.UNSIGNED_SHORT, 0)"); +shouldGenerateGLError(context, context.INVALID_OPERATION, "context.drawElements(context.TRIANGLES, 3, context.UNSIGNED_SHORT, 2*15)"); + +shouldGenerateGLError(context, context.INVALID_VALUE, "context.drawElements(context.TRIANGLES, 0xffffffff, context.UNSIGNED_SHORT, 0)"); +shouldGenerateGLError(context, context.INVALID_OPERATION, "context.drawElements(context.TRIANGLES, 0x7fffffff, context.UNSIGNED_SHORT, 0)"); + debug("") successfullyParsed = true; diff --git a/dom/interfaces/canvas/nsICanvasRenderingContextWebGL.idl b/dom/interfaces/canvas/nsICanvasRenderingContextWebGL.idl index 28332aa35a16..db69148e54ba 100644 --- a/dom/interfaces/canvas/nsICanvasRenderingContextWebGL.idl +++ b/dom/interfaces/canvas/nsICanvasRenderingContextWebGL.idl @@ -638,7 +638,7 @@ interface nsICanvasRenderingContextWebGL : nsISupports void drawArrays(in WebGLenum mode, in WebGLint first, in WebGLsizei count); // Modified: void glDrawElements(WebGLenum mode, WebGLsizei count, WebGLenum type, const void* indices); - void drawElements(in WebGLenum mode, in WebGLuint count, in WebGLenum type, in WebGLuint offset); + void drawElements(in WebGLenum mode, in WebGLsizei count, in WebGLenum type, in WebGLint offset); void enable(in WebGLenum cap); void enableVertexAttribArray(in WebGLuint index);