Bug 1941128 - Turn off network.dns.native_https_query on Mac again
[gecko.git] / dom / canvas / TexUnpackBlob.cpp
blobc3eadcdb3a6be7f11bdd230b55e42dfd61e9e39a
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/layers/SharedSurfacesParent.h"
15 #include "mozilla/layers/TextureHost.h"
16 #include "mozilla/layers/VideoBridgeParent.h"
17 #include "mozilla/RefPtr.h"
18 #include "nsLayoutUtils.h"
19 #include "WebGLBuffer.h"
20 #include "WebGLContext.h"
21 #include "WebGLFormats.h"
22 #include "WebGLTexelConversions.h"
23 #include "WebGLTexture.h"
25 namespace mozilla {
27 bool webgl::PixelPackingState::AssertCurrentUnpack(gl::GLContext& gl,
28 const bool isWebgl2) const {
29 if (!kIsDebug) return true;
31 auto actual = PixelPackingState{};
32 gl.GetInt(LOCAL_GL_UNPACK_ALIGNMENT, &actual.alignmentInTypeElems);
33 if (isWebgl2) {
34 gl.GetInt(LOCAL_GL_UNPACK_ROW_LENGTH, &actual.rowLength);
35 gl.GetInt(LOCAL_GL_UNPACK_IMAGE_HEIGHT, &actual.imageHeight);
37 gl.GetInt(LOCAL_GL_UNPACK_SKIP_PIXELS, &actual.skipPixels);
38 gl.GetInt(LOCAL_GL_UNPACK_SKIP_ROWS, &actual.skipRows);
39 gl.GetInt(LOCAL_GL_UNPACK_SKIP_IMAGES, &actual.skipImages);
41 if (*this == actual) return true;
43 const auto ToStr = [](const PixelPackingState& x) {
44 const auto text = nsPrintfCString(
45 "%u,%u,%u;%u,%u,%u", x.alignmentInTypeElems, x.rowLength, x.imageHeight,
46 x.skipPixels, x.skipRows, x.skipImages);
47 return mozilla::ToString(text);
50 const auto was = ToStr(actual);
51 const auto expected = ToStr(*this);
52 gfxCriticalError() << "PixelUnpackStateGl was not current. Was " << was
53 << ". Expected << " << expected << ".";
54 return false;
57 void webgl::PixelPackingState::ApplyUnpack(gl::GLContext& gl,
58 const bool isWebgl2,
59 const uvec3& uploadSize) const {
60 gl.fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT,
61 AssertedCast<GLsizei>(alignmentInTypeElems));
62 if (!isWebgl2) return;
64 // Re-simplify. (ANGLE seems to have an issue with imageHeight ==
65 // uploadSize.y)
66 auto rowLengthOrZero = rowLength;
67 auto imageHeightOrZero = imageHeight;
68 if (rowLengthOrZero == uploadSize.x) {
69 rowLengthOrZero = 0;
71 if (imageHeightOrZero == uploadSize.y) {
72 imageHeightOrZero = 0;
75 gl.fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH,
76 AssertedCast<GLsizei>(rowLengthOrZero));
77 gl.fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT,
78 AssertedCast<GLsizei>(imageHeightOrZero));
80 gl.fPixelStorei(LOCAL_GL_UNPACK_SKIP_PIXELS,
81 AssertedCast<GLsizei>(skipPixels));
82 gl.fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS, AssertedCast<GLsizei>(skipRows));
83 gl.fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES,
84 AssertedCast<GLsizei>(skipImages));
87 namespace webgl {
89 static bool IsPIValidForDOM(const webgl::PackingInfo& pi) {
90 // https://www.khronos.org/registry/webgl/specs/latest/2.0/#TEXTURE_TYPES_FORMATS_FROM_DOM_ELEMENTS_TABLE
92 // Just check for invalid individual formats and types, not combinations.
93 switch (pi.format) {
94 case LOCAL_GL_RGB:
95 case LOCAL_GL_RGBA:
96 case LOCAL_GL_LUMINANCE_ALPHA:
97 case LOCAL_GL_LUMINANCE:
98 case LOCAL_GL_ALPHA:
99 case LOCAL_GL_RED:
100 case LOCAL_GL_RED_INTEGER:
101 case LOCAL_GL_RG:
102 case LOCAL_GL_RG_INTEGER:
103 case LOCAL_GL_RGB_INTEGER:
104 case LOCAL_GL_RGBA_INTEGER:
105 break;
107 case LOCAL_GL_SRGB:
108 case LOCAL_GL_SRGB_ALPHA:
109 // Allowed in WebGL1+EXT_srgb
110 break;
112 default:
113 return false;
116 switch (pi.type) {
117 case LOCAL_GL_UNSIGNED_BYTE:
118 case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
119 case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
120 case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
121 case LOCAL_GL_HALF_FLOAT:
122 case LOCAL_GL_HALF_FLOAT_OES:
123 case LOCAL_GL_FLOAT:
124 case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV:
125 break;
127 default:
128 return false;
131 return true;
134 static bool ValidatePIForDOM(const WebGLContext* const webgl,
135 const webgl::PackingInfo& pi) {
136 if (!IsPIValidForDOM(pi)) {
137 webgl->ErrorInvalidValue("Format or type is invalid for DOM sources.");
138 return false;
140 return true;
143 static WebGLTexelFormat FormatForPackingInfo(const PackingInfo& pi) {
144 switch (pi.type) {
145 case LOCAL_GL_UNSIGNED_BYTE:
146 switch (pi.format) {
147 case LOCAL_GL_RED:
148 case LOCAL_GL_LUMINANCE:
149 case LOCAL_GL_RED_INTEGER:
150 return WebGLTexelFormat::R8;
152 case LOCAL_GL_ALPHA:
153 return WebGLTexelFormat::A8;
155 case LOCAL_GL_LUMINANCE_ALPHA:
156 return WebGLTexelFormat::RA8;
158 case LOCAL_GL_RGB:
159 case LOCAL_GL_RGB_INTEGER:
160 case LOCAL_GL_SRGB:
161 return WebGLTexelFormat::RGB8;
163 case LOCAL_GL_RGBA:
164 case LOCAL_GL_RGBA_INTEGER:
165 case LOCAL_GL_SRGB_ALPHA:
166 return WebGLTexelFormat::RGBA8;
168 case LOCAL_GL_RG:
169 case LOCAL_GL_RG_INTEGER:
170 return WebGLTexelFormat::RG8;
172 default:
173 break;
175 break;
177 case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
178 if (pi.format == LOCAL_GL_RGB) return WebGLTexelFormat::RGB565;
179 break;
181 case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
182 if (pi.format == LOCAL_GL_RGBA) return WebGLTexelFormat::RGBA5551;
183 break;
185 case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
186 if (pi.format == LOCAL_GL_RGBA) return WebGLTexelFormat::RGBA4444;
187 break;
189 case LOCAL_GL_HALF_FLOAT:
190 case LOCAL_GL_HALF_FLOAT_OES:
191 switch (pi.format) {
192 case LOCAL_GL_RED:
193 case LOCAL_GL_LUMINANCE:
194 return WebGLTexelFormat::R16F;
196 case LOCAL_GL_ALPHA:
197 return WebGLTexelFormat::A16F;
198 case LOCAL_GL_LUMINANCE_ALPHA:
199 return WebGLTexelFormat::RA16F;
200 case LOCAL_GL_RG:
201 return WebGLTexelFormat::RG16F;
202 case LOCAL_GL_RGB:
203 return WebGLTexelFormat::RGB16F;
204 case LOCAL_GL_RGBA:
205 return WebGLTexelFormat::RGBA16F;
207 default:
208 break;
210 break;
212 case LOCAL_GL_FLOAT:
213 switch (pi.format) {
214 case LOCAL_GL_RED:
215 case LOCAL_GL_LUMINANCE:
216 return WebGLTexelFormat::R32F;
218 case LOCAL_GL_ALPHA:
219 return WebGLTexelFormat::A32F;
220 case LOCAL_GL_LUMINANCE_ALPHA:
221 return WebGLTexelFormat::RA32F;
222 case LOCAL_GL_RG:
223 return WebGLTexelFormat::RG32F;
224 case LOCAL_GL_RGB:
225 return WebGLTexelFormat::RGB32F;
226 case LOCAL_GL_RGBA:
227 return WebGLTexelFormat::RGBA32F;
229 default:
230 break;
232 break;
234 case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV:
235 if (pi.format == LOCAL_GL_RGB) return WebGLTexelFormat::RGB11F11F10F;
236 break;
238 default:
239 break;
242 return WebGLTexelFormat::FormatNotSupportingAnyConversion;
245 ////////////////////
247 static uint32_t ZeroOn2D(const GLenum target, const uint32_t val) {
248 const bool is2d = !IsTexTarget3D(target);
249 if (is2d) return 0;
250 return val;
253 static bool ValidateUnpackPixels(const WebGLContext* webgl,
254 const webgl::PackingInfo& pi,
255 const uint32_t availRows,
256 const webgl::TexUnpackBlob& blob) {
257 const auto& unpackingRes = blob.mDesc.ExplicitUnpacking(pi, {});
258 if (!unpackingRes.isOk()) {
259 webgl->ErrorInvalidOperation("%s", unpackingRes.inspectErr().c_str());
260 return false;
262 const auto& unpacking = unpackingRes.inspect();
264 if (availRows < unpacking.metrics.totalRows) {
265 webgl->ErrorInvalidOperation(
266 "Desired upload requires more rows (%zu) than is"
267 " available (%zu).",
268 unpacking.metrics.totalRows, availRows);
269 return false;
272 return true;
275 static bool ValidateUnpackBytes(const WebGLContext* const webgl,
276 const webgl::PackingInfo& pi,
277 const size_t availByteCount,
278 const webgl::TexUnpackBlob& blob) {
279 const auto& unpackingRes = blob.mDesc.ExplicitUnpacking(pi, {});
280 if (!unpackingRes.isOk()) {
281 webgl->ErrorInvalidOperation("%s", unpackingRes.inspectErr().c_str());
282 return false;
284 const auto& unpacking = unpackingRes.inspect();
286 if (availByteCount < unpacking.metrics.totalBytesUsed) {
287 webgl->ErrorInvalidOperation(
288 "Desired upload requires more bytes (%zu) than are"
289 " available (%zu).",
290 unpacking.metrics.totalBytesUsed, availByteCount);
291 return false;
294 return true;
297 ////////////////////
299 // Check if the surface descriptor describes a memory which contains a single
300 // RGBA data source.
301 static bool SDIsRGBBuffer(const layers::SurfaceDescriptor& sd) {
302 return sd.type() == layers::SurfaceDescriptor::TSurfaceDescriptorBuffer &&
303 sd.get_SurfaceDescriptorBuffer().desc().type() ==
304 layers::BufferDescriptor::TRGBDescriptor;
307 // Check if the surface descriptor describes a GPUVideo texture for which we
308 // only have an opaque source/handle from SurfaceDescriptorRemoteDecoder to
309 // derive the actual texture from.
310 static bool SDIsNullRemoteDecoder(const layers::SurfaceDescriptor& sd) {
311 return sd.type() == layers::SurfaceDescriptor::TSurfaceDescriptorGPUVideo &&
312 sd.get_SurfaceDescriptorGPUVideo()
313 .get_SurfaceDescriptorRemoteDecoder()
314 .subdesc()
315 .type() == layers::RemoteDecoderVideoSubDescriptor::Tnull_t;
318 // Check if the surface descriptor describes an ExternalImage surface for which
319 // we only have an opaque source/handle to derive the actual surface from.
320 static bool SDIsExternalImage(const layers::SurfaceDescriptor& sd) {
321 return sd.type() ==
322 layers::SurfaceDescriptor::TSurfaceDescriptorExternalImage &&
323 sd.get_SurfaceDescriptorExternalImage().source() ==
324 wr::ExternalImageSource::SharedSurfaces;
327 // static
328 std::unique_ptr<TexUnpackBlob> TexUnpackBlob::Create(
329 const TexUnpackBlobDesc& desc) {
330 return std::unique_ptr<TexUnpackBlob>{[&]() -> TexUnpackBlob* {
331 if (!IsTarget3D(desc.imageTarget) && desc.size.z != 1) {
332 MOZ_ASSERT(false);
333 return nullptr;
336 switch (desc.unpacking.alignmentInTypeElems) {
337 case 1:
338 case 2:
339 case 4:
340 case 8:
341 break;
342 default:
343 MOZ_ASSERT(false);
344 return nullptr;
347 if (desc.sd) {
348 // Shmem buffers need to be treated as if they were a DataSourceSurface.
349 // Otherwise, TexUnpackImage will try to blit the surface descriptor as
350 // if it can be mapped as a framebuffer, whereas the Shmem is still CPU
351 // data.
352 if (SDIsRGBBuffer(*desc.sd) || SDIsNullRemoteDecoder(*desc.sd) ||
353 SDIsExternalImage(*desc.sd)) {
354 return new TexUnpackSurface(desc);
356 return new TexUnpackImage(desc);
358 if (desc.dataSurf) {
359 return new TexUnpackSurface(desc);
362 if (desc.srcAlphaType != gfxAlphaType::NonPremult) {
363 MOZ_ASSERT(false);
364 return nullptr;
366 return new TexUnpackBytes(desc);
367 }()};
370 static bool HasColorAndAlpha(const WebGLTexelFormat format) {
371 switch (format) {
372 case WebGLTexelFormat::RA8:
373 case WebGLTexelFormat::RA16F:
374 case WebGLTexelFormat::RA32F:
375 case WebGLTexelFormat::RGBA8:
376 case WebGLTexelFormat::RGBA5551:
377 case WebGLTexelFormat::RGBA4444:
378 case WebGLTexelFormat::RGBA16F:
379 case WebGLTexelFormat::RGBA32F:
380 case WebGLTexelFormat::BGRA8:
381 return true;
382 default:
383 return false;
387 bool TexUnpackBlob::ConvertIfNeeded(
388 const WebGLContext* const webgl, const uint32_t rowLength,
389 const uint32_t rowCount, WebGLTexelFormat srcFormat,
390 const uint8_t* const srcBegin, const ptrdiff_t srcStride,
391 WebGLTexelFormat dstFormat, const ptrdiff_t dstStride,
392 const uint8_t** const out_begin,
393 UniqueBuffer* const out_anchoredBuffer) const {
394 MOZ_ASSERT(srcFormat != WebGLTexelFormat::FormatNotSupportingAnyConversion);
395 MOZ_ASSERT(dstFormat != WebGLTexelFormat::FormatNotSupportingAnyConversion);
397 *out_begin = srcBegin;
399 const auto& unpacking = mDesc.unpacking;
401 if (!rowLength || !rowCount) return true;
403 const auto srcIsPremult = (mDesc.srcAlphaType == gfxAlphaType::Premult);
404 auto dstIsPremult = unpacking.premultiplyAlpha;
405 const auto fnHasPremultMismatch = [&]() {
406 if (mDesc.srcAlphaType == gfxAlphaType::Opaque) return false;
408 if (!HasColorAndAlpha(srcFormat)) return false;
410 return srcIsPremult != dstIsPremult;
413 const auto srcOrigin =
414 (unpacking.flipY ? gl::OriginPos::TopLeft : gl::OriginPos::BottomLeft);
415 auto dstOrigin = gl::OriginPos::BottomLeft;
417 if (!mDesc.applyUnpackTransforms) {
418 dstIsPremult = srcIsPremult;
419 dstOrigin = srcOrigin;
422 // TODO (Bug 754256): Figure out the source colorSpace.
423 dom::PredefinedColorSpace srcColorSpace = dom::PredefinedColorSpace::Srgb;
424 dom::PredefinedColorSpace dstColorSpace =
425 webgl->mUnpackColorSpace ? *webgl->mUnpackColorSpace
426 : dom::PredefinedColorSpace::Srgb;
428 if (srcFormat != dstFormat) {
429 webgl->GeneratePerfWarning(
430 "Conversion requires pixel reformatting. (%u->%u)", uint32_t(srcFormat),
431 uint32_t(dstFormat));
432 } else if (fnHasPremultMismatch()) {
433 webgl->GeneratePerfWarning(
434 "Conversion requires change in"
435 " alpha-premultiplication.");
436 } else if (srcOrigin != dstOrigin) {
437 webgl->GeneratePerfWarning("Conversion requires y-flip.");
438 } else if (srcStride != dstStride) {
439 webgl->GeneratePerfWarning("Conversion requires change in stride. (%u->%u)",
440 uint32_t(srcStride), uint32_t(dstStride));
441 } else if (srcColorSpace != dstColorSpace) {
442 webgl->GeneratePerfWarning(
443 "Conversion requires colorSpace conversion. (%u->%u)",
444 uint32_t(srcColorSpace), uint32_t(dstColorSpace));
445 } else {
446 return true;
449 ////
451 const auto dstTotalBytes = CheckedUint32(rowCount) * dstStride;
452 if (!dstTotalBytes.isValid()) {
453 webgl->ErrorOutOfMemory("Calculation failed.");
454 return false;
457 auto dstBuffer = UniqueBuffer::Take(calloc(1u, dstTotalBytes.value()));
458 if (!dstBuffer.get()) {
459 webgl->ErrorOutOfMemory("Failed to allocate dest buffer.");
460 return false;
462 const auto dstBegin = static_cast<uint8_t*>(dstBuffer.get());
464 ////
466 // And go!:
467 bool wasTrivial;
468 if (!ConvertImage(rowLength, rowCount, srcBegin, srcStride, srcOrigin,
469 srcFormat, srcIsPremult, dstBegin, dstStride, dstOrigin,
470 dstFormat, dstIsPremult, srcColorSpace, dstColorSpace,
471 &wasTrivial)) {
472 webgl->ErrorImplementationBug("ConvertImage failed.");
473 return false;
476 *out_begin = dstBegin;
477 *out_anchoredBuffer = std::move(dstBuffer);
478 return true;
481 static GLenum DoTexOrSubImage(bool isSubImage, gl::GLContext* gl,
482 TexImageTarget target, GLint level,
483 const DriverUnpackInfo* dui, GLint xOffset,
484 GLint yOffset, GLint zOffset, GLsizei width,
485 GLsizei height, GLsizei depth, const void* data) {
486 if (isSubImage) {
487 return DoTexSubImage(gl, target, level, xOffset, yOffset, zOffset, width,
488 height, depth, dui->ToPacking(), data);
489 } else {
490 return DoTexImage(gl, target, level, dui, width, height, depth, data);
494 //////////////////////////////////////////////////////////////////////////////////////////
495 // TexUnpackBytes
497 bool TexUnpackBytes::Validate(const WebGLContext* const webgl,
498 const webgl::PackingInfo& pi) {
499 if (!HasData()) return true;
501 CheckedInt<size_t> availBytes = 0;
502 if (mDesc.cpuData) {
503 availBytes = mDesc.cpuData->size();
504 } else if (mDesc.pboOffset) {
505 const auto& pboOffset = *mDesc.pboOffset;
507 const auto& pbo =
508 webgl->ValidateBufferSelection(LOCAL_GL_PIXEL_UNPACK_BUFFER);
509 if (!pbo) return false; // Might be invalid e.g. due to in-use by TF.
510 availBytes = pbo->ByteLength();
511 availBytes -= pboOffset;
512 } else {
513 MOZ_ASSERT(false, "Must be one of the above");
515 if (!availBytes.isValid()) {
516 webgl->ErrorInvalidOperation("Offset is passed end of buffer.");
517 return false;
520 return ValidateUnpackBytes(webgl, pi, availBytes.value(), *this);
523 bool TexUnpackBytes::TexOrSubImage(bool isSubImage, bool needsRespec,
524 WebGLTexture* tex, GLint level,
525 const webgl::DriverUnpackInfo* dui,
526 GLint xOffset, GLint yOffset, GLint zOffset,
527 const webgl::PackingInfo& pi,
528 GLenum* const out_error) const {
529 const auto& webgl = tex->mContext;
530 const auto& target = mDesc.imageTarget;
531 const auto& size = mDesc.size;
532 const auto& webglUnpackState = mDesc.unpacking;
534 const auto unpackingRes = mDesc.ExplicitUnpacking(pi, {});
536 const auto format = FormatForPackingInfo(pi);
538 const uint8_t* uploadPtr = nullptr;
539 if (mDesc.cpuData) {
540 uploadPtr = mDesc.cpuData->data();
541 } else if (mDesc.pboOffset) {
542 uploadPtr = reinterpret_cast<const uint8_t*>(*mDesc.pboOffset);
545 UniqueBuffer tempBuffer;
547 do {
548 if (mDesc.pboOffset || !uploadPtr) break;
550 if (!webglUnpackState.flipY && !webglUnpackState.premultiplyAlpha) {
551 break;
554 webgl->GenerateWarning(
555 "Alpha-premult and y-flip are deprecated for"
556 " non-DOM-Element uploads.");
558 MOZ_RELEASE_ASSERT(unpackingRes.isOk());
559 const auto& unpacking = unpackingRes.inspect();
560 const auto stride = unpacking.metrics.bytesPerRowStride;
561 // clang-format off
562 if (!ConvertIfNeeded(webgl, unpacking.state.rowLength,
563 unpacking.metrics.totalRows,
564 format, uploadPtr, AutoAssertCast(stride),
565 format, AutoAssertCast(stride), &uploadPtr, &tempBuffer)) {
566 return false;
568 // clang-format on
569 } while (false);
571 //////
573 const auto& gl = webgl->gl;
575 bool useParanoidHandling = false;
576 if (mNeedsExactUpload && webgl->mBoundPixelUnpackBuffer) {
577 webgl->GenerateWarning(
578 "Uploads from a buffer with a final row with a byte"
579 " count smaller than the row stride can incur extra"
580 " overhead.");
582 if (gl->WorkAroundDriverBugs()) {
583 useParanoidHandling |= (gl->Vendor() == gl::GLVendor::NVIDIA);
587 if (!useParanoidHandling) {
588 const ScopedLazyBind bindPBO(gl, LOCAL_GL_PIXEL_UNPACK_BUFFER,
589 webgl->mBoundPixelUnpackBuffer);
591 *out_error =
592 DoTexOrSubImage(isSubImage, gl, target, level, dui, xOffset, yOffset,
593 zOffset, size.x, size.y, size.z, uploadPtr);
594 return true;
597 //////
599 MOZ_ASSERT(webgl->mBoundPixelUnpackBuffer);
601 if (!isSubImage) {
602 // Alloc first to catch OOMs.
603 AssertUintParamCorrect(gl, LOCAL_GL_PIXEL_UNPACK_BUFFER_BINDING, 0);
604 *out_error =
605 DoTexOrSubImage(false, gl, target, level, dui, xOffset, yOffset,
606 zOffset, size.x, size.y, size.z, nullptr);
607 if (*out_error) return true;
609 if (!size.x || !size.y || !size.z) {
610 // Nothing to do.
611 return true;
614 MOZ_RELEASE_ASSERT(unpackingRes.isOk());
615 const auto& unpacking = unpackingRes.inspect();
617 const ScopedLazyBind bindPBO(gl, LOCAL_GL_PIXEL_UNPACK_BUFFER,
618 webgl->mBoundPixelUnpackBuffer);
620 //////
622 // Make our sometimes-implicit values explicit. Also this keeps them constant
623 // when we ask for height=mHeight-1 and such.
624 gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH,
625 AutoAssertCast(unpacking.state.rowLength));
626 gl->fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT,
627 AutoAssertCast(unpacking.state.imageHeight));
629 if (size.z > 1) {
630 *out_error =
631 DoTexOrSubImage(true, gl, target, level, dui, xOffset, yOffset, zOffset,
632 size.x, size.y, size.z - 1, uploadPtr);
635 // Skip the images we uploaded.
636 const auto skipImages = ZeroOn2D(target, unpacking.state.skipImages);
637 gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, skipImages + size.z - 1);
639 if (size.y > 1) {
640 *out_error =
641 DoTexOrSubImage(true, gl, target, level, dui, xOffset, yOffset,
642 zOffset + size.z - 1, size.x, size.y - 1, 1, uploadPtr);
645 // -
647 const auto lastRowOffset =
648 unpacking.metrics.totalBytesStrided - unpacking.metrics.bytesPerRowStride;
649 const auto lastRowPtr = uploadPtr + lastRowOffset;
651 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1); // No stride padding.
652 gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, 0); // No padding in general.
653 gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, 0); // Don't skip images,
654 gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS,
655 0); // or rows.
656 // Keep skipping pixels though!
657 *out_error = DoTexOrSubImage(true, gl, target, level, dui, xOffset,
658 yOffset + size.y - 1, zOffset + size.z - 1,
659 AutoAssertCast(size.x), 1, 1, lastRowPtr);
661 // Caller will reset all our modified PixelStorei state.
663 return true;
666 ////////////////////////////////////////////////////////////////////////////////
667 ////////////////////////////////////////////////////////////////////////////////
668 // TexUnpackImage
670 TexUnpackImage::~TexUnpackImage() = default;
672 bool TexUnpackImage::Validate(const WebGLContext* const webgl,
673 const webgl::PackingInfo& pi) {
674 if (!ValidatePIForDOM(webgl, pi)) return false;
676 if (!mDesc.structuredSrcSize) {
677 gfxCriticalError() << "TexUnpackImage missing structuredSrcSize.";
678 return false;
680 const auto& elemSize = *mDesc.structuredSrcSize;
681 if (mDesc.dataSurf) {
682 const auto& surfSize = mDesc.dataSurf->GetSize();
683 const auto surfSize2 = ivec2::FromSize(surfSize)->StaticCast<uvec2>();
684 if (uvec2{elemSize.x, elemSize.y} != surfSize2) {
685 gfxCriticalError()
686 << "TexUnpackImage mismatched structuredSrcSize for dataSurf.";
687 return false;
691 const auto fullRows = elemSize.y;
692 return ValidateUnpackPixels(webgl, pi, fullRows, *this);
695 Maybe<std::string> BlitPreventReason(
696 const int32_t level, const ivec3& offset, const GLenum internalFormat,
697 const webgl::PackingInfo& pi, const TexUnpackBlobDesc& desc,
698 const OptionalRenderableFormatBits optionalRenderableFormatBits,
699 bool sameColorSpace) {
700 const auto& size = desc.size;
701 const auto& unpacking = desc.unpacking;
703 const auto ret = [&]() -> const char* {
704 if (size.z != 1) {
705 return "depth is not 1";
707 if (offset.x != 0 || offset.y != 0 || offset.z != 0) {
708 return "x/y/zOffset is not 0";
711 if (unpacking.skipPixels || unpacking.skipRows || unpacking.skipImages) {
712 return "non-zero UNPACK_SKIP_* not yet supported";
715 const auto premultReason = [&]() -> const char* {
716 if (desc.srcAlphaType == gfxAlphaType::Opaque) return nullptr;
718 const bool srcIsPremult = (desc.srcAlphaType == gfxAlphaType::Premult);
719 const auto& dstIsPremult = unpacking.premultiplyAlpha;
720 if (srcIsPremult == dstIsPremult) return nullptr;
722 if (dstIsPremult) {
723 return "UNPACK_PREMULTIPLY_ALPHA_WEBGL is not true";
724 } else {
725 return "UNPACK_PREMULTIPLY_ALPHA_WEBGL is not false";
727 }();
728 if (premultReason) return premultReason;
730 if (!sameColorSpace) {
731 return "not same colorSpace";
734 const auto formatReason = [&]() -> const char* {
735 if (pi.type != LOCAL_GL_UNSIGNED_BYTE) {
736 return "`unpackType` must be `UNSIGNED_BYTE`";
739 switch (pi.format) {
740 case LOCAL_GL_RGBA:
741 return nullptr; // All internalFormats for unpackFormat=RGBA are
742 // renderable.
744 case LOCAL_GL_RGB:
745 break;
747 default:
748 return "`unpackFormat` must be `RGBA` or maybe `RGB`";
751 // -
753 struct {
754 OptionalRenderableFormatBits bits;
755 const char* errorMsg;
756 } required;
758 switch (internalFormat) {
759 case LOCAL_GL_RGB565:
760 return nullptr;
761 case LOCAL_GL_RGB:
762 case LOCAL_GL_RGB8:
763 required = {
764 OptionalRenderableFormatBits::RGB8,
765 "Unavailable, as blitting internalFormats RGB or RGB8 requires "
766 "that RGB8 must be a renderable format.",
768 break;
769 case LOCAL_GL_SRGB:
770 case LOCAL_GL_SRGB8:
771 required = {
772 OptionalRenderableFormatBits::SRGB8,
773 "Unavailable, as blitting internalFormats SRGB or SRGB8 requires "
774 "that SRGB8 must be a renderable format.",
776 break;
777 case 0:
778 // texSubImage, so internalFormat is unknown, and could be anything!
779 required = {
780 OptionalRenderableFormatBits::RGB8 |
781 OptionalRenderableFormatBits::SRGB8,
782 "Unavailable, as blitting texSubImage with unpackFormat=RGB "
783 "requires that RGB8 and SRGB8 must be renderable formats.",
785 break;
786 default:
787 gfxCriticalError()
788 << "Unexpected internalFormat for unpackFormat=RGB: 0x"
789 << gfx::hexa(internalFormat);
790 return "Unexpected internalFormat for unpackFormat=RGB";
793 const auto availableBits = optionalRenderableFormatBits;
794 if ((required.bits | availableBits) != availableBits) {
795 return required.errorMsg;
798 // -
800 return nullptr;
801 }();
802 if (formatReason) return formatReason;
804 return nullptr;
805 }();
806 if (ret) {
807 return Some(std::string(ret));
809 return {};
812 bool TexUnpackImage::TexOrSubImage(bool isSubImage, bool needsRespec,
813 WebGLTexture* tex, GLint level,
814 const webgl::DriverUnpackInfo* dui,
815 GLint xOffset, GLint yOffset, GLint zOffset,
816 const webgl::PackingInfo& pi,
817 GLenum* const out_error) const {
818 MOZ_ASSERT_IF(needsRespec, !isSubImage);
820 const auto& webgl = tex->mContext;
821 const auto& target = mDesc.imageTarget;
822 const auto& size = mDesc.size;
823 const auto& sd = *(mDesc.sd);
824 const auto& unpacking = mDesc.unpacking;
826 const auto& gl = webgl->GL();
828 // -
830 // TODO (Bug 754256): Figure out the source colorSpace.
831 dom::PredefinedColorSpace srcColorSpace = dom::PredefinedColorSpace::Srgb;
832 dom::PredefinedColorSpace dstColorSpace =
833 webgl->mUnpackColorSpace ? *webgl->mUnpackColorSpace
834 : dom::PredefinedColorSpace::Srgb;
835 bool sameColorSpace = (srcColorSpace == dstColorSpace);
837 const auto reason = BlitPreventReason(
838 level, {xOffset, yOffset, zOffset}, dui->internalFormat, pi, mDesc,
839 webgl->mOptionalRenderableFormatBits, sameColorSpace);
840 if (reason) {
841 webgl->GeneratePerfWarning(
842 "Failed to hit GPU-copy fast-path."
843 " (%s) Falling back to CPU upload.",
844 reason->c_str());
845 return false;
848 // -
850 if (needsRespec) {
851 *out_error =
852 DoTexOrSubImage(isSubImage, gl, target, level, dui, xOffset, yOffset,
853 zOffset, size.x, size.y, size.z, nullptr);
854 if (*out_error) return true;
858 gl::ScopedFramebuffer scopedFB(gl);
859 gl::ScopedBindFramebuffer bindFB(gl, scopedFB.FB());
862 gl::GLContext::LocalErrorScope errorScope(*gl);
864 gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER,
865 LOCAL_GL_COLOR_ATTACHMENT0, target,
866 tex->mGLName, level);
868 const auto err = errorScope.GetError();
869 MOZ_ALWAYS_TRUE(!err);
872 const GLenum status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
873 MOZ_ALWAYS_TRUE(status == LOCAL_GL_FRAMEBUFFER_COMPLETE);
875 const auto dstOrigin =
876 (unpacking.flipY ? gl::OriginPos::TopLeft : gl::OriginPos::BottomLeft);
877 if (!gl->BlitHelper()->BlitSdToFramebuffer(sd, {size.x, size.y},
878 dstOrigin)) {
879 gfxCriticalNote << "BlitSdToFramebuffer failed for type "
880 << int(sd.type());
881 // Maybe the resource isn't valid anymore?
882 gl->fClearColor(0.2, 0.0, 0.2, 1.0);
883 gl->fClear(LOCAL_GL_COLOR_BUFFER_BIT);
884 const auto& cur = webgl->mColorClearValue;
885 gl->fClearColor(cur[0], cur[1], cur[2], cur[3]);
886 webgl->GenerateWarning(
887 "Fast Tex(Sub)Image upload failed without recourse, clearing to "
888 "[0.2, 0.0, 0.2, 1.0]. Please file a bug!");
892 return true;
895 ////////////////////////////////////////////////////////////////////////////////
896 ////////////////////////////////////////////////////////////////////////////////
897 // TexUnpackSurface
899 TexUnpackSurface::~TexUnpackSurface() = default;
901 //////////
903 static bool GetFormatForSurf(const gfx::SourceSurface* surf,
904 WebGLTexelFormat* const out_texelFormat,
905 uint8_t* const out_bpp) {
906 const auto surfFormat = surf->GetFormat();
907 switch (surfFormat) {
908 case gfx::SurfaceFormat::B8G8R8A8:
909 *out_texelFormat = WebGLTexelFormat::BGRA8;
910 *out_bpp = 4;
911 return true;
913 case gfx::SurfaceFormat::B8G8R8X8:
914 *out_texelFormat = WebGLTexelFormat::BGRX8;
915 *out_bpp = 4;
916 return true;
918 case gfx::SurfaceFormat::R8G8B8A8:
919 *out_texelFormat = WebGLTexelFormat::RGBA8;
920 *out_bpp = 4;
921 return true;
923 case gfx::SurfaceFormat::R8G8B8X8:
924 *out_texelFormat = WebGLTexelFormat::RGBX8;
925 *out_bpp = 4;
926 return true;
928 case gfx::SurfaceFormat::R5G6B5_UINT16:
929 *out_texelFormat = WebGLTexelFormat::RGB565;
930 *out_bpp = 2;
931 return true;
933 case gfx::SurfaceFormat::A8:
934 *out_texelFormat = WebGLTexelFormat::A8;
935 *out_bpp = 1;
936 return true;
938 case gfx::SurfaceFormat::YUV420:
939 // Ugh...
940 NS_ERROR("We don't handle uploads from YUV sources yet.");
941 // When we want to, check out gfx/ycbcr/YCbCrUtils.h. (specifically
942 // GetYCbCrToRGBDestFormatAndSize and ConvertYCbCrToRGB)
943 return false;
945 default:
946 return false;
950 //////////
952 bool TexUnpackSurface::Validate(const WebGLContext* const webgl,
953 const webgl::PackingInfo& pi) {
954 if (!ValidatePIForDOM(webgl, pi)) return false;
956 if (!mDesc.structuredSrcSize) {
957 gfxCriticalError() << "TexUnpackSurface missing structuredSrcSize.";
958 return false;
960 const auto& elemSize = *mDesc.structuredSrcSize;
961 if (mDesc.dataSurf) {
962 const auto& surfSize = mDesc.dataSurf->GetSize();
963 const auto surfSize2 = ivec2::FromSize(surfSize)->StaticCast<uvec2>();
964 if (uvec2{elemSize.x, elemSize.y} != surfSize2) {
965 gfxCriticalError()
966 << "TexUnpackSurface mismatched structuredSrcSize for dataSurf.";
967 return false;
971 const auto fullRows = elemSize.y;
972 return ValidateUnpackPixels(webgl, pi, fullRows, *this);
975 bool TexUnpackSurface::TexOrSubImage(bool isSubImage, bool needsRespec,
976 WebGLTexture* tex, GLint level,
977 const webgl::DriverUnpackInfo* dui,
978 GLint xOffset, GLint yOffset,
979 GLint zOffset,
980 const webgl::PackingInfo& dstPI,
981 GLenum* const out_error) const {
982 const auto& webgl = tex->mContext;
983 const auto& size = mDesc.size;
984 RefPtr<gfx::DataSourceSurface> surf;
985 if (mDesc.sd) {
986 // If we get here, we assume the SD describes an RGBA Shmem.
987 const auto& sd = *(mDesc.sd);
988 if (SDIsRGBBuffer(sd)) {
989 const auto& sdb = sd.get_SurfaceDescriptorBuffer();
990 const auto& rgb = sdb.desc().get_RGBDescriptor();
991 const auto& data = sdb.data();
992 MOZ_ASSERT(data.type() == layers::MemoryOrShmem::TShmem);
993 const auto& shmem = data.get_Shmem();
994 surf = gfx::Factory::CreateWrappingDataSourceSurface(
995 shmem.get<uint8_t>(), layers::ImageDataSerializer::GetRGBStride(rgb),
996 rgb.size(), rgb.format());
997 } else if (SDIsNullRemoteDecoder(sd)) {
998 const auto& sdrd = sd.get_SurfaceDescriptorGPUVideo()
999 .get_SurfaceDescriptorRemoteDecoder();
1000 RefPtr<layers::VideoBridgeParent> parent =
1001 layers::VideoBridgeParent::GetSingleton(sdrd.source());
1002 if (!parent) {
1003 gfxCriticalNote << "TexUnpackSurface failed to get VideoBridgeParent";
1004 return false;
1006 RefPtr<layers::TextureHost> texture =
1007 parent->LookupTexture(webgl->GetContentId(), sdrd.handle());
1008 if (!texture) {
1009 gfxCriticalNote << "TexUnpackSurface failed to get TextureHost";
1010 return false;
1012 surf = texture->GetAsSurface();
1013 } else if (SDIsExternalImage(sd)) {
1014 const auto& sdei = sd.get_SurfaceDescriptorExternalImage();
1015 if (auto* sharedSurfacesHolder = webgl->GetSharedSurfacesHolder()) {
1016 surf = sharedSurfacesHolder->Get(sdei.id());
1018 if (!surf) {
1019 // Most likely the content process crashed before it was able to finish
1020 // sharing the surface with the compositor process.
1021 gfxCriticalNote << "TexUnpackSurface failed to get ExternalImage";
1022 return false;
1024 } else {
1025 MOZ_ASSERT_UNREACHABLE("Unexpected surface descriptor!");
1027 if (!surf) {
1028 gfxCriticalError() << "TexUnpackSurface failed to create wrapping "
1029 "DataSourceSurface for Shmem.";
1030 return false;
1032 } else {
1033 surf = mDesc.dataSurf;
1036 ////
1038 WebGLTexelFormat srcFormat;
1039 uint8_t srcBPP;
1040 if (!GetFormatForSurf(surf, &srcFormat, &srcBPP)) {
1041 webgl->ErrorImplementationBug(
1042 "GetFormatForSurf failed for"
1043 " WebGLTexelFormat::%u.",
1044 uint32_t(surf->GetFormat()));
1045 return false;
1048 gfx::DataSourceSurface::ScopedMap map(surf,
1049 gfx::DataSourceSurface::MapType::READ);
1050 if (!map.IsMapped()) {
1051 webgl->ErrorOutOfMemory("Failed to map source surface for upload.");
1052 return false;
1055 const auto& srcBegin = map.GetData();
1056 const auto srcStride = static_cast<size_t>(map.GetStride());
1058 // -
1060 const auto dstFormat = FormatForPackingInfo(dstPI);
1061 const auto dstBpp = BytesPerPixel(dstPI);
1062 const size_t dstUsedBytesPerRow = dstBpp * surf->GetSize().width;
1063 auto dstStride = dstUsedBytesPerRow;
1064 if (dstFormat == srcFormat) {
1065 dstStride = srcStride; // Try to match.
1068 // -
1070 auto dstUnpackingRes = mDesc.ExplicitUnpacking(dstPI, Some(dstStride));
1071 if (dstUnpackingRes.isOk()) {
1072 const auto& dstUnpacking = dstUnpackingRes.inspect();
1073 if (!webgl->IsWebGL2() && dstUnpacking.state.rowLength != size.x) {
1074 dstUnpackingRes = Err("WebGL1 can't handle rowLength != size.x");
1077 if (!dstUnpackingRes.isOk()) {
1078 dstStride = dstUsedBytesPerRow;
1079 dstUnpackingRes = mDesc.ExplicitUnpacking(dstPI, Some(dstStride));
1081 if (!dstUnpackingRes.isOk()) {
1082 gfxCriticalError() << dstUnpackingRes.inspectErr();
1083 webgl->ErrorImplementationBug("ExplicitUnpacking failed: %s",
1084 dstUnpackingRes.inspectErr().c_str());
1085 return false;
1087 const auto& dstUnpacking = dstUnpackingRes.inspect();
1088 MOZ_ASSERT(dstUnpacking.metrics.bytesPerRowStride == dstStride);
1090 // -
1092 const uint8_t* dstBegin = srcBegin;
1093 UniqueBuffer tempBuffer;
1094 // clang-format off
1095 if (!ConvertIfNeeded(webgl, surf->GetSize().width, surf->GetSize().height,
1096 srcFormat, srcBegin, AutoAssertCast(srcStride),
1097 dstFormat, AutoAssertCast(dstUnpacking.metrics.bytesPerRowStride), &dstBegin,
1098 &tempBuffer)) {
1099 return false;
1101 // clang-format on
1103 ////
1105 const auto& gl = webgl->gl;
1106 if (!gl->MakeCurrent()) {
1107 *out_error = LOCAL_GL_CONTEXT_LOST;
1108 return true;
1111 dstUnpacking.state.ApplyUnpack(*gl, webgl->IsWebGL2(), size);
1113 *out_error =
1114 DoTexOrSubImage(isSubImage, gl, mDesc.imageTarget, level, dui, xOffset,
1115 yOffset, zOffset, size.x, size.y, size.z, dstBegin);
1117 // Caller will reset all our modified PixelStorei state.
1119 return true;
1122 } // namespace webgl
1123 } // namespace mozilla