Merge mozilla-central to autoland on a CLOSED TREE
[gecko.git] / dom / canvas / WebGLTextureUpload.cpp
blob7a35712c34ab45b95a43704ca6ff16c885d11ce7
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "WebGLTexture.h"
8 #include <algorithm>
9 #include <limits>
11 #include "CanvasUtils.h"
12 #include "ClientWebGLContext.h"
13 #include "GLBlitHelper.h"
14 #include "GLContext.h"
15 #include "mozilla/Casting.h"
16 #include "mozilla/gfx/2D.h"
17 #include "mozilla/gfx/Logging.h"
18 #include "mozilla/dom/HTMLCanvasElement.h"
19 #include "mozilla/dom/HTMLVideoElement.h"
20 #include "mozilla/dom/ImageBitmap.h"
21 #include "mozilla/dom/ImageData.h"
22 #include "mozilla/dom/OffscreenCanvas.h"
23 #include "mozilla/MathAlgorithms.h"
24 #include "mozilla/Scoped.h"
25 #include "mozilla/ScopeExit.h"
26 #include "mozilla/StaticPrefs_webgl.h"
27 #include "mozilla/Unused.h"
28 #include "nsLayoutUtils.h"
29 #include "ScopedGLHelpers.h"
30 #include "TexUnpackBlob.h"
31 #include "WebGLBuffer.h"
32 #include "WebGLContext.h"
33 #include "WebGLContextUtils.h"
34 #include "WebGLFramebuffer.h"
35 #include "WebGLTexelConversions.h"
37 namespace mozilla {
38 namespace webgl {
40 Maybe<TexUnpackBlobDesc> FromImageBitmap(const GLenum target, Maybe<uvec3> size,
41 const dom::ImageBitmap& imageBitmap,
42 ErrorResult* const out_rv) {
43 if (imageBitmap.IsWriteOnly()) {
44 out_rv->Throw(NS_ERROR_DOM_SECURITY_ERR);
45 return {};
48 const auto cloneData = imageBitmap.ToCloneData();
49 if (!cloneData) {
50 return {};
53 const RefPtr<gfx::DataSourceSurface> surf = cloneData->mSurface;
54 const auto imageSize = *uvec2::FromSize(surf->GetSize());
55 if (!size) {
56 size.emplace(imageSize.x, imageSize.y, 1);
59 // WhatWG "HTML Living Standard" (30 October 2015):
60 // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned as
61 // non-premultiplied alpha values."
62 return Some(TexUnpackBlobDesc{target,
63 size.value(),
64 cloneData->mAlphaType,
65 {},
66 {},
67 Some(imageSize),
68 nullptr,
69 {},
70 surf,
71 {},
72 false});
75 static layers::SurfaceDescriptor Flatten(const layers::SurfaceDescriptor& sd) {
76 const auto sdType = sd.type();
77 if (sdType != layers::SurfaceDescriptor::TSurfaceDescriptorGPUVideo) {
78 return sd;
80 const auto& sdv = sd.get_SurfaceDescriptorGPUVideo();
81 const auto& sdvType = sdv.type();
82 if (sdvType !=
83 layers::SurfaceDescriptorGPUVideo::TSurfaceDescriptorRemoteDecoder) {
84 return sd;
87 const auto& sdrd = sdv.get_SurfaceDescriptorRemoteDecoder();
88 const auto& subdesc = sdrd.subdesc();
89 const auto& subdescType = subdesc.type();
90 switch (subdescType) {
91 case layers::RemoteDecoderVideoSubDescriptor::T__None:
92 case layers::RemoteDecoderVideoSubDescriptor::Tnull_t:
93 return sd;
95 case layers::RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorD3D10:
96 return subdesc.get_SurfaceDescriptorD3D10();
97 case layers::RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorDXGIYCbCr:
98 return subdesc.get_SurfaceDescriptorDXGIYCbCr();
99 case layers::RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorDMABuf:
100 return subdesc.get_SurfaceDescriptorDMABuf();
101 case layers::RemoteDecoderVideoSubDescriptor::
102 TSurfaceDescriptorMacIOSurface:
103 return subdesc.get_SurfaceDescriptorMacIOSurface();
104 case layers::RemoteDecoderVideoSubDescriptor::
105 TSurfaceDescriptorDcompSurface:
106 return subdesc.get_SurfaceDescriptorDcompSurface();
108 MOZ_CRASH("unreachable");
111 Maybe<webgl::TexUnpackBlobDesc> FromOffscreenCanvas(
112 const ClientWebGLContext& webgl, const GLenum target, Maybe<uvec3> size,
113 const dom::OffscreenCanvas& canvas, ErrorResult* const out_error) {
114 if (canvas.IsWriteOnly()) {
115 webgl.EnqueueWarning(
116 "OffscreenCanvas is write-only, thus cannot be uploaded.");
117 out_error->ThrowSecurityError(
118 "OffscreenCanvas is write-only, thus cannot be uploaded.");
119 return {};
122 // The canvas spec says that drawImage should draw the first frame of
123 // animated images. The webgl spec doesn't mention the issue, so we do the
124 // same as drawImage.
125 uint32_t flags = nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE;
126 auto sfer = nsLayoutUtils::SurfaceFromOffscreenCanvas(
127 const_cast<dom::OffscreenCanvas*>(&canvas), flags);
129 RefPtr<gfx::DataSourceSurface> dataSurf;
130 if (sfer.GetSourceSurface()) {
131 dataSurf = sfer.GetSourceSurface()->GetDataSurface();
134 if (!dataSurf) {
135 webgl.EnqueueWarning("Resource has no data (yet?). Uploading zeros.");
136 if (!size) {
137 size.emplace(0, 0, 1);
139 return Some(
140 TexUnpackBlobDesc{target, size.value(), gfxAlphaType::NonPremult});
143 // We checked this above before we requested the surface.
144 MOZ_RELEASE_ASSERT(!sfer.mIsWriteOnly);
146 uvec2 canvasSize = *uvec2::FromSize(dataSurf->GetSize());
147 if (!size) {
148 size.emplace(canvasSize.x, canvasSize.y, 1);
151 return Some(TexUnpackBlobDesc{target,
152 size.value(),
153 sfer.mAlphaType,
156 Some(canvasSize),
159 dataSurf});
162 Maybe<webgl::TexUnpackBlobDesc> FromDomElem(const ClientWebGLContext& webgl,
163 const GLenum target,
164 Maybe<uvec3> size,
165 const dom::Element& elem,
166 ErrorResult* const out_error) {
167 if (elem.IsHTMLElement(nsGkAtoms::canvas)) {
168 const dom::HTMLCanvasElement* srcCanvas =
169 static_cast<const dom::HTMLCanvasElement*>(&elem);
170 if (srcCanvas->IsWriteOnly()) {
171 out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
172 return {};
176 // The canvas spec says that drawImage should draw the first frame of
177 // animated images. The webgl spec doesn't mention the issue, so we do the
178 // same as drawImage.
179 uint32_t flags = nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE |
180 nsLayoutUtils::SFE_USE_ELEMENT_SIZE_IF_VECTOR |
181 nsLayoutUtils::SFE_EXACT_SIZE_SURFACE |
182 nsLayoutUtils::SFE_ALLOW_NON_PREMULT;
183 const auto& unpacking = webgl.State().mPixelUnpackState;
184 if (unpacking.colorspaceConversion == LOCAL_GL_NONE) {
185 flags |= nsLayoutUtils::SFE_NO_COLORSPACE_CONVERSION;
188 RefPtr<gfx::DrawTarget> idealDrawTarget = nullptr; // Don't care for now.
189 auto sfer = nsLayoutUtils::SurfaceFromElement(
190 const_cast<dom::Element*>(&elem), flags, idealDrawTarget);
192 //////
194 uvec2 elemSize;
196 const auto& layersImage = sfer.mLayersImage;
197 Maybe<layers::SurfaceDescriptor> sd;
198 if (layersImage) {
199 elemSize = *uvec2::FromSize(layersImage->GetSize());
201 sd = layersImage->GetDesc();
202 if (sd) {
203 sd = Some(Flatten(*sd));
205 if (!sd) {
206 NS_WARNING("No SurfaceDescriptor for layers::Image!");
210 RefPtr<gfx::DataSourceSurface> dataSurf;
211 if (!sd && sfer.GetSourceSurface()) {
212 const auto surf = sfer.GetSourceSurface();
213 elemSize = *uvec2::FromSize(surf->GetSize());
215 // WARNING: OSX can lose our MakeCurrent here.
216 dataSurf = surf->GetDataSurface();
219 //////
221 if (!size) {
222 size.emplace(elemSize.x, elemSize.y, 1);
225 ////
227 if (!sd && !dataSurf) {
228 webgl.EnqueueWarning("Resource has no data (yet?). Uploading zeros.");
229 if (!size) {
230 size.emplace(0, 0, 1);
232 return Some(
233 TexUnpackBlobDesc{target, size.value(), gfxAlphaType::NonPremult});
236 //////
238 // While it's counter-intuitive, the shape of the SFEResult API means that we
239 // should try to pull out a surface first, and then, if we do pull out a
240 // surface, check CORS/write-only/etc..
242 if (!sfer.mCORSUsed) {
243 auto& srcPrincipal = sfer.mPrincipal;
244 nsIPrincipal* dstPrincipal = webgl.PrincipalOrNull();
245 if (!dstPrincipal || !dstPrincipal->Subsumes(srcPrincipal)) {
246 webgl.EnqueueWarning("Cross-origin elements require CORS.");
247 out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
248 return {};
252 if (sfer.mIsWriteOnly) {
253 // mIsWriteOnly defaults to true, and so will be true even if SFE merely
254 // failed. Thus we must test mIsWriteOnly after successfully retrieving an
255 // Image or SourceSurface.
256 webgl.EnqueueWarning("Element is write-only, thus cannot be uploaded.");
257 out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
258 return {};
261 //////
262 // Ok, we're good!
264 return Some(TexUnpackBlobDesc{target,
265 size.value(),
266 sfer.mAlphaType,
269 Some(elemSize),
270 layersImage,
272 dataSurf});
275 } // namespace webgl
277 //////////////////////////////////////////////////////////////////////////////////////////
278 //////////////////////////////////////////////////////////////////////////////////////////
280 static bool ValidateTexImage(WebGLContext* webgl, WebGLTexture* texture,
281 TexImageTarget target, uint32_t level,
282 webgl::ImageInfo** const out_imageInfo) {
283 // Check level
284 if (level >= WebGLTexture::kMaxLevelCount) {
285 webgl->ErrorInvalidValue("`level` is too large.");
286 return false;
289 auto& imageInfo = texture->ImageInfoAt(target, level);
290 *out_imageInfo = &imageInfo;
291 return true;
294 // For *TexImage*
295 bool WebGLTexture::ValidateTexImageSpecification(
296 TexImageTarget target, uint32_t level, const uvec3& size,
297 webgl::ImageInfo** const out_imageInfo) {
298 if (mImmutable) {
299 mContext->ErrorInvalidOperation("Specified texture is immutable.");
300 return false;
303 // Do this early to validate `level`.
304 webgl::ImageInfo* imageInfo;
305 if (!ValidateTexImage(mContext, this, target, level, &imageInfo))
306 return false;
308 if (mTarget == LOCAL_GL_TEXTURE_CUBE_MAP && size.x != size.y) {
309 mContext->ErrorInvalidValue("Cube map images must be square.");
310 return false;
313 /* GLES 3.0.4, p133-134:
314 * GL_MAX_TEXTURE_SIZE is *not* the max allowed texture size. Rather, it is
315 * the max (width/height) size guaranteed not to generate an INVALID_VALUE for
316 * too-large dimensions. Sizes larger than GL_MAX_TEXTURE_SIZE *may or may
317 * not* result in an INVALID_VALUE, or possibly GL_OOM.
319 * However, we have needed to set our maximums lower in the past to prevent
320 * resource corruption. Therefore we have limits.maxTex2dSize, which is
321 * neither necessarily lower nor higher than MAX_TEXTURE_SIZE.
323 * Note that limits.maxTex2dSize must be >= than the advertized
324 * MAX_TEXTURE_SIZE. For simplicity, we advertize MAX_TEXTURE_SIZE as
325 * limits.maxTex2dSize.
328 uint32_t maxWidthHeight = 0;
329 uint32_t maxDepth = 0;
330 uint32_t maxLevel = 0;
332 const auto& limits = mContext->Limits();
333 MOZ_ASSERT(level <= 31);
334 switch (target.get()) {
335 case LOCAL_GL_TEXTURE_2D:
336 maxWidthHeight = limits.maxTex2dSize >> level;
337 maxDepth = 1;
338 maxLevel = CeilingLog2(limits.maxTex2dSize);
339 break;
341 case LOCAL_GL_TEXTURE_3D:
342 maxWidthHeight = limits.maxTex3dSize >> level;
343 maxDepth = maxWidthHeight;
344 maxLevel = CeilingLog2(limits.maxTex3dSize);
345 break;
347 case LOCAL_GL_TEXTURE_2D_ARRAY:
348 maxWidthHeight = limits.maxTex2dSize >> level;
349 // "The maximum number of layers for two-dimensional array textures
350 // (depth) must be at least MAX_ARRAY_TEXTURE_LAYERS for all levels."
351 maxDepth = limits.maxTexArrayLayers;
352 maxLevel = CeilingLog2(limits.maxTex2dSize);
353 break;
355 default: // cube maps
356 MOZ_ASSERT(IsCubeMap());
357 maxWidthHeight = limits.maxTexCubeSize >> level;
358 maxDepth = 1;
359 maxLevel = CeilingLog2(limits.maxTexCubeSize);
360 break;
363 if (level > maxLevel) {
364 mContext->ErrorInvalidValue("Requested level is not supported for target.");
365 return false;
368 if (size.x > maxWidthHeight || size.y > maxWidthHeight || size.z > maxDepth) {
369 mContext->ErrorInvalidValue("Requested size at this level is unsupported.");
370 return false;
374 /* GL ES Version 2.0.25 - 3.7.1 Texture Image Specification
375 * "If level is greater than zero, and either width or
376 * height is not a power-of-two, the error INVALID_VALUE is
377 * generated."
379 * This restriction does not apply to GL ES Version 3.0+.
381 bool requirePOT = (!mContext->IsWebGL2() && level != 0);
383 if (requirePOT) {
384 if (!IsPowerOfTwo(size.x) || !IsPowerOfTwo(size.y)) {
385 mContext->ErrorInvalidValue(
386 "For level > 0, width and height must be"
387 " powers of two.");
388 return false;
393 *out_imageInfo = imageInfo;
394 return true;
397 // For *TexSubImage*
398 bool WebGLTexture::ValidateTexImageSelection(
399 TexImageTarget target, uint32_t level, const uvec3& offset,
400 const uvec3& size, webgl::ImageInfo** const out_imageInfo) {
401 webgl::ImageInfo* imageInfo;
402 if (!ValidateTexImage(mContext, this, target, level, &imageInfo))
403 return false;
405 if (!imageInfo->IsDefined()) {
406 mContext->ErrorInvalidOperation(
407 "The specified TexImage has not yet been"
408 " specified.");
409 return false;
412 const auto totalX = CheckedUint32(offset.x) + size.x;
413 const auto totalY = CheckedUint32(offset.y) + size.y;
414 const auto totalZ = CheckedUint32(offset.z) + size.z;
416 if (!totalX.isValid() || totalX.value() > imageInfo->mWidth ||
417 !totalY.isValid() || totalY.value() > imageInfo->mHeight ||
418 !totalZ.isValid() || totalZ.value() > imageInfo->mDepth) {
419 mContext->ErrorInvalidValue(
420 "Offset+size must be <= the size of the existing"
421 " specified image.");
422 return false;
425 *out_imageInfo = imageInfo;
426 return true;
429 static bool ValidateCompressedTexUnpack(WebGLContext* webgl, const uvec3& size,
430 const webgl::FormatInfo* format,
431 size_t dataSize) {
432 auto compression = format->compression;
434 auto bytesPerBlock = compression->bytesPerBlock;
435 auto blockWidth = compression->blockWidth;
436 auto blockHeight = compression->blockHeight;
438 auto widthInBlocks = CheckedUint32(size.x) / blockWidth;
439 auto heightInBlocks = CheckedUint32(size.y) / blockHeight;
440 if (size.x % blockWidth) widthInBlocks += 1;
441 if (size.y % blockHeight) heightInBlocks += 1;
443 const CheckedUint32 blocksPerImage = widthInBlocks * heightInBlocks;
444 const CheckedUint32 bytesPerImage = bytesPerBlock * blocksPerImage;
445 const CheckedUint32 bytesNeeded = bytesPerImage * size.z;
447 if (!bytesNeeded.isValid()) {
448 webgl->ErrorOutOfMemory("Overflow while computing the needed buffer size.");
449 return false;
452 if (dataSize != bytesNeeded.value()) {
453 webgl->ErrorInvalidValue(
454 "Provided buffer's size must match expected size."
455 " (needs %u, has %zu)",
456 bytesNeeded.value(), dataSize);
457 return false;
460 return true;
463 static bool DoChannelsMatchForCopyTexImage(const webgl::FormatInfo* srcFormat,
464 const webgl::FormatInfo* dstFormat) {
465 // GLES 3.0.4 p140 Table 3.16 "Valid CopyTexImage source
466 // framebuffer/destination texture base internal format combinations."
468 switch (srcFormat->unsizedFormat) {
469 case webgl::UnsizedFormat::RGBA:
470 switch (dstFormat->unsizedFormat) {
471 case webgl::UnsizedFormat::A:
472 case webgl::UnsizedFormat::L:
473 case webgl::UnsizedFormat::LA:
474 case webgl::UnsizedFormat::R:
475 case webgl::UnsizedFormat::RG:
476 case webgl::UnsizedFormat::RGB:
477 case webgl::UnsizedFormat::RGBA:
478 return true;
479 default:
480 return false;
483 case webgl::UnsizedFormat::RGB:
484 switch (dstFormat->unsizedFormat) {
485 case webgl::UnsizedFormat::L:
486 case webgl::UnsizedFormat::R:
487 case webgl::UnsizedFormat::RG:
488 case webgl::UnsizedFormat::RGB:
489 return true;
490 default:
491 return false;
494 case webgl::UnsizedFormat::RG:
495 switch (dstFormat->unsizedFormat) {
496 case webgl::UnsizedFormat::L:
497 case webgl::UnsizedFormat::R:
498 case webgl::UnsizedFormat::RG:
499 return true;
500 default:
501 return false;
504 case webgl::UnsizedFormat::R:
505 switch (dstFormat->unsizedFormat) {
506 case webgl::UnsizedFormat::L:
507 case webgl::UnsizedFormat::R:
508 return true;
509 default:
510 return false;
513 default:
514 return false;
518 static bool EnsureImageDataInitializedForUpload(
519 WebGLTexture* tex, TexImageTarget target, uint32_t level,
520 const uvec3& offset, const uvec3& size, webgl::ImageInfo* imageInfo,
521 bool* const out_expectsInit = nullptr) {
522 if (out_expectsInit) {
523 *out_expectsInit = false;
525 if (!imageInfo->mUninitializedSlices) return true;
527 if (size.x == imageInfo->mWidth && size.y == imageInfo->mHeight) {
528 bool expectsInit = false;
529 auto& isSliceUninit = *imageInfo->mUninitializedSlices;
530 for (const auto z : IntegerRange(offset.z, offset.z + size.z)) {
531 if (!isSliceUninit[z]) continue;
532 expectsInit = true;
533 isSliceUninit[z] = false;
535 if (out_expectsInit) {
536 *out_expectsInit = expectsInit;
539 if (!expectsInit) return true;
541 bool hasUninitialized = false;
542 for (const auto z : IntegerRange(imageInfo->mDepth)) {
543 hasUninitialized |= isSliceUninit[z];
545 if (!hasUninitialized) {
546 imageInfo->mUninitializedSlices = Nothing();
548 return true;
551 WebGLContext* webgl = tex->mContext;
552 webgl->GenerateWarning(
553 "Texture has not been initialized prior to a"
554 " partial upload, forcing the browser to clear it."
555 " This may be slow.");
556 if (!tex->EnsureImageDataInitialized(target, level)) {
557 MOZ_ASSERT(false, "Unexpected failure to init image data.");
558 return false;
561 return true;
564 //////////////////////////////////////////////////////////////////////////////////////////
565 //////////////////////////////////////////////////////////////////////////////////////////
566 // Actual calls
568 static inline GLenum DoTexStorage(gl::GLContext* gl, TexTarget target,
569 GLsizei levels, GLenum sizedFormat,
570 GLsizei width, GLsizei height,
571 GLsizei depth) {
572 gl::GLContext::LocalErrorScope errorScope(*gl);
574 switch (target.get()) {
575 case LOCAL_GL_TEXTURE_2D:
576 case LOCAL_GL_TEXTURE_CUBE_MAP:
577 MOZ_ASSERT(depth == 1);
578 gl->fTexStorage2D(target.get(), levels, sizedFormat, width, height);
579 break;
581 case LOCAL_GL_TEXTURE_3D:
582 case LOCAL_GL_TEXTURE_2D_ARRAY:
583 gl->fTexStorage3D(target.get(), levels, sizedFormat, width, height,
584 depth);
585 break;
587 default:
588 MOZ_CRASH("GFX: bad target");
591 return errorScope.GetError();
594 bool IsTarget3D(TexImageTarget target) {
595 switch (target.get()) {
596 case LOCAL_GL_TEXTURE_2D:
597 case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
598 case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
599 case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
600 case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
601 case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
602 case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
603 return false;
605 case LOCAL_GL_TEXTURE_3D:
606 case LOCAL_GL_TEXTURE_2D_ARRAY:
607 return true;
609 default:
610 MOZ_CRASH("GFX: bad target");
614 GLenum DoTexImage(gl::GLContext* gl, TexImageTarget target, GLint level,
615 const webgl::DriverUnpackInfo* dui, GLsizei width,
616 GLsizei height, GLsizei depth, const void* data) {
617 const GLint border = 0;
619 gl::GLContext::LocalErrorScope errorScope(*gl);
621 if (IsTarget3D(target)) {
622 gl->fTexImage3D(target.get(), level, dui->internalFormat, width, height,
623 depth, border, dui->unpackFormat, dui->unpackType, data);
624 } else {
625 MOZ_ASSERT(depth == 1);
626 gl->fTexImage2D(target.get(), level, dui->internalFormat, width, height,
627 border, dui->unpackFormat, dui->unpackType, data);
630 return errorScope.GetError();
633 GLenum DoTexSubImage(gl::GLContext* gl, TexImageTarget target, GLint level,
634 GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width,
635 GLsizei height, GLsizei depth,
636 const webgl::PackingInfo& pi, const void* data) {
637 gl::GLContext::LocalErrorScope errorScope(*gl);
639 if (IsTarget3D(target)) {
640 gl->fTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset, width,
641 height, depth, pi.format, pi.type, data);
642 } else {
643 MOZ_ASSERT(zOffset == 0);
644 MOZ_ASSERT(depth == 1);
645 gl->fTexSubImage2D(target.get(), level, xOffset, yOffset, width, height,
646 pi.format, pi.type, data);
649 return errorScope.GetError();
652 static inline GLenum DoCompressedTexImage(gl::GLContext* gl,
653 TexImageTarget target, GLint level,
654 GLenum internalFormat, GLsizei width,
655 GLsizei height, GLsizei depth,
656 GLsizei dataSize, const void* data) {
657 const GLint border = 0;
659 gl::GLContext::LocalErrorScope errorScope(*gl);
661 if (IsTarget3D(target)) {
662 gl->fCompressedTexImage3D(target.get(), level, internalFormat, width,
663 height, depth, border, dataSize, data);
664 } else {
665 MOZ_ASSERT(depth == 1);
666 gl->fCompressedTexImage2D(target.get(), level, internalFormat, width,
667 height, border, dataSize, data);
670 return errorScope.GetError();
673 GLenum DoCompressedTexSubImage(gl::GLContext* gl, TexImageTarget target,
674 GLint level, GLint xOffset, GLint yOffset,
675 GLint zOffset, GLsizei width, GLsizei height,
676 GLsizei depth, GLenum sizedUnpackFormat,
677 GLsizei dataSize, const void* data) {
678 gl::GLContext::LocalErrorScope errorScope(*gl);
680 if (IsTarget3D(target)) {
681 gl->fCompressedTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset,
682 width, height, depth, sizedUnpackFormat,
683 dataSize, data);
684 } else {
685 MOZ_ASSERT(zOffset == 0);
686 MOZ_ASSERT(depth == 1);
687 gl->fCompressedTexSubImage2D(target.get(), level, xOffset, yOffset, width,
688 height, sizedUnpackFormat, dataSize, data);
691 return errorScope.GetError();
694 static inline GLenum DoCopyTexSubImage(gl::GLContext* gl, TexImageTarget target,
695 GLint level, GLint xOffset,
696 GLint yOffset, GLint zOffset, GLint x,
697 GLint y, GLsizei width, GLsizei height) {
698 gl::GLContext::LocalErrorScope errorScope(*gl);
700 if (IsTarget3D(target)) {
701 gl->fCopyTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset, x, y,
702 width, height);
703 } else {
704 MOZ_ASSERT(zOffset == 0);
705 gl->fCopyTexSubImage2D(target.get(), level, xOffset, yOffset, x, y, width,
706 height);
709 return errorScope.GetError();
712 //////////////////////////////////////////////////////////////////////////////////////////
713 //////////////////////////////////////////////////////////////////////////////////////////
714 // Actual (mostly generic) function implementations
716 static bool ValidateCompressedTexImageRestrictions(
717 const WebGLContext* webgl, TexImageTarget target, uint32_t level,
718 const webgl::FormatInfo* format, const uvec3& size) {
719 const auto fnIsDimValid_S3TC = [&](const char* const name, uint32_t levelSize,
720 uint32_t blockSize) {
721 const auto impliedBaseSize = levelSize << level;
722 if (impliedBaseSize % blockSize == 0) return true;
723 webgl->ErrorInvalidOperation(
724 "%u is never a valid %s for level %u, because it implies a base mip %s "
725 "of %u."
726 " %s requires that base mip levels have a %s multiple of %u.",
727 levelSize, name, level, name, impliedBaseSize, format->name, name,
728 blockSize);
729 return false;
732 switch (format->compression->family) {
733 case webgl::CompressionFamily::ASTC:
734 if (target == LOCAL_GL_TEXTURE_3D &&
735 !webgl->gl->IsExtensionSupported(
736 gl::GLContext::KHR_texture_compression_astc_hdr)) {
737 webgl->ErrorInvalidOperation("TEXTURE_3D requires ASTC's hdr profile.");
738 return false;
740 break;
742 case webgl::CompressionFamily::PVRTC:
743 if (!IsPowerOfTwo(size.x) || !IsPowerOfTwo(size.y)) {
744 webgl->ErrorInvalidValue("%s requires power-of-two width and height.",
745 format->name);
746 return false;
748 break;
750 case webgl::CompressionFamily::BPTC:
751 case webgl::CompressionFamily::RGTC:
752 case webgl::CompressionFamily::S3TC:
753 if (!fnIsDimValid_S3TC("width", size.x,
754 format->compression->blockWidth) ||
755 !fnIsDimValid_S3TC("height", size.y,
756 format->compression->blockHeight)) {
757 return false;
759 break;
761 // Default: There are no restrictions on CompressedTexImage.
762 case webgl::CompressionFamily::ES3:
763 case webgl::CompressionFamily::ETC1:
764 break;
767 return true;
770 static bool ValidateFormatAndSize(const WebGLContext* webgl,
771 TexImageTarget target,
772 const webgl::FormatInfo* format,
773 const uvec3& size) {
774 // Check if texture size will likely be rejected by the driver and give a more
775 // meaningful error message.
776 auto baseImageSize = CheckedInt<uint64_t>(format->estimatedBytesPerPixel) *
777 (uint32_t)size.x * (uint32_t)size.y * (uint32_t)size.z;
778 if (target == LOCAL_GL_TEXTURE_CUBE_MAP) {
779 baseImageSize *= 6;
781 if (!baseImageSize.isValid() ||
782 baseImageSize.value() >
783 (uint64_t)StaticPrefs::webgl_max_size_per_texture_mib() *
784 (1024 * 1024)) {
785 webgl->ErrorOutOfMemory(
786 "Texture size too large; base image mebibytes > "
787 "webgl.max-size-per-texture-mib");
788 return false;
791 // GLES 3.0.4 p127:
792 // "Textures with a base internal format of DEPTH_COMPONENT or DEPTH_STENCIL
793 // are supported by texture image specification commands only if `target` is
794 // TEXTURE_2D, TEXTURE_2D_ARRAY, or TEXTURE_CUBE_MAP. Using these formats in
795 // conjunction with any other `target` will result in an INVALID_OPERATION
796 // error."
797 const bool ok = [&]() {
798 if (bool(format->d) & (target == LOCAL_GL_TEXTURE_3D)) return false;
800 if (format->compression) {
801 switch (format->compression->family) {
802 case webgl::CompressionFamily::ES3:
803 case webgl::CompressionFamily::S3TC:
804 if (target == LOCAL_GL_TEXTURE_3D) return false;
805 break;
807 case webgl::CompressionFamily::ETC1:
808 case webgl::CompressionFamily::PVRTC:
809 case webgl::CompressionFamily::RGTC:
810 if (target == LOCAL_GL_TEXTURE_3D ||
811 target == LOCAL_GL_TEXTURE_2D_ARRAY) {
812 return false;
814 break;
815 default:
816 break;
819 return true;
820 }();
821 if (!ok) {
822 webgl->ErrorInvalidOperation("Format %s cannot be used with target %s.",
823 format->name, GetEnumName(target.get()));
824 return false;
827 return true;
830 void WebGLTexture::TexStorage(TexTarget target, uint32_t levels,
831 GLenum sizedFormat, const uvec3& size) {
832 // Check levels
833 if (levels < 1) {
834 mContext->ErrorInvalidValue("`levels` must be >= 1.");
835 return;
838 if (!size.x || !size.y || !size.z) {
839 mContext->ErrorInvalidValue("Dimensions must be non-zero.");
840 return;
843 const TexImageTarget testTarget =
844 IsCubeMap() ? LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X : target.get();
845 webgl::ImageInfo* baseImageInfo;
846 if (!ValidateTexImageSpecification(testTarget, 0, size, &baseImageInfo)) {
847 return;
849 MOZ_ALWAYS_TRUE(baseImageInfo);
851 auto dstUsage = mContext->mFormatUsage->GetSizedTexUsage(sizedFormat);
852 if (!dstUsage) {
853 mContext->ErrorInvalidEnumInfo("internalformat", sizedFormat);
854 return;
856 auto dstFormat = dstUsage->format;
858 if (!ValidateFormatAndSize(mContext, testTarget, dstFormat, size)) return;
860 if (dstFormat->compression) {
861 if (!ValidateCompressedTexImageRestrictions(mContext, testTarget, 0,
862 dstFormat, size)) {
863 return;
867 ////////////////////////////////////
869 const bool levelsOk = [&]() {
870 // Right-shift is only defined for bits-1, which is too large anyways.
871 const auto lastLevel = uint32_t(levels - 1);
872 if (lastLevel > 31) return false;
874 const auto lastLevelWidth = uint32_t(size.x) >> lastLevel;
875 const auto lastLevelHeight = uint32_t(size.y) >> lastLevel;
877 // If these are all zero, then some earlier level was the final 1x1(x1)
878 // level.
879 bool ok = lastLevelWidth || lastLevelHeight;
880 if (target == LOCAL_GL_TEXTURE_3D) {
881 const auto lastLevelDepth = uint32_t(size.z) >> lastLevel;
882 ok |= bool(lastLevelDepth);
884 return ok;
885 }();
886 if (!levelsOk) {
887 mContext->ErrorInvalidOperation(
888 "Too many levels requested for the given"
889 " dimensions. (levels: %u, width: %u, height: %u,"
890 " depth: %u)",
891 levels, size.x, size.y, size.z);
892 return;
895 ////////////////////////////////////
896 // Do the thing!
898 GLenum error = DoTexStorage(mContext->gl, target.get(), levels, sizedFormat,
899 size.x, size.y, size.z);
901 mContext->OnDataAllocCall();
903 if (error == LOCAL_GL_OUT_OF_MEMORY) {
904 mContext->ErrorOutOfMemory("Ran out of memory during texture allocation.");
905 Truncate();
906 return;
908 if (error) {
909 mContext->GenerateError(error, "Unexpected error from driver.");
910 const nsPrintfCString call(
911 "DoTexStorage(0x%04x, %i, 0x%04x, %i,%i,%i) -> 0x%04x", target.get(),
912 levels, sizedFormat, size.x, size.y, size.z, error);
913 gfxCriticalError() << "Unexpected error from driver: "
914 << call.BeginReading();
915 return;
918 ////////////////////////////////////
919 // Update our specification data.
921 auto uninitializedSlices = Some(std::vector<bool>(size.z, true));
922 const webgl::ImageInfo newInfo{dstUsage, size.x, size.y, size.z,
923 std::move(uninitializedSlices)};
926 const auto base_level = mBaseMipmapLevel;
927 mBaseMipmapLevel = 0;
929 ImageInfoAtFace(0, 0) = newInfo;
930 PopulateMipChain(levels - 1);
932 mBaseMipmapLevel = base_level;
935 mImmutable = true;
936 mImmutableLevelCount = AutoAssertCast(levels);
937 ClampLevelBaseAndMax();
940 ////////////////////////////////////////
941 // Tex(Sub)Image
943 // TexSubImage iff `!respectFormat`
944 void WebGLTexture::TexImage(uint32_t level, GLenum respecFormat,
945 const uvec3& offset, const webgl::PackingInfo& pi,
946 const webgl::TexUnpackBlobDesc& src) {
947 Maybe<RawBuffer<>> cpuDataView;
948 if (src.cpuData) {
949 cpuDataView = Some(RawBuffer<>{src.cpuData->Data()});
951 const auto srcViewDesc = webgl::TexUnpackBlobDesc{src.imageTarget,
952 src.size,
953 src.srcAlphaType,
954 std::move(cpuDataView),
955 src.pboOffset,
956 src.structuredSrcSize,
957 src.image,
958 src.sd,
959 src.dataSurf,
960 src.unpacking,
961 src.applyUnpackTransforms};
963 const auto blob = webgl::TexUnpackBlob::Create(srcViewDesc);
964 if (!blob) {
965 MOZ_ASSERT(false);
966 return;
969 const auto imageTarget = blob->mDesc.imageTarget;
970 auto size = blob->mDesc.size;
972 if (!IsTarget3D(imageTarget)) {
973 size.z = 1;
976 ////////////////////////////////////
977 // Get dest info
979 const auto& fua = mContext->mFormatUsage;
980 const auto fnValidateUnpackEnums = [&]() {
981 if (!fua->AreUnpackEnumsValid(pi.format, pi.type)) {
982 mContext->ErrorInvalidEnum("Invalid unpack format/type: %s/%s",
983 EnumString(pi.format).c_str(),
984 EnumString(pi.type).c_str());
985 return false;
987 return true;
990 webgl::ImageInfo* imageInfo;
991 const webgl::FormatUsageInfo* dstUsage;
992 if (respecFormat) {
993 if (!ValidateTexImageSpecification(imageTarget, level, size, &imageInfo))
994 return;
995 MOZ_ASSERT(imageInfo);
997 if (!fua->IsInternalFormatEnumValid(respecFormat)) {
998 mContext->ErrorInvalidValue("Invalid internalformat: 0x%04x",
999 respecFormat);
1000 return;
1003 dstUsage = fua->GetSizedTexUsage(respecFormat);
1004 if (!dstUsage) {
1005 if (respecFormat != pi.format) {
1006 /* GL ES Version 3.0.4 - 3.8.3 Texture Image Specification
1007 * "Specifying a combination of values for format, type, and
1008 * internalformat that is not listed as a valid combination
1009 * in tables 3.2 or 3.3 generates the error INVALID_OPERATION."
1011 if (!fnValidateUnpackEnums()) return;
1012 mContext->ErrorInvalidOperation(
1013 "Unsized internalFormat must match"
1014 " unpack format.");
1015 return;
1018 dstUsage = fua->GetUnsizedTexUsage(pi);
1021 if (!dstUsage) {
1022 if (!fnValidateUnpackEnums()) return;
1023 mContext->ErrorInvalidOperation(
1024 "Invalid internalformat/format/type:"
1025 " 0x%04x/0x%04x/0x%04x",
1026 respecFormat, pi.format, pi.type);
1027 return;
1030 const auto& dstFormat = dstUsage->format;
1031 if (!ValidateFormatAndSize(mContext, imageTarget, dstFormat, size)) return;
1033 if (!mContext->IsWebGL2() && dstFormat->d) {
1034 if (imageTarget != LOCAL_GL_TEXTURE_2D || blob->HasData() || level != 0) {
1035 mContext->ErrorInvalidOperation(
1036 "With format %s, this function may only"
1037 " be called with target=TEXTURE_2D,"
1038 " data=null, and level=0.",
1039 dstFormat->name);
1040 return;
1043 } else {
1044 if (!ValidateTexImageSelection(imageTarget, level, offset, size,
1045 &imageInfo)) {
1046 return;
1048 MOZ_ASSERT(imageInfo);
1049 dstUsage = imageInfo->mFormat;
1051 const auto& dstFormat = dstUsage->format;
1052 if (!mContext->IsWebGL2() && dstFormat->d) {
1053 mContext->ErrorInvalidOperation(
1054 "Function may not be called on a texture of"
1055 " format %s.",
1056 dstFormat->name);
1057 return;
1061 ////////////////////////////////////
1062 // Get source info
1064 const webgl::DriverUnpackInfo* driverUnpackInfo;
1065 if (!dstUsage->IsUnpackValid(pi, &driverUnpackInfo)) {
1066 if (!fnValidateUnpackEnums()) return;
1067 mContext->ErrorInvalidOperation(
1068 "Mismatched internalFormat and format/type:"
1069 " 0x%04x and 0x%04x/0x%04x",
1070 respecFormat, pi.format, pi.type);
1071 return;
1074 if (!blob->Validate(mContext, pi)) return;
1076 ////////////////////////////////////
1077 // Do the thing!
1079 Maybe<webgl::ImageInfo> newImageInfo;
1080 bool isRespec = false;
1081 if (respecFormat) {
1082 // It's tempting to do allocation first, and TexSubImage second, but this is
1083 // generally slower.
1084 newImageInfo = Some(webgl::ImageInfo{dstUsage, size.x, size.y, size.z});
1085 if (!blob->HasData()) {
1086 newImageInfo->mUninitializedSlices =
1087 Some(std::vector<bool>(size.z, true));
1090 isRespec = (imageInfo->mWidth != newImageInfo->mWidth ||
1091 imageInfo->mHeight != newImageInfo->mHeight ||
1092 imageInfo->mDepth != newImageInfo->mDepth ||
1093 imageInfo->mFormat != newImageInfo->mFormat);
1094 } else {
1095 if (!blob->HasData()) {
1096 mContext->ErrorInvalidValue("`source` cannot be null.");
1097 return;
1099 if (!EnsureImageDataInitializedForUpload(this, imageTarget, level, offset,
1100 size, imageInfo)) {
1101 return;
1105 webgl::PixelPackingState{}.AssertCurrentUnpack(*mContext->gl,
1106 mContext->IsWebGL2());
1108 blob->mDesc.unpacking.ApplyUnpack(*mContext->gl, mContext->IsWebGL2(), size);
1109 const auto revertUnpacking = MakeScopeExit([&]() {
1110 webgl::PixelPackingState{}.ApplyUnpack(*mContext->gl, mContext->IsWebGL2(),
1111 size);
1114 const bool isSubImage = !respecFormat;
1115 GLenum glError = 0;
1116 if (!blob->TexOrSubImage(isSubImage, isRespec, this, level, driverUnpackInfo,
1117 offset.x, offset.y, offset.z, pi, &glError)) {
1118 return;
1121 if (glError == LOCAL_GL_OUT_OF_MEMORY) {
1122 mContext->ErrorOutOfMemory("Driver ran out of memory during upload.");
1123 Truncate();
1124 return;
1127 if (glError) {
1128 const auto enumStr = EnumString(glError);
1129 const nsPrintfCString dui(
1130 "Unexpected error %s during upload. (dui: %x/%x/%x)", enumStr.c_str(),
1131 driverUnpackInfo->internalFormat, driverUnpackInfo->unpackFormat,
1132 driverUnpackInfo->unpackType);
1133 mContext->ErrorInvalidOperation("%s", dui.BeginReading());
1134 gfxCriticalError() << mContext->FuncName() << ": " << dui.BeginReading();
1135 return;
1138 ////////////////////////////////////
1139 // Update our specification data?
1141 if (respecFormat) {
1142 mContext->OnDataAllocCall();
1143 *imageInfo = *newImageInfo;
1144 InvalidateCaches();
1148 ////////////////////////////////////////
1149 // CompressedTex(Sub)Image
1151 static inline bool IsSubImageBlockAligned(
1152 const webgl::CompressedFormatInfo* compression,
1153 const webgl::ImageInfo* imageInfo, GLint xOffset, GLint yOffset,
1154 uint32_t width, uint32_t height) {
1155 if (xOffset % compression->blockWidth != 0 ||
1156 yOffset % compression->blockHeight != 0) {
1157 return false;
1160 if (width % compression->blockWidth != 0 &&
1161 xOffset + width != imageInfo->mWidth)
1162 return false;
1164 if (height % compression->blockHeight != 0 &&
1165 yOffset + height != imageInfo->mHeight)
1166 return false;
1168 return true;
1171 // CompressedTexSubImage iff `sub`
1172 void WebGLTexture::CompressedTexImage(bool sub, GLenum imageTarget,
1173 uint32_t level, GLenum formatEnum,
1174 const uvec3& offset, const uvec3& size,
1175 const Range<const uint8_t>& src,
1176 const uint32_t pboImageSize,
1177 const Maybe<uint64_t>& pboOffset) {
1178 auto imageSize = pboImageSize;
1179 if (pboOffset) {
1180 const auto& buffer =
1181 mContext->ValidateBufferSelection(LOCAL_GL_PIXEL_UNPACK_BUFFER);
1182 if (!buffer) return;
1183 auto availBytes = buffer->ByteLength();
1184 if (*pboOffset > availBytes) {
1185 mContext->GenerateError(
1186 LOCAL_GL_INVALID_OPERATION,
1187 "`offset` (%llu) must be <= PIXEL_UNPACK_BUFFER size (%llu).",
1188 *pboOffset, availBytes);
1189 return;
1191 availBytes -= *pboOffset;
1192 if (availBytes < pboImageSize) {
1193 mContext->GenerateError(
1194 LOCAL_GL_INVALID_OPERATION,
1195 "PIXEL_UNPACK_BUFFER size minus `offset` (%llu) too small for"
1196 " `pboImageSize` (%u).",
1197 availBytes, pboImageSize);
1198 return;
1200 } else {
1201 if (mContext->mBoundPixelUnpackBuffer) {
1202 mContext->GenerateError(LOCAL_GL_INVALID_OPERATION,
1203 "PIXEL_UNPACK_BUFFER is non-null.");
1204 return;
1206 imageSize = src.length();
1209 // -
1211 const auto usage = mContext->mFormatUsage->GetSizedTexUsage(formatEnum);
1212 if (!usage || !usage->format->compression) {
1213 mContext->ErrorInvalidEnumArg("format", formatEnum);
1214 return;
1217 webgl::ImageInfo* imageInfo;
1218 if (!sub) {
1219 if (!ValidateTexImageSpecification(imageTarget, level, size, &imageInfo)) {
1220 return;
1222 MOZ_ASSERT(imageInfo);
1224 if (!ValidateFormatAndSize(mContext, imageTarget, usage->format, size))
1225 return;
1226 if (!ValidateCompressedTexImageRestrictions(mContext, imageTarget, level,
1227 usage->format, size)) {
1228 return;
1230 } else {
1231 if (!ValidateTexImageSelection(imageTarget, level, offset, size,
1232 &imageInfo))
1233 return;
1234 MOZ_ASSERT(imageInfo);
1236 const auto dstUsage = imageInfo->mFormat;
1237 if (usage != dstUsage) {
1238 mContext->ErrorInvalidOperation(
1239 "`format` must match the format of the"
1240 " existing texture image.");
1241 return;
1244 const auto& format = usage->format;
1245 switch (format->compression->family) {
1246 // Forbidden:
1247 case webgl::CompressionFamily::ETC1:
1248 mContext->ErrorInvalidOperation(
1249 "Format does not allow sub-image"
1250 " updates.");
1251 return;
1253 // Block-aligned:
1254 case webgl::CompressionFamily::ES3: // Yes, the ES3 formats don't match
1255 // the ES3
1256 case webgl::CompressionFamily::S3TC: // default behavior.
1257 case webgl::CompressionFamily::BPTC:
1258 case webgl::CompressionFamily::RGTC:
1259 if (!IsSubImageBlockAligned(format->compression, imageInfo, offset.x,
1260 offset.y, size.x, size.y)) {
1261 mContext->ErrorInvalidOperation(
1262 "Format requires block-aligned sub-image"
1263 " updates.");
1264 return;
1266 break;
1268 // Full-only: (The ES3 default)
1269 case webgl::CompressionFamily::ASTC:
1270 case webgl::CompressionFamily::PVRTC:
1271 if (offset.x || offset.y || size.x != imageInfo->mWidth ||
1272 size.y != imageInfo->mHeight) {
1273 mContext->ErrorInvalidOperation(
1274 "Format does not allow partial sub-image"
1275 " updates.");
1276 return;
1278 break;
1282 switch (usage->format->compression->family) {
1283 case webgl::CompressionFamily::BPTC:
1284 case webgl::CompressionFamily::RGTC:
1285 if (level == 0) {
1286 if (size.x % 4 != 0 || size.y % 4 != 0) {
1287 mContext->ErrorInvalidOperation(
1288 "For level == 0, width and height must be multiples of 4.");
1289 return;
1292 break;
1294 default:
1295 break;
1298 if (!ValidateCompressedTexUnpack(mContext, size, usage->format, imageSize))
1299 return;
1301 ////////////////////////////////////
1302 // Do the thing!
1304 if (sub) {
1305 if (!EnsureImageDataInitializedForUpload(this, imageTarget, level, offset,
1306 size, imageInfo)) {
1307 return;
1311 const ScopedLazyBind bindPBO(mContext->gl, LOCAL_GL_PIXEL_UNPACK_BUFFER,
1312 mContext->mBoundPixelUnpackBuffer);
1313 GLenum error;
1314 const void* ptr;
1315 if (pboOffset) {
1316 ptr = reinterpret_cast<const void*>(*pboOffset);
1317 } else {
1318 ptr = reinterpret_cast<const void*>(src.begin().get());
1321 if (!sub) {
1322 error = DoCompressedTexImage(mContext->gl, imageTarget, level, formatEnum,
1323 size.x, size.y, size.z, imageSize, ptr);
1324 } else {
1325 error = DoCompressedTexSubImage(mContext->gl, imageTarget, level, offset.x,
1326 offset.y, offset.z, size.x, size.y, size.z,
1327 formatEnum, imageSize, ptr);
1329 if (error == LOCAL_GL_OUT_OF_MEMORY) {
1330 mContext->ErrorOutOfMemory("Ran out of memory during upload.");
1331 Truncate();
1332 return;
1334 if (error) {
1335 mContext->GenerateError(error, "Unexpected error from driver.");
1336 nsCString call;
1337 if (!sub) {
1338 call = nsPrintfCString(
1339 "DoCompressedTexImage(0x%04x, %u, 0x%04x, %u,%u,%u, %u, %p)",
1340 imageTarget, level, formatEnum, size.x, size.y, size.z, imageSize,
1341 ptr);
1342 } else {
1343 call = nsPrintfCString(
1344 "DoCompressedTexSubImage(0x%04x, %u, %u,%u,%u, %u,%u,%u, 0x%04x, %u, "
1345 "%p)",
1346 imageTarget, level, offset.x, offset.y, offset.z, size.x, size.y,
1347 size.z, formatEnum, imageSize, ptr);
1349 gfxCriticalError() << "Unexpected error " << gfx::hexa(error)
1350 << " from driver: " << call.BeginReading();
1351 return;
1354 ////////////////////////////////////
1355 // Update our specification data?
1357 if (!sub) {
1358 const auto uninitializedSlices = Nothing();
1359 const webgl::ImageInfo newImageInfo{usage, size.x, size.y, size.z,
1360 uninitializedSlices};
1361 *imageInfo = newImageInfo;
1362 InvalidateCaches();
1366 ////////////////////////////////////////
1367 // CopyTex(Sub)Image
1369 static bool ValidateCopyTexImageFormats(WebGLContext* webgl,
1370 const webgl::FormatInfo* srcFormat,
1371 const webgl::FormatInfo* dstFormat) {
1372 MOZ_ASSERT(!srcFormat->compression);
1373 if (dstFormat->compression) {
1374 webgl->ErrorInvalidEnum(
1375 "Specified destination must not have a compressed"
1376 " format.");
1377 return false;
1380 if (dstFormat->effectiveFormat == webgl::EffectiveFormat::RGB9_E5) {
1381 webgl->ErrorInvalidOperation(
1382 "RGB9_E5 is an invalid destination for"
1383 " CopyTex(Sub)Image. (GLES 3.0.4 p145)");
1384 return false;
1387 if (!DoChannelsMatchForCopyTexImage(srcFormat, dstFormat)) {
1388 webgl->ErrorInvalidOperation(
1389 "Destination channels must be compatible with"
1390 " source channels. (GLES 3.0.4 p140 Table 3.16)");
1391 return false;
1394 return true;
1397 ////////////////////////////////////////////////////////////////////////////////
1399 class ScopedCopyTexImageSource {
1400 WebGLContext* const mWebGL;
1401 GLuint mRB;
1402 GLuint mFB;
1404 public:
1405 ScopedCopyTexImageSource(WebGLContext* webgl, uint32_t srcWidth,
1406 uint32_t srcHeight,
1407 const webgl::FormatInfo* srcFormat,
1408 const webgl::FormatUsageInfo* dstUsage);
1409 ~ScopedCopyTexImageSource();
1412 ScopedCopyTexImageSource::ScopedCopyTexImageSource(
1413 WebGLContext* webgl, uint32_t srcWidth, uint32_t srcHeight,
1414 const webgl::FormatInfo* srcFormat, const webgl::FormatUsageInfo* dstUsage)
1415 : mWebGL(webgl), mRB(0), mFB(0) {
1416 switch (dstUsage->format->unsizedFormat) {
1417 case webgl::UnsizedFormat::L:
1418 case webgl::UnsizedFormat::A:
1419 case webgl::UnsizedFormat::LA:
1420 webgl->GenerateWarning(
1421 "Copying to a LUMINANCE, ALPHA, or LUMINANCE_ALPHA"
1422 " is deprecated, and has severely reduced performance"
1423 " on some platforms.");
1424 break;
1426 default:
1427 MOZ_ASSERT(!dstUsage->textureSwizzleRGBA);
1428 return;
1431 if (!dstUsage->textureSwizzleRGBA) return;
1433 gl::GLContext* gl = webgl->gl;
1435 GLenum sizedFormat;
1437 switch (srcFormat->componentType) {
1438 case webgl::ComponentType::NormUInt:
1439 sizedFormat = LOCAL_GL_RGBA8;
1440 break;
1442 case webgl::ComponentType::Float:
1443 if (webgl->IsExtensionEnabled(
1444 WebGLExtensionID::WEBGL_color_buffer_float)) {
1445 sizedFormat = LOCAL_GL_RGBA32F;
1446 webgl->WarnIfImplicit(WebGLExtensionID::WEBGL_color_buffer_float);
1447 break;
1450 if (webgl->IsExtensionEnabled(
1451 WebGLExtensionID::EXT_color_buffer_half_float)) {
1452 sizedFormat = LOCAL_GL_RGBA16F;
1453 webgl->WarnIfImplicit(WebGLExtensionID::EXT_color_buffer_half_float);
1454 break;
1456 MOZ_CRASH("GFX: Should be able to request CopyTexImage from Float.");
1458 default:
1459 MOZ_CRASH("GFX: Should be able to request CopyTexImage from this type.");
1462 gl::ScopedTexture scopedTex(gl);
1463 gl::ScopedBindTexture scopedBindTex(gl, scopedTex.Texture(),
1464 LOCAL_GL_TEXTURE_2D);
1466 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER,
1467 LOCAL_GL_NEAREST);
1468 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER,
1469 LOCAL_GL_NEAREST);
1471 GLint blitSwizzle[4] = {LOCAL_GL_ZERO};
1472 switch (dstUsage->format->unsizedFormat) {
1473 case webgl::UnsizedFormat::L:
1474 blitSwizzle[0] = LOCAL_GL_RED;
1475 break;
1477 case webgl::UnsizedFormat::A:
1478 blitSwizzle[0] = LOCAL_GL_ALPHA;
1479 break;
1481 case webgl::UnsizedFormat::LA:
1482 blitSwizzle[0] = LOCAL_GL_RED;
1483 blitSwizzle[1] = LOCAL_GL_ALPHA;
1484 break;
1486 default:
1487 MOZ_CRASH("GFX: Unhandled unsizedFormat.");
1490 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_R,
1491 blitSwizzle[0]);
1492 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_G,
1493 blitSwizzle[1]);
1494 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_B,
1495 blitSwizzle[2]);
1496 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_A,
1497 blitSwizzle[3]);
1499 gl->fCopyTexImage2D(LOCAL_GL_TEXTURE_2D, 0, sizedFormat, 0, 0, srcWidth,
1500 srcHeight, 0);
1502 // Now create the swizzled FB we'll be exposing.
1504 GLuint rgbaRB = 0;
1505 GLuint rgbaFB = 0;
1507 gl->fGenRenderbuffers(1, &rgbaRB);
1508 gl::ScopedBindRenderbuffer scopedRB(gl, rgbaRB);
1509 gl->fRenderbufferStorage(LOCAL_GL_RENDERBUFFER, sizedFormat, srcWidth,
1510 srcHeight);
1512 gl->fGenFramebuffers(1, &rgbaFB);
1513 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, rgbaFB);
1514 gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER,
1515 LOCAL_GL_COLOR_ATTACHMENT0,
1516 LOCAL_GL_RENDERBUFFER, rgbaRB);
1518 const GLenum status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
1519 if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
1520 MOZ_CRASH("GFX: Temp framebuffer is not complete.");
1524 // Draw-blit rgbaTex into rgbaFB.
1525 const gfx::IntSize srcSize(srcWidth, srcHeight);
1527 const gl::ScopedBindFramebuffer bindFB(gl, rgbaFB);
1528 gl->BlitHelper()->DrawBlitTextureToFramebuffer(scopedTex.Texture(), srcSize,
1529 srcSize);
1532 // Leave RB and FB alive, and FB bound.
1533 mRB = rgbaRB;
1534 mFB = rgbaFB;
1537 template <typename T>
1538 static inline GLenum ToGLHandle(const T& obj) {
1539 return (obj ? obj->mGLName : 0);
1542 ScopedCopyTexImageSource::~ScopedCopyTexImageSource() {
1543 if (!mFB) {
1544 MOZ_ASSERT(!mRB);
1545 return;
1547 MOZ_ASSERT(mRB);
1549 gl::GLContext* gl = mWebGL->gl;
1551 // If we're swizzling, it's because we're on a GL core (3.2+) profile, which
1552 // has split framebuffer support.
1553 gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER,
1554 ToGLHandle(mWebGL->mBoundDrawFramebuffer));
1555 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER,
1556 ToGLHandle(mWebGL->mBoundReadFramebuffer));
1558 gl->fDeleteFramebuffers(1, &mFB);
1559 gl->fDeleteRenderbuffers(1, &mRB);
1562 ////////////////////////////////////////////////////////////////////////////////
1564 static bool GetUnsizedFormatForCopy(GLenum internalFormat,
1565 webgl::UnsizedFormat* const out) {
1566 switch (internalFormat) {
1567 case LOCAL_GL_RED:
1568 *out = webgl::UnsizedFormat::R;
1569 break;
1570 case LOCAL_GL_RG:
1571 *out = webgl::UnsizedFormat::RG;
1572 break;
1573 case LOCAL_GL_RGB:
1574 *out = webgl::UnsizedFormat::RGB;
1575 break;
1576 case LOCAL_GL_RGBA:
1577 *out = webgl::UnsizedFormat::RGBA;
1578 break;
1579 case LOCAL_GL_LUMINANCE:
1580 *out = webgl::UnsizedFormat::L;
1581 break;
1582 case LOCAL_GL_ALPHA:
1583 *out = webgl::UnsizedFormat::A;
1584 break;
1585 case LOCAL_GL_LUMINANCE_ALPHA:
1586 *out = webgl::UnsizedFormat::LA;
1587 break;
1589 default:
1590 return false;
1593 return true;
1596 static const webgl::FormatUsageInfo* ValidateCopyDestUsage(
1597 WebGLContext* webgl, const webgl::FormatInfo* srcFormat,
1598 GLenum internalFormat) {
1599 const auto& fua = webgl->mFormatUsage;
1601 switch (internalFormat) {
1602 case LOCAL_GL_R8_SNORM:
1603 case LOCAL_GL_RG8_SNORM:
1604 case LOCAL_GL_RGB8_SNORM:
1605 case LOCAL_GL_RGBA8_SNORM:
1606 webgl->ErrorInvalidEnum("SNORM formats are invalid for CopyTexImage.");
1607 return nullptr;
1610 auto dstUsage = fua->GetSizedTexUsage(internalFormat);
1611 if (!dstUsage) {
1612 // Ok, maybe it's unsized.
1613 webgl::UnsizedFormat unsizedFormat;
1614 if (!GetUnsizedFormatForCopy(internalFormat, &unsizedFormat)) {
1615 webgl->ErrorInvalidEnumInfo("internalFormat", internalFormat);
1616 return nullptr;
1619 const auto dstFormat = srcFormat->GetCopyDecayFormat(unsizedFormat);
1620 if (dstFormat) {
1621 dstUsage = fua->GetUsage(dstFormat->effectiveFormat);
1623 if (!dstUsage) {
1624 webgl->ErrorInvalidOperation(
1625 "0x%04x is not a valid unsized format for"
1626 " source format %s.",
1627 internalFormat, srcFormat->name);
1628 return nullptr;
1631 return dstUsage;
1633 // Alright, it's sized.
1635 const auto dstFormat = dstUsage->format;
1637 if (dstFormat->componentType != srcFormat->componentType) {
1638 webgl->ErrorInvalidOperation(
1639 "For sized internalFormats, source and dest"
1640 " component types must match. (source: %s, dest:"
1641 " %s)",
1642 srcFormat->name, dstFormat->name);
1643 return nullptr;
1646 bool componentSizesMatch = true;
1647 if (dstFormat->r) {
1648 componentSizesMatch &= (dstFormat->r == srcFormat->r);
1650 if (dstFormat->g) {
1651 componentSizesMatch &= (dstFormat->g == srcFormat->g);
1653 if (dstFormat->b) {
1654 componentSizesMatch &= (dstFormat->b == srcFormat->b);
1656 if (dstFormat->a) {
1657 componentSizesMatch &= (dstFormat->a == srcFormat->a);
1660 if (!componentSizesMatch) {
1661 webgl->ErrorInvalidOperation(
1662 "For sized internalFormats, source and dest"
1663 " component sizes must match exactly. (source: %s,"
1664 " dest: %s)",
1665 srcFormat->name, dstFormat->name);
1666 return nullptr;
1669 return dstUsage;
1672 static bool ValidateCopyTexImageForFeedback(const WebGLContext& webgl,
1673 const WebGLTexture& tex,
1674 const uint32_t mipLevel,
1675 const uint32_t zLayer) {
1676 const auto& fb = webgl.BoundReadFb();
1677 if (fb) {
1678 MOZ_ASSERT(fb->ColorReadBuffer());
1679 const auto& attach = *fb->ColorReadBuffer();
1680 MOZ_ASSERT(attach.ZLayerCount() ==
1681 1); // Multiview invalid for copyTexImage.
1683 if (attach.Texture() == &tex && attach.Layer() == zLayer &&
1684 attach.MipLevel() == mipLevel) {
1685 // Note that the TexImageTargets *don't* have to match for this to be
1686 // undefined per GLES 3.0.4 p211, thus an INVALID_OP in WebGL.
1687 webgl.ErrorInvalidOperation(
1688 "Feedback loop detected, as this texture"
1689 " is already attached to READ_FRAMEBUFFER's"
1690 " READ_BUFFER-selected COLOR_ATTACHMENT%u.",
1691 attach.mAttachmentPoint);
1692 return false;
1695 return true;
1698 static bool DoCopyTexOrSubImage(WebGLContext* webgl, bool isSubImage,
1699 bool needsInit, WebGLTexture* const tex,
1700 const TexImageTarget target, GLint level,
1701 GLint xWithinSrc, GLint yWithinSrc,
1702 uint32_t srcTotalWidth, uint32_t srcTotalHeight,
1703 const webgl::FormatUsageInfo* srcUsage,
1704 GLint xOffset, GLint yOffset, GLint zOffset,
1705 uint32_t dstWidth, uint32_t dstHeight,
1706 const webgl::FormatUsageInfo* dstUsage) {
1707 const auto& gl = webgl->gl;
1709 ////
1711 int32_t readX, readY;
1712 int32_t writeX, writeY;
1713 int32_t rwWidth, rwHeight;
1714 if (!Intersect(srcTotalWidth, xWithinSrc, dstWidth, &readX, &writeX,
1715 &rwWidth) ||
1716 !Intersect(srcTotalHeight, yWithinSrc, dstHeight, &readY, &writeY,
1717 &rwHeight)) {
1718 webgl->ErrorOutOfMemory("Bad subrect selection.");
1719 return false;
1722 writeX += xOffset;
1723 writeY += yOffset;
1725 ////
1727 GLenum error = 0;
1728 nsCString errorText;
1729 do {
1730 const auto& idealUnpack = dstUsage->idealUnpack;
1731 const auto& pi = idealUnpack->ToPacking();
1733 UniqueBuffer zeros;
1734 const bool fullOverwrite =
1735 (uint32_t(rwWidth) == dstWidth && uint32_t(rwHeight) == dstHeight);
1736 if (needsInit && !fullOverwrite) {
1737 CheckedInt<size_t> byteCount = BytesPerPixel(pi);
1738 byteCount *= dstWidth;
1739 byteCount *= dstHeight;
1741 if (byteCount.isValid()) {
1742 zeros = UniqueBuffer::Take(calloc(1u, byteCount.value()));
1745 if (!zeros.get()) {
1746 webgl->ErrorOutOfMemory("Ran out of memory allocating zeros.");
1747 return false;
1751 if (!isSubImage || zeros) {
1752 webgl::PixelPackingState{}.AssertCurrentUnpack(*gl, webgl->IsWebGL2());
1754 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1);
1755 const auto revert = MakeScopeExit(
1756 [&]() { gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4); });
1757 if (!isSubImage) {
1758 error = DoTexImage(gl, target, level, idealUnpack, dstWidth, dstHeight,
1759 1, nullptr);
1760 if (error) {
1761 errorText = nsPrintfCString(
1762 "DoTexImage(0x%04x, %i, {0x%04x, 0x%04x, 0x%04x}, %u,%u,1) -> "
1763 "0x%04x",
1764 target.get(), level, idealUnpack->internalFormat,
1765 idealUnpack->unpackFormat, idealUnpack->unpackType, dstWidth,
1766 dstHeight, error);
1767 break;
1770 if (zeros) {
1771 error = DoTexSubImage(gl, target, level, xOffset, yOffset, zOffset,
1772 dstWidth, dstHeight, 1, pi, zeros.get());
1773 if (error) {
1774 errorText = nsPrintfCString(
1775 "DoTexSubImage(0x%04x, %i, %i,%i,%i, %u,%u,1, {0x%04x, 0x%04x}) "
1776 "-> "
1777 "0x%04x",
1778 target.get(), level, xOffset, yOffset, zOffset, dstWidth,
1779 dstHeight, idealUnpack->unpackFormat, idealUnpack->unpackType,
1780 error);
1781 break;
1786 if (!rwWidth || !rwHeight) {
1787 // There aren't any pixels to copy, so we're 'done'.
1788 return true;
1791 const auto& srcFormat = srcUsage->format;
1792 ScopedCopyTexImageSource maybeSwizzle(webgl, srcTotalWidth, srcTotalHeight,
1793 srcFormat, dstUsage);
1795 error = DoCopyTexSubImage(gl, target, level, writeX, writeY, zOffset, readX,
1796 readY, rwWidth, rwHeight);
1797 if (error) {
1798 errorText = nsPrintfCString(
1799 "DoCopyTexSubImage(0x%04x, %i, %i,%i,%i, %i,%i, %u,%u) -> 0x%04x",
1800 target.get(), level, writeX, writeY, zOffset, readX, readY, rwWidth,
1801 rwHeight, error);
1802 break;
1805 return true;
1806 } while (false);
1808 if (error == LOCAL_GL_OUT_OF_MEMORY) {
1809 webgl->ErrorOutOfMemory("Ran out of memory during texture copy.");
1810 tex->Truncate();
1811 return false;
1814 if (gl->IsANGLE() && error == LOCAL_GL_INVALID_OPERATION) {
1815 webgl->ErrorImplementationBug(
1816 "ANGLE is particular about CopyTexSubImage"
1817 " formats matching exactly.");
1818 return false;
1821 webgl->GenerateError(error, "Unexpected error from driver.");
1822 gfxCriticalError() << "Unexpected error from driver: "
1823 << errorText.BeginReading();
1824 return false;
1827 // CopyTexSubImage if `!respecFormat`
1828 void WebGLTexture::CopyTexImage(GLenum imageTarget, uint32_t level,
1829 GLenum respecFormat, const uvec3& dstOffset,
1830 const ivec2& srcOffset, const uvec2& size2) {
1831 ////////////////////////////////////
1832 // Get source info
1834 const webgl::FormatUsageInfo* srcUsage;
1835 uint32_t srcTotalWidth;
1836 uint32_t srcTotalHeight;
1837 if (!mContext->BindCurFBForColorRead(&srcUsage, &srcTotalWidth,
1838 &srcTotalHeight)) {
1839 return;
1841 const auto& srcFormat = srcUsage->format;
1843 if (!ValidateCopyTexImageForFeedback(*mContext, *this, level, dstOffset.z))
1844 return;
1846 const auto size = uvec3{size2.x, size2.y, 1};
1848 ////////////////////////////////////
1849 // Get dest info
1851 webgl::ImageInfo* imageInfo;
1852 const webgl::FormatUsageInfo* dstUsage;
1853 if (respecFormat) {
1854 if (!ValidateTexImageSpecification(imageTarget, level, size, &imageInfo))
1855 return;
1856 MOZ_ASSERT(imageInfo);
1858 dstUsage = ValidateCopyDestUsage(mContext, srcFormat, respecFormat);
1859 if (!dstUsage) return;
1861 if (!ValidateFormatAndSize(mContext, imageTarget, dstUsage->format, size))
1862 return;
1863 } else {
1864 if (!ValidateTexImageSelection(imageTarget, level, dstOffset, size,
1865 &imageInfo)) {
1866 return;
1868 MOZ_ASSERT(imageInfo);
1870 dstUsage = imageInfo->mFormat;
1871 MOZ_ASSERT(dstUsage);
1874 const auto& dstFormat = dstUsage->format;
1875 if (!mContext->IsWebGL2() && dstFormat->d) {
1876 mContext->ErrorInvalidOperation(
1877 "Function may not be called with format %s.", dstFormat->name);
1878 return;
1881 ////////////////////////////////////
1882 // Check that source and dest info are compatible
1884 if (!ValidateCopyTexImageFormats(mContext, srcFormat, dstFormat)) return;
1886 ////////////////////////////////////
1887 // Do the thing!
1889 const bool isSubImage = !respecFormat;
1890 bool expectsInit = true;
1891 if (isSubImage) {
1892 if (!EnsureImageDataInitializedForUpload(this, imageTarget, level,
1893 dstOffset, size, imageInfo,
1894 &expectsInit)) {
1895 return;
1899 if (!DoCopyTexOrSubImage(mContext, isSubImage, expectsInit, this, imageTarget,
1900 level, srcOffset.x, srcOffset.y, srcTotalWidth,
1901 srcTotalHeight, srcUsage, dstOffset.x, dstOffset.y,
1902 dstOffset.z, size.x, size.y, dstUsage)) {
1903 return;
1906 mContext->OnDataAllocCall();
1908 ////////////////////////////////////
1909 // Update our specification data?
1911 if (respecFormat) {
1912 const auto uninitializedSlices = Nothing();
1913 const webgl::ImageInfo newImageInfo{dstUsage, size.x, size.y, size.z,
1914 uninitializedSlices};
1915 *imageInfo = newImageInfo;
1916 InvalidateCaches();
1920 } // namespace mozilla