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