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"
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/layers/SharedSurfacesChild.h"
25 #include "mozilla/MathAlgorithms.h"
26 #include "mozilla/ScopeExit.h"
27 #include "mozilla/StaticPrefs_webgl.h"
28 #include "mozilla/Unused.h"
29 #include "nsLayoutUtils.h"
30 #include "ScopedGLHelpers.h"
31 #include "TexUnpackBlob.h"
32 #include "WebGLBuffer.h"
33 #include "WebGLContext.h"
34 #include "WebGLFormats.h"
35 #include "WebGLContextUtils.h"
36 #include "WebGLFramebuffer.h"
37 #include "WebGLTexelConversions.h"
42 // The canvas spec says that drawImage should draw the first frame of
43 // animated images. The webgl spec doesn't mention the issue, so we do the
45 static constexpr uint32_t kDefaultSurfaceFromElementFlags
=
46 nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE
|
47 nsLayoutUtils::SFE_USE_ELEMENT_SIZE_IF_VECTOR
|
48 nsLayoutUtils::SFE_EXACT_SIZE_SURFACE
|
49 nsLayoutUtils::SFE_ALLOW_NON_PREMULT
;
51 Maybe
<TexUnpackBlobDesc
> FromImageBitmap(const GLenum target
, Maybe
<uvec3
> size
,
52 const dom::ImageBitmap
& imageBitmap
,
53 ErrorResult
* const out_rv
) {
54 if (imageBitmap
.IsWriteOnly()) {
55 out_rv
->Throw(NS_ERROR_DOM_SECURITY_ERR
);
59 const auto cloneData
= imageBitmap
.ToCloneData();
64 const RefPtr
<gfx::DataSourceSurface
> surf
= cloneData
->mSurface
;
65 if (NS_WARN_IF(!surf
)) {
69 const auto imageSize
= *uvec2::FromSize(surf
->GetSize());
71 size
.emplace(imageSize
.x
, imageSize
.y
, 1);
74 // For SourceSurfaceSharedData, try to get SurfaceDescriptorExternalImage.
75 Maybe
<layers::SurfaceDescriptor
> sd
;
76 layers::SharedSurfacesChild::Share(surf
, sd
);
78 // WhatWG "HTML Living Standard" (30 October 2015):
79 // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned as
80 // non-premultiplied alpha values."
81 return Some(TexUnpackBlobDesc
{target
,
83 cloneData
->mAlphaType
,
94 static layers::SurfaceDescriptor
Flatten(const layers::SurfaceDescriptor
& sd
) {
95 const auto sdType
= sd
.type();
96 if (sdType
!= layers::SurfaceDescriptor::TSurfaceDescriptorGPUVideo
) {
99 const auto& sdv
= sd
.get_SurfaceDescriptorGPUVideo();
100 const auto& sdvType
= sdv
.type();
102 layers::SurfaceDescriptorGPUVideo::TSurfaceDescriptorRemoteDecoder
) {
106 const auto& sdrd
= sdv
.get_SurfaceDescriptorRemoteDecoder();
107 const auto& subdesc
= sdrd
.subdesc();
108 const auto& subdescType
= subdesc
.type();
109 switch (subdescType
) {
110 case layers::RemoteDecoderVideoSubDescriptor::T__None
:
111 case layers::RemoteDecoderVideoSubDescriptor::Tnull_t
:
114 case layers::RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorD3D10
:
115 return subdesc
.get_SurfaceDescriptorD3D10();
116 case layers::RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorDXGIYCbCr
:
117 return subdesc
.get_SurfaceDescriptorDXGIYCbCr();
118 case layers::RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorDMABuf
:
119 return subdesc
.get_SurfaceDescriptorDMABuf();
120 case layers::RemoteDecoderVideoSubDescriptor::
121 TSurfaceDescriptorMacIOSurface
:
122 return subdesc
.get_SurfaceDescriptorMacIOSurface();
123 case layers::RemoteDecoderVideoSubDescriptor::
124 TSurfaceDescriptorDcompSurface
:
125 return subdesc
.get_SurfaceDescriptorDcompSurface();
127 MOZ_CRASH("unreachable");
130 Maybe
<webgl::TexUnpackBlobDesc
> FromOffscreenCanvas(
131 const ClientWebGLContext
& webgl
, const GLenum target
, Maybe
<uvec3
> size
,
132 const dom::OffscreenCanvas
& canvas
, ErrorResult
* const out_error
) {
133 if (canvas
.IsWriteOnly()) {
134 webgl
.EnqueueWarning(
135 "OffscreenCanvas is write-only, thus cannot be uploaded.");
136 out_error
->ThrowSecurityError(
137 "OffscreenCanvas is write-only, thus cannot be uploaded.");
141 auto sfer
= nsLayoutUtils::SurfaceFromOffscreenCanvas(
142 const_cast<dom::OffscreenCanvas
*>(&canvas
),
143 kDefaultSurfaceFromElementFlags
);
144 return FromSurfaceFromElementResult(webgl
, target
, size
, sfer
, out_error
);
147 Maybe
<webgl::TexUnpackBlobDesc
> FromVideoFrame(
148 const ClientWebGLContext
& webgl
, const GLenum target
, Maybe
<uvec3
> size
,
149 const dom::VideoFrame
& videoFrame
, ErrorResult
* const out_error
) {
150 auto sfer
= nsLayoutUtils::SurfaceFromVideoFrame(
151 const_cast<dom::VideoFrame
*>(&videoFrame
),
152 kDefaultSurfaceFromElementFlags
);
153 return FromSurfaceFromElementResult(webgl
, target
, size
, sfer
, out_error
);
156 Maybe
<webgl::TexUnpackBlobDesc
> FromDomElem(const ClientWebGLContext
& webgl
,
159 const dom::Element
& elem
,
160 ErrorResult
* const out_error
) {
161 if (elem
.IsHTMLElement(nsGkAtoms::canvas
)) {
162 const dom::HTMLCanvasElement
* srcCanvas
=
163 static_cast<const dom::HTMLCanvasElement
*>(&elem
);
164 if (srcCanvas
->IsWriteOnly()) {
165 out_error
->Throw(NS_ERROR_DOM_SECURITY_ERR
);
170 uint32_t flags
= kDefaultSurfaceFromElementFlags
;
171 const auto& unpacking
= webgl
.State().mPixelUnpackState
;
172 if (unpacking
.colorspaceConversion
== LOCAL_GL_NONE
) {
173 flags
|= nsLayoutUtils::SFE_NO_COLORSPACE_CONVERSION
;
176 RefPtr
<gfx::DrawTarget
> idealDrawTarget
= nullptr; // Don't care for now.
177 auto sfer
= nsLayoutUtils::SurfaceFromElement(
178 const_cast<dom::Element
*>(&elem
), flags
, idealDrawTarget
);
179 return FromSurfaceFromElementResult(webgl
, target
, size
, sfer
, out_error
);
182 Maybe
<webgl::TexUnpackBlobDesc
> FromSurfaceFromElementResult(
183 const ClientWebGLContext
& webgl
, const GLenum target
, Maybe
<uvec3
> size
,
184 SurfaceFromElementResult
& sfer
, ErrorResult
* const out_error
) {
187 const auto& layersImage
= sfer
.mLayersImage
;
188 Maybe
<layers::SurfaceDescriptor
> sd
;
190 elemSize
= *uvec2::FromSize(layersImage
->GetSize());
192 sd
= layersImage
->GetDesc();
194 sd
= Some(Flatten(*sd
));
197 NS_WARNING("No SurfaceDescriptor for layers::Image!");
201 RefPtr
<gfx::DataSourceSurface
> dataSurf
;
202 if (!sd
&& sfer
.GetSourceSurface()) {
203 const auto surf
= sfer
.GetSourceSurface();
204 elemSize
= *uvec2::FromSize(surf
->GetSize());
206 // WARNING: OSX can lose our MakeCurrent here.
207 dataSurf
= surf
->GetDataSurface();
211 // For SourceSurfaceSharedData, try to get SurfaceDescriptorExternalImage.
212 layers::SharedSurfacesChild::Share(dataSurf
, sd
);
218 size
.emplace(elemSize
.x
, elemSize
.y
, 1);
223 if (!sd
&& !dataSurf
) {
224 webgl
.EnqueueWarning("Resource has no data (yet?). Uploading zeros.");
226 size
.emplace(0, 0, 1);
229 TexUnpackBlobDesc
{target
, size
.value(), gfxAlphaType::NonPremult
});
234 // While it's counter-intuitive, the shape of the SFEResult API means that we
235 // should try to pull out a surface first, and then, if we do pull out a
236 // surface, check CORS/write-only/etc..
238 if (!sfer
.mCORSUsed
) {
239 auto& srcPrincipal
= sfer
.mPrincipal
;
240 nsIPrincipal
* dstPrincipal
= webgl
.PrincipalOrNull();
241 if (!dstPrincipal
|| !dstPrincipal
->Subsumes(srcPrincipal
)) {
242 webgl
.EnqueueWarning("Cross-origin elements require CORS.");
243 out_error
->Throw(NS_ERROR_DOM_SECURITY_ERR
);
248 if (sfer
.mIsWriteOnly
) {
249 // mIsWriteOnly defaults to true, and so will be true even if SFE merely
250 // failed. Thus we must test mIsWriteOnly after successfully retrieving an
251 // Image or SourceSurface.
252 webgl
.EnqueueWarning("Element is write-only, thus cannot be uploaded.");
253 out_error
->Throw(NS_ERROR_DOM_SECURITY_ERR
);
260 return Some(TexUnpackBlobDesc
{target
,
273 //////////////////////////////////////////////////////////////////////////////////////////
274 //////////////////////////////////////////////////////////////////////////////////////////
276 static bool ValidateTexImage(WebGLContext
* webgl
, WebGLTexture
* texture
,
277 TexImageTarget target
, uint32_t level
,
278 webgl::ImageInfo
** const out_imageInfo
) {
280 if (level
>= WebGLTexture::kMaxLevelCount
) {
281 webgl
->ErrorInvalidValue("`level` is too large.");
285 auto& imageInfo
= texture
->ImageInfoAt(target
, level
);
286 *out_imageInfo
= &imageInfo
;
291 bool WebGLTexture::ValidateTexImageSpecification(
292 TexImageTarget target
, uint32_t level
, const uvec3
& size
,
293 webgl::ImageInfo
** const out_imageInfo
) {
295 mContext
->ErrorInvalidOperation("Specified texture is immutable.");
299 // Do this early to validate `level`.
300 webgl::ImageInfo
* imageInfo
;
301 if (!ValidateTexImage(mContext
, this, target
, level
, &imageInfo
))
304 if (mTarget
== LOCAL_GL_TEXTURE_CUBE_MAP
&& size
.x
!= size
.y
) {
305 mContext
->ErrorInvalidValue("Cube map images must be square.");
309 /* GLES 3.0.4, p133-134:
310 * GL_MAX_TEXTURE_SIZE is *not* the max allowed texture size. Rather, it is
311 * the max (width/height) size guaranteed not to generate an INVALID_VALUE for
312 * too-large dimensions. Sizes larger than GL_MAX_TEXTURE_SIZE *may or may
313 * not* result in an INVALID_VALUE, or possibly GL_OOM.
315 * However, we have needed to set our maximums lower in the past to prevent
316 * resource corruption. Therefore we have limits.maxTex2dSize, which is
317 * neither necessarily lower nor higher than MAX_TEXTURE_SIZE.
319 * Note that limits.maxTex2dSize must be >= than the advertized
320 * MAX_TEXTURE_SIZE. For simplicity, we advertize MAX_TEXTURE_SIZE as
321 * limits.maxTex2dSize.
324 uint32_t maxWidthHeight
= 0;
325 uint32_t maxDepth
= 0;
326 uint32_t maxLevel
= 0;
328 const auto& limits
= mContext
->Limits();
329 MOZ_ASSERT(level
<= 31);
330 switch (target
.get()) {
331 case LOCAL_GL_TEXTURE_2D
:
332 maxWidthHeight
= limits
.maxTex2dSize
>> level
;
334 maxLevel
= CeilingLog2(limits
.maxTex2dSize
);
337 case LOCAL_GL_TEXTURE_3D
:
338 maxWidthHeight
= limits
.maxTex3dSize
>> level
;
339 maxDepth
= maxWidthHeight
;
340 maxLevel
= CeilingLog2(limits
.maxTex3dSize
);
343 case LOCAL_GL_TEXTURE_2D_ARRAY
:
344 maxWidthHeight
= limits
.maxTex2dSize
>> level
;
345 // "The maximum number of layers for two-dimensional array textures
346 // (depth) must be at least MAX_ARRAY_TEXTURE_LAYERS for all levels."
347 maxDepth
= limits
.maxTexArrayLayers
;
348 maxLevel
= CeilingLog2(limits
.maxTex2dSize
);
351 default: // cube maps
352 MOZ_ASSERT(IsCubeMap());
353 maxWidthHeight
= limits
.maxTexCubeSize
>> level
;
355 maxLevel
= CeilingLog2(limits
.maxTexCubeSize
);
359 if (level
> maxLevel
) {
360 mContext
->ErrorInvalidValue("Requested level is not supported for target.");
364 if (size
.x
> maxWidthHeight
|| size
.y
> maxWidthHeight
|| size
.z
> maxDepth
) {
365 mContext
->ErrorInvalidValue("Requested size at this level is unsupported.");
370 /* GL ES Version 2.0.25 - 3.7.1 Texture Image Specification
371 * "If level is greater than zero, and either width or
372 * height is not a power-of-two, the error INVALID_VALUE is
375 * This restriction does not apply to GL ES Version 3.0+.
377 bool requirePOT
= (!mContext
->IsWebGL2() && level
!= 0);
380 if (!IsPowerOfTwo(size
.x
) || !IsPowerOfTwo(size
.y
)) {
381 mContext
->ErrorInvalidValue(
382 "For level > 0, width and height must be"
389 *out_imageInfo
= imageInfo
;
394 bool WebGLTexture::ValidateTexImageSelection(
395 TexImageTarget target
, uint32_t level
, const uvec3
& offset
,
396 const uvec3
& size
, webgl::ImageInfo
** const out_imageInfo
) {
397 webgl::ImageInfo
* imageInfo
;
398 if (!ValidateTexImage(mContext
, this, target
, level
, &imageInfo
))
401 if (!imageInfo
->IsDefined()) {
402 mContext
->ErrorInvalidOperation(
403 "The specified TexImage has not yet been"
408 const auto totalX
= CheckedUint32(offset
.x
) + size
.x
;
409 const auto totalY
= CheckedUint32(offset
.y
) + size
.y
;
410 const auto totalZ
= CheckedUint32(offset
.z
) + size
.z
;
412 if (!totalX
.isValid() || totalX
.value() > imageInfo
->mWidth
||
413 !totalY
.isValid() || totalY
.value() > imageInfo
->mHeight
||
414 !totalZ
.isValid() || totalZ
.value() > imageInfo
->mDepth
) {
415 mContext
->ErrorInvalidValue(
416 "Offset+size must be <= the size of the existing"
417 " specified image.");
421 *out_imageInfo
= imageInfo
;
425 static bool ValidateCompressedTexUnpack(WebGLContext
* webgl
, const uvec3
& size
,
426 const webgl::FormatInfo
* format
,
428 auto compression
= format
->compression
;
430 auto bytesPerBlock
= compression
->bytesPerBlock
;
431 auto blockWidth
= compression
->blockWidth
;
432 auto blockHeight
= compression
->blockHeight
;
434 auto widthInBlocks
= CheckedUint32(size
.x
) / blockWidth
;
435 auto heightInBlocks
= CheckedUint32(size
.y
) / blockHeight
;
436 if (size
.x
% blockWidth
) widthInBlocks
+= 1;
437 if (size
.y
% blockHeight
) heightInBlocks
+= 1;
439 const CheckedUint32 blocksPerImage
= widthInBlocks
* heightInBlocks
;
440 const CheckedUint32 bytesPerImage
= bytesPerBlock
* blocksPerImage
;
441 const CheckedUint32 bytesNeeded
= bytesPerImage
* size
.z
;
443 if (!bytesNeeded
.isValid()) {
444 webgl
->ErrorOutOfMemory("Overflow while computing the needed buffer size.");
448 if (dataSize
!= bytesNeeded
.value()) {
449 webgl
->ErrorInvalidValue(
450 "Provided buffer's size must match expected size."
451 " (needs %u, has %zu)",
452 bytesNeeded
.value(), dataSize
);
459 static bool DoChannelsMatchForCopyTexImage(const webgl::FormatInfo
* srcFormat
,
460 const webgl::FormatInfo
* dstFormat
) {
461 // GLES 3.0.4 p140 Table 3.16 "Valid CopyTexImage source
462 // framebuffer/destination texture base internal format combinations."
464 switch (srcFormat
->unsizedFormat
) {
465 case webgl::UnsizedFormat::RGBA
:
466 switch (dstFormat
->unsizedFormat
) {
467 case webgl::UnsizedFormat::A
:
468 case webgl::UnsizedFormat::L
:
469 case webgl::UnsizedFormat::LA
:
470 case webgl::UnsizedFormat::R
:
471 case webgl::UnsizedFormat::RG
:
472 case webgl::UnsizedFormat::RGB
:
473 case webgl::UnsizedFormat::RGBA
:
479 case webgl::UnsizedFormat::RGB
:
480 switch (dstFormat
->unsizedFormat
) {
481 case webgl::UnsizedFormat::L
:
482 case webgl::UnsizedFormat::R
:
483 case webgl::UnsizedFormat::RG
:
484 case webgl::UnsizedFormat::RGB
:
490 case webgl::UnsizedFormat::RG
:
491 switch (dstFormat
->unsizedFormat
) {
492 case webgl::UnsizedFormat::L
:
493 case webgl::UnsizedFormat::R
:
494 case webgl::UnsizedFormat::RG
:
500 case webgl::UnsizedFormat::R
:
501 switch (dstFormat
->unsizedFormat
) {
502 case webgl::UnsizedFormat::L
:
503 case webgl::UnsizedFormat::R
:
514 static bool EnsureImageDataInitializedForUpload(
515 WebGLTexture
* tex
, TexImageTarget target
, uint32_t level
,
516 const uvec3
& offset
, const uvec3
& size
, webgl::ImageInfo
* imageInfo
,
517 bool* const out_expectsInit
= nullptr) {
518 if (out_expectsInit
) {
519 *out_expectsInit
= false;
521 if (!imageInfo
->mUninitializedSlices
) return true;
523 if (size
.x
== imageInfo
->mWidth
&& size
.y
== imageInfo
->mHeight
) {
524 bool expectsInit
= false;
525 auto& isSliceUninit
= *imageInfo
->mUninitializedSlices
;
526 for (const auto z
: IntegerRange(offset
.z
, offset
.z
+ size
.z
)) {
527 if (!isSliceUninit
[z
]) continue;
529 isSliceUninit
[z
] = false;
531 if (out_expectsInit
) {
532 *out_expectsInit
= expectsInit
;
535 if (!expectsInit
) return true;
537 bool hasUninitialized
= false;
538 for (const auto z
: IntegerRange(imageInfo
->mDepth
)) {
539 hasUninitialized
|= isSliceUninit
[z
];
541 if (!hasUninitialized
) {
542 imageInfo
->mUninitializedSlices
.reset();
547 WebGLContext
* webgl
= tex
->mContext
;
548 webgl
->GenerateWarning(
549 "Texture has not been initialized prior to a"
550 " partial upload, forcing the browser to clear it."
551 " This may be slow.");
552 if (!tex
->EnsureImageDataInitialized(target
, level
)) {
553 MOZ_ASSERT(false, "Unexpected failure to init image data.");
560 //////////////////////////////////////////////////////////////////////////////////////////
561 //////////////////////////////////////////////////////////////////////////////////////////
564 static inline GLenum
DoTexStorage(gl::GLContext
* gl
, TexTarget target
,
565 GLsizei levels
, GLenum sizedFormat
,
566 GLsizei width
, GLsizei height
,
568 gl::GLContext::LocalErrorScope
errorScope(*gl
);
570 switch (target
.get()) {
571 case LOCAL_GL_TEXTURE_2D
:
572 case LOCAL_GL_TEXTURE_CUBE_MAP
:
573 MOZ_ASSERT(depth
== 1);
574 gl
->fTexStorage2D(target
.get(), levels
, sizedFormat
, width
, height
);
577 case LOCAL_GL_TEXTURE_3D
:
578 case LOCAL_GL_TEXTURE_2D_ARRAY
:
579 gl
->fTexStorage3D(target
.get(), levels
, sizedFormat
, width
, height
,
584 MOZ_CRASH("GFX: bad target");
587 return errorScope
.GetError();
590 bool IsTarget3D(TexImageTarget target
) {
591 switch (target
.get()) {
592 case LOCAL_GL_TEXTURE_2D
:
593 case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X
:
594 case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X
:
595 case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y
:
596 case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y
:
597 case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z
:
598 case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
:
601 case LOCAL_GL_TEXTURE_3D
:
602 case LOCAL_GL_TEXTURE_2D_ARRAY
:
606 MOZ_CRASH("GFX: bad target");
610 GLenum
DoTexImage(gl::GLContext
* gl
, TexImageTarget target
, GLint level
,
611 const webgl::DriverUnpackInfo
* dui
, GLsizei width
,
612 GLsizei height
, GLsizei depth
, const void* data
) {
613 const GLint border
= 0;
615 gl::GLContext::LocalErrorScope
errorScope(*gl
);
617 if (IsTarget3D(target
)) {
618 gl
->fTexImage3D(target
.get(), level
, dui
->internalFormat
, width
, height
,
619 depth
, border
, dui
->unpackFormat
, dui
->unpackType
, data
);
621 MOZ_ASSERT(depth
== 1);
622 gl
->fTexImage2D(target
.get(), level
, dui
->internalFormat
, width
, height
,
623 border
, dui
->unpackFormat
, dui
->unpackType
, data
);
626 return errorScope
.GetError();
629 GLenum
DoTexSubImage(gl::GLContext
* gl
, TexImageTarget target
, GLint level
,
630 GLint xOffset
, GLint yOffset
, GLint zOffset
, GLsizei width
,
631 GLsizei height
, GLsizei depth
,
632 const webgl::PackingInfo
& pi
, const void* data
) {
633 gl::GLContext::LocalErrorScope
errorScope(*gl
);
635 if (IsTarget3D(target
)) {
636 gl
->fTexSubImage3D(target
.get(), level
, xOffset
, yOffset
, zOffset
, width
,
637 height
, depth
, pi
.format
, pi
.type
, data
);
639 MOZ_ASSERT(zOffset
== 0);
640 MOZ_ASSERT(depth
== 1);
641 gl
->fTexSubImage2D(target
.get(), level
, xOffset
, yOffset
, width
, height
,
642 pi
.format
, pi
.type
, data
);
645 return errorScope
.GetError();
648 static inline GLenum
DoCompressedTexImage(gl::GLContext
* gl
,
649 TexImageTarget target
, GLint level
,
650 GLenum internalFormat
, GLsizei width
,
651 GLsizei height
, GLsizei depth
,
652 GLsizei dataSize
, const void* data
) {
653 const GLint border
= 0;
655 gl::GLContext::LocalErrorScope
errorScope(*gl
);
657 if (IsTarget3D(target
)) {
658 gl
->fCompressedTexImage3D(target
.get(), level
, internalFormat
, width
,
659 height
, depth
, border
, dataSize
, data
);
661 MOZ_ASSERT(depth
== 1);
662 gl
->fCompressedTexImage2D(target
.get(), level
, internalFormat
, width
,
663 height
, border
, dataSize
, data
);
666 return errorScope
.GetError();
669 GLenum
DoCompressedTexSubImage(gl::GLContext
* gl
, TexImageTarget target
,
670 GLint level
, GLint xOffset
, GLint yOffset
,
671 GLint zOffset
, GLsizei width
, GLsizei height
,
672 GLsizei depth
, GLenum sizedUnpackFormat
,
673 GLsizei dataSize
, const void* data
) {
674 gl::GLContext::LocalErrorScope
errorScope(*gl
);
676 if (IsTarget3D(target
)) {
677 gl
->fCompressedTexSubImage3D(target
.get(), level
, xOffset
, yOffset
, zOffset
,
678 width
, height
, depth
, sizedUnpackFormat
,
681 MOZ_ASSERT(zOffset
== 0);
682 MOZ_ASSERT(depth
== 1);
683 gl
->fCompressedTexSubImage2D(target
.get(), level
, xOffset
, yOffset
, width
,
684 height
, sizedUnpackFormat
, dataSize
, data
);
687 return errorScope
.GetError();
690 static inline GLenum
DoCopyTexSubImage(gl::GLContext
* gl
, TexImageTarget target
,
691 GLint level
, GLint xOffset
,
692 GLint yOffset
, GLint zOffset
, GLint x
,
693 GLint y
, GLsizei width
, GLsizei height
) {
694 gl::GLContext::LocalErrorScope
errorScope(*gl
);
696 if (IsTarget3D(target
)) {
697 gl
->fCopyTexSubImage3D(target
.get(), level
, xOffset
, yOffset
, zOffset
, x
, y
,
700 MOZ_ASSERT(zOffset
== 0);
701 gl
->fCopyTexSubImage2D(target
.get(), level
, xOffset
, yOffset
, x
, y
, width
,
705 return errorScope
.GetError();
708 //////////////////////////////////////////////////////////////////////////////////////////
709 //////////////////////////////////////////////////////////////////////////////////////////
710 // Actual (mostly generic) function implementations
712 static bool ValidateCompressedTexImageRestrictions(
713 const WebGLContext
* webgl
, TexImageTarget target
, uint32_t level
,
714 const webgl::FormatInfo
* format
, const uvec3
& size
) {
715 const auto fnIsDimValid_S3TC
= [&](const char* const name
, uint32_t levelSize
,
716 uint32_t blockSize
) {
717 const auto impliedBaseSize
= levelSize
<< level
;
718 if (impliedBaseSize
% blockSize
== 0) return true;
719 webgl
->ErrorInvalidOperation(
720 "%u is never a valid %s for level %u, because it implies a base mip %s "
722 " %s requires that base mip levels have a %s multiple of %u.",
723 levelSize
, name
, level
, name
, impliedBaseSize
, format
->name
, name
,
728 switch (format
->compression
->family
) {
729 case webgl::CompressionFamily::ASTC
:
730 if (target
== LOCAL_GL_TEXTURE_3D
&&
731 !webgl
->gl
->IsExtensionSupported(
732 gl::GLContext::KHR_texture_compression_astc_hdr
)) {
733 webgl
->ErrorInvalidOperation("TEXTURE_3D requires ASTC's hdr profile.");
738 case webgl::CompressionFamily::PVRTC
:
739 if (!IsPowerOfTwo(size
.x
) || !IsPowerOfTwo(size
.y
)) {
740 webgl
->ErrorInvalidValue("%s requires power-of-two width and height.",
746 case webgl::CompressionFamily::BPTC
:
747 case webgl::CompressionFamily::RGTC
:
748 case webgl::CompressionFamily::S3TC
:
749 if (!fnIsDimValid_S3TC("width", size
.x
,
750 format
->compression
->blockWidth
) ||
751 !fnIsDimValid_S3TC("height", size
.y
,
752 format
->compression
->blockHeight
)) {
757 // Default: There are no restrictions on CompressedTexImage.
758 case webgl::CompressionFamily::ES3
:
759 case webgl::CompressionFamily::ETC1
:
766 static bool ValidateFormatAndSize(const WebGLContext
* webgl
,
767 TexImageTarget target
,
768 const webgl::FormatInfo
* format
,
770 // Check if texture size will likely be rejected by the driver and give a more
771 // meaningful error message.
772 auto baseImageSize
= CheckedInt
<uint64_t>(format
->estimatedBytesPerPixel
) *
773 (uint32_t)size
.x
* (uint32_t)size
.y
* (uint32_t)size
.z
;
774 if (target
== LOCAL_GL_TEXTURE_CUBE_MAP
) {
777 if (!baseImageSize
.isValid() ||
778 baseImageSize
.value() >
779 (uint64_t)StaticPrefs::webgl_max_size_per_texture_mib() *
781 webgl
->ErrorOutOfMemory(
782 "Texture size too large; base image mebibytes > "
783 "webgl.max-size-per-texture-mib");
788 // "Textures with a base internal format of DEPTH_COMPONENT or DEPTH_STENCIL
789 // are supported by texture image specification commands only if `target` is
790 // TEXTURE_2D, TEXTURE_2D_ARRAY, or TEXTURE_CUBE_MAP. Using these formats in
791 // conjunction with any other `target` will result in an INVALID_OPERATION
793 const bool ok
= [&]() {
794 if (bool(format
->d
) & (target
== LOCAL_GL_TEXTURE_3D
)) return false;
796 if (format
->compression
) {
797 switch (format
->compression
->family
) {
798 case webgl::CompressionFamily::ES3
:
799 case webgl::CompressionFamily::S3TC
:
800 if (target
== LOCAL_GL_TEXTURE_3D
) return false;
803 case webgl::CompressionFamily::ETC1
:
804 case webgl::CompressionFamily::PVRTC
:
805 case webgl::CompressionFamily::RGTC
:
806 if (target
== LOCAL_GL_TEXTURE_3D
||
807 target
== LOCAL_GL_TEXTURE_2D_ARRAY
) {
818 webgl
->ErrorInvalidOperation("Format %s cannot be used with target %s.",
819 format
->name
, GetEnumName(target
.get()));
826 void WebGLTexture::TexStorage(TexTarget target
, uint32_t levels
,
827 GLenum sizedFormat
, const uvec3
& size
) {
830 mContext
->ErrorInvalidValue("`levels` must be >= 1.");
834 if (!size
.x
|| !size
.y
|| !size
.z
) {
835 mContext
->ErrorInvalidValue("Dimensions must be non-zero.");
839 const TexImageTarget testTarget
=
840 IsCubeMap() ? LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X
: target
.get();
841 webgl::ImageInfo
* baseImageInfo
;
842 if (!ValidateTexImageSpecification(testTarget
, 0, size
, &baseImageInfo
)) {
845 MOZ_ALWAYS_TRUE(baseImageInfo
);
847 auto dstUsage
= mContext
->mFormatUsage
->GetSizedTexUsage(sizedFormat
);
849 mContext
->ErrorInvalidEnumInfo("internalformat", sizedFormat
);
852 auto dstFormat
= dstUsage
->format
;
854 if (!ValidateFormatAndSize(mContext
, testTarget
, dstFormat
, size
)) return;
856 if (dstFormat
->compression
) {
857 if (!ValidateCompressedTexImageRestrictions(mContext
, testTarget
, 0,
863 ////////////////////////////////////
865 const bool levelsOk
= [&]() {
866 // Right-shift is only defined for bits-1, which is too large anyways.
867 const auto lastLevel
= uint32_t(levels
- 1);
868 if (lastLevel
> 31) return false;
870 const auto lastLevelWidth
= uint32_t(size
.x
) >> lastLevel
;
871 const auto lastLevelHeight
= uint32_t(size
.y
) >> lastLevel
;
873 // If these are all zero, then some earlier level was the final 1x1(x1)
875 bool ok
= lastLevelWidth
|| lastLevelHeight
;
876 if (target
== LOCAL_GL_TEXTURE_3D
) {
877 const auto lastLevelDepth
= uint32_t(size
.z
) >> lastLevel
;
878 ok
|= bool(lastLevelDepth
);
883 mContext
->ErrorInvalidOperation(
884 "Too many levels requested for the given"
885 " dimensions. (levels: %u, width: %u, height: %u,"
887 levels
, size
.x
, size
.y
, size
.z
);
891 ////////////////////////////////////
894 GLenum error
= DoTexStorage(mContext
->gl
, target
.get(), levels
, sizedFormat
,
895 size
.x
, size
.y
, size
.z
);
897 mContext
->OnDataAllocCall();
899 if (error
== LOCAL_GL_OUT_OF_MEMORY
) {
900 mContext
->ErrorOutOfMemory("Ran out of memory during texture allocation.");
905 mContext
->GenerateError(error
, "Unexpected error from driver.");
906 const nsPrintfCString
call(
907 "DoTexStorage(0x%04x, %i, 0x%04x, %i,%i,%i) -> 0x%04x", target
.get(),
908 levels
, sizedFormat
, size
.x
, size
.y
, size
.z
, error
);
909 gfxCriticalError() << "Unexpected error from driver: "
910 << call
.BeginReading();
914 ////////////////////////////////////
915 // Update our specification data.
917 std::vector
<bool> uninitializedSlices(size
.z
, true);
918 const webgl::ImageInfo newInfo
{dstUsage
, size
.x
, size
.y
, size
.z
,
919 std::move(uninitializedSlices
)};
922 const auto base_level
= mBaseMipmapLevel
;
923 mBaseMipmapLevel
= 0;
925 ImageInfoAtFace(0, 0) = newInfo
;
926 PopulateMipChain(levels
- 1);
928 mBaseMipmapLevel
= base_level
;
932 mImmutableLevelCount
= AutoAssertCast(levels
);
933 ClampLevelBaseAndMax();
936 ////////////////////////////////////////
939 // TexSubImage iff `!respectFormat`
940 void WebGLTexture::TexImage(uint32_t level
, GLenum respecFormat
,
941 const uvec3
& offset
, const webgl::PackingInfo
& pi
,
942 const webgl::TexUnpackBlobDesc
& src
) {
943 const auto blob
= webgl::TexUnpackBlob::Create(src
);
949 const auto imageTarget
= blob
->mDesc
.imageTarget
;
950 auto size
= blob
->mDesc
.size
;
952 if (!IsTarget3D(imageTarget
)) {
956 ////////////////////////////////////
959 const auto& fua
= mContext
->mFormatUsage
;
960 const auto fnValidateUnpackEnums
= [&]() {
961 if (!fua
->AreUnpackEnumsValid(pi
.format
, pi
.type
)) {
962 mContext
->ErrorInvalidEnum("Invalid unpack format/type: %s/%s",
963 EnumString(pi
.format
).c_str(),
964 EnumString(pi
.type
).c_str());
970 webgl::ImageInfo
* imageInfo
;
971 const webgl::FormatUsageInfo
* dstUsage
;
973 if (!ValidateTexImageSpecification(imageTarget
, level
, size
, &imageInfo
))
975 MOZ_ASSERT(imageInfo
);
977 if (!fua
->IsInternalFormatEnumValid(respecFormat
)) {
978 mContext
->ErrorInvalidValue("Invalid internalformat: 0x%04x",
983 dstUsage
= fua
->GetSizedTexUsage(respecFormat
);
985 if (respecFormat
!= pi
.format
) {
986 /* GL ES Version 3.0.4 - 3.8.3 Texture Image Specification
987 * "Specifying a combination of values for format, type, and
988 * internalformat that is not listed as a valid combination
989 * in tables 3.2 or 3.3 generates the error INVALID_OPERATION."
991 if (!fnValidateUnpackEnums()) return;
992 mContext
->ErrorInvalidOperation(
993 "Unsized internalFormat must match"
998 dstUsage
= fua
->GetUnsizedTexUsage(pi
);
1002 if (!fnValidateUnpackEnums()) return;
1003 mContext
->ErrorInvalidOperation(
1004 "Invalid internalformat/format/type:"
1005 " 0x%04x/0x%04x/0x%04x",
1006 respecFormat
, pi
.format
, pi
.type
);
1010 const auto& dstFormat
= dstUsage
->format
;
1011 if (!ValidateFormatAndSize(mContext
, imageTarget
, dstFormat
, size
)) return;
1013 if (!mContext
->IsWebGL2() && dstFormat
->d
) {
1014 if (imageTarget
!= LOCAL_GL_TEXTURE_2D
|| blob
->HasData() || level
!= 0) {
1015 mContext
->ErrorInvalidOperation(
1016 "With format %s, this function may only"
1017 " be called with target=TEXTURE_2D,"
1018 " data=null, and level=0.",
1024 if (!ValidateTexImageSelection(imageTarget
, level
, offset
, size
,
1028 MOZ_ASSERT(imageInfo
);
1029 dstUsage
= imageInfo
->mFormat
;
1031 const auto& dstFormat
= dstUsage
->format
;
1032 if (!mContext
->IsWebGL2() && dstFormat
->d
) {
1033 mContext
->ErrorInvalidOperation(
1034 "Function may not be called on a texture of"
1041 ////////////////////////////////////
1044 const webgl::DriverUnpackInfo
* driverUnpackInfo
;
1045 if (!dstUsage
->IsUnpackValid(pi
, &driverUnpackInfo
)) {
1046 if (!fnValidateUnpackEnums()) return;
1047 mContext
->ErrorInvalidOperation(
1048 "Mismatched internalFormat and format/type:"
1049 " 0x%04x and 0x%04x/0x%04x",
1050 respecFormat
, pi
.format
, pi
.type
);
1054 if (!blob
->Validate(mContext
, pi
)) return;
1056 ////////////////////////////////////
1059 Maybe
<webgl::ImageInfo
> newImageInfo
;
1060 bool isRespec
= false;
1062 // It's tempting to do allocation first, and TexSubImage second, but this is
1063 // generally slower.
1064 newImageInfo
= Some(webgl::ImageInfo
{dstUsage
, size
.x
, size
.y
, size
.z
});
1065 if (!blob
->HasData()) {
1066 newImageInfo
->mUninitializedSlices
.emplace(size
.z
, true);
1069 isRespec
= (imageInfo
->mWidth
!= newImageInfo
->mWidth
||
1070 imageInfo
->mHeight
!= newImageInfo
->mHeight
||
1071 imageInfo
->mDepth
!= newImageInfo
->mDepth
||
1072 imageInfo
->mFormat
!= newImageInfo
->mFormat
);
1074 if (!blob
->HasData()) {
1075 mContext
->ErrorInvalidValue("`source` cannot be null.");
1078 if (!EnsureImageDataInitializedForUpload(this, imageTarget
, level
, offset
,
1084 webgl::PixelPackingState
{}.AssertCurrentUnpack(*mContext
->gl
,
1085 mContext
->IsWebGL2());
1087 blob
->mDesc
.unpacking
.ApplyUnpack(*mContext
->gl
, mContext
->IsWebGL2(), size
);
1088 const auto revertUnpacking
= MakeScopeExit([&]() {
1089 webgl::PixelPackingState
{}.ApplyUnpack(*mContext
->gl
, mContext
->IsWebGL2(),
1093 const bool isSubImage
= !respecFormat
;
1095 if (!blob
->TexOrSubImage(isSubImage
, isRespec
, this, level
, driverUnpackInfo
,
1096 offset
.x
, offset
.y
, offset
.z
, pi
, &glError
)) {
1100 if (glError
== LOCAL_GL_OUT_OF_MEMORY
) {
1101 mContext
->ErrorOutOfMemory("Driver ran out of memory during upload.");
1107 const auto enumStr
= EnumString(glError
);
1108 const nsPrintfCString
dui(
1109 "Unexpected error %s during upload. (dui: %x/%x/%x)", enumStr
.c_str(),
1110 driverUnpackInfo
->internalFormat
, driverUnpackInfo
->unpackFormat
,
1111 driverUnpackInfo
->unpackType
);
1112 mContext
->ErrorInvalidOperation("%s", dui
.BeginReading());
1113 gfxCriticalError() << mContext
->FuncName() << ": " << dui
.BeginReading();
1117 ////////////////////////////////////
1118 // Update our specification data?
1121 mContext
->OnDataAllocCall();
1122 *imageInfo
= *newImageInfo
;
1127 ////////////////////////////////////////
1128 // CompressedTex(Sub)Image
1130 static inline bool IsSubImageBlockAligned(
1131 const webgl::CompressedFormatInfo
* compression
,
1132 const webgl::ImageInfo
* imageInfo
, GLint xOffset
, GLint yOffset
,
1133 uint32_t width
, uint32_t height
) {
1134 if (xOffset
% compression
->blockWidth
!= 0 ||
1135 yOffset
% compression
->blockHeight
!= 0) {
1139 if (width
% compression
->blockWidth
!= 0 &&
1140 xOffset
+ width
!= imageInfo
->mWidth
)
1143 if (height
% compression
->blockHeight
!= 0 &&
1144 yOffset
+ height
!= imageInfo
->mHeight
)
1150 // CompressedTexSubImage iff `sub`
1151 void WebGLTexture::CompressedTexImage(bool sub
, GLenum imageTarget
,
1152 uint32_t level
, GLenum formatEnum
,
1153 const uvec3
& offset
, const uvec3
& size
,
1154 const Range
<const uint8_t>& src
,
1155 const uint32_t pboImageSize
,
1156 const Maybe
<uint64_t>& pboOffset
) {
1157 auto imageSize
= pboImageSize
;
1159 const auto& buffer
=
1160 mContext
->ValidateBufferSelection(LOCAL_GL_PIXEL_UNPACK_BUFFER
);
1161 if (!buffer
) return;
1162 auto availBytes
= buffer
->ByteLength();
1163 if (*pboOffset
> availBytes
) {
1164 mContext
->GenerateError(
1165 LOCAL_GL_INVALID_OPERATION
,
1166 "`offset` (%llu) must be <= PIXEL_UNPACK_BUFFER size (%llu).",
1167 *pboOffset
, availBytes
);
1170 availBytes
-= *pboOffset
;
1171 if (availBytes
< pboImageSize
) {
1172 mContext
->GenerateError(
1173 LOCAL_GL_INVALID_OPERATION
,
1174 "PIXEL_UNPACK_BUFFER size minus `offset` (%llu) too small for"
1175 " `pboImageSize` (%u).",
1176 availBytes
, pboImageSize
);
1180 if (mContext
->mBoundPixelUnpackBuffer
) {
1181 mContext
->GenerateError(LOCAL_GL_INVALID_OPERATION
,
1182 "PIXEL_UNPACK_BUFFER is non-null.");
1185 imageSize
= src
.length();
1190 const auto usage
= mContext
->mFormatUsage
->GetSizedTexUsage(formatEnum
);
1191 if (!usage
|| !usage
->format
->compression
) {
1192 mContext
->ErrorInvalidEnumArg("format", formatEnum
);
1196 webgl::ImageInfo
* imageInfo
;
1198 if (!ValidateTexImageSpecification(imageTarget
, level
, size
, &imageInfo
)) {
1201 MOZ_ASSERT(imageInfo
);
1203 if (!ValidateFormatAndSize(mContext
, imageTarget
, usage
->format
, size
))
1205 if (!ValidateCompressedTexImageRestrictions(mContext
, imageTarget
, level
,
1206 usage
->format
, size
)) {
1210 if (!ValidateTexImageSelection(imageTarget
, level
, offset
, size
,
1213 MOZ_ASSERT(imageInfo
);
1215 const auto dstUsage
= imageInfo
->mFormat
;
1216 if (usage
!= dstUsage
) {
1217 mContext
->ErrorInvalidOperation(
1218 "`format` must match the format of the"
1219 " existing texture image.");
1223 const auto& format
= usage
->format
;
1224 switch (format
->compression
->family
) {
1226 case webgl::CompressionFamily::ETC1
:
1227 mContext
->ErrorInvalidOperation(
1228 "Format does not allow sub-image"
1233 case webgl::CompressionFamily::ES3
: // Yes, the ES3 formats don't match
1235 case webgl::CompressionFamily::S3TC
: // default behavior.
1236 case webgl::CompressionFamily::BPTC
:
1237 case webgl::CompressionFamily::RGTC
:
1238 if (!IsSubImageBlockAligned(format
->compression
, imageInfo
, offset
.x
,
1239 offset
.y
, size
.x
, size
.y
)) {
1240 mContext
->ErrorInvalidOperation(
1241 "Format requires block-aligned sub-image"
1247 // Full-only: (The ES3 default)
1248 case webgl::CompressionFamily::ASTC
:
1249 case webgl::CompressionFamily::PVRTC
:
1250 if (offset
.x
|| offset
.y
|| size
.x
!= imageInfo
->mWidth
||
1251 size
.y
!= imageInfo
->mHeight
) {
1252 mContext
->ErrorInvalidOperation(
1253 "Format does not allow partial sub-image"
1261 switch (usage
->format
->compression
->family
) {
1262 case webgl::CompressionFamily::BPTC
:
1263 case webgl::CompressionFamily::RGTC
:
1265 if (size
.x
% 4 != 0 || size
.y
% 4 != 0) {
1266 mContext
->ErrorInvalidOperation(
1267 "For level == 0, width and height must be multiples of 4.");
1277 if (!ValidateCompressedTexUnpack(mContext
, size
, usage
->format
, imageSize
))
1280 ////////////////////////////////////
1284 if (!EnsureImageDataInitializedForUpload(this, imageTarget
, level
, offset
,
1290 const ScopedLazyBind
bindPBO(mContext
->gl
, LOCAL_GL_PIXEL_UNPACK_BUFFER
,
1291 mContext
->mBoundPixelUnpackBuffer
);
1295 ptr
= reinterpret_cast<const void*>(*pboOffset
);
1297 ptr
= reinterpret_cast<const void*>(src
.begin().get());
1301 error
= DoCompressedTexImage(mContext
->gl
, imageTarget
, level
, formatEnum
,
1302 size
.x
, size
.y
, size
.z
, imageSize
, ptr
);
1304 error
= DoCompressedTexSubImage(mContext
->gl
, imageTarget
, level
, offset
.x
,
1305 offset
.y
, offset
.z
, size
.x
, size
.y
, size
.z
,
1306 formatEnum
, imageSize
, ptr
);
1308 if (error
== LOCAL_GL_OUT_OF_MEMORY
) {
1309 mContext
->ErrorOutOfMemory("Ran out of memory during upload.");
1314 mContext
->GenerateError(error
, "Unexpected error from driver.");
1317 call
= nsPrintfCString(
1318 "DoCompressedTexImage(0x%04x, %u, 0x%04x, %u,%u,%u, %u, %p)",
1319 imageTarget
, level
, formatEnum
, size
.x
, size
.y
, size
.z
, imageSize
,
1322 call
= nsPrintfCString(
1323 "DoCompressedTexSubImage(0x%04x, %u, %u,%u,%u, %u,%u,%u, 0x%04x, %u, "
1325 imageTarget
, level
, offset
.x
, offset
.y
, offset
.z
, size
.x
, size
.y
,
1326 size
.z
, formatEnum
, imageSize
, ptr
);
1328 gfxCriticalError() << "Unexpected error " << gfx::hexa(error
)
1329 << " from driver: " << call
.BeginReading();
1333 ////////////////////////////////////
1334 // Update our specification data?
1337 constexpr auto uninitializedSlices
= std::nullopt
;
1338 const webgl::ImageInfo newImageInfo
{usage
, size
.x
, size
.y
, size
.z
,
1339 uninitializedSlices
};
1340 *imageInfo
= newImageInfo
;
1345 ////////////////////////////////////////
1346 // CopyTex(Sub)Image
1348 static bool ValidateCopyTexImageFormats(WebGLContext
* webgl
,
1349 const webgl::FormatInfo
* srcFormat
,
1350 const webgl::FormatInfo
* dstFormat
) {
1351 MOZ_ASSERT(!srcFormat
->compression
);
1352 if (dstFormat
->compression
) {
1353 webgl
->ErrorInvalidEnum(
1354 "Specified destination must not have a compressed"
1359 if (dstFormat
->effectiveFormat
== webgl::EffectiveFormat::RGB9_E5
) {
1360 webgl
->ErrorInvalidOperation(
1361 "RGB9_E5 is an invalid destination for"
1362 " CopyTex(Sub)Image. (GLES 3.0.4 p145)");
1366 if (!DoChannelsMatchForCopyTexImage(srcFormat
, dstFormat
)) {
1367 webgl
->ErrorInvalidOperation(
1368 "Destination channels must be compatible with"
1369 " source channels. (GLES 3.0.4 p140 Table 3.16)");
1376 ////////////////////////////////////////////////////////////////////////////////
1378 class ScopedCopyTexImageSource
{
1379 WebGLContext
* const mWebGL
;
1384 ScopedCopyTexImageSource(WebGLContext
* webgl
, uint32_t srcWidth
,
1386 const webgl::FormatInfo
* srcFormat
,
1387 const webgl::FormatUsageInfo
* dstUsage
);
1388 ~ScopedCopyTexImageSource();
1391 ScopedCopyTexImageSource::ScopedCopyTexImageSource(
1392 WebGLContext
* webgl
, uint32_t srcWidth
, uint32_t srcHeight
,
1393 const webgl::FormatInfo
* srcFormat
, const webgl::FormatUsageInfo
* dstUsage
)
1394 : mWebGL(webgl
), mRB(0), mFB(0) {
1395 switch (dstUsage
->format
->unsizedFormat
) {
1396 case webgl::UnsizedFormat::L
:
1397 case webgl::UnsizedFormat::A
:
1398 case webgl::UnsizedFormat::LA
:
1399 webgl
->GenerateWarning(
1400 "Copying to a LUMINANCE, ALPHA, or LUMINANCE_ALPHA"
1401 " is deprecated, and has severely reduced performance"
1402 " on some platforms.");
1406 MOZ_ASSERT(!dstUsage
->textureSwizzleRGBA
);
1410 if (!dstUsage
->textureSwizzleRGBA
) return;
1412 gl::GLContext
* gl
= webgl
->gl
;
1416 switch (srcFormat
->componentType
) {
1417 case webgl::ComponentType::NormUInt
:
1418 sizedFormat
= LOCAL_GL_RGBA8
;
1421 case webgl::ComponentType::Float
:
1422 if (webgl
->IsExtensionEnabled(
1423 WebGLExtensionID::WEBGL_color_buffer_float
)) {
1424 sizedFormat
= LOCAL_GL_RGBA32F
;
1425 webgl
->WarnIfImplicit(WebGLExtensionID::WEBGL_color_buffer_float
);
1429 if (webgl
->IsExtensionEnabled(
1430 WebGLExtensionID::EXT_color_buffer_half_float
)) {
1431 sizedFormat
= LOCAL_GL_RGBA16F
;
1432 webgl
->WarnIfImplicit(WebGLExtensionID::EXT_color_buffer_half_float
);
1435 MOZ_CRASH("GFX: Should be able to request CopyTexImage from Float.");
1438 MOZ_CRASH("GFX: Should be able to request CopyTexImage from this type.");
1441 gl::ScopedTexture
scopedTex(gl
);
1442 gl::ScopedBindTexture
scopedBindTex(gl
, scopedTex
.Texture(),
1443 LOCAL_GL_TEXTURE_2D
);
1445 gl
->fTexParameteri(LOCAL_GL_TEXTURE_2D
, LOCAL_GL_TEXTURE_MIN_FILTER
,
1447 gl
->fTexParameteri(LOCAL_GL_TEXTURE_2D
, LOCAL_GL_TEXTURE_MAG_FILTER
,
1450 GLint blitSwizzle
[4] = {LOCAL_GL_ZERO
};
1451 switch (dstUsage
->format
->unsizedFormat
) {
1452 case webgl::UnsizedFormat::L
:
1453 blitSwizzle
[0] = LOCAL_GL_RED
;
1456 case webgl::UnsizedFormat::A
:
1457 blitSwizzle
[0] = LOCAL_GL_ALPHA
;
1460 case webgl::UnsizedFormat::LA
:
1461 blitSwizzle
[0] = LOCAL_GL_RED
;
1462 blitSwizzle
[1] = LOCAL_GL_ALPHA
;
1466 MOZ_CRASH("GFX: Unhandled unsizedFormat.");
1469 gl
->fTexParameteri(LOCAL_GL_TEXTURE_2D
, LOCAL_GL_TEXTURE_SWIZZLE_R
,
1471 gl
->fTexParameteri(LOCAL_GL_TEXTURE_2D
, LOCAL_GL_TEXTURE_SWIZZLE_G
,
1473 gl
->fTexParameteri(LOCAL_GL_TEXTURE_2D
, LOCAL_GL_TEXTURE_SWIZZLE_B
,
1475 gl
->fTexParameteri(LOCAL_GL_TEXTURE_2D
, LOCAL_GL_TEXTURE_SWIZZLE_A
,
1478 gl
->fCopyTexImage2D(LOCAL_GL_TEXTURE_2D
, 0, sizedFormat
, 0, 0, srcWidth
,
1481 // Now create the swizzled FB we'll be exposing.
1486 gl
->fGenRenderbuffers(1, &rgbaRB
);
1487 gl::ScopedBindRenderbuffer
scopedRB(gl
, rgbaRB
);
1488 gl
->fRenderbufferStorage(LOCAL_GL_RENDERBUFFER
, sizedFormat
, srcWidth
,
1491 gl
->fGenFramebuffers(1, &rgbaFB
);
1492 gl
->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER
, rgbaFB
);
1493 gl
->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER
,
1494 LOCAL_GL_COLOR_ATTACHMENT0
,
1495 LOCAL_GL_RENDERBUFFER
, rgbaRB
);
1497 const GLenum status
= gl
->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER
);
1498 if (status
!= LOCAL_GL_FRAMEBUFFER_COMPLETE
) {
1499 MOZ_CRASH("GFX: Temp framebuffer is not complete.");
1503 // Draw-blit rgbaTex into rgbaFB.
1504 const gfx::IntSize
srcSize(srcWidth
, srcHeight
);
1506 const gl::ScopedBindFramebuffer
bindFB(gl
, rgbaFB
);
1507 gl
->BlitHelper()->DrawBlitTextureToFramebuffer(scopedTex
.Texture(), srcSize
,
1511 // Leave RB and FB alive, and FB bound.
1516 template <typename T
>
1517 static inline GLenum
ToGLHandle(const T
& obj
) {
1518 return (obj
? obj
->mGLName
: 0);
1521 ScopedCopyTexImageSource::~ScopedCopyTexImageSource() {
1528 gl::GLContext
* gl
= mWebGL
->gl
;
1530 // If we're swizzling, it's because we're on a GL core (3.2+) profile, which
1531 // has split framebuffer support.
1532 gl
->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER
,
1533 ToGLHandle(mWebGL
->mBoundDrawFramebuffer
));
1534 gl
->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER
,
1535 ToGLHandle(mWebGL
->mBoundReadFramebuffer
));
1537 gl
->fDeleteFramebuffers(1, &mFB
);
1538 gl
->fDeleteRenderbuffers(1, &mRB
);
1541 ////////////////////////////////////////////////////////////////////////////////
1543 static bool GetUnsizedFormatForCopy(GLenum internalFormat
,
1544 webgl::UnsizedFormat
* const out
) {
1545 switch (internalFormat
) {
1547 *out
= webgl::UnsizedFormat::R
;
1550 *out
= webgl::UnsizedFormat::RG
;
1553 *out
= webgl::UnsizedFormat::RGB
;
1556 *out
= webgl::UnsizedFormat::RGBA
;
1558 case LOCAL_GL_LUMINANCE
:
1559 *out
= webgl::UnsizedFormat::L
;
1561 case LOCAL_GL_ALPHA
:
1562 *out
= webgl::UnsizedFormat::A
;
1564 case LOCAL_GL_LUMINANCE_ALPHA
:
1565 *out
= webgl::UnsizedFormat::LA
;
1575 static const webgl::FormatUsageInfo
* ValidateCopyDestUsage(
1576 WebGLContext
* webgl
, const webgl::FormatInfo
* srcFormat
,
1577 GLenum internalFormat
) {
1578 const auto& fua
= webgl
->mFormatUsage
;
1580 switch (internalFormat
) {
1581 case LOCAL_GL_R8_SNORM
:
1582 case LOCAL_GL_RG8_SNORM
:
1583 case LOCAL_GL_RGB8_SNORM
:
1584 case LOCAL_GL_RGBA8_SNORM
:
1585 webgl
->ErrorInvalidEnum("SNORM formats are invalid for CopyTexImage.");
1589 auto dstUsage
= fua
->GetSizedTexUsage(internalFormat
);
1591 // Ok, maybe it's unsized.
1592 webgl::UnsizedFormat unsizedFormat
;
1593 if (!GetUnsizedFormatForCopy(internalFormat
, &unsizedFormat
)) {
1594 webgl
->ErrorInvalidEnumInfo("internalFormat", internalFormat
);
1598 const auto dstFormat
= srcFormat
->GetCopyDecayFormat(unsizedFormat
);
1600 dstUsage
= fua
->GetUsage(dstFormat
->effectiveFormat
);
1603 webgl
->ErrorInvalidOperation(
1604 "0x%04x is not a valid unsized format for"
1605 " source format %s.",
1606 internalFormat
, srcFormat
->name
);
1612 // Alright, it's sized.
1614 const auto dstFormat
= dstUsage
->format
;
1616 if (dstFormat
->componentType
!= srcFormat
->componentType
) {
1617 webgl
->ErrorInvalidOperation(
1618 "For sized internalFormats, source and dest"
1619 " component types must match. (source: %s, dest:"
1621 srcFormat
->name
, dstFormat
->name
);
1625 bool componentSizesMatch
= true;
1627 componentSizesMatch
&= (dstFormat
->r
== srcFormat
->r
);
1630 componentSizesMatch
&= (dstFormat
->g
== srcFormat
->g
);
1633 componentSizesMatch
&= (dstFormat
->b
== srcFormat
->b
);
1636 componentSizesMatch
&= (dstFormat
->a
== srcFormat
->a
);
1639 if (!componentSizesMatch
) {
1640 webgl
->ErrorInvalidOperation(
1641 "For sized internalFormats, source and dest"
1642 " component sizes must match exactly. (source: %s,"
1644 srcFormat
->name
, dstFormat
->name
);
1651 static bool ValidateCopyTexImageForFeedback(const WebGLContext
& webgl
,
1652 const WebGLTexture
& tex
,
1653 const uint32_t mipLevel
,
1654 const uint32_t zLayer
) {
1655 const auto& fb
= webgl
.BoundReadFb();
1657 MOZ_ASSERT(fb
->ColorReadBuffer());
1658 const auto& attach
= *fb
->ColorReadBuffer();
1659 MOZ_ASSERT(attach
.ZLayerCount() ==
1660 1); // Multiview invalid for copyTexImage.
1662 if (attach
.Texture() == &tex
&& attach
.Layer() == zLayer
&&
1663 attach
.MipLevel() == mipLevel
) {
1664 // Note that the TexImageTargets *don't* have to match for this to be
1665 // undefined per GLES 3.0.4 p211, thus an INVALID_OP in WebGL.
1666 webgl
.ErrorInvalidOperation(
1667 "Feedback loop detected, as this texture"
1668 " is already attached to READ_FRAMEBUFFER's"
1669 " READ_BUFFER-selected COLOR_ATTACHMENT%u.",
1670 attach
.mAttachmentPoint
);
1677 static bool DoCopyTexOrSubImage(WebGLContext
* webgl
, bool isSubImage
,
1678 bool needsInit
, WebGLTexture
* const tex
,
1679 const TexImageTarget target
, GLint level
,
1680 GLint xWithinSrc
, GLint yWithinSrc
,
1681 uint32_t srcTotalWidth
, uint32_t srcTotalHeight
,
1682 const webgl::FormatUsageInfo
* srcUsage
,
1683 GLint xOffset
, GLint yOffset
, GLint zOffset
,
1684 uint32_t dstWidth
, uint32_t dstHeight
,
1685 const webgl::FormatUsageInfo
* dstUsage
) {
1686 const auto& gl
= webgl
->gl
;
1690 int32_t readX
, readY
;
1691 int32_t writeX
, writeY
;
1692 int32_t rwWidth
, rwHeight
;
1693 if (!Intersect(srcTotalWidth
, xWithinSrc
, dstWidth
, &readX
, &writeX
,
1695 !Intersect(srcTotalHeight
, yWithinSrc
, dstHeight
, &readY
, &writeY
,
1697 webgl
->ErrorOutOfMemory("Bad subrect selection.");
1707 nsCString errorText
;
1709 const auto& idealUnpack
= dstUsage
->idealUnpack
;
1710 const auto& pi
= idealUnpack
->ToPacking();
1713 const bool fullOverwrite
=
1714 (uint32_t(rwWidth
) == dstWidth
&& uint32_t(rwHeight
) == dstHeight
);
1715 if (needsInit
&& !fullOverwrite
) {
1716 CheckedInt
<size_t> byteCount
= BytesPerPixel(pi
);
1717 byteCount
*= dstWidth
;
1718 byteCount
*= dstHeight
;
1720 if (byteCount
.isValid()) {
1721 zeros
= UniqueBuffer::Take(calloc(1u, byteCount
.value()));
1725 webgl
->ErrorOutOfMemory("Ran out of memory allocating zeros.");
1730 if (!isSubImage
|| zeros
) {
1731 webgl::PixelPackingState
{}.AssertCurrentUnpack(*gl
, webgl
->IsWebGL2());
1733 gl
->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT
, 1);
1734 const auto revert
= MakeScopeExit(
1735 [&]() { gl
->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT
, 4); });
1737 error
= DoTexImage(gl
, target
, level
, idealUnpack
, dstWidth
, dstHeight
,
1740 errorText
= nsPrintfCString(
1741 "DoTexImage(0x%04x, %i, {0x%04x, 0x%04x, 0x%04x}, %u,%u,1) -> "
1743 target
.get(), level
, idealUnpack
->internalFormat
,
1744 idealUnpack
->unpackFormat
, idealUnpack
->unpackType
, dstWidth
,
1750 error
= DoTexSubImage(gl
, target
, level
, xOffset
, yOffset
, zOffset
,
1751 dstWidth
, dstHeight
, 1, pi
, zeros
.get());
1753 errorText
= nsPrintfCString(
1754 "DoTexSubImage(0x%04x, %i, %i,%i,%i, %u,%u,1, {0x%04x, 0x%04x}) "
1757 target
.get(), level
, xOffset
, yOffset
, zOffset
, dstWidth
,
1758 dstHeight
, idealUnpack
->unpackFormat
, idealUnpack
->unpackType
,
1765 if (!rwWidth
|| !rwHeight
) {
1766 // There aren't any pixels to copy, so we're 'done'.
1770 const auto& srcFormat
= srcUsage
->format
;
1771 ScopedCopyTexImageSource
maybeSwizzle(webgl
, srcTotalWidth
, srcTotalHeight
,
1772 srcFormat
, dstUsage
);
1774 error
= DoCopyTexSubImage(gl
, target
, level
, writeX
, writeY
, zOffset
, readX
,
1775 readY
, rwWidth
, rwHeight
);
1777 errorText
= nsPrintfCString(
1778 "DoCopyTexSubImage(0x%04x, %i, %i,%i,%i, %i,%i, %u,%u) -> 0x%04x",
1779 target
.get(), level
, writeX
, writeY
, zOffset
, readX
, readY
, rwWidth
,
1787 if (error
== LOCAL_GL_OUT_OF_MEMORY
) {
1788 webgl
->ErrorOutOfMemory("Ran out of memory during texture copy.");
1793 if (gl
->IsANGLE() && error
== LOCAL_GL_INVALID_OPERATION
) {
1794 webgl
->ErrorImplementationBug(
1795 "ANGLE is particular about CopyTexSubImage"
1796 " formats matching exactly.");
1800 webgl
->GenerateError(error
, "Unexpected error from driver.");
1801 gfxCriticalError() << "Unexpected error from driver: "
1802 << errorText
.BeginReading();
1806 // CopyTexSubImage if `!respecFormat`
1807 void WebGLTexture::CopyTexImage(GLenum imageTarget
, uint32_t level
,
1808 GLenum respecFormat
, const uvec3
& dstOffset
,
1809 const ivec2
& srcOffset
, const uvec2
& size2
) {
1810 ////////////////////////////////////
1813 const webgl::FormatUsageInfo
* srcUsage
;
1814 uint32_t srcTotalWidth
;
1815 uint32_t srcTotalHeight
;
1816 if (!mContext
->BindCurFBForColorRead(&srcUsage
, &srcTotalWidth
,
1820 const auto& srcFormat
= srcUsage
->format
;
1822 if (!ValidateCopyTexImageForFeedback(*mContext
, *this, level
, dstOffset
.z
))
1825 const auto size
= uvec3
{size2
.x
, size2
.y
, 1};
1827 ////////////////////////////////////
1830 webgl::ImageInfo
* imageInfo
;
1831 const webgl::FormatUsageInfo
* dstUsage
;
1833 if (!ValidateTexImageSpecification(imageTarget
, level
, size
, &imageInfo
))
1835 MOZ_ASSERT(imageInfo
);
1837 dstUsage
= ValidateCopyDestUsage(mContext
, srcFormat
, respecFormat
);
1838 if (!dstUsage
) return;
1840 if (!ValidateFormatAndSize(mContext
, imageTarget
, dstUsage
->format
, size
))
1843 if (!ValidateTexImageSelection(imageTarget
, level
, dstOffset
, size
,
1847 MOZ_ASSERT(imageInfo
);
1849 dstUsage
= imageInfo
->mFormat
;
1850 MOZ_ASSERT(dstUsage
);
1853 const auto& dstFormat
= dstUsage
->format
;
1854 if (!mContext
->IsWebGL2() && dstFormat
->d
) {
1855 mContext
->ErrorInvalidOperation(
1856 "Function may not be called with format %s.", dstFormat
->name
);
1860 ////////////////////////////////////
1861 // Check that source and dest info are compatible
1863 if (!ValidateCopyTexImageFormats(mContext
, srcFormat
, dstFormat
)) return;
1865 ////////////////////////////////////
1868 const bool isSubImage
= !respecFormat
;
1869 bool expectsInit
= true;
1871 if (!EnsureImageDataInitializedForUpload(this, imageTarget
, level
,
1872 dstOffset
, size
, imageInfo
,
1878 if (!DoCopyTexOrSubImage(mContext
, isSubImage
, expectsInit
, this, imageTarget
,
1879 level
, srcOffset
.x
, srcOffset
.y
, srcTotalWidth
,
1880 srcTotalHeight
, srcUsage
, dstOffset
.x
, dstOffset
.y
,
1881 dstOffset
.z
, size
.x
, size
.y
, dstUsage
)) {
1886 mContext
->OnDataAllocCall();
1888 ////////////////////////////////////
1889 // Update our specification data?
1892 constexpr auto uninitializedSlices
= std::nullopt
;
1893 const webgl::ImageInfo newImageInfo
{dstUsage
, size
.x
, size
.y
, size
.z
,
1894 uninitializedSlices
};
1895 *imageInfo
= newImageInfo
;
1900 } // namespace mozilla