Bug 1850713: remove duplicated setting of early hint preloader id in `ScriptLoader...
[gecko.git] / dom / canvas / TexUnpackBlob.cpp
blob73746cca5e6438aa772949dd230b943572856407
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/layers/ImageDataSerializer.h"
14 #include "mozilla/RefPtr.h"
15 #include "nsLayoutUtils.h"
16 #include "WebGLBuffer.h"
17 #include "WebGLContext.h"
18 #include "WebGLFormats.h"
19 #include "WebGLTexelConversions.h"
20 #include "WebGLTexture.h"
22 namespace mozilla {
24 bool webgl::PixelPackingState::AssertCurrentUnpack(gl::GLContext& gl,
25 const bool isWebgl2) const {
26 auto actual = PixelPackingState{};
27 gl.GetInt(LOCAL_GL_UNPACK_ALIGNMENT, &actual.alignmentInTypeElems);
28 if (isWebgl2) {
29 gl.GetInt(LOCAL_GL_UNPACK_ROW_LENGTH, &actual.rowLength);
30 gl.GetInt(LOCAL_GL_UNPACK_IMAGE_HEIGHT, &actual.imageHeight);
32 gl.GetInt(LOCAL_GL_UNPACK_SKIP_PIXELS, &actual.skipPixels);
33 gl.GetInt(LOCAL_GL_UNPACK_SKIP_ROWS, &actual.skipRows);
34 gl.GetInt(LOCAL_GL_UNPACK_SKIP_IMAGES, &actual.skipImages);
36 if (*this == actual) return true;
38 const auto ToStr = [](const PixelPackingState& x) {
39 const auto text = nsPrintfCString(
40 "%u,%u,%u;%u,%u,%u", x.alignmentInTypeElems, x.rowLength, x.imageHeight,
41 x.skipPixels, x.skipRows, x.skipImages);
42 return mozilla::ToString(text);
45 const auto was = ToStr(actual);
46 const auto expected = ToStr(*this);
47 gfxCriticalError() << "PixelUnpackStateGl was not current. Was " << was
48 << ". Expected << " << expected << ".";
49 return false;
52 void webgl::PixelPackingState::ApplyUnpack(gl::GLContext& gl,
53 const bool isWebgl2,
54 const uvec3& uploadSize) const {
55 gl.fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT,
56 AssertedCast<GLsizei>(alignmentInTypeElems));
57 if (!isWebgl2) return;
59 // Re-simplify. (ANGLE seems to have an issue with imageHeight ==
60 // uploadSize.y)
61 auto rowLengthOrZero = rowLength;
62 auto imageHeightOrZero = imageHeight;
63 if (rowLengthOrZero == uploadSize.x) {
64 rowLengthOrZero = 0;
66 if (imageHeightOrZero == uploadSize.y) {
67 imageHeightOrZero = 0;
70 gl.fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH,
71 AssertedCast<GLsizei>(rowLengthOrZero));
72 gl.fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT,
73 AssertedCast<GLsizei>(imageHeightOrZero));
75 gl.fPixelStorei(LOCAL_GL_UNPACK_SKIP_PIXELS,
76 AssertedCast<GLsizei>(skipPixels));
77 gl.fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS, AssertedCast<GLsizei>(skipRows));
78 gl.fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES,
79 AssertedCast<GLsizei>(skipImages));
82 namespace webgl {
84 static bool IsPIValidForDOM(const webgl::PackingInfo& pi) {
85 // https://www.khronos.org/registry/webgl/specs/latest/2.0/#TEXTURE_TYPES_FORMATS_FROM_DOM_ELEMENTS_TABLE
87 // Just check for invalid individual formats and types, not combinations.
88 switch (pi.format) {
89 case LOCAL_GL_RGB:
90 case LOCAL_GL_RGBA:
91 case LOCAL_GL_LUMINANCE_ALPHA:
92 case LOCAL_GL_LUMINANCE:
93 case LOCAL_GL_ALPHA:
94 case LOCAL_GL_RED:
95 case LOCAL_GL_RED_INTEGER:
96 case LOCAL_GL_RG:
97 case LOCAL_GL_RG_INTEGER:
98 case LOCAL_GL_RGB_INTEGER:
99 case LOCAL_GL_RGBA_INTEGER:
100 break;
102 case LOCAL_GL_SRGB:
103 case LOCAL_GL_SRGB_ALPHA:
104 // Allowed in WebGL1+EXT_srgb
105 break;
107 default:
108 return false;
111 switch (pi.type) {
112 case LOCAL_GL_UNSIGNED_BYTE:
113 case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
114 case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
115 case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
116 case LOCAL_GL_HALF_FLOAT:
117 case LOCAL_GL_HALF_FLOAT_OES:
118 case LOCAL_GL_FLOAT:
119 case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV:
120 break;
122 default:
123 return false;
126 return true;
129 static bool ValidatePIForDOM(const WebGLContext* const webgl,
130 const webgl::PackingInfo& pi) {
131 if (!IsPIValidForDOM(pi)) {
132 webgl->ErrorInvalidValue("Format or type is invalid for DOM sources.");
133 return false;
135 return true;
138 static WebGLTexelFormat FormatForPackingInfo(const PackingInfo& pi) {
139 switch (pi.type) {
140 case LOCAL_GL_UNSIGNED_BYTE:
141 switch (pi.format) {
142 case LOCAL_GL_RED:
143 case LOCAL_GL_LUMINANCE:
144 case LOCAL_GL_RED_INTEGER:
145 return WebGLTexelFormat::R8;
147 case LOCAL_GL_ALPHA:
148 return WebGLTexelFormat::A8;
150 case LOCAL_GL_LUMINANCE_ALPHA:
151 return WebGLTexelFormat::RA8;
153 case LOCAL_GL_RGB:
154 case LOCAL_GL_RGB_INTEGER:
155 case LOCAL_GL_SRGB:
156 return WebGLTexelFormat::RGB8;
158 case LOCAL_GL_RGBA:
159 case LOCAL_GL_RGBA_INTEGER:
160 case LOCAL_GL_SRGB_ALPHA:
161 return WebGLTexelFormat::RGBA8;
163 case LOCAL_GL_RG:
164 case LOCAL_GL_RG_INTEGER:
165 return WebGLTexelFormat::RG8;
167 default:
168 break;
170 break;
172 case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
173 if (pi.format == LOCAL_GL_RGB) return WebGLTexelFormat::RGB565;
174 break;
176 case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
177 if (pi.format == LOCAL_GL_RGBA) return WebGLTexelFormat::RGBA5551;
178 break;
180 case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
181 if (pi.format == LOCAL_GL_RGBA) return WebGLTexelFormat::RGBA4444;
182 break;
184 case LOCAL_GL_HALF_FLOAT:
185 case LOCAL_GL_HALF_FLOAT_OES:
186 switch (pi.format) {
187 case LOCAL_GL_RED:
188 case LOCAL_GL_LUMINANCE:
189 return WebGLTexelFormat::R16F;
191 case LOCAL_GL_ALPHA:
192 return WebGLTexelFormat::A16F;
193 case LOCAL_GL_LUMINANCE_ALPHA:
194 return WebGLTexelFormat::RA16F;
195 case LOCAL_GL_RG:
196 return WebGLTexelFormat::RG16F;
197 case LOCAL_GL_RGB:
198 return WebGLTexelFormat::RGB16F;
199 case LOCAL_GL_RGBA:
200 return WebGLTexelFormat::RGBA16F;
202 default:
203 break;
205 break;
207 case LOCAL_GL_FLOAT:
208 switch (pi.format) {
209 case LOCAL_GL_RED:
210 case LOCAL_GL_LUMINANCE:
211 return WebGLTexelFormat::R32F;
213 case LOCAL_GL_ALPHA:
214 return WebGLTexelFormat::A32F;
215 case LOCAL_GL_LUMINANCE_ALPHA:
216 return WebGLTexelFormat::RA32F;
217 case LOCAL_GL_RG:
218 return WebGLTexelFormat::RG32F;
219 case LOCAL_GL_RGB:
220 return WebGLTexelFormat::RGB32F;
221 case LOCAL_GL_RGBA:
222 return WebGLTexelFormat::RGBA32F;
224 default:
225 break;
227 break;
229 case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV:
230 if (pi.format == LOCAL_GL_RGB) return WebGLTexelFormat::RGB11F11F10F;
231 break;
233 default:
234 break;
237 return WebGLTexelFormat::FormatNotSupportingAnyConversion;
240 ////////////////////
242 static uint32_t ZeroOn2D(const GLenum target, const uint32_t val) {
243 const bool is2d = !IsTexTarget3D(target);
244 if (is2d) return 0;
245 return val;
248 static bool ValidateUnpackPixels(const WebGLContext* webgl,
249 const webgl::PackingInfo& pi,
250 const uint32_t availRows,
251 const webgl::TexUnpackBlob& blob) {
252 const auto& unpackingRes = blob.mDesc.ExplicitUnpacking(pi, {});
253 if (!unpackingRes.isOk()) {
254 webgl->ErrorInvalidOperation("%s", unpackingRes.inspectErr().c_str());
255 return false;
257 const auto& unpacking = unpackingRes.inspect();
259 if (availRows < unpacking.metrics.totalRows) {
260 webgl->ErrorInvalidOperation(
261 "Desired upload requires more rows (%zu) than is"
262 " available (%zu).",
263 unpacking.metrics.totalRows, availRows);
264 return false;
267 return true;
270 static bool ValidateUnpackBytes(const WebGLContext* const webgl,
271 const webgl::PackingInfo& pi,
272 const size_t availByteCount,
273 const webgl::TexUnpackBlob& blob) {
274 const auto& unpackingRes = blob.mDesc.ExplicitUnpacking(pi, {});
275 if (!unpackingRes.isOk()) {
276 webgl->ErrorInvalidOperation("%s", unpackingRes.inspectErr().c_str());
277 return false;
279 const auto& unpacking = unpackingRes.inspect();
281 if (availByteCount < unpacking.metrics.totalBytesUsed) {
282 webgl->ErrorInvalidOperation(
283 "Desired upload requires more bytes (%zu) than are"
284 " available (%zu).",
285 unpacking.metrics.totalBytesUsed, availByteCount);
286 return false;
289 return true;
292 ////////////////////
294 // Check if the surface descriptor describes a memory which contains a single
295 // RGBA data source.
296 static bool SDIsRGBBuffer(const layers::SurfaceDescriptor& sd) {
297 return sd.type() == layers::SurfaceDescriptor::TSurfaceDescriptorBuffer &&
298 sd.get_SurfaceDescriptorBuffer().desc().type() ==
299 layers::BufferDescriptor::TRGBDescriptor;
302 // static
303 std::unique_ptr<TexUnpackBlob> TexUnpackBlob::Create(
304 const TexUnpackBlobDesc& desc) {
305 return std::unique_ptr<TexUnpackBlob>{[&]() -> TexUnpackBlob* {
306 if (!IsTarget3D(desc.imageTarget) && desc.size.z != 1) {
307 MOZ_ASSERT(false);
308 return nullptr;
311 switch (desc.unpacking.alignmentInTypeElems) {
312 case 1:
313 case 2:
314 case 4:
315 case 8:
316 break;
317 default:
318 MOZ_ASSERT(false);
319 return nullptr;
322 if (desc.sd) {
323 // Shmem buffers need to be treated as if they were a DataSourceSurface.
324 // Otherwise, TexUnpackImage will try to blit the surface descriptor as
325 // if it can be mapped as a framebuffer, whereas the Shmem is still CPU
326 // data.
327 if (SDIsRGBBuffer(*desc.sd)) return new TexUnpackSurface(desc);
328 return new TexUnpackImage(desc);
330 if (desc.dataSurf) {
331 return new TexUnpackSurface(desc);
334 if (desc.srcAlphaType != gfxAlphaType::NonPremult) {
335 MOZ_ASSERT(false);
336 return nullptr;
338 return new TexUnpackBytes(desc);
339 }()};
342 static bool HasColorAndAlpha(const WebGLTexelFormat format) {
343 switch (format) {
344 case WebGLTexelFormat::RA8:
345 case WebGLTexelFormat::RA16F:
346 case WebGLTexelFormat::RA32F:
347 case WebGLTexelFormat::RGBA8:
348 case WebGLTexelFormat::RGBA5551:
349 case WebGLTexelFormat::RGBA4444:
350 case WebGLTexelFormat::RGBA16F:
351 case WebGLTexelFormat::RGBA32F:
352 case WebGLTexelFormat::BGRA8:
353 return true;
354 default:
355 return false;
359 bool TexUnpackBlob::ConvertIfNeeded(
360 const WebGLContext* const webgl, const uint32_t rowLength,
361 const uint32_t rowCount, WebGLTexelFormat srcFormat,
362 const uint8_t* const srcBegin, const ptrdiff_t srcStride,
363 WebGLTexelFormat dstFormat, const ptrdiff_t dstStride,
364 const uint8_t** const out_begin,
365 UniqueBuffer* const out_anchoredBuffer) const {
366 MOZ_ASSERT(srcFormat != WebGLTexelFormat::FormatNotSupportingAnyConversion);
367 MOZ_ASSERT(dstFormat != WebGLTexelFormat::FormatNotSupportingAnyConversion);
369 *out_begin = srcBegin;
371 const auto& unpacking = mDesc.unpacking;
373 if (!rowLength || !rowCount) return true;
375 const auto srcIsPremult = (mDesc.srcAlphaType == gfxAlphaType::Premult);
376 auto dstIsPremult = unpacking.premultiplyAlpha;
377 const auto fnHasPremultMismatch = [&]() {
378 if (mDesc.srcAlphaType == gfxAlphaType::Opaque) return false;
380 if (!HasColorAndAlpha(srcFormat)) return false;
382 return srcIsPremult != dstIsPremult;
385 const auto srcOrigin =
386 (unpacking.flipY ? gl::OriginPos::TopLeft : gl::OriginPos::BottomLeft);
387 auto dstOrigin = gl::OriginPos::BottomLeft;
389 if (!mDesc.applyUnpackTransforms) {
390 dstIsPremult = srcIsPremult;
391 dstOrigin = srcOrigin;
394 if (srcFormat != dstFormat) {
395 webgl->GeneratePerfWarning(
396 "Conversion requires pixel reformatting. (%u->%u)", uint32_t(srcFormat),
397 uint32_t(dstFormat));
398 } else if (fnHasPremultMismatch()) {
399 webgl->GeneratePerfWarning(
400 "Conversion requires change in"
401 " alpha-premultiplication.");
402 } else if (srcOrigin != dstOrigin) {
403 webgl->GeneratePerfWarning("Conversion requires y-flip.");
404 } else if (srcStride != dstStride) {
405 webgl->GeneratePerfWarning("Conversion requires change in stride. (%u->%u)",
406 uint32_t(srcStride), uint32_t(dstStride));
407 } else {
408 return true;
411 ////
413 const auto dstTotalBytes = CheckedUint32(rowCount) * dstStride;
414 if (!dstTotalBytes.isValid()) {
415 webgl->ErrorOutOfMemory("Calculation failed.");
416 return false;
419 auto dstBuffer = UniqueBuffer::Take(calloc(1u, dstTotalBytes.value()));
420 if (!dstBuffer.get()) {
421 webgl->ErrorOutOfMemory("Failed to allocate dest buffer.");
422 return false;
424 const auto dstBegin = static_cast<uint8_t*>(dstBuffer.get());
426 ////
428 // And go!:
429 bool wasTrivial;
430 if (!ConvertImage(rowLength, rowCount, srcBegin, srcStride, srcOrigin,
431 srcFormat, srcIsPremult, dstBegin, dstStride, dstOrigin,
432 dstFormat, dstIsPremult, &wasTrivial)) {
433 webgl->ErrorImplementationBug("ConvertImage failed.");
434 return false;
437 *out_begin = dstBegin;
438 *out_anchoredBuffer = std::move(dstBuffer);
439 return true;
442 static GLenum DoTexOrSubImage(bool isSubImage, gl::GLContext* gl,
443 TexImageTarget target, GLint level,
444 const DriverUnpackInfo* dui, GLint xOffset,
445 GLint yOffset, GLint zOffset, GLsizei width,
446 GLsizei height, GLsizei depth, const void* data) {
447 if (isSubImage) {
448 return DoTexSubImage(gl, target, level, xOffset, yOffset, zOffset, width,
449 height, depth, dui->ToPacking(), data);
450 } else {
451 return DoTexImage(gl, target, level, dui, width, height, depth, data);
455 //////////////////////////////////////////////////////////////////////////////////////////
456 // TexUnpackBytes
458 bool TexUnpackBytes::Validate(const WebGLContext* const webgl,
459 const webgl::PackingInfo& pi) {
460 if (!HasData()) return true;
462 CheckedInt<size_t> availBytes = 0;
463 if (mDesc.cpuData) {
464 const auto& range = mDesc.cpuData->Data();
465 availBytes = range.length();
466 } else if (mDesc.pboOffset) {
467 const auto& pboOffset = *mDesc.pboOffset;
469 const auto& pbo =
470 webgl->ValidateBufferSelection(LOCAL_GL_PIXEL_UNPACK_BUFFER);
471 if (!pbo) return false; // Might be invalid e.g. due to in-use by TF.
472 availBytes = pbo->ByteLength();
473 availBytes -= pboOffset;
474 } else {
475 MOZ_ASSERT(false, "Must be one of the above");
477 if (!availBytes.isValid()) {
478 webgl->ErrorInvalidOperation("Offset is passed end of buffer.");
479 return false;
482 return ValidateUnpackBytes(webgl, pi, availBytes.value(), *this);
485 bool TexUnpackBytes::TexOrSubImage(bool isSubImage, bool needsRespec,
486 WebGLTexture* tex, GLint level,
487 const webgl::DriverUnpackInfo* dui,
488 GLint xOffset, GLint yOffset, GLint zOffset,
489 const webgl::PackingInfo& pi,
490 GLenum* const out_error) const {
491 const auto& webgl = tex->mContext;
492 const auto& target = mDesc.imageTarget;
493 const auto& size = mDesc.size;
494 const auto& webglUnpackState = mDesc.unpacking;
496 const auto unpackingRes = mDesc.ExplicitUnpacking(pi, {});
498 const auto format = FormatForPackingInfo(pi);
500 const uint8_t* uploadPtr = nullptr;
501 if (mDesc.cpuData) {
502 const auto range = mDesc.cpuData->Data();
503 uploadPtr = range.begin().get();
504 if (!uploadPtr) {
505 MOZ_ASSERT(!range.length());
507 } else if (mDesc.pboOffset) {
508 uploadPtr = reinterpret_cast<const uint8_t*>(*mDesc.pboOffset);
511 UniqueBuffer tempBuffer;
513 do {
514 if (mDesc.pboOffset || !uploadPtr) break;
516 if (!webglUnpackState.flipY && !webglUnpackState.premultiplyAlpha) {
517 break;
520 webgl->GenerateWarning(
521 "Alpha-premult and y-flip are deprecated for"
522 " non-DOM-Element uploads.");
524 MOZ_RELEASE_ASSERT(unpackingRes.isOk());
525 const auto& unpacking = unpackingRes.inspect();
526 const auto stride = unpacking.metrics.bytesPerRowStride;
527 // clang-format off
528 if (!ConvertIfNeeded(webgl, unpacking.state.rowLength,
529 unpacking.metrics.totalRows,
530 format, uploadPtr, AutoAssertCast(stride),
531 format, AutoAssertCast(stride), &uploadPtr, &tempBuffer)) {
532 return false;
534 // clang-format on
535 } while (false);
537 //////
539 const auto& gl = webgl->gl;
541 bool useParanoidHandling = false;
542 if (mNeedsExactUpload && webgl->mBoundPixelUnpackBuffer) {
543 webgl->GenerateWarning(
544 "Uploads from a buffer with a final row with a byte"
545 " count smaller than the row stride can incur extra"
546 " overhead.");
548 if (gl->WorkAroundDriverBugs()) {
549 useParanoidHandling |= (gl->Vendor() == gl::GLVendor::NVIDIA);
553 if (!useParanoidHandling) {
554 const ScopedLazyBind bindPBO(gl, LOCAL_GL_PIXEL_UNPACK_BUFFER,
555 webgl->mBoundPixelUnpackBuffer);
557 *out_error =
558 DoTexOrSubImage(isSubImage, gl, target, level, dui, xOffset, yOffset,
559 zOffset, size.x, size.y, size.z, uploadPtr);
560 return true;
563 //////
565 MOZ_ASSERT(webgl->mBoundPixelUnpackBuffer);
567 if (!isSubImage) {
568 // Alloc first to catch OOMs.
569 AssertUintParamCorrect(gl, LOCAL_GL_PIXEL_UNPACK_BUFFER_BINDING, 0);
570 *out_error =
571 DoTexOrSubImage(false, gl, target, level, dui, xOffset, yOffset,
572 zOffset, size.x, size.y, size.z, nullptr);
573 if (*out_error) return true;
575 if (!size.x || !size.y || !size.z) {
576 // Nothing to do.
577 return true;
580 MOZ_RELEASE_ASSERT(unpackingRes.isOk());
581 const auto& unpacking = unpackingRes.inspect();
583 const ScopedLazyBind bindPBO(gl, LOCAL_GL_PIXEL_UNPACK_BUFFER,
584 webgl->mBoundPixelUnpackBuffer);
586 //////
588 // Make our sometimes-implicit values explicit. Also this keeps them constant
589 // when we ask for height=mHeight-1 and such.
590 gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH,
591 AutoAssertCast(unpacking.state.rowLength));
592 gl->fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT,
593 AutoAssertCast(unpacking.state.imageHeight));
595 if (size.z > 1) {
596 *out_error =
597 DoTexOrSubImage(true, gl, target, level, dui, xOffset, yOffset, zOffset,
598 size.x, size.y, size.z - 1, uploadPtr);
601 // Skip the images we uploaded.
602 const auto skipImages = ZeroOn2D(target, unpacking.state.skipImages);
603 gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, skipImages + size.z - 1);
605 if (size.y > 1) {
606 *out_error =
607 DoTexOrSubImage(true, gl, target, level, dui, xOffset, yOffset,
608 zOffset + size.z - 1, size.x, size.y - 1, 1, uploadPtr);
611 // -
613 const auto lastRowOffset =
614 unpacking.metrics.totalBytesStrided - unpacking.metrics.bytesPerRowStride;
615 const auto lastRowPtr = uploadPtr + lastRowOffset;
617 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1); // No stride padding.
618 gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, 0); // No padding in general.
619 gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, 0); // Don't skip images,
620 gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS,
621 0); // or rows.
622 // Keep skipping pixels though!
623 *out_error = DoTexOrSubImage(true, gl, target, level, dui, xOffset,
624 yOffset + size.y - 1, zOffset + size.z - 1,
625 AutoAssertCast(size.x), 1, 1, lastRowPtr);
627 // Caller will reset all our modified PixelStorei state.
629 return true;
632 ////////////////////////////////////////////////////////////////////////////////
633 ////////////////////////////////////////////////////////////////////////////////
634 // TexUnpackImage
636 TexUnpackImage::~TexUnpackImage() = default;
638 bool TexUnpackImage::Validate(const WebGLContext* const webgl,
639 const webgl::PackingInfo& pi) {
640 if (!ValidatePIForDOM(webgl, pi)) return false;
642 if (!mDesc.structuredSrcSize) {
643 gfxCriticalError() << "TexUnpackImage missing structuredSrcSize.";
644 return false;
646 const auto& elemSize = *mDesc.structuredSrcSize;
647 if (mDesc.dataSurf) {
648 const auto& surfSize = mDesc.dataSurf->GetSize();
649 const auto surfSize2 = ivec2::FromSize(surfSize)->StaticCast<uvec2>();
650 if (uvec2{elemSize.x, elemSize.y} != surfSize2) {
651 gfxCriticalError()
652 << "TexUnpackImage mismatched structuredSrcSize for dataSurf.";
653 return false;
657 const auto fullRows = elemSize.y;
658 return ValidateUnpackPixels(webgl, pi, fullRows, *this);
661 Maybe<std::string> BlitPreventReason(const int32_t level, const ivec3& offset,
662 const webgl::PackingInfo& pi,
663 const TexUnpackBlobDesc& desc) {
664 const auto& size = desc.size;
665 const auto& unpacking = desc.unpacking;
667 const auto ret = [&]() -> const char* {
668 if (size.z != 1) {
669 return "depth is not 1";
671 if (offset.x != 0 || offset.y != 0 || offset.z != 0) {
672 return "x/y/zOffset is not 0";
675 if (unpacking.skipPixels || unpacking.skipRows || unpacking.skipImages) {
676 return "non-zero UNPACK_SKIP_* not yet supported";
679 const auto premultReason = [&]() -> const char* {
680 if (desc.srcAlphaType == gfxAlphaType::Opaque) return nullptr;
682 const bool srcIsPremult = (desc.srcAlphaType == gfxAlphaType::Premult);
683 const auto& dstIsPremult = unpacking.premultiplyAlpha;
684 if (srcIsPremult == dstIsPremult) return nullptr;
686 if (dstIsPremult) {
687 return "UNPACK_PREMULTIPLY_ALPHA_WEBGL is not true";
688 } else {
689 return "UNPACK_PREMULTIPLY_ALPHA_WEBGL is not false";
691 }();
692 if (premultReason) return premultReason;
694 if (pi.format != LOCAL_GL_RGBA) {
695 return "`format` is not RGBA";
698 if (pi.type != LOCAL_GL_UNSIGNED_BYTE) {
699 return "`type` is not UNSIGNED_BYTE";
701 return nullptr;
702 }();
703 if (ret) {
704 return Some(std::string(ret));
706 return {};
709 bool TexUnpackImage::TexOrSubImage(bool isSubImage, bool needsRespec,
710 WebGLTexture* tex, GLint level,
711 const webgl::DriverUnpackInfo* dui,
712 GLint xOffset, GLint yOffset, GLint zOffset,
713 const webgl::PackingInfo& pi,
714 GLenum* const out_error) const {
715 MOZ_ASSERT_IF(needsRespec, !isSubImage);
717 const auto& webgl = tex->mContext;
718 const auto& target = mDesc.imageTarget;
719 const auto& size = mDesc.size;
720 const auto& sd = *(mDesc.sd);
721 const auto& unpacking = mDesc.unpacking;
723 const auto& gl = webgl->GL();
725 // -
727 const auto reason =
728 BlitPreventReason(level, {xOffset, yOffset, zOffset}, pi, mDesc);
729 if (reason) {
730 webgl->GeneratePerfWarning(
731 "Failed to hit GPU-copy fast-path."
732 " (%s) Falling back to CPU upload.",
733 reason->c_str());
734 return false;
737 // -
739 if (needsRespec) {
740 *out_error =
741 DoTexOrSubImage(isSubImage, gl, target, level, dui, xOffset, yOffset,
742 zOffset, size.x, size.y, size.z, nullptr);
743 if (*out_error) return true;
747 gl::ScopedFramebuffer scopedFB(gl);
748 gl::ScopedBindFramebuffer bindFB(gl, scopedFB.FB());
751 gl::GLContext::LocalErrorScope errorScope(*gl);
753 gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER,
754 LOCAL_GL_COLOR_ATTACHMENT0, target,
755 tex->mGLName, level);
757 const auto err = errorScope.GetError();
758 MOZ_ALWAYS_TRUE(!err);
761 const GLenum status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
762 MOZ_ALWAYS_TRUE(status == LOCAL_GL_FRAMEBUFFER_COMPLETE);
764 const auto dstOrigin =
765 (unpacking.flipY ? gl::OriginPos::TopLeft : gl::OriginPos::BottomLeft);
766 if (!gl->BlitHelper()->BlitSdToFramebuffer(sd, {size.x, size.y},
767 dstOrigin)) {
768 gfxCriticalNote << "BlitSdToFramebuffer failed for type "
769 << int(sd.type());
770 // Maybe the resource isn't valid anymore?
771 gl->fClearColor(0.2, 0.0, 0.2, 1.0);
772 gl->fClear(LOCAL_GL_COLOR_BUFFER_BIT);
773 const auto& cur = webgl->mColorClearValue;
774 gl->fClearColor(cur[0], cur[1], cur[2], cur[3]);
775 webgl->GenerateWarning(
776 "Fast Tex(Sub)Image upload failed without recourse, clearing to "
777 "[0.2, 0.0, 0.2, 1.0]. Please file a bug!");
781 return true;
784 ////////////////////////////////////////////////////////////////////////////////
785 ////////////////////////////////////////////////////////////////////////////////
786 // TexUnpackSurface
788 TexUnpackSurface::~TexUnpackSurface() = default;
790 //////////
792 static bool GetFormatForSurf(const gfx::SourceSurface* surf,
793 WebGLTexelFormat* const out_texelFormat,
794 uint8_t* const out_bpp) {
795 const auto surfFormat = surf->GetFormat();
796 switch (surfFormat) {
797 case gfx::SurfaceFormat::B8G8R8A8:
798 *out_texelFormat = WebGLTexelFormat::BGRA8;
799 *out_bpp = 4;
800 return true;
802 case gfx::SurfaceFormat::B8G8R8X8:
803 *out_texelFormat = WebGLTexelFormat::BGRX8;
804 *out_bpp = 4;
805 return true;
807 case gfx::SurfaceFormat::R8G8B8A8:
808 *out_texelFormat = WebGLTexelFormat::RGBA8;
809 *out_bpp = 4;
810 return true;
812 case gfx::SurfaceFormat::R8G8B8X8:
813 *out_texelFormat = WebGLTexelFormat::RGBX8;
814 *out_bpp = 4;
815 return true;
817 case gfx::SurfaceFormat::R5G6B5_UINT16:
818 *out_texelFormat = WebGLTexelFormat::RGB565;
819 *out_bpp = 2;
820 return true;
822 case gfx::SurfaceFormat::A8:
823 *out_texelFormat = WebGLTexelFormat::A8;
824 *out_bpp = 1;
825 return true;
827 case gfx::SurfaceFormat::YUV:
828 // Ugh...
829 NS_ERROR("We don't handle uploads from YUV sources yet.");
830 // When we want to, check out gfx/ycbcr/YCbCrUtils.h. (specifically
831 // GetYCbCrToRGBDestFormatAndSize and ConvertYCbCrToRGB)
832 return false;
834 default:
835 return false;
839 //////////
841 bool TexUnpackSurface::Validate(const WebGLContext* const webgl,
842 const webgl::PackingInfo& pi) {
843 if (!ValidatePIForDOM(webgl, pi)) return false;
845 if (!mDesc.structuredSrcSize) {
846 gfxCriticalError() << "TexUnpackSurface missing structuredSrcSize.";
847 return false;
849 const auto& elemSize = *mDesc.structuredSrcSize;
850 if (mDesc.dataSurf) {
851 const auto& surfSize = mDesc.dataSurf->GetSize();
852 const auto surfSize2 = ivec2::FromSize(surfSize)->StaticCast<uvec2>();
853 if (uvec2{elemSize.x, elemSize.y} != surfSize2) {
854 gfxCriticalError()
855 << "TexUnpackSurface mismatched structuredSrcSize for dataSurf.";
856 return false;
860 const auto fullRows = elemSize.y;
861 return ValidateUnpackPixels(webgl, pi, fullRows, *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 RefPtr<gfx::DataSourceSurface> surf;
874 if (mDesc.sd) {
875 // If we get here, we assume the SD describes an RGBA Shmem.
876 const auto& sd = *(mDesc.sd);
877 MOZ_ASSERT(SDIsRGBBuffer(sd));
878 const auto& sdb = sd.get_SurfaceDescriptorBuffer();
879 const auto& rgb = sdb.desc().get_RGBDescriptor();
880 const auto& data = sdb.data();
881 MOZ_ASSERT(data.type() == layers::MemoryOrShmem::TShmem);
882 const auto& shmem = data.get_Shmem();
883 surf = gfx::Factory::CreateWrappingDataSourceSurface(
884 shmem.get<uint8_t>(), layers::ImageDataSerializer::GetRGBStride(rgb),
885 rgb.size(), rgb.format());
886 if (!surf) {
887 gfxCriticalError() << "TexUnpackSurface failed to create wrapping "
888 "DataSourceSurface for Shmem.";
889 return false;
891 } else {
892 surf = mDesc.dataSurf;
895 ////
897 WebGLTexelFormat srcFormat;
898 uint8_t srcBPP;
899 if (!GetFormatForSurf(surf, &srcFormat, &srcBPP)) {
900 webgl->ErrorImplementationBug(
901 "GetFormatForSurf failed for"
902 " WebGLTexelFormat::%u.",
903 uint32_t(surf->GetFormat()));
904 return false;
907 gfx::DataSourceSurface::ScopedMap map(surf,
908 gfx::DataSourceSurface::MapType::READ);
909 if (!map.IsMapped()) {
910 webgl->ErrorOutOfMemory("Failed to map source surface for upload.");
911 return false;
914 const auto& srcBegin = map.GetData();
915 const auto srcStride = static_cast<size_t>(map.GetStride());
917 // -
919 const auto dstFormat = FormatForPackingInfo(dstPI);
920 const auto dstBpp = BytesPerPixel(dstPI);
921 const size_t dstUsedBytesPerRow = dstBpp * surf->GetSize().width;
922 auto dstStride = dstUsedBytesPerRow;
923 if (dstFormat == srcFormat) {
924 dstStride = srcStride; // Try to match.
927 // -
929 auto dstUnpackingRes = mDesc.ExplicitUnpacking(dstPI, Some(dstStride));
930 if (dstUnpackingRes.isOk()) {
931 const auto& dstUnpacking = dstUnpackingRes.inspect();
932 if (!webgl->IsWebGL2() && dstUnpacking.state.rowLength != size.x) {
933 dstUnpackingRes = Err("WebGL1 can't handle rowLength != size.x");
936 if (!dstUnpackingRes.isOk()) {
937 dstStride = dstUsedBytesPerRow;
938 dstUnpackingRes = mDesc.ExplicitUnpacking(dstPI, Some(dstStride));
940 if (!dstUnpackingRes.isOk()) {
941 gfxCriticalError() << dstUnpackingRes.inspectErr();
942 webgl->ErrorImplementationBug("ExplicitUnpacking failed: %s",
943 dstUnpackingRes.inspectErr().c_str());
944 return false;
946 const auto& dstUnpacking = dstUnpackingRes.inspect();
947 MOZ_ASSERT(dstUnpacking.metrics.bytesPerRowStride == dstStride);
949 // -
951 const uint8_t* dstBegin = srcBegin;
952 UniqueBuffer tempBuffer;
953 // clang-format off
954 if (!ConvertIfNeeded(webgl, surf->GetSize().width, surf->GetSize().height,
955 srcFormat, srcBegin, AutoAssertCast(srcStride),
956 dstFormat, AutoAssertCast(dstUnpacking.metrics.bytesPerRowStride), &dstBegin,
957 &tempBuffer)) {
958 return false;
960 // clang-format on
962 ////
964 const auto& gl = webgl->gl;
965 if (!gl->MakeCurrent()) {
966 *out_error = LOCAL_GL_CONTEXT_LOST;
967 return true;
970 dstUnpacking.state.ApplyUnpack(*gl, webgl->IsWebGL2(), size);
972 *out_error =
973 DoTexOrSubImage(isSubImage, gl, mDesc.imageTarget, level, dui, xOffset,
974 yOffset, zOffset, size.x, size.y, size.z, dstBegin);
976 // Caller will reset all our modified PixelStorei state.
978 return true;
981 } // namespace webgl
982 } // namespace mozilla