Bug 1925561 - Use a quicker radius calculation for ArcParams. r=aosmond
[gecko.git] / dom / canvas / WebGLTextureUpload.cpp
blob30d8fad8fa3b6cb3d513e75368752a2064e0ac2f
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 "WebGLContextUtils.h"
35 #include "WebGLFramebuffer.h"
36 #include "WebGLTexelConversions.h"
38 namespace mozilla {
39 namespace webgl {
41 // The canvas spec says that drawImage should draw the first frame of
42 // animated images. The webgl spec doesn't mention the issue, so we do the
43 // same as drawImage.
44 static constexpr uint32_t kDefaultSurfaceFromElementFlags =
45 nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE |
46 nsLayoutUtils::SFE_USE_ELEMENT_SIZE_IF_VECTOR |
47 nsLayoutUtils::SFE_EXACT_SIZE_SURFACE |
48 nsLayoutUtils::SFE_ALLOW_NON_PREMULT;
50 Maybe<TexUnpackBlobDesc> FromImageBitmap(const GLenum target, Maybe<uvec3> size,
51 const dom::ImageBitmap& imageBitmap,
52 ErrorResult* const out_rv) {
53 if (imageBitmap.IsWriteOnly()) {
54 out_rv->Throw(NS_ERROR_DOM_SECURITY_ERR);
55 return {};
58 const auto cloneData = imageBitmap.ToCloneData();
59 if (!cloneData) {
60 return {};
63 const RefPtr<gfx::DataSourceSurface> surf = cloneData->mSurface;
64 if (NS_WARN_IF(!surf)) {
65 return {};
68 const auto imageSize = *uvec2::FromSize(surf->GetSize());
69 if (!size) {
70 size.emplace(imageSize.x, imageSize.y, 1);
73 // For SourceSurfaceSharedData, try to get SurfaceDescriptorExternalImage.
74 Maybe<layers::SurfaceDescriptor> sd;
75 layers::SharedSurfacesChild::Share(surf, sd);
77 // WhatWG "HTML Living Standard" (30 October 2015):
78 // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned as
79 // non-premultiplied alpha values."
80 return Some(TexUnpackBlobDesc{target,
81 size.value(),
82 cloneData->mAlphaType,
83 {},
84 {},
85 Some(imageSize),
86 nullptr,
87 sd,
88 surf,
89 {},
90 false});
93 static layers::SurfaceDescriptor Flatten(const layers::SurfaceDescriptor& sd) {
94 const auto sdType = sd.type();
95 if (sdType != layers::SurfaceDescriptor::TSurfaceDescriptorGPUVideo) {
96 return sd;
98 const auto& sdv = sd.get_SurfaceDescriptorGPUVideo();
99 const auto& sdvType = sdv.type();
100 if (sdvType !=
101 layers::SurfaceDescriptorGPUVideo::TSurfaceDescriptorRemoteDecoder) {
102 return sd;
105 const auto& sdrd = sdv.get_SurfaceDescriptorRemoteDecoder();
106 const auto& subdesc = sdrd.subdesc();
107 const auto& subdescType = subdesc.type();
108 switch (subdescType) {
109 case layers::RemoteDecoderVideoSubDescriptor::T__None:
110 case layers::RemoteDecoderVideoSubDescriptor::Tnull_t:
111 return sd;
113 case layers::RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorD3D10:
114 return subdesc.get_SurfaceDescriptorD3D10();
115 case layers::RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorDXGIYCbCr:
116 return subdesc.get_SurfaceDescriptorDXGIYCbCr();
117 case layers::RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorDMABuf:
118 return subdesc.get_SurfaceDescriptorDMABuf();
119 case layers::RemoteDecoderVideoSubDescriptor::
120 TSurfaceDescriptorMacIOSurface:
121 return subdesc.get_SurfaceDescriptorMacIOSurface();
122 case layers::RemoteDecoderVideoSubDescriptor::
123 TSurfaceDescriptorDcompSurface:
124 return subdesc.get_SurfaceDescriptorDcompSurface();
126 MOZ_CRASH("unreachable");
129 Maybe<webgl::TexUnpackBlobDesc> FromOffscreenCanvas(
130 const ClientWebGLContext& webgl, const GLenum target, Maybe<uvec3> size,
131 const dom::OffscreenCanvas& canvas, ErrorResult* const out_error) {
132 if (canvas.IsWriteOnly()) {
133 webgl.EnqueueWarning(
134 "OffscreenCanvas is write-only, thus cannot be uploaded.");
135 out_error->ThrowSecurityError(
136 "OffscreenCanvas is write-only, thus cannot be uploaded.");
137 return {};
140 auto sfer = nsLayoutUtils::SurfaceFromOffscreenCanvas(
141 const_cast<dom::OffscreenCanvas*>(&canvas),
142 kDefaultSurfaceFromElementFlags);
143 return FromSurfaceFromElementResult(webgl, target, size, sfer, out_error);
146 Maybe<webgl::TexUnpackBlobDesc> FromVideoFrame(
147 const ClientWebGLContext& webgl, const GLenum target, Maybe<uvec3> size,
148 const dom::VideoFrame& videoFrame, ErrorResult* const out_error) {
149 auto sfer = nsLayoutUtils::SurfaceFromVideoFrame(
150 const_cast<dom::VideoFrame*>(&videoFrame),
151 kDefaultSurfaceFromElementFlags);
152 return FromSurfaceFromElementResult(webgl, target, size, sfer, out_error);
155 Maybe<webgl::TexUnpackBlobDesc> FromDomElem(const ClientWebGLContext& webgl,
156 const GLenum target,
157 Maybe<uvec3> size,
158 const dom::Element& elem,
159 ErrorResult* const out_error) {
160 if (elem.IsHTMLElement(nsGkAtoms::canvas)) {
161 const dom::HTMLCanvasElement* srcCanvas =
162 static_cast<const dom::HTMLCanvasElement*>(&elem);
163 if (srcCanvas->IsWriteOnly()) {
164 out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
165 return {};
169 uint32_t flags = kDefaultSurfaceFromElementFlags;
170 const auto& unpacking = webgl.State().mPixelUnpackState;
171 if (unpacking.colorspaceConversion == LOCAL_GL_NONE) {
172 flags |= nsLayoutUtils::SFE_NO_COLORSPACE_CONVERSION;
175 RefPtr<gfx::DrawTarget> idealDrawTarget = nullptr; // Don't care for now.
176 auto sfer = nsLayoutUtils::SurfaceFromElement(
177 const_cast<dom::Element*>(&elem), flags, idealDrawTarget);
178 return FromSurfaceFromElementResult(webgl, target, size, sfer, out_error);
181 Maybe<webgl::TexUnpackBlobDesc> FromSurfaceFromElementResult(
182 const ClientWebGLContext& webgl, const GLenum target, Maybe<uvec3> size,
183 SurfaceFromElementResult& sfer, ErrorResult* const out_error) {
184 uvec2 elemSize;
186 const auto& layersImage = sfer.mLayersImage;
187 Maybe<layers::SurfaceDescriptor> sd;
188 if (layersImage) {
189 elemSize = *uvec2::FromSize(layersImage->GetSize());
191 sd = layersImage->GetDesc();
192 if (sd) {
193 sd = Some(Flatten(*sd));
195 if (!sd) {
196 NS_WARNING("No SurfaceDescriptor for layers::Image!");
200 RefPtr<gfx::DataSourceSurface> dataSurf;
201 if (!sd && sfer.GetSourceSurface()) {
202 const auto surf = sfer.GetSourceSurface();
203 elemSize = *uvec2::FromSize(surf->GetSize());
205 // WARNING: OSX can lose our MakeCurrent here.
206 dataSurf = surf->GetDataSurface();
209 if (!sd) {
210 // For SourceSurfaceSharedData, try to get SurfaceDescriptorExternalImage.
211 layers::SharedSurfacesChild::Share(dataSurf, sd);
214 //////
216 if (!size) {
217 size.emplace(elemSize.x, elemSize.y, 1);
220 ////
222 if (!sd && !dataSurf) {
223 webgl.EnqueueWarning("Resource has no data (yet?). Uploading zeros.");
224 if (!size) {
225 size.emplace(0, 0, 1);
227 return Some(
228 TexUnpackBlobDesc{target, size.value(), gfxAlphaType::NonPremult});
231 //////
233 // While it's counter-intuitive, the shape of the SFEResult API means that we
234 // should try to pull out a surface first, and then, if we do pull out a
235 // surface, check CORS/write-only/etc..
237 if (!sfer.mCORSUsed) {
238 auto& srcPrincipal = sfer.mPrincipal;
239 nsIPrincipal* dstPrincipal = webgl.PrincipalOrNull();
240 if (!dstPrincipal || !dstPrincipal->Subsumes(srcPrincipal)) {
241 webgl.EnqueueWarning("Cross-origin elements require CORS.");
242 out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
243 return {};
247 if (sfer.mIsWriteOnly) {
248 // mIsWriteOnly defaults to true, and so will be true even if SFE merely
249 // failed. Thus we must test mIsWriteOnly after successfully retrieving an
250 // Image or SourceSurface.
251 webgl.EnqueueWarning("Element is write-only, thus cannot be uploaded.");
252 out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
253 return {};
256 //////
257 // Ok, we're good!
259 return Some(TexUnpackBlobDesc{target,
260 size.value(),
261 sfer.mAlphaType,
264 Some(elemSize),
265 layersImage,
267 dataSurf});
270 } // namespace webgl
272 //////////////////////////////////////////////////////////////////////////////////////////
273 //////////////////////////////////////////////////////////////////////////////////////////
275 static bool ValidateTexImage(WebGLContext* webgl, WebGLTexture* texture,
276 TexImageTarget target, uint32_t level,
277 webgl::ImageInfo** const out_imageInfo) {
278 // Check level
279 if (level >= WebGLTexture::kMaxLevelCount) {
280 webgl->ErrorInvalidValue("`level` is too large.");
281 return false;
284 auto& imageInfo = texture->ImageInfoAt(target, level);
285 *out_imageInfo = &imageInfo;
286 return true;
289 // For *TexImage*
290 bool WebGLTexture::ValidateTexImageSpecification(
291 TexImageTarget target, uint32_t level, const uvec3& size,
292 webgl::ImageInfo** const out_imageInfo) {
293 if (mImmutable) {
294 mContext->ErrorInvalidOperation("Specified texture is immutable.");
295 return false;
298 // Do this early to validate `level`.
299 webgl::ImageInfo* imageInfo;
300 if (!ValidateTexImage(mContext, this, target, level, &imageInfo))
301 return false;
303 if (mTarget == LOCAL_GL_TEXTURE_CUBE_MAP && size.x != size.y) {
304 mContext->ErrorInvalidValue("Cube map images must be square.");
305 return false;
308 /* GLES 3.0.4, p133-134:
309 * GL_MAX_TEXTURE_SIZE is *not* the max allowed texture size. Rather, it is
310 * the max (width/height) size guaranteed not to generate an INVALID_VALUE for
311 * too-large dimensions. Sizes larger than GL_MAX_TEXTURE_SIZE *may or may
312 * not* result in an INVALID_VALUE, or possibly GL_OOM.
314 * However, we have needed to set our maximums lower in the past to prevent
315 * resource corruption. Therefore we have limits.maxTex2dSize, which is
316 * neither necessarily lower nor higher than MAX_TEXTURE_SIZE.
318 * Note that limits.maxTex2dSize must be >= than the advertized
319 * MAX_TEXTURE_SIZE. For simplicity, we advertize MAX_TEXTURE_SIZE as
320 * limits.maxTex2dSize.
323 uint32_t maxWidthHeight = 0;
324 uint32_t maxDepth = 0;
325 uint32_t maxLevel = 0;
327 const auto& limits = mContext->Limits();
328 MOZ_ASSERT(level <= 31);
329 switch (target.get()) {
330 case LOCAL_GL_TEXTURE_2D:
331 maxWidthHeight = limits.maxTex2dSize >> level;
332 maxDepth = 1;
333 maxLevel = CeilingLog2(limits.maxTex2dSize);
334 break;
336 case LOCAL_GL_TEXTURE_3D:
337 maxWidthHeight = limits.maxTex3dSize >> level;
338 maxDepth = maxWidthHeight;
339 maxLevel = CeilingLog2(limits.maxTex3dSize);
340 break;
342 case LOCAL_GL_TEXTURE_2D_ARRAY:
343 maxWidthHeight = limits.maxTex2dSize >> level;
344 // "The maximum number of layers for two-dimensional array textures
345 // (depth) must be at least MAX_ARRAY_TEXTURE_LAYERS for all levels."
346 maxDepth = limits.maxTexArrayLayers;
347 maxLevel = CeilingLog2(limits.maxTex2dSize);
348 break;
350 default: // cube maps
351 MOZ_ASSERT(IsCubeMap());
352 maxWidthHeight = limits.maxTexCubeSize >> level;
353 maxDepth = 1;
354 maxLevel = CeilingLog2(limits.maxTexCubeSize);
355 break;
358 if (level > maxLevel) {
359 mContext->ErrorInvalidValue("Requested level is not supported for target.");
360 return false;
363 if (size.x > maxWidthHeight || size.y > maxWidthHeight || size.z > maxDepth) {
364 mContext->ErrorInvalidValue("Requested size at this level is unsupported.");
365 return false;
369 /* GL ES Version 2.0.25 - 3.7.1 Texture Image Specification
370 * "If level is greater than zero, and either width or
371 * height is not a power-of-two, the error INVALID_VALUE is
372 * generated."
374 * This restriction does not apply to GL ES Version 3.0+.
376 bool requirePOT = (!mContext->IsWebGL2() && level != 0);
378 if (requirePOT) {
379 if (!IsPowerOfTwo(size.x) || !IsPowerOfTwo(size.y)) {
380 mContext->ErrorInvalidValue(
381 "For level > 0, width and height must be"
382 " powers of two.");
383 return false;
388 *out_imageInfo = imageInfo;
389 return true;
392 // For *TexSubImage*
393 bool WebGLTexture::ValidateTexImageSelection(
394 TexImageTarget target, uint32_t level, const uvec3& offset,
395 const uvec3& size, webgl::ImageInfo** const out_imageInfo) {
396 webgl::ImageInfo* imageInfo;
397 if (!ValidateTexImage(mContext, this, target, level, &imageInfo))
398 return false;
400 if (!imageInfo->IsDefined()) {
401 mContext->ErrorInvalidOperation(
402 "The specified TexImage has not yet been"
403 " specified.");
404 return false;
407 const auto totalX = CheckedUint32(offset.x) + size.x;
408 const auto totalY = CheckedUint32(offset.y) + size.y;
409 const auto totalZ = CheckedUint32(offset.z) + size.z;
411 if (!totalX.isValid() || totalX.value() > imageInfo->mWidth ||
412 !totalY.isValid() || totalY.value() > imageInfo->mHeight ||
413 !totalZ.isValid() || totalZ.value() > imageInfo->mDepth) {
414 mContext->ErrorInvalidValue(
415 "Offset+size must be <= the size of the existing"
416 " specified image.");
417 return false;
420 *out_imageInfo = imageInfo;
421 return true;
424 static bool ValidateCompressedTexUnpack(WebGLContext* webgl, const uvec3& size,
425 const webgl::FormatInfo* format,
426 size_t dataSize) {
427 auto compression = format->compression;
429 auto bytesPerBlock = compression->bytesPerBlock;
430 auto blockWidth = compression->blockWidth;
431 auto blockHeight = compression->blockHeight;
433 auto widthInBlocks = CheckedUint32(size.x) / blockWidth;
434 auto heightInBlocks = CheckedUint32(size.y) / blockHeight;
435 if (size.x % blockWidth) widthInBlocks += 1;
436 if (size.y % blockHeight) heightInBlocks += 1;
438 const CheckedUint32 blocksPerImage = widthInBlocks * heightInBlocks;
439 const CheckedUint32 bytesPerImage = bytesPerBlock * blocksPerImage;
440 const CheckedUint32 bytesNeeded = bytesPerImage * size.z;
442 if (!bytesNeeded.isValid()) {
443 webgl->ErrorOutOfMemory("Overflow while computing the needed buffer size.");
444 return false;
447 if (dataSize != bytesNeeded.value()) {
448 webgl->ErrorInvalidValue(
449 "Provided buffer's size must match expected size."
450 " (needs %u, has %zu)",
451 bytesNeeded.value(), dataSize);
452 return false;
455 return true;
458 static bool DoChannelsMatchForCopyTexImage(const webgl::FormatInfo* srcFormat,
459 const webgl::FormatInfo* dstFormat) {
460 // GLES 3.0.4 p140 Table 3.16 "Valid CopyTexImage source
461 // framebuffer/destination texture base internal format combinations."
463 switch (srcFormat->unsizedFormat) {
464 case webgl::UnsizedFormat::RGBA:
465 switch (dstFormat->unsizedFormat) {
466 case webgl::UnsizedFormat::A:
467 case webgl::UnsizedFormat::L:
468 case webgl::UnsizedFormat::LA:
469 case webgl::UnsizedFormat::R:
470 case webgl::UnsizedFormat::RG:
471 case webgl::UnsizedFormat::RGB:
472 case webgl::UnsizedFormat::RGBA:
473 return true;
474 default:
475 return false;
478 case webgl::UnsizedFormat::RGB:
479 switch (dstFormat->unsizedFormat) {
480 case webgl::UnsizedFormat::L:
481 case webgl::UnsizedFormat::R:
482 case webgl::UnsizedFormat::RG:
483 case webgl::UnsizedFormat::RGB:
484 return true;
485 default:
486 return false;
489 case webgl::UnsizedFormat::RG:
490 switch (dstFormat->unsizedFormat) {
491 case webgl::UnsizedFormat::L:
492 case webgl::UnsizedFormat::R:
493 case webgl::UnsizedFormat::RG:
494 return true;
495 default:
496 return false;
499 case webgl::UnsizedFormat::R:
500 switch (dstFormat->unsizedFormat) {
501 case webgl::UnsizedFormat::L:
502 case webgl::UnsizedFormat::R:
503 return true;
504 default:
505 return false;
508 default:
509 return false;
513 static bool EnsureImageDataInitializedForUpload(
514 WebGLTexture* tex, TexImageTarget target, uint32_t level,
515 const uvec3& offset, const uvec3& size, webgl::ImageInfo* imageInfo,
516 bool* const out_expectsInit = nullptr) {
517 if (out_expectsInit) {
518 *out_expectsInit = false;
520 if (!imageInfo->mUninitializedSlices) return true;
522 if (size.x == imageInfo->mWidth && size.y == imageInfo->mHeight) {
523 bool expectsInit = false;
524 auto& isSliceUninit = *imageInfo->mUninitializedSlices;
525 for (const auto z : IntegerRange(offset.z, offset.z + size.z)) {
526 if (!isSliceUninit[z]) continue;
527 expectsInit = true;
528 isSliceUninit[z] = false;
530 if (out_expectsInit) {
531 *out_expectsInit = expectsInit;
534 if (!expectsInit) return true;
536 bool hasUninitialized = false;
537 for (const auto z : IntegerRange(imageInfo->mDepth)) {
538 hasUninitialized |= isSliceUninit[z];
540 if (!hasUninitialized) {
541 imageInfo->mUninitializedSlices = Nothing();
543 return true;
546 WebGLContext* webgl = tex->mContext;
547 webgl->GenerateWarning(
548 "Texture has not been initialized prior to a"
549 " partial upload, forcing the browser to clear it."
550 " This may be slow.");
551 if (!tex->EnsureImageDataInitialized(target, level)) {
552 MOZ_ASSERT(false, "Unexpected failure to init image data.");
553 return false;
556 return true;
559 //////////////////////////////////////////////////////////////////////////////////////////
560 //////////////////////////////////////////////////////////////////////////////////////////
561 // Actual calls
563 static inline GLenum DoTexStorage(gl::GLContext* gl, TexTarget target,
564 GLsizei levels, GLenum sizedFormat,
565 GLsizei width, GLsizei height,
566 GLsizei depth) {
567 gl::GLContext::LocalErrorScope errorScope(*gl);
569 switch (target.get()) {
570 case LOCAL_GL_TEXTURE_2D:
571 case LOCAL_GL_TEXTURE_CUBE_MAP:
572 MOZ_ASSERT(depth == 1);
573 gl->fTexStorage2D(target.get(), levels, sizedFormat, width, height);
574 break;
576 case LOCAL_GL_TEXTURE_3D:
577 case LOCAL_GL_TEXTURE_2D_ARRAY:
578 gl->fTexStorage3D(target.get(), levels, sizedFormat, width, height,
579 depth);
580 break;
582 default:
583 MOZ_CRASH("GFX: bad target");
586 return errorScope.GetError();
589 bool IsTarget3D(TexImageTarget target) {
590 switch (target.get()) {
591 case LOCAL_GL_TEXTURE_2D:
592 case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
593 case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
594 case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
595 case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
596 case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
597 case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
598 return false;
600 case LOCAL_GL_TEXTURE_3D:
601 case LOCAL_GL_TEXTURE_2D_ARRAY:
602 return true;
604 default:
605 MOZ_CRASH("GFX: bad target");
609 GLenum DoTexImage(gl::GLContext* gl, TexImageTarget target, GLint level,
610 const webgl::DriverUnpackInfo* dui, GLsizei width,
611 GLsizei height, GLsizei depth, const void* data) {
612 const GLint border = 0;
614 gl::GLContext::LocalErrorScope errorScope(*gl);
616 if (IsTarget3D(target)) {
617 gl->fTexImage3D(target.get(), level, dui->internalFormat, width, height,
618 depth, border, dui->unpackFormat, dui->unpackType, data);
619 } else {
620 MOZ_ASSERT(depth == 1);
621 gl->fTexImage2D(target.get(), level, dui->internalFormat, width, height,
622 border, dui->unpackFormat, dui->unpackType, data);
625 return errorScope.GetError();
628 GLenum DoTexSubImage(gl::GLContext* gl, TexImageTarget target, GLint level,
629 GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width,
630 GLsizei height, GLsizei depth,
631 const webgl::PackingInfo& pi, const void* data) {
632 gl::GLContext::LocalErrorScope errorScope(*gl);
634 if (IsTarget3D(target)) {
635 gl->fTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset, width,
636 height, depth, pi.format, pi.type, data);
637 } else {
638 MOZ_ASSERT(zOffset == 0);
639 MOZ_ASSERT(depth == 1);
640 gl->fTexSubImage2D(target.get(), level, xOffset, yOffset, width, height,
641 pi.format, pi.type, data);
644 return errorScope.GetError();
647 static inline GLenum DoCompressedTexImage(gl::GLContext* gl,
648 TexImageTarget target, GLint level,
649 GLenum internalFormat, GLsizei width,
650 GLsizei height, GLsizei depth,
651 GLsizei dataSize, const void* data) {
652 const GLint border = 0;
654 gl::GLContext::LocalErrorScope errorScope(*gl);
656 if (IsTarget3D(target)) {
657 gl->fCompressedTexImage3D(target.get(), level, internalFormat, width,
658 height, depth, border, dataSize, data);
659 } else {
660 MOZ_ASSERT(depth == 1);
661 gl->fCompressedTexImage2D(target.get(), level, internalFormat, width,
662 height, border, dataSize, data);
665 return errorScope.GetError();
668 GLenum DoCompressedTexSubImage(gl::GLContext* gl, TexImageTarget target,
669 GLint level, GLint xOffset, GLint yOffset,
670 GLint zOffset, GLsizei width, GLsizei height,
671 GLsizei depth, GLenum sizedUnpackFormat,
672 GLsizei dataSize, const void* data) {
673 gl::GLContext::LocalErrorScope errorScope(*gl);
675 if (IsTarget3D(target)) {
676 gl->fCompressedTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset,
677 width, height, depth, sizedUnpackFormat,
678 dataSize, data);
679 } else {
680 MOZ_ASSERT(zOffset == 0);
681 MOZ_ASSERT(depth == 1);
682 gl->fCompressedTexSubImage2D(target.get(), level, xOffset, yOffset, width,
683 height, sizedUnpackFormat, dataSize, data);
686 return errorScope.GetError();
689 static inline GLenum DoCopyTexSubImage(gl::GLContext* gl, TexImageTarget target,
690 GLint level, GLint xOffset,
691 GLint yOffset, GLint zOffset, GLint x,
692 GLint y, GLsizei width, GLsizei height) {
693 gl::GLContext::LocalErrorScope errorScope(*gl);
695 if (IsTarget3D(target)) {
696 gl->fCopyTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset, x, y,
697 width, height);
698 } else {
699 MOZ_ASSERT(zOffset == 0);
700 gl->fCopyTexSubImage2D(target.get(), level, xOffset, yOffset, x, y, width,
701 height);
704 return errorScope.GetError();
707 //////////////////////////////////////////////////////////////////////////////////////////
708 //////////////////////////////////////////////////////////////////////////////////////////
709 // Actual (mostly generic) function implementations
711 static bool ValidateCompressedTexImageRestrictions(
712 const WebGLContext* webgl, TexImageTarget target, uint32_t level,
713 const webgl::FormatInfo* format, const uvec3& size) {
714 const auto fnIsDimValid_S3TC = [&](const char* const name, uint32_t levelSize,
715 uint32_t blockSize) {
716 const auto impliedBaseSize = levelSize << level;
717 if (impliedBaseSize % blockSize == 0) return true;
718 webgl->ErrorInvalidOperation(
719 "%u is never a valid %s for level %u, because it implies a base mip %s "
720 "of %u."
721 " %s requires that base mip levels have a %s multiple of %u.",
722 levelSize, name, level, name, impliedBaseSize, format->name, name,
723 blockSize);
724 return false;
727 switch (format->compression->family) {
728 case webgl::CompressionFamily::ASTC:
729 if (target == LOCAL_GL_TEXTURE_3D &&
730 !webgl->gl->IsExtensionSupported(
731 gl::GLContext::KHR_texture_compression_astc_hdr)) {
732 webgl->ErrorInvalidOperation("TEXTURE_3D requires ASTC's hdr profile.");
733 return false;
735 break;
737 case webgl::CompressionFamily::PVRTC:
738 if (!IsPowerOfTwo(size.x) || !IsPowerOfTwo(size.y)) {
739 webgl->ErrorInvalidValue("%s requires power-of-two width and height.",
740 format->name);
741 return false;
743 break;
745 case webgl::CompressionFamily::BPTC:
746 case webgl::CompressionFamily::RGTC:
747 case webgl::CompressionFamily::S3TC:
748 if (!fnIsDimValid_S3TC("width", size.x,
749 format->compression->blockWidth) ||
750 !fnIsDimValid_S3TC("height", size.y,
751 format->compression->blockHeight)) {
752 return false;
754 break;
756 // Default: There are no restrictions on CompressedTexImage.
757 case webgl::CompressionFamily::ES3:
758 case webgl::CompressionFamily::ETC1:
759 break;
762 return true;
765 static bool ValidateFormatAndSize(const WebGLContext* webgl,
766 TexImageTarget target,
767 const webgl::FormatInfo* format,
768 const uvec3& size) {
769 // Check if texture size will likely be rejected by the driver and give a more
770 // meaningful error message.
771 auto baseImageSize = CheckedInt<uint64_t>(format->estimatedBytesPerPixel) *
772 (uint32_t)size.x * (uint32_t)size.y * (uint32_t)size.z;
773 if (target == LOCAL_GL_TEXTURE_CUBE_MAP) {
774 baseImageSize *= 6;
776 if (!baseImageSize.isValid() ||
777 baseImageSize.value() >
778 (uint64_t)StaticPrefs::webgl_max_size_per_texture_mib() *
779 (1024 * 1024)) {
780 webgl->ErrorOutOfMemory(
781 "Texture size too large; base image mebibytes > "
782 "webgl.max-size-per-texture-mib");
783 return false;
786 // GLES 3.0.4 p127:
787 // "Textures with a base internal format of DEPTH_COMPONENT or DEPTH_STENCIL
788 // are supported by texture image specification commands only if `target` is
789 // TEXTURE_2D, TEXTURE_2D_ARRAY, or TEXTURE_CUBE_MAP. Using these formats in
790 // conjunction with any other `target` will result in an INVALID_OPERATION
791 // error."
792 const bool ok = [&]() {
793 if (bool(format->d) & (target == LOCAL_GL_TEXTURE_3D)) return false;
795 if (format->compression) {
796 switch (format->compression->family) {
797 case webgl::CompressionFamily::ES3:
798 case webgl::CompressionFamily::S3TC:
799 if (target == LOCAL_GL_TEXTURE_3D) return false;
800 break;
802 case webgl::CompressionFamily::ETC1:
803 case webgl::CompressionFamily::PVRTC:
804 case webgl::CompressionFamily::RGTC:
805 if (target == LOCAL_GL_TEXTURE_3D ||
806 target == LOCAL_GL_TEXTURE_2D_ARRAY) {
807 return false;
809 break;
810 default:
811 break;
814 return true;
815 }();
816 if (!ok) {
817 webgl->ErrorInvalidOperation("Format %s cannot be used with target %s.",
818 format->name, GetEnumName(target.get()));
819 return false;
822 return true;
825 void WebGLTexture::TexStorage(TexTarget target, uint32_t levels,
826 GLenum sizedFormat, const uvec3& size) {
827 // Check levels
828 if (levels < 1) {
829 mContext->ErrorInvalidValue("`levels` must be >= 1.");
830 return;
833 if (!size.x || !size.y || !size.z) {
834 mContext->ErrorInvalidValue("Dimensions must be non-zero.");
835 return;
838 const TexImageTarget testTarget =
839 IsCubeMap() ? LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X : target.get();
840 webgl::ImageInfo* baseImageInfo;
841 if (!ValidateTexImageSpecification(testTarget, 0, size, &baseImageInfo)) {
842 return;
844 MOZ_ALWAYS_TRUE(baseImageInfo);
846 auto dstUsage = mContext->mFormatUsage->GetSizedTexUsage(sizedFormat);
847 if (!dstUsage) {
848 mContext->ErrorInvalidEnumInfo("internalformat", sizedFormat);
849 return;
851 auto dstFormat = dstUsage->format;
853 if (!ValidateFormatAndSize(mContext, testTarget, dstFormat, size)) return;
855 if (dstFormat->compression) {
856 if (!ValidateCompressedTexImageRestrictions(mContext, testTarget, 0,
857 dstFormat, size)) {
858 return;
862 ////////////////////////////////////
864 const bool levelsOk = [&]() {
865 // Right-shift is only defined for bits-1, which is too large anyways.
866 const auto lastLevel = uint32_t(levels - 1);
867 if (lastLevel > 31) return false;
869 const auto lastLevelWidth = uint32_t(size.x) >> lastLevel;
870 const auto lastLevelHeight = uint32_t(size.y) >> lastLevel;
872 // If these are all zero, then some earlier level was the final 1x1(x1)
873 // level.
874 bool ok = lastLevelWidth || lastLevelHeight;
875 if (target == LOCAL_GL_TEXTURE_3D) {
876 const auto lastLevelDepth = uint32_t(size.z) >> lastLevel;
877 ok |= bool(lastLevelDepth);
879 return ok;
880 }();
881 if (!levelsOk) {
882 mContext->ErrorInvalidOperation(
883 "Too many levels requested for the given"
884 " dimensions. (levels: %u, width: %u, height: %u,"
885 " depth: %u)",
886 levels, size.x, size.y, size.z);
887 return;
890 ////////////////////////////////////
891 // Do the thing!
893 GLenum error = DoTexStorage(mContext->gl, target.get(), levels, sizedFormat,
894 size.x, size.y, size.z);
896 mContext->OnDataAllocCall();
898 if (error == LOCAL_GL_OUT_OF_MEMORY) {
899 mContext->ErrorOutOfMemory("Ran out of memory during texture allocation.");
900 Truncate();
901 return;
903 if (error) {
904 mContext->GenerateError(error, "Unexpected error from driver.");
905 const nsPrintfCString call(
906 "DoTexStorage(0x%04x, %i, 0x%04x, %i,%i,%i) -> 0x%04x", target.get(),
907 levels, sizedFormat, size.x, size.y, size.z, error);
908 gfxCriticalError() << "Unexpected error from driver: "
909 << call.BeginReading();
910 return;
913 ////////////////////////////////////
914 // Update our specification data.
916 auto uninitializedSlices = Some(std::vector<bool>(size.z, true));
917 const webgl::ImageInfo newInfo{dstUsage, size.x, size.y, size.z,
918 std::move(uninitializedSlices)};
921 const auto base_level = mBaseMipmapLevel;
922 mBaseMipmapLevel = 0;
924 ImageInfoAtFace(0, 0) = newInfo;
925 PopulateMipChain(levels - 1);
927 mBaseMipmapLevel = base_level;
930 mImmutable = true;
931 mImmutableLevelCount = AutoAssertCast(levels);
932 ClampLevelBaseAndMax();
935 ////////////////////////////////////////
936 // Tex(Sub)Image
938 // TexSubImage iff `!respectFormat`
939 void WebGLTexture::TexImage(uint32_t level, GLenum respecFormat,
940 const uvec3& offset, const webgl::PackingInfo& pi,
941 const webgl::TexUnpackBlobDesc& src) {
942 const auto blob = webgl::TexUnpackBlob::Create(src);
943 if (!blob) {
944 MOZ_ASSERT(false);
945 return;
948 const auto imageTarget = blob->mDesc.imageTarget;
949 auto size = blob->mDesc.size;
951 if (!IsTarget3D(imageTarget)) {
952 size.z = 1;
955 ////////////////////////////////////
956 // Get dest info
958 const auto& fua = mContext->mFormatUsage;
959 const auto fnValidateUnpackEnums = [&]() {
960 if (!fua->AreUnpackEnumsValid(pi.format, pi.type)) {
961 mContext->ErrorInvalidEnum("Invalid unpack format/type: %s/%s",
962 EnumString(pi.format).c_str(),
963 EnumString(pi.type).c_str());
964 return false;
966 return true;
969 webgl::ImageInfo* imageInfo;
970 const webgl::FormatUsageInfo* dstUsage;
971 if (respecFormat) {
972 if (!ValidateTexImageSpecification(imageTarget, level, size, &imageInfo))
973 return;
974 MOZ_ASSERT(imageInfo);
976 if (!fua->IsInternalFormatEnumValid(respecFormat)) {
977 mContext->ErrorInvalidValue("Invalid internalformat: 0x%04x",
978 respecFormat);
979 return;
982 dstUsage = fua->GetSizedTexUsage(respecFormat);
983 if (!dstUsage) {
984 if (respecFormat != pi.format) {
985 /* GL ES Version 3.0.4 - 3.8.3 Texture Image Specification
986 * "Specifying a combination of values for format, type, and
987 * internalformat that is not listed as a valid combination
988 * in tables 3.2 or 3.3 generates the error INVALID_OPERATION."
990 if (!fnValidateUnpackEnums()) return;
991 mContext->ErrorInvalidOperation(
992 "Unsized internalFormat must match"
993 " unpack format.");
994 return;
997 dstUsage = fua->GetUnsizedTexUsage(pi);
1000 if (!dstUsage) {
1001 if (!fnValidateUnpackEnums()) return;
1002 mContext->ErrorInvalidOperation(
1003 "Invalid internalformat/format/type:"
1004 " 0x%04x/0x%04x/0x%04x",
1005 respecFormat, pi.format, pi.type);
1006 return;
1009 const auto& dstFormat = dstUsage->format;
1010 if (!ValidateFormatAndSize(mContext, imageTarget, dstFormat, size)) return;
1012 if (!mContext->IsWebGL2() && dstFormat->d) {
1013 if (imageTarget != LOCAL_GL_TEXTURE_2D || blob->HasData() || level != 0) {
1014 mContext->ErrorInvalidOperation(
1015 "With format %s, this function may only"
1016 " be called with target=TEXTURE_2D,"
1017 " data=null, and level=0.",
1018 dstFormat->name);
1019 return;
1022 } else {
1023 if (!ValidateTexImageSelection(imageTarget, level, offset, size,
1024 &imageInfo)) {
1025 return;
1027 MOZ_ASSERT(imageInfo);
1028 dstUsage = imageInfo->mFormat;
1030 const auto& dstFormat = dstUsage->format;
1031 if (!mContext->IsWebGL2() && dstFormat->d) {
1032 mContext->ErrorInvalidOperation(
1033 "Function may not be called on a texture of"
1034 " format %s.",
1035 dstFormat->name);
1036 return;
1040 ////////////////////////////////////
1041 // Get source info
1043 const webgl::DriverUnpackInfo* driverUnpackInfo;
1044 if (!dstUsage->IsUnpackValid(pi, &driverUnpackInfo)) {
1045 if (!fnValidateUnpackEnums()) return;
1046 mContext->ErrorInvalidOperation(
1047 "Mismatched internalFormat and format/type:"
1048 " 0x%04x and 0x%04x/0x%04x",
1049 respecFormat, pi.format, pi.type);
1050 return;
1053 if (!blob->Validate(mContext, pi)) return;
1055 ////////////////////////////////////
1056 // Do the thing!
1058 Maybe<webgl::ImageInfo> newImageInfo;
1059 bool isRespec = false;
1060 if (respecFormat) {
1061 // It's tempting to do allocation first, and TexSubImage second, but this is
1062 // generally slower.
1063 newImageInfo = Some(webgl::ImageInfo{dstUsage, size.x, size.y, size.z});
1064 if (!blob->HasData()) {
1065 newImageInfo->mUninitializedSlices =
1066 Some(std::vector<bool>(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 const auto uninitializedSlices = Nothing();
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 const auto uninitializedSlices = Nothing();
1893 const webgl::ImageInfo newImageInfo{dstUsage, size.x, size.y, size.z,
1894 uninitializedSlices};
1895 *imageInfo = newImageInfo;
1896 InvalidateCaches();
1900 } // namespace mozilla