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