--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -537,17 +537,18 @@ public:
void PixelStorei(GLenum pname, GLint param);
void PolygonOffset(GLfloat factor, GLfloat units);
protected:
bool ReadPixels_SharedPrecheck(ErrorResult* const out_error);
void ReadPixelsImpl(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format,
GLenum type, void* data, uint32_t dataLen);
bool DoReadPixelsAndConvert(const webgl::FormatInfo* srcFormat, GLint x, GLint y,
GLsizei width, GLsizei height, GLenum format,
- GLenum destType, void* dest);
+ GLenum destType, void* dest, uint32_t dataLen,
+ uint32_t rowStride);
public:
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height,
GLenum format, GLenum type,
const dom::Nullable<dom::ArrayBufferView>& pixels,
ErrorResult& rv);
void RenderbufferStorage(GLenum target, GLenum internalFormat,
GLsizei width, GLsizei height);
protected:
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -1226,17 +1226,18 @@ IsNeedsANGLEWorkAround(const webgl::Form
default:
return false;
}
}
bool
WebGLContext::DoReadPixelsAndConvert(const webgl::FormatInfo* srcFormat, GLint x, GLint y,
GLsizei width, GLsizei height, GLenum format,
- GLenum destType, void* dest)
+ GLenum destType, void* dest, uint32_t destSize,
+ uint32_t rowStride)
{
if (gl->WorkAroundDriverBugs() &&
gl->IsANGLE() &&
gl->Version() < 300 && // ANGLE ES2 doesn't support HALF_FLOAT reads properly.
IsNeedsANGLEWorkAround(srcFormat))
{
MOZ_RELEASE_ASSERT(!IsWebGL2()); // No SKIP_PIXELS, etc.
MOZ_ASSERT(!mBoundPixelPackBuffer); // Let's be real clear.
@@ -1300,17 +1301,43 @@ WebGLContext::DoReadPixelsAndConvert(con
srcRow += readStride;
dstRow += destStride;
}
return true;
}
- gl->fReadPixels(x, y, width, height, format, destType, dest);
+ // On at least Win+NV, we'll get PBO errors if we don't handle this:
+ const auto naiveBytesNeeded = CheckedUint32(rowStride) * height;
+ const bool isDangerCloseToEdge = (!naiveBytesNeeded.isValid() ||
+ naiveBytesNeeded.value() > destSize);
+ const bool useParanoidHandling = (gl->WorkAroundDriverBugs() &&
+ isDangerCloseToEdge &&
+ mBoundPixelPackBuffer);
+ if (!useParanoidHandling) {
+ gl->fReadPixels(x, y, width, height, format, destType, dest);
+ return true;
+ }
+
+ const auto bodyHeight = height - 1;
+ if (bodyHeight) {
+ gl->fReadPixels(x, y, width, bodyHeight, format, destType, dest);
+ }
+
+ gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 1);
+ gl->fPixelStorei(LOCAL_GL_PACK_ROW_LENGTH, 0);
+ gl->fPixelStorei(LOCAL_GL_PACK_SKIP_ROWS, 0);
+
+ const auto tailRowOffset = (char*)dest + rowStride * bodyHeight;
+ gl->fReadPixels(x, y+bodyHeight, width, 1, format, destType, tailRowOffset);
+
+ gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, mPixelStore_PackAlignment);
+ gl->fPixelStorei(LOCAL_GL_PACK_ROW_LENGTH, mPixelStore_PackRowLength);
+ gl->fPixelStorei(LOCAL_GL_PACK_SKIP_ROWS, mPixelStore_PackSkipRows);
return true;
}
static bool
IsFormatAndTypeUnpackable(GLenum format, GLenum type, bool isWebGL2)
{
switch (type) {
case LOCAL_GL_UNSIGNED_BYTE:
@@ -1760,17 +1787,17 @@ WebGLContext::ReadPixelsImpl(GLint x, GL
uint32_t readX, readY;
uint32_t writeX, writeY;
uint32_t rwWidth, rwHeight;
Intersect(srcWidth, x, width, &readX, &writeX, &rwWidth);
Intersect(srcHeight, y, height, &readY, &writeY, &rwHeight);
if (rwWidth == uint32_t(width) && rwHeight == uint32_t(height)) {
DoReadPixelsAndConvert(srcFormat->format, x, y, width, height, packFormat,
- packType, dest);
+ packType, dest, dataLen, rowStride);
return;
}
// Read request contains out-of-bounds pixels. Unfortunately:
// GLES 3.0.4 p194 "Obtaining Pixels from the Framebuffer":
// "If any of these pixels lies outside of the window allocated to the current GL
// context, or outside of the image attached to the currently bound framebuffer
// object, then the values obtained for those pixels are undefined."
@@ -1792,29 +1819,29 @@ WebGLContext::ReadPixelsImpl(GLint x, GL
if (!mPixelStore_PackRowLength) {
gl->fPixelStorei(LOCAL_GL_PACK_ROW_LENGTH,
mPixelStore_PackSkipPixels + width);
}
gl->fPixelStorei(LOCAL_GL_PACK_SKIP_PIXELS, mPixelStore_PackSkipPixels + writeX);
gl->fPixelStorei(LOCAL_GL_PACK_SKIP_ROWS, mPixelStore_PackSkipRows + writeY);
DoReadPixelsAndConvert(srcFormat->format, readX, readY, rwWidth, rwHeight,
- packFormat, packType, dest);
+ packFormat, packType, dest, dataLen, rowStride);
gl->fPixelStorei(LOCAL_GL_PACK_ROW_LENGTH, mPixelStore_PackRowLength);
gl->fPixelStorei(LOCAL_GL_PACK_SKIP_PIXELS, mPixelStore_PackSkipPixels);
gl->fPixelStorei(LOCAL_GL_PACK_SKIP_ROWS, mPixelStore_PackSkipRows);
} else {
// I *did* say "hilariously slow".
uint8_t* row = (uint8_t*)dest + writeX * bytesPerPixel;
row += writeY * rowStride;
for (uint32_t j = 0; j < rwHeight; j++) {
DoReadPixelsAndConvert(srcFormat->format, readX, readY+j, rwWidth, 1,
- packFormat, packType, row);
+ packFormat, packType, row, dataLen, rowStride);
row += rowStride;
}
}
}
void
WebGLContext::RenderbufferStorage_base(const char* funcName, GLenum target,
GLsizei samples, GLenum internalFormat,