Bug 1700051: part 26) Correct typo in comment of `mozInlineSpellWordUtil::BuildSoftTe...
[gecko.git] / dom / canvas / WebGLTextureUpload.cpp
blobef43a3d95c2a379eea70787d2657a39182ff3a87
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/MathAlgorithms.h"
23 #include "mozilla/Scoped.h"
24 #include "mozilla/ScopeExit.h"
25 #include "mozilla/StaticPrefs_webgl.h"
26 #include "mozilla/Unused.h"
27 #include "nsLayoutUtils.h"
28 #include "ScopedGLHelpers.h"
29 #include "TexUnpackBlob.h"
30 #include "WebGLBuffer.h"
31 #include "WebGLContext.h"
32 #include "WebGLContextUtils.h"
33 #include "WebGLFramebuffer.h"
34 #include "WebGLTexelConversions.h"
36 namespace mozilla {
37 namespace webgl {
39 Maybe<TexUnpackBlobDesc> FromImageBitmap(const GLenum target, uvec3 size,
40 const dom::ImageBitmap& imageBitmap,
41 ErrorResult* const out_rv) {
42 if (imageBitmap.IsWriteOnly()) {
43 out_rv->Throw(NS_ERROR_DOM_SECURITY_ERR);
44 return {};
47 const auto cloneData = imageBitmap.ToCloneData();
48 if (!cloneData) {
49 return {};
52 const RefPtr<gfx::DataSourceSurface> surf = cloneData->mSurface;
53 const auto imageSize = *uvec2::FromSize(surf->GetSize());
55 if (!size.x) {
56 size.x = imageSize.x;
59 if (!size.y) {
60 size.y = imageSize.y;
63 // WhatWG "HTML Living Standard" (30 October 2015):
64 // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned as
65 // non-premultiplied alpha values."
66 return Some(TexUnpackBlobDesc{target,
67 size,
68 cloneData->mAlphaType,
69 {},
70 {},
71 imageSize,
72 nullptr,
73 {},
74 surf});
77 TexUnpackBlobDesc FromImageData(const GLenum target, uvec3 size,
78 const dom::ImageData& imageData,
79 dom::Uint8ClampedArray* const scopedArr) {
80 MOZ_RELEASE_ASSERT(scopedArr->Init(imageData.GetDataObject()));
81 scopedArr->ComputeState();
82 const size_t dataSize = scopedArr->Length();
83 const auto data = reinterpret_cast<uint8_t*>(scopedArr->Data());
85 const gfx::IntSize imageISize(imageData.Width(), imageData.Height());
86 const auto imageUSize = *uvec2::FromSize(imageISize);
87 const size_t stride = imageUSize.x * 4;
88 const gfx::SurfaceFormat surfFormat = gfx::SurfaceFormat::R8G8B8A8;
89 MOZ_ALWAYS_TRUE(dataSize == stride * imageUSize.y);
91 const RefPtr<gfx::DataSourceSurface> surf =
92 gfx::Factory::CreateWrappingDataSourceSurface(data, stride, imageISize,
93 surfFormat);
94 MOZ_ASSERT(surf);
96 ////
98 if (!size.x) {
99 size.x = imageUSize.x;
102 if (!size.y) {
103 size.y = imageUSize.y;
106 ////
108 // WhatWG "HTML Living Standard" (30 October 2015):
109 // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned as
110 // non-premultiplied alpha values."
111 return {target, size, gfxAlphaType::NonPremult, {}, {}, imageUSize, nullptr,
112 {}, surf};
115 static layers::SurfaceDescriptor Flatten(const layers::SurfaceDescriptor& sd) {
116 const auto sdType = sd.type();
117 if (sdType != layers::SurfaceDescriptor::TSurfaceDescriptorGPUVideo) {
118 return sd;
120 const auto& sdv = sd.get_SurfaceDescriptorGPUVideo();
121 const auto& sdvType = sdv.type();
122 if (sdvType !=
123 layers::SurfaceDescriptorGPUVideo::TSurfaceDescriptorRemoteDecoder) {
124 return sd;
127 const auto& sdrd = sdv.get_SurfaceDescriptorRemoteDecoder();
128 const auto& subdesc = sdrd.subdesc();
129 const auto& subdescType = subdesc.type();
130 switch (subdescType) {
131 case layers::RemoteDecoderVideoSubDescriptor::T__None:
132 case layers::RemoteDecoderVideoSubDescriptor::Tnull_t:
133 return sd;
135 case layers::RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorD3D10:
136 return subdesc.get_SurfaceDescriptorD3D10();
137 case layers::RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorDXGIYCbCr:
138 return subdesc.get_SurfaceDescriptorDXGIYCbCr();
139 case layers::RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorDMABuf:
140 return subdesc.get_SurfaceDescriptorDMABuf();
141 case layers::RemoteDecoderVideoSubDescriptor::
142 TSurfaceDescriptorMacIOSurface:
143 return subdesc.get_SurfaceDescriptorMacIOSurface();
145 MOZ_CRASH("unreachable");
148 Maybe<webgl::TexUnpackBlobDesc> FromDomElem(const ClientWebGLContext& webgl,
149 const GLenum target, uvec3 size,
150 const dom::Element& elem,
151 ErrorResult* const out_error) {
152 const auto& canvas = *webgl.GetCanvas();
154 if (elem.IsHTMLElement(nsGkAtoms::canvas)) {
155 const dom::HTMLCanvasElement* srcCanvas =
156 static_cast<const dom::HTMLCanvasElement*>(&elem);
157 if (srcCanvas->IsWriteOnly()) {
158 out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
159 return {};
163 // The canvas spec says that drawImage should draw the first frame of
164 // animated images. The webgl spec doesn't mention the issue, so we do the
165 // same as drawImage.
166 uint32_t flags = nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE |
167 nsLayoutUtils::SFE_WANT_IMAGE_SURFACE |
168 nsLayoutUtils::SFE_USE_ELEMENT_SIZE_IF_VECTOR |
169 nsLayoutUtils::SFE_ALLOW_NON_PREMULT;
170 const auto& unpacking = webgl.State().mPixelUnpackState;
171 if (unpacking.mColorspaceConversion == 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);
179 //////
181 uvec2 elemSize;
183 const auto& layersImage = sfer.mLayersImage;
184 Maybe<layers::SurfaceDescriptor> sd;
185 if (layersImage) {
186 elemSize = *uvec2::FromSize(layersImage->GetSize());
188 sd = layersImage->GetDesc();
189 if (sd) {
190 sd = Some(Flatten(*sd));
192 if (!sd) {
193 NS_WARNING("No SurfaceDescriptor for layers::Image!");
197 RefPtr<gfx::DataSourceSurface> dataSurf;
198 if (!sd && sfer.GetSourceSurface()) {
199 const auto surf = sfer.GetSourceSurface();
200 elemSize = *uvec2::FromSize(surf->GetSize());
202 // WARNING: OSX can lose our MakeCurrent here.
203 dataSurf = surf->GetDataSurface();
206 //////
208 if (!size.x) {
209 size.x = elemSize.x;
212 if (!size.y) {
213 size.y = elemSize.y;
216 ////
218 if (!sd && !dataSurf) {
219 webgl.EnqueueWarning("Resource has no data (yet?). Uploading zeros.");
220 return Some(TexUnpackBlobDesc{target, size, gfxAlphaType::NonPremult});
223 //////
225 // While it's counter-intuitive, the shape of the SFEResult API means that we
226 // should try to pull out a surface first, and then, if we do pull out a
227 // surface, check CORS/write-only/etc..
229 if (!sfer.mCORSUsed) {
230 auto& srcPrincipal = sfer.mPrincipal;
231 nsIPrincipal* dstPrincipal = canvas.NodePrincipal();
233 if (!dstPrincipal->Subsumes(srcPrincipal)) {
234 webgl.EnqueueWarning("Cross-origin elements require CORS.");
235 out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
236 return {};
240 if (sfer.mIsWriteOnly) {
241 // mIsWriteOnly defaults to true, and so will be true even if SFE merely
242 // failed. Thus we must test mIsWriteOnly after successfully retrieving an
243 // Image or SourceSurface.
244 webgl.EnqueueWarning("Element is write-only, thus cannot be uploaded.");
245 out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
246 return {};
249 //////
250 // Ok, we're good!
252 return Some(TexUnpackBlobDesc{target,
253 size,
254 sfer.mAlphaType,
257 elemSize,
258 layersImage,
260 dataSurf});
263 } // namespace webgl
265 //////////////////////////////////////////////////////////////////////////////////////////
266 //////////////////////////////////////////////////////////////////////////////////////////
268 static bool ValidateTexImage(WebGLContext* webgl, WebGLTexture* texture,
269 TexImageTarget target, uint32_t level,
270 webgl::ImageInfo** const out_imageInfo) {
271 // Check level
272 if (level >= WebGLTexture::kMaxLevelCount) {
273 webgl->ErrorInvalidValue("`level` is too large.");
274 return false;
277 auto& imageInfo = texture->ImageInfoAt(target, level);
278 *out_imageInfo = &imageInfo;
279 return true;
282 // For *TexImage*
283 bool WebGLTexture::ValidateTexImageSpecification(
284 TexImageTarget target, uint32_t level, const uvec3& size,
285 webgl::ImageInfo** const out_imageInfo) {
286 if (mImmutable) {
287 mContext->ErrorInvalidOperation("Specified texture is immutable.");
288 return false;
291 // Do this early to validate `level`.
292 webgl::ImageInfo* imageInfo;
293 if (!ValidateTexImage(mContext, this, target, level, &imageInfo))
294 return false;
296 if (mTarget == LOCAL_GL_TEXTURE_CUBE_MAP && size.x != size.y) {
297 mContext->ErrorInvalidValue("Cube map images must be square.");
298 return false;
301 /* GLES 3.0.4, p133-134:
302 * GL_MAX_TEXTURE_SIZE is *not* the max allowed texture size. Rather, it is
303 * the max (width/height) size guaranteed not to generate an INVALID_VALUE for
304 * too-large dimensions. Sizes larger than GL_MAX_TEXTURE_SIZE *may or may
305 * not* result in an INVALID_VALUE, or possibly GL_OOM.
307 * However, we have needed to set our maximums lower in the past to prevent
308 * resource corruption. Therefore we have limits.maxTex2dSize, which is
309 * neither necessarily lower nor higher than MAX_TEXTURE_SIZE.
311 * Note that limits.maxTex2dSize must be >= than the advertized
312 * MAX_TEXTURE_SIZE. For simplicity, we advertize MAX_TEXTURE_SIZE as
313 * limits.maxTex2dSize.
316 uint32_t maxWidthHeight = 0;
317 uint32_t maxDepth = 0;
318 uint32_t maxLevel = 0;
320 const auto& limits = mContext->Limits();
321 MOZ_ASSERT(level <= 31);
322 switch (target.get()) {
323 case LOCAL_GL_TEXTURE_2D:
324 maxWidthHeight = limits.maxTex2dSize >> level;
325 maxDepth = 1;
326 maxLevel = CeilingLog2(limits.maxTex2dSize);
327 break;
329 case LOCAL_GL_TEXTURE_3D:
330 maxWidthHeight = limits.maxTex3dSize >> level;
331 maxDepth = maxWidthHeight;
332 maxLevel = CeilingLog2(limits.maxTex3dSize);
333 break;
335 case LOCAL_GL_TEXTURE_2D_ARRAY:
336 maxWidthHeight = limits.maxTex2dSize >> level;
337 // "The maximum number of layers for two-dimensional array textures
338 // (depth) must be at least MAX_ARRAY_TEXTURE_LAYERS for all levels."
339 maxDepth = limits.maxTexArrayLayers;
340 maxLevel = CeilingLog2(limits.maxTex2dSize);
341 break;
343 default: // cube maps
344 MOZ_ASSERT(IsCubeMap());
345 maxWidthHeight = limits.maxTexCubeSize >> level;
346 maxDepth = 1;
347 maxLevel = CeilingLog2(limits.maxTexCubeSize);
348 break;
351 if (level > maxLevel) {
352 mContext->ErrorInvalidValue("Requested level is not supported for target.");
353 return false;
356 if (size.x > maxWidthHeight || size.y > maxWidthHeight || size.z > maxDepth) {
357 mContext->ErrorInvalidValue("Requested size at this level is unsupported.");
358 return false;
362 /* GL ES Version 2.0.25 - 3.7.1 Texture Image Specification
363 * "If level is greater than zero, and either width or
364 * height is not a power-of-two, the error INVALID_VALUE is
365 * generated."
367 * This restriction does not apply to GL ES Version 3.0+.
369 bool requirePOT = (!mContext->IsWebGL2() && level != 0);
371 if (requirePOT) {
372 if (!IsPowerOfTwo(size.x) || !IsPowerOfTwo(size.y)) {
373 mContext->ErrorInvalidValue(
374 "For level > 0, width and height must be"
375 " powers of two.");
376 return false;
381 *out_imageInfo = imageInfo;
382 return true;
385 // For *TexSubImage*
386 bool WebGLTexture::ValidateTexImageSelection(
387 TexImageTarget target, uint32_t level, const uvec3& offset,
388 const uvec3& size, webgl::ImageInfo** const out_imageInfo) {
389 webgl::ImageInfo* imageInfo;
390 if (!ValidateTexImage(mContext, this, target, level, &imageInfo))
391 return false;
393 if (!imageInfo->IsDefined()) {
394 mContext->ErrorInvalidOperation(
395 "The specified TexImage has not yet been"
396 " specified.");
397 return false;
400 const auto totalX = CheckedUint32(offset.x) + size.x;
401 const auto totalY = CheckedUint32(offset.y) + size.y;
402 const auto totalZ = CheckedUint32(offset.z) + size.z;
404 if (!totalX.isValid() || totalX.value() > imageInfo->mWidth ||
405 !totalY.isValid() || totalY.value() > imageInfo->mHeight ||
406 !totalZ.isValid() || totalZ.value() > imageInfo->mDepth) {
407 mContext->ErrorInvalidValue(
408 "Offset+size must be <= the size of the existing"
409 " specified image.");
410 return false;
413 *out_imageInfo = imageInfo;
414 return true;
417 static bool ValidateCompressedTexUnpack(WebGLContext* webgl, const uvec3& size,
418 const webgl::FormatInfo* format,
419 size_t dataSize) {
420 auto compression = format->compression;
422 auto bytesPerBlock = compression->bytesPerBlock;
423 auto blockWidth = compression->blockWidth;
424 auto blockHeight = compression->blockHeight;
426 auto widthInBlocks = CheckedUint32(size.x) / blockWidth;
427 auto heightInBlocks = CheckedUint32(size.y) / blockHeight;
428 if (size.x % blockWidth) widthInBlocks += 1;
429 if (size.y % blockHeight) heightInBlocks += 1;
431 const CheckedUint32 blocksPerImage = widthInBlocks * heightInBlocks;
432 const CheckedUint32 bytesPerImage = bytesPerBlock * blocksPerImage;
433 const CheckedUint32 bytesNeeded = bytesPerImage * size.z;
435 if (!bytesNeeded.isValid()) {
436 webgl->ErrorOutOfMemory("Overflow while computing the needed buffer size.");
437 return false;
440 if (dataSize != bytesNeeded.value()) {
441 webgl->ErrorInvalidValue(
442 "Provided buffer's size must match expected size."
443 " (needs %u, has %zu)",
444 bytesNeeded.value(), dataSize);
445 return false;
448 return true;
451 static bool DoChannelsMatchForCopyTexImage(const webgl::FormatInfo* srcFormat,
452 const webgl::FormatInfo* dstFormat) {
453 // GLES 3.0.4 p140 Table 3.16 "Valid CopyTexImage source
454 // framebuffer/destination texture base internal format combinations."
456 switch (srcFormat->unsizedFormat) {
457 case webgl::UnsizedFormat::RGBA:
458 switch (dstFormat->unsizedFormat) {
459 case webgl::UnsizedFormat::A:
460 case webgl::UnsizedFormat::L:
461 case webgl::UnsizedFormat::LA:
462 case webgl::UnsizedFormat::R:
463 case webgl::UnsizedFormat::RG:
464 case webgl::UnsizedFormat::RGB:
465 case webgl::UnsizedFormat::RGBA:
466 return true;
467 default:
468 return false;
471 case webgl::UnsizedFormat::RGB:
472 switch (dstFormat->unsizedFormat) {
473 case webgl::UnsizedFormat::L:
474 case webgl::UnsizedFormat::R:
475 case webgl::UnsizedFormat::RG:
476 case webgl::UnsizedFormat::RGB:
477 return true;
478 default:
479 return false;
482 case webgl::UnsizedFormat::RG:
483 switch (dstFormat->unsizedFormat) {
484 case webgl::UnsizedFormat::L:
485 case webgl::UnsizedFormat::R:
486 case webgl::UnsizedFormat::RG:
487 return true;
488 default:
489 return false;
492 case webgl::UnsizedFormat::R:
493 switch (dstFormat->unsizedFormat) {
494 case webgl::UnsizedFormat::L:
495 case webgl::UnsizedFormat::R:
496 return true;
497 default:
498 return false;
501 default:
502 return false;
506 static bool EnsureImageDataInitializedForUpload(
507 WebGLTexture* tex, TexImageTarget target, uint32_t level,
508 const uvec3& offset, const uvec3& size, webgl::ImageInfo* imageInfo,
509 bool* const out_expectsInit = nullptr) {
510 if (out_expectsInit) {
511 *out_expectsInit = false;
513 if (!imageInfo->mUninitializedSlices) return true;
515 if (size.x == imageInfo->mWidth && size.y == imageInfo->mHeight) {
516 bool expectsInit = false;
517 auto& isSliceUninit = *imageInfo->mUninitializedSlices;
518 for (const auto z : IntegerRange(offset.z, offset.z + size.z)) {
519 if (!isSliceUninit[z]) continue;
520 expectsInit = true;
521 isSliceUninit[z] = false;
523 if (out_expectsInit) {
524 *out_expectsInit = expectsInit;
527 if (!expectsInit) return true;
529 bool hasUninitialized = false;
530 for (const auto z : IntegerRange(imageInfo->mDepth)) {
531 hasUninitialized |= isSliceUninit[z];
533 if (!hasUninitialized) {
534 imageInfo->mUninitializedSlices = Nothing();
536 return true;
539 WebGLContext* webgl = tex->mContext;
540 webgl->GenerateWarning(
541 "Texture has not been initialized prior to a"
542 " partial upload, forcing the browser to clear it."
543 " This may be slow.");
544 if (!tex->EnsureImageDataInitialized(target, level)) {
545 MOZ_ASSERT(false, "Unexpected failure to init image data.");
546 return false;
549 return true;
552 //////////////////////////////////////////////////////////////////////////////////////////
553 //////////////////////////////////////////////////////////////////////////////////////////
554 // Actual calls
556 static inline GLenum DoTexStorage(gl::GLContext* gl, TexTarget target,
557 GLsizei levels, GLenum sizedFormat,
558 GLsizei width, GLsizei height,
559 GLsizei depth) {
560 gl::GLContext::LocalErrorScope errorScope(*gl);
562 switch (target.get()) {
563 case LOCAL_GL_TEXTURE_2D:
564 case LOCAL_GL_TEXTURE_CUBE_MAP:
565 MOZ_ASSERT(depth == 1);
566 gl->fTexStorage2D(target.get(), levels, sizedFormat, width, height);
567 break;
569 case LOCAL_GL_TEXTURE_3D:
570 case LOCAL_GL_TEXTURE_2D_ARRAY:
571 gl->fTexStorage3D(target.get(), levels, sizedFormat, width, height,
572 depth);
573 break;
575 default:
576 MOZ_CRASH("GFX: bad target");
579 return errorScope.GetError();
582 bool IsTarget3D(TexImageTarget target) {
583 switch (target.get()) {
584 case LOCAL_GL_TEXTURE_2D:
585 case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
586 case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
587 case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
588 case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
589 case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
590 case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
591 return false;
593 case LOCAL_GL_TEXTURE_3D:
594 case LOCAL_GL_TEXTURE_2D_ARRAY:
595 return true;
597 default:
598 MOZ_CRASH("GFX: bad target");
602 GLenum DoTexImage(gl::GLContext* gl, TexImageTarget target, GLint level,
603 const webgl::DriverUnpackInfo* dui, GLsizei width,
604 GLsizei height, GLsizei depth, const void* data) {
605 const GLint border = 0;
607 gl::GLContext::LocalErrorScope errorScope(*gl);
609 if (IsTarget3D(target)) {
610 gl->fTexImage3D(target.get(), level, dui->internalFormat, width, height,
611 depth, border, dui->unpackFormat, dui->unpackType, data);
612 } else {
613 MOZ_ASSERT(depth == 1);
614 gl->fTexImage2D(target.get(), level, dui->internalFormat, width, height,
615 border, dui->unpackFormat, dui->unpackType, data);
618 return errorScope.GetError();
621 GLenum DoTexSubImage(gl::GLContext* gl, TexImageTarget target, GLint level,
622 GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width,
623 GLsizei height, GLsizei depth,
624 const webgl::PackingInfo& pi, const void* data) {
625 gl::GLContext::LocalErrorScope errorScope(*gl);
627 if (IsTarget3D(target)) {
628 gl->fTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset, width,
629 height, depth, pi.format, pi.type, data);
630 } else {
631 MOZ_ASSERT(zOffset == 0);
632 MOZ_ASSERT(depth == 1);
633 gl->fTexSubImage2D(target.get(), level, xOffset, yOffset, width, height,
634 pi.format, pi.type, data);
637 return errorScope.GetError();
640 static inline GLenum DoCompressedTexImage(gl::GLContext* gl,
641 TexImageTarget target, GLint level,
642 GLenum internalFormat, GLsizei width,
643 GLsizei height, GLsizei depth,
644 GLsizei dataSize, const void* data) {
645 const GLint border = 0;
647 gl::GLContext::LocalErrorScope errorScope(*gl);
649 if (IsTarget3D(target)) {
650 gl->fCompressedTexImage3D(target.get(), level, internalFormat, width,
651 height, depth, border, dataSize, data);
652 } else {
653 MOZ_ASSERT(depth == 1);
654 gl->fCompressedTexImage2D(target.get(), level, internalFormat, width,
655 height, border, dataSize, data);
658 return errorScope.GetError();
661 GLenum DoCompressedTexSubImage(gl::GLContext* gl, TexImageTarget target,
662 GLint level, GLint xOffset, GLint yOffset,
663 GLint zOffset, GLsizei width, GLsizei height,
664 GLsizei depth, GLenum sizedUnpackFormat,
665 GLsizei dataSize, const void* data) {
666 gl::GLContext::LocalErrorScope errorScope(*gl);
668 if (IsTarget3D(target)) {
669 gl->fCompressedTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset,
670 width, height, depth, sizedUnpackFormat,
671 dataSize, data);
672 } else {
673 MOZ_ASSERT(zOffset == 0);
674 MOZ_ASSERT(depth == 1);
675 gl->fCompressedTexSubImage2D(target.get(), level, xOffset, yOffset, width,
676 height, sizedUnpackFormat, dataSize, data);
679 return errorScope.GetError();
682 static inline GLenum DoCopyTexSubImage(gl::GLContext* gl, TexImageTarget target,
683 GLint level, GLint xOffset,
684 GLint yOffset, GLint zOffset, GLint x,
685 GLint y, GLsizei width, GLsizei height) {
686 gl::GLContext::LocalErrorScope errorScope(*gl);
688 if (IsTarget3D(target)) {
689 gl->fCopyTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset, x, y,
690 width, height);
691 } else {
692 MOZ_ASSERT(zOffset == 0);
693 gl->fCopyTexSubImage2D(target.get(), level, xOffset, yOffset, x, y, width,
694 height);
697 return errorScope.GetError();
700 //////////////////////////////////////////////////////////////////////////////////////////
701 //////////////////////////////////////////////////////////////////////////////////////////
702 // Actual (mostly generic) function implementations
704 static bool ValidateCompressedTexImageRestrictions(
705 const WebGLContext* webgl, TexImageTarget target, uint32_t level,
706 const webgl::FormatInfo* format, const uvec3& size) {
707 const auto fnIsDimValid_S3TC = [level](uint32_t size, uint32_t blockSize) {
708 if (size % blockSize == 0) return true;
710 if (level == 0) return false;
712 return (size == 0 || size == 1 || size == 2);
715 switch (format->compression->family) {
716 case webgl::CompressionFamily::ASTC:
717 if (target == LOCAL_GL_TEXTURE_3D &&
718 !webgl->gl->IsExtensionSupported(
719 gl::GLContext::KHR_texture_compression_astc_hdr)) {
720 webgl->ErrorInvalidOperation("TEXTURE_3D requires ASTC's hdr profile.");
721 return false;
723 break;
725 case webgl::CompressionFamily::PVRTC:
726 if (!IsPowerOfTwo(size.x) || !IsPowerOfTwo(size.y)) {
727 webgl->ErrorInvalidValue("%s requires power-of-two width and height.",
728 format->name);
729 return false;
732 break;
734 case webgl::CompressionFamily::S3TC:
735 if (!fnIsDimValid_S3TC(size.x, format->compression->blockWidth) ||
736 !fnIsDimValid_S3TC(size.y, format->compression->blockHeight)) {
737 webgl->ErrorInvalidOperation(
738 "%s requires that width and height are"
739 " block-aligned, or, if level>0, equal to 0, 1,"
740 " or 2.",
741 format->name);
742 return false;
745 break;
747 // Default: There are no restrictions on CompressedTexImage.
748 default: // ETC1, ES3
749 break;
752 return true;
755 static bool ValidateTargetForFormat(const WebGLContext* webgl,
756 TexImageTarget target,
757 const webgl::FormatInfo* format) {
758 // GLES 3.0.4 p127:
759 // "Textures with a base internal format of DEPTH_COMPONENT or DEPTH_STENCIL
760 // are supported by texture image specification commands only if `target` is
761 // TEXTURE_2D, TEXTURE_2D_ARRAY, or TEXTURE_CUBE_MAP. Using these formats in
762 // conjunction with any other `target` will result in an INVALID_OPERATION
763 // error."
764 const bool ok = [&]() {
765 if (bool(format->d) & (target == LOCAL_GL_TEXTURE_3D)) return false;
767 if (format->compression) {
768 switch (format->compression->family) {
769 case webgl::CompressionFamily::ES3:
770 case webgl::CompressionFamily::S3TC:
771 if (target == LOCAL_GL_TEXTURE_3D) return false;
772 break;
774 case webgl::CompressionFamily::ETC1:
775 case webgl::CompressionFamily::PVRTC:
776 case webgl::CompressionFamily::RGTC:
777 if (target == LOCAL_GL_TEXTURE_3D ||
778 target == LOCAL_GL_TEXTURE_2D_ARRAY) {
779 return false;
781 break;
782 default:
783 break;
786 return true;
787 }();
788 if (!ok) {
789 webgl->ErrorInvalidOperation("Format %s cannot be used with target %s.",
790 format->name, GetEnumName(target.get()));
791 return false;
794 return true;
797 void WebGLTexture::TexStorage(TexTarget target, uint32_t levels,
798 GLenum sizedFormat, const uvec3& size) {
799 // Check levels
800 if (levels < 1) {
801 mContext->ErrorInvalidValue("`levels` must be >= 1.");
802 return;
805 if (!size.x || !size.y || !size.z) {
806 mContext->ErrorInvalidValue("Dimensions must be non-zero.");
807 return;
810 const TexImageTarget testTarget =
811 IsCubeMap() ? LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X : target.get();
812 webgl::ImageInfo* baseImageInfo;
813 if (!ValidateTexImageSpecification(testTarget, 0, size, &baseImageInfo)) {
814 return;
816 MOZ_ALWAYS_TRUE(baseImageInfo);
818 auto dstUsage = mContext->mFormatUsage->GetSizedTexUsage(sizedFormat);
819 if (!dstUsage) {
820 mContext->ErrorInvalidEnumInfo("internalformat", sizedFormat);
821 return;
823 auto dstFormat = dstUsage->format;
825 if (!ValidateTargetForFormat(mContext, testTarget, dstFormat)) return;
827 if (dstFormat->compression) {
828 if (!ValidateCompressedTexImageRestrictions(mContext, testTarget, 0,
829 dstFormat, size)) {
830 return;
834 ////////////////////////////////////
836 const bool levelsOk = [&]() {
837 // Right-shift is only defined for bits-1, which is too large anyways.
838 const auto lastLevel = uint32_t(levels - 1);
839 if (lastLevel > 31) return false;
841 const auto lastLevelWidth = uint32_t(size.x) >> lastLevel;
842 const auto lastLevelHeight = uint32_t(size.y) >> lastLevel;
844 // If these are all zero, then some earlier level was the final 1x1(x1)
845 // level.
846 bool ok = lastLevelWidth || lastLevelHeight;
847 if (target == LOCAL_GL_TEXTURE_3D) {
848 const auto lastLevelDepth = uint32_t(size.z) >> lastLevel;
849 ok |= bool(lastLevelDepth);
851 return ok;
852 }();
853 if (!levelsOk) {
854 mContext->ErrorInvalidOperation(
855 "Too many levels requested for the given"
856 " dimensions. (levels: %u, width: %u, height: %u,"
857 " depth: %u)",
858 levels, size.x, size.y, size.z);
859 return;
862 ////////////////////////////////////
863 // Do the thing!
865 GLenum error = DoTexStorage(mContext->gl, target.get(), levels, sizedFormat,
866 size.x, size.y, size.z);
868 mContext->OnDataAllocCall();
870 if (error == LOCAL_GL_OUT_OF_MEMORY) {
871 mContext->ErrorOutOfMemory("Ran out of memory during texture allocation.");
872 Truncate();
873 return;
875 if (error) {
876 mContext->GenerateError(error, "Unexpected error from driver.");
877 const nsPrintfCString call(
878 "DoTexStorage(0x%04x, %i, 0x%04x, %i,%i,%i) -> 0x%04x", target.get(),
879 levels, sizedFormat, size.x, size.y, size.z, error);
880 gfxCriticalError() << "Unexpected error from driver: "
881 << call.BeginReading();
882 return;
885 ////////////////////////////////////
886 // Update our specification data.
888 auto uninitializedSlices = Some(std::vector<bool>(size.z, true));
889 const webgl::ImageInfo newInfo{dstUsage, size.x, size.y, size.z,
890 std::move(uninitializedSlices)};
893 const auto base_level = mBaseMipmapLevel;
894 mBaseMipmapLevel = 0;
896 ImageInfoAtFace(0, 0) = newInfo;
897 PopulateMipChain(levels - 1);
899 mBaseMipmapLevel = base_level;
902 mImmutable = true;
903 mImmutableLevelCount = AutoAssertCast(levels);
904 ClampLevelBaseAndMax();
907 ////////////////////////////////////////
908 // Tex(Sub)Image
910 // TexSubImage iff `!respectFormat`
911 void WebGLTexture::TexImage(uint32_t level, GLenum respecFormat,
912 const uvec3& offset, const webgl::PackingInfo& pi,
913 const webgl::TexUnpackBlobDesc& src) {
914 Maybe<RawBuffer<>> cpuDataView;
915 if (src.cpuData) {
916 cpuDataView = Some(RawBuffer<>{src.cpuData->Data()});
918 const auto srcViewDesc = webgl::TexUnpackBlobDesc{
919 src.imageTarget, src.size, src.srcAlphaType, std::move(cpuDataView),
920 src.pboOffset, src.imageSize, src.image, src.sd,
921 src.dataSurf, src.unpacking};
922 const auto blob = webgl::TexUnpackBlob::Create(srcViewDesc);
923 if (!blob) {
924 MOZ_ASSERT(false);
925 return;
928 const auto imageTarget = blob->mDesc.imageTarget;
929 auto size = blob->mDesc.size;
931 if (!IsTarget3D(imageTarget)) {
932 size.z = 1;
935 ////////////////////////////////////
936 // Get dest info
938 const auto& fua = mContext->mFormatUsage;
939 const auto fnValidateUnpackEnums = [&]() {
940 if (!fua->AreUnpackEnumsValid(pi.format, pi.type)) {
941 mContext->ErrorInvalidEnum("Invalid unpack format/type: %s/%s",
942 EnumString(pi.format).c_str(),
943 EnumString(pi.type).c_str());
944 return false;
946 return true;
949 webgl::ImageInfo* imageInfo;
950 const webgl::FormatUsageInfo* dstUsage;
951 if (respecFormat) {
952 if (!ValidateTexImageSpecification(imageTarget, level, size, &imageInfo))
953 return;
954 MOZ_ASSERT(imageInfo);
956 if (!fua->IsInternalFormatEnumValid(respecFormat)) {
957 mContext->ErrorInvalidValue("Invalid internalformat: 0x%04x",
958 respecFormat);
959 return;
962 dstUsage = fua->GetSizedTexUsage(respecFormat);
963 if (!dstUsage) {
964 if (respecFormat != pi.format) {
965 /* GL ES Version 3.0.4 - 3.8.3 Texture Image Specification
966 * "Specifying a combination of values for format, type, and
967 * internalformat that is not listed as a valid combination
968 * in tables 3.2 or 3.3 generates the error INVALID_OPERATION."
970 if (!fnValidateUnpackEnums()) return;
971 mContext->ErrorInvalidOperation(
972 "Unsized internalFormat must match"
973 " unpack format.");
974 return;
977 dstUsage = fua->GetUnsizedTexUsage(pi);
980 if (!dstUsage) {
981 if (!fnValidateUnpackEnums()) return;
982 mContext->ErrorInvalidOperation(
983 "Invalid internalformat/format/type:"
984 " 0x%04x/0x%04x/0x%04x",
985 respecFormat, pi.format, pi.type);
986 return;
989 const auto& dstFormat = dstUsage->format;
990 if (!ValidateTargetForFormat(mContext, imageTarget, dstFormat)) return;
992 if (!mContext->IsWebGL2() && dstFormat->d) {
993 if (imageTarget != LOCAL_GL_TEXTURE_2D || blob->HasData() || level != 0) {
994 mContext->ErrorInvalidOperation(
995 "With format %s, this function may only"
996 " be called with target=TEXTURE_2D,"
997 " data=null, and level=0.",
998 dstFormat->name);
999 return;
1002 } else {
1003 if (!ValidateTexImageSelection(imageTarget, level, offset, size,
1004 &imageInfo)) {
1005 return;
1007 MOZ_ASSERT(imageInfo);
1008 dstUsage = imageInfo->mFormat;
1010 const auto& dstFormat = dstUsage->format;
1011 if (!mContext->IsWebGL2() && dstFormat->d) {
1012 mContext->ErrorInvalidOperation(
1013 "Function may not be called on a texture of"
1014 " format %s.",
1015 dstFormat->name);
1016 return;
1020 ////////////////////////////////////
1021 // Get source info
1023 const webgl::DriverUnpackInfo* driverUnpackInfo;
1024 if (!dstUsage->IsUnpackValid(pi, &driverUnpackInfo)) {
1025 if (!fnValidateUnpackEnums()) return;
1026 mContext->ErrorInvalidOperation(
1027 "Mismatched internalFormat and format/type:"
1028 " 0x%04x and 0x%04x/0x%04x",
1029 respecFormat, pi.format, pi.type);
1030 return;
1033 if (!blob->Validate(mContext, pi)) return;
1035 ////////////////////////////////////
1036 // Do the thing!
1038 Maybe<webgl::ImageInfo> newImageInfo;
1039 bool isRespec = false;
1040 if (respecFormat) {
1041 // It's tempting to do allocation first, and TexSubImage second, but this is
1042 // generally slower.
1043 newImageInfo = Some(webgl::ImageInfo{dstUsage, size.x, size.y, size.z});
1044 if (!blob->HasData()) {
1045 newImageInfo->mUninitializedSlices =
1046 Some(std::vector<bool>(size.z, true));
1049 isRespec = (imageInfo->mWidth != newImageInfo->mWidth ||
1050 imageInfo->mHeight != newImageInfo->mHeight ||
1051 imageInfo->mDepth != newImageInfo->mDepth ||
1052 imageInfo->mFormat != newImageInfo->mFormat);
1053 } else {
1054 if (!blob->HasData()) {
1055 mContext->ErrorInvalidValue("`source` cannot be null.");
1056 return;
1058 if (!EnsureImageDataInitializedForUpload(this, imageTarget, level, offset,
1059 size, imageInfo)) {
1060 return;
1064 WebGLPixelStore::AssertDefault(*mContext->gl, mContext->IsWebGL2());
1066 blob->mDesc.unpacking.Apply(*mContext->gl, mContext->IsWebGL2(), size);
1067 const auto revertUnpacking = MakeScopeExit([&]() {
1068 const WebGLPixelStore defaultUnpacking;
1069 defaultUnpacking.Apply(*mContext->gl, mContext->IsWebGL2(), size);
1072 const bool isSubImage = !respecFormat;
1073 GLenum glError = 0;
1074 if (!blob->TexOrSubImage(isSubImage, isRespec, this, level, driverUnpackInfo,
1075 offset.x, offset.y, offset.z, pi, &glError)) {
1076 return;
1079 if (glError == LOCAL_GL_OUT_OF_MEMORY) {
1080 mContext->ErrorOutOfMemory("Driver ran out of memory during upload.");
1081 Truncate();
1082 return;
1085 if (glError) {
1086 const auto enumStr = EnumString(glError);
1087 const nsPrintfCString dui(
1088 "Unexpected error %s during upload. (dui: %x/%x/%x)", enumStr.c_str(),
1089 driverUnpackInfo->internalFormat, driverUnpackInfo->unpackFormat,
1090 driverUnpackInfo->unpackType);
1091 mContext->ErrorInvalidOperation("%s", dui.BeginReading());
1092 gfxCriticalError() << mContext->FuncName() << ": " << dui.BeginReading();
1093 return;
1096 ////////////////////////////////////
1097 // Update our specification data?
1099 if (respecFormat) {
1100 mContext->OnDataAllocCall();
1101 *imageInfo = *newImageInfo;
1102 InvalidateCaches();
1106 ////////////////////////////////////////
1107 // CompressedTex(Sub)Image
1109 static inline bool IsSubImageBlockAligned(
1110 const webgl::CompressedFormatInfo* compression,
1111 const webgl::ImageInfo* imageInfo, GLint xOffset, GLint yOffset,
1112 uint32_t width, uint32_t height) {
1113 if (xOffset % compression->blockWidth != 0 ||
1114 yOffset % compression->blockHeight != 0) {
1115 return false;
1118 if (width % compression->blockWidth != 0 &&
1119 xOffset + width != imageInfo->mWidth)
1120 return false;
1122 if (height % compression->blockHeight != 0 &&
1123 yOffset + height != imageInfo->mHeight)
1124 return false;
1126 return true;
1129 // CompressedTexSubImage iff `sub`
1130 void WebGLTexture::CompressedTexImage(bool sub, GLenum imageTarget,
1131 uint32_t level, GLenum formatEnum,
1132 const uvec3& offset, const uvec3& size,
1133 const Range<const uint8_t>& src,
1134 const uint32_t pboImageSize,
1135 const Maybe<uint64_t>& pboOffset) {
1136 auto imageSize = pboImageSize;
1137 if (pboOffset) {
1138 const auto& buffer =
1139 mContext->ValidateBufferSelection(LOCAL_GL_PIXEL_UNPACK_BUFFER);
1140 if (!buffer) return;
1141 auto availBytes = buffer->ByteLength();
1142 if (*pboOffset > availBytes) {
1143 mContext->GenerateError(
1144 LOCAL_GL_INVALID_OPERATION,
1145 "`offset` (%llu) must be <= PIXEL_UNPACK_BUFFER size (%llu).",
1146 *pboOffset, availBytes);
1147 return;
1149 availBytes -= *pboOffset;
1150 if (availBytes < pboImageSize) {
1151 mContext->GenerateError(
1152 LOCAL_GL_INVALID_OPERATION,
1153 "PIXEL_UNPACK_BUFFER size minus `offset` (%llu) too small for"
1154 " `pboImageSize` (%u).",
1155 availBytes, pboImageSize);
1156 return;
1158 } else {
1159 if (mContext->mBoundPixelUnpackBuffer) {
1160 mContext->GenerateError(LOCAL_GL_INVALID_OPERATION,
1161 "PIXEL_UNPACK_BUFFER is non-null.");
1162 return;
1164 imageSize = src.length();
1167 // -
1169 const auto usage = mContext->mFormatUsage->GetSizedTexUsage(formatEnum);
1170 if (!usage || !usage->format->compression) {
1171 mContext->ErrorInvalidEnumArg("format", formatEnum);
1172 return;
1175 webgl::ImageInfo* imageInfo;
1176 if (!sub) {
1177 if (!ValidateTexImageSpecification(imageTarget, level, size, &imageInfo)) {
1178 return;
1180 MOZ_ASSERT(imageInfo);
1182 if (!ValidateTargetForFormat(mContext, imageTarget, usage->format)) return;
1183 if (!ValidateCompressedTexImageRestrictions(mContext, imageTarget, level,
1184 usage->format, size)) {
1185 return;
1187 } else {
1188 if (!ValidateTexImageSelection(imageTarget, level, offset, size,
1189 &imageInfo))
1190 return;
1191 MOZ_ASSERT(imageInfo);
1193 const auto dstUsage = imageInfo->mFormat;
1194 if (usage != dstUsage) {
1195 mContext->ErrorInvalidOperation(
1196 "`format` must match the format of the"
1197 " existing texture image.");
1198 return;
1201 const auto& format = usage->format;
1202 switch (format->compression->family) {
1203 // Forbidden:
1204 case webgl::CompressionFamily::ETC1:
1205 mContext->ErrorInvalidOperation(
1206 "Format does not allow sub-image"
1207 " updates.");
1208 return;
1210 // Block-aligned:
1211 case webgl::CompressionFamily::ES3: // Yes, the ES3 formats don't match
1212 // the ES3
1213 case webgl::CompressionFamily::S3TC: // default behavior.
1214 case webgl::CompressionFamily::BPTC:
1215 case webgl::CompressionFamily::RGTC:
1216 if (!IsSubImageBlockAligned(format->compression, imageInfo, offset.x,
1217 offset.y, size.x, size.y)) {
1218 mContext->ErrorInvalidOperation(
1219 "Format requires block-aligned sub-image"
1220 " updates.");
1221 return;
1223 break;
1225 // Full-only: (The ES3 default)
1226 case webgl::CompressionFamily::ASTC:
1227 case webgl::CompressionFamily::PVRTC:
1228 if (offset.x || offset.y || size.x != imageInfo->mWidth ||
1229 size.y != imageInfo->mHeight) {
1230 mContext->ErrorInvalidOperation(
1231 "Format does not allow partial sub-image"
1232 " updates.");
1233 return;
1235 break;
1239 switch (usage->format->compression->family) {
1240 case webgl::CompressionFamily::BPTC:
1241 case webgl::CompressionFamily::RGTC:
1242 if (level == 0) {
1243 if (size.x % 4 != 0 || size.y % 4 != 0) {
1244 mContext->ErrorInvalidOperation(
1245 "For level == 0, width and height must be multiples of 4.");
1246 return;
1249 break;
1251 default:
1252 break;
1255 if (!ValidateCompressedTexUnpack(mContext, size, usage->format, imageSize))
1256 return;
1258 ////////////////////////////////////
1259 // Do the thing!
1261 if (sub) {
1262 if (!EnsureImageDataInitializedForUpload(this, imageTarget, level, offset,
1263 size, imageInfo)) {
1264 return;
1268 const ScopedLazyBind bindPBO(mContext->gl, LOCAL_GL_PIXEL_UNPACK_BUFFER,
1269 mContext->mBoundPixelUnpackBuffer);
1270 GLenum error;
1271 const void* ptr;
1272 if (pboOffset) {
1273 ptr = reinterpret_cast<const void*>(*pboOffset);
1274 } else {
1275 ptr = reinterpret_cast<const void*>(src.begin().get());
1278 if (!sub) {
1279 error = DoCompressedTexImage(mContext->gl, imageTarget, level, formatEnum,
1280 size.x, size.y, size.z, imageSize, ptr);
1281 } else {
1282 error = DoCompressedTexSubImage(mContext->gl, imageTarget, level, offset.x,
1283 offset.y, offset.z, size.x, size.y, size.z,
1284 formatEnum, imageSize, ptr);
1286 if (error == LOCAL_GL_OUT_OF_MEMORY) {
1287 mContext->ErrorOutOfMemory("Ran out of memory during upload.");
1288 Truncate();
1289 return;
1291 if (error) {
1292 mContext->GenerateError(error, "Unexpected error from driver.");
1293 nsCString call;
1294 if (!sub) {
1295 call = nsPrintfCString(
1296 "DoCompressedTexImage(0x%04x, %u, 0x%04x, %u,%u,%u, %u, %p)",
1297 imageTarget, level, formatEnum, size.x, size.y, size.z, imageSize,
1298 ptr);
1299 } else {
1300 call = nsPrintfCString(
1301 "DoCompressedTexSubImage(0x%04x, %u, %u,%u,%u, %u,%u,%u, 0x%04x, %u, "
1302 "%p)",
1303 imageTarget, level, offset.x, offset.y, offset.z, size.x, size.y,
1304 size.z, formatEnum, imageSize, ptr);
1306 gfxCriticalError() << "Unexpected error " << gfx::hexa(error)
1307 << " from driver: " << call.BeginReading();
1308 return;
1311 ////////////////////////////////////
1312 // Update our specification data?
1314 if (!sub) {
1315 const auto uninitializedSlices = Nothing();
1316 const webgl::ImageInfo newImageInfo{usage, size.x, size.y, size.z,
1317 uninitializedSlices};
1318 *imageInfo = newImageInfo;
1319 InvalidateCaches();
1323 ////////////////////////////////////////
1324 // CopyTex(Sub)Image
1326 static bool ValidateCopyTexImageFormats(WebGLContext* webgl,
1327 const webgl::FormatInfo* srcFormat,
1328 const webgl::FormatInfo* dstFormat) {
1329 MOZ_ASSERT(!srcFormat->compression);
1330 if (dstFormat->compression) {
1331 webgl->ErrorInvalidEnum(
1332 "Specified destination must not have a compressed"
1333 " format.");
1334 return false;
1337 if (dstFormat->effectiveFormat == webgl::EffectiveFormat::RGB9_E5) {
1338 webgl->ErrorInvalidOperation(
1339 "RGB9_E5 is an invalid destination for"
1340 " CopyTex(Sub)Image. (GLES 3.0.4 p145)");
1341 return false;
1344 if (!DoChannelsMatchForCopyTexImage(srcFormat, dstFormat)) {
1345 webgl->ErrorInvalidOperation(
1346 "Destination channels must be compatible with"
1347 " source channels. (GLES 3.0.4 p140 Table 3.16)");
1348 return false;
1351 return true;
1354 ////////////////////////////////////////////////////////////////////////////////
1356 class ScopedCopyTexImageSource {
1357 WebGLContext* const mWebGL;
1358 GLuint mRB;
1359 GLuint mFB;
1361 public:
1362 ScopedCopyTexImageSource(WebGLContext* webgl, uint32_t srcWidth,
1363 uint32_t srcHeight,
1364 const webgl::FormatInfo* srcFormat,
1365 const webgl::FormatUsageInfo* dstUsage);
1366 ~ScopedCopyTexImageSource();
1369 ScopedCopyTexImageSource::ScopedCopyTexImageSource(
1370 WebGLContext* webgl, uint32_t srcWidth, uint32_t srcHeight,
1371 const webgl::FormatInfo* srcFormat, const webgl::FormatUsageInfo* dstUsage)
1372 : mWebGL(webgl), mRB(0), mFB(0) {
1373 switch (dstUsage->format->unsizedFormat) {
1374 case webgl::UnsizedFormat::L:
1375 case webgl::UnsizedFormat::A:
1376 case webgl::UnsizedFormat::LA:
1377 webgl->GenerateWarning(
1378 "Copying to a LUMINANCE, ALPHA, or LUMINANCE_ALPHA"
1379 " is deprecated, and has severely reduced performance"
1380 " on some platforms.");
1381 break;
1383 default:
1384 MOZ_ASSERT(!dstUsage->textureSwizzleRGBA);
1385 return;
1388 if (!dstUsage->textureSwizzleRGBA) return;
1390 gl::GLContext* gl = webgl->gl;
1392 GLenum sizedFormat;
1394 switch (srcFormat->componentType) {
1395 case webgl::ComponentType::NormUInt:
1396 sizedFormat = LOCAL_GL_RGBA8;
1397 break;
1399 case webgl::ComponentType::Float:
1400 if (webgl->IsExtensionEnabled(
1401 WebGLExtensionID::WEBGL_color_buffer_float)) {
1402 sizedFormat = LOCAL_GL_RGBA32F;
1403 webgl->WarnIfImplicit(WebGLExtensionID::WEBGL_color_buffer_float);
1404 break;
1407 if (webgl->IsExtensionEnabled(
1408 WebGLExtensionID::EXT_color_buffer_half_float)) {
1409 sizedFormat = LOCAL_GL_RGBA16F;
1410 webgl->WarnIfImplicit(WebGLExtensionID::EXT_color_buffer_half_float);
1411 break;
1413 MOZ_CRASH("GFX: Should be able to request CopyTexImage from Float.");
1415 default:
1416 MOZ_CRASH("GFX: Should be able to request CopyTexImage from this type.");
1419 gl::ScopedTexture scopedTex(gl);
1420 gl::ScopedBindTexture scopedBindTex(gl, scopedTex.Texture(),
1421 LOCAL_GL_TEXTURE_2D);
1423 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER,
1424 LOCAL_GL_NEAREST);
1425 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER,
1426 LOCAL_GL_NEAREST);
1428 GLint blitSwizzle[4] = {LOCAL_GL_ZERO};
1429 switch (dstUsage->format->unsizedFormat) {
1430 case webgl::UnsizedFormat::L:
1431 blitSwizzle[0] = LOCAL_GL_RED;
1432 break;
1434 case webgl::UnsizedFormat::A:
1435 blitSwizzle[0] = LOCAL_GL_ALPHA;
1436 break;
1438 case webgl::UnsizedFormat::LA:
1439 blitSwizzle[0] = LOCAL_GL_RED;
1440 blitSwizzle[1] = LOCAL_GL_ALPHA;
1441 break;
1443 default:
1444 MOZ_CRASH("GFX: Unhandled unsizedFormat.");
1447 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_R,
1448 blitSwizzle[0]);
1449 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_G,
1450 blitSwizzle[1]);
1451 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_B,
1452 blitSwizzle[2]);
1453 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_A,
1454 blitSwizzle[3]);
1456 gl->fCopyTexImage2D(LOCAL_GL_TEXTURE_2D, 0, sizedFormat, 0, 0, srcWidth,
1457 srcHeight, 0);
1459 // Now create the swizzled FB we'll be exposing.
1461 GLuint rgbaRB = 0;
1462 GLuint rgbaFB = 0;
1464 gl->fGenRenderbuffers(1, &rgbaRB);
1465 gl::ScopedBindRenderbuffer scopedRB(gl, rgbaRB);
1466 gl->fRenderbufferStorage(LOCAL_GL_RENDERBUFFER, sizedFormat, srcWidth,
1467 srcHeight);
1469 gl->fGenFramebuffers(1, &rgbaFB);
1470 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, rgbaFB);
1471 gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER,
1472 LOCAL_GL_COLOR_ATTACHMENT0,
1473 LOCAL_GL_RENDERBUFFER, rgbaRB);
1475 const GLenum status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
1476 if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
1477 MOZ_CRASH("GFX: Temp framebuffer is not complete.");
1481 // Draw-blit rgbaTex into rgbaFB.
1482 const gfx::IntSize srcSize(srcWidth, srcHeight);
1484 const gl::ScopedBindFramebuffer bindFB(gl, rgbaFB);
1485 gl->BlitHelper()->DrawBlitTextureToFramebuffer(scopedTex.Texture(), srcSize,
1486 srcSize);
1489 // Leave RB and FB alive, and FB bound.
1490 mRB = rgbaRB;
1491 mFB = rgbaFB;
1494 template <typename T>
1495 static inline GLenum ToGLHandle(const T& obj) {
1496 return (obj ? obj->mGLName : 0);
1499 ScopedCopyTexImageSource::~ScopedCopyTexImageSource() {
1500 if (!mFB) {
1501 MOZ_ASSERT(!mRB);
1502 return;
1504 MOZ_ASSERT(mRB);
1506 gl::GLContext* gl = mWebGL->gl;
1508 // If we're swizzling, it's because we're on a GL core (3.2+) profile, which
1509 // has split framebuffer support.
1510 gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER,
1511 ToGLHandle(mWebGL->mBoundDrawFramebuffer));
1512 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER,
1513 ToGLHandle(mWebGL->mBoundReadFramebuffer));
1515 gl->fDeleteFramebuffers(1, &mFB);
1516 gl->fDeleteRenderbuffers(1, &mRB);
1519 ////////////////////////////////////////////////////////////////////////////////
1521 static bool GetUnsizedFormatForCopy(GLenum internalFormat,
1522 webgl::UnsizedFormat* const out) {
1523 switch (internalFormat) {
1524 case LOCAL_GL_RED:
1525 *out = webgl::UnsizedFormat::R;
1526 break;
1527 case LOCAL_GL_RG:
1528 *out = webgl::UnsizedFormat::RG;
1529 break;
1530 case LOCAL_GL_RGB:
1531 *out = webgl::UnsizedFormat::RGB;
1532 break;
1533 case LOCAL_GL_RGBA:
1534 *out = webgl::UnsizedFormat::RGBA;
1535 break;
1536 case LOCAL_GL_LUMINANCE:
1537 *out = webgl::UnsizedFormat::L;
1538 break;
1539 case LOCAL_GL_ALPHA:
1540 *out = webgl::UnsizedFormat::A;
1541 break;
1542 case LOCAL_GL_LUMINANCE_ALPHA:
1543 *out = webgl::UnsizedFormat::LA;
1544 break;
1546 default:
1547 return false;
1550 return true;
1553 static const webgl::FormatUsageInfo* ValidateCopyDestUsage(
1554 WebGLContext* webgl, const webgl::FormatInfo* srcFormat,
1555 GLenum internalFormat) {
1556 const auto& fua = webgl->mFormatUsage;
1558 switch (internalFormat) {
1559 case LOCAL_GL_R8_SNORM:
1560 case LOCAL_GL_RG8_SNORM:
1561 case LOCAL_GL_RGB8_SNORM:
1562 case LOCAL_GL_RGBA8_SNORM:
1563 webgl->ErrorInvalidEnum("SNORM formats are invalid for CopyTexImage.");
1564 return nullptr;
1567 auto dstUsage = fua->GetSizedTexUsage(internalFormat);
1568 if (!dstUsage) {
1569 // Ok, maybe it's unsized.
1570 webgl::UnsizedFormat unsizedFormat;
1571 if (!GetUnsizedFormatForCopy(internalFormat, &unsizedFormat)) {
1572 webgl->ErrorInvalidEnumInfo("internalFormat", internalFormat);
1573 return nullptr;
1576 const auto dstFormat = srcFormat->GetCopyDecayFormat(unsizedFormat);
1577 if (dstFormat) {
1578 dstUsage = fua->GetUsage(dstFormat->effectiveFormat);
1580 if (!dstUsage) {
1581 webgl->ErrorInvalidOperation(
1582 "0x%04x is not a valid unsized format for"
1583 " source format %s.",
1584 internalFormat, srcFormat->name);
1585 return nullptr;
1588 return dstUsage;
1590 // Alright, it's sized.
1592 const auto dstFormat = dstUsage->format;
1594 if (dstFormat->componentType != srcFormat->componentType) {
1595 webgl->ErrorInvalidOperation(
1596 "For sized internalFormats, source and dest"
1597 " component types must match. (source: %s, dest:"
1598 " %s)",
1599 srcFormat->name, dstFormat->name);
1600 return nullptr;
1603 bool componentSizesMatch = true;
1604 if (dstFormat->r) {
1605 componentSizesMatch &= (dstFormat->r == srcFormat->r);
1607 if (dstFormat->g) {
1608 componentSizesMatch &= (dstFormat->g == srcFormat->g);
1610 if (dstFormat->b) {
1611 componentSizesMatch &= (dstFormat->b == srcFormat->b);
1613 if (dstFormat->a) {
1614 componentSizesMatch &= (dstFormat->a == srcFormat->a);
1617 if (!componentSizesMatch) {
1618 webgl->ErrorInvalidOperation(
1619 "For sized internalFormats, source and dest"
1620 " component sizes must match exactly. (source: %s,"
1621 " dest: %s)",
1622 srcFormat->name, dstFormat->name);
1623 return nullptr;
1626 return dstUsage;
1629 static bool ValidateCopyTexImageForFeedback(const WebGLContext& webgl,
1630 const WebGLTexture& tex,
1631 const uint32_t mipLevel,
1632 const uint32_t zLayer) {
1633 const auto& fb = webgl.BoundReadFb();
1634 if (fb) {
1635 MOZ_ASSERT(fb->ColorReadBuffer());
1636 const auto& attach = *fb->ColorReadBuffer();
1637 MOZ_ASSERT(attach.ZLayerCount() ==
1638 1); // Multiview invalid for copyTexImage.
1640 if (attach.Texture() == &tex && attach.Layer() == zLayer &&
1641 attach.MipLevel() == mipLevel) {
1642 // Note that the TexImageTargets *don't* have to match for this to be
1643 // undefined per GLES 3.0.4 p211, thus an INVALID_OP in WebGL.
1644 webgl.ErrorInvalidOperation(
1645 "Feedback loop detected, as this texture"
1646 " is already attached to READ_FRAMEBUFFER's"
1647 " READ_BUFFER-selected COLOR_ATTACHMENT%u.",
1648 attach.mAttachmentPoint);
1649 return false;
1652 return true;
1655 static bool DoCopyTexOrSubImage(WebGLContext* webgl, bool isSubImage,
1656 bool needsInit, WebGLTexture* const tex,
1657 const TexImageTarget target, GLint level,
1658 GLint xWithinSrc, GLint yWithinSrc,
1659 uint32_t srcTotalWidth, uint32_t srcTotalHeight,
1660 const webgl::FormatUsageInfo* srcUsage,
1661 GLint xOffset, GLint yOffset, GLint zOffset,
1662 uint32_t dstWidth, uint32_t dstHeight,
1663 const webgl::FormatUsageInfo* dstUsage) {
1664 const auto& gl = webgl->gl;
1666 ////
1668 int32_t readX, readY;
1669 int32_t writeX, writeY;
1670 int32_t rwWidth, rwHeight;
1671 if (!Intersect(srcTotalWidth, xWithinSrc, dstWidth, &readX, &writeX,
1672 &rwWidth) ||
1673 !Intersect(srcTotalHeight, yWithinSrc, dstHeight, &readY, &writeY,
1674 &rwHeight)) {
1675 webgl->ErrorOutOfMemory("Bad subrect selection.");
1676 return false;
1679 writeX += xOffset;
1680 writeY += yOffset;
1682 ////
1684 GLenum error = 0;
1685 nsCString errorText;
1686 do {
1687 const auto& idealUnpack = dstUsage->idealUnpack;
1688 const auto& pi = idealUnpack->ToPacking();
1690 UniqueBuffer zeros;
1691 const bool fullOverwrite =
1692 (uint32_t(rwWidth) == dstWidth && uint32_t(rwHeight) == dstHeight);
1693 if (needsInit && !fullOverwrite) {
1694 CheckedInt<size_t> byteCount = BytesPerPixel(pi);
1695 byteCount *= dstWidth;
1696 byteCount *= dstHeight;
1698 if (byteCount.isValid()) {
1699 zeros = calloc(1u, byteCount.value());
1702 if (!zeros.get()) {
1703 webgl->ErrorOutOfMemory("Ran out of memory allocating zeros.");
1704 return false;
1708 if (!isSubImage || zeros) {
1709 WebGLPixelStore::AssertDefault(*gl, webgl->IsWebGL2());
1711 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1);
1712 const auto revert = MakeScopeExit(
1713 [&]() { gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4); });
1714 if (!isSubImage) {
1715 error = DoTexImage(gl, target, level, idealUnpack, dstWidth, dstHeight,
1716 1, nullptr);
1717 if (error) {
1718 errorText = nsPrintfCString(
1719 "DoTexImage(0x%04x, %i, {0x%04x, 0x%04x, 0x%04x}, %u,%u,1) -> "
1720 "0x%04x",
1721 target.get(), level, idealUnpack->internalFormat,
1722 idealUnpack->unpackFormat, idealUnpack->unpackType, dstWidth,
1723 dstHeight, error);
1724 break;
1727 if (zeros) {
1728 error = DoTexSubImage(gl, target, level, xOffset, yOffset, zOffset,
1729 dstWidth, dstHeight, 1, pi, zeros.get());
1730 if (error) {
1731 errorText = nsPrintfCString(
1732 "DoTexSubImage(0x%04x, %i, %i,%i,%i, %u,%u,1, {0x%04x, 0x%04x}) "
1733 "-> "
1734 "0x%04x",
1735 target.get(), level, xOffset, yOffset, zOffset, dstWidth,
1736 dstHeight, idealUnpack->unpackFormat, idealUnpack->unpackType,
1737 error);
1738 break;
1743 if (!rwWidth || !rwHeight) {
1744 // There aren't any pixels to copy, so we're 'done'.
1745 return true;
1748 const auto& srcFormat = srcUsage->format;
1749 ScopedCopyTexImageSource maybeSwizzle(webgl, srcTotalWidth, srcTotalHeight,
1750 srcFormat, dstUsage);
1752 error = DoCopyTexSubImage(gl, target, level, writeX, writeY, zOffset, readX,
1753 readY, rwWidth, rwHeight);
1754 if (error) {
1755 errorText = nsPrintfCString(
1756 "DoCopyTexSubImage(0x%04x, %i, %i,%i,%i, %i,%i, %u,%u) -> 0x%04x",
1757 target.get(), level, writeX, writeY, zOffset, readX, readY, rwWidth,
1758 rwHeight, error);
1759 break;
1762 return true;
1763 } while (false);
1765 if (error == LOCAL_GL_OUT_OF_MEMORY) {
1766 webgl->ErrorOutOfMemory("Ran out of memory during texture copy.");
1767 tex->Truncate();
1768 return false;
1771 if (gl->IsANGLE() && error == LOCAL_GL_INVALID_OPERATION) {
1772 webgl->ErrorImplementationBug(
1773 "ANGLE is particular about CopyTexSubImage"
1774 " formats matching exactly.");
1775 return false;
1778 webgl->GenerateError(error, "Unexpected error from driver.");
1779 gfxCriticalError() << "Unexpected error from driver: "
1780 << errorText.BeginReading();
1781 return false;
1784 // CopyTexSubImage if `!respecFormat`
1785 void WebGLTexture::CopyTexImage(GLenum imageTarget, uint32_t level,
1786 GLenum respecFormat, const uvec3& dstOffset,
1787 const ivec2& srcOffset, const uvec2& size2) {
1788 ////////////////////////////////////
1789 // Get source info
1791 const webgl::FormatUsageInfo* srcUsage;
1792 uint32_t srcTotalWidth;
1793 uint32_t srcTotalHeight;
1794 if (!mContext->BindCurFBForColorRead(&srcUsage, &srcTotalWidth,
1795 &srcTotalHeight)) {
1796 return;
1798 const auto& srcFormat = srcUsage->format;
1800 if (!ValidateCopyTexImageForFeedback(*mContext, *this, level, dstOffset.z))
1801 return;
1803 const auto size = uvec3{size2.x, size2.y, 1};
1805 ////////////////////////////////////
1806 // Get dest info
1808 webgl::ImageInfo* imageInfo;
1809 const webgl::FormatUsageInfo* dstUsage;
1810 if (respecFormat) {
1811 if (!ValidateTexImageSpecification(imageTarget, level, size, &imageInfo))
1812 return;
1813 MOZ_ASSERT(imageInfo);
1815 dstUsage = ValidateCopyDestUsage(mContext, srcFormat, respecFormat);
1816 if (!dstUsage) return;
1818 if (!ValidateTargetForFormat(mContext, imageTarget, dstUsage->format))
1819 return;
1820 } else {
1821 if (!ValidateTexImageSelection(imageTarget, level, dstOffset, size,
1822 &imageInfo)) {
1823 return;
1825 MOZ_ASSERT(imageInfo);
1827 dstUsage = imageInfo->mFormat;
1828 MOZ_ASSERT(dstUsage);
1831 const auto& dstFormat = dstUsage->format;
1832 if (!mContext->IsWebGL2() && dstFormat->d) {
1833 mContext->ErrorInvalidOperation(
1834 "Function may not be called with format %s.", dstFormat->name);
1835 return;
1838 ////////////////////////////////////
1839 // Check that source and dest info are compatible
1841 if (!ValidateCopyTexImageFormats(mContext, srcFormat, dstFormat)) return;
1843 ////////////////////////////////////
1844 // Do the thing!
1846 const bool isSubImage = !respecFormat;
1847 bool expectsInit = true;
1848 if (isSubImage) {
1849 if (!EnsureImageDataInitializedForUpload(this, imageTarget, level,
1850 dstOffset, size, imageInfo,
1851 &expectsInit)) {
1852 return;
1856 if (!DoCopyTexOrSubImage(mContext, isSubImage, expectsInit, this, imageTarget,
1857 level, srcOffset.x, srcOffset.y, srcTotalWidth,
1858 srcTotalHeight, srcUsage, dstOffset.x, dstOffset.y,
1859 dstOffset.z, size.x, size.y, dstUsage)) {
1860 return;
1863 mContext->OnDataAllocCall();
1865 ////////////////////////////////////
1866 // Update our specification data?
1868 if (respecFormat) {
1869 const auto uninitializedSlices = Nothing();
1870 const webgl::ImageInfo newImageInfo{dstUsage, size.x, size.y, size.z,
1871 uninitializedSlices};
1872 *imageInfo = newImageInfo;
1873 InvalidateCaches();
1877 } // namespace mozilla