Bug 1685822 [wpt PR 27117] - [Import Maps] Add tests for rejecting multiple import...
[gecko.git] / dom / canvas / TexUnpackBlob.cpp
bloba2a60aa61f4bf768153b95790195c43addd23b36
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
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/. */
6 #include "TexUnpackBlob.h"
8 #include "GLBlitHelper.h"
9 #include "GLContext.h"
10 #include "mozilla/dom/Element.h"
11 #include "mozilla/dom/HTMLCanvasElement.h"
12 #include "mozilla/gfx/Logging.h"
13 #include "mozilla/RefPtr.h"
14 #include "nsLayoutUtils.h"
15 #include "WebGLBuffer.h"
16 #include "WebGLContext.h"
17 #include "WebGLFormats.h"
18 #include "WebGLTexelConversions.h"
19 #include "WebGLTexture.h"
21 namespace mozilla {
23 bool WebGLPixelStore::AssertCurrent(gl::GLContext& gl,
24 const bool isWebgl2) const {
25 WebGLPixelStore actual;
26 gl.GetInt(LOCAL_GL_UNPACK_ALIGNMENT, &actual.mUnpackAlignment);
27 if (isWebgl2) {
28 gl.GetInt(LOCAL_GL_UNPACK_ROW_LENGTH, &actual.mUnpackRowLength);
29 gl.GetInt(LOCAL_GL_UNPACK_IMAGE_HEIGHT, &actual.mUnpackImageHeight);
31 gl.GetInt(LOCAL_GL_UNPACK_SKIP_PIXELS, &actual.mUnpackSkipPixels);
32 gl.GetInt(LOCAL_GL_UNPACK_SKIP_ROWS, &actual.mUnpackSkipRows);
33 gl.GetInt(LOCAL_GL_UNPACK_SKIP_IMAGES, &actual.mUnpackSkipImages);
36 bool ok = true;
37 ok &= (mUnpackAlignment == actual.mUnpackAlignment);
38 ok &= (mUnpackRowLength == actual.mUnpackRowLength);
39 ok &= (mUnpackImageHeight == actual.mUnpackImageHeight);
40 ok &= (mUnpackSkipPixels == actual.mUnpackSkipPixels);
41 ok &= (mUnpackSkipRows == actual.mUnpackSkipRows);
42 ok &= (mUnpackSkipImages == actual.mUnpackSkipImages);
43 if (ok) return ok;
45 const auto fnToStr = [](const WebGLPixelStore& x) {
46 const auto text = nsPrintfCString("%u,%u,%u;%u,%u,%u", x.mUnpackAlignment,
47 x.mUnpackRowLength, x.mUnpackImageHeight,
48 x.mUnpackSkipPixels, x.mUnpackSkipRows,
49 x.mUnpackSkipImages);
50 return ToString(text);
53 const auto was = fnToStr(actual);
54 const auto expected = fnToStr(*this);
55 gfxCriticalError() << "WebGLPixelStore not current. Was " << was
56 << ". Expected << " << expected << ".";
57 return ok;
60 void WebGLPixelStore::Apply(gl::GLContext& gl, const bool isWebgl2,
61 const uvec3& uploadSize) const {
62 gl.fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, mUnpackAlignment);
63 if (!isWebgl2) return;
65 // Re-simplify. (ANGLE seems to have an issue with imageHeight ==
66 // uploadSize.y)
67 auto rowLength = mUnpackRowLength;
68 auto imageHeight = mUnpackImageHeight;
69 if (rowLength == uploadSize.x) {
70 rowLength = 0;
72 if (imageHeight == uploadSize.y) {
73 imageHeight = 0;
76 gl.fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, rowLength);
77 gl.fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT, imageHeight);
79 gl.fPixelStorei(LOCAL_GL_UNPACK_SKIP_PIXELS, mUnpackSkipPixels);
80 gl.fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS, mUnpackSkipRows);
81 gl.fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, mUnpackSkipImages);
84 namespace webgl {
86 static bool IsPIValidForDOM(const webgl::PackingInfo& pi) {
87 // https://www.khronos.org/registry/webgl/specs/latest/2.0/#TEXTURE_TYPES_FORMATS_FROM_DOM_ELEMENTS_TABLE
89 // Just check for invalid individual formats and types, not combinations.
90 switch (pi.format) {
91 case LOCAL_GL_RGB:
92 case LOCAL_GL_RGBA:
93 case LOCAL_GL_LUMINANCE_ALPHA:
94 case LOCAL_GL_LUMINANCE:
95 case LOCAL_GL_ALPHA:
96 case LOCAL_GL_RED:
97 case LOCAL_GL_RED_INTEGER:
98 case LOCAL_GL_RG:
99 case LOCAL_GL_RG_INTEGER:
100 case LOCAL_GL_RGB_INTEGER:
101 case LOCAL_GL_RGBA_INTEGER:
102 break;
104 case LOCAL_GL_SRGB:
105 case LOCAL_GL_SRGB_ALPHA:
106 // Allowed in WebGL1+EXT_srgb
107 break;
109 default:
110 return false;
113 switch (pi.type) {
114 case LOCAL_GL_UNSIGNED_BYTE:
115 case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
116 case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
117 case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
118 case LOCAL_GL_HALF_FLOAT:
119 case LOCAL_GL_HALF_FLOAT_OES:
120 case LOCAL_GL_FLOAT:
121 case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV:
122 break;
124 default:
125 return false;
128 return true;
131 static bool ValidatePIForDOM(const WebGLContext* const webgl,
132 const webgl::PackingInfo& pi) {
133 if (!IsPIValidForDOM(pi)) {
134 webgl->ErrorInvalidValue("Format or type is invalid for DOM sources.");
135 return false;
137 return true;
140 static WebGLTexelFormat FormatForPackingInfo(const PackingInfo& pi) {
141 switch (pi.type) {
142 case LOCAL_GL_UNSIGNED_BYTE:
143 switch (pi.format) {
144 case LOCAL_GL_RED:
145 case LOCAL_GL_LUMINANCE:
146 case LOCAL_GL_RED_INTEGER:
147 return WebGLTexelFormat::R8;
149 case LOCAL_GL_ALPHA:
150 return WebGLTexelFormat::A8;
152 case LOCAL_GL_LUMINANCE_ALPHA:
153 return WebGLTexelFormat::RA8;
155 case LOCAL_GL_RGB:
156 case LOCAL_GL_RGB_INTEGER:
157 case LOCAL_GL_SRGB:
158 return WebGLTexelFormat::RGB8;
160 case LOCAL_GL_RGBA:
161 case LOCAL_GL_RGBA_INTEGER:
162 case LOCAL_GL_SRGB_ALPHA:
163 return WebGLTexelFormat::RGBA8;
165 case LOCAL_GL_RG:
166 case LOCAL_GL_RG_INTEGER:
167 return WebGLTexelFormat::RG8;
169 default:
170 break;
172 break;
174 case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
175 if (pi.format == LOCAL_GL_RGB) return WebGLTexelFormat::RGB565;
176 break;
178 case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
179 if (pi.format == LOCAL_GL_RGBA) return WebGLTexelFormat::RGBA5551;
180 break;
182 case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
183 if (pi.format == LOCAL_GL_RGBA) return WebGLTexelFormat::RGBA4444;
184 break;
186 case LOCAL_GL_HALF_FLOAT:
187 case LOCAL_GL_HALF_FLOAT_OES:
188 switch (pi.format) {
189 case LOCAL_GL_RED:
190 case LOCAL_GL_LUMINANCE:
191 return WebGLTexelFormat::R16F;
193 case LOCAL_GL_ALPHA:
194 return WebGLTexelFormat::A16F;
195 case LOCAL_GL_LUMINANCE_ALPHA:
196 return WebGLTexelFormat::RA16F;
197 case LOCAL_GL_RG:
198 return WebGLTexelFormat::RG16F;
199 case LOCAL_GL_RGB:
200 return WebGLTexelFormat::RGB16F;
201 case LOCAL_GL_RGBA:
202 return WebGLTexelFormat::RGBA16F;
204 default:
205 break;
207 break;
209 case LOCAL_GL_FLOAT:
210 switch (pi.format) {
211 case LOCAL_GL_RED:
212 case LOCAL_GL_LUMINANCE:
213 return WebGLTexelFormat::R32F;
215 case LOCAL_GL_ALPHA:
216 return WebGLTexelFormat::A32F;
217 case LOCAL_GL_LUMINANCE_ALPHA:
218 return WebGLTexelFormat::RA32F;
219 case LOCAL_GL_RG:
220 return WebGLTexelFormat::RG32F;
221 case LOCAL_GL_RGB:
222 return WebGLTexelFormat::RGB32F;
223 case LOCAL_GL_RGBA:
224 return WebGLTexelFormat::RGBA32F;
226 default:
227 break;
229 break;
231 case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV:
232 if (pi.format == LOCAL_GL_RGB) return WebGLTexelFormat::RGB11F11F10F;
233 break;
235 default:
236 break;
239 return WebGLTexelFormat::FormatNotSupportingAnyConversion;
242 ////////////////////
244 static uint32_t ZeroOn2D(const GLenum target, const uint32_t val) {
245 const bool is2d = !IsTexTarget3D(target);
246 if (is2d) return 0;
247 return val;
250 static bool ValidateUnpackPixels(const WebGLContext* webgl, uint32_t fullRows,
251 uint32_t tailPixels,
252 webgl::TexUnpackBlob* const blob) {
253 const auto& size = blob->mDesc.size;
254 if (!size.x || !size.y || !size.z) return true;
256 const auto& unpacking = blob->mDesc.unpacking;
258 // -
260 const auto usedPixelsPerRow =
261 CheckedUint32(unpacking.mUnpackSkipPixels) + size.x;
262 if (!usedPixelsPerRow.isValid() ||
263 usedPixelsPerRow.value() > unpacking.mUnpackRowLength) {
264 webgl->ErrorInvalidOperation(
265 "UNPACK_SKIP_PIXELS + width >"
266 " UNPACK_ROW_LENGTH.");
267 return false;
270 if (size.y > unpacking.mUnpackImageHeight) {
271 webgl->ErrorInvalidOperation("height > UNPACK_IMAGE_HEIGHT.");
272 return false;
275 //////
277 // The spec doesn't bound SKIP_ROWS + height <= IMAGE_HEIGHT, unfortunately.
278 auto skipFullRows =
279 CheckedUint32(unpacking.mUnpackSkipImages) * unpacking.mUnpackImageHeight;
280 skipFullRows += unpacking.mUnpackSkipRows;
282 // Full rows in the final image, excluding the tail.
283 MOZ_ASSERT(size.y >= 1);
284 MOZ_ASSERT(size.z >= 1);
285 auto usedFullRows = CheckedUint32(size.z - 1) * unpacking.mUnpackImageHeight;
286 usedFullRows += size.y - 1;
288 const auto fullRowsNeeded = skipFullRows + usedFullRows;
289 if (!fullRowsNeeded.isValid()) {
290 webgl->ErrorOutOfMemory("Invalid calculation for required row count.");
291 return false;
294 if (fullRows > fullRowsNeeded.value()) {
295 blob->mNeedsExactUpload = false;
296 return true;
299 if (fullRows == fullRowsNeeded.value() &&
300 tailPixels >= usedPixelsPerRow.value()) {
301 MOZ_ASSERT(blob->mNeedsExactUpload);
302 return true;
305 webgl->ErrorInvalidOperation(
306 "Desired upload requires more data than is"
307 " available: (%u rows plus %u pixels needed, %u rows"
308 " plus %u pixels available)",
309 fullRowsNeeded.value(), usedPixelsPerRow.value(), fullRows, tailPixels);
310 return false;
313 static bool ValidateUnpackBytes(const WebGLContext* const webgl,
314 const webgl::PackingInfo& pi,
315 size_t availByteCount,
316 webgl::TexUnpackBlob* const blob) {
317 const auto& size = blob->mDesc.size;
318 if (!size.x || !size.y || !size.z) return true;
319 const auto& unpacking = blob->mDesc.unpacking;
321 const auto bytesPerPixel = webgl::BytesPerPixel(pi);
322 const auto bytesPerRow =
323 CheckedUint32(unpacking.mUnpackRowLength) * bytesPerPixel;
324 const auto rowStride =
325 RoundUpToMultipleOf(bytesPerRow, unpacking.mUnpackAlignment);
327 const auto fullRows = availByteCount / rowStride;
328 if (!fullRows.isValid()) {
329 webgl->ErrorOutOfMemory("Unacceptable upload size calculated.");
330 return false;
333 const auto bodyBytes = fullRows.value() * rowStride.value();
334 const auto tailPixels = (availByteCount - bodyBytes) / bytesPerPixel;
336 return ValidateUnpackPixels(webgl, fullRows.value(), tailPixels, blob);
339 ////////////////////
341 // static
342 std::unique_ptr<TexUnpackBlob> TexUnpackBlob::Create(
343 const TexUnpackBlobDesc& desc) {
344 return std::unique_ptr<TexUnpackBlob>{[&]() -> TexUnpackBlob* {
345 if (!IsTarget3D(desc.imageTarget) && desc.size.z != 1) {
346 MOZ_ASSERT(false);
347 return nullptr;
350 switch (desc.unpacking.mUnpackAlignment) {
351 case 1:
352 case 2:
353 case 4:
354 case 8:
355 break;
356 default:
357 MOZ_ASSERT(false);
358 return nullptr;
361 if (desc.sd) {
362 return new TexUnpackImage(desc);
364 if (desc.dataSurf) {
365 return new TexUnpackSurface(desc);
368 if (desc.srcAlphaType != gfxAlphaType::NonPremult) {
369 MOZ_ASSERT(false);
370 return nullptr;
372 return new TexUnpackBytes(desc);
373 }()};
376 static bool HasColorAndAlpha(const WebGLTexelFormat format) {
377 switch (format) {
378 case WebGLTexelFormat::RA8:
379 case WebGLTexelFormat::RA16F:
380 case WebGLTexelFormat::RA32F:
381 case WebGLTexelFormat::RGBA8:
382 case WebGLTexelFormat::RGBA5551:
383 case WebGLTexelFormat::RGBA4444:
384 case WebGLTexelFormat::RGBA16F:
385 case WebGLTexelFormat::RGBA32F:
386 case WebGLTexelFormat::BGRA8:
387 return true;
388 default:
389 return false;
393 bool TexUnpackBlob::ConvertIfNeeded(
394 const WebGLContext* const webgl, const uint32_t rowLength,
395 const uint32_t rowCount, WebGLTexelFormat srcFormat,
396 const uint8_t* const srcBegin, const ptrdiff_t srcStride,
397 WebGLTexelFormat dstFormat, const ptrdiff_t dstStride,
398 const uint8_t** const out_begin,
399 UniqueBuffer* const out_anchoredBuffer) const {
400 MOZ_ASSERT(srcFormat != WebGLTexelFormat::FormatNotSupportingAnyConversion);
401 MOZ_ASSERT(dstFormat != WebGLTexelFormat::FormatNotSupportingAnyConversion);
403 *out_begin = srcBegin;
405 const auto& unpacking = mDesc.unpacking;
407 if (!rowLength || !rowCount) return true;
409 const auto srcIsPremult = (mDesc.srcAlphaType == gfxAlphaType::Premult);
410 const auto& dstIsPremult = unpacking.mPremultiplyAlpha;
411 const auto fnHasPremultMismatch = [&]() {
412 if (mDesc.srcAlphaType == gfxAlphaType::Opaque) return false;
414 if (!HasColorAndAlpha(srcFormat)) return false;
416 return srcIsPremult != dstIsPremult;
419 const auto srcOrigin =
420 (unpacking.mFlipY ? gl::OriginPos::TopLeft : gl::OriginPos::BottomLeft);
421 const auto dstOrigin = gl::OriginPos::BottomLeft;
423 if (srcFormat != dstFormat) {
424 webgl->GeneratePerfWarning(
425 "Conversion requires pixel reformatting. (%u->%u)", uint32_t(srcFormat),
426 uint32_t(dstFormat));
427 } else if (fnHasPremultMismatch()) {
428 webgl->GeneratePerfWarning(
429 "Conversion requires change in"
430 " alpha-premultiplication.");
431 } else if (srcOrigin != dstOrigin) {
432 webgl->GeneratePerfWarning("Conversion requires y-flip.");
433 } else if (srcStride != dstStride) {
434 webgl->GeneratePerfWarning("Conversion requires change in stride. (%u->%u)",
435 uint32_t(srcStride), uint32_t(dstStride));
436 } else {
437 return true;
440 ////
442 const auto dstTotalBytes = CheckedUint32(rowCount) * dstStride;
443 if (!dstTotalBytes.isValid()) {
444 webgl->ErrorOutOfMemory("Calculation failed.");
445 return false;
448 UniqueBuffer dstBuffer = calloc(1u, (size_t)dstTotalBytes.value());
449 if (!dstBuffer.get()) {
450 webgl->ErrorOutOfMemory("Failed to allocate dest buffer.");
451 return false;
453 const auto dstBegin = static_cast<uint8_t*>(dstBuffer.get());
455 ////
457 // And go!:
458 bool wasTrivial;
459 if (!ConvertImage(rowLength, rowCount, srcBegin, srcStride, srcOrigin,
460 srcFormat, srcIsPremult, dstBegin, dstStride, dstOrigin,
461 dstFormat, dstIsPremult, &wasTrivial)) {
462 webgl->ErrorImplementationBug("ConvertImage failed.");
463 return false;
466 *out_begin = dstBegin;
467 *out_anchoredBuffer = std::move(dstBuffer);
468 return true;
471 static GLenum DoTexOrSubImage(bool isSubImage, gl::GLContext* gl,
472 TexImageTarget target, GLint level,
473 const DriverUnpackInfo* dui, GLint xOffset,
474 GLint yOffset, GLint zOffset, GLsizei width,
475 GLsizei height, GLsizei depth, const void* data) {
476 if (isSubImage) {
477 return DoTexSubImage(gl, target, level, xOffset, yOffset, zOffset, width,
478 height, depth, dui->ToPacking(), data);
479 } else {
480 return DoTexImage(gl, target, level, dui, width, height, depth, data);
484 //////////////////////////////////////////////////////////////////////////////////////////
485 // TexUnpackBytes
487 bool TexUnpackBytes::Validate(const WebGLContext* const webgl,
488 const webgl::PackingInfo& pi) {
489 if (!HasData()) return true;
491 CheckedInt<size_t> availBytes = 0;
492 if (mDesc.cpuData) {
493 const auto& range = mDesc.cpuData->Data();
494 availBytes = range.length();
495 } else if (mDesc.pboOffset) {
496 const auto& pboOffset = *mDesc.pboOffset;
498 const auto& pbo =
499 webgl->ValidateBufferSelection(LOCAL_GL_PIXEL_UNPACK_BUFFER);
500 if (!pbo) return false; // Might be invalid e.g. due to in-use by TF.
501 availBytes = pbo->ByteLength();
502 availBytes -= pboOffset;
503 } else {
504 MOZ_ASSERT(false, "Must be one of the above");
506 if (!availBytes.isValid()) {
507 webgl->ErrorInvalidOperation("Offset is passed end of buffer.");
508 return false;
511 return ValidateUnpackBytes(webgl, pi, availBytes.value(), this);
514 bool TexUnpackBytes::TexOrSubImage(bool isSubImage, bool needsRespec,
515 WebGLTexture* tex, GLint level,
516 const webgl::DriverUnpackInfo* dui,
517 GLint xOffset, GLint yOffset, GLint zOffset,
518 const webgl::PackingInfo& pi,
519 GLenum* const out_error) const {
520 const auto& webgl = tex->mContext;
521 const auto& target = mDesc.imageTarget;
522 const auto& size = mDesc.size;
523 const auto& unpacking = mDesc.unpacking;
525 const auto format = FormatForPackingInfo(pi);
526 const auto bytesPerPixel = webgl::BytesPerPixel(pi);
528 const uint8_t* uploadPtr = nullptr;
529 if (mDesc.cpuData) {
530 const auto range = mDesc.cpuData->Data();
531 uploadPtr = range.begin().get();
532 if (!uploadPtr) {
533 MOZ_ASSERT(!range.length());
537 UniqueBuffer tempBuffer;
539 do {
540 if (mDesc.pboOffset || !uploadPtr) break;
542 if (!unpacking.mFlipY && !unpacking.mPremultiplyAlpha) {
543 break;
546 webgl->GenerateWarning(
547 "Alpha-premult and y-flip are deprecated for"
548 " non-DOM-Element uploads.");
550 const uint32_t rowLength = size.x;
551 const uint32_t rowCount = size.y * size.z;
552 const auto stride = RoundUpToMultipleOf(rowLength * bytesPerPixel,
553 unpacking.mUnpackAlignment);
554 const auto srcPtr = uploadPtr;
555 if (!ConvertIfNeeded(webgl, rowLength, rowCount, format, srcPtr, stride,
556 format, stride, &uploadPtr, &tempBuffer)) {
557 return false;
559 } while (false);
561 //////
563 const auto& gl = webgl->gl;
565 bool useParanoidHandling = false;
566 if (mNeedsExactUpload && webgl->mBoundPixelUnpackBuffer) {
567 webgl->GenerateWarning(
568 "Uploads from a buffer with a final row with a byte"
569 " count smaller than the row stride can incur extra"
570 " overhead.");
572 if (gl->WorkAroundDriverBugs()) {
573 useParanoidHandling |= (gl->Vendor() == gl::GLVendor::NVIDIA);
577 if (!useParanoidHandling) {
578 if (webgl->mBoundPixelUnpackBuffer) {
579 gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER,
580 webgl->mBoundPixelUnpackBuffer->mGLName);
583 *out_error =
584 DoTexOrSubImage(isSubImage, gl, target, level, dui, xOffset, yOffset,
585 zOffset, size.x, size.y, size.z, uploadPtr);
587 if (webgl->mBoundPixelUnpackBuffer) {
588 gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, 0);
590 return true;
593 //////
595 MOZ_ASSERT(webgl->mBoundPixelUnpackBuffer);
597 if (!isSubImage) {
598 // Alloc first to catch OOMs.
599 AssertUintParamCorrect(gl, LOCAL_GL_PIXEL_UNPACK_BUFFER, 0);
600 *out_error =
601 DoTexOrSubImage(false, gl, target, level, dui, xOffset, yOffset,
602 zOffset, size.x, size.y, size.z, nullptr);
603 if (*out_error) return true;
606 const ScopedLazyBind bindPBO(gl, LOCAL_GL_PIXEL_UNPACK_BUFFER,
607 webgl->mBoundPixelUnpackBuffer);
609 //////
611 // Make our sometimes-implicit values explicit. Also this keeps them constant
612 // when we ask for height=mHeight-1 and such.
613 gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, unpacking.mUnpackRowLength);
614 gl->fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT, unpacking.mUnpackImageHeight);
616 if (size.z > 1) {
617 *out_error =
618 DoTexOrSubImage(true, gl, target, level, dui, xOffset, yOffset, zOffset,
619 size.x, size.y, size.z - 1, uploadPtr);
622 // Skip the images we uploaded.
623 const auto skipImages = ZeroOn2D(target, unpacking.mUnpackSkipImages);
624 gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, skipImages + size.z - 1);
626 if (size.y > 1) {
627 *out_error =
628 DoTexOrSubImage(true, gl, target, level, dui, xOffset, yOffset,
629 zOffset + size.z - 1, size.x, size.y - 1, 1, uploadPtr);
632 const auto totalSkipRows =
633 CheckedUint32(skipImages) * unpacking.mUnpackImageHeight +
634 unpacking.mUnpackSkipRows;
635 const auto totalFullRows =
636 CheckedUint32(size.z - 1) * unpacking.mUnpackImageHeight + size.y - 1;
637 const auto tailOffsetRows = totalSkipRows + totalFullRows;
639 const auto bytesPerRow =
640 CheckedUint32(unpacking.mUnpackRowLength) * bytesPerPixel;
641 const auto rowStride =
642 RoundUpToMultipleOf(bytesPerRow, unpacking.mUnpackAlignment);
643 if (!rowStride.isValid()) {
644 MOZ_CRASH("Should be checked earlier.");
646 const auto tailOffsetBytes = tailOffsetRows * rowStride;
648 uploadPtr += tailOffsetBytes.value();
650 //////
652 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1); // No stride padding.
653 gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, 0); // No padding in general.
654 gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, 0); // Don't skip images,
655 gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS,
656 0); // or rows.
657 // Keep skipping pixels though!
659 *out_error = DoTexOrSubImage(true, gl, target, level, dui, xOffset,
660 yOffset + size.y - 1, zOffset + size.z - 1,
661 size.x, 1, 1, uploadPtr);
663 // Caller will reset all our modified PixelStorei state.
665 return true;
668 ////////////////////////////////////////////////////////////////////////////////
669 ////////////////////////////////////////////////////////////////////////////////
670 // TexUnpackImage
672 TexUnpackImage::~TexUnpackImage() = default;
674 bool TexUnpackImage::Validate(const WebGLContext* const webgl,
675 const webgl::PackingInfo& pi) {
676 if (!ValidatePIForDOM(webgl, pi)) return false;
678 const auto fullRows = mDesc.imageSize.y;
679 return ValidateUnpackPixels(webgl, fullRows, 0, this);
682 Maybe<std::string> BlitPreventReason(const int32_t level, const ivec3& offset,
683 const webgl::PackingInfo& pi,
684 const TexUnpackBlobDesc& desc) {
685 const auto& size = desc.size;
686 const auto& unpacking = desc.unpacking;
688 const auto ret = [&]() -> const char* {
689 if (size.z != 1) {
690 return "depth is not 1";
692 if (offset.x != 0 || offset.y != 0 || offset.z != 0) {
693 return "x/y/zOffset is not 0";
696 if (unpacking.mUnpackSkipPixels || unpacking.mUnpackSkipRows ||
697 unpacking.mUnpackSkipImages) {
698 return "non-zero UNPACK_SKIP_* not yet supported";
701 const auto premultReason = [&]() -> const char* {
702 if (desc.srcAlphaType == gfxAlphaType::Opaque) return nullptr;
704 const bool srcIsPremult = (desc.srcAlphaType == gfxAlphaType::Premult);
705 const auto& dstIsPremult = unpacking.mPremultiplyAlpha;
706 if (srcIsPremult == dstIsPremult) return nullptr;
708 if (dstIsPremult) {
709 return "UNPACK_PREMULTIPLY_ALPHA_WEBGL is not true";
710 } else {
711 return "UNPACK_PREMULTIPLY_ALPHA_WEBGL is not false";
713 }();
714 if (premultReason) return premultReason;
716 if (pi.format != LOCAL_GL_RGBA) {
717 return "`format` is not RGBA";
720 if (pi.type != LOCAL_GL_UNSIGNED_BYTE) {
721 return "`type` is not UNSIGNED_BYTE";
723 return nullptr;
724 }();
725 if (ret) {
726 return Some(std::string(ret));
728 return {};
731 bool TexUnpackImage::TexOrSubImage(bool isSubImage, bool needsRespec,
732 WebGLTexture* tex, GLint level,
733 const webgl::DriverUnpackInfo* dui,
734 GLint xOffset, GLint yOffset, GLint zOffset,
735 const webgl::PackingInfo& pi,
736 GLenum* const out_error) const {
737 MOZ_ASSERT_IF(needsRespec, !isSubImage);
739 const auto& webgl = tex->mContext;
740 const auto& target = mDesc.imageTarget;
741 const auto& size = mDesc.size;
742 const auto& sd = *(mDesc.sd);
743 const auto& unpacking = mDesc.unpacking;
745 const auto& gl = webgl->GL();
747 // -
749 const auto reason =
750 BlitPreventReason(level, {xOffset, yOffset, zOffset}, pi, mDesc);
751 if (reason) {
752 webgl->GeneratePerfWarning(
753 "Failed to hit GPU-copy fast-path."
754 " (%s) Falling back to CPU upload.",
755 reason->c_str());
756 return false;
759 // -
761 if (needsRespec) {
762 *out_error =
763 DoTexOrSubImage(isSubImage, gl, target, level, dui, xOffset, yOffset,
764 zOffset, size.x, size.y, size.z, nullptr);
765 if (*out_error) return true;
769 gl::ScopedFramebuffer scopedFB(gl);
770 gl::ScopedBindFramebuffer bindFB(gl, scopedFB.FB());
773 gl::GLContext::LocalErrorScope errorScope(*gl);
775 gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER,
776 LOCAL_GL_COLOR_ATTACHMENT0, target,
777 tex->mGLName, level);
779 const auto err = errorScope.GetError();
780 MOZ_ALWAYS_TRUE(!err);
783 const GLenum status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
784 MOZ_ALWAYS_TRUE(status == LOCAL_GL_FRAMEBUFFER_COMPLETE);
786 const auto dstOrigin =
787 (unpacking.mFlipY ? gl::OriginPos::TopLeft : gl::OriginPos::BottomLeft);
788 if (!gl->BlitHelper()->BlitSdToFramebuffer(sd, {size.x, size.y},
789 dstOrigin)) {
790 webgl->ErrorImplementationBug("BlitSdToFramebuffer failed for type %i.",
791 int(sd.type()));
792 return false;
796 return true;
799 ////////////////////////////////////////////////////////////////////////////////
800 ////////////////////////////////////////////////////////////////////////////////
801 // TexUnpackSurface
803 TexUnpackSurface::~TexUnpackSurface() = default;
805 //////////
807 static bool GetFormatForSurf(const gfx::SourceSurface* surf,
808 WebGLTexelFormat* const out_texelFormat,
809 uint8_t* const out_bpp) {
810 const auto surfFormat = surf->GetFormat();
811 switch (surfFormat) {
812 case gfx::SurfaceFormat::B8G8R8A8:
813 *out_texelFormat = WebGLTexelFormat::BGRA8;
814 *out_bpp = 4;
815 return true;
817 case gfx::SurfaceFormat::B8G8R8X8:
818 *out_texelFormat = WebGLTexelFormat::BGRX8;
819 *out_bpp = 4;
820 return true;
822 case gfx::SurfaceFormat::R8G8B8A8:
823 *out_texelFormat = WebGLTexelFormat::RGBA8;
824 *out_bpp = 4;
825 return true;
827 case gfx::SurfaceFormat::R8G8B8X8:
828 *out_texelFormat = WebGLTexelFormat::RGBX8;
829 *out_bpp = 4;
830 return true;
832 case gfx::SurfaceFormat::R5G6B5_UINT16:
833 *out_texelFormat = WebGLTexelFormat::RGB565;
834 *out_bpp = 2;
835 return true;
837 case gfx::SurfaceFormat::A8:
838 *out_texelFormat = WebGLTexelFormat::A8;
839 *out_bpp = 1;
840 return true;
842 case gfx::SurfaceFormat::YUV:
843 // Ugh...
844 NS_ERROR("We don't handle uploads from YUV sources yet.");
845 // When we want to, check out gfx/ycbcr/YCbCrUtils.h. (specifically
846 // GetYCbCrToRGBDestFormatAndSize and ConvertYCbCrToRGB)
847 return false;
849 default:
850 return false;
854 //////////
856 bool TexUnpackSurface::Validate(const WebGLContext* const webgl,
857 const webgl::PackingInfo& pi) {
858 if (!ValidatePIForDOM(webgl, pi)) return false;
860 const auto fullRows = mDesc.dataSurf->GetSize().height;
861 return ValidateUnpackPixels(webgl, fullRows, 0, this);
864 bool TexUnpackSurface::TexOrSubImage(bool isSubImage, bool needsRespec,
865 WebGLTexture* tex, GLint level,
866 const webgl::DriverUnpackInfo* dui,
867 GLint xOffset, GLint yOffset,
868 GLint zOffset,
869 const webgl::PackingInfo& dstPI,
870 GLenum* const out_error) const {
871 const auto& webgl = tex->mContext;
872 const auto& size = mDesc.size;
873 auto& surf = *(mDesc.dataSurf);
875 ////
877 const auto rowLength = surf.GetSize().width;
878 const auto rowCount = surf.GetSize().height;
880 const auto& dstBPP = webgl::BytesPerPixel(dstPI);
881 const auto dstFormat = FormatForPackingInfo(dstPI);
883 ////
885 WebGLTexelFormat srcFormat;
886 uint8_t srcBPP;
887 if (!GetFormatForSurf(&surf, &srcFormat, &srcBPP)) {
888 webgl->ErrorImplementationBug(
889 "GetFormatForSurf failed for"
890 " WebGLTexelFormat::%u.",
891 uint32_t(surf.GetFormat()));
892 return false;
895 gfx::DataSourceSurface::ScopedMap map(&surf,
896 gfx::DataSourceSurface::MapType::READ);
897 if (!map.IsMapped()) {
898 webgl->ErrorOutOfMemory("Failed to map source surface for upload.");
899 return false;
902 const auto& srcBegin = map.GetData();
903 const auto& srcStride = map.GetStride();
905 ////
907 const auto srcRowLengthBytes = rowLength * srcBPP;
909 const uint8_t maxGLAlignment = 8;
910 uint8_t srcAlignment = 1;
911 for (; srcAlignment <= maxGLAlignment; srcAlignment *= 2) {
912 const auto strideGuess =
913 RoundUpToMultipleOf(srcRowLengthBytes, srcAlignment);
914 if (strideGuess == srcStride) break;
916 const uint32_t dstAlignment =
917 (srcAlignment > maxGLAlignment) ? 1 : srcAlignment;
919 const auto dstRowLengthBytes = rowLength * dstBPP;
920 const auto dstStride = RoundUpToMultipleOf(dstRowLengthBytes, dstAlignment);
922 ////
924 const uint8_t* dstBegin = srcBegin;
925 UniqueBuffer tempBuffer;
926 if (!ConvertIfNeeded(webgl, rowLength, rowCount, srcFormat, srcBegin,
927 srcStride, dstFormat, dstStride, &dstBegin,
928 &tempBuffer)) {
929 return false;
932 ////
934 const auto& gl = webgl->gl;
935 if (!gl->MakeCurrent()) {
936 *out_error = LOCAL_GL_CONTEXT_LOST;
937 return true;
940 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, dstAlignment);
941 if (webgl->IsWebGL2()) {
942 gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, rowLength);
945 *out_error =
946 DoTexOrSubImage(isSubImage, gl, mDesc.imageTarget, level, dui, xOffset,
947 yOffset, zOffset, size.x, size.y, size.z, dstBegin);
949 // Caller will reset all our modified PixelStorei state.
951 return true;
954 } // namespace webgl
955 } // namespace mozilla