b=570538; [webgl] fix up buffer validation with interleaved data; r=bjacob

This commit is contained in:
Vladimir Vukicevic 2010-06-08 15:14:43 -07:00
parent 6897f88628
commit 39732698ea
6 changed files with 179 additions and 51 deletions

View File

@ -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();
}
};

View File

@ -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<GLushort>(count, byteOffset);
else if (type == LOCAL_GL_UNSIGNED_BYTE)
} else if (type == LOCAL_GL_UNSIGNED_BYTE) {
maxIndex = mBoundElementArrayBuffer->FindMaximum<GLubyte>(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<char> 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);

View File

@ -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;

View File

@ -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;

View File

@ -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;
</script>

View File

@ -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);