Bug 1936278 - Prevent search mode chiclet from being dismissed when clicking in page...
[gecko.git] / dom / canvas / WebGLTextureUpload.cpp
blob0ecdb56518d1b5f493de69eeb8f21fbf66e8d971
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/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"
39 namespace mozilla {
40 namespace webgl {
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
44 // same as drawImage.
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);
56 return {};
59 const auto cloneData = imageBitmap.ToCloneData();
60 if (!cloneData) {
61 return {};
64 const RefPtr<gfx::DataSourceSurface> surf = cloneData->mSurface;
65 if (NS_WARN_IF(!surf)) {
66 return {};
69 const auto imageSize = *uvec2::FromSize(surf->GetSize());
70 if (!size) {
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,
82 size.value(),
83 cloneData->mAlphaType,
84 {},
85 {},
86 Some(imageSize),
87 nullptr,
88 sd,
89 surf,
90 {},
91 false});
94 static layers::SurfaceDescriptor Flatten(const layers::SurfaceDescriptor& sd) {
95 const auto sdType = sd.type();
96 if (sdType != layers::SurfaceDescriptor::TSurfaceDescriptorGPUVideo) {
97 return sd;
99 const auto& sdv = sd.get_SurfaceDescriptorGPUVideo();
100 const auto& sdvType = sdv.type();
101 if (sdvType !=
102 layers::SurfaceDescriptorGPUVideo::TSurfaceDescriptorRemoteDecoder) {
103 return sd;
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:
112 return sd;
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.");
138 return {};
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,
157 const GLenum target,
158 Maybe<uvec3> size,
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);
166 return {};
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) {
185 uvec2 elemSize;
187 const auto& layersImage = sfer.mLayersImage;
188 Maybe<layers::SurfaceDescriptor> sd;
189 if (layersImage) {
190 elemSize = *uvec2::FromSize(layersImage->GetSize());
192 sd = layersImage->GetDesc();
193 if (sd) {
194 sd = Some(Flatten(*sd));
196 if (!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();
210 if (!sd) {
211 // For SourceSurfaceSharedData, try to get SurfaceDescriptorExternalImage.
212 layers::SharedSurfacesChild::Share(dataSurf, sd);
215 //////
217 if (!size) {
218 size.emplace(elemSize.x, elemSize.y, 1);
221 ////
223 if (!sd && !dataSurf) {
224 webgl.EnqueueWarning("Resource has no data (yet?). Uploading zeros.");
225 if (!size) {
226 size.emplace(0, 0, 1);
228 return Some(
229 TexUnpackBlobDesc{target, size.value(), gfxAlphaType::NonPremult});
232 //////
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);
244 return {};
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);
254 return {};
257 //////
258 // Ok, we're good!
260 return Some(TexUnpackBlobDesc{target,
261 size.value(),
262 sfer.mAlphaType,
265 Some(elemSize),
266 layersImage,
268 dataSurf});
271 } // namespace webgl
273 //////////////////////////////////////////////////////////////////////////////////////////
274 //////////////////////////////////////////////////////////////////////////////////////////
276 static bool ValidateTexImage(WebGLContext* webgl, WebGLTexture* texture,
277 TexImageTarget target, uint32_t level,
278 webgl::ImageInfo** const out_imageInfo) {
279 // Check level
280 if (level >= WebGLTexture::kMaxLevelCount) {
281 webgl->ErrorInvalidValue("`level` is too large.");
282 return false;
285 auto& imageInfo = texture->ImageInfoAt(target, level);
286 *out_imageInfo = &imageInfo;
287 return true;
290 // For *TexImage*
291 bool WebGLTexture::ValidateTexImageSpecification(
292 TexImageTarget target, uint32_t level, const uvec3& size,
293 webgl::ImageInfo** const out_imageInfo) {
294 if (mImmutable) {
295 mContext->ErrorInvalidOperation("Specified texture is immutable.");
296 return false;
299 // Do this early to validate `level`.
300 webgl::ImageInfo* imageInfo;
301 if (!ValidateTexImage(mContext, this, target, level, &imageInfo))
302 return false;
304 if (mTarget == LOCAL_GL_TEXTURE_CUBE_MAP && size.x != size.y) {
305 mContext->ErrorInvalidValue("Cube map images must be square.");
306 return false;
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;
333 maxDepth = 1;
334 maxLevel = CeilingLog2(limits.maxTex2dSize);
335 break;
337 case LOCAL_GL_TEXTURE_3D:
338 maxWidthHeight = limits.maxTex3dSize >> level;
339 maxDepth = maxWidthHeight;
340 maxLevel = CeilingLog2(limits.maxTex3dSize);
341 break;
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);
349 break;
351 default: // cube maps
352 MOZ_ASSERT(IsCubeMap());
353 maxWidthHeight = limits.maxTexCubeSize >> level;
354 maxDepth = 1;
355 maxLevel = CeilingLog2(limits.maxTexCubeSize);
356 break;
359 if (level > maxLevel) {
360 mContext->ErrorInvalidValue("Requested level is not supported for target.");
361 return false;
364 if (size.x > maxWidthHeight || size.y > maxWidthHeight || size.z > maxDepth) {
365 mContext->ErrorInvalidValue("Requested size at this level is unsupported.");
366 return false;
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
373 * generated."
375 * This restriction does not apply to GL ES Version 3.0+.
377 bool requirePOT = (!mContext->IsWebGL2() && level != 0);
379 if (requirePOT) {
380 if (!IsPowerOfTwo(size.x) || !IsPowerOfTwo(size.y)) {
381 mContext->ErrorInvalidValue(
382 "For level > 0, width and height must be"
383 " powers of two.");
384 return false;
389 *out_imageInfo = imageInfo;
390 return true;
393 // For *TexSubImage*
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))
399 return false;
401 if (!imageInfo->IsDefined()) {
402 mContext->ErrorInvalidOperation(
403 "The specified TexImage has not yet been"
404 " specified.");
405 return false;
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.");
418 return false;
421 *out_imageInfo = imageInfo;
422 return true;
425 static bool ValidateCompressedTexUnpack(WebGLContext* webgl, const uvec3& size,
426 const webgl::FormatInfo* format,
427 size_t dataSize) {
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.");
445 return false;
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);
453 return false;
456 return true;
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:
474 return true;
475 default:
476 return false;
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:
485 return true;
486 default:
487 return false;
490 case webgl::UnsizedFormat::RG:
491 switch (dstFormat->unsizedFormat) {
492 case webgl::UnsizedFormat::L:
493 case webgl::UnsizedFormat::R:
494 case webgl::UnsizedFormat::RG:
495 return true;
496 default:
497 return false;
500 case webgl::UnsizedFormat::R:
501 switch (dstFormat->unsizedFormat) {
502 case webgl::UnsizedFormat::L:
503 case webgl::UnsizedFormat::R:
504 return true;
505 default:
506 return false;
509 default:
510 return false;
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;
528 expectsInit = true;
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();
544 return true;
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.");
554 return false;
557 return true;
560 //////////////////////////////////////////////////////////////////////////////////////////
561 //////////////////////////////////////////////////////////////////////////////////////////
562 // Actual calls
564 static inline GLenum DoTexStorage(gl::GLContext* gl, TexTarget target,
565 GLsizei levels, GLenum sizedFormat,
566 GLsizei width, GLsizei height,
567 GLsizei depth) {
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);
575 break;
577 case LOCAL_GL_TEXTURE_3D:
578 case LOCAL_GL_TEXTURE_2D_ARRAY:
579 gl->fTexStorage3D(target.get(), levels, sizedFormat, width, height,
580 depth);
581 break;
583 default:
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:
599 return false;
601 case LOCAL_GL_TEXTURE_3D:
602 case LOCAL_GL_TEXTURE_2D_ARRAY:
603 return true;
605 default:
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);
620 } else {
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);
638 } else {
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);
660 } else {
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,
679 dataSize, data);
680 } else {
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,
698 width, height);
699 } else {
700 MOZ_ASSERT(zOffset == 0);
701 gl->fCopyTexSubImage2D(target.get(), level, xOffset, yOffset, x, y, width,
702 height);
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 "
721 "of %u."
722 " %s requires that base mip levels have a %s multiple of %u.",
723 levelSize, name, level, name, impliedBaseSize, format->name, name,
724 blockSize);
725 return false;
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.");
734 return false;
736 break;
738 case webgl::CompressionFamily::PVRTC:
739 if (!IsPowerOfTwo(size.x) || !IsPowerOfTwo(size.y)) {
740 webgl->ErrorInvalidValue("%s requires power-of-two width and height.",
741 format->name);
742 return false;
744 break;
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)) {
753 return false;
755 break;
757 // Default: There are no restrictions on CompressedTexImage.
758 case webgl::CompressionFamily::ES3:
759 case webgl::CompressionFamily::ETC1:
760 break;
763 return true;
766 static bool ValidateFormatAndSize(const WebGLContext* webgl,
767 TexImageTarget target,
768 const webgl::FormatInfo* format,
769 const uvec3& size) {
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) {
775 baseImageSize *= 6;
777 if (!baseImageSize.isValid() ||
778 baseImageSize.value() >
779 (uint64_t)StaticPrefs::webgl_max_size_per_texture_mib() *
780 (1024 * 1024)) {
781 webgl->ErrorOutOfMemory(
782 "Texture size too large; base image mebibytes > "
783 "webgl.max-size-per-texture-mib");
784 return false;
787 // GLES 3.0.4 p127:
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
792 // error."
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;
801 break;
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) {
808 return false;
810 break;
811 default:
812 break;
815 return true;
816 }();
817 if (!ok) {
818 webgl->ErrorInvalidOperation("Format %s cannot be used with target %s.",
819 format->name, GetEnumName(target.get()));
820 return false;
823 return true;
826 void WebGLTexture::TexStorage(TexTarget target, uint32_t levels,
827 GLenum sizedFormat, const uvec3& size) {
828 // Check levels
829 if (levels < 1) {
830 mContext->ErrorInvalidValue("`levels` must be >= 1.");
831 return;
834 if (!size.x || !size.y || !size.z) {
835 mContext->ErrorInvalidValue("Dimensions must be non-zero.");
836 return;
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)) {
843 return;
845 MOZ_ALWAYS_TRUE(baseImageInfo);
847 auto dstUsage = mContext->mFormatUsage->GetSizedTexUsage(sizedFormat);
848 if (!dstUsage) {
849 mContext->ErrorInvalidEnumInfo("internalformat", sizedFormat);
850 return;
852 auto dstFormat = dstUsage->format;
854 if (!ValidateFormatAndSize(mContext, testTarget, dstFormat, size)) return;
856 if (dstFormat->compression) {
857 if (!ValidateCompressedTexImageRestrictions(mContext, testTarget, 0,
858 dstFormat, size)) {
859 return;
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)
874 // level.
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);
880 return ok;
881 }();
882 if (!levelsOk) {
883 mContext->ErrorInvalidOperation(
884 "Too many levels requested for the given"
885 " dimensions. (levels: %u, width: %u, height: %u,"
886 " depth: %u)",
887 levels, size.x, size.y, size.z);
888 return;
891 ////////////////////////////////////
892 // Do the thing!
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.");
901 Truncate();
902 return;
904 if (error) {
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();
911 return;
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;
931 mImmutable = true;
932 mImmutableLevelCount = AutoAssertCast(levels);
933 ClampLevelBaseAndMax();
936 ////////////////////////////////////////
937 // Tex(Sub)Image
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);
944 if (!blob) {
945 MOZ_ASSERT(false);
946 return;
949 const auto imageTarget = blob->mDesc.imageTarget;
950 auto size = blob->mDesc.size;
952 if (!IsTarget3D(imageTarget)) {
953 size.z = 1;
956 ////////////////////////////////////
957 // Get dest info
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());
965 return false;
967 return true;
970 webgl::ImageInfo* imageInfo;
971 const webgl::FormatUsageInfo* dstUsage;
972 if (respecFormat) {
973 if (!ValidateTexImageSpecification(imageTarget, level, size, &imageInfo))
974 return;
975 MOZ_ASSERT(imageInfo);
977 if (!fua->IsInternalFormatEnumValid(respecFormat)) {
978 mContext->ErrorInvalidValue("Invalid internalformat: 0x%04x",
979 respecFormat);
980 return;
983 dstUsage = fua->GetSizedTexUsage(respecFormat);
984 if (!dstUsage) {
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"
994 " unpack format.");
995 return;
998 dstUsage = fua->GetUnsizedTexUsage(pi);
1001 if (!dstUsage) {
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);
1007 return;
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.",
1019 dstFormat->name);
1020 return;
1023 } else {
1024 if (!ValidateTexImageSelection(imageTarget, level, offset, size,
1025 &imageInfo)) {
1026 return;
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"
1035 " format %s.",
1036 dstFormat->name);
1037 return;
1041 ////////////////////////////////////
1042 // Get source info
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);
1051 return;
1054 if (!blob->Validate(mContext, pi)) return;
1056 ////////////////////////////////////
1057 // Do the thing!
1059 Maybe<webgl::ImageInfo> newImageInfo;
1060 bool isRespec = false;
1061 if (respecFormat) {
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);
1073 } else {
1074 if (!blob->HasData()) {
1075 mContext->ErrorInvalidValue("`source` cannot be null.");
1076 return;
1078 if (!EnsureImageDataInitializedForUpload(this, imageTarget, level, offset,
1079 size, imageInfo)) {
1080 return;
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(),
1090 size);
1093 const bool isSubImage = !respecFormat;
1094 GLenum glError = 0;
1095 if (!blob->TexOrSubImage(isSubImage, isRespec, this, level, driverUnpackInfo,
1096 offset.x, offset.y, offset.z, pi, &glError)) {
1097 return;
1100 if (glError == LOCAL_GL_OUT_OF_MEMORY) {
1101 mContext->ErrorOutOfMemory("Driver ran out of memory during upload.");
1102 Truncate();
1103 return;
1106 if (glError) {
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();
1114 return;
1117 ////////////////////////////////////
1118 // Update our specification data?
1120 if (respecFormat) {
1121 mContext->OnDataAllocCall();
1122 *imageInfo = *newImageInfo;
1123 InvalidateCaches();
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) {
1136 return false;
1139 if (width % compression->blockWidth != 0 &&
1140 xOffset + width != imageInfo->mWidth)
1141 return false;
1143 if (height % compression->blockHeight != 0 &&
1144 yOffset + height != imageInfo->mHeight)
1145 return false;
1147 return true;
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;
1158 if (pboOffset) {
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);
1168 return;
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);
1177 return;
1179 } else {
1180 if (mContext->mBoundPixelUnpackBuffer) {
1181 mContext->GenerateError(LOCAL_GL_INVALID_OPERATION,
1182 "PIXEL_UNPACK_BUFFER is non-null.");
1183 return;
1185 imageSize = src.length();
1188 // -
1190 const auto usage = mContext->mFormatUsage->GetSizedTexUsage(formatEnum);
1191 if (!usage || !usage->format->compression) {
1192 mContext->ErrorInvalidEnumArg("format", formatEnum);
1193 return;
1196 webgl::ImageInfo* imageInfo;
1197 if (!sub) {
1198 if (!ValidateTexImageSpecification(imageTarget, level, size, &imageInfo)) {
1199 return;
1201 MOZ_ASSERT(imageInfo);
1203 if (!ValidateFormatAndSize(mContext, imageTarget, usage->format, size))
1204 return;
1205 if (!ValidateCompressedTexImageRestrictions(mContext, imageTarget, level,
1206 usage->format, size)) {
1207 return;
1209 } else {
1210 if (!ValidateTexImageSelection(imageTarget, level, offset, size,
1211 &imageInfo))
1212 return;
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.");
1220 return;
1223 const auto& format = usage->format;
1224 switch (format->compression->family) {
1225 // Forbidden:
1226 case webgl::CompressionFamily::ETC1:
1227 mContext->ErrorInvalidOperation(
1228 "Format does not allow sub-image"
1229 " updates.");
1230 return;
1232 // Block-aligned:
1233 case webgl::CompressionFamily::ES3: // Yes, the ES3 formats don't match
1234 // the ES3
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"
1242 " updates.");
1243 return;
1245 break;
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"
1254 " updates.");
1255 return;
1257 break;
1261 switch (usage->format->compression->family) {
1262 case webgl::CompressionFamily::BPTC:
1263 case webgl::CompressionFamily::RGTC:
1264 if (level == 0) {
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.");
1268 return;
1271 break;
1273 default:
1274 break;
1277 if (!ValidateCompressedTexUnpack(mContext, size, usage->format, imageSize))
1278 return;
1280 ////////////////////////////////////
1281 // Do the thing!
1283 if (sub) {
1284 if (!EnsureImageDataInitializedForUpload(this, imageTarget, level, offset,
1285 size, imageInfo)) {
1286 return;
1290 const ScopedLazyBind bindPBO(mContext->gl, LOCAL_GL_PIXEL_UNPACK_BUFFER,
1291 mContext->mBoundPixelUnpackBuffer);
1292 GLenum error;
1293 const void* ptr;
1294 if (pboOffset) {
1295 ptr = reinterpret_cast<const void*>(*pboOffset);
1296 } else {
1297 ptr = reinterpret_cast<const void*>(src.begin().get());
1300 if (!sub) {
1301 error = DoCompressedTexImage(mContext->gl, imageTarget, level, formatEnum,
1302 size.x, size.y, size.z, imageSize, ptr);
1303 } else {
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.");
1310 Truncate();
1311 return;
1313 if (error) {
1314 mContext->GenerateError(error, "Unexpected error from driver.");
1315 nsCString call;
1316 if (!sub) {
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,
1320 ptr);
1321 } else {
1322 call = nsPrintfCString(
1323 "DoCompressedTexSubImage(0x%04x, %u, %u,%u,%u, %u,%u,%u, 0x%04x, %u, "
1324 "%p)",
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();
1330 return;
1333 ////////////////////////////////////
1334 // Update our specification data?
1336 if (!sub) {
1337 constexpr auto uninitializedSlices = std::nullopt;
1338 const webgl::ImageInfo newImageInfo{usage, size.x, size.y, size.z,
1339 uninitializedSlices};
1340 *imageInfo = newImageInfo;
1341 InvalidateCaches();
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"
1355 " format.");
1356 return false;
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)");
1363 return false;
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)");
1370 return false;
1373 return true;
1376 ////////////////////////////////////////////////////////////////////////////////
1378 class ScopedCopyTexImageSource {
1379 WebGLContext* const mWebGL;
1380 GLuint mRB;
1381 GLuint mFB;
1383 public:
1384 ScopedCopyTexImageSource(WebGLContext* webgl, uint32_t srcWidth,
1385 uint32_t srcHeight,
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.");
1403 break;
1405 default:
1406 MOZ_ASSERT(!dstUsage->textureSwizzleRGBA);
1407 return;
1410 if (!dstUsage->textureSwizzleRGBA) return;
1412 gl::GLContext* gl = webgl->gl;
1414 GLenum sizedFormat;
1416 switch (srcFormat->componentType) {
1417 case webgl::ComponentType::NormUInt:
1418 sizedFormat = LOCAL_GL_RGBA8;
1419 break;
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);
1426 break;
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);
1433 break;
1435 MOZ_CRASH("GFX: Should be able to request CopyTexImage from Float.");
1437 default:
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,
1446 LOCAL_GL_NEAREST);
1447 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER,
1448 LOCAL_GL_NEAREST);
1450 GLint blitSwizzle[4] = {LOCAL_GL_ZERO};
1451 switch (dstUsage->format->unsizedFormat) {
1452 case webgl::UnsizedFormat::L:
1453 blitSwizzle[0] = LOCAL_GL_RED;
1454 break;
1456 case webgl::UnsizedFormat::A:
1457 blitSwizzle[0] = LOCAL_GL_ALPHA;
1458 break;
1460 case webgl::UnsizedFormat::LA:
1461 blitSwizzle[0] = LOCAL_GL_RED;
1462 blitSwizzle[1] = LOCAL_GL_ALPHA;
1463 break;
1465 default:
1466 MOZ_CRASH("GFX: Unhandled unsizedFormat.");
1469 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_R,
1470 blitSwizzle[0]);
1471 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_G,
1472 blitSwizzle[1]);
1473 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_B,
1474 blitSwizzle[2]);
1475 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_A,
1476 blitSwizzle[3]);
1478 gl->fCopyTexImage2D(LOCAL_GL_TEXTURE_2D, 0, sizedFormat, 0, 0, srcWidth,
1479 srcHeight, 0);
1481 // Now create the swizzled FB we'll be exposing.
1483 GLuint rgbaRB = 0;
1484 GLuint rgbaFB = 0;
1486 gl->fGenRenderbuffers(1, &rgbaRB);
1487 gl::ScopedBindRenderbuffer scopedRB(gl, rgbaRB);
1488 gl->fRenderbufferStorage(LOCAL_GL_RENDERBUFFER, sizedFormat, srcWidth,
1489 srcHeight);
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,
1508 srcSize);
1511 // Leave RB and FB alive, and FB bound.
1512 mRB = rgbaRB;
1513 mFB = rgbaFB;
1516 template <typename T>
1517 static inline GLenum ToGLHandle(const T& obj) {
1518 return (obj ? obj->mGLName : 0);
1521 ScopedCopyTexImageSource::~ScopedCopyTexImageSource() {
1522 if (!mFB) {
1523 MOZ_ASSERT(!mRB);
1524 return;
1526 MOZ_ASSERT(mRB);
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) {
1546 case LOCAL_GL_RED:
1547 *out = webgl::UnsizedFormat::R;
1548 break;
1549 case LOCAL_GL_RG:
1550 *out = webgl::UnsizedFormat::RG;
1551 break;
1552 case LOCAL_GL_RGB:
1553 *out = webgl::UnsizedFormat::RGB;
1554 break;
1555 case LOCAL_GL_RGBA:
1556 *out = webgl::UnsizedFormat::RGBA;
1557 break;
1558 case LOCAL_GL_LUMINANCE:
1559 *out = webgl::UnsizedFormat::L;
1560 break;
1561 case LOCAL_GL_ALPHA:
1562 *out = webgl::UnsizedFormat::A;
1563 break;
1564 case LOCAL_GL_LUMINANCE_ALPHA:
1565 *out = webgl::UnsizedFormat::LA;
1566 break;
1568 default:
1569 return false;
1572 return true;
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.");
1586 return nullptr;
1589 auto dstUsage = fua->GetSizedTexUsage(internalFormat);
1590 if (!dstUsage) {
1591 // Ok, maybe it's unsized.
1592 webgl::UnsizedFormat unsizedFormat;
1593 if (!GetUnsizedFormatForCopy(internalFormat, &unsizedFormat)) {
1594 webgl->ErrorInvalidEnumInfo("internalFormat", internalFormat);
1595 return nullptr;
1598 const auto dstFormat = srcFormat->GetCopyDecayFormat(unsizedFormat);
1599 if (dstFormat) {
1600 dstUsage = fua->GetUsage(dstFormat->effectiveFormat);
1602 if (!dstUsage) {
1603 webgl->ErrorInvalidOperation(
1604 "0x%04x is not a valid unsized format for"
1605 " source format %s.",
1606 internalFormat, srcFormat->name);
1607 return nullptr;
1610 return dstUsage;
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:"
1620 " %s)",
1621 srcFormat->name, dstFormat->name);
1622 return nullptr;
1625 bool componentSizesMatch = true;
1626 if (dstFormat->r) {
1627 componentSizesMatch &= (dstFormat->r == srcFormat->r);
1629 if (dstFormat->g) {
1630 componentSizesMatch &= (dstFormat->g == srcFormat->g);
1632 if (dstFormat->b) {
1633 componentSizesMatch &= (dstFormat->b == srcFormat->b);
1635 if (dstFormat->a) {
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,"
1643 " dest: %s)",
1644 srcFormat->name, dstFormat->name);
1645 return nullptr;
1648 return dstUsage;
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();
1656 if (fb) {
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);
1671 return false;
1674 return true;
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;
1688 ////
1690 int32_t readX, readY;
1691 int32_t writeX, writeY;
1692 int32_t rwWidth, rwHeight;
1693 if (!Intersect(srcTotalWidth, xWithinSrc, dstWidth, &readX, &writeX,
1694 &rwWidth) ||
1695 !Intersect(srcTotalHeight, yWithinSrc, dstHeight, &readY, &writeY,
1696 &rwHeight)) {
1697 webgl->ErrorOutOfMemory("Bad subrect selection.");
1698 return false;
1701 writeX += xOffset;
1702 writeY += yOffset;
1704 ////
1706 GLenum error = 0;
1707 nsCString errorText;
1708 do {
1709 const auto& idealUnpack = dstUsage->idealUnpack;
1710 const auto& pi = idealUnpack->ToPacking();
1712 UniqueBuffer zeros;
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()));
1724 if (!zeros.get()) {
1725 webgl->ErrorOutOfMemory("Ran out of memory allocating zeros.");
1726 return false;
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); });
1736 if (!isSubImage) {
1737 error = DoTexImage(gl, target, level, idealUnpack, dstWidth, dstHeight,
1738 1, nullptr);
1739 if (error) {
1740 errorText = nsPrintfCString(
1741 "DoTexImage(0x%04x, %i, {0x%04x, 0x%04x, 0x%04x}, %u,%u,1) -> "
1742 "0x%04x",
1743 target.get(), level, idealUnpack->internalFormat,
1744 idealUnpack->unpackFormat, idealUnpack->unpackType, dstWidth,
1745 dstHeight, error);
1746 break;
1749 if (zeros) {
1750 error = DoTexSubImage(gl, target, level, xOffset, yOffset, zOffset,
1751 dstWidth, dstHeight, 1, pi, zeros.get());
1752 if (error) {
1753 errorText = nsPrintfCString(
1754 "DoTexSubImage(0x%04x, %i, %i,%i,%i, %u,%u,1, {0x%04x, 0x%04x}) "
1755 "-> "
1756 "0x%04x",
1757 target.get(), level, xOffset, yOffset, zOffset, dstWidth,
1758 dstHeight, idealUnpack->unpackFormat, idealUnpack->unpackType,
1759 error);
1760 break;
1765 if (!rwWidth || !rwHeight) {
1766 // There aren't any pixels to copy, so we're 'done'.
1767 return true;
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);
1776 if (error) {
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,
1780 rwHeight, error);
1781 break;
1784 return true;
1785 } while (false);
1787 if (error == LOCAL_GL_OUT_OF_MEMORY) {
1788 webgl->ErrorOutOfMemory("Ran out of memory during texture copy.");
1789 tex->Truncate();
1790 return false;
1793 if (gl->IsANGLE() && error == LOCAL_GL_INVALID_OPERATION) {
1794 webgl->ErrorImplementationBug(
1795 "ANGLE is particular about CopyTexSubImage"
1796 " formats matching exactly.");
1797 return false;
1800 webgl->GenerateError(error, "Unexpected error from driver.");
1801 gfxCriticalError() << "Unexpected error from driver: "
1802 << errorText.BeginReading();
1803 return false;
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 ////////////////////////////////////
1811 // Get source info
1813 const webgl::FormatUsageInfo* srcUsage;
1814 uint32_t srcTotalWidth;
1815 uint32_t srcTotalHeight;
1816 if (!mContext->BindCurFBForColorRead(&srcUsage, &srcTotalWidth,
1817 &srcTotalHeight)) {
1818 return;
1820 const auto& srcFormat = srcUsage->format;
1822 if (!ValidateCopyTexImageForFeedback(*mContext, *this, level, dstOffset.z))
1823 return;
1825 const auto size = uvec3{size2.x, size2.y, 1};
1827 ////////////////////////////////////
1828 // Get dest info
1830 webgl::ImageInfo* imageInfo;
1831 const webgl::FormatUsageInfo* dstUsage;
1832 if (respecFormat) {
1833 if (!ValidateTexImageSpecification(imageTarget, level, size, &imageInfo))
1834 return;
1835 MOZ_ASSERT(imageInfo);
1837 dstUsage = ValidateCopyDestUsage(mContext, srcFormat, respecFormat);
1838 if (!dstUsage) return;
1840 if (!ValidateFormatAndSize(mContext, imageTarget, dstUsage->format, size))
1841 return;
1842 } else {
1843 if (!ValidateTexImageSelection(imageTarget, level, dstOffset, size,
1844 &imageInfo)) {
1845 return;
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);
1857 return;
1860 ////////////////////////////////////
1861 // Check that source and dest info are compatible
1863 if (!ValidateCopyTexImageFormats(mContext, srcFormat, dstFormat)) return;
1865 ////////////////////////////////////
1866 // Do the thing!
1868 const bool isSubImage = !respecFormat;
1869 bool expectsInit = true;
1870 if (isSubImage) {
1871 if (!EnsureImageDataInitializedForUpload(this, imageTarget, level,
1872 dstOffset, size, imageInfo,
1873 &expectsInit)) {
1874 return;
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)) {
1882 Truncate();
1883 return;
1886 mContext->OnDataAllocCall();
1888 ////////////////////////////////////
1889 // Update our specification data?
1891 if (respecFormat) {
1892 constexpr auto uninitializedSlices = std::nullopt;
1893 const webgl::ImageInfo newImageInfo{dstUsage, size.x, size.y, size.z,
1894 uninitializedSlices};
1895 *imageInfo = newImageInfo;
1896 InvalidateCaches();
1900 } // namespace mozilla