b=571029; readPixels API change, support both old and new signatures, also fix 572797 (allow non-fitting rectangles), and check that sizes are nonnegative; r=vladimir

This commit is contained in:
Benoit Jacob 2010-06-19 10:46:12 -04:00
parent 54306dde5e
commit daa03541ae
5 changed files with 234 additions and 26 deletions

View File

@ -209,6 +209,101 @@ nsICanvasRenderingContextWebGL_BufferSubData(JSContext *cx, uintN argc, jsval *v
return JS_TRUE;
}
/*
* ReadPixels takes:
* TexImage2D(int, int, int, int, uint, uint, ArrayBufferView)
*/
static JSBool
nsICanvasRenderingContextWebGL_ReadPixels(JSContext *cx, uintN argc, jsval *vp)
{
XPC_QS_ASSERT_CONTEXT_OK(cx);
JSObject *obj = JS_THIS_OBJECT(cx, vp);
if (!obj)
return JS_FALSE;
nsresult rv;
nsICanvasRenderingContextWebGL *self;
xpc_qsSelfRef selfref;
js::AutoValueRooter tvr(cx);
if (!xpc_qsUnwrapThis(cx, obj, nsnull, &self, &selfref.ptr, tvr.addr(), nsnull))
return JS_FALSE;
// XXX we currently allow passing only 6 args to support the API. Eventually drop that.
if (argc < 6)
return xpc_qsThrow(cx, NS_ERROR_XPC_NOT_ENOUGH_ARGS);
jsval *argv = JS_ARGV(cx, vp);
// arguments common to all cases
GET_INT32_ARG(argv0, 0);
GET_INT32_ARG(argv1, 1);
GET_INT32_ARG(argv2, 2);
GET_INT32_ARG(argv3, 3);
GET_UINT32_ARG(argv4, 4);
GET_UINT32_ARG(argv5, 5);
if (argc == 6) {
/*** BEGIN old API deprecated code. Eventually drop that. ***/
// the code here is ugly, but temporary. It comes from the old ReadPixels implementation.
// Remove it as soon as it's OK to drop the old API.
PRInt32 byteLength;
rv = self->ReadPixels_byteLength_old_API_deprecated(argv2, argv3, argv4, argv5, &byteLength);
if (NS_FAILED(rv)) {
xpc_qsThrow(cx, NS_ERROR_FAILURE);
return JS_FALSE;
}
JSObject *abufObject = js_CreateArrayBuffer(cx, byteLength);
if (!abufObject) {
xpc_qsThrow(cx, NS_ERROR_FAILURE);
return JS_FALSE;
}
js::ArrayBuffer *abuf = js::ArrayBuffer::fromJSObject(abufObject);
rv = self->ReadPixels_buf(
argv0, argv1, argv2, argv3, argv4, argv5, abuf);
if (NS_FAILED(rv)) {
xpc_qsThrow(cx, NS_ERROR_FAILURE);
return JS_FALSE;
}
JSObject *retval = js_CreateTypedArrayWithBuffer(cx, js::TypedArray::TYPE_UINT8,
abufObject, 0, byteLength);
*vp = OBJECT_TO_JSVAL(retval);
return JS_TRUE; // return here to be unaffected by the *vp = JSVAL_VOID; below
/*** END old API deprecated code ***/
} else if ( argc == 7
&& JSVAL_IS_OBJECT(argv[6])
&& !JSVAL_IS_PRIMITIVE(argv[6]))
{
JSObject *argv6 = JSVAL_TO_OBJECT(argv[6]);
if (js_IsArrayBuffer(argv6)) {
rv = self->ReadPixels_buf(argv0, argv1, argv2, argv3,
argv4, argv5, js::ArrayBuffer::fromJSObject(argv6));
} else if (js_IsTypedArray(argv6)) {
rv = self->ReadPixels_array(argv0, argv1, argv2, argv3,
argv4, argv5,
js::TypedArray::fromJSObject(argv6));
} else {
xpc_qsThrowBadArg(cx, NS_ERROR_FAILURE, vp, 6);
return JS_FALSE;
}
} else {
xpc_qsThrow(cx, NS_ERROR_FAILURE);
return JS_FALSE;
}
if (NS_FAILED(rv))
return xpc_qsThrowMethodFailed(cx, rv, vp);
*vp = JSVAL_VOID;
return JS_TRUE;
}
/*
* TexImage2D takes:
* TexImage2D(uint, int, uint, int, int, int, uint, uint, ArrayBufferView)\

View File

@ -332,6 +332,8 @@ protected:
WebGLsizei width, WebGLsizei height,
WebGLenum format, WebGLenum type,
void *pixels, PRUint32 byteLength);
nsresult ReadPixels_base(WebGLint x, WebGLint y, WebGLsizei width, WebGLsizei height,
WebGLenum format, WebGLenum type, void *data, PRUint32 byteLength);
nsresult DOMElementToImageSurface(nsIDOMElement *imageOrCanvas,
gfxImageSurface **imageOut,

View File

@ -2088,21 +2088,25 @@ WebGLContext::PixelStorei(WebGLenum pname, WebGLint param)
GL_SAME_METHOD_2(PolygonOffset, PolygonOffset, float, float)
NS_IMETHODIMP
WebGLContext::ReadPixels(WebGLint x, WebGLint y, WebGLsizei width, WebGLsizei height, WebGLenum format, WebGLenum type)
WebGLContext::ReadPixels(PRInt32 dummy)
{
NativeJSContext js;
if (NS_FAILED(js.error))
return js.error;
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
WebGLContext::ReadPixels_base(WebGLint x, WebGLint y, WebGLsizei width, WebGLsizei height,
WebGLenum format, WebGLenum type, void *data, PRUint32 byteLength)
{
if (HTMLCanvasElement()->IsWriteOnly() && !nsContentUtils::IsCallerTrustedForRead()) {
LogMessage("ReadPixels: Not allowed");
return NS_ERROR_DOM_SECURITY_ERR;
}
if (width < 0 || height < 0)
return ErrorInvalidValue("ReadPixels: negative size passed");
WebGLsizei boundWidth = mBoundFramebuffer ? mBoundFramebuffer->width() : mWidth;
WebGLsizei boundHeight = mBoundFramebuffer ? mBoundFramebuffer->height() : mHeight;
if (!CanvasUtils::CheckSaneSubrectSize(x, y, width, height, boundWidth, boundHeight))
return ErrorInvalidOperation("ReadPixels: invalid dimensions (outside of framebuffer)");
PRUint32 size = 0;
switch (format) {
@ -2141,18 +2145,129 @@ WebGLContext::ReadPixels(WebGLint x, WebGLint y, WebGLsizei width, WebGLsizei he
PRUint32 alignedRowSize = (plainRowSize + packAlignment-1) &
~PRUint32(packAlignment-1);
PRUint32 len = (height-1)*alignedRowSize + plainRowSize;
PRUint32 neededByteLength = (height-1)*alignedRowSize + plainRowSize;
JSObject *abufObject = js_CreateArrayBuffer(js.ctx, len);
if (!abufObject)
return SynthesizeGLError(LOCAL_GL_OUT_OF_MEMORY, "readPixels: could not allocate buffer");
if(neededByteLength > byteLength)
return ErrorInvalidOperation("ReadPixels: buffer too small");
js::ArrayBuffer *abuf = js::ArrayBuffer::fromJSObject(abufObject);
if (CanvasUtils::CheckSaneSubrectSize(x, y, width, height, boundWidth, boundHeight)) {
// the easy case: we're not reading out-of-range pixels
gl->fReadPixels(x, y, width, height, format, type, data);
} else {
// the rectangle doesn't fit entirely in the bound buffer. We then have to set to zero the part
// of the buffer that correspond to out-of-range pixels. We don't want to rely on system OpenGL
// to do that for us, because passing out of range parameters to a buggy OpenGL implementation
// could conceivably allow to read memory we shouldn't be allowed to read. So we manually initialize
// the buffer to zero and compute the parameters to pass to OpenGL. We have to use an intermediate buffer
// to accomodate the potentially different strides (widths).
gl->fReadPixels((GLint) x, (GLint) y, width, height, format, type, (GLvoid *) abuf->data);
// zero the whole destination buffer. Too bad for the part that's going to be overwritten, we're not
// 100% efficient here, but in practice this is a quite rare case anyway.
memset(data, 0, byteLength);
if ( x >= boundWidth
|| x+width <= 0
|| y >= boundHeight
|| y+height <= 0)
{
// we are completely outside of range, can exit now with buffer filled with zeros
return NS_OK;
}
// compute the parameters of the subrect we're actually going to call glReadPixels on
GLint subrect_x = PR_MAX(x, 0);
GLint subrect_end_x = PR_MIN(x+width, boundWidth);
GLsizei subrect_width = subrect_end_x - subrect_x;
GLint subrect_y = PR_MAX(y, 0);
GLint subrect_end_y = PR_MIN(y+height, boundHeight);
GLsizei subrect_height = subrect_end_y - subrect_y;
// now, same computation as above to find the size of the intermediate buffer to allocate for the subrect
PRUint32 subrect_plainRowSize = subrect_width * size;
PRUint32 subrect_alignedRowSize = (subrect_plainRowSize + packAlignment-1) &
~PRUint32(packAlignment-1);
PRUint32 subrect_byteLength = (subrect_height-1)*subrect_alignedRowSize + subrect_plainRowSize;
// create subrect buffer, call glReadPixels, copy pixels into destination buffer, delete subrect buffer
GLubyte *subrect_data = new GLubyte[subrect_byteLength];
gl->fReadPixels(subrect_x, subrect_y, subrect_width, subrect_height, format, type, subrect_data);
for (GLint y_inside_subrect = 0; y_inside_subrect < subrect_height; ++y_inside_subrect) {
GLint subrect_x_in_dest_buffer = subrect_x - x;
GLint subrect_y_in_dest_buffer = subrect_y - y;
memcpy(static_cast<GLubyte*>(data)
+ alignedRowSize * (subrect_y_in_dest_buffer + y_inside_subrect)
+ size * subrect_x_in_dest_buffer, // destination
subrect_data + subrect_alignedRowSize * y_inside_subrect, // source
subrect_plainRowSize); // size
}
delete [] subrect_data;
}
return NS_OK;
}
NS_IMETHODIMP
WebGLContext::ReadPixels_array(WebGLint x, WebGLint y, WebGLsizei width, WebGLsizei height,
WebGLenum format, WebGLenum type, js::TypedArray *pixels)
{
return ReadPixels_base(x, y, width, height, format, type,
pixels ? pixels->data : 0,
pixels ? pixels->byteLength : 0);
}
NS_IMETHODIMP
WebGLContext::ReadPixels_buf(WebGLint x, WebGLint y, WebGLsizei width, WebGLsizei height,
WebGLenum format, WebGLenum type, js::ArrayBuffer *pixels)
{
return ReadPixels_base(x, y, width, height, format, type,
pixels ? pixels->data : 0,
pixels ? pixels->byteLength : 0);
}
NS_IMETHODIMP
WebGLContext::ReadPixels_byteLength_old_API_deprecated(WebGLsizei width, WebGLsizei height,
WebGLenum format, WebGLenum type, WebGLsizei *retval)
{
*retval = 0;
if (width < 0 || height < 0)
return ErrorInvalidValue("ReadPixels: negative size passed");
PRUint32 size = 0;
switch (format) {
case LOCAL_GL_ALPHA:
size = 1;
break;
case LOCAL_GL_RGB:
size = 3;
break;
case LOCAL_GL_RGBA:
size = 4;
break;
default:
return ErrorInvalidEnum("ReadPixels: unsupported pixel format");
}
switch (type) {
// case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
// case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
// case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
case LOCAL_GL_UNSIGNED_BYTE:
break;
default:
return ErrorInvalidEnum("ReadPixels: unsupported pixel type");
}
PRUint32 packAlignment;
gl->fGetIntegerv(LOCAL_GL_PACK_ALIGNMENT, (GLint*) &packAlignment);
PRUint32 plainRowSize = width*size;
// alignedRowSize = row size rounded up to next multiple of
// packAlignment which is a power of 2
PRUint32 alignedRowSize = (plainRowSize + packAlignment-1) &
~PRUint32(packAlignment-1);
*retval = (height-1)*alignedRowSize + plainRowSize;
JSObject *retval = js_CreateTypedArrayWithBuffer(js.ctx, js::TypedArray::TYPE_UINT8, abufObject, 0, len);
js.SetRetVal(retval);
return NS_OK;
}
@ -2874,14 +2989,6 @@ NS_IMETHODIMP
WebGLContext::TexImage2D_dom_old_API_deprecated(WebGLenum target, WebGLint level, nsIDOMElement *elt,
PRBool flipY, PRBool premultiplyAlpha)
{
static PRBool firsttime = PR_TRUE;
if (firsttime) {
LogMessage("The WebGL spec changed, TexImage2D is now taking at least 6 parameters, please "
"adapt your JavaScript code as support for the old API will soon be dropped!");
firsttime = PR_FALSE;
}
nsRefPtr<gfxImageSurface> isurf;
nsresult rv = DOMElementToImageSurface(elt, getter_AddRefs(isurf),

View File

@ -721,9 +721,13 @@ interface nsICanvasRenderingContextWebGL : nsISupports
void pixelStorei(in WebGLenum pname, in WebGLint param);
void polygonOffset(in WebGLfloat factor, in WebGLfloat units);
// TBD
//ZZ void glReadPixels(WebGLint x, WebGLint y, WebGLsizei width, WebGLsizei height, WebGLenum format, WebGLenum type, void* pixels);
void readPixels(in WebGLint x, in WebGLint y, in WebGLsizei width, in WebGLsizei height, in WebGLenum format, in WebGLenum type);
void readPixels([optional] in long dummy);
[noscript] void readPixels_array(in WebGLint x, in WebGLint y, in WebGLsizei width, in WebGLsizei height,
in WebGLenum format, in WebGLenum type, in WebGLArrayPtr pixels);
[noscript] void readPixels_buf(in WebGLint x, in WebGLint y, in WebGLsizei width, in WebGLsizei height,
in WebGLenum format, in WebGLenum type, in WebGLArrayBufferPtr pixels);
[noscript] WebGLsizei readPixels_byteLength_old_API_deprecated(
in WebGLsizei width, in WebGLsizei height, in WebGLenum format, in WebGLenum type);
//void glReleaseShaderCompiler();

View File

@ -457,7 +457,6 @@ members = [
'-nsICanvasRenderingContextWebGL.texParameteri',
'-nsICanvasRenderingContextWebGL.getUniform',
'-nsICanvasRenderingContextWebGL.getVertexAttrib',
'-nsICanvasRenderingContextWebGL.readPixels',
'-nsICanvasRenderingContextWebGL.getShaderParameter',
]
@ -813,6 +812,7 @@ customMethodCalls = {
# WebGL
'nsICanvasRenderingContextWebGL_BufferData': CUSTOM_QS,
'nsICanvasRenderingContextWebGL_BufferSubData': CUSTOM_QS,
'nsICanvasRenderingContextWebGL_ReadPixels': CUSTOM_QS,
'nsICanvasRenderingContextWebGL_TexImage2D': CUSTOM_QS,
'nsICanvasRenderingContextWebGL_TexSubImage2D': CUSTOM_QS,
'nsICanvasRenderingContextWebGL_Uniform1iv': CUSTOM_QS_TN,