Bug 1671598 [wpt PR 26128] - [AspectRatio] Fix divide by zero with a small float...
[gecko.git] / dom / canvas / WebGLTextureUpload.cpp
blobcf4d88e0d43784353361dd6cb5687d63779dafba
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 "WebGLTexture.h"
8 #include <algorithm>
9 #include <limits>
11 #include "CanvasUtils.h"
12 #include "ClientWebGLContext.h"
13 #include "GLBlitHelper.h"
14 #include "GLContext.h"
15 #include "mozilla/Casting.h"
16 #include "mozilla/gfx/2D.h"
17 #include "mozilla/dom/HTMLCanvasElement.h"
18 #include "mozilla/dom/HTMLVideoElement.h"
19 #include "mozilla/dom/ImageBitmap.h"
20 #include "mozilla/dom/ImageData.h"
21 #include "mozilla/MathAlgorithms.h"
22 #include "mozilla/Scoped.h"
23 #include "mozilla/StaticPrefs_webgl.h"
24 #include "mozilla/Unused.h"
25 #include "nsLayoutUtils.h"
26 #include "ScopedGLHelpers.h"
27 #include "TexUnpackBlob.h"
28 #include "WebGLBuffer.h"
29 #include "WebGLContext.h"
30 #include "WebGLContextUtils.h"
31 #include "WebGLFramebuffer.h"
32 #include "WebGLTexelConversions.h"
34 namespace mozilla {
35 namespace webgl {
37 Maybe<TexUnpackBlobDesc> FromImageBitmap(const GLenum target, uvec3 size,
38 const dom::ImageBitmap& imageBitmap,
39 ErrorResult* const out_rv) {
40 if (imageBitmap.IsWriteOnly()) {
41 out_rv->Throw(NS_ERROR_DOM_SECURITY_ERR);
42 return {};
45 const auto cloneData = imageBitmap.ToCloneData();
46 if (!cloneData) {
47 return {};
50 const RefPtr<gfx::DataSourceSurface> surf = cloneData->mSurface;
52 if (!size.x) {
53 size.x = surf->GetSize().width;
56 if (!size.y) {
57 size.y = surf->GetSize().height;
60 // WhatWG "HTML Living Standard" (30 October 2015):
61 // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned as
62 // non-premultiplied alpha values."
63 return Some(
64 TexUnpackBlobDesc{target, size, cloneData->mAlphaType, {}, {}, {}, surf});
67 TexUnpackBlobDesc FromImageData(const GLenum target, uvec3 size,
68 const dom::ImageData& imageData,
69 dom::Uint8ClampedArray* const scopedArr) {
70 MOZ_RELEASE_ASSERT(scopedArr->Init(imageData.GetDataObject()));
71 scopedArr->ComputeState();
72 const size_t dataSize = scopedArr->Length();
73 const auto data = reinterpret_cast<uint8_t*>(scopedArr->Data());
75 const gfx::IntSize imageSize(imageData.Width(), imageData.Height());
76 const size_t stride = imageSize.width * 4;
77 const gfx::SurfaceFormat surfFormat = gfx::SurfaceFormat::R8G8B8A8;
78 MOZ_ALWAYS_TRUE(dataSize == stride * imageSize.height);
80 const RefPtr<gfx::DataSourceSurface> surf =
81 gfx::Factory::CreateWrappingDataSourceSurface(data, stride, imageSize,
82 surfFormat);
83 MOZ_ASSERT(surf);
85 ////
87 if (!size.x) {
88 size.x = imageData.Width();
91 if (!size.y) {
92 size.y = imageData.Height();
95 ////
97 // WhatWG "HTML Living Standard" (30 October 2015):
98 // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned as
99 // non-premultiplied alpha values."
100 return {target, size, gfxAlphaType::NonPremult, {}, {}, {}, surf};
103 Maybe<webgl::TexUnpackBlobDesc> FromDomElem(const ClientWebGLContext& webgl,
104 const GLenum target, uvec3 size,
105 const dom::Element& elem,
106 const bool allowBlitImage,
107 ErrorResult* const out_error) {
108 const auto& canvas = *webgl.GetCanvas();
110 if (elem.IsHTMLElement(nsGkAtoms::canvas)) {
111 const dom::HTMLCanvasElement* srcCanvas =
112 static_cast<const dom::HTMLCanvasElement*>(&elem);
113 if (srcCanvas->IsWriteOnly()) {
114 out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
115 return {};
119 // The canvas spec says that drawImage should draw the first frame of
120 // animated images. The webgl spec doesn't mention the issue, so we do the
121 // same as drawImage.
122 uint32_t flags = nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE |
123 nsLayoutUtils::SFE_WANT_IMAGE_SURFACE |
124 nsLayoutUtils::SFE_USE_ELEMENT_SIZE_IF_VECTOR |
125 nsLayoutUtils::SFE_ALLOW_NON_PREMULT;
126 const auto& unpacking = webgl.State().mPixelUnpackState;
127 if (unpacking.mColorspaceConversion == LOCAL_GL_NONE) {
128 flags |= nsLayoutUtils::SFE_NO_COLORSPACE_CONVERSION;
131 RefPtr<gfx::DrawTarget> idealDrawTarget = nullptr; // Don't care for now.
132 auto sfer = nsLayoutUtils::SurfaceFromElement(
133 const_cast<dom::Element*>(&elem), flags, idealDrawTarget);
135 //////
137 uint32_t elemWidth = 0;
138 uint32_t elemHeight = 0;
139 layers::Image* layersImage = nullptr;
141 if (sfer.mLayersImage && allowBlitImage) {
142 layersImage = sfer.mLayersImage;
143 elemWidth = layersImage->GetSize().width;
144 elemHeight = layersImage->GetSize().height;
147 RefPtr<gfx::DataSourceSurface> dataSurf;
148 if (!layersImage && sfer.GetSourceSurface()) {
149 const auto surf = sfer.GetSourceSurface();
150 elemWidth = surf->GetSize().width;
151 elemHeight = surf->GetSize().height;
153 // WARNING: OSX can lose our MakeCurrent here.
154 dataSurf = surf->GetDataSurface();
157 //////
159 if (!size.x) {
160 size.x = elemWidth;
163 if (!size.y) {
164 size.y = elemHeight;
167 ////
169 if (!layersImage && !dataSurf) {
170 return Some(TexUnpackBlobDesc{target, size, gfxAlphaType::NonPremult});
173 //////
175 // While it's counter-intuitive, the shape of the SFEResult API means that we
176 // should try to pull out a surface first, and then, if we do pull out a
177 // surface, check CORS/write-only/etc..
179 if (!sfer.mCORSUsed) {
180 auto& srcPrincipal = sfer.mPrincipal;
181 nsIPrincipal* dstPrincipal = canvas.NodePrincipal();
183 if (!dstPrincipal->Subsumes(srcPrincipal)) {
184 webgl.EnqueueWarning("Cross-origin elements require CORS.");
185 out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
186 return {};
190 if (sfer.mIsWriteOnly) {
191 // mIsWriteOnly defaults to true, and so will be true even if SFE merely
192 // failed. Thus we must test mIsWriteOnly after successfully retrieving an
193 // Image or SourceSurface.
194 webgl.EnqueueWarning("Element is write-only, thus cannot be uploaded.");
195 out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
196 return {};
199 //////
200 // Ok, we're good!
202 if (layersImage) {
203 return Some(
204 TexUnpackBlobDesc{target, size, sfer.mAlphaType, {}, {}, layersImage});
207 MOZ_ASSERT(dataSurf);
208 return Some(
209 TexUnpackBlobDesc{target, size, sfer.mAlphaType, {}, {}, {}, dataSurf});
212 } // namespace webgl
214 //////////////////////////////////////////////////////////////////////////////////////////
215 //////////////////////////////////////////////////////////////////////////////////////////
217 static bool ValidateTexImage(WebGLContext* webgl, WebGLTexture* texture,
218 TexImageTarget target, uint32_t level,
219 webgl::ImageInfo** const out_imageInfo) {
220 // Check level
221 if (level >= WebGLTexture::kMaxLevelCount) {
222 webgl->ErrorInvalidValue("`level` is too large.");
223 return false;
226 auto& imageInfo = texture->ImageInfoAt(target, level);
227 *out_imageInfo = &imageInfo;
228 return true;
231 // For *TexImage*
232 bool WebGLTexture::ValidateTexImageSpecification(
233 TexImageTarget target, uint32_t level, const uvec3& size,
234 webgl::ImageInfo** const out_imageInfo) {
235 if (mImmutable) {
236 mContext->ErrorInvalidOperation("Specified texture is immutable.");
237 return false;
240 // Do this early to validate `level`.
241 webgl::ImageInfo* imageInfo;
242 if (!ValidateTexImage(mContext, this, target, level, &imageInfo))
243 return false;
245 if (mTarget == LOCAL_GL_TEXTURE_CUBE_MAP && size.x != size.y) {
246 mContext->ErrorInvalidValue("Cube map images must be square.");
247 return false;
250 /* GLES 3.0.4, p133-134:
251 * GL_MAX_TEXTURE_SIZE is *not* the max allowed texture size. Rather, it is
252 * the max (width/height) size guaranteed not to generate an INVALID_VALUE for
253 * too-large dimensions. Sizes larger than GL_MAX_TEXTURE_SIZE *may or may
254 * not* result in an INVALID_VALUE, or possibly GL_OOM.
256 * However, we have needed to set our maximums lower in the past to prevent
257 * resource corruption. Therefore we have limits.maxTex2dSize, which is
258 * neither necessarily lower nor higher than MAX_TEXTURE_SIZE.
260 * Note that limits.maxTex2dSize must be >= than the advertized
261 * MAX_TEXTURE_SIZE. For simplicity, we advertize MAX_TEXTURE_SIZE as
262 * limits.maxTex2dSize.
265 uint32_t maxWidthHeight = 0;
266 uint32_t maxDepth = 0;
267 uint32_t maxLevel = 0;
269 const auto& limits = mContext->Limits();
270 MOZ_ASSERT(level <= 31);
271 switch (target.get()) {
272 case LOCAL_GL_TEXTURE_2D:
273 maxWidthHeight = limits.maxTex2dSize >> level;
274 maxDepth = 1;
275 maxLevel = CeilingLog2(limits.maxTex2dSize);
276 break;
278 case LOCAL_GL_TEXTURE_3D:
279 maxWidthHeight = limits.maxTex3dSize >> level;
280 maxDepth = maxWidthHeight;
281 maxLevel = CeilingLog2(limits.maxTex3dSize);
282 break;
284 case LOCAL_GL_TEXTURE_2D_ARRAY:
285 maxWidthHeight = limits.maxTex2dSize >> level;
286 // "The maximum number of layers for two-dimensional array textures
287 // (depth) must be at least MAX_ARRAY_TEXTURE_LAYERS for all levels."
288 maxDepth = limits.maxTexArrayLayers;
289 maxLevel = CeilingLog2(limits.maxTex2dSize);
290 break;
292 default: // cube maps
293 MOZ_ASSERT(IsCubeMap());
294 maxWidthHeight = limits.maxTexCubeSize >> level;
295 maxDepth = 1;
296 maxLevel = CeilingLog2(limits.maxTexCubeSize);
297 break;
300 if (level > maxLevel) {
301 mContext->ErrorInvalidValue("Requested level is not supported for target.");
302 return false;
305 if (size.x > maxWidthHeight || size.y > maxWidthHeight || size.z > maxDepth) {
306 mContext->ErrorInvalidValue("Requested size at this level is unsupported.");
307 return false;
311 /* GL ES Version 2.0.25 - 3.7.1 Texture Image Specification
312 * "If level is greater than zero, and either width or
313 * height is not a power-of-two, the error INVALID_VALUE is
314 * generated."
316 * This restriction does not apply to GL ES Version 3.0+.
318 bool requirePOT = (!mContext->IsWebGL2() && level != 0);
320 if (requirePOT) {
321 if (!IsPowerOfTwo(size.x) || !IsPowerOfTwo(size.y)) {
322 mContext->ErrorInvalidValue(
323 "For level > 0, width and height must be"
324 " powers of two.");
325 return false;
330 *out_imageInfo = imageInfo;
331 return true;
334 // For *TexSubImage*
335 bool WebGLTexture::ValidateTexImageSelection(
336 TexImageTarget target, uint32_t level, const uvec3& offset,
337 const uvec3& size, webgl::ImageInfo** const out_imageInfo) {
338 webgl::ImageInfo* imageInfo;
339 if (!ValidateTexImage(mContext, this, target, level, &imageInfo))
340 return false;
342 if (!imageInfo->IsDefined()) {
343 mContext->ErrorInvalidOperation(
344 "The specified TexImage has not yet been"
345 " specified.");
346 return false;
349 const auto totalX = CheckedUint32(offset.x) + size.x;
350 const auto totalY = CheckedUint32(offset.y) + size.y;
351 const auto totalZ = CheckedUint32(offset.z) + size.z;
353 if (!totalX.isValid() || totalX.value() > imageInfo->mWidth ||
354 !totalY.isValid() || totalY.value() > imageInfo->mHeight ||
355 !totalZ.isValid() || totalZ.value() > imageInfo->mDepth) {
356 mContext->ErrorInvalidValue(
357 "Offset+size must be <= the size of the existing"
358 " specified image.");
359 return false;
362 *out_imageInfo = imageInfo;
363 return true;
366 static bool ValidateCompressedTexUnpack(WebGLContext* webgl, const uvec3& size,
367 const webgl::FormatInfo* format,
368 size_t dataSize) {
369 auto compression = format->compression;
371 auto bytesPerBlock = compression->bytesPerBlock;
372 auto blockWidth = compression->blockWidth;
373 auto blockHeight = compression->blockHeight;
375 auto widthInBlocks = CheckedUint32(size.x) / blockWidth;
376 auto heightInBlocks = CheckedUint32(size.y) / blockHeight;
377 if (size.x % blockWidth) widthInBlocks += 1;
378 if (size.y % blockHeight) heightInBlocks += 1;
380 const CheckedUint32 blocksPerImage = widthInBlocks * heightInBlocks;
381 const CheckedUint32 bytesPerImage = bytesPerBlock * blocksPerImage;
382 const CheckedUint32 bytesNeeded = bytesPerImage * size.z;
384 if (!bytesNeeded.isValid()) {
385 webgl->ErrorOutOfMemory("Overflow while computing the needed buffer size.");
386 return false;
389 if (dataSize != bytesNeeded.value()) {
390 webgl->ErrorInvalidValue(
391 "Provided buffer's size must match expected size."
392 " (needs %u, has %zu)",
393 bytesNeeded.value(), dataSize);
394 return false;
397 return true;
400 static bool DoChannelsMatchForCopyTexImage(const webgl::FormatInfo* srcFormat,
401 const webgl::FormatInfo* dstFormat) {
402 // GLES 3.0.4 p140 Table 3.16 "Valid CopyTexImage source
403 // framebuffer/destination texture base internal format combinations."
405 switch (srcFormat->unsizedFormat) {
406 case webgl::UnsizedFormat::RGBA:
407 switch (dstFormat->unsizedFormat) {
408 case webgl::UnsizedFormat::A:
409 case webgl::UnsizedFormat::L:
410 case webgl::UnsizedFormat::LA:
411 case webgl::UnsizedFormat::R:
412 case webgl::UnsizedFormat::RG:
413 case webgl::UnsizedFormat::RGB:
414 case webgl::UnsizedFormat::RGBA:
415 return true;
416 default:
417 return false;
420 case webgl::UnsizedFormat::RGB:
421 switch (dstFormat->unsizedFormat) {
422 case webgl::UnsizedFormat::L:
423 case webgl::UnsizedFormat::R:
424 case webgl::UnsizedFormat::RG:
425 case webgl::UnsizedFormat::RGB:
426 return true;
427 default:
428 return false;
431 case webgl::UnsizedFormat::RG:
432 switch (dstFormat->unsizedFormat) {
433 case webgl::UnsizedFormat::L:
434 case webgl::UnsizedFormat::R:
435 case webgl::UnsizedFormat::RG:
436 return true;
437 default:
438 return false;
441 case webgl::UnsizedFormat::R:
442 switch (dstFormat->unsizedFormat) {
443 case webgl::UnsizedFormat::L:
444 case webgl::UnsizedFormat::R:
445 return true;
446 default:
447 return false;
450 default:
451 return false;
455 static bool EnsureImageDataInitializedForUpload(
456 WebGLTexture* tex, TexImageTarget target, uint32_t level,
457 const uvec3& offset, const uvec3& size, webgl::ImageInfo* imageInfo,
458 bool* const out_expectsInit = nullptr) {
459 if (out_expectsInit) {
460 *out_expectsInit = false;
462 if (!imageInfo->mUninitializedSlices) return true;
464 if (size.x == imageInfo->mWidth && size.y == imageInfo->mHeight) {
465 bool expectsInit = false;
466 auto& isSliceUninit = *imageInfo->mUninitializedSlices;
467 for (const auto z : IntegerRange(offset.z, offset.z + size.z)) {
468 if (!isSliceUninit[z]) continue;
469 expectsInit = true;
470 isSliceUninit[z] = false;
472 if (out_expectsInit) {
473 *out_expectsInit = expectsInit;
476 if (!expectsInit) return true;
478 bool hasUninitialized = false;
479 for (const auto z : IntegerRange(imageInfo->mDepth)) {
480 hasUninitialized |= isSliceUninit[z];
482 if (!hasUninitialized) {
483 imageInfo->mUninitializedSlices = Nothing();
485 return true;
488 WebGLContext* webgl = tex->mContext;
489 webgl->GenerateWarning(
490 "Texture has not been initialized prior to a"
491 " partial upload, forcing the browser to clear it."
492 " This may be slow.");
493 if (!tex->EnsureImageDataInitialized(target, level)) {
494 MOZ_ASSERT(false, "Unexpected failure to init image data.");
495 return false;
498 return true;
501 //////////////////////////////////////////////////////////////////////////////////////////
502 //////////////////////////////////////////////////////////////////////////////////////////
503 // Actual calls
505 static inline GLenum DoTexStorage(gl::GLContext* gl, TexTarget target,
506 GLsizei levels, GLenum sizedFormat,
507 GLsizei width, GLsizei height,
508 GLsizei depth) {
509 gl::GLContext::LocalErrorScope errorScope(*gl);
511 switch (target.get()) {
512 case LOCAL_GL_TEXTURE_2D:
513 case LOCAL_GL_TEXTURE_CUBE_MAP:
514 MOZ_ASSERT(depth == 1);
515 gl->fTexStorage2D(target.get(), levels, sizedFormat, width, height);
516 break;
518 case LOCAL_GL_TEXTURE_3D:
519 case LOCAL_GL_TEXTURE_2D_ARRAY:
520 gl->fTexStorage3D(target.get(), levels, sizedFormat, width, height,
521 depth);
522 break;
524 default:
525 MOZ_CRASH("GFX: bad target");
528 return errorScope.GetError();
531 bool IsTarget3D(TexImageTarget target) {
532 switch (target.get()) {
533 case LOCAL_GL_TEXTURE_2D:
534 case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
535 case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
536 case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
537 case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
538 case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
539 case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
540 return false;
542 case LOCAL_GL_TEXTURE_3D:
543 case LOCAL_GL_TEXTURE_2D_ARRAY:
544 return true;
546 default:
547 MOZ_CRASH("GFX: bad target");
551 GLenum DoTexImage(gl::GLContext* gl, TexImageTarget target, GLint level,
552 const webgl::DriverUnpackInfo* dui, GLsizei width,
553 GLsizei height, GLsizei depth, const void* data) {
554 const GLint border = 0;
556 gl::GLContext::LocalErrorScope errorScope(*gl);
558 if (IsTarget3D(target)) {
559 gl->fTexImage3D(target.get(), level, dui->internalFormat, width, height,
560 depth, border, dui->unpackFormat, dui->unpackType, data);
561 } else {
562 MOZ_ASSERT(depth == 1);
563 gl->fTexImage2D(target.get(), level, dui->internalFormat, width, height,
564 border, dui->unpackFormat, dui->unpackType, data);
567 return errorScope.GetError();
570 GLenum DoTexSubImage(gl::GLContext* gl, TexImageTarget target, GLint level,
571 GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width,
572 GLsizei height, GLsizei depth,
573 const webgl::PackingInfo& pi, const void* data) {
574 gl::GLContext::LocalErrorScope errorScope(*gl);
576 if (IsTarget3D(target)) {
577 gl->fTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset, width,
578 height, depth, pi.format, pi.type, data);
579 } else {
580 MOZ_ASSERT(zOffset == 0);
581 MOZ_ASSERT(depth == 1);
582 gl->fTexSubImage2D(target.get(), level, xOffset, yOffset, width, height,
583 pi.format, pi.type, data);
586 return errorScope.GetError();
589 static inline GLenum DoCompressedTexImage(gl::GLContext* gl,
590 TexImageTarget target, GLint level,
591 GLenum internalFormat, GLsizei width,
592 GLsizei height, GLsizei depth,
593 GLsizei dataSize, const void* data) {
594 const GLint border = 0;
596 gl::GLContext::LocalErrorScope errorScope(*gl);
598 if (IsTarget3D(target)) {
599 gl->fCompressedTexImage3D(target.get(), level, internalFormat, width,
600 height, depth, border, dataSize, data);
601 } else {
602 MOZ_ASSERT(depth == 1);
603 gl->fCompressedTexImage2D(target.get(), level, internalFormat, width,
604 height, border, dataSize, data);
607 return errorScope.GetError();
610 GLenum DoCompressedTexSubImage(gl::GLContext* gl, TexImageTarget target,
611 GLint level, GLint xOffset, GLint yOffset,
612 GLint zOffset, GLsizei width, GLsizei height,
613 GLsizei depth, GLenum sizedUnpackFormat,
614 GLsizei dataSize, const void* data) {
615 gl::GLContext::LocalErrorScope errorScope(*gl);
617 if (IsTarget3D(target)) {
618 gl->fCompressedTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset,
619 width, height, depth, sizedUnpackFormat,
620 dataSize, data);
621 } else {
622 MOZ_ASSERT(zOffset == 0);
623 MOZ_ASSERT(depth == 1);
624 gl->fCompressedTexSubImage2D(target.get(), level, xOffset, yOffset, width,
625 height, sizedUnpackFormat, dataSize, data);
628 return errorScope.GetError();
631 static inline GLenum DoCopyTexSubImage(gl::GLContext* gl, TexImageTarget target,
632 GLint level, GLint xOffset,
633 GLint yOffset, GLint zOffset, GLint x,
634 GLint y, GLsizei width, GLsizei height) {
635 gl::GLContext::LocalErrorScope errorScope(*gl);
637 if (IsTarget3D(target)) {
638 gl->fCopyTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset, x, y,
639 width, height);
640 } else {
641 MOZ_ASSERT(zOffset == 0);
642 gl->fCopyTexSubImage2D(target.get(), level, xOffset, yOffset, x, y, width,
643 height);
646 return errorScope.GetError();
649 //////////////////////////////////////////////////////////////////////////////////////////
650 //////////////////////////////////////////////////////////////////////////////////////////
651 // Actual (mostly generic) function implementations
653 static bool ValidateCompressedTexImageRestrictions(
654 const WebGLContext* webgl, TexImageTarget target, uint32_t level,
655 const webgl::FormatInfo* format, const uvec3& size) {
656 const auto fnIsDimValid_S3TC = [level](uint32_t size, uint32_t blockSize) {
657 if (size % blockSize == 0) return true;
659 if (level == 0) return false;
661 return (size == 0 || size == 1 || size == 2);
664 switch (format->compression->family) {
665 case webgl::CompressionFamily::ASTC:
666 if (target == LOCAL_GL_TEXTURE_3D &&
667 !webgl->gl->IsExtensionSupported(
668 gl::GLContext::KHR_texture_compression_astc_hdr)) {
669 webgl->ErrorInvalidOperation("TEXTURE_3D requires ASTC's hdr profile.");
670 return false;
672 break;
674 case webgl::CompressionFamily::PVRTC:
675 if (!IsPowerOfTwo(size.x) || !IsPowerOfTwo(size.y)) {
676 webgl->ErrorInvalidValue("%s requires power-of-two width and height.",
677 format->name);
678 return false;
681 break;
683 case webgl::CompressionFamily::S3TC:
684 if (!fnIsDimValid_S3TC(size.x, format->compression->blockWidth) ||
685 !fnIsDimValid_S3TC(size.y, format->compression->blockHeight)) {
686 webgl->ErrorInvalidOperation(
687 "%s requires that width and height are"
688 " block-aligned, or, if level>0, equal to 0, 1,"
689 " or 2.",
690 format->name);
691 return false;
694 break;
696 // Default: There are no restrictions on CompressedTexImage.
697 default: // ETC1, ES3
698 break;
701 return true;
704 static bool ValidateTargetForFormat(const WebGLContext* webgl,
705 TexImageTarget target,
706 const webgl::FormatInfo* format) {
707 // GLES 3.0.4 p127:
708 // "Textures with a base internal format of DEPTH_COMPONENT or DEPTH_STENCIL
709 // are supported by texture image specification commands only if `target` is
710 // TEXTURE_2D, TEXTURE_2D_ARRAY, or TEXTURE_CUBE_MAP. Using these formats in
711 // conjunction with any other `target` will result in an INVALID_OPERATION
712 // error."
713 const bool ok = [&]() {
714 if (bool(format->d) & (target == LOCAL_GL_TEXTURE_3D)) return false;
716 if (format->compression) {
717 switch (format->compression->family) {
718 case webgl::CompressionFamily::ES3:
719 case webgl::CompressionFamily::S3TC:
720 if (target == LOCAL_GL_TEXTURE_3D) return false;
721 break;
723 case webgl::CompressionFamily::ETC1:
724 case webgl::CompressionFamily::PVRTC:
725 case webgl::CompressionFamily::RGTC:
726 if (target == LOCAL_GL_TEXTURE_3D ||
727 target == LOCAL_GL_TEXTURE_2D_ARRAY) {
728 return false;
730 break;
731 default:
732 break;
735 return true;
736 }();
737 if (!ok) {
738 webgl->ErrorInvalidOperation("Format %s cannot be used with target %s.",
739 format->name, GetEnumName(target.get()));
740 return false;
743 return true;
746 void WebGLTexture::TexStorage(TexTarget target, uint32_t levels,
747 GLenum sizedFormat, const uvec3& size) {
748 // Check levels
749 if (levels < 1) {
750 mContext->ErrorInvalidValue("`levels` must be >= 1.");
751 return;
754 if (!size.x || !size.y || !size.z) {
755 mContext->ErrorInvalidValue("Dimensions must be non-zero.");
756 return;
759 const TexImageTarget testTarget =
760 IsCubeMap() ? LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X : target.get();
761 webgl::ImageInfo* baseImageInfo;
762 if (!ValidateTexImageSpecification(testTarget, 0, size, &baseImageInfo)) {
763 return;
765 MOZ_ALWAYS_TRUE(baseImageInfo);
767 auto dstUsage = mContext->mFormatUsage->GetSizedTexUsage(sizedFormat);
768 if (!dstUsage) {
769 mContext->ErrorInvalidEnumInfo("internalformat", sizedFormat);
770 return;
772 auto dstFormat = dstUsage->format;
774 if (!ValidateTargetForFormat(mContext, testTarget, dstFormat)) return;
776 if (dstFormat->compression) {
777 if (!ValidateCompressedTexImageRestrictions(mContext, testTarget, 0,
778 dstFormat, size)) {
779 return;
783 ////////////////////////////////////
785 const bool levelsOk = [&]() {
786 // Right-shift is only defined for bits-1, which is too large anyways.
787 const auto lastLevel = uint32_t(levels - 1);
788 if (lastLevel > 31) return false;
790 const auto lastLevelWidth = uint32_t(size.x) >> lastLevel;
791 const auto lastLevelHeight = uint32_t(size.y) >> lastLevel;
793 // If these are all zero, then some earlier level was the final 1x1(x1)
794 // level.
795 bool ok = lastLevelWidth || lastLevelHeight;
796 if (target == LOCAL_GL_TEXTURE_3D) {
797 const auto lastLevelDepth = uint32_t(size.z) >> lastLevel;
798 ok |= bool(lastLevelDepth);
800 return ok;
801 }();
802 if (!levelsOk) {
803 mContext->ErrorInvalidOperation(
804 "Too many levels requested for the given"
805 " dimensions. (levels: %u, width: %u, height: %u,"
806 " depth: %u)",
807 levels, size.x, size.y, size.z);
808 return;
811 ////////////////////////////////////
812 // Do the thing!
814 GLenum error = DoTexStorage(mContext->gl, target.get(), levels, sizedFormat,
815 size.x, size.y, size.z);
817 mContext->OnDataAllocCall();
819 if (error == LOCAL_GL_OUT_OF_MEMORY) {
820 mContext->ErrorOutOfMemory("Ran out of memory during texture allocation.");
821 Truncate();
822 return;
824 if (error) {
825 mContext->GenerateError(error, "Unexpected error from driver.");
826 const nsPrintfCString call(
827 "DoTexStorage(0x%04x, %i, 0x%04x, %i,%i,%i) -> 0x%04x", target.get(),
828 levels, sizedFormat, size.x, size.y, size.z, error);
829 gfxCriticalError() << "Unexpected error from driver: "
830 << call.BeginReading();
831 return;
834 ////////////////////////////////////
835 // Update our specification data.
837 auto uninitializedSlices = Some(std::vector<bool>(size.z, true));
838 const webgl::ImageInfo newInfo{dstUsage, size.x, size.y, size.z,
839 std::move(uninitializedSlices)};
842 const auto base_level = mBaseMipmapLevel;
843 mBaseMipmapLevel = 0;
845 ImageInfoAtFace(0, 0) = newInfo;
846 PopulateMipChain(levels - 1);
848 mBaseMipmapLevel = base_level;
851 mImmutable = true;
852 mImmutableLevelCount = AutoAssertCast(levels);
853 ClampLevelBaseAndMax();
856 ////////////////////////////////////////
857 // Tex(Sub)Image
859 // TexSubImage iff `!respectFormat`
860 void WebGLTexture::TexImage(uint32_t level, GLenum respecFormat,
861 const uvec3& offset, const webgl::PackingInfo& pi,
862 const webgl::TexUnpackBlobDesc& src) {
863 Maybe<RawBuffer<>> cpuDataView;
864 if (src.cpuData) {
865 cpuDataView = Some(RawBuffer<>{src.cpuData->Data()});
867 const auto srcViewDesc = webgl::TexUnpackBlobDesc{
868 src.imageTarget, src.size, src.srcAlphaType, std::move(cpuDataView),
869 src.pboOffset, src.image, src.surf, src.unpacking};
870 const auto blob = webgl::TexUnpackBlob::Create(srcViewDesc);
871 if (!blob) {
872 MOZ_ASSERT(false);
873 return;
876 const auto imageTarget = blob->mDesc.imageTarget;
877 auto size = blob->mDesc.size;
879 if (!IsTarget3D(imageTarget)) {
880 size.z = 1;
883 ////////////////////////////////////
884 // Get dest info
886 const auto& fua = mContext->mFormatUsage;
887 const auto fnValidateUnpackEnums = [&]() {
888 if (!fua->AreUnpackEnumsValid(pi.format, pi.type)) {
889 mContext->ErrorInvalidEnum("Invalid unpack format/type: %s/%s",
890 EnumString(pi.format).c_str(),
891 EnumString(pi.type).c_str());
892 return false;
894 return true;
897 webgl::ImageInfo* imageInfo;
898 const webgl::FormatUsageInfo* dstUsage;
899 if (respecFormat) {
900 if (!ValidateTexImageSpecification(imageTarget, level, size, &imageInfo))
901 return;
902 MOZ_ASSERT(imageInfo);
904 if (!fua->IsInternalFormatEnumValid(respecFormat)) {
905 mContext->ErrorInvalidValue("Invalid internalformat: 0x%04x",
906 respecFormat);
907 return;
910 dstUsage = fua->GetSizedTexUsage(respecFormat);
911 if (!dstUsage) {
912 if (respecFormat != pi.format) {
913 /* GL ES Version 3.0.4 - 3.8.3 Texture Image Specification
914 * "Specifying a combination of values for format, type, and
915 * internalformat that is not listed as a valid combination
916 * in tables 3.2 or 3.3 generates the error INVALID_OPERATION."
918 if (!fnValidateUnpackEnums()) return;
919 mContext->ErrorInvalidOperation(
920 "Unsized internalFormat must match"
921 " unpack format.");
922 return;
925 dstUsage = fua->GetUnsizedTexUsage(pi);
928 if (!dstUsage) {
929 if (!fnValidateUnpackEnums()) return;
930 mContext->ErrorInvalidOperation(
931 "Invalid internalformat/format/type:"
932 " 0x%04x/0x%04x/0x%04x",
933 respecFormat, pi.format, pi.type);
934 return;
937 const auto& dstFormat = dstUsage->format;
938 if (!ValidateTargetForFormat(mContext, imageTarget, dstFormat)) return;
940 if (!mContext->IsWebGL2() && dstFormat->d) {
941 if (imageTarget != LOCAL_GL_TEXTURE_2D || blob->HasData() || level != 0) {
942 mContext->ErrorInvalidOperation(
943 "With format %s, this function may only"
944 " be called with target=TEXTURE_2D,"
945 " data=null, and level=0.",
946 dstFormat->name);
947 return;
950 } else {
951 if (!ValidateTexImageSelection(imageTarget, level, offset, size,
952 &imageInfo)) {
953 return;
955 MOZ_ASSERT(imageInfo);
956 dstUsage = imageInfo->mFormat;
958 const auto& dstFormat = dstUsage->format;
959 if (!mContext->IsWebGL2() && dstFormat->d) {
960 mContext->ErrorInvalidOperation(
961 "Function may not be called on a texture of"
962 " format %s.",
963 dstFormat->name);
964 return;
968 ////////////////////////////////////
969 // Get source info
971 const webgl::DriverUnpackInfo* driverUnpackInfo;
972 if (!dstUsage->IsUnpackValid(pi, &driverUnpackInfo)) {
973 if (!fnValidateUnpackEnums()) return;
974 mContext->ErrorInvalidOperation(
975 "Mismatched internalFormat and format/type:"
976 " 0x%04x and 0x%04x/0x%04x",
977 respecFormat, pi.format, pi.type);
978 return;
981 if (!blob->Validate(mContext, pi)) return;
983 ////////////////////////////////////
984 // Do the thing!
986 Maybe<webgl::ImageInfo> newImageInfo;
987 bool isRespec = false;
988 if (respecFormat) {
989 // It's tempting to do allocation first, and TexSubImage second, but this is
990 // generally slower.
991 newImageInfo = Some(webgl::ImageInfo{dstUsage, size.x, size.y, size.z});
992 if (!blob->HasData()) {
993 newImageInfo->mUninitializedSlices =
994 Some(std::vector<bool>(size.z, true));
997 isRespec = (imageInfo->mWidth != newImageInfo->mWidth ||
998 imageInfo->mHeight != newImageInfo->mHeight ||
999 imageInfo->mDepth != newImageInfo->mDepth ||
1000 imageInfo->mFormat != newImageInfo->mFormat);
1001 } else {
1002 if (!blob->HasData()) {
1003 mContext->ErrorInvalidValue("`source` cannot be null.");
1004 return;
1006 if (!EnsureImageDataInitializedForUpload(this, imageTarget, level, offset,
1007 size, imageInfo)) {
1008 return;
1012 WebGLPixelStore::AssertDefault(*mContext->gl, mContext->IsWebGL2());
1014 blob->mDesc.unpacking.Apply(*mContext->gl, mContext->IsWebGL2(), size);
1015 const auto revertUnpacking = MakeScopeExit([&]() {
1016 const WebGLPixelStore defaultUnpacking;
1017 defaultUnpacking.Apply(*mContext->gl, mContext->IsWebGL2(), size);
1020 const bool isSubImage = !respecFormat;
1021 GLenum glError;
1022 if (!blob->TexOrSubImage(isSubImage, isRespec, this, level, driverUnpackInfo,
1023 offset.x, offset.y, offset.z, pi, &glError)) {
1024 return;
1027 if (glError == LOCAL_GL_OUT_OF_MEMORY) {
1028 mContext->ErrorOutOfMemory("Driver ran out of memory during upload.");
1029 Truncate();
1030 return;
1033 if (glError) {
1034 const auto enumStr = EnumString(glError);
1035 const nsPrintfCString dui(
1036 "Unexpected error %s during upload. (dui: %x/%x/%x)", enumStr.c_str(),
1037 driverUnpackInfo->internalFormat, driverUnpackInfo->unpackFormat,
1038 driverUnpackInfo->unpackType);
1039 mContext->ErrorInvalidOperation("%s", dui.BeginReading());
1040 gfxCriticalError() << mContext->FuncName() << ": " << dui.BeginReading();
1041 return;
1044 ////////////////////////////////////
1045 // Update our specification data?
1047 if (respecFormat) {
1048 mContext->OnDataAllocCall();
1049 *imageInfo = *newImageInfo;
1050 InvalidateCaches();
1054 ////////////////////////////////////////
1055 // CompressedTex(Sub)Image
1057 static inline bool IsSubImageBlockAligned(
1058 const webgl::CompressedFormatInfo* compression,
1059 const webgl::ImageInfo* imageInfo, GLint xOffset, GLint yOffset,
1060 uint32_t width, uint32_t height) {
1061 if (xOffset % compression->blockWidth != 0 ||
1062 yOffset % compression->blockHeight != 0) {
1063 return false;
1066 if (width % compression->blockWidth != 0 &&
1067 xOffset + width != imageInfo->mWidth)
1068 return false;
1070 if (height % compression->blockHeight != 0 &&
1071 yOffset + height != imageInfo->mHeight)
1072 return false;
1074 return true;
1077 // CompressedTexSubImage iff `sub`
1078 void WebGLTexture::CompressedTexImage(bool sub, GLenum imageTarget,
1079 uint32_t level, GLenum formatEnum,
1080 const uvec3& offset, const uvec3& size,
1081 const Range<const uint8_t>& src,
1082 const uint32_t pboImageSize,
1083 const Maybe<uint64_t>& pboOffset) {
1084 auto imageSize = pboImageSize;
1085 if (pboOffset) {
1086 const auto& buffer =
1087 mContext->ValidateBufferSelection(LOCAL_GL_PIXEL_UNPACK_BUFFER);
1088 if (!buffer) return;
1089 auto availBytes = buffer->ByteLength();
1090 if (*pboOffset > availBytes) {
1091 mContext->GenerateError(
1092 LOCAL_GL_INVALID_OPERATION,
1093 "`offset` (%llu) must be <= PIXEL_UNPACK_BUFFER size (%llu).",
1094 *pboOffset, availBytes);
1095 return;
1097 availBytes -= *pboOffset;
1098 if (availBytes < pboImageSize) {
1099 mContext->GenerateError(
1100 LOCAL_GL_INVALID_OPERATION,
1101 "PIXEL_UNPACK_BUFFER size minus `offset` (%llu) too small for"
1102 " `pboImageSize` (%u).",
1103 availBytes, pboImageSize);
1104 return;
1106 } else {
1107 if (mContext->mBoundPixelUnpackBuffer) {
1108 mContext->GenerateError(LOCAL_GL_INVALID_OPERATION,
1109 "PIXEL_UNPACK_BUFFER is non-null.");
1110 return;
1112 imageSize = src.length();
1115 // -
1117 const auto usage = mContext->mFormatUsage->GetSizedTexUsage(formatEnum);
1118 if (!usage || !usage->format->compression) {
1119 mContext->ErrorInvalidEnumArg("format", formatEnum);
1120 return;
1123 webgl::ImageInfo* imageInfo;
1124 if (!sub) {
1125 if (!ValidateTexImageSpecification(imageTarget, level, size, &imageInfo)) {
1126 return;
1128 MOZ_ASSERT(imageInfo);
1130 if (!ValidateTargetForFormat(mContext, imageTarget, usage->format)) return;
1131 if (!ValidateCompressedTexImageRestrictions(mContext, imageTarget, level,
1132 usage->format, size)) {
1133 return;
1135 } else {
1136 if (!ValidateTexImageSelection(imageTarget, level, offset, size,
1137 &imageInfo))
1138 return;
1139 MOZ_ASSERT(imageInfo);
1141 const auto dstUsage = imageInfo->mFormat;
1142 if (usage != dstUsage) {
1143 mContext->ErrorInvalidOperation(
1144 "`format` must match the format of the"
1145 " existing texture image.");
1146 return;
1149 const auto& format = usage->format;
1150 switch (format->compression->family) {
1151 // Forbidden:
1152 case webgl::CompressionFamily::ETC1:
1153 mContext->ErrorInvalidOperation(
1154 "Format does not allow sub-image"
1155 " updates.");
1156 return;
1158 // Block-aligned:
1159 case webgl::CompressionFamily::ES3: // Yes, the ES3 formats don't match
1160 // the ES3
1161 case webgl::CompressionFamily::S3TC: // default behavior.
1162 case webgl::CompressionFamily::BPTC:
1163 case webgl::CompressionFamily::RGTC:
1164 if (!IsSubImageBlockAligned(format->compression, imageInfo, offset.x,
1165 offset.y, size.x, size.y)) {
1166 mContext->ErrorInvalidOperation(
1167 "Format requires block-aligned sub-image"
1168 " updates.");
1169 return;
1171 break;
1173 // Full-only: (The ES3 default)
1174 default: // PVRTC
1175 if (offset.x || offset.y || size.x != imageInfo->mWidth ||
1176 size.y != imageInfo->mHeight) {
1177 mContext->ErrorInvalidOperation(
1178 "Format does not allow partial sub-image"
1179 " updates.");
1180 return;
1182 break;
1186 if (!ValidateCompressedTexUnpack(mContext, size, usage->format, imageSize))
1187 return;
1189 ////////////////////////////////////
1190 // Do the thing!
1192 if (sub) {
1193 if (!EnsureImageDataInitializedForUpload(this, imageTarget, level, offset,
1194 size, imageInfo)) {
1195 return;
1199 const ScopedLazyBind bindPBO(mContext->gl, LOCAL_GL_PIXEL_UNPACK_BUFFER,
1200 mContext->mBoundPixelUnpackBuffer);
1201 GLenum error;
1202 const void* ptr;
1203 if (pboOffset) {
1204 ptr = reinterpret_cast<const void*>(*pboOffset);
1205 } else {
1206 ptr = reinterpret_cast<const void*>(src.begin().get());
1209 if (!sub) {
1210 error = DoCompressedTexImage(mContext->gl, imageTarget, level, formatEnum,
1211 size.x, size.y, size.z, imageSize, ptr);
1212 } else {
1213 error = DoCompressedTexSubImage(mContext->gl, imageTarget, level, offset.x,
1214 offset.y, offset.z, size.x, size.y, size.z,
1215 formatEnum, imageSize, ptr);
1217 if (error == LOCAL_GL_OUT_OF_MEMORY) {
1218 mContext->ErrorOutOfMemory("Ran out of memory during upload.");
1219 Truncate();
1220 return;
1222 if (error) {
1223 mContext->GenerateError(error, "Unexpected error from driver.");
1224 nsCString call;
1225 if (!sub) {
1226 call = nsPrintfCString(
1227 "DoCompressedTexImage(0x%04x, %u, 0x%04x, %u,%u,%u, %u, %p)",
1228 imageTarget, level, formatEnum, size.x, size.y, size.z, imageSize,
1229 ptr);
1230 } else {
1231 call = nsPrintfCString(
1232 "DoCompressedTexSubImage(0x%04x, %u, %u,%u,%u, %u,%u,%u, 0x%04x, %u, "
1233 "%p)",
1234 imageTarget, level, offset.x, offset.y, offset.z, size.x, size.y,
1235 size.z, formatEnum, imageSize, ptr);
1237 gfxCriticalError() << "Unexpected error " << gfx::hexa(error)
1238 << " from driver: " << call.BeginReading();
1239 return;
1242 ////////////////////////////////////
1243 // Update our specification data?
1245 if (!sub) {
1246 const auto uninitializedSlices = Nothing();
1247 const webgl::ImageInfo newImageInfo{usage, size.x, size.y, size.z,
1248 uninitializedSlices};
1249 *imageInfo = newImageInfo;
1250 InvalidateCaches();
1254 ////////////////////////////////////////
1255 // CopyTex(Sub)Image
1257 static bool ValidateCopyTexImageFormats(WebGLContext* webgl,
1258 const webgl::FormatInfo* srcFormat,
1259 const webgl::FormatInfo* dstFormat) {
1260 MOZ_ASSERT(!srcFormat->compression);
1261 if (dstFormat->compression) {
1262 webgl->ErrorInvalidEnum(
1263 "Specified destination must not have a compressed"
1264 " format.");
1265 return false;
1268 if (dstFormat->effectiveFormat == webgl::EffectiveFormat::RGB9_E5) {
1269 webgl->ErrorInvalidOperation(
1270 "RGB9_E5 is an invalid destination for"
1271 " CopyTex(Sub)Image. (GLES 3.0.4 p145)");
1272 return false;
1275 if (!DoChannelsMatchForCopyTexImage(srcFormat, dstFormat)) {
1276 webgl->ErrorInvalidOperation(
1277 "Destination channels must be compatible with"
1278 " source channels. (GLES 3.0.4 p140 Table 3.16)");
1279 return false;
1282 return true;
1285 ////////////////////////////////////////////////////////////////////////////////
1287 class ScopedCopyTexImageSource {
1288 WebGLContext* const mWebGL;
1289 GLuint mRB;
1290 GLuint mFB;
1292 public:
1293 ScopedCopyTexImageSource(WebGLContext* webgl, uint32_t srcWidth,
1294 uint32_t srcHeight,
1295 const webgl::FormatInfo* srcFormat,
1296 const webgl::FormatUsageInfo* dstUsage);
1297 ~ScopedCopyTexImageSource();
1300 ScopedCopyTexImageSource::ScopedCopyTexImageSource(
1301 WebGLContext* webgl, uint32_t srcWidth, uint32_t srcHeight,
1302 const webgl::FormatInfo* srcFormat, const webgl::FormatUsageInfo* dstUsage)
1303 : mWebGL(webgl), mRB(0), mFB(0) {
1304 switch (dstUsage->format->unsizedFormat) {
1305 case webgl::UnsizedFormat::L:
1306 case webgl::UnsizedFormat::A:
1307 case webgl::UnsizedFormat::LA:
1308 webgl->GenerateWarning(
1309 "Copying to a LUMINANCE, ALPHA, or LUMINANCE_ALPHA"
1310 " is deprecated, and has severely reduced performance"
1311 " on some platforms.");
1312 break;
1314 default:
1315 MOZ_ASSERT(!dstUsage->textureSwizzleRGBA);
1316 return;
1319 if (!dstUsage->textureSwizzleRGBA) return;
1321 gl::GLContext* gl = webgl->gl;
1323 GLenum sizedFormat;
1325 switch (srcFormat->componentType) {
1326 case webgl::ComponentType::NormUInt:
1327 sizedFormat = LOCAL_GL_RGBA8;
1328 break;
1330 case webgl::ComponentType::Float:
1331 if (webgl->IsExtensionEnabled(
1332 WebGLExtensionID::WEBGL_color_buffer_float)) {
1333 sizedFormat = LOCAL_GL_RGBA32F;
1334 webgl->WarnIfImplicit(WebGLExtensionID::WEBGL_color_buffer_float);
1335 break;
1338 if (webgl->IsExtensionEnabled(
1339 WebGLExtensionID::EXT_color_buffer_half_float)) {
1340 sizedFormat = LOCAL_GL_RGBA16F;
1341 webgl->WarnIfImplicit(WebGLExtensionID::EXT_color_buffer_half_float);
1342 break;
1344 MOZ_CRASH("GFX: Should be able to request CopyTexImage from Float.");
1346 default:
1347 MOZ_CRASH("GFX: Should be able to request CopyTexImage from this type.");
1350 gl::ScopedTexture scopedTex(gl);
1351 gl::ScopedBindTexture scopedBindTex(gl, scopedTex.Texture(),
1352 LOCAL_GL_TEXTURE_2D);
1354 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER,
1355 LOCAL_GL_NEAREST);
1356 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER,
1357 LOCAL_GL_NEAREST);
1359 GLint blitSwizzle[4] = {LOCAL_GL_ZERO};
1360 switch (dstUsage->format->unsizedFormat) {
1361 case webgl::UnsizedFormat::L:
1362 blitSwizzle[0] = LOCAL_GL_RED;
1363 break;
1365 case webgl::UnsizedFormat::A:
1366 blitSwizzle[0] = LOCAL_GL_ALPHA;
1367 break;
1369 case webgl::UnsizedFormat::LA:
1370 blitSwizzle[0] = LOCAL_GL_RED;
1371 blitSwizzle[1] = LOCAL_GL_ALPHA;
1372 break;
1374 default:
1375 MOZ_CRASH("GFX: Unhandled unsizedFormat.");
1378 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_R,
1379 blitSwizzle[0]);
1380 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_G,
1381 blitSwizzle[1]);
1382 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_B,
1383 blitSwizzle[2]);
1384 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_A,
1385 blitSwizzle[3]);
1387 gl->fCopyTexImage2D(LOCAL_GL_TEXTURE_2D, 0, sizedFormat, 0, 0, srcWidth,
1388 srcHeight, 0);
1390 // Now create the swizzled FB we'll be exposing.
1392 GLuint rgbaRB = 0;
1393 GLuint rgbaFB = 0;
1395 gl->fGenRenderbuffers(1, &rgbaRB);
1396 gl::ScopedBindRenderbuffer scopedRB(gl, rgbaRB);
1397 gl->fRenderbufferStorage(LOCAL_GL_RENDERBUFFER, sizedFormat, srcWidth,
1398 srcHeight);
1400 gl->fGenFramebuffers(1, &rgbaFB);
1401 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, rgbaFB);
1402 gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER,
1403 LOCAL_GL_COLOR_ATTACHMENT0,
1404 LOCAL_GL_RENDERBUFFER, rgbaRB);
1406 const GLenum status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
1407 if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
1408 MOZ_CRASH("GFX: Temp framebuffer is not complete.");
1412 // Draw-blit rgbaTex into rgbaFB.
1413 const gfx::IntSize srcSize(srcWidth, srcHeight);
1415 const gl::ScopedBindFramebuffer bindFB(gl, rgbaFB);
1416 gl->BlitHelper()->DrawBlitTextureToFramebuffer(scopedTex.Texture(), srcSize,
1417 srcSize);
1420 // Leave RB and FB alive, and FB bound.
1421 mRB = rgbaRB;
1422 mFB = rgbaFB;
1425 template <typename T>
1426 static inline GLenum ToGLHandle(const T& obj) {
1427 return (obj ? obj->mGLName : 0);
1430 ScopedCopyTexImageSource::~ScopedCopyTexImageSource() {
1431 if (!mFB) {
1432 MOZ_ASSERT(!mRB);
1433 return;
1435 MOZ_ASSERT(mRB);
1437 gl::GLContext* gl = mWebGL->gl;
1439 // If we're swizzling, it's because we're on a GL core (3.2+) profile, which
1440 // has split framebuffer support.
1441 gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER,
1442 ToGLHandle(mWebGL->mBoundDrawFramebuffer));
1443 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER,
1444 ToGLHandle(mWebGL->mBoundReadFramebuffer));
1446 gl->fDeleteFramebuffers(1, &mFB);
1447 gl->fDeleteRenderbuffers(1, &mRB);
1450 ////////////////////////////////////////////////////////////////////////////////
1452 static bool GetUnsizedFormatForCopy(GLenum internalFormat,
1453 webgl::UnsizedFormat* const out) {
1454 switch (internalFormat) {
1455 case LOCAL_GL_RED:
1456 *out = webgl::UnsizedFormat::R;
1457 break;
1458 case LOCAL_GL_RG:
1459 *out = webgl::UnsizedFormat::RG;
1460 break;
1461 case LOCAL_GL_RGB:
1462 *out = webgl::UnsizedFormat::RGB;
1463 break;
1464 case LOCAL_GL_RGBA:
1465 *out = webgl::UnsizedFormat::RGBA;
1466 break;
1467 case LOCAL_GL_LUMINANCE:
1468 *out = webgl::UnsizedFormat::L;
1469 break;
1470 case LOCAL_GL_ALPHA:
1471 *out = webgl::UnsizedFormat::A;
1472 break;
1473 case LOCAL_GL_LUMINANCE_ALPHA:
1474 *out = webgl::UnsizedFormat::LA;
1475 break;
1477 default:
1478 return false;
1481 return true;
1484 static const webgl::FormatUsageInfo* ValidateCopyDestUsage(
1485 WebGLContext* webgl, const webgl::FormatInfo* srcFormat,
1486 GLenum internalFormat) {
1487 const auto& fua = webgl->mFormatUsage;
1489 switch (internalFormat) {
1490 case LOCAL_GL_R8_SNORM:
1491 case LOCAL_GL_RG8_SNORM:
1492 case LOCAL_GL_RGB8_SNORM:
1493 case LOCAL_GL_RGBA8_SNORM:
1494 webgl->ErrorInvalidEnum("SNORM formats are invalid for CopyTexImage.");
1495 return nullptr;
1498 auto dstUsage = fua->GetSizedTexUsage(internalFormat);
1499 if (!dstUsage) {
1500 // Ok, maybe it's unsized.
1501 webgl::UnsizedFormat unsizedFormat;
1502 if (!GetUnsizedFormatForCopy(internalFormat, &unsizedFormat)) {
1503 webgl->ErrorInvalidEnumInfo("internalFormat", internalFormat);
1504 return nullptr;
1507 const auto dstFormat = srcFormat->GetCopyDecayFormat(unsizedFormat);
1508 if (dstFormat) {
1509 dstUsage = fua->GetUsage(dstFormat->effectiveFormat);
1511 if (!dstUsage) {
1512 webgl->ErrorInvalidOperation(
1513 "0x%04x is not a valid unsized format for"
1514 " source format %s.",
1515 internalFormat, srcFormat->name);
1516 return nullptr;
1519 return dstUsage;
1521 // Alright, it's sized.
1523 const auto dstFormat = dstUsage->format;
1525 if (dstFormat->componentType != srcFormat->componentType) {
1526 webgl->ErrorInvalidOperation(
1527 "For sized internalFormats, source and dest"
1528 " component types must match. (source: %s, dest:"
1529 " %s)",
1530 srcFormat->name, dstFormat->name);
1531 return nullptr;
1534 bool componentSizesMatch = true;
1535 if (dstFormat->r) {
1536 componentSizesMatch &= (dstFormat->r == srcFormat->r);
1538 if (dstFormat->g) {
1539 componentSizesMatch &= (dstFormat->g == srcFormat->g);
1541 if (dstFormat->b) {
1542 componentSizesMatch &= (dstFormat->b == srcFormat->b);
1544 if (dstFormat->a) {
1545 componentSizesMatch &= (dstFormat->a == srcFormat->a);
1548 if (!componentSizesMatch) {
1549 webgl->ErrorInvalidOperation(
1550 "For sized internalFormats, source and dest"
1551 " component sizes must match exactly. (source: %s,"
1552 " dest: %s)",
1553 srcFormat->name, dstFormat->name);
1554 return nullptr;
1557 return dstUsage;
1560 static bool ValidateCopyTexImageForFeedback(const WebGLContext& webgl,
1561 const WebGLTexture& tex,
1562 const uint32_t mipLevel,
1563 const uint32_t zLayer) {
1564 const auto& fb = webgl.BoundReadFb();
1565 if (fb) {
1566 MOZ_ASSERT(fb->ColorReadBuffer());
1567 const auto& attach = *fb->ColorReadBuffer();
1568 MOZ_ASSERT(attach.ZLayerCount() ==
1569 1); // Multiview invalid for copyTexImage.
1571 if (attach.Texture() == &tex && attach.Layer() == zLayer &&
1572 attach.MipLevel() == mipLevel) {
1573 // Note that the TexImageTargets *don't* have to match for this to be
1574 // undefined per GLES 3.0.4 p211, thus an INVALID_OP in WebGL.
1575 webgl.ErrorInvalidOperation(
1576 "Feedback loop detected, as this texture"
1577 " is already attached to READ_FRAMEBUFFER's"
1578 " READ_BUFFER-selected COLOR_ATTACHMENT%u.",
1579 attach.mAttachmentPoint);
1580 return false;
1583 return true;
1586 static bool DoCopyTexOrSubImage(WebGLContext* webgl, bool isSubImage,
1587 bool needsInit, WebGLTexture* const tex,
1588 const TexImageTarget target, GLint level,
1589 GLint xWithinSrc, GLint yWithinSrc,
1590 uint32_t srcTotalWidth, uint32_t srcTotalHeight,
1591 const webgl::FormatUsageInfo* srcUsage,
1592 GLint xOffset, GLint yOffset, GLint zOffset,
1593 uint32_t dstWidth, uint32_t dstHeight,
1594 const webgl::FormatUsageInfo* dstUsage) {
1595 const auto& gl = webgl->gl;
1597 ////
1599 int32_t readX, readY;
1600 int32_t writeX, writeY;
1601 int32_t rwWidth, rwHeight;
1602 if (!Intersect(srcTotalWidth, xWithinSrc, dstWidth, &readX, &writeX,
1603 &rwWidth) ||
1604 !Intersect(srcTotalHeight, yWithinSrc, dstHeight, &readY, &writeY,
1605 &rwHeight)) {
1606 webgl->ErrorOutOfMemory("Bad subrect selection.");
1607 return false;
1610 writeX += xOffset;
1611 writeY += yOffset;
1613 ////
1615 GLenum error = 0;
1616 nsCString errorText;
1617 do {
1618 const auto& idealUnpack = dstUsage->idealUnpack;
1619 const auto& pi = idealUnpack->ToPacking();
1621 UniqueBuffer zeros;
1622 const bool fullOverwrite =
1623 (uint32_t(rwWidth) == dstWidth && uint32_t(rwHeight) == dstHeight);
1624 if (needsInit && !fullOverwrite) {
1625 CheckedInt<size_t> byteCount = BytesPerPixel(pi);
1626 byteCount *= dstWidth;
1627 byteCount *= dstHeight;
1629 if (byteCount.isValid()) {
1630 zeros = calloc(1u, byteCount.value());
1633 if (!zeros.get()) {
1634 webgl->ErrorOutOfMemory("Ran out of memory allocating zeros.");
1635 return false;
1639 if (!isSubImage || zeros) {
1640 WebGLPixelStore::AssertDefault(*gl, webgl->IsWebGL2());
1642 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1);
1643 const auto revert = MakeScopeExit(
1644 [&]() { gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4); });
1645 if (!isSubImage) {
1646 error = DoTexImage(gl, target, level, idealUnpack, dstWidth, dstHeight,
1647 1, nullptr);
1648 if (error) {
1649 errorText = nsPrintfCString(
1650 "DoTexImage(0x%04x, %i, {0x%04x, 0x%04x, 0x%04x}, %u,%u,1) -> "
1651 "0x%04x",
1652 target.get(), level, idealUnpack->internalFormat,
1653 idealUnpack->unpackFormat, idealUnpack->unpackType, dstWidth,
1654 dstHeight, error);
1655 break;
1658 if (zeros) {
1659 error = DoTexSubImage(gl, target, level, xOffset, yOffset, zOffset,
1660 dstWidth, dstHeight, 1, pi, zeros.get());
1661 if (error) {
1662 errorText = nsPrintfCString(
1663 "DoTexSubImage(0x%04x, %i, %i,%i,%i, %u,%u,1, {0x%04x, 0x%04x}) "
1664 "-> "
1665 "0x%04x",
1666 target.get(), level, xOffset, yOffset, zOffset, dstWidth,
1667 dstHeight, idealUnpack->unpackFormat, idealUnpack->unpackType,
1668 error);
1669 break;
1674 if (!rwWidth || !rwHeight) {
1675 // There aren't any pixels to copy, so we're 'done'.
1676 return true;
1679 const auto& srcFormat = srcUsage->format;
1680 ScopedCopyTexImageSource maybeSwizzle(webgl, srcTotalWidth, srcTotalHeight,
1681 srcFormat, dstUsage);
1683 error = DoCopyTexSubImage(gl, target, level, writeX, writeY, zOffset, readX,
1684 readY, rwWidth, rwHeight);
1685 if (error) {
1686 errorText = nsPrintfCString(
1687 "DoCopyTexSubImage(0x%04x, %i, %i,%i,%i, %i,%i, %u,%u) -> 0x%04x",
1688 target.get(), level, writeX, writeY, zOffset, readX, readY, rwWidth,
1689 rwHeight, error);
1690 break;
1693 return true;
1694 } while (false);
1696 if (error == LOCAL_GL_OUT_OF_MEMORY) {
1697 webgl->ErrorOutOfMemory("Ran out of memory during texture copy.");
1698 tex->Truncate();
1699 return false;
1702 if (gl->IsANGLE() && error == LOCAL_GL_INVALID_OPERATION) {
1703 webgl->ErrorImplementationBug(
1704 "ANGLE is particular about CopyTexSubImage"
1705 " formats matching exactly.");
1706 return false;
1709 webgl->GenerateError(error, "Unexpected error from driver.");
1710 gfxCriticalError() << "Unexpected error from driver: "
1711 << errorText.BeginReading();
1712 return false;
1715 // CopyTexSubImage if `!respecFormat`
1716 void WebGLTexture::CopyTexImage(GLenum imageTarget, uint32_t level,
1717 GLenum respecFormat, const uvec3& dstOffset,
1718 const ivec2& srcOffset, const uvec2& size2) {
1719 ////////////////////////////////////
1720 // Get source info
1722 const webgl::FormatUsageInfo* srcUsage;
1723 uint32_t srcTotalWidth;
1724 uint32_t srcTotalHeight;
1725 if (!mContext->BindCurFBForColorRead(&srcUsage, &srcTotalWidth,
1726 &srcTotalHeight)) {
1727 return;
1729 const auto& srcFormat = srcUsage->format;
1731 if (!ValidateCopyTexImageForFeedback(*mContext, *this, level, dstOffset.z))
1732 return;
1734 const auto size = uvec3{size2.x, size2.y, 1};
1736 ////////////////////////////////////
1737 // Get dest info
1739 webgl::ImageInfo* imageInfo;
1740 const webgl::FormatUsageInfo* dstUsage;
1741 if (respecFormat) {
1742 if (!ValidateTexImageSpecification(imageTarget, level, size, &imageInfo))
1743 return;
1744 MOZ_ASSERT(imageInfo);
1746 dstUsage = ValidateCopyDestUsage(mContext, srcFormat, respecFormat);
1747 if (!dstUsage) return;
1749 if (!ValidateTargetForFormat(mContext, imageTarget, dstUsage->format))
1750 return;
1751 } else {
1752 if (!ValidateTexImageSelection(imageTarget, level, dstOffset, size,
1753 &imageInfo)) {
1754 return;
1756 MOZ_ASSERT(imageInfo);
1758 dstUsage = imageInfo->mFormat;
1759 MOZ_ASSERT(dstUsage);
1762 const auto& dstFormat = dstUsage->format;
1763 if (!mContext->IsWebGL2() && dstFormat->d) {
1764 mContext->ErrorInvalidOperation(
1765 "Function may not be called with format %s.", dstFormat->name);
1766 return;
1769 ////////////////////////////////////
1770 // Check that source and dest info are compatible
1772 if (!ValidateCopyTexImageFormats(mContext, srcFormat, dstFormat)) return;
1774 ////////////////////////////////////
1775 // Do the thing!
1777 const bool isSubImage = !respecFormat;
1778 bool expectsInit = true;
1779 if (isSubImage) {
1780 if (!EnsureImageDataInitializedForUpload(this, imageTarget, level,
1781 dstOffset, size, imageInfo,
1782 &expectsInit)) {
1783 return;
1787 if (!DoCopyTexOrSubImage(mContext, isSubImage, expectsInit, this, imageTarget,
1788 level, srcOffset.x, srcOffset.y, srcTotalWidth,
1789 srcTotalHeight, srcUsage, dstOffset.x, dstOffset.y,
1790 dstOffset.z, size.x, size.y, dstUsage)) {
1791 return;
1794 mContext->OnDataAllocCall();
1796 ////////////////////////////////////
1797 // Update our specification data?
1799 if (respecFormat) {
1800 const auto uninitializedSlices = Nothing();
1801 const webgl::ImageInfo newImageInfo{dstUsage, size.x, size.y, size.z,
1802 uninitializedSlices};
1803 *imageInfo = newImageInfo;
1804 InvalidateCaches();
1808 } // namespace mozilla