Bug 1886946: Remove incorrect assertion that buffer is not-pinned. r=sfink
[gecko.git] / dom / canvas / TexUnpackBlob.cpp
blobe13dc1c06487a62fd7a041e8d7d3de87b11d24a6
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(
672 const int32_t level, const ivec3& offset, const GLenum internalFormat,
673 const webgl::PackingInfo& pi, const TexUnpackBlobDesc& desc,
674 const OptionalRenderableFormatBits optionalRenderableFormatBits) {
675 const auto& size = desc.size;
676 const auto& unpacking = desc.unpacking;
678 const auto ret = [&]() -> const char* {
679 if (size.z != 1) {
680 return "depth is not 1";
682 if (offset.x != 0 || offset.y != 0 || offset.z != 0) {
683 return "x/y/zOffset is not 0";
686 if (unpacking.skipPixels || unpacking.skipRows || unpacking.skipImages) {
687 return "non-zero UNPACK_SKIP_* not yet supported";
690 const auto premultReason = [&]() -> const char* {
691 if (desc.srcAlphaType == gfxAlphaType::Opaque) return nullptr;
693 const bool srcIsPremult = (desc.srcAlphaType == gfxAlphaType::Premult);
694 const auto& dstIsPremult = unpacking.premultiplyAlpha;
695 if (srcIsPremult == dstIsPremult) return nullptr;
697 if (dstIsPremult) {
698 return "UNPACK_PREMULTIPLY_ALPHA_WEBGL is not true";
699 } else {
700 return "UNPACK_PREMULTIPLY_ALPHA_WEBGL is not false";
702 }();
703 if (premultReason) return premultReason;
705 const auto formatReason = [&]() -> const char* {
706 if (pi.type != LOCAL_GL_UNSIGNED_BYTE) {
707 return "`unpackType` must be `UNSIGNED_BYTE`";
710 switch (pi.format) {
711 case LOCAL_GL_RGBA:
712 return nullptr; // All internalFormats for unpackFormat=RGBA are
713 // renderable.
715 case LOCAL_GL_RGB:
716 break;
718 default:
719 return "`unpackFormat` must be `RGBA` or maybe `RGB`";
722 // -
724 struct {
725 OptionalRenderableFormatBits bits;
726 const char* errorMsg;
727 } required;
729 switch (internalFormat) {
730 case LOCAL_GL_RGB565:
731 return nullptr;
732 case LOCAL_GL_RGB:
733 case LOCAL_GL_RGB8:
734 required = {
735 OptionalRenderableFormatBits::RGB8,
736 "Unavailable, as blitting internalFormats RGB or RGB8 requires "
737 "that RGB8 must be a renderable format.",
739 break;
740 case LOCAL_GL_SRGB:
741 case LOCAL_GL_SRGB8:
742 required = {
743 OptionalRenderableFormatBits::SRGB8,
744 "Unavailable, as blitting internalFormats SRGB or SRGB8 requires "
745 "that SRGB8 must be a renderable format.",
747 break;
748 case 0:
749 // texSubImage, so internalFormat is unknown, and could be anything!
750 required = {
751 OptionalRenderableFormatBits::RGB8 |
752 OptionalRenderableFormatBits::SRGB8,
753 "Unavailable, as blitting texSubImage with unpackFormat=RGB "
754 "requires that RGB8 and SRGB8 must be renderable formats.",
756 break;
757 default:
758 gfxCriticalError()
759 << "Unexpected internalFormat for unpackFormat=RGB: 0x"
760 << gfx::hexa(internalFormat);
761 return "Unexpected internalFormat for unpackFormat=RGB";
764 const auto availableBits = optionalRenderableFormatBits;
765 if ((required.bits | availableBits) != availableBits) {
766 return required.errorMsg;
769 // -
771 return nullptr;
772 }();
773 if (formatReason) return formatReason;
775 return nullptr;
776 }();
777 if (ret) {
778 return Some(std::string(ret));
780 return {};
783 bool TexUnpackImage::TexOrSubImage(bool isSubImage, bool needsRespec,
784 WebGLTexture* tex, GLint level,
785 const webgl::DriverUnpackInfo* dui,
786 GLint xOffset, GLint yOffset, GLint zOffset,
787 const webgl::PackingInfo& pi,
788 GLenum* const out_error) const {
789 MOZ_ASSERT_IF(needsRespec, !isSubImage);
791 const auto& webgl = tex->mContext;
792 const auto& target = mDesc.imageTarget;
793 const auto& size = mDesc.size;
794 const auto& sd = *(mDesc.sd);
795 const auto& unpacking = mDesc.unpacking;
797 const auto& gl = webgl->GL();
799 // -
801 const auto reason =
802 BlitPreventReason(level, {xOffset, yOffset, zOffset}, dui->internalFormat,
803 pi, mDesc, webgl->mOptionalRenderableFormatBits);
804 if (reason) {
805 webgl->GeneratePerfWarning(
806 "Failed to hit GPU-copy fast-path."
807 " (%s) Falling back to CPU upload.",
808 reason->c_str());
809 return false;
812 // -
814 if (needsRespec) {
815 *out_error =
816 DoTexOrSubImage(isSubImage, gl, target, level, dui, xOffset, yOffset,
817 zOffset, size.x, size.y, size.z, nullptr);
818 if (*out_error) return true;
822 gl::ScopedFramebuffer scopedFB(gl);
823 gl::ScopedBindFramebuffer bindFB(gl, scopedFB.FB());
826 gl::GLContext::LocalErrorScope errorScope(*gl);
828 gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER,
829 LOCAL_GL_COLOR_ATTACHMENT0, target,
830 tex->mGLName, level);
832 const auto err = errorScope.GetError();
833 MOZ_ALWAYS_TRUE(!err);
836 const GLenum status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
837 MOZ_ALWAYS_TRUE(status == LOCAL_GL_FRAMEBUFFER_COMPLETE);
839 const auto dstOrigin =
840 (unpacking.flipY ? gl::OriginPos::TopLeft : gl::OriginPos::BottomLeft);
841 if (!gl->BlitHelper()->BlitSdToFramebuffer(sd, {size.x, size.y},
842 dstOrigin)) {
843 gfxCriticalNote << "BlitSdToFramebuffer failed for type "
844 << int(sd.type());
845 // Maybe the resource isn't valid anymore?
846 gl->fClearColor(0.2, 0.0, 0.2, 1.0);
847 gl->fClear(LOCAL_GL_COLOR_BUFFER_BIT);
848 const auto& cur = webgl->mColorClearValue;
849 gl->fClearColor(cur[0], cur[1], cur[2], cur[3]);
850 webgl->GenerateWarning(
851 "Fast Tex(Sub)Image upload failed without recourse, clearing to "
852 "[0.2, 0.0, 0.2, 1.0]. Please file a bug!");
856 return true;
859 ////////////////////////////////////////////////////////////////////////////////
860 ////////////////////////////////////////////////////////////////////////////////
861 // TexUnpackSurface
863 TexUnpackSurface::~TexUnpackSurface() = default;
865 //////////
867 static bool GetFormatForSurf(const gfx::SourceSurface* surf,
868 WebGLTexelFormat* const out_texelFormat,
869 uint8_t* const out_bpp) {
870 const auto surfFormat = surf->GetFormat();
871 switch (surfFormat) {
872 case gfx::SurfaceFormat::B8G8R8A8:
873 *out_texelFormat = WebGLTexelFormat::BGRA8;
874 *out_bpp = 4;
875 return true;
877 case gfx::SurfaceFormat::B8G8R8X8:
878 *out_texelFormat = WebGLTexelFormat::BGRX8;
879 *out_bpp = 4;
880 return true;
882 case gfx::SurfaceFormat::R8G8B8A8:
883 *out_texelFormat = WebGLTexelFormat::RGBA8;
884 *out_bpp = 4;
885 return true;
887 case gfx::SurfaceFormat::R8G8B8X8:
888 *out_texelFormat = WebGLTexelFormat::RGBX8;
889 *out_bpp = 4;
890 return true;
892 case gfx::SurfaceFormat::R5G6B5_UINT16:
893 *out_texelFormat = WebGLTexelFormat::RGB565;
894 *out_bpp = 2;
895 return true;
897 case gfx::SurfaceFormat::A8:
898 *out_texelFormat = WebGLTexelFormat::A8;
899 *out_bpp = 1;
900 return true;
902 case gfx::SurfaceFormat::YUV:
903 // Ugh...
904 NS_ERROR("We don't handle uploads from YUV sources yet.");
905 // When we want to, check out gfx/ycbcr/YCbCrUtils.h. (specifically
906 // GetYCbCrToRGBDestFormatAndSize and ConvertYCbCrToRGB)
907 return false;
909 default:
910 return false;
914 //////////
916 bool TexUnpackSurface::Validate(const WebGLContext* const webgl,
917 const webgl::PackingInfo& pi) {
918 if (!ValidatePIForDOM(webgl, pi)) return false;
920 if (!mDesc.structuredSrcSize) {
921 gfxCriticalError() << "TexUnpackSurface missing structuredSrcSize.";
922 return false;
924 const auto& elemSize = *mDesc.structuredSrcSize;
925 if (mDesc.dataSurf) {
926 const auto& surfSize = mDesc.dataSurf->GetSize();
927 const auto surfSize2 = ivec2::FromSize(surfSize)->StaticCast<uvec2>();
928 if (uvec2{elemSize.x, elemSize.y} != surfSize2) {
929 gfxCriticalError()
930 << "TexUnpackSurface mismatched structuredSrcSize for dataSurf.";
931 return false;
935 const auto fullRows = elemSize.y;
936 return ValidateUnpackPixels(webgl, pi, fullRows, *this);
939 bool TexUnpackSurface::TexOrSubImage(bool isSubImage, bool needsRespec,
940 WebGLTexture* tex, GLint level,
941 const webgl::DriverUnpackInfo* dui,
942 GLint xOffset, GLint yOffset,
943 GLint zOffset,
944 const webgl::PackingInfo& dstPI,
945 GLenum* const out_error) const {
946 const auto& webgl = tex->mContext;
947 const auto& size = mDesc.size;
948 RefPtr<gfx::DataSourceSurface> surf;
949 if (mDesc.sd) {
950 // If we get here, we assume the SD describes an RGBA Shmem.
951 const auto& sd = *(mDesc.sd);
952 if (SDIsRGBBuffer(sd)) {
953 const auto& sdb = sd.get_SurfaceDescriptorBuffer();
954 const auto& rgb = sdb.desc().get_RGBDescriptor();
955 const auto& data = sdb.data();
956 MOZ_ASSERT(data.type() == layers::MemoryOrShmem::TShmem);
957 const auto& shmem = data.get_Shmem();
958 surf = gfx::Factory::CreateWrappingDataSourceSurface(
959 shmem.get<uint8_t>(), layers::ImageDataSerializer::GetRGBStride(rgb),
960 rgb.size(), rgb.format());
961 } else if (SDIsNullRemoteDecoder(sd)) {
962 const auto& sdrd = sd.get_SurfaceDescriptorGPUVideo()
963 .get_SurfaceDescriptorRemoteDecoder();
964 RefPtr<layers::VideoBridgeParent> parent =
965 layers::VideoBridgeParent::GetSingleton(sdrd.source());
966 if (!parent) {
967 gfxCriticalNote << "TexUnpackSurface failed to get VideoBridgeParent";
968 return false;
970 RefPtr<layers::TextureHost> texture =
971 parent->LookupTexture(webgl->GetContentId(), sdrd.handle());
972 if (!texture) {
973 gfxCriticalNote << "TexUnpackSurface failed to get TextureHost";
974 return false;
976 surf = texture->GetAsSurface();
977 } else {
978 MOZ_ASSERT_UNREACHABLE("Unexpected surface descriptor!");
980 if (!surf) {
981 gfxCriticalError() << "TexUnpackSurface failed to create wrapping "
982 "DataSourceSurface for Shmem.";
983 return false;
985 } else {
986 surf = mDesc.dataSurf;
989 ////
991 WebGLTexelFormat srcFormat;
992 uint8_t srcBPP;
993 if (!GetFormatForSurf(surf, &srcFormat, &srcBPP)) {
994 webgl->ErrorImplementationBug(
995 "GetFormatForSurf failed for"
996 " WebGLTexelFormat::%u.",
997 uint32_t(surf->GetFormat()));
998 return false;
1001 gfx::DataSourceSurface::ScopedMap map(surf,
1002 gfx::DataSourceSurface::MapType::READ);
1003 if (!map.IsMapped()) {
1004 webgl->ErrorOutOfMemory("Failed to map source surface for upload.");
1005 return false;
1008 const auto& srcBegin = map.GetData();
1009 const auto srcStride = static_cast<size_t>(map.GetStride());
1011 // -
1013 const auto dstFormat = FormatForPackingInfo(dstPI);
1014 const auto dstBpp = BytesPerPixel(dstPI);
1015 const size_t dstUsedBytesPerRow = dstBpp * surf->GetSize().width;
1016 auto dstStride = dstUsedBytesPerRow;
1017 if (dstFormat == srcFormat) {
1018 dstStride = srcStride; // Try to match.
1021 // -
1023 auto dstUnpackingRes = mDesc.ExplicitUnpacking(dstPI, Some(dstStride));
1024 if (dstUnpackingRes.isOk()) {
1025 const auto& dstUnpacking = dstUnpackingRes.inspect();
1026 if (!webgl->IsWebGL2() && dstUnpacking.state.rowLength != size.x) {
1027 dstUnpackingRes = Err("WebGL1 can't handle rowLength != size.x");
1030 if (!dstUnpackingRes.isOk()) {
1031 dstStride = dstUsedBytesPerRow;
1032 dstUnpackingRes = mDesc.ExplicitUnpacking(dstPI, Some(dstStride));
1034 if (!dstUnpackingRes.isOk()) {
1035 gfxCriticalError() << dstUnpackingRes.inspectErr();
1036 webgl->ErrorImplementationBug("ExplicitUnpacking failed: %s",
1037 dstUnpackingRes.inspectErr().c_str());
1038 return false;
1040 const auto& dstUnpacking = dstUnpackingRes.inspect();
1041 MOZ_ASSERT(dstUnpacking.metrics.bytesPerRowStride == dstStride);
1043 // -
1045 const uint8_t* dstBegin = srcBegin;
1046 UniqueBuffer tempBuffer;
1047 // clang-format off
1048 if (!ConvertIfNeeded(webgl, surf->GetSize().width, surf->GetSize().height,
1049 srcFormat, srcBegin, AutoAssertCast(srcStride),
1050 dstFormat, AutoAssertCast(dstUnpacking.metrics.bytesPerRowStride), &dstBegin,
1051 &tempBuffer)) {
1052 return false;
1054 // clang-format on
1056 ////
1058 const auto& gl = webgl->gl;
1059 if (!gl->MakeCurrent()) {
1060 *out_error = LOCAL_GL_CONTEXT_LOST;
1061 return true;
1064 dstUnpacking.state.ApplyUnpack(*gl, webgl->IsWebGL2(), size);
1066 *out_error =
1067 DoTexOrSubImage(isSubImage, gl, mDesc.imageTarget, level, dui, xOffset,
1068 yOffset, zOffset, size.x, size.y, size.z, dstBegin);
1070 // Caller will reset all our modified PixelStorei state.
1072 return true;
1075 } // namespace webgl
1076 } // namespace mozilla