Backed out 4 changesets (bug 1858627) for causing clipboard/paste failures. CLOSED...
[gecko.git] / dom / canvas / TexUnpackBlob.cpp
blobe9e64fe9b99eddde3753d09fab1b6107c1f79c75
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/TextureHost.h"
15 #include "mozilla/layers/VideoBridgeParent.h"
16 #include "mozilla/RefPtr.h"
17 #include "nsLayoutUtils.h"
18 #include "WebGLBuffer.h"
19 #include "WebGLContext.h"
20 #include "WebGLFormats.h"
21 #include "WebGLTexelConversions.h"
22 #include "WebGLTexture.h"
24 namespace mozilla {
26 bool webgl::PixelPackingState::AssertCurrentUnpack(gl::GLContext& gl,
27 const bool isWebgl2) const {
28 auto actual = PixelPackingState{};
29 gl.GetInt(LOCAL_GL_UNPACK_ALIGNMENT, &actual.alignmentInTypeElems);
30 if (isWebgl2) {
31 gl.GetInt(LOCAL_GL_UNPACK_ROW_LENGTH, &actual.rowLength);
32 gl.GetInt(LOCAL_GL_UNPACK_IMAGE_HEIGHT, &actual.imageHeight);
34 gl.GetInt(LOCAL_GL_UNPACK_SKIP_PIXELS, &actual.skipPixels);
35 gl.GetInt(LOCAL_GL_UNPACK_SKIP_ROWS, &actual.skipRows);
36 gl.GetInt(LOCAL_GL_UNPACK_SKIP_IMAGES, &actual.skipImages);
38 if (*this == actual) return true;
40 const auto ToStr = [](const PixelPackingState& x) {
41 const auto text = nsPrintfCString(
42 "%u,%u,%u;%u,%u,%u", x.alignmentInTypeElems, x.rowLength, x.imageHeight,
43 x.skipPixels, x.skipRows, x.skipImages);
44 return mozilla::ToString(text);
47 const auto was = ToStr(actual);
48 const auto expected = ToStr(*this);
49 gfxCriticalError() << "PixelUnpackStateGl was not current. Was " << was
50 << ". Expected << " << expected << ".";
51 return false;
54 void webgl::PixelPackingState::ApplyUnpack(gl::GLContext& gl,
55 const bool isWebgl2,
56 const uvec3& uploadSize) const {
57 gl.fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT,
58 AssertedCast<GLsizei>(alignmentInTypeElems));
59 if (!isWebgl2) return;
61 // Re-simplify. (ANGLE seems to have an issue with imageHeight ==
62 // uploadSize.y)
63 auto rowLengthOrZero = rowLength;
64 auto imageHeightOrZero = imageHeight;
65 if (rowLengthOrZero == uploadSize.x) {
66 rowLengthOrZero = 0;
68 if (imageHeightOrZero == uploadSize.y) {
69 imageHeightOrZero = 0;
72 gl.fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH,
73 AssertedCast<GLsizei>(rowLengthOrZero));
74 gl.fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT,
75 AssertedCast<GLsizei>(imageHeightOrZero));
77 gl.fPixelStorei(LOCAL_GL_UNPACK_SKIP_PIXELS,
78 AssertedCast<GLsizei>(skipPixels));
79 gl.fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS, AssertedCast<GLsizei>(skipRows));
80 gl.fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES,
81 AssertedCast<GLsizei>(skipImages));
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,
251 const webgl::PackingInfo& pi,
252 const uint32_t availRows,
253 const webgl::TexUnpackBlob& blob) {
254 const auto& unpackingRes = blob.mDesc.ExplicitUnpacking(pi, {});
255 if (!unpackingRes.isOk()) {
256 webgl->ErrorInvalidOperation("%s", unpackingRes.inspectErr().c_str());
257 return false;
259 const auto& unpacking = unpackingRes.inspect();
261 if (availRows < unpacking.metrics.totalRows) {
262 webgl->ErrorInvalidOperation(
263 "Desired upload requires more rows (%zu) than is"
264 " available (%zu).",
265 unpacking.metrics.totalRows, availRows);
266 return false;
269 return true;
272 static bool ValidateUnpackBytes(const WebGLContext* const webgl,
273 const webgl::PackingInfo& pi,
274 const size_t availByteCount,
275 const webgl::TexUnpackBlob& blob) {
276 const auto& unpackingRes = blob.mDesc.ExplicitUnpacking(pi, {});
277 if (!unpackingRes.isOk()) {
278 webgl->ErrorInvalidOperation("%s", unpackingRes.inspectErr().c_str());
279 return false;
281 const auto& unpacking = unpackingRes.inspect();
283 if (availByteCount < unpacking.metrics.totalBytesUsed) {
284 webgl->ErrorInvalidOperation(
285 "Desired upload requires more bytes (%zu) than are"
286 " available (%zu).",
287 unpacking.metrics.totalBytesUsed, availByteCount);
288 return false;
291 return true;
294 ////////////////////
296 // Check if the surface descriptor describes a memory which contains a single
297 // RGBA data source.
298 static bool SDIsRGBBuffer(const layers::SurfaceDescriptor& sd) {
299 return sd.type() == layers::SurfaceDescriptor::TSurfaceDescriptorBuffer &&
300 sd.get_SurfaceDescriptorBuffer().desc().type() ==
301 layers::BufferDescriptor::TRGBDescriptor;
304 // Check if the surface descriptor describes a GPUVideo texture for which we
305 // only have an opaque source/handle from SurfaceDescriptorRemoteDecoder to
306 // derive the actual texture from.
307 static bool SDIsNullRemoteDecoder(const layers::SurfaceDescriptor& sd) {
308 return sd.type() == layers::SurfaceDescriptor::TSurfaceDescriptorGPUVideo &&
309 sd.get_SurfaceDescriptorGPUVideo()
310 .get_SurfaceDescriptorRemoteDecoder()
311 .subdesc()
312 .type() == layers::RemoteDecoderVideoSubDescriptor::Tnull_t;
315 // static
316 std::unique_ptr<TexUnpackBlob> TexUnpackBlob::Create(
317 const TexUnpackBlobDesc& desc) {
318 return std::unique_ptr<TexUnpackBlob>{[&]() -> TexUnpackBlob* {
319 if (!IsTarget3D(desc.imageTarget) && desc.size.z != 1) {
320 MOZ_ASSERT(false);
321 return nullptr;
324 switch (desc.unpacking.alignmentInTypeElems) {
325 case 1:
326 case 2:
327 case 4:
328 case 8:
329 break;
330 default:
331 MOZ_ASSERT(false);
332 return nullptr;
335 if (desc.sd) {
336 // Shmem buffers need to be treated as if they were a DataSourceSurface.
337 // Otherwise, TexUnpackImage will try to blit the surface descriptor as
338 // if it can be mapped as a framebuffer, whereas the Shmem is still CPU
339 // data.
340 if (SDIsRGBBuffer(*desc.sd) || SDIsNullRemoteDecoder(*desc.sd)) {
341 return new TexUnpackSurface(desc);
343 return new TexUnpackImage(desc);
345 if (desc.dataSurf) {
346 return new TexUnpackSurface(desc);
349 if (desc.srcAlphaType != gfxAlphaType::NonPremult) {
350 MOZ_ASSERT(false);
351 return nullptr;
353 return new TexUnpackBytes(desc);
354 }()};
357 static bool HasColorAndAlpha(const WebGLTexelFormat format) {
358 switch (format) {
359 case WebGLTexelFormat::RA8:
360 case WebGLTexelFormat::RA16F:
361 case WebGLTexelFormat::RA32F:
362 case WebGLTexelFormat::RGBA8:
363 case WebGLTexelFormat::RGBA5551:
364 case WebGLTexelFormat::RGBA4444:
365 case WebGLTexelFormat::RGBA16F:
366 case WebGLTexelFormat::RGBA32F:
367 case WebGLTexelFormat::BGRA8:
368 return true;
369 default:
370 return false;
374 bool TexUnpackBlob::ConvertIfNeeded(
375 const WebGLContext* const webgl, const uint32_t rowLength,
376 const uint32_t rowCount, WebGLTexelFormat srcFormat,
377 const uint8_t* const srcBegin, const ptrdiff_t srcStride,
378 WebGLTexelFormat dstFormat, const ptrdiff_t dstStride,
379 const uint8_t** const out_begin,
380 UniqueBuffer* const out_anchoredBuffer) const {
381 MOZ_ASSERT(srcFormat != WebGLTexelFormat::FormatNotSupportingAnyConversion);
382 MOZ_ASSERT(dstFormat != WebGLTexelFormat::FormatNotSupportingAnyConversion);
384 *out_begin = srcBegin;
386 const auto& unpacking = mDesc.unpacking;
388 if (!rowLength || !rowCount) return true;
390 const auto srcIsPremult = (mDesc.srcAlphaType == gfxAlphaType::Premult);
391 auto dstIsPremult = unpacking.premultiplyAlpha;
392 const auto fnHasPremultMismatch = [&]() {
393 if (mDesc.srcAlphaType == gfxAlphaType::Opaque) return false;
395 if (!HasColorAndAlpha(srcFormat)) return false;
397 return srcIsPremult != dstIsPremult;
400 const auto srcOrigin =
401 (unpacking.flipY ? gl::OriginPos::TopLeft : gl::OriginPos::BottomLeft);
402 auto dstOrigin = gl::OriginPos::BottomLeft;
404 if (!mDesc.applyUnpackTransforms) {
405 dstIsPremult = srcIsPremult;
406 dstOrigin = srcOrigin;
409 if (srcFormat != dstFormat) {
410 webgl->GeneratePerfWarning(
411 "Conversion requires pixel reformatting. (%u->%u)", uint32_t(srcFormat),
412 uint32_t(dstFormat));
413 } else if (fnHasPremultMismatch()) {
414 webgl->GeneratePerfWarning(
415 "Conversion requires change in"
416 " alpha-premultiplication.");
417 } else if (srcOrigin != dstOrigin) {
418 webgl->GeneratePerfWarning("Conversion requires y-flip.");
419 } else if (srcStride != dstStride) {
420 webgl->GeneratePerfWarning("Conversion requires change in stride. (%u->%u)",
421 uint32_t(srcStride), uint32_t(dstStride));
422 } else {
423 return true;
426 ////
428 const auto dstTotalBytes = CheckedUint32(rowCount) * dstStride;
429 if (!dstTotalBytes.isValid()) {
430 webgl->ErrorOutOfMemory("Calculation failed.");
431 return false;
434 auto dstBuffer = UniqueBuffer::Take(calloc(1u, dstTotalBytes.value()));
435 if (!dstBuffer.get()) {
436 webgl->ErrorOutOfMemory("Failed to allocate dest buffer.");
437 return false;
439 const auto dstBegin = static_cast<uint8_t*>(dstBuffer.get());
441 ////
443 // And go!:
444 bool wasTrivial;
445 if (!ConvertImage(rowLength, rowCount, srcBegin, srcStride, srcOrigin,
446 srcFormat, srcIsPremult, dstBegin, dstStride, dstOrigin,
447 dstFormat, dstIsPremult, &wasTrivial)) {
448 webgl->ErrorImplementationBug("ConvertImage failed.");
449 return false;
452 *out_begin = dstBegin;
453 *out_anchoredBuffer = std::move(dstBuffer);
454 return true;
457 static GLenum DoTexOrSubImage(bool isSubImage, gl::GLContext* gl,
458 TexImageTarget target, GLint level,
459 const DriverUnpackInfo* dui, GLint xOffset,
460 GLint yOffset, GLint zOffset, GLsizei width,
461 GLsizei height, GLsizei depth, const void* data) {
462 if (isSubImage) {
463 return DoTexSubImage(gl, target, level, xOffset, yOffset, zOffset, width,
464 height, depth, dui->ToPacking(), data);
465 } else {
466 return DoTexImage(gl, target, level, dui, width, height, depth, data);
470 //////////////////////////////////////////////////////////////////////////////////////////
471 // TexUnpackBytes
473 bool TexUnpackBytes::Validate(const WebGLContext* const webgl,
474 const webgl::PackingInfo& pi) {
475 if (!HasData()) return true;
477 CheckedInt<size_t> availBytes = 0;
478 if (mDesc.cpuData) {
479 availBytes = mDesc.cpuData->size();
480 } else if (mDesc.pboOffset) {
481 const auto& pboOffset = *mDesc.pboOffset;
483 const auto& pbo =
484 webgl->ValidateBufferSelection(LOCAL_GL_PIXEL_UNPACK_BUFFER);
485 if (!pbo) return false; // Might be invalid e.g. due to in-use by TF.
486 availBytes = pbo->ByteLength();
487 availBytes -= pboOffset;
488 } else {
489 MOZ_ASSERT(false, "Must be one of the above");
491 if (!availBytes.isValid()) {
492 webgl->ErrorInvalidOperation("Offset is passed end of buffer.");
493 return false;
496 return ValidateUnpackBytes(webgl, pi, availBytes.value(), *this);
499 bool TexUnpackBytes::TexOrSubImage(bool isSubImage, bool needsRespec,
500 WebGLTexture* tex, GLint level,
501 const webgl::DriverUnpackInfo* dui,
502 GLint xOffset, GLint yOffset, GLint zOffset,
503 const webgl::PackingInfo& pi,
504 GLenum* const out_error) const {
505 const auto& webgl = tex->mContext;
506 const auto& target = mDesc.imageTarget;
507 const auto& size = mDesc.size;
508 const auto& webglUnpackState = mDesc.unpacking;
510 const auto unpackingRes = mDesc.ExplicitUnpacking(pi, {});
512 const auto format = FormatForPackingInfo(pi);
514 const uint8_t* uploadPtr = nullptr;
515 if (mDesc.cpuData) {
516 uploadPtr = mDesc.cpuData->data();
517 } else if (mDesc.pboOffset) {
518 uploadPtr = reinterpret_cast<const uint8_t*>(*mDesc.pboOffset);
521 UniqueBuffer tempBuffer;
523 do {
524 if (mDesc.pboOffset || !uploadPtr) break;
526 if (!webglUnpackState.flipY && !webglUnpackState.premultiplyAlpha) {
527 break;
530 webgl->GenerateWarning(
531 "Alpha-premult and y-flip are deprecated for"
532 " non-DOM-Element uploads.");
534 MOZ_RELEASE_ASSERT(unpackingRes.isOk());
535 const auto& unpacking = unpackingRes.inspect();
536 const auto stride = unpacking.metrics.bytesPerRowStride;
537 // clang-format off
538 if (!ConvertIfNeeded(webgl, unpacking.state.rowLength,
539 unpacking.metrics.totalRows,
540 format, uploadPtr, AutoAssertCast(stride),
541 format, AutoAssertCast(stride), &uploadPtr, &tempBuffer)) {
542 return false;
544 // clang-format on
545 } while (false);
547 //////
549 const auto& gl = webgl->gl;
551 bool useParanoidHandling = false;
552 if (mNeedsExactUpload && webgl->mBoundPixelUnpackBuffer) {
553 webgl->GenerateWarning(
554 "Uploads from a buffer with a final row with a byte"
555 " count smaller than the row stride can incur extra"
556 " overhead.");
558 if (gl->WorkAroundDriverBugs()) {
559 useParanoidHandling |= (gl->Vendor() == gl::GLVendor::NVIDIA);
563 if (!useParanoidHandling) {
564 const ScopedLazyBind bindPBO(gl, LOCAL_GL_PIXEL_UNPACK_BUFFER,
565 webgl->mBoundPixelUnpackBuffer);
567 *out_error =
568 DoTexOrSubImage(isSubImage, gl, target, level, dui, xOffset, yOffset,
569 zOffset, size.x, size.y, size.z, uploadPtr);
570 return true;
573 //////
575 MOZ_ASSERT(webgl->mBoundPixelUnpackBuffer);
577 if (!isSubImage) {
578 // Alloc first to catch OOMs.
579 AssertUintParamCorrect(gl, LOCAL_GL_PIXEL_UNPACK_BUFFER_BINDING, 0);
580 *out_error =
581 DoTexOrSubImage(false, gl, target, level, dui, xOffset, yOffset,
582 zOffset, size.x, size.y, size.z, nullptr);
583 if (*out_error) return true;
585 if (!size.x || !size.y || !size.z) {
586 // Nothing to do.
587 return true;
590 MOZ_RELEASE_ASSERT(unpackingRes.isOk());
591 const auto& unpacking = unpackingRes.inspect();
593 const ScopedLazyBind bindPBO(gl, LOCAL_GL_PIXEL_UNPACK_BUFFER,
594 webgl->mBoundPixelUnpackBuffer);
596 //////
598 // Make our sometimes-implicit values explicit. Also this keeps them constant
599 // when we ask for height=mHeight-1 and such.
600 gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH,
601 AutoAssertCast(unpacking.state.rowLength));
602 gl->fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT,
603 AutoAssertCast(unpacking.state.imageHeight));
605 if (size.z > 1) {
606 *out_error =
607 DoTexOrSubImage(true, gl, target, level, dui, xOffset, yOffset, zOffset,
608 size.x, size.y, size.z - 1, uploadPtr);
611 // Skip the images we uploaded.
612 const auto skipImages = ZeroOn2D(target, unpacking.state.skipImages);
613 gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, skipImages + size.z - 1);
615 if (size.y > 1) {
616 *out_error =
617 DoTexOrSubImage(true, gl, target, level, dui, xOffset, yOffset,
618 zOffset + size.z - 1, size.x, size.y - 1, 1, uploadPtr);
621 // -
623 const auto lastRowOffset =
624 unpacking.metrics.totalBytesStrided - unpacking.metrics.bytesPerRowStride;
625 const auto lastRowPtr = uploadPtr + lastRowOffset;
627 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1); // No stride padding.
628 gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, 0); // No padding in general.
629 gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, 0); // Don't skip images,
630 gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS,
631 0); // or rows.
632 // Keep skipping pixels though!
633 *out_error = DoTexOrSubImage(true, gl, target, level, dui, xOffset,
634 yOffset + size.y - 1, zOffset + size.z - 1,
635 AutoAssertCast(size.x), 1, 1, lastRowPtr);
637 // Caller will reset all our modified PixelStorei state.
639 return true;
642 ////////////////////////////////////////////////////////////////////////////////
643 ////////////////////////////////////////////////////////////////////////////////
644 // TexUnpackImage
646 TexUnpackImage::~TexUnpackImage() = default;
648 bool TexUnpackImage::Validate(const WebGLContext* const webgl,
649 const webgl::PackingInfo& pi) {
650 if (!ValidatePIForDOM(webgl, pi)) return false;
652 if (!mDesc.structuredSrcSize) {
653 gfxCriticalError() << "TexUnpackImage missing structuredSrcSize.";
654 return false;
656 const auto& elemSize = *mDesc.structuredSrcSize;
657 if (mDesc.dataSurf) {
658 const auto& surfSize = mDesc.dataSurf->GetSize();
659 const auto surfSize2 = ivec2::FromSize(surfSize)->StaticCast<uvec2>();
660 if (uvec2{elemSize.x, elemSize.y} != surfSize2) {
661 gfxCriticalError()
662 << "TexUnpackImage mismatched structuredSrcSize for dataSurf.";
663 return false;
667 const auto fullRows = elemSize.y;
668 return ValidateUnpackPixels(webgl, pi, fullRows, *this);
671 Maybe<std::string> BlitPreventReason(const int32_t level, const ivec3& offset,
672 const GLenum internalFormat,
673 const webgl::PackingInfo& pi,
674 const TexUnpackBlobDesc& desc,
675 const bool isRgb8Renderable) {
676 const auto& size = desc.size;
677 const auto& unpacking = desc.unpacking;
679 const auto ret = [&]() -> const char* {
680 if (size.z != 1) {
681 return "depth is not 1";
683 if (offset.x != 0 || offset.y != 0 || offset.z != 0) {
684 return "x/y/zOffset is not 0";
687 if (unpacking.skipPixels || unpacking.skipRows || unpacking.skipImages) {
688 return "non-zero UNPACK_SKIP_* not yet supported";
691 const auto premultReason = [&]() -> const char* {
692 if (desc.srcAlphaType == gfxAlphaType::Opaque) return nullptr;
694 const bool srcIsPremult = (desc.srcAlphaType == gfxAlphaType::Premult);
695 const auto& dstIsPremult = unpacking.premultiplyAlpha;
696 if (srcIsPremult == dstIsPremult) return nullptr;
698 if (dstIsPremult) {
699 return "UNPACK_PREMULTIPLY_ALPHA_WEBGL is not true";
700 } else {
701 return "UNPACK_PREMULTIPLY_ALPHA_WEBGL is not false";
703 }();
704 if (premultReason) return premultReason;
706 const auto formatReason = [&]() -> const char* {
707 if (pi.type != LOCAL_GL_UNSIGNED_BYTE) {
708 return "`type` must be `UNSIGNED_BYTE`";
711 switch (internalFormat) {
712 case LOCAL_GL_RGBA:
713 case LOCAL_GL_RGBA8:
714 return nullptr;
716 case LOCAL_GL_RGB:
717 case LOCAL_GL_RGB8:
718 if (isRgb8Renderable) {
719 return nullptr;
721 break;
723 if (isRgb8Renderable) {
724 return "effective format must be RGB8 or RGBA8";
725 } else {
726 return "effective format must be RGBA8";
728 }();
729 if (formatReason) return formatReason;
731 return nullptr;
732 }();
733 if (ret) {
734 return Some(std::string(ret));
736 return {};
739 bool TexUnpackImage::TexOrSubImage(bool isSubImage, bool needsRespec,
740 WebGLTexture* tex, GLint level,
741 const webgl::DriverUnpackInfo* dui,
742 GLint xOffset, GLint yOffset, GLint zOffset,
743 const webgl::PackingInfo& pi,
744 GLenum* const out_error) const {
745 MOZ_ASSERT_IF(needsRespec, !isSubImage);
747 const auto& webgl = tex->mContext;
748 const auto& target = mDesc.imageTarget;
749 const auto& size = mDesc.size;
750 const auto& sd = *(mDesc.sd);
751 const auto& unpacking = mDesc.unpacking;
753 const auto& gl = webgl->GL();
755 // -
757 const auto reason =
758 BlitPreventReason(level, {xOffset, yOffset, zOffset}, dui->internalFormat,
759 pi, mDesc, webgl->mIsRgb8Renderable);
760 if (reason) {
761 webgl->GeneratePerfWarning(
762 "Failed to hit GPU-copy fast-path."
763 " (%s) Falling back to CPU upload.",
764 reason->c_str());
765 return false;
768 // -
770 if (needsRespec) {
771 *out_error =
772 DoTexOrSubImage(isSubImage, gl, target, level, dui, xOffset, yOffset,
773 zOffset, size.x, size.y, size.z, nullptr);
774 if (*out_error) return true;
778 gl::ScopedFramebuffer scopedFB(gl);
779 gl::ScopedBindFramebuffer bindFB(gl, scopedFB.FB());
782 gl::GLContext::LocalErrorScope errorScope(*gl);
784 gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER,
785 LOCAL_GL_COLOR_ATTACHMENT0, target,
786 tex->mGLName, level);
788 const auto err = errorScope.GetError();
789 MOZ_ALWAYS_TRUE(!err);
792 const GLenum status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
793 MOZ_ALWAYS_TRUE(status == LOCAL_GL_FRAMEBUFFER_COMPLETE);
795 const auto dstOrigin =
796 (unpacking.flipY ? gl::OriginPos::TopLeft : gl::OriginPos::BottomLeft);
797 if (!gl->BlitHelper()->BlitSdToFramebuffer(sd, {size.x, size.y},
798 dstOrigin)) {
799 gfxCriticalNote << "BlitSdToFramebuffer failed for type "
800 << int(sd.type());
801 // Maybe the resource isn't valid anymore?
802 gl->fClearColor(0.2, 0.0, 0.2, 1.0);
803 gl->fClear(LOCAL_GL_COLOR_BUFFER_BIT);
804 const auto& cur = webgl->mColorClearValue;
805 gl->fClearColor(cur[0], cur[1], cur[2], cur[3]);
806 webgl->GenerateWarning(
807 "Fast Tex(Sub)Image upload failed without recourse, clearing to "
808 "[0.2, 0.0, 0.2, 1.0]. Please file a bug!");
812 return true;
815 ////////////////////////////////////////////////////////////////////////////////
816 ////////////////////////////////////////////////////////////////////////////////
817 // TexUnpackSurface
819 TexUnpackSurface::~TexUnpackSurface() = default;
821 //////////
823 static bool GetFormatForSurf(const gfx::SourceSurface* surf,
824 WebGLTexelFormat* const out_texelFormat,
825 uint8_t* const out_bpp) {
826 const auto surfFormat = surf->GetFormat();
827 switch (surfFormat) {
828 case gfx::SurfaceFormat::B8G8R8A8:
829 *out_texelFormat = WebGLTexelFormat::BGRA8;
830 *out_bpp = 4;
831 return true;
833 case gfx::SurfaceFormat::B8G8R8X8:
834 *out_texelFormat = WebGLTexelFormat::BGRX8;
835 *out_bpp = 4;
836 return true;
838 case gfx::SurfaceFormat::R8G8B8A8:
839 *out_texelFormat = WebGLTexelFormat::RGBA8;
840 *out_bpp = 4;
841 return true;
843 case gfx::SurfaceFormat::R8G8B8X8:
844 *out_texelFormat = WebGLTexelFormat::RGBX8;
845 *out_bpp = 4;
846 return true;
848 case gfx::SurfaceFormat::R5G6B5_UINT16:
849 *out_texelFormat = WebGLTexelFormat::RGB565;
850 *out_bpp = 2;
851 return true;
853 case gfx::SurfaceFormat::A8:
854 *out_texelFormat = WebGLTexelFormat::A8;
855 *out_bpp = 1;
856 return true;
858 case gfx::SurfaceFormat::YUV:
859 // Ugh...
860 NS_ERROR("We don't handle uploads from YUV sources yet.");
861 // When we want to, check out gfx/ycbcr/YCbCrUtils.h. (specifically
862 // GetYCbCrToRGBDestFormatAndSize and ConvertYCbCrToRGB)
863 return false;
865 default:
866 return false;
870 //////////
872 bool TexUnpackSurface::Validate(const WebGLContext* const webgl,
873 const webgl::PackingInfo& pi) {
874 if (!ValidatePIForDOM(webgl, pi)) return false;
876 if (!mDesc.structuredSrcSize) {
877 gfxCriticalError() << "TexUnpackSurface missing structuredSrcSize.";
878 return false;
880 const auto& elemSize = *mDesc.structuredSrcSize;
881 if (mDesc.dataSurf) {
882 const auto& surfSize = mDesc.dataSurf->GetSize();
883 const auto surfSize2 = ivec2::FromSize(surfSize)->StaticCast<uvec2>();
884 if (uvec2{elemSize.x, elemSize.y} != surfSize2) {
885 gfxCriticalError()
886 << "TexUnpackSurface mismatched structuredSrcSize for dataSurf.";
887 return false;
891 const auto fullRows = elemSize.y;
892 return ValidateUnpackPixels(webgl, pi, fullRows, *this);
895 bool TexUnpackSurface::TexOrSubImage(bool isSubImage, bool needsRespec,
896 WebGLTexture* tex, GLint level,
897 const webgl::DriverUnpackInfo* dui,
898 GLint xOffset, GLint yOffset,
899 GLint zOffset,
900 const webgl::PackingInfo& dstPI,
901 GLenum* const out_error) const {
902 const auto& webgl = tex->mContext;
903 const auto& size = mDesc.size;
904 RefPtr<gfx::DataSourceSurface> surf;
905 if (mDesc.sd) {
906 // If we get here, we assume the SD describes an RGBA Shmem.
907 const auto& sd = *(mDesc.sd);
908 if (SDIsRGBBuffer(sd)) {
909 const auto& sdb = sd.get_SurfaceDescriptorBuffer();
910 const auto& rgb = sdb.desc().get_RGBDescriptor();
911 const auto& data = sdb.data();
912 MOZ_ASSERT(data.type() == layers::MemoryOrShmem::TShmem);
913 const auto& shmem = data.get_Shmem();
914 surf = gfx::Factory::CreateWrappingDataSourceSurface(
915 shmem.get<uint8_t>(), layers::ImageDataSerializer::GetRGBStride(rgb),
916 rgb.size(), rgb.format());
917 } else if (SDIsNullRemoteDecoder(sd)) {
918 const auto& sdrd = sd.get_SurfaceDescriptorGPUVideo()
919 .get_SurfaceDescriptorRemoteDecoder();
920 RefPtr<layers::VideoBridgeParent> parent =
921 layers::VideoBridgeParent::GetSingleton(sdrd.source());
922 if (!parent) {
923 gfxCriticalNote << "TexUnpackSurface failed to get VideoBridgeParent";
924 return false;
926 RefPtr<layers::TextureHost> texture =
927 parent->LookupTexture(webgl->GetContentId(), sdrd.handle());
928 if (!texture) {
929 gfxCriticalNote << "TexUnpackSurface failed to get TextureHost";
930 return false;
932 surf = texture->GetAsSurface();
933 } else {
934 MOZ_ASSERT_UNREACHABLE("Unexpected surface descriptor!");
936 if (!surf) {
937 gfxCriticalError() << "TexUnpackSurface failed to create wrapping "
938 "DataSourceSurface for Shmem.";
939 return false;
941 } else {
942 surf = mDesc.dataSurf;
945 ////
947 WebGLTexelFormat srcFormat;
948 uint8_t srcBPP;
949 if (!GetFormatForSurf(surf, &srcFormat, &srcBPP)) {
950 webgl->ErrorImplementationBug(
951 "GetFormatForSurf failed for"
952 " WebGLTexelFormat::%u.",
953 uint32_t(surf->GetFormat()));
954 return false;
957 gfx::DataSourceSurface::ScopedMap map(surf,
958 gfx::DataSourceSurface::MapType::READ);
959 if (!map.IsMapped()) {
960 webgl->ErrorOutOfMemory("Failed to map source surface for upload.");
961 return false;
964 const auto& srcBegin = map.GetData();
965 const auto srcStride = static_cast<size_t>(map.GetStride());
967 // -
969 const auto dstFormat = FormatForPackingInfo(dstPI);
970 const auto dstBpp = BytesPerPixel(dstPI);
971 const size_t dstUsedBytesPerRow = dstBpp * surf->GetSize().width;
972 auto dstStride = dstUsedBytesPerRow;
973 if (dstFormat == srcFormat) {
974 dstStride = srcStride; // Try to match.
977 // -
979 auto dstUnpackingRes = mDesc.ExplicitUnpacking(dstPI, Some(dstStride));
980 if (dstUnpackingRes.isOk()) {
981 const auto& dstUnpacking = dstUnpackingRes.inspect();
982 if (!webgl->IsWebGL2() && dstUnpacking.state.rowLength != size.x) {
983 dstUnpackingRes = Err("WebGL1 can't handle rowLength != size.x");
986 if (!dstUnpackingRes.isOk()) {
987 dstStride = dstUsedBytesPerRow;
988 dstUnpackingRes = mDesc.ExplicitUnpacking(dstPI, Some(dstStride));
990 if (!dstUnpackingRes.isOk()) {
991 gfxCriticalError() << dstUnpackingRes.inspectErr();
992 webgl->ErrorImplementationBug("ExplicitUnpacking failed: %s",
993 dstUnpackingRes.inspectErr().c_str());
994 return false;
996 const auto& dstUnpacking = dstUnpackingRes.inspect();
997 MOZ_ASSERT(dstUnpacking.metrics.bytesPerRowStride == dstStride);
999 // -
1001 const uint8_t* dstBegin = srcBegin;
1002 UniqueBuffer tempBuffer;
1003 // clang-format off
1004 if (!ConvertIfNeeded(webgl, surf->GetSize().width, surf->GetSize().height,
1005 srcFormat, srcBegin, AutoAssertCast(srcStride),
1006 dstFormat, AutoAssertCast(dstUnpacking.metrics.bytesPerRowStride), &dstBegin,
1007 &tempBuffer)) {
1008 return false;
1010 // clang-format on
1012 ////
1014 const auto& gl = webgl->gl;
1015 if (!gl->MakeCurrent()) {
1016 *out_error = LOCAL_GL_CONTEXT_LOST;
1017 return true;
1020 dstUnpacking.state.ApplyUnpack(*gl, webgl->IsWebGL2(), size);
1022 *out_error =
1023 DoTexOrSubImage(isSubImage, gl, mDesc.imageTarget, level, dui, xOffset,
1024 yOffset, zOffset, size.x, size.y, size.z, dstBegin);
1026 // Caller will reset all our modified PixelStorei state.
1028 return true;
1031 } // namespace webgl
1032 } // namespace mozilla