1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "TexUnpackBlob.h"
7 
8 #include "GLBlitHelper.h"
9 #include "GLContext.h"
10 #include "mozilla/dom/Element.h"
11 #include "mozilla/dom/HTMLCanvasElement.h"
12 #include "mozilla/RefPtr.h"
13 #include "nsLayoutUtils.h"
14 #include "WebGLBuffer.h"
15 #include "WebGLContext.h"
16 #include "WebGLTexelConversions.h"
17 #include "WebGLTexture.h"
18 
19 namespace mozilla {
20 namespace webgl {
21 
22 static bool
IsPIValidForDOM(const webgl::PackingInfo & pi)23 IsPIValidForDOM(const webgl::PackingInfo& pi)
24 {
25     // https://www.khronos.org/registry/webgl/specs/latest/2.0/#TEXTURE_TYPES_FORMATS_FROM_DOM_ELEMENTS_TABLE
26 
27     // Just check for invalid individual formats and types, not combinations.
28     switch (pi.format) {
29     case LOCAL_GL_RGB:
30     case LOCAL_GL_RGBA:
31     case LOCAL_GL_LUMINANCE_ALPHA:
32     case LOCAL_GL_LUMINANCE:
33     case LOCAL_GL_ALPHA:
34     case LOCAL_GL_RED:
35     case LOCAL_GL_RED_INTEGER:
36     case LOCAL_GL_RG:
37     case LOCAL_GL_RG_INTEGER:
38     case LOCAL_GL_RGB_INTEGER:
39     case LOCAL_GL_RGBA_INTEGER:
40         break;
41 
42     case LOCAL_GL_SRGB:
43     case LOCAL_GL_SRGB_ALPHA:
44         // Allowed in WebGL1+EXT_srgb
45         break;
46 
47     default:
48         return false;
49     }
50 
51     switch (pi.type) {
52     case LOCAL_GL_UNSIGNED_BYTE:
53     case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
54     case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
55     case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
56     case LOCAL_GL_HALF_FLOAT:
57     case LOCAL_GL_HALF_FLOAT_OES:
58     case LOCAL_GL_FLOAT:
59     case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV:
60         break;
61 
62     default:
63         return false;
64     }
65 
66     return true;
67 }
68 
69 static bool
ValidatePIForDOM(WebGLContext * webgl,const char * funcName,const webgl::PackingInfo & pi)70 ValidatePIForDOM(WebGLContext* webgl, const char* funcName,
71                  const webgl::PackingInfo& pi)
72 {
73     if (!IsPIValidForDOM(pi)) {
74         webgl->ErrorInvalidOperation("%s: Format or type is invalid for DOM sources.",
75                                      funcName);
76         return false;
77     }
78     return true;
79 }
80 
81 static WebGLTexelFormat
FormatForPackingInfo(const PackingInfo & pi)82 FormatForPackingInfo(const PackingInfo& pi)
83 {
84     switch (pi.type) {
85     case LOCAL_GL_UNSIGNED_BYTE:
86         switch (pi.format) {
87         case LOCAL_GL_RED:
88         case LOCAL_GL_LUMINANCE:
89         case LOCAL_GL_RED_INTEGER:
90             return WebGLTexelFormat::R8;
91 
92         case LOCAL_GL_ALPHA:
93             return WebGLTexelFormat::A8;
94 
95         case LOCAL_GL_LUMINANCE_ALPHA:
96             return WebGLTexelFormat::RA8;
97 
98         case LOCAL_GL_RGB:
99         case LOCAL_GL_RGB_INTEGER:
100             return WebGLTexelFormat::RGB8;
101 
102         case LOCAL_GL_RGBA:
103         case LOCAL_GL_RGBA_INTEGER:
104             return WebGLTexelFormat::RGBA8;
105 
106         case LOCAL_GL_RG:
107         case LOCAL_GL_RG_INTEGER:
108             return WebGLTexelFormat::RG8;
109 
110         default:
111             break;
112         }
113         break;
114 
115     case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
116         if (pi.format == LOCAL_GL_RGB)
117             return WebGLTexelFormat::RGB565;
118         break;
119 
120     case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
121         if (pi.format == LOCAL_GL_RGBA)
122             return WebGLTexelFormat::RGBA5551;
123         break;
124 
125     case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
126         if (pi.format == LOCAL_GL_RGBA)
127             return WebGLTexelFormat::RGBA4444;
128         break;
129 
130     case LOCAL_GL_HALF_FLOAT:
131     case LOCAL_GL_HALF_FLOAT_OES:
132         switch (pi.format) {
133         case LOCAL_GL_RED:
134         case LOCAL_GL_LUMINANCE:
135             return WebGLTexelFormat::R16F;
136 
137         case LOCAL_GL_ALPHA:           return WebGLTexelFormat::A16F;
138         case LOCAL_GL_LUMINANCE_ALPHA: return WebGLTexelFormat::RA16F;
139         case LOCAL_GL_RG:              return WebGLTexelFormat::RG16F;
140         case LOCAL_GL_RGB:             return WebGLTexelFormat::RGB16F;
141         case LOCAL_GL_RGBA:            return WebGLTexelFormat::RGBA16F;
142 
143         default:
144             break;
145         }
146         break;
147 
148     case LOCAL_GL_FLOAT:
149         switch (pi.format) {
150         case LOCAL_GL_RED:
151         case LOCAL_GL_LUMINANCE:
152             return WebGLTexelFormat::R32F;
153 
154         case LOCAL_GL_ALPHA:           return WebGLTexelFormat::A32F;
155         case LOCAL_GL_LUMINANCE_ALPHA: return WebGLTexelFormat::RA32F;
156         case LOCAL_GL_RG:              return WebGLTexelFormat::RG32F;
157         case LOCAL_GL_RGB:             return WebGLTexelFormat::RGB32F;
158         case LOCAL_GL_RGBA:            return WebGLTexelFormat::RGBA32F;
159 
160         default:
161             break;
162         }
163         break;
164 
165     case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV:
166         if (pi.format == LOCAL_GL_RGB)
167             return WebGLTexelFormat::RGB11F11F10F;
168         break;
169 
170     default:
171         break;
172     }
173 
174     return WebGLTexelFormat::FormatNotSupportingAnyConversion;
175 }
176 
177 ////////////////////
178 
179 static bool
ValidateUnpackPixels(WebGLContext * webgl,const char * funcName,uint32_t fullRows,uint32_t tailPixels,webgl::TexUnpackBlob * blob)180 ValidateUnpackPixels(WebGLContext* webgl, const char* funcName, uint32_t fullRows,
181                      uint32_t tailPixels, webgl::TexUnpackBlob* blob)
182 {
183     if (!blob->mWidth || !blob->mHeight || !blob->mDepth)
184         return true;
185 
186     const auto usedPixelsPerRow = CheckedUint32(blob->mSkipPixels) + blob->mWidth;
187     if (!usedPixelsPerRow.isValid() || usedPixelsPerRow.value() > blob->mRowLength) {
188         webgl->ErrorInvalidOperation("%s: UNPACK_SKIP_PIXELS + width >"
189                                      " UNPACK_ROW_LENGTH.",
190                                      funcName);
191         return false;
192     }
193 
194     if (blob->mHeight > blob->mImageHeight) {
195         webgl->ErrorInvalidOperation("%s: height > UNPACK_IMAGE_HEIGHT.", funcName);
196         return false;
197     }
198 
199     //////
200 
201     // The spec doesn't bound SKIP_ROWS + height <= IMAGE_HEIGHT, unfortunately.
202     auto skipFullRows = CheckedUint32(blob->mSkipImages) * blob->mImageHeight;
203     skipFullRows += blob->mSkipRows;
204 
205     MOZ_ASSERT(blob->mDepth >= 1);
206     MOZ_ASSERT(blob->mHeight >= 1);
207     auto usedFullRows = CheckedUint32(blob->mDepth - 1) * blob->mImageHeight;
208     usedFullRows += blob->mHeight - 1; // Full rows in the final image, excluding the tail.
209 
210     const auto fullRowsNeeded = skipFullRows + usedFullRows;
211     if (!fullRowsNeeded.isValid()) {
212         webgl->ErrorOutOfMemory("%s: Invalid calculation for required row count.",
213                                 funcName);
214         return false;
215     }
216 
217     if (fullRows > fullRowsNeeded.value())
218         return true;
219 
220     if (fullRows == fullRowsNeeded.value() && tailPixels >= usedPixelsPerRow.value()) {
221         blob->mNeedsExactUpload = true;
222         return true;
223     }
224 
225     webgl->ErrorInvalidOperation("%s: Desired upload requires more data than is"
226                                  " available: (%u rows plus %u pixels needed, %u rows"
227                                  " plus %u pixels available)",
228                                  funcName, fullRowsNeeded.value(),
229                                  usedPixelsPerRow.value(), fullRows, tailPixels);
230     return false;
231 }
232 
233 static bool
ValidateUnpackBytes(WebGLContext * webgl,const char * funcName,const webgl::PackingInfo & pi,size_t availByteCount,webgl::TexUnpackBlob * blob)234 ValidateUnpackBytes(WebGLContext* webgl, const char* funcName,
235                     const webgl::PackingInfo& pi, size_t availByteCount,
236                     webgl::TexUnpackBlob* blob)
237 {
238     if (!blob->mWidth || !blob->mHeight || !blob->mDepth)
239         return true;
240 
241     const auto bytesPerPixel = webgl::BytesPerPixel(pi);
242     const auto bytesPerRow = CheckedUint32(blob->mRowLength) * bytesPerPixel;
243     const auto rowStride = RoundUpToMultipleOf(bytesPerRow, blob->mAlignment);
244 
245     const auto fullRows = availByteCount / rowStride;
246     if (!fullRows.isValid()) {
247         webgl->ErrorOutOfMemory("%s: Unacceptable upload size calculated.");
248         return false;
249     }
250 
251     const auto bodyBytes = fullRows.value() * rowStride.value();
252     const auto tailPixels = (availByteCount - bodyBytes) / bytesPerPixel;
253 
254     return ValidateUnpackPixels(webgl, funcName, fullRows.value(), tailPixels, blob);
255 }
256 
257 ////////////////////
258 
259 static uint32_t
ZeroOn2D(TexImageTarget target,uint32_t val)260 ZeroOn2D(TexImageTarget target, uint32_t val)
261 {
262     return (IsTarget3D(target) ? val : 0);
263 }
264 
265 static uint32_t
FallbackOnZero(uint32_t val,uint32_t fallback)266 FallbackOnZero(uint32_t val, uint32_t fallback)
267 {
268     return (val ? val : fallback);
269 }
270 
TexUnpackBlob(const WebGLContext * webgl,TexImageTarget target,uint32_t rowLength,uint32_t width,uint32_t height,uint32_t depth,bool srcIsPremult)271 TexUnpackBlob::TexUnpackBlob(const WebGLContext* webgl, TexImageTarget target,
272                              uint32_t rowLength, uint32_t width, uint32_t height,
273                              uint32_t depth, bool srcIsPremult)
274     : mAlignment(webgl->mPixelStore_UnpackAlignment)
275     , mRowLength(rowLength)
276     , mImageHeight(FallbackOnZero(ZeroOn2D(target, webgl->mPixelStore_UnpackImageHeight),
277                                   height))
278 
279     , mSkipPixels(webgl->mPixelStore_UnpackSkipPixels)
280     , mSkipRows(webgl->mPixelStore_UnpackSkipRows)
281     , mSkipImages(ZeroOn2D(target, webgl->mPixelStore_UnpackSkipImages))
282 
283     , mWidth(width)
284     , mHeight(height)
285     , mDepth(depth)
286 
287     , mSrcIsPremult(srcIsPremult)
288 
289     , mNeedsExactUpload(false)
290 {
291     MOZ_ASSERT_IF(!IsTarget3D(target), mDepth == 1);
292 }
293 
294 bool
ConvertIfNeeded(WebGLContext * webgl,const char * funcName,const uint32_t rowLength,const uint32_t rowCount,WebGLTexelFormat srcFormat,const uint8_t * const srcBegin,const ptrdiff_t srcStride,WebGLTexelFormat dstFormat,const ptrdiff_t dstStride,const uint8_t ** const out_begin,UniqueBuffer * const out_anchoredBuffer) const295 TexUnpackBlob::ConvertIfNeeded(WebGLContext* webgl, const char* funcName,
296                                const uint32_t rowLength, const uint32_t rowCount,
297                                WebGLTexelFormat srcFormat,
298                                const uint8_t* const srcBegin, const ptrdiff_t srcStride,
299                                WebGLTexelFormat dstFormat, const ptrdiff_t dstStride,
300                                const uint8_t** const out_begin,
301                                UniqueBuffer* const out_anchoredBuffer) const
302 {
303     MOZ_ASSERT(srcFormat != WebGLTexelFormat::FormatNotSupportingAnyConversion);
304     MOZ_ASSERT(dstFormat != WebGLTexelFormat::FormatNotSupportingAnyConversion);
305 
306     *out_begin = srcBegin;
307 
308     if (!rowLength || !rowCount)
309         return true;
310 
311     const auto& dstIsPremult = webgl->mPixelStore_PremultiplyAlpha;
312     const auto srcOrigin = (webgl->mPixelStore_FlipY ? gl::OriginPos::TopLeft
313                                                      : gl::OriginPos::BottomLeft);
314     const auto dstOrigin = gl::OriginPos::BottomLeft;
315 
316     if (srcFormat != dstFormat) {
317         webgl->GenerateWarning("%s: Conversion requires pixel reformatting.", funcName);
318     } else if (mSrcIsPremult != dstIsPremult) {
319         webgl->GenerateWarning("%s: Conversion requires change in"
320                                "alpha-premultiplication.",
321                                funcName);
322     } else if (srcOrigin != dstOrigin) {
323         webgl->GenerateWarning("%s: Conversion requires y-flip.", funcName);
324     } else if (srcStride != dstStride) {
325         webgl->GenerateWarning("%s: Conversion requires change in stride.", funcName);
326     } else {
327         return true;
328     }
329 
330     ////
331 
332     const auto dstTotalBytes = CheckedUint32(rowCount) * dstStride;
333     if (!dstTotalBytes.isValid()) {
334         webgl->ErrorOutOfMemory("%s: Calculation failed.", funcName);
335         return false;
336     }
337 
338     UniqueBuffer dstBuffer = calloc(1, dstTotalBytes.value());
339     if (!dstBuffer.get()) {
340         webgl->ErrorOutOfMemory("%s: Failed to allocate dest buffer.", funcName);
341         return false;
342     }
343     const auto dstBegin = static_cast<uint8_t*>(dstBuffer.get());
344 
345     ////
346 
347     // And go!:
348     bool wasTrivial;
349     if (!ConvertImage(rowLength, rowCount,
350                       srcBegin, srcStride, srcOrigin, srcFormat, mSrcIsPremult,
351                       dstBegin, dstStride, dstOrigin, dstFormat, dstIsPremult,
352                       &wasTrivial))
353     {
354         webgl->ErrorImplementationBug("%s: ConvertImage failed.", funcName);
355         return false;
356     }
357 
358     *out_begin = dstBegin;
359     *out_anchoredBuffer = Move(dstBuffer);
360     return true;
361 }
362 
363 static GLenum
DoTexOrSubImage(bool isSubImage,gl::GLContext * gl,TexImageTarget target,GLint level,const DriverUnpackInfo * dui,GLint xOffset,GLint yOffset,GLint zOffset,GLsizei width,GLsizei height,GLsizei depth,const void * data)364 DoTexOrSubImage(bool isSubImage, gl::GLContext* gl, TexImageTarget target, GLint level,
365                 const DriverUnpackInfo* dui, GLint xOffset, GLint yOffset, GLint zOffset,
366                 GLsizei width, GLsizei height, GLsizei depth, const void* data)
367 {
368     if (isSubImage) {
369         return DoTexSubImage(gl, target, level, xOffset, yOffset, zOffset, width, height,
370                              depth, dui->ToPacking(), data);
371     } else {
372         return DoTexImage(gl, target, level, dui, width, height, depth, data);
373     }
374 }
375 
376 //////////////////////////////////////////////////////////////////////////////////////////
377 // TexUnpackBytes
378 
TexUnpackBytes(const WebGLContext * webgl,TexImageTarget target,uint32_t width,uint32_t height,uint32_t depth,bool isClientData,const uint8_t * ptr,size_t availBytes)379 TexUnpackBytes::TexUnpackBytes(const WebGLContext* webgl, TexImageTarget target,
380                                uint32_t width, uint32_t height, uint32_t depth,
381                                bool isClientData, const uint8_t* ptr, size_t availBytes)
382     : TexUnpackBlob(webgl, target,
383                     FallbackOnZero(webgl->mPixelStore_UnpackRowLength, width),
384                     width, height, depth, false)
385     , mIsClientData(isClientData)
386     , mPtr(ptr)
387     , mAvailBytes(availBytes)
388 { }
389 
390 bool
Validate(WebGLContext * webgl,const char * funcName,const webgl::PackingInfo & pi)391 TexUnpackBytes::Validate(WebGLContext* webgl, const char* funcName,
392                          const webgl::PackingInfo& pi)
393 {
394     if (mIsClientData && !mPtr)
395         return true;
396 
397     return ValidateUnpackBytes(webgl, funcName, pi, mAvailBytes, this);
398 }
399 
400 bool
TexOrSubImage(bool isSubImage,bool needsRespec,const char * funcName,WebGLTexture * tex,TexImageTarget target,GLint level,const webgl::DriverUnpackInfo * dui,GLint xOffset,GLint yOffset,GLint zOffset,GLenum * const out_error) const401 TexUnpackBytes::TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
402                               WebGLTexture* tex, TexImageTarget target, GLint level,
403                               const webgl::DriverUnpackInfo* dui, GLint xOffset,
404                               GLint yOffset, GLint zOffset, GLenum* const out_error) const
405 {
406     WebGLContext* webgl = tex->mContext;
407 
408     const auto pi = dui->ToPacking();
409     const auto format = FormatForPackingInfo(pi);
410     const auto bytesPerPixel = webgl::BytesPerPixel(pi);
411 
412     const uint8_t* uploadPtr = mPtr;
413     UniqueBuffer tempBuffer;
414 
415     do {
416         if (!mIsClientData || !mPtr)
417             break;
418 
419         if (!webgl->mPixelStore_FlipY &&
420             !webgl->mPixelStore_PremultiplyAlpha)
421         {
422             break;
423         }
424 
425         if (webgl->mPixelStore_UnpackImageHeight ||
426             webgl->mPixelStore_UnpackSkipImages ||
427             webgl->mPixelStore_UnpackRowLength ||
428             webgl->mPixelStore_UnpackSkipRows ||
429             webgl->mPixelStore_UnpackSkipPixels)
430         {
431             webgl->ErrorInvalidOperation("%s: Non-DOM-Element uploads with alpha-premult"
432                                          " or y-flip do not support subrect selection.",
433                                          funcName);
434             return false;
435         }
436 
437         webgl->GenerateWarning("%s: Alpha-premult and y-flip are deprecated for"
438                                " non-DOM-Element uploads.",
439                                funcName);
440 
441         const uint32_t rowLength = mWidth;
442         const uint32_t rowCount = mHeight * mDepth;
443         const auto stride = RoundUpToMultipleOf(rowLength * bytesPerPixel, mAlignment);
444         if (!ConvertIfNeeded(webgl, funcName, rowLength, rowCount, format, mPtr, stride,
445                              format, stride, &uploadPtr, &tempBuffer))
446         {
447             return false;
448         }
449     } while (false);
450 
451     //////
452 
453     const auto& gl = webgl->gl;
454 
455     bool useParanoidHandling = false;
456     if (mNeedsExactUpload && webgl->mBoundPixelUnpackBuffer) {
457         webgl->GenerateWarning("%s: Uploads from a buffer with a final row with a byte"
458                                " count smaller than the row stride can incur extra"
459                                " overhead.",
460                                funcName);
461 
462         if (gl->WorkAroundDriverBugs()) {
463             useParanoidHandling |= (gl->Vendor() == gl::GLVendor::NVIDIA);
464         }
465     }
466 
467     if (!useParanoidHandling) {
468         if (webgl->mBoundPixelUnpackBuffer) {
469             gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER,
470                             webgl->mBoundPixelUnpackBuffer->mGLName);
471         }
472 
473         *out_error = DoTexOrSubImage(isSubImage, gl, target, level, dui, xOffset, yOffset,
474                                      zOffset, mWidth, mHeight, mDepth, uploadPtr);
475 
476         if (webgl->mBoundPixelUnpackBuffer) {
477             gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, 0);
478         }
479         return true;
480     }
481 
482     //////
483 
484     MOZ_ASSERT(webgl->mBoundPixelUnpackBuffer);
485 
486     if (!isSubImage) {
487         // Alloc first to catch OOMs.
488         AssertUintParamCorrect(gl, LOCAL_GL_PIXEL_UNPACK_BUFFER, 0);
489         *out_error = DoTexOrSubImage(false, gl, target, level, dui, xOffset, yOffset,
490                                      zOffset, mWidth, mHeight, mDepth, nullptr);
491         if (*out_error)
492             return true;
493     }
494 
495     const ScopedLazyBind bindPBO(gl, LOCAL_GL_PIXEL_UNPACK_BUFFER,
496                                  webgl->mBoundPixelUnpackBuffer);
497 
498     //////
499 
500     // Make our sometimes-implicit values explicit. Also this keeps them constant when we
501     // ask for height=mHeight-1 and such.
502     gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, mRowLength);
503     gl->fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT, mImageHeight);
504 
505     if (mDepth > 1) {
506         *out_error = DoTexOrSubImage(true, gl, target, level, dui, xOffset, yOffset,
507                                      zOffset, mWidth, mHeight, mDepth-1, uploadPtr);
508     }
509 
510     // Skip the images we uploaded.
511     gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, mSkipImages + mDepth - 1);
512 
513     if (mHeight > 1) {
514         *out_error = DoTexOrSubImage(true, gl, target, level, dui, xOffset, yOffset,
515                                      zOffset+mDepth-1, mWidth, mHeight-1, 1, uploadPtr);
516     }
517 
518     const auto totalSkipRows = CheckedUint32(mSkipImages) * mImageHeight + mSkipRows;
519     const auto totalFullRows = CheckedUint32(mDepth - 1) * mImageHeight + mHeight - 1;
520     const auto tailOffsetRows = totalSkipRows + totalFullRows;
521 
522     const auto bytesPerRow = CheckedUint32(mRowLength) * bytesPerPixel;
523     const auto rowStride = RoundUpToMultipleOf(bytesPerRow, mAlignment);
524     if (!rowStride.isValid()) {
525         MOZ_CRASH("Should be checked earlier.");
526     }
527     const auto tailOffsetBytes = tailOffsetRows * rowStride;
528 
529     uploadPtr += tailOffsetBytes.value();
530 
531     //////
532 
533     gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1);   // No stride padding.
534     gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, 0);  // No padding in general.
535     gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, 0); // Don't skip images,
536     gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS, 0);   // or rows.
537                                                       // Keep skipping pixels though!
538 
539     *out_error = DoTexOrSubImage(true, gl, target, level, dui, xOffset,
540                                  yOffset+mHeight-1, zOffset+mDepth-1, mWidth, 1, 1,
541                                  uploadPtr);
542 
543     // Reset all our modified state.
544     gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, webgl->mPixelStore_UnpackAlignment);
545     gl->fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT, webgl->mPixelStore_UnpackImageHeight);
546     gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, webgl->mPixelStore_UnpackRowLength);
547     gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, webgl->mPixelStore_UnpackSkipImages);
548     gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS, webgl->mPixelStore_UnpackSkipRows);
549 
550     return true;
551 }
552 
553 ////////////////////////////////////////////////////////////////////////////////
554 ////////////////////////////////////////////////////////////////////////////////
555 // TexUnpackImage
556 
TexUnpackImage(const WebGLContext * webgl,TexImageTarget target,uint32_t width,uint32_t height,uint32_t depth,layers::Image * image,bool isAlphaPremult)557 TexUnpackImage::TexUnpackImage(const WebGLContext* webgl, TexImageTarget target,
558                                uint32_t width, uint32_t height, uint32_t depth,
559                                layers::Image* image, bool isAlphaPremult)
560     : TexUnpackBlob(webgl, target, image->GetSize().width, width, height, depth,
561                     isAlphaPremult)
562     , mImage(image)
563 { }
564 
~TexUnpackImage()565 TexUnpackImage::~TexUnpackImage()
566 { }
567 
568 bool
Validate(WebGLContext * webgl,const char * funcName,const webgl::PackingInfo & pi)569 TexUnpackImage::Validate(WebGLContext* webgl, const char* funcName,
570                          const webgl::PackingInfo& pi)
571 {
572     if (!ValidatePIForDOM(webgl, funcName, pi))
573         return false;
574 
575     const auto fullRows = mImage->GetSize().height;
576     return ValidateUnpackPixels(webgl, funcName, fullRows, 0, this);
577 }
578 
579 bool
TexOrSubImage(bool isSubImage,bool needsRespec,const char * funcName,WebGLTexture * tex,TexImageTarget target,GLint level,const webgl::DriverUnpackInfo * dui,GLint xOffset,GLint yOffset,GLint zOffset,GLenum * const out_error) const580 TexUnpackImage::TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
581                               WebGLTexture* tex, TexImageTarget target, GLint level,
582                               const webgl::DriverUnpackInfo* dui, GLint xOffset,
583                               GLint yOffset, GLint zOffset, GLenum* const out_error) const
584 {
585     MOZ_ASSERT_IF(needsRespec, !isSubImage);
586 
587     WebGLContext* webgl = tex->mContext;
588 
589     gl::GLContext* gl = webgl->GL();
590     gl->MakeCurrent();
591 
592     if (needsRespec) {
593         *out_error = DoTexOrSubImage(isSubImage, gl, target.get(), level, dui, xOffset,
594                                      yOffset, zOffset, mWidth, mHeight, mDepth,
595                                      nullptr);
596         if (*out_error)
597             return true;
598     }
599 
600     do {
601         if (mDepth != 1)
602             break;
603 
604         const auto& dstIsPremult = webgl->mPixelStore_PremultiplyAlpha;
605         if (mSrcIsPremult != dstIsPremult)
606             break;
607 
608         if (dui->unpackFormat != LOCAL_GL_RGB && dui->unpackFormat != LOCAL_GL_RGBA)
609             break;
610 
611         if (dui->unpackType != LOCAL_GL_UNSIGNED_BYTE)
612             break;
613 
614         gl::ScopedFramebuffer scopedFB(gl);
615         gl::ScopedBindFramebuffer bindFB(gl, scopedFB.FB());
616 
617         {
618             gl::GLContext::LocalErrorScope errorScope(*gl);
619 
620             gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0,
621                                       target.get(), tex->mGLName, level);
622 
623             if (errorScope.GetError())
624                 break;
625         }
626 
627         const GLenum status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
628         if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE)
629             break;
630 
631         const gfx::IntSize destSize(mWidth, mHeight);
632         const auto dstOrigin = (webgl->mPixelStore_FlipY ? gl::OriginPos::TopLeft
633                                                          : gl::OriginPos::BottomLeft);
634         if (!gl->BlitHelper()->BlitImageToFramebuffer(mImage, destSize, scopedFB.FB(),
635                                                       dstOrigin))
636         {
637             break;
638         }
639 
640         // Blitting was successful, so we're done!
641         *out_error = 0;
642         return true;
643     } while (false);
644 
645     webgl->GenerateWarning("%s: Failed to hit GPU-copy fast-path. Falling back to CPU"
646                            " upload.",
647                            funcName);
648 
649     const RefPtr<gfx::SourceSurface> surf = mImage->GetAsSourceSurface();
650 
651     RefPtr<gfx::DataSourceSurface> dataSurf;
652     if (surf) {
653         // WARNING: OSX can lose our MakeCurrent here.
654         dataSurf = surf->GetDataSurface();
655     }
656     if (!dataSurf) {
657         webgl->ErrorOutOfMemory("%s: GetAsSourceSurface or GetDataSurface failed after"
658                                 " blit failed for TexUnpackImage.",
659                                 funcName);
660         return false;
661     }
662 
663     const TexUnpackSurface surfBlob(webgl, target, mWidth, mHeight, mDepth, dataSurf,
664                                     mSrcIsPremult);
665 
666     return surfBlob.TexOrSubImage(isSubImage, needsRespec, funcName, tex, target, level,
667                                   dui, xOffset, yOffset, zOffset, out_error);
668 }
669 
670 ////////////////////////////////////////////////////////////////////////////////
671 ////////////////////////////////////////////////////////////////////////////////
672 // TexUnpackSurface
673 
TexUnpackSurface(const WebGLContext * webgl,TexImageTarget target,uint32_t width,uint32_t height,uint32_t depth,gfx::DataSourceSurface * surf,bool isAlphaPremult)674 TexUnpackSurface::TexUnpackSurface(const WebGLContext* webgl, TexImageTarget target,
675                                    uint32_t width, uint32_t height, uint32_t depth,
676                                    gfx::DataSourceSurface* surf, bool isAlphaPremult)
677     : TexUnpackBlob(webgl, target, surf->GetSize().width, width, height, depth,
678                     isAlphaPremult)
679     , mSurf(surf)
680 { }
681 
682 //////////
683 
684 static bool
GetFormatForSurf(gfx::SourceSurface * surf,WebGLTexelFormat * const out_texelFormat,uint8_t * const out_bpp)685 GetFormatForSurf(gfx::SourceSurface* surf, WebGLTexelFormat* const out_texelFormat,
686                  uint8_t* const out_bpp)
687 {
688     const auto surfFormat = surf->GetFormat();
689     switch (surfFormat) {
690     case gfx::SurfaceFormat::B8G8R8A8:
691         *out_texelFormat = WebGLTexelFormat::BGRA8;
692         *out_bpp = 4;
693         return true;
694 
695     case gfx::SurfaceFormat::B8G8R8X8:
696         *out_texelFormat = WebGLTexelFormat::BGRX8;
697         *out_bpp = 4;
698         return true;
699 
700     case gfx::SurfaceFormat::R8G8B8A8:
701         *out_texelFormat = WebGLTexelFormat::RGBA8;
702         *out_bpp = 4;
703         return true;
704 
705     case gfx::SurfaceFormat::R8G8B8X8:
706         *out_texelFormat = WebGLTexelFormat::RGBX8;
707         *out_bpp = 4;
708         return true;
709 
710     case gfx::SurfaceFormat::R5G6B5_UINT16:
711         *out_texelFormat = WebGLTexelFormat::RGB565;
712         *out_bpp = 2;
713         return true;
714 
715     case gfx::SurfaceFormat::A8:
716         *out_texelFormat = WebGLTexelFormat::A8;
717         *out_bpp = 1;
718         return true;
719 
720     case gfx::SurfaceFormat::YUV:
721         // Ugh...
722         NS_ERROR("We don't handle uploads from YUV sources yet.");
723         // When we want to, check out gfx/ycbcr/YCbCrUtils.h. (specifically
724         // GetYCbCrToRGBDestFormatAndSize and ConvertYCbCrToRGB)
725         return false;
726 
727     default:
728         return false;
729     }
730 }
731 
732 //////////
733 
734 bool
Validate(WebGLContext * webgl,const char * funcName,const webgl::PackingInfo & pi)735 TexUnpackSurface::Validate(WebGLContext* webgl, const char* funcName,
736                            const webgl::PackingInfo& pi)
737 {
738     if (!ValidatePIForDOM(webgl, funcName, pi))
739         return false;
740 
741     const auto fullRows = mSurf->GetSize().height;
742     return ValidateUnpackPixels(webgl, funcName, fullRows, 0, this);
743 }
744 
745 bool
TexOrSubImage(bool isSubImage,bool needsRespec,const char * funcName,WebGLTexture * tex,TexImageTarget target,GLint level,const webgl::DriverUnpackInfo * dstDUI,GLint xOffset,GLint yOffset,GLint zOffset,GLenum * const out_error) const746 TexUnpackSurface::TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
747                                 WebGLTexture* tex, TexImageTarget target, GLint level,
748                                 const webgl::DriverUnpackInfo* dstDUI, GLint xOffset,
749                                 GLint yOffset, GLint zOffset,
750                                 GLenum* const out_error) const
751 {
752     const auto& webgl = tex->mContext;
753 
754     ////
755 
756     const auto rowLength = mSurf->GetSize().width;
757     const auto rowCount = mSurf->GetSize().height;
758 
759     const auto& dstPI = dstDUI->ToPacking();
760     const auto& dstBPP = webgl::BytesPerPixel(dstPI);
761     const auto dstFormat = FormatForPackingInfo(dstPI);
762 
763     ////
764 
765     WebGLTexelFormat srcFormat;
766     uint8_t srcBPP;
767     if (!GetFormatForSurf(mSurf, &srcFormat, &srcBPP)) {
768         webgl->ErrorImplementationBug("%s: GetFormatForSurf failed for"
769                                       " WebGLTexelFormat::%u.",
770                                       funcName, uint32_t(mSurf->GetFormat()));
771         return false;
772     }
773 
774     gfx::DataSourceSurface::ScopedMap map(mSurf, gfx::DataSourceSurface::MapType::READ);
775     if (!map.IsMapped()) {
776         webgl->ErrorOutOfMemory("%s: Failed to map source surface for upload.", funcName);
777         return false;
778     }
779 
780     const auto& srcBegin = map.GetData();
781     const auto& srcStride = map.GetStride();
782 
783     ////
784 
785     const auto srcRowLengthBytes = rowLength * srcBPP;
786 
787     const uint8_t maxGLAlignment = 8;
788     uint8_t srcAlignment = 1;
789     for (; srcAlignment <= maxGLAlignment; srcAlignment *= 2) {
790         const auto strideGuess = RoundUpToMultipleOf(srcRowLengthBytes, srcAlignment);
791         if (strideGuess == srcStride)
792             break;
793     }
794     const uint32_t dstAlignment = (srcAlignment > maxGLAlignment) ? 1 : srcAlignment;
795 
796     const auto dstRowLengthBytes = rowLength * dstBPP;
797     const auto dstStride = RoundUpToMultipleOf(dstRowLengthBytes, dstAlignment);
798 
799     ////
800 
801     const uint8_t* dstBegin = srcBegin;
802     UniqueBuffer tempBuffer;
803     if (!ConvertIfNeeded(webgl, funcName, rowLength, rowCount, srcFormat, srcBegin,
804                          srcStride, dstFormat, dstStride, &dstBegin, &tempBuffer))
805     {
806         return false;
807     }
808 
809     ////
810 
811     const auto& gl = webgl->gl;
812     MOZ_ALWAYS_TRUE( gl->MakeCurrent() );
813 
814     gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, dstAlignment);
815     if (webgl->IsWebGL2()) {
816         gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, rowLength);
817     }
818 
819     *out_error = DoTexOrSubImage(isSubImage, gl, target.get(), level, dstDUI, xOffset,
820                                  yOffset, zOffset, mWidth, mHeight, mDepth, dstBegin);
821 
822     gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, webgl->mPixelStore_UnpackAlignment);
823     if (webgl->IsWebGL2()) {
824         gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, webgl->mPixelStore_UnpackRowLength);
825     }
826 
827     return true;
828 }
829 
830 } // namespace webgl
831 } // namespace mozilla
832