Bug 1886946: Remove incorrect assertion that buffer is not-pinned. r=sfink
[gecko.git] / dom / canvas / WebGLTextureUpload.cpp
blob1a439ca5bca1e1b4a9add4d65ac97b174b80cda4
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 "WebGLTextureUpload.h"
7 #include "WebGLTexture.h"
9 #include <algorithm>
10 #include <limits>
12 #include "CanvasUtils.h"
13 #include "ClientWebGLContext.h"
14 #include "GLBlitHelper.h"
15 #include "GLContext.h"
16 #include "mozilla/Casting.h"
17 #include "mozilla/gfx/2D.h"
18 #include "mozilla/gfx/Logging.h"
19 #include "mozilla/dom/HTMLCanvasElement.h"
20 #include "mozilla/dom/HTMLVideoElement.h"
21 #include "mozilla/dom/ImageBitmap.h"
22 #include "mozilla/dom/ImageData.h"
23 #include "mozilla/dom/OffscreenCanvas.h"
24 #include "mozilla/MathAlgorithms.h"
25 #include "mozilla/ScopeExit.h"
26 #include "mozilla/StaticPrefs_webgl.h"
27 #include "mozilla/Unused.h"
28 #include "nsLayoutUtils.h"
29 #include "ScopedGLHelpers.h"
30 #include "TexUnpackBlob.h"
31 #include "WebGLBuffer.h"
32 #include "WebGLContext.h"
33 #include "WebGLContextUtils.h"
34 #include "WebGLFramebuffer.h"
35 #include "WebGLTexelConversions.h"
37 namespace mozilla {
38 namespace webgl {
40 // The canvas spec says that drawImage should draw the first frame of
41 // animated images. The webgl spec doesn't mention the issue, so we do the
42 // same as drawImage.
43 static constexpr uint32_t kDefaultSurfaceFromElementFlags =
44 nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE |
45 nsLayoutUtils::SFE_USE_ELEMENT_SIZE_IF_VECTOR |
46 nsLayoutUtils::SFE_EXACT_SIZE_SURFACE |
47 nsLayoutUtils::SFE_ALLOW_NON_PREMULT;
49 Maybe<TexUnpackBlobDesc> FromImageBitmap(const GLenum target, Maybe<uvec3> size,
50 const dom::ImageBitmap& imageBitmap,
51 ErrorResult* const out_rv) {
52 if (imageBitmap.IsWriteOnly()) {
53 out_rv->Throw(NS_ERROR_DOM_SECURITY_ERR);
54 return {};
57 const auto cloneData = imageBitmap.ToCloneData();
58 if (!cloneData) {
59 return {};
62 const RefPtr<gfx::DataSourceSurface> surf = cloneData->mSurface;
63 if (NS_WARN_IF(!surf)) {
64 return {};
67 const auto imageSize = *uvec2::FromSize(surf->GetSize());
68 if (!size) {
69 size.emplace(imageSize.x, imageSize.y, 1);
72 // WhatWG "HTML Living Standard" (30 October 2015):
73 // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned as
74 // non-premultiplied alpha values."
75 return Some(TexUnpackBlobDesc{target,
76 size.value(),
77 cloneData->mAlphaType,
78 {},
79 {},
80 Some(imageSize),
81 nullptr,
82 {},
83 surf,
84 {},
85 false});
88 static layers::SurfaceDescriptor Flatten(const layers::SurfaceDescriptor& sd) {
89 const auto sdType = sd.type();
90 if (sdType != layers::SurfaceDescriptor::TSurfaceDescriptorGPUVideo) {
91 return sd;
93 const auto& sdv = sd.get_SurfaceDescriptorGPUVideo();
94 const auto& sdvType = sdv.type();
95 if (sdvType !=
96 layers::SurfaceDescriptorGPUVideo::TSurfaceDescriptorRemoteDecoder) {
97 return sd;
100 const auto& sdrd = sdv.get_SurfaceDescriptorRemoteDecoder();
101 const auto& subdesc = sdrd.subdesc();
102 const auto& subdescType = subdesc.type();
103 switch (subdescType) {
104 case layers::RemoteDecoderVideoSubDescriptor::T__None:
105 case layers::RemoteDecoderVideoSubDescriptor::Tnull_t:
106 return sd;
108 case layers::RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorD3D10:
109 return subdesc.get_SurfaceDescriptorD3D10();
110 case layers::RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorDXGIYCbCr:
111 return subdesc.get_SurfaceDescriptorDXGIYCbCr();
112 case layers::RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorDMABuf:
113 return subdesc.get_SurfaceDescriptorDMABuf();
114 case layers::RemoteDecoderVideoSubDescriptor::
115 TSurfaceDescriptorMacIOSurface:
116 return subdesc.get_SurfaceDescriptorMacIOSurface();
117 case layers::RemoteDecoderVideoSubDescriptor::
118 TSurfaceDescriptorDcompSurface:
119 return subdesc.get_SurfaceDescriptorDcompSurface();
121 MOZ_CRASH("unreachable");
124 Maybe<webgl::TexUnpackBlobDesc> FromOffscreenCanvas(
125 const ClientWebGLContext& webgl, const GLenum target, Maybe<uvec3> size,
126 const dom::OffscreenCanvas& canvas, ErrorResult* const out_error) {
127 if (canvas.IsWriteOnly()) {
128 webgl.EnqueueWarning(
129 "OffscreenCanvas is write-only, thus cannot be uploaded.");
130 out_error->ThrowSecurityError(
131 "OffscreenCanvas is write-only, thus cannot be uploaded.");
132 return {};
135 auto sfer = nsLayoutUtils::SurfaceFromOffscreenCanvas(
136 const_cast<dom::OffscreenCanvas*>(&canvas),
137 kDefaultSurfaceFromElementFlags);
138 return FromSurfaceFromElementResult(webgl, target, size, sfer, out_error);
141 Maybe<webgl::TexUnpackBlobDesc> FromVideoFrame(
142 const ClientWebGLContext& webgl, const GLenum target, Maybe<uvec3> size,
143 const dom::VideoFrame& videoFrame, ErrorResult* const out_error) {
144 auto sfer = nsLayoutUtils::SurfaceFromVideoFrame(
145 const_cast<dom::VideoFrame*>(&videoFrame),
146 kDefaultSurfaceFromElementFlags);
147 return FromSurfaceFromElementResult(webgl, target, size, sfer, out_error);
150 Maybe<webgl::TexUnpackBlobDesc> FromDomElem(const ClientWebGLContext& webgl,
151 const GLenum target,
152 Maybe<uvec3> size,
153 const dom::Element& elem,
154 ErrorResult* const out_error) {
155 if (elem.IsHTMLElement(nsGkAtoms::canvas)) {
156 const dom::HTMLCanvasElement* srcCanvas =
157 static_cast<const dom::HTMLCanvasElement*>(&elem);
158 if (srcCanvas->IsWriteOnly()) {
159 out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
160 return {};
164 uint32_t flags = kDefaultSurfaceFromElementFlags;
165 const auto& unpacking = webgl.State().mPixelUnpackState;
166 if (unpacking.colorspaceConversion == LOCAL_GL_NONE) {
167 flags |= nsLayoutUtils::SFE_NO_COLORSPACE_CONVERSION;
170 RefPtr<gfx::DrawTarget> idealDrawTarget = nullptr; // Don't care for now.
171 auto sfer = nsLayoutUtils::SurfaceFromElement(
172 const_cast<dom::Element*>(&elem), flags, idealDrawTarget);
173 return FromSurfaceFromElementResult(webgl, target, size, sfer, out_error);
176 Maybe<webgl::TexUnpackBlobDesc> FromSurfaceFromElementResult(
177 const ClientWebGLContext& webgl, const GLenum target, Maybe<uvec3> size,
178 SurfaceFromElementResult& sfer, ErrorResult* const out_error) {
179 uvec2 elemSize;
181 const auto& layersImage = sfer.mLayersImage;
182 Maybe<layers::SurfaceDescriptor> sd;
183 if (layersImage) {
184 elemSize = *uvec2::FromSize(layersImage->GetSize());
186 sd = layersImage->GetDesc();
187 if (sd) {
188 sd = Some(Flatten(*sd));
190 if (!sd) {
191 NS_WARNING("No SurfaceDescriptor for layers::Image!");
195 RefPtr<gfx::DataSourceSurface> dataSurf;
196 if (!sd && sfer.GetSourceSurface()) {
197 const auto surf = sfer.GetSourceSurface();
198 elemSize = *uvec2::FromSize(surf->GetSize());
200 // WARNING: OSX can lose our MakeCurrent here.
201 dataSurf = surf->GetDataSurface();
204 //////
206 if (!size) {
207 size.emplace(elemSize.x, elemSize.y, 1);
210 ////
212 if (!sd && !dataSurf) {
213 webgl.EnqueueWarning("Resource has no data (yet?). Uploading zeros.");
214 if (!size) {
215 size.emplace(0, 0, 1);
217 return Some(
218 TexUnpackBlobDesc{target, size.value(), gfxAlphaType::NonPremult});
221 //////
223 // While it's counter-intuitive, the shape of the SFEResult API means that we
224 // should try to pull out a surface first, and then, if we do pull out a
225 // surface, check CORS/write-only/etc..
227 if (!sfer.mCORSUsed) {
228 auto& srcPrincipal = sfer.mPrincipal;
229 nsIPrincipal* dstPrincipal = webgl.PrincipalOrNull();
230 if (!dstPrincipal || !dstPrincipal->Subsumes(srcPrincipal)) {
231 webgl.EnqueueWarning("Cross-origin elements require CORS.");
232 out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
233 return {};
237 if (sfer.mIsWriteOnly) {
238 // mIsWriteOnly defaults to true, and so will be true even if SFE merely
239 // failed. Thus we must test mIsWriteOnly after successfully retrieving an
240 // Image or SourceSurface.
241 webgl.EnqueueWarning("Element is write-only, thus cannot be uploaded.");
242 out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
243 return {};
246 //////
247 // Ok, we're good!
249 return Some(TexUnpackBlobDesc{target,
250 size.value(),
251 sfer.mAlphaType,
254 Some(elemSize),
255 layersImage,
257 dataSurf});
260 } // namespace webgl
262 //////////////////////////////////////////////////////////////////////////////////////////
263 //////////////////////////////////////////////////////////////////////////////////////////
265 static bool ValidateTexImage(WebGLContext* webgl, WebGLTexture* texture,
266 TexImageTarget target, uint32_t level,
267 webgl::ImageInfo** const out_imageInfo) {
268 // Check level
269 if (level >= WebGLTexture::kMaxLevelCount) {
270 webgl->ErrorInvalidValue("`level` is too large.");
271 return false;
274 auto& imageInfo = texture->ImageInfoAt(target, level);
275 *out_imageInfo = &imageInfo;
276 return true;
279 // For *TexImage*
280 bool WebGLTexture::ValidateTexImageSpecification(
281 TexImageTarget target, uint32_t level, const uvec3& size,
282 webgl::ImageInfo** const out_imageInfo) {
283 if (mImmutable) {
284 mContext->ErrorInvalidOperation("Specified texture is immutable.");
285 return false;
288 // Do this early to validate `level`.
289 webgl::ImageInfo* imageInfo;
290 if (!ValidateTexImage(mContext, this, target, level, &imageInfo))
291 return false;
293 if (mTarget == LOCAL_GL_TEXTURE_CUBE_MAP && size.x != size.y) {
294 mContext->ErrorInvalidValue("Cube map images must be square.");
295 return false;
298 /* GLES 3.0.4, p133-134:
299 * GL_MAX_TEXTURE_SIZE is *not* the max allowed texture size. Rather, it is
300 * the max (width/height) size guaranteed not to generate an INVALID_VALUE for
301 * too-large dimensions. Sizes larger than GL_MAX_TEXTURE_SIZE *may or may
302 * not* result in an INVALID_VALUE, or possibly GL_OOM.
304 * However, we have needed to set our maximums lower in the past to prevent
305 * resource corruption. Therefore we have limits.maxTex2dSize, which is
306 * neither necessarily lower nor higher than MAX_TEXTURE_SIZE.
308 * Note that limits.maxTex2dSize must be >= than the advertized
309 * MAX_TEXTURE_SIZE. For simplicity, we advertize MAX_TEXTURE_SIZE as
310 * limits.maxTex2dSize.
313 uint32_t maxWidthHeight = 0;
314 uint32_t maxDepth = 0;
315 uint32_t maxLevel = 0;
317 const auto& limits = mContext->Limits();
318 MOZ_ASSERT(level <= 31);
319 switch (target.get()) {
320 case LOCAL_GL_TEXTURE_2D:
321 maxWidthHeight = limits.maxTex2dSize >> level;
322 maxDepth = 1;
323 maxLevel = CeilingLog2(limits.maxTex2dSize);
324 break;
326 case LOCAL_GL_TEXTURE_3D:
327 maxWidthHeight = limits.maxTex3dSize >> level;
328 maxDepth = maxWidthHeight;
329 maxLevel = CeilingLog2(limits.maxTex3dSize);
330 break;
332 case LOCAL_GL_TEXTURE_2D_ARRAY:
333 maxWidthHeight = limits.maxTex2dSize >> level;
334 // "The maximum number of layers for two-dimensional array textures
335 // (depth) must be at least MAX_ARRAY_TEXTURE_LAYERS for all levels."
336 maxDepth = limits.maxTexArrayLayers;
337 maxLevel = CeilingLog2(limits.maxTex2dSize);
338 break;
340 default: // cube maps
341 MOZ_ASSERT(IsCubeMap());
342 maxWidthHeight = limits.maxTexCubeSize >> level;
343 maxDepth = 1;
344 maxLevel = CeilingLog2(limits.maxTexCubeSize);
345 break;
348 if (level > maxLevel) {
349 mContext->ErrorInvalidValue("Requested level is not supported for target.");
350 return false;
353 if (size.x > maxWidthHeight || size.y > maxWidthHeight || size.z > maxDepth) {
354 mContext->ErrorInvalidValue("Requested size at this level is unsupported.");
355 return false;
359 /* GL ES Version 2.0.25 - 3.7.1 Texture Image Specification
360 * "If level is greater than zero, and either width or
361 * height is not a power-of-two, the error INVALID_VALUE is
362 * generated."
364 * This restriction does not apply to GL ES Version 3.0+.
366 bool requirePOT = (!mContext->IsWebGL2() && level != 0);
368 if (requirePOT) {
369 if (!IsPowerOfTwo(size.x) || !IsPowerOfTwo(size.y)) {
370 mContext->ErrorInvalidValue(
371 "For level > 0, width and height must be"
372 " powers of two.");
373 return false;
378 *out_imageInfo = imageInfo;
379 return true;
382 // For *TexSubImage*
383 bool WebGLTexture::ValidateTexImageSelection(
384 TexImageTarget target, uint32_t level, const uvec3& offset,
385 const uvec3& size, webgl::ImageInfo** const out_imageInfo) {
386 webgl::ImageInfo* imageInfo;
387 if (!ValidateTexImage(mContext, this, target, level, &imageInfo))
388 return false;
390 if (!imageInfo->IsDefined()) {
391 mContext->ErrorInvalidOperation(
392 "The specified TexImage has not yet been"
393 " specified.");
394 return false;
397 const auto totalX = CheckedUint32(offset.x) + size.x;
398 const auto totalY = CheckedUint32(offset.y) + size.y;
399 const auto totalZ = CheckedUint32(offset.z) + size.z;
401 if (!totalX.isValid() || totalX.value() > imageInfo->mWidth ||
402 !totalY.isValid() || totalY.value() > imageInfo->mHeight ||
403 !totalZ.isValid() || totalZ.value() > imageInfo->mDepth) {
404 mContext->ErrorInvalidValue(
405 "Offset+size must be <= the size of the existing"
406 " specified image.");
407 return false;
410 *out_imageInfo = imageInfo;
411 return true;
414 static bool ValidateCompressedTexUnpack(WebGLContext* webgl, const uvec3& size,
415 const webgl::FormatInfo* format,
416 size_t dataSize) {
417 auto compression = format->compression;
419 auto bytesPerBlock = compression->bytesPerBlock;
420 auto blockWidth = compression->blockWidth;
421 auto blockHeight = compression->blockHeight;
423 auto widthInBlocks = CheckedUint32(size.x) / blockWidth;
424 auto heightInBlocks = CheckedUint32(size.y) / blockHeight;
425 if (size.x % blockWidth) widthInBlocks += 1;
426 if (size.y % blockHeight) heightInBlocks += 1;
428 const CheckedUint32 blocksPerImage = widthInBlocks * heightInBlocks;
429 const CheckedUint32 bytesPerImage = bytesPerBlock * blocksPerImage;
430 const CheckedUint32 bytesNeeded = bytesPerImage * size.z;
432 if (!bytesNeeded.isValid()) {
433 webgl->ErrorOutOfMemory("Overflow while computing the needed buffer size.");
434 return false;
437 if (dataSize != bytesNeeded.value()) {
438 webgl->ErrorInvalidValue(
439 "Provided buffer's size must match expected size."
440 " (needs %u, has %zu)",
441 bytesNeeded.value(), dataSize);
442 return false;
445 return true;
448 static bool DoChannelsMatchForCopyTexImage(const webgl::FormatInfo* srcFormat,
449 const webgl::FormatInfo* dstFormat) {
450 // GLES 3.0.4 p140 Table 3.16 "Valid CopyTexImage source
451 // framebuffer/destination texture base internal format combinations."
453 switch (srcFormat->unsizedFormat) {
454 case webgl::UnsizedFormat::RGBA:
455 switch (dstFormat->unsizedFormat) {
456 case webgl::UnsizedFormat::A:
457 case webgl::UnsizedFormat::L:
458 case webgl::UnsizedFormat::LA:
459 case webgl::UnsizedFormat::R:
460 case webgl::UnsizedFormat::RG:
461 case webgl::UnsizedFormat::RGB:
462 case webgl::UnsizedFormat::RGBA:
463 return true;
464 default:
465 return false;
468 case webgl::UnsizedFormat::RGB:
469 switch (dstFormat->unsizedFormat) {
470 case webgl::UnsizedFormat::L:
471 case webgl::UnsizedFormat::R:
472 case webgl::UnsizedFormat::RG:
473 case webgl::UnsizedFormat::RGB:
474 return true;
475 default:
476 return false;
479 case webgl::UnsizedFormat::RG:
480 switch (dstFormat->unsizedFormat) {
481 case webgl::UnsizedFormat::L:
482 case webgl::UnsizedFormat::R:
483 case webgl::UnsizedFormat::RG:
484 return true;
485 default:
486 return false;
489 case webgl::UnsizedFormat::R:
490 switch (dstFormat->unsizedFormat) {
491 case webgl::UnsizedFormat::L:
492 case webgl::UnsizedFormat::R:
493 return true;
494 default:
495 return false;
498 default:
499 return false;
503 static bool EnsureImageDataInitializedForUpload(
504 WebGLTexture* tex, TexImageTarget target, uint32_t level,
505 const uvec3& offset, const uvec3& size, webgl::ImageInfo* imageInfo,
506 bool* const out_expectsInit = nullptr) {
507 if (out_expectsInit) {
508 *out_expectsInit = false;
510 if (!imageInfo->mUninitializedSlices) return true;
512 if (size.x == imageInfo->mWidth && size.y == imageInfo->mHeight) {
513 bool expectsInit = false;
514 auto& isSliceUninit = *imageInfo->mUninitializedSlices;
515 for (const auto z : IntegerRange(offset.z, offset.z + size.z)) {
516 if (!isSliceUninit[z]) continue;
517 expectsInit = true;
518 isSliceUninit[z] = false;
520 if (out_expectsInit) {
521 *out_expectsInit = expectsInit;
524 if (!expectsInit) return true;
526 bool hasUninitialized = false;
527 for (const auto z : IntegerRange(imageInfo->mDepth)) {
528 hasUninitialized |= isSliceUninit[z];
530 if (!hasUninitialized) {
531 imageInfo->mUninitializedSlices = Nothing();
533 return true;
536 WebGLContext* webgl = tex->mContext;
537 webgl->GenerateWarning(
538 "Texture has not been initialized prior to a"
539 " partial upload, forcing the browser to clear it."
540 " This may be slow.");
541 if (!tex->EnsureImageDataInitialized(target, level)) {
542 MOZ_ASSERT(false, "Unexpected failure to init image data.");
543 return false;
546 return true;
549 //////////////////////////////////////////////////////////////////////////////////////////
550 //////////////////////////////////////////////////////////////////////////////////////////
551 // Actual calls
553 static inline GLenum DoTexStorage(gl::GLContext* gl, TexTarget target,
554 GLsizei levels, GLenum sizedFormat,
555 GLsizei width, GLsizei height,
556 GLsizei depth) {
557 gl::GLContext::LocalErrorScope errorScope(*gl);
559 switch (target.get()) {
560 case LOCAL_GL_TEXTURE_2D:
561 case LOCAL_GL_TEXTURE_CUBE_MAP:
562 MOZ_ASSERT(depth == 1);
563 gl->fTexStorage2D(target.get(), levels, sizedFormat, width, height);
564 break;
566 case LOCAL_GL_TEXTURE_3D:
567 case LOCAL_GL_TEXTURE_2D_ARRAY:
568 gl->fTexStorage3D(target.get(), levels, sizedFormat, width, height,
569 depth);
570 break;
572 default:
573 MOZ_CRASH("GFX: bad target");
576 return errorScope.GetError();
579 bool IsTarget3D(TexImageTarget target) {
580 switch (target.get()) {
581 case LOCAL_GL_TEXTURE_2D:
582 case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
583 case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
584 case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
585 case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
586 case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
587 case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
588 return false;
590 case LOCAL_GL_TEXTURE_3D:
591 case LOCAL_GL_TEXTURE_2D_ARRAY:
592 return true;
594 default:
595 MOZ_CRASH("GFX: bad target");
599 GLenum DoTexImage(gl::GLContext* gl, TexImageTarget target, GLint level,
600 const webgl::DriverUnpackInfo* dui, GLsizei width,
601 GLsizei height, GLsizei depth, const void* data) {
602 const GLint border = 0;
604 gl::GLContext::LocalErrorScope errorScope(*gl);
606 if (IsTarget3D(target)) {
607 gl->fTexImage3D(target.get(), level, dui->internalFormat, width, height,
608 depth, border, dui->unpackFormat, dui->unpackType, data);
609 } else {
610 MOZ_ASSERT(depth == 1);
611 gl->fTexImage2D(target.get(), level, dui->internalFormat, width, height,
612 border, dui->unpackFormat, dui->unpackType, data);
615 return errorScope.GetError();
618 GLenum DoTexSubImage(gl::GLContext* gl, TexImageTarget target, GLint level,
619 GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width,
620 GLsizei height, GLsizei depth,
621 const webgl::PackingInfo& pi, const void* data) {
622 gl::GLContext::LocalErrorScope errorScope(*gl);
624 if (IsTarget3D(target)) {
625 gl->fTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset, width,
626 height, depth, pi.format, pi.type, data);
627 } else {
628 MOZ_ASSERT(zOffset == 0);
629 MOZ_ASSERT(depth == 1);
630 gl->fTexSubImage2D(target.get(), level, xOffset, yOffset, width, height,
631 pi.format, pi.type, data);
634 return errorScope.GetError();
637 static inline GLenum DoCompressedTexImage(gl::GLContext* gl,
638 TexImageTarget target, GLint level,
639 GLenum internalFormat, GLsizei width,
640 GLsizei height, GLsizei depth,
641 GLsizei dataSize, const void* data) {
642 const GLint border = 0;
644 gl::GLContext::LocalErrorScope errorScope(*gl);
646 if (IsTarget3D(target)) {
647 gl->fCompressedTexImage3D(target.get(), level, internalFormat, width,
648 height, depth, border, dataSize, data);
649 } else {
650 MOZ_ASSERT(depth == 1);
651 gl->fCompressedTexImage2D(target.get(), level, internalFormat, width,
652 height, border, dataSize, data);
655 return errorScope.GetError();
658 GLenum DoCompressedTexSubImage(gl::GLContext* gl, TexImageTarget target,
659 GLint level, GLint xOffset, GLint yOffset,
660 GLint zOffset, GLsizei width, GLsizei height,
661 GLsizei depth, GLenum sizedUnpackFormat,
662 GLsizei dataSize, const void* data) {
663 gl::GLContext::LocalErrorScope errorScope(*gl);
665 if (IsTarget3D(target)) {
666 gl->fCompressedTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset,
667 width, height, depth, sizedUnpackFormat,
668 dataSize, data);
669 } else {
670 MOZ_ASSERT(zOffset == 0);
671 MOZ_ASSERT(depth == 1);
672 gl->fCompressedTexSubImage2D(target.get(), level, xOffset, yOffset, width,
673 height, sizedUnpackFormat, dataSize, data);
676 return errorScope.GetError();
679 static inline GLenum DoCopyTexSubImage(gl::GLContext* gl, TexImageTarget target,
680 GLint level, GLint xOffset,
681 GLint yOffset, GLint zOffset, GLint x,
682 GLint y, GLsizei width, GLsizei height) {
683 gl::GLContext::LocalErrorScope errorScope(*gl);
685 if (IsTarget3D(target)) {
686 gl->fCopyTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset, x, y,
687 width, height);
688 } else {
689 MOZ_ASSERT(zOffset == 0);
690 gl->fCopyTexSubImage2D(target.get(), level, xOffset, yOffset, x, y, width,
691 height);
694 return errorScope.GetError();
697 //////////////////////////////////////////////////////////////////////////////////////////
698 //////////////////////////////////////////////////////////////////////////////////////////
699 // Actual (mostly generic) function implementations
701 static bool ValidateCompressedTexImageRestrictions(
702 const WebGLContext* webgl, TexImageTarget target, uint32_t level,
703 const webgl::FormatInfo* format, const uvec3& size) {
704 const auto fnIsDimValid_S3TC = [&](const char* const name, uint32_t levelSize,
705 uint32_t blockSize) {
706 const auto impliedBaseSize = levelSize << level;
707 if (impliedBaseSize % blockSize == 0) return true;
708 webgl->ErrorInvalidOperation(
709 "%u is never a valid %s for level %u, because it implies a base mip %s "
710 "of %u."
711 " %s requires that base mip levels have a %s multiple of %u.",
712 levelSize, name, level, name, impliedBaseSize, format->name, name,
713 blockSize);
714 return false;
717 switch (format->compression->family) {
718 case webgl::CompressionFamily::ASTC:
719 if (target == LOCAL_GL_TEXTURE_3D &&
720 !webgl->gl->IsExtensionSupported(
721 gl::GLContext::KHR_texture_compression_astc_hdr)) {
722 webgl->ErrorInvalidOperation("TEXTURE_3D requires ASTC's hdr profile.");
723 return false;
725 break;
727 case webgl::CompressionFamily::PVRTC:
728 if (!IsPowerOfTwo(size.x) || !IsPowerOfTwo(size.y)) {
729 webgl->ErrorInvalidValue("%s requires power-of-two width and height.",
730 format->name);
731 return false;
733 break;
735 case webgl::CompressionFamily::BPTC:
736 case webgl::CompressionFamily::RGTC:
737 case webgl::CompressionFamily::S3TC:
738 if (!fnIsDimValid_S3TC("width", size.x,
739 format->compression->blockWidth) ||
740 !fnIsDimValid_S3TC("height", size.y,
741 format->compression->blockHeight)) {
742 return false;
744 break;
746 // Default: There are no restrictions on CompressedTexImage.
747 case webgl::CompressionFamily::ES3:
748 case webgl::CompressionFamily::ETC1:
749 break;
752 return true;
755 static bool ValidateFormatAndSize(const WebGLContext* webgl,
756 TexImageTarget target,
757 const webgl::FormatInfo* format,
758 const uvec3& size) {
759 // Check if texture size will likely be rejected by the driver and give a more
760 // meaningful error message.
761 auto baseImageSize = CheckedInt<uint64_t>(format->estimatedBytesPerPixel) *
762 (uint32_t)size.x * (uint32_t)size.y * (uint32_t)size.z;
763 if (target == LOCAL_GL_TEXTURE_CUBE_MAP) {
764 baseImageSize *= 6;
766 if (!baseImageSize.isValid() ||
767 baseImageSize.value() >
768 (uint64_t)StaticPrefs::webgl_max_size_per_texture_mib() *
769 (1024 * 1024)) {
770 webgl->ErrorOutOfMemory(
771 "Texture size too large; base image mebibytes > "
772 "webgl.max-size-per-texture-mib");
773 return false;
776 // GLES 3.0.4 p127:
777 // "Textures with a base internal format of DEPTH_COMPONENT or DEPTH_STENCIL
778 // are supported by texture image specification commands only if `target` is
779 // TEXTURE_2D, TEXTURE_2D_ARRAY, or TEXTURE_CUBE_MAP. Using these formats in
780 // conjunction with any other `target` will result in an INVALID_OPERATION
781 // error."
782 const bool ok = [&]() {
783 if (bool(format->d) & (target == LOCAL_GL_TEXTURE_3D)) return false;
785 if (format->compression) {
786 switch (format->compression->family) {
787 case webgl::CompressionFamily::ES3:
788 case webgl::CompressionFamily::S3TC:
789 if (target == LOCAL_GL_TEXTURE_3D) return false;
790 break;
792 case webgl::CompressionFamily::ETC1:
793 case webgl::CompressionFamily::PVRTC:
794 case webgl::CompressionFamily::RGTC:
795 if (target == LOCAL_GL_TEXTURE_3D ||
796 target == LOCAL_GL_TEXTURE_2D_ARRAY) {
797 return false;
799 break;
800 default:
801 break;
804 return true;
805 }();
806 if (!ok) {
807 webgl->ErrorInvalidOperation("Format %s cannot be used with target %s.",
808 format->name, GetEnumName(target.get()));
809 return false;
812 return true;
815 void WebGLTexture::TexStorage(TexTarget target, uint32_t levels,
816 GLenum sizedFormat, const uvec3& size) {
817 // Check levels
818 if (levels < 1) {
819 mContext->ErrorInvalidValue("`levels` must be >= 1.");
820 return;
823 if (!size.x || !size.y || !size.z) {
824 mContext->ErrorInvalidValue("Dimensions must be non-zero.");
825 return;
828 const TexImageTarget testTarget =
829 IsCubeMap() ? LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X : target.get();
830 webgl::ImageInfo* baseImageInfo;
831 if (!ValidateTexImageSpecification(testTarget, 0, size, &baseImageInfo)) {
832 return;
834 MOZ_ALWAYS_TRUE(baseImageInfo);
836 auto dstUsage = mContext->mFormatUsage->GetSizedTexUsage(sizedFormat);
837 if (!dstUsage) {
838 mContext->ErrorInvalidEnumInfo("internalformat", sizedFormat);
839 return;
841 auto dstFormat = dstUsage->format;
843 if (!ValidateFormatAndSize(mContext, testTarget, dstFormat, size)) return;
845 if (dstFormat->compression) {
846 if (!ValidateCompressedTexImageRestrictions(mContext, testTarget, 0,
847 dstFormat, size)) {
848 return;
852 ////////////////////////////////////
854 const bool levelsOk = [&]() {
855 // Right-shift is only defined for bits-1, which is too large anyways.
856 const auto lastLevel = uint32_t(levels - 1);
857 if (lastLevel > 31) return false;
859 const auto lastLevelWidth = uint32_t(size.x) >> lastLevel;
860 const auto lastLevelHeight = uint32_t(size.y) >> lastLevel;
862 // If these are all zero, then some earlier level was the final 1x1(x1)
863 // level.
864 bool ok = lastLevelWidth || lastLevelHeight;
865 if (target == LOCAL_GL_TEXTURE_3D) {
866 const auto lastLevelDepth = uint32_t(size.z) >> lastLevel;
867 ok |= bool(lastLevelDepth);
869 return ok;
870 }();
871 if (!levelsOk) {
872 mContext->ErrorInvalidOperation(
873 "Too many levels requested for the given"
874 " dimensions. (levels: %u, width: %u, height: %u,"
875 " depth: %u)",
876 levels, size.x, size.y, size.z);
877 return;
880 ////////////////////////////////////
881 // Do the thing!
883 GLenum error = DoTexStorage(mContext->gl, target.get(), levels, sizedFormat,
884 size.x, size.y, size.z);
886 mContext->OnDataAllocCall();
888 if (error == LOCAL_GL_OUT_OF_MEMORY) {
889 mContext->ErrorOutOfMemory("Ran out of memory during texture allocation.");
890 Truncate();
891 return;
893 if (error) {
894 mContext->GenerateError(error, "Unexpected error from driver.");
895 const nsPrintfCString call(
896 "DoTexStorage(0x%04x, %i, 0x%04x, %i,%i,%i) -> 0x%04x", target.get(),
897 levels, sizedFormat, size.x, size.y, size.z, error);
898 gfxCriticalError() << "Unexpected error from driver: "
899 << call.BeginReading();
900 return;
903 ////////////////////////////////////
904 // Update our specification data.
906 auto uninitializedSlices = Some(std::vector<bool>(size.z, true));
907 const webgl::ImageInfo newInfo{dstUsage, size.x, size.y, size.z,
908 std::move(uninitializedSlices)};
911 const auto base_level = mBaseMipmapLevel;
912 mBaseMipmapLevel = 0;
914 ImageInfoAtFace(0, 0) = newInfo;
915 PopulateMipChain(levels - 1);
917 mBaseMipmapLevel = base_level;
920 mImmutable = true;
921 mImmutableLevelCount = AutoAssertCast(levels);
922 ClampLevelBaseAndMax();
925 ////////////////////////////////////////
926 // Tex(Sub)Image
928 // TexSubImage iff `!respectFormat`
929 void WebGLTexture::TexImage(uint32_t level, GLenum respecFormat,
930 const uvec3& offset, const webgl::PackingInfo& pi,
931 const webgl::TexUnpackBlobDesc& src) {
932 const auto blob = webgl::TexUnpackBlob::Create(src);
933 if (!blob) {
934 MOZ_ASSERT(false);
935 return;
938 const auto imageTarget = blob->mDesc.imageTarget;
939 auto size = blob->mDesc.size;
941 if (!IsTarget3D(imageTarget)) {
942 size.z = 1;
945 ////////////////////////////////////
946 // Get dest info
948 const auto& fua = mContext->mFormatUsage;
949 const auto fnValidateUnpackEnums = [&]() {
950 if (!fua->AreUnpackEnumsValid(pi.format, pi.type)) {
951 mContext->ErrorInvalidEnum("Invalid unpack format/type: %s/%s",
952 EnumString(pi.format).c_str(),
953 EnumString(pi.type).c_str());
954 return false;
956 return true;
959 webgl::ImageInfo* imageInfo;
960 const webgl::FormatUsageInfo* dstUsage;
961 if (respecFormat) {
962 if (!ValidateTexImageSpecification(imageTarget, level, size, &imageInfo))
963 return;
964 MOZ_ASSERT(imageInfo);
966 if (!fua->IsInternalFormatEnumValid(respecFormat)) {
967 mContext->ErrorInvalidValue("Invalid internalformat: 0x%04x",
968 respecFormat);
969 return;
972 dstUsage = fua->GetSizedTexUsage(respecFormat);
973 if (!dstUsage) {
974 if (respecFormat != pi.format) {
975 /* GL ES Version 3.0.4 - 3.8.3 Texture Image Specification
976 * "Specifying a combination of values for format, type, and
977 * internalformat that is not listed as a valid combination
978 * in tables 3.2 or 3.3 generates the error INVALID_OPERATION."
980 if (!fnValidateUnpackEnums()) return;
981 mContext->ErrorInvalidOperation(
982 "Unsized internalFormat must match"
983 " unpack format.");
984 return;
987 dstUsage = fua->GetUnsizedTexUsage(pi);
990 if (!dstUsage) {
991 if (!fnValidateUnpackEnums()) return;
992 mContext->ErrorInvalidOperation(
993 "Invalid internalformat/format/type:"
994 " 0x%04x/0x%04x/0x%04x",
995 respecFormat, pi.format, pi.type);
996 return;
999 const auto& dstFormat = dstUsage->format;
1000 if (!ValidateFormatAndSize(mContext, imageTarget, dstFormat, size)) return;
1002 if (!mContext->IsWebGL2() && dstFormat->d) {
1003 if (imageTarget != LOCAL_GL_TEXTURE_2D || blob->HasData() || level != 0) {
1004 mContext->ErrorInvalidOperation(
1005 "With format %s, this function may only"
1006 " be called with target=TEXTURE_2D,"
1007 " data=null, and level=0.",
1008 dstFormat->name);
1009 return;
1012 } else {
1013 if (!ValidateTexImageSelection(imageTarget, level, offset, size,
1014 &imageInfo)) {
1015 return;
1017 MOZ_ASSERT(imageInfo);
1018 dstUsage = imageInfo->mFormat;
1020 const auto& dstFormat = dstUsage->format;
1021 if (!mContext->IsWebGL2() && dstFormat->d) {
1022 mContext->ErrorInvalidOperation(
1023 "Function may not be called on a texture of"
1024 " format %s.",
1025 dstFormat->name);
1026 return;
1030 ////////////////////////////////////
1031 // Get source info
1033 const webgl::DriverUnpackInfo* driverUnpackInfo;
1034 if (!dstUsage->IsUnpackValid(pi, &driverUnpackInfo)) {
1035 if (!fnValidateUnpackEnums()) return;
1036 mContext->ErrorInvalidOperation(
1037 "Mismatched internalFormat and format/type:"
1038 " 0x%04x and 0x%04x/0x%04x",
1039 respecFormat, pi.format, pi.type);
1040 return;
1043 if (!blob->Validate(mContext, pi)) return;
1045 ////////////////////////////////////
1046 // Do the thing!
1048 Maybe<webgl::ImageInfo> newImageInfo;
1049 bool isRespec = false;
1050 if (respecFormat) {
1051 // It's tempting to do allocation first, and TexSubImage second, but this is
1052 // generally slower.
1053 newImageInfo = Some(webgl::ImageInfo{dstUsage, size.x, size.y, size.z});
1054 if (!blob->HasData()) {
1055 newImageInfo->mUninitializedSlices =
1056 Some(std::vector<bool>(size.z, true));
1059 isRespec = (imageInfo->mWidth != newImageInfo->mWidth ||
1060 imageInfo->mHeight != newImageInfo->mHeight ||
1061 imageInfo->mDepth != newImageInfo->mDepth ||
1062 imageInfo->mFormat != newImageInfo->mFormat);
1063 } else {
1064 if (!blob->HasData()) {
1065 mContext->ErrorInvalidValue("`source` cannot be null.");
1066 return;
1068 if (!EnsureImageDataInitializedForUpload(this, imageTarget, level, offset,
1069 size, imageInfo)) {
1070 return;
1074 webgl::PixelPackingState{}.AssertCurrentUnpack(*mContext->gl,
1075 mContext->IsWebGL2());
1077 blob->mDesc.unpacking.ApplyUnpack(*mContext->gl, mContext->IsWebGL2(), size);
1078 const auto revertUnpacking = MakeScopeExit([&]() {
1079 webgl::PixelPackingState{}.ApplyUnpack(*mContext->gl, mContext->IsWebGL2(),
1080 size);
1083 const bool isSubImage = !respecFormat;
1084 GLenum glError = 0;
1085 if (!blob->TexOrSubImage(isSubImage, isRespec, this, level, driverUnpackInfo,
1086 offset.x, offset.y, offset.z, pi, &glError)) {
1087 return;
1090 if (glError == LOCAL_GL_OUT_OF_MEMORY) {
1091 mContext->ErrorOutOfMemory("Driver ran out of memory during upload.");
1092 Truncate();
1093 return;
1096 if (glError) {
1097 const auto enumStr = EnumString(glError);
1098 const nsPrintfCString dui(
1099 "Unexpected error %s during upload. (dui: %x/%x/%x)", enumStr.c_str(),
1100 driverUnpackInfo->internalFormat, driverUnpackInfo->unpackFormat,
1101 driverUnpackInfo->unpackType);
1102 mContext->ErrorInvalidOperation("%s", dui.BeginReading());
1103 gfxCriticalError() << mContext->FuncName() << ": " << dui.BeginReading();
1104 return;
1107 ////////////////////////////////////
1108 // Update our specification data?
1110 if (respecFormat) {
1111 mContext->OnDataAllocCall();
1112 *imageInfo = *newImageInfo;
1113 InvalidateCaches();
1117 ////////////////////////////////////////
1118 // CompressedTex(Sub)Image
1120 static inline bool IsSubImageBlockAligned(
1121 const webgl::CompressedFormatInfo* compression,
1122 const webgl::ImageInfo* imageInfo, GLint xOffset, GLint yOffset,
1123 uint32_t width, uint32_t height) {
1124 if (xOffset % compression->blockWidth != 0 ||
1125 yOffset % compression->blockHeight != 0) {
1126 return false;
1129 if (width % compression->blockWidth != 0 &&
1130 xOffset + width != imageInfo->mWidth)
1131 return false;
1133 if (height % compression->blockHeight != 0 &&
1134 yOffset + height != imageInfo->mHeight)
1135 return false;
1137 return true;
1140 // CompressedTexSubImage iff `sub`
1141 void WebGLTexture::CompressedTexImage(bool sub, GLenum imageTarget,
1142 uint32_t level, GLenum formatEnum,
1143 const uvec3& offset, const uvec3& size,
1144 const Range<const uint8_t>& src,
1145 const uint32_t pboImageSize,
1146 const Maybe<uint64_t>& pboOffset) {
1147 auto imageSize = pboImageSize;
1148 if (pboOffset) {
1149 const auto& buffer =
1150 mContext->ValidateBufferSelection(LOCAL_GL_PIXEL_UNPACK_BUFFER);
1151 if (!buffer) return;
1152 auto availBytes = buffer->ByteLength();
1153 if (*pboOffset > availBytes) {
1154 mContext->GenerateError(
1155 LOCAL_GL_INVALID_OPERATION,
1156 "`offset` (%llu) must be <= PIXEL_UNPACK_BUFFER size (%llu).",
1157 *pboOffset, availBytes);
1158 return;
1160 availBytes -= *pboOffset;
1161 if (availBytes < pboImageSize) {
1162 mContext->GenerateError(
1163 LOCAL_GL_INVALID_OPERATION,
1164 "PIXEL_UNPACK_BUFFER size minus `offset` (%llu) too small for"
1165 " `pboImageSize` (%u).",
1166 availBytes, pboImageSize);
1167 return;
1169 } else {
1170 if (mContext->mBoundPixelUnpackBuffer) {
1171 mContext->GenerateError(LOCAL_GL_INVALID_OPERATION,
1172 "PIXEL_UNPACK_BUFFER is non-null.");
1173 return;
1175 imageSize = src.length();
1178 // -
1180 const auto usage = mContext->mFormatUsage->GetSizedTexUsage(formatEnum);
1181 if (!usage || !usage->format->compression) {
1182 mContext->ErrorInvalidEnumArg("format", formatEnum);
1183 return;
1186 webgl::ImageInfo* imageInfo;
1187 if (!sub) {
1188 if (!ValidateTexImageSpecification(imageTarget, level, size, &imageInfo)) {
1189 return;
1191 MOZ_ASSERT(imageInfo);
1193 if (!ValidateFormatAndSize(mContext, imageTarget, usage->format, size))
1194 return;
1195 if (!ValidateCompressedTexImageRestrictions(mContext, imageTarget, level,
1196 usage->format, size)) {
1197 return;
1199 } else {
1200 if (!ValidateTexImageSelection(imageTarget, level, offset, size,
1201 &imageInfo))
1202 return;
1203 MOZ_ASSERT(imageInfo);
1205 const auto dstUsage = imageInfo->mFormat;
1206 if (usage != dstUsage) {
1207 mContext->ErrorInvalidOperation(
1208 "`format` must match the format of the"
1209 " existing texture image.");
1210 return;
1213 const auto& format = usage->format;
1214 switch (format->compression->family) {
1215 // Forbidden:
1216 case webgl::CompressionFamily::ETC1:
1217 mContext->ErrorInvalidOperation(
1218 "Format does not allow sub-image"
1219 " updates.");
1220 return;
1222 // Block-aligned:
1223 case webgl::CompressionFamily::ES3: // Yes, the ES3 formats don't match
1224 // the ES3
1225 case webgl::CompressionFamily::S3TC: // default behavior.
1226 case webgl::CompressionFamily::BPTC:
1227 case webgl::CompressionFamily::RGTC:
1228 if (!IsSubImageBlockAligned(format->compression, imageInfo, offset.x,
1229 offset.y, size.x, size.y)) {
1230 mContext->ErrorInvalidOperation(
1231 "Format requires block-aligned sub-image"
1232 " updates.");
1233 return;
1235 break;
1237 // Full-only: (The ES3 default)
1238 case webgl::CompressionFamily::ASTC:
1239 case webgl::CompressionFamily::PVRTC:
1240 if (offset.x || offset.y || size.x != imageInfo->mWidth ||
1241 size.y != imageInfo->mHeight) {
1242 mContext->ErrorInvalidOperation(
1243 "Format does not allow partial sub-image"
1244 " updates.");
1245 return;
1247 break;
1251 switch (usage->format->compression->family) {
1252 case webgl::CompressionFamily::BPTC:
1253 case webgl::CompressionFamily::RGTC:
1254 if (level == 0) {
1255 if (size.x % 4 != 0 || size.y % 4 != 0) {
1256 mContext->ErrorInvalidOperation(
1257 "For level == 0, width and height must be multiples of 4.");
1258 return;
1261 break;
1263 default:
1264 break;
1267 if (!ValidateCompressedTexUnpack(mContext, size, usage->format, imageSize))
1268 return;
1270 ////////////////////////////////////
1271 // Do the thing!
1273 if (sub) {
1274 if (!EnsureImageDataInitializedForUpload(this, imageTarget, level, offset,
1275 size, imageInfo)) {
1276 return;
1280 const ScopedLazyBind bindPBO(mContext->gl, LOCAL_GL_PIXEL_UNPACK_BUFFER,
1281 mContext->mBoundPixelUnpackBuffer);
1282 GLenum error;
1283 const void* ptr;
1284 if (pboOffset) {
1285 ptr = reinterpret_cast<const void*>(*pboOffset);
1286 } else {
1287 ptr = reinterpret_cast<const void*>(src.begin().get());
1290 if (!sub) {
1291 error = DoCompressedTexImage(mContext->gl, imageTarget, level, formatEnum,
1292 size.x, size.y, size.z, imageSize, ptr);
1293 } else {
1294 error = DoCompressedTexSubImage(mContext->gl, imageTarget, level, offset.x,
1295 offset.y, offset.z, size.x, size.y, size.z,
1296 formatEnum, imageSize, ptr);
1298 if (error == LOCAL_GL_OUT_OF_MEMORY) {
1299 mContext->ErrorOutOfMemory("Ran out of memory during upload.");
1300 Truncate();
1301 return;
1303 if (error) {
1304 mContext->GenerateError(error, "Unexpected error from driver.");
1305 nsCString call;
1306 if (!sub) {
1307 call = nsPrintfCString(
1308 "DoCompressedTexImage(0x%04x, %u, 0x%04x, %u,%u,%u, %u, %p)",
1309 imageTarget, level, formatEnum, size.x, size.y, size.z, imageSize,
1310 ptr);
1311 } else {
1312 call = nsPrintfCString(
1313 "DoCompressedTexSubImage(0x%04x, %u, %u,%u,%u, %u,%u,%u, 0x%04x, %u, "
1314 "%p)",
1315 imageTarget, level, offset.x, offset.y, offset.z, size.x, size.y,
1316 size.z, formatEnum, imageSize, ptr);
1318 gfxCriticalError() << "Unexpected error " << gfx::hexa(error)
1319 << " from driver: " << call.BeginReading();
1320 return;
1323 ////////////////////////////////////
1324 // Update our specification data?
1326 if (!sub) {
1327 const auto uninitializedSlices = Nothing();
1328 const webgl::ImageInfo newImageInfo{usage, size.x, size.y, size.z,
1329 uninitializedSlices};
1330 *imageInfo = newImageInfo;
1331 InvalidateCaches();
1335 ////////////////////////////////////////
1336 // CopyTex(Sub)Image
1338 static bool ValidateCopyTexImageFormats(WebGLContext* webgl,
1339 const webgl::FormatInfo* srcFormat,
1340 const webgl::FormatInfo* dstFormat) {
1341 MOZ_ASSERT(!srcFormat->compression);
1342 if (dstFormat->compression) {
1343 webgl->ErrorInvalidEnum(
1344 "Specified destination must not have a compressed"
1345 " format.");
1346 return false;
1349 if (dstFormat->effectiveFormat == webgl::EffectiveFormat::RGB9_E5) {
1350 webgl->ErrorInvalidOperation(
1351 "RGB9_E5 is an invalid destination for"
1352 " CopyTex(Sub)Image. (GLES 3.0.4 p145)");
1353 return false;
1356 if (!DoChannelsMatchForCopyTexImage(srcFormat, dstFormat)) {
1357 webgl->ErrorInvalidOperation(
1358 "Destination channels must be compatible with"
1359 " source channels. (GLES 3.0.4 p140 Table 3.16)");
1360 return false;
1363 return true;
1366 ////////////////////////////////////////////////////////////////////////////////
1368 class ScopedCopyTexImageSource {
1369 WebGLContext* const mWebGL;
1370 GLuint mRB;
1371 GLuint mFB;
1373 public:
1374 ScopedCopyTexImageSource(WebGLContext* webgl, uint32_t srcWidth,
1375 uint32_t srcHeight,
1376 const webgl::FormatInfo* srcFormat,
1377 const webgl::FormatUsageInfo* dstUsage);
1378 ~ScopedCopyTexImageSource();
1381 ScopedCopyTexImageSource::ScopedCopyTexImageSource(
1382 WebGLContext* webgl, uint32_t srcWidth, uint32_t srcHeight,
1383 const webgl::FormatInfo* srcFormat, const webgl::FormatUsageInfo* dstUsage)
1384 : mWebGL(webgl), mRB(0), mFB(0) {
1385 switch (dstUsage->format->unsizedFormat) {
1386 case webgl::UnsizedFormat::L:
1387 case webgl::UnsizedFormat::A:
1388 case webgl::UnsizedFormat::LA:
1389 webgl->GenerateWarning(
1390 "Copying to a LUMINANCE, ALPHA, or LUMINANCE_ALPHA"
1391 " is deprecated, and has severely reduced performance"
1392 " on some platforms.");
1393 break;
1395 default:
1396 MOZ_ASSERT(!dstUsage->textureSwizzleRGBA);
1397 return;
1400 if (!dstUsage->textureSwizzleRGBA) return;
1402 gl::GLContext* gl = webgl->gl;
1404 GLenum sizedFormat;
1406 switch (srcFormat->componentType) {
1407 case webgl::ComponentType::NormUInt:
1408 sizedFormat = LOCAL_GL_RGBA8;
1409 break;
1411 case webgl::ComponentType::Float:
1412 if (webgl->IsExtensionEnabled(
1413 WebGLExtensionID::WEBGL_color_buffer_float)) {
1414 sizedFormat = LOCAL_GL_RGBA32F;
1415 webgl->WarnIfImplicit(WebGLExtensionID::WEBGL_color_buffer_float);
1416 break;
1419 if (webgl->IsExtensionEnabled(
1420 WebGLExtensionID::EXT_color_buffer_half_float)) {
1421 sizedFormat = LOCAL_GL_RGBA16F;
1422 webgl->WarnIfImplicit(WebGLExtensionID::EXT_color_buffer_half_float);
1423 break;
1425 MOZ_CRASH("GFX: Should be able to request CopyTexImage from Float.");
1427 default:
1428 MOZ_CRASH("GFX: Should be able to request CopyTexImage from this type.");
1431 gl::ScopedTexture scopedTex(gl);
1432 gl::ScopedBindTexture scopedBindTex(gl, scopedTex.Texture(),
1433 LOCAL_GL_TEXTURE_2D);
1435 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER,
1436 LOCAL_GL_NEAREST);
1437 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER,
1438 LOCAL_GL_NEAREST);
1440 GLint blitSwizzle[4] = {LOCAL_GL_ZERO};
1441 switch (dstUsage->format->unsizedFormat) {
1442 case webgl::UnsizedFormat::L:
1443 blitSwizzle[0] = LOCAL_GL_RED;
1444 break;
1446 case webgl::UnsizedFormat::A:
1447 blitSwizzle[0] = LOCAL_GL_ALPHA;
1448 break;
1450 case webgl::UnsizedFormat::LA:
1451 blitSwizzle[0] = LOCAL_GL_RED;
1452 blitSwizzle[1] = LOCAL_GL_ALPHA;
1453 break;
1455 default:
1456 MOZ_CRASH("GFX: Unhandled unsizedFormat.");
1459 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_R,
1460 blitSwizzle[0]);
1461 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_G,
1462 blitSwizzle[1]);
1463 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_B,
1464 blitSwizzle[2]);
1465 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_A,
1466 blitSwizzle[3]);
1468 gl->fCopyTexImage2D(LOCAL_GL_TEXTURE_2D, 0, sizedFormat, 0, 0, srcWidth,
1469 srcHeight, 0);
1471 // Now create the swizzled FB we'll be exposing.
1473 GLuint rgbaRB = 0;
1474 GLuint rgbaFB = 0;
1476 gl->fGenRenderbuffers(1, &rgbaRB);
1477 gl::ScopedBindRenderbuffer scopedRB(gl, rgbaRB);
1478 gl->fRenderbufferStorage(LOCAL_GL_RENDERBUFFER, sizedFormat, srcWidth,
1479 srcHeight);
1481 gl->fGenFramebuffers(1, &rgbaFB);
1482 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, rgbaFB);
1483 gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER,
1484 LOCAL_GL_COLOR_ATTACHMENT0,
1485 LOCAL_GL_RENDERBUFFER, rgbaRB);
1487 const GLenum status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
1488 if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
1489 MOZ_CRASH("GFX: Temp framebuffer is not complete.");
1493 // Draw-blit rgbaTex into rgbaFB.
1494 const gfx::IntSize srcSize(srcWidth, srcHeight);
1496 const gl::ScopedBindFramebuffer bindFB(gl, rgbaFB);
1497 gl->BlitHelper()->DrawBlitTextureToFramebuffer(scopedTex.Texture(), srcSize,
1498 srcSize);
1501 // Leave RB and FB alive, and FB bound.
1502 mRB = rgbaRB;
1503 mFB = rgbaFB;
1506 template <typename T>
1507 static inline GLenum ToGLHandle(const T& obj) {
1508 return (obj ? obj->mGLName : 0);
1511 ScopedCopyTexImageSource::~ScopedCopyTexImageSource() {
1512 if (!mFB) {
1513 MOZ_ASSERT(!mRB);
1514 return;
1516 MOZ_ASSERT(mRB);
1518 gl::GLContext* gl = mWebGL->gl;
1520 // If we're swizzling, it's because we're on a GL core (3.2+) profile, which
1521 // has split framebuffer support.
1522 gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER,
1523 ToGLHandle(mWebGL->mBoundDrawFramebuffer));
1524 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER,
1525 ToGLHandle(mWebGL->mBoundReadFramebuffer));
1527 gl->fDeleteFramebuffers(1, &mFB);
1528 gl->fDeleteRenderbuffers(1, &mRB);
1531 ////////////////////////////////////////////////////////////////////////////////
1533 static bool GetUnsizedFormatForCopy(GLenum internalFormat,
1534 webgl::UnsizedFormat* const out) {
1535 switch (internalFormat) {
1536 case LOCAL_GL_RED:
1537 *out = webgl::UnsizedFormat::R;
1538 break;
1539 case LOCAL_GL_RG:
1540 *out = webgl::UnsizedFormat::RG;
1541 break;
1542 case LOCAL_GL_RGB:
1543 *out = webgl::UnsizedFormat::RGB;
1544 break;
1545 case LOCAL_GL_RGBA:
1546 *out = webgl::UnsizedFormat::RGBA;
1547 break;
1548 case LOCAL_GL_LUMINANCE:
1549 *out = webgl::UnsizedFormat::L;
1550 break;
1551 case LOCAL_GL_ALPHA:
1552 *out = webgl::UnsizedFormat::A;
1553 break;
1554 case LOCAL_GL_LUMINANCE_ALPHA:
1555 *out = webgl::UnsizedFormat::LA;
1556 break;
1558 default:
1559 return false;
1562 return true;
1565 static const webgl::FormatUsageInfo* ValidateCopyDestUsage(
1566 WebGLContext* webgl, const webgl::FormatInfo* srcFormat,
1567 GLenum internalFormat) {
1568 const auto& fua = webgl->mFormatUsage;
1570 switch (internalFormat) {
1571 case LOCAL_GL_R8_SNORM:
1572 case LOCAL_GL_RG8_SNORM:
1573 case LOCAL_GL_RGB8_SNORM:
1574 case LOCAL_GL_RGBA8_SNORM:
1575 webgl->ErrorInvalidEnum("SNORM formats are invalid for CopyTexImage.");
1576 return nullptr;
1579 auto dstUsage = fua->GetSizedTexUsage(internalFormat);
1580 if (!dstUsage) {
1581 // Ok, maybe it's unsized.
1582 webgl::UnsizedFormat unsizedFormat;
1583 if (!GetUnsizedFormatForCopy(internalFormat, &unsizedFormat)) {
1584 webgl->ErrorInvalidEnumInfo("internalFormat", internalFormat);
1585 return nullptr;
1588 const auto dstFormat = srcFormat->GetCopyDecayFormat(unsizedFormat);
1589 if (dstFormat) {
1590 dstUsage = fua->GetUsage(dstFormat->effectiveFormat);
1592 if (!dstUsage) {
1593 webgl->ErrorInvalidOperation(
1594 "0x%04x is not a valid unsized format for"
1595 " source format %s.",
1596 internalFormat, srcFormat->name);
1597 return nullptr;
1600 return dstUsage;
1602 // Alright, it's sized.
1604 const auto dstFormat = dstUsage->format;
1606 if (dstFormat->componentType != srcFormat->componentType) {
1607 webgl->ErrorInvalidOperation(
1608 "For sized internalFormats, source and dest"
1609 " component types must match. (source: %s, dest:"
1610 " %s)",
1611 srcFormat->name, dstFormat->name);
1612 return nullptr;
1615 bool componentSizesMatch = true;
1616 if (dstFormat->r) {
1617 componentSizesMatch &= (dstFormat->r == srcFormat->r);
1619 if (dstFormat->g) {
1620 componentSizesMatch &= (dstFormat->g == srcFormat->g);
1622 if (dstFormat->b) {
1623 componentSizesMatch &= (dstFormat->b == srcFormat->b);
1625 if (dstFormat->a) {
1626 componentSizesMatch &= (dstFormat->a == srcFormat->a);
1629 if (!componentSizesMatch) {
1630 webgl->ErrorInvalidOperation(
1631 "For sized internalFormats, source and dest"
1632 " component sizes must match exactly. (source: %s,"
1633 " dest: %s)",
1634 srcFormat->name, dstFormat->name);
1635 return nullptr;
1638 return dstUsage;
1641 static bool ValidateCopyTexImageForFeedback(const WebGLContext& webgl,
1642 const WebGLTexture& tex,
1643 const uint32_t mipLevel,
1644 const uint32_t zLayer) {
1645 const auto& fb = webgl.BoundReadFb();
1646 if (fb) {
1647 MOZ_ASSERT(fb->ColorReadBuffer());
1648 const auto& attach = *fb->ColorReadBuffer();
1649 MOZ_ASSERT(attach.ZLayerCount() ==
1650 1); // Multiview invalid for copyTexImage.
1652 if (attach.Texture() == &tex && attach.Layer() == zLayer &&
1653 attach.MipLevel() == mipLevel) {
1654 // Note that the TexImageTargets *don't* have to match for this to be
1655 // undefined per GLES 3.0.4 p211, thus an INVALID_OP in WebGL.
1656 webgl.ErrorInvalidOperation(
1657 "Feedback loop detected, as this texture"
1658 " is already attached to READ_FRAMEBUFFER's"
1659 " READ_BUFFER-selected COLOR_ATTACHMENT%u.",
1660 attach.mAttachmentPoint);
1661 return false;
1664 return true;
1667 static bool DoCopyTexOrSubImage(WebGLContext* webgl, bool isSubImage,
1668 bool needsInit, WebGLTexture* const tex,
1669 const TexImageTarget target, GLint level,
1670 GLint xWithinSrc, GLint yWithinSrc,
1671 uint32_t srcTotalWidth, uint32_t srcTotalHeight,
1672 const webgl::FormatUsageInfo* srcUsage,
1673 GLint xOffset, GLint yOffset, GLint zOffset,
1674 uint32_t dstWidth, uint32_t dstHeight,
1675 const webgl::FormatUsageInfo* dstUsage) {
1676 const auto& gl = webgl->gl;
1678 ////
1680 int32_t readX, readY;
1681 int32_t writeX, writeY;
1682 int32_t rwWidth, rwHeight;
1683 if (!Intersect(srcTotalWidth, xWithinSrc, dstWidth, &readX, &writeX,
1684 &rwWidth) ||
1685 !Intersect(srcTotalHeight, yWithinSrc, dstHeight, &readY, &writeY,
1686 &rwHeight)) {
1687 webgl->ErrorOutOfMemory("Bad subrect selection.");
1688 return false;
1691 writeX += xOffset;
1692 writeY += yOffset;
1694 ////
1696 GLenum error = 0;
1697 nsCString errorText;
1698 do {
1699 const auto& idealUnpack = dstUsage->idealUnpack;
1700 const auto& pi = idealUnpack->ToPacking();
1702 UniqueBuffer zeros;
1703 const bool fullOverwrite =
1704 (uint32_t(rwWidth) == dstWidth && uint32_t(rwHeight) == dstHeight);
1705 if (needsInit && !fullOverwrite) {
1706 CheckedInt<size_t> byteCount = BytesPerPixel(pi);
1707 byteCount *= dstWidth;
1708 byteCount *= dstHeight;
1710 if (byteCount.isValid()) {
1711 zeros = UniqueBuffer::Take(calloc(1u, byteCount.value()));
1714 if (!zeros.get()) {
1715 webgl->ErrorOutOfMemory("Ran out of memory allocating zeros.");
1716 return false;
1720 if (!isSubImage || zeros) {
1721 webgl::PixelPackingState{}.AssertCurrentUnpack(*gl, webgl->IsWebGL2());
1723 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1);
1724 const auto revert = MakeScopeExit(
1725 [&]() { gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4); });
1726 if (!isSubImage) {
1727 error = DoTexImage(gl, target, level, idealUnpack, dstWidth, dstHeight,
1728 1, nullptr);
1729 if (error) {
1730 errorText = nsPrintfCString(
1731 "DoTexImage(0x%04x, %i, {0x%04x, 0x%04x, 0x%04x}, %u,%u,1) -> "
1732 "0x%04x",
1733 target.get(), level, idealUnpack->internalFormat,
1734 idealUnpack->unpackFormat, idealUnpack->unpackType, dstWidth,
1735 dstHeight, error);
1736 break;
1739 if (zeros) {
1740 error = DoTexSubImage(gl, target, level, xOffset, yOffset, zOffset,
1741 dstWidth, dstHeight, 1, pi, zeros.get());
1742 if (error) {
1743 errorText = nsPrintfCString(
1744 "DoTexSubImage(0x%04x, %i, %i,%i,%i, %u,%u,1, {0x%04x, 0x%04x}) "
1745 "-> "
1746 "0x%04x",
1747 target.get(), level, xOffset, yOffset, zOffset, dstWidth,
1748 dstHeight, idealUnpack->unpackFormat, idealUnpack->unpackType,
1749 error);
1750 break;
1755 if (!rwWidth || !rwHeight) {
1756 // There aren't any pixels to copy, so we're 'done'.
1757 return true;
1760 const auto& srcFormat = srcUsage->format;
1761 ScopedCopyTexImageSource maybeSwizzle(webgl, srcTotalWidth, srcTotalHeight,
1762 srcFormat, dstUsage);
1764 error = DoCopyTexSubImage(gl, target, level, writeX, writeY, zOffset, readX,
1765 readY, rwWidth, rwHeight);
1766 if (error) {
1767 errorText = nsPrintfCString(
1768 "DoCopyTexSubImage(0x%04x, %i, %i,%i,%i, %i,%i, %u,%u) -> 0x%04x",
1769 target.get(), level, writeX, writeY, zOffset, readX, readY, rwWidth,
1770 rwHeight, error);
1771 break;
1774 return true;
1775 } while (false);
1777 if (error == LOCAL_GL_OUT_OF_MEMORY) {
1778 webgl->ErrorOutOfMemory("Ran out of memory during texture copy.");
1779 tex->Truncate();
1780 return false;
1783 if (gl->IsANGLE() && error == LOCAL_GL_INVALID_OPERATION) {
1784 webgl->ErrorImplementationBug(
1785 "ANGLE is particular about CopyTexSubImage"
1786 " formats matching exactly.");
1787 return false;
1790 webgl->GenerateError(error, "Unexpected error from driver.");
1791 gfxCriticalError() << "Unexpected error from driver: "
1792 << errorText.BeginReading();
1793 return false;
1796 // CopyTexSubImage if `!respecFormat`
1797 void WebGLTexture::CopyTexImage(GLenum imageTarget, uint32_t level,
1798 GLenum respecFormat, const uvec3& dstOffset,
1799 const ivec2& srcOffset, const uvec2& size2) {
1800 ////////////////////////////////////
1801 // Get source info
1803 const webgl::FormatUsageInfo* srcUsage;
1804 uint32_t srcTotalWidth;
1805 uint32_t srcTotalHeight;
1806 if (!mContext->BindCurFBForColorRead(&srcUsage, &srcTotalWidth,
1807 &srcTotalHeight)) {
1808 return;
1810 const auto& srcFormat = srcUsage->format;
1812 if (!ValidateCopyTexImageForFeedback(*mContext, *this, level, dstOffset.z))
1813 return;
1815 const auto size = uvec3{size2.x, size2.y, 1};
1817 ////////////////////////////////////
1818 // Get dest info
1820 webgl::ImageInfo* imageInfo;
1821 const webgl::FormatUsageInfo* dstUsage;
1822 if (respecFormat) {
1823 if (!ValidateTexImageSpecification(imageTarget, level, size, &imageInfo))
1824 return;
1825 MOZ_ASSERT(imageInfo);
1827 dstUsage = ValidateCopyDestUsage(mContext, srcFormat, respecFormat);
1828 if (!dstUsage) return;
1830 if (!ValidateFormatAndSize(mContext, imageTarget, dstUsage->format, size))
1831 return;
1832 } else {
1833 if (!ValidateTexImageSelection(imageTarget, level, dstOffset, size,
1834 &imageInfo)) {
1835 return;
1837 MOZ_ASSERT(imageInfo);
1839 dstUsage = imageInfo->mFormat;
1840 MOZ_ASSERT(dstUsage);
1843 const auto& dstFormat = dstUsage->format;
1844 if (!mContext->IsWebGL2() && dstFormat->d) {
1845 mContext->ErrorInvalidOperation(
1846 "Function may not be called with format %s.", dstFormat->name);
1847 return;
1850 ////////////////////////////////////
1851 // Check that source and dest info are compatible
1853 if (!ValidateCopyTexImageFormats(mContext, srcFormat, dstFormat)) return;
1855 ////////////////////////////////////
1856 // Do the thing!
1858 const bool isSubImage = !respecFormat;
1859 bool expectsInit = true;
1860 if (isSubImage) {
1861 if (!EnsureImageDataInitializedForUpload(this, imageTarget, level,
1862 dstOffset, size, imageInfo,
1863 &expectsInit)) {
1864 return;
1868 if (!DoCopyTexOrSubImage(mContext, isSubImage, expectsInit, this, imageTarget,
1869 level, srcOffset.x, srcOffset.y, srcTotalWidth,
1870 srcTotalHeight, srcUsage, dstOffset.x, dstOffset.y,
1871 dstOffset.z, size.x, size.y, dstUsage)) {
1872 Truncate();
1873 return;
1876 mContext->OnDataAllocCall();
1878 ////////////////////////////////////
1879 // Update our specification data?
1881 if (respecFormat) {
1882 const auto uninitializedSlices = Nothing();
1883 const webgl::ImageInfo newImageInfo{dstUsage, size.x, size.y, size.z,
1884 uninitializedSlices};
1885 *imageInfo = newImageInfo;
1886 InvalidateCaches();
1890 } // namespace mozilla