Bug 1732219 - Add API for fetching the preview image. r=geckoview-reviewers,agi,mconley
[gecko.git] / dom / canvas / WebGLTextureUpload.cpp
blob28437a3c5cdae86a65e9e5ef6ce73b10bdd81264
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,
75 {},
76 false});
79 TexUnpackBlobDesc FromImageData(const GLenum target, uvec3 size,
80 const dom::ImageData& imageData,
81 dom::Uint8ClampedArray* const scopedArr) {
82 MOZ_RELEASE_ASSERT(scopedArr->Init(imageData.GetDataObject()));
83 scopedArr->ComputeState();
84 const size_t dataSize = scopedArr->Length();
85 const auto data = reinterpret_cast<uint8_t*>(scopedArr->Data());
87 const gfx::IntSize imageISize(imageData.Width(), imageData.Height());
88 const auto imageUSize = *uvec2::FromSize(imageISize);
89 const size_t stride = imageUSize.x * 4;
90 const gfx::SurfaceFormat surfFormat = gfx::SurfaceFormat::R8G8B8A8;
91 MOZ_ALWAYS_TRUE(dataSize == stride * imageUSize.y);
93 const RefPtr<gfx::DataSourceSurface> surf =
94 gfx::Factory::CreateWrappingDataSourceSurface(data, stride, imageISize,
95 surfFormat);
96 MOZ_ASSERT(surf);
98 ////
100 if (!size.x) {
101 size.x = imageUSize.x;
104 if (!size.y) {
105 size.y = imageUSize.y;
108 ////
110 // WhatWG "HTML Living Standard" (30 October 2015):
111 // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned as
112 // non-premultiplied alpha values."
113 return {target, size, gfxAlphaType::NonPremult, {}, {}, imageUSize, nullptr,
114 {}, surf};
117 static layers::SurfaceDescriptor Flatten(const layers::SurfaceDescriptor& sd) {
118 const auto sdType = sd.type();
119 if (sdType != layers::SurfaceDescriptor::TSurfaceDescriptorGPUVideo) {
120 return sd;
122 const auto& sdv = sd.get_SurfaceDescriptorGPUVideo();
123 const auto& sdvType = sdv.type();
124 if (sdvType !=
125 layers::SurfaceDescriptorGPUVideo::TSurfaceDescriptorRemoteDecoder) {
126 return sd;
129 const auto& sdrd = sdv.get_SurfaceDescriptorRemoteDecoder();
130 const auto& subdesc = sdrd.subdesc();
131 const auto& subdescType = subdesc.type();
132 switch (subdescType) {
133 case layers::RemoteDecoderVideoSubDescriptor::T__None:
134 case layers::RemoteDecoderVideoSubDescriptor::Tnull_t:
135 return sd;
137 case layers::RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorD3D10:
138 return subdesc.get_SurfaceDescriptorD3D10();
139 case layers::RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorDXGIYCbCr:
140 return subdesc.get_SurfaceDescriptorDXGIYCbCr();
141 case layers::RemoteDecoderVideoSubDescriptor::TSurfaceDescriptorDMABuf:
142 return subdesc.get_SurfaceDescriptorDMABuf();
143 case layers::RemoteDecoderVideoSubDescriptor::
144 TSurfaceDescriptorMacIOSurface:
145 return subdesc.get_SurfaceDescriptorMacIOSurface();
147 MOZ_CRASH("unreachable");
150 Maybe<webgl::TexUnpackBlobDesc> FromDomElem(const ClientWebGLContext& webgl,
151 const GLenum target, uvec3 size,
152 const dom::Element& elem,
153 ErrorResult* const out_error) {
154 const auto& canvas = *webgl.GetCanvas();
156 if (elem.IsHTMLElement(nsGkAtoms::canvas)) {
157 const dom::HTMLCanvasElement* srcCanvas =
158 static_cast<const dom::HTMLCanvasElement*>(&elem);
159 if (srcCanvas->IsWriteOnly()) {
160 out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
161 return {};
165 // The canvas spec says that drawImage should draw the first frame of
166 // animated images. The webgl spec doesn't mention the issue, so we do the
167 // same as drawImage.
168 uint32_t flags = nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE |
169 nsLayoutUtils::SFE_USE_ELEMENT_SIZE_IF_VECTOR |
170 nsLayoutUtils::SFE_EXACT_SIZE_SURFACE |
171 nsLayoutUtils::SFE_ALLOW_NON_PREMULT;
172 const auto& unpacking = webgl.State().mPixelUnpackState;
173 if (unpacking.mColorspaceConversion == LOCAL_GL_NONE) {
174 flags |= nsLayoutUtils::SFE_NO_COLORSPACE_CONVERSION;
177 RefPtr<gfx::DrawTarget> idealDrawTarget = nullptr; // Don't care for now.
178 auto sfer = nsLayoutUtils::SurfaceFromElement(
179 const_cast<dom::Element*>(&elem), flags, idealDrawTarget);
181 //////
183 uvec2 elemSize;
185 const auto& layersImage = sfer.mLayersImage;
186 Maybe<layers::SurfaceDescriptor> sd;
187 if (layersImage) {
188 elemSize = *uvec2::FromSize(layersImage->GetSize());
190 sd = layersImage->GetDesc();
191 if (sd) {
192 sd = Some(Flatten(*sd));
194 if (!sd) {
195 NS_WARNING("No SurfaceDescriptor for layers::Image!");
199 RefPtr<gfx::DataSourceSurface> dataSurf;
200 if (!sd && sfer.GetSourceSurface()) {
201 const auto surf = sfer.GetSourceSurface();
202 elemSize = *uvec2::FromSize(surf->GetSize());
204 // WARNING: OSX can lose our MakeCurrent here.
205 dataSurf = surf->GetDataSurface();
208 //////
210 if (!size.x) {
211 size.x = elemSize.x;
214 if (!size.y) {
215 size.y = elemSize.y;
218 ////
220 if (!sd && !dataSurf) {
221 webgl.EnqueueWarning("Resource has no data (yet?). Uploading zeros.");
222 return Some(TexUnpackBlobDesc{target, size, gfxAlphaType::NonPremult});
225 //////
227 // While it's counter-intuitive, the shape of the SFEResult API means that we
228 // should try to pull out a surface first, and then, if we do pull out a
229 // surface, check CORS/write-only/etc..
231 if (!sfer.mCORSUsed) {
232 auto& srcPrincipal = sfer.mPrincipal;
233 nsIPrincipal* dstPrincipal = canvas.NodePrincipal();
235 if (!dstPrincipal->Subsumes(srcPrincipal)) {
236 webgl.EnqueueWarning("Cross-origin elements require CORS.");
237 out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
238 return {};
242 if (sfer.mIsWriteOnly) {
243 // mIsWriteOnly defaults to true, and so will be true even if SFE merely
244 // failed. Thus we must test mIsWriteOnly after successfully retrieving an
245 // Image or SourceSurface.
246 webgl.EnqueueWarning("Element is write-only, thus cannot be uploaded.");
247 out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
248 return {};
251 //////
252 // Ok, we're good!
254 return Some(TexUnpackBlobDesc{target,
255 size,
256 sfer.mAlphaType,
259 elemSize,
260 layersImage,
262 dataSurf});
265 } // namespace webgl
267 //////////////////////////////////////////////////////////////////////////////////////////
268 //////////////////////////////////////////////////////////////////////////////////////////
270 static bool ValidateTexImage(WebGLContext* webgl, WebGLTexture* texture,
271 TexImageTarget target, uint32_t level,
272 webgl::ImageInfo** const out_imageInfo) {
273 // Check level
274 if (level >= WebGLTexture::kMaxLevelCount) {
275 webgl->ErrorInvalidValue("`level` is too large.");
276 return false;
279 auto& imageInfo = texture->ImageInfoAt(target, level);
280 *out_imageInfo = &imageInfo;
281 return true;
284 // For *TexImage*
285 bool WebGLTexture::ValidateTexImageSpecification(
286 TexImageTarget target, uint32_t level, const uvec3& size,
287 webgl::ImageInfo** const out_imageInfo) {
288 if (mImmutable) {
289 mContext->ErrorInvalidOperation("Specified texture is immutable.");
290 return false;
293 // Do this early to validate `level`.
294 webgl::ImageInfo* imageInfo;
295 if (!ValidateTexImage(mContext, this, target, level, &imageInfo))
296 return false;
298 if (mTarget == LOCAL_GL_TEXTURE_CUBE_MAP && size.x != size.y) {
299 mContext->ErrorInvalidValue("Cube map images must be square.");
300 return false;
303 /* GLES 3.0.4, p133-134:
304 * GL_MAX_TEXTURE_SIZE is *not* the max allowed texture size. Rather, it is
305 * the max (width/height) size guaranteed not to generate an INVALID_VALUE for
306 * too-large dimensions. Sizes larger than GL_MAX_TEXTURE_SIZE *may or may
307 * not* result in an INVALID_VALUE, or possibly GL_OOM.
309 * However, we have needed to set our maximums lower in the past to prevent
310 * resource corruption. Therefore we have limits.maxTex2dSize, which is
311 * neither necessarily lower nor higher than MAX_TEXTURE_SIZE.
313 * Note that limits.maxTex2dSize must be >= than the advertized
314 * MAX_TEXTURE_SIZE. For simplicity, we advertize MAX_TEXTURE_SIZE as
315 * limits.maxTex2dSize.
318 uint32_t maxWidthHeight = 0;
319 uint32_t maxDepth = 0;
320 uint32_t maxLevel = 0;
322 const auto& limits = mContext->Limits();
323 MOZ_ASSERT(level <= 31);
324 switch (target.get()) {
325 case LOCAL_GL_TEXTURE_2D:
326 maxWidthHeight = limits.maxTex2dSize >> level;
327 maxDepth = 1;
328 maxLevel = CeilingLog2(limits.maxTex2dSize);
329 break;
331 case LOCAL_GL_TEXTURE_3D:
332 maxWidthHeight = limits.maxTex3dSize >> level;
333 maxDepth = maxWidthHeight;
334 maxLevel = CeilingLog2(limits.maxTex3dSize);
335 break;
337 case LOCAL_GL_TEXTURE_2D_ARRAY:
338 maxWidthHeight = limits.maxTex2dSize >> level;
339 // "The maximum number of layers for two-dimensional array textures
340 // (depth) must be at least MAX_ARRAY_TEXTURE_LAYERS for all levels."
341 maxDepth = limits.maxTexArrayLayers;
342 maxLevel = CeilingLog2(limits.maxTex2dSize);
343 break;
345 default: // cube maps
346 MOZ_ASSERT(IsCubeMap());
347 maxWidthHeight = limits.maxTexCubeSize >> level;
348 maxDepth = 1;
349 maxLevel = CeilingLog2(limits.maxTexCubeSize);
350 break;
353 if (level > maxLevel) {
354 mContext->ErrorInvalidValue("Requested level is not supported for target.");
355 return false;
358 if (size.x > maxWidthHeight || size.y > maxWidthHeight || size.z > maxDepth) {
359 mContext->ErrorInvalidValue("Requested size at this level is unsupported.");
360 return false;
364 /* GL ES Version 2.0.25 - 3.7.1 Texture Image Specification
365 * "If level is greater than zero, and either width or
366 * height is not a power-of-two, the error INVALID_VALUE is
367 * generated."
369 * This restriction does not apply to GL ES Version 3.0+.
371 bool requirePOT = (!mContext->IsWebGL2() && level != 0);
373 if (requirePOT) {
374 if (!IsPowerOfTwo(size.x) || !IsPowerOfTwo(size.y)) {
375 mContext->ErrorInvalidValue(
376 "For level > 0, width and height must be"
377 " powers of two.");
378 return false;
383 *out_imageInfo = imageInfo;
384 return true;
387 // For *TexSubImage*
388 bool WebGLTexture::ValidateTexImageSelection(
389 TexImageTarget target, uint32_t level, const uvec3& offset,
390 const uvec3& size, webgl::ImageInfo** const out_imageInfo) {
391 webgl::ImageInfo* imageInfo;
392 if (!ValidateTexImage(mContext, this, target, level, &imageInfo))
393 return false;
395 if (!imageInfo->IsDefined()) {
396 mContext->ErrorInvalidOperation(
397 "The specified TexImage has not yet been"
398 " specified.");
399 return false;
402 const auto totalX = CheckedUint32(offset.x) + size.x;
403 const auto totalY = CheckedUint32(offset.y) + size.y;
404 const auto totalZ = CheckedUint32(offset.z) + size.z;
406 if (!totalX.isValid() || totalX.value() > imageInfo->mWidth ||
407 !totalY.isValid() || totalY.value() > imageInfo->mHeight ||
408 !totalZ.isValid() || totalZ.value() > imageInfo->mDepth) {
409 mContext->ErrorInvalidValue(
410 "Offset+size must be <= the size of the existing"
411 " specified image.");
412 return false;
415 *out_imageInfo = imageInfo;
416 return true;
419 static bool ValidateCompressedTexUnpack(WebGLContext* webgl, const uvec3& size,
420 const webgl::FormatInfo* format,
421 size_t dataSize) {
422 auto compression = format->compression;
424 auto bytesPerBlock = compression->bytesPerBlock;
425 auto blockWidth = compression->blockWidth;
426 auto blockHeight = compression->blockHeight;
428 auto widthInBlocks = CheckedUint32(size.x) / blockWidth;
429 auto heightInBlocks = CheckedUint32(size.y) / blockHeight;
430 if (size.x % blockWidth) widthInBlocks += 1;
431 if (size.y % blockHeight) heightInBlocks += 1;
433 const CheckedUint32 blocksPerImage = widthInBlocks * heightInBlocks;
434 const CheckedUint32 bytesPerImage = bytesPerBlock * blocksPerImage;
435 const CheckedUint32 bytesNeeded = bytesPerImage * size.z;
437 if (!bytesNeeded.isValid()) {
438 webgl->ErrorOutOfMemory("Overflow while computing the needed buffer size.");
439 return false;
442 if (dataSize != bytesNeeded.value()) {
443 webgl->ErrorInvalidValue(
444 "Provided buffer's size must match expected size."
445 " (needs %u, has %zu)",
446 bytesNeeded.value(), dataSize);
447 return false;
450 return true;
453 static bool DoChannelsMatchForCopyTexImage(const webgl::FormatInfo* srcFormat,
454 const webgl::FormatInfo* dstFormat) {
455 // GLES 3.0.4 p140 Table 3.16 "Valid CopyTexImage source
456 // framebuffer/destination texture base internal format combinations."
458 switch (srcFormat->unsizedFormat) {
459 case webgl::UnsizedFormat::RGBA:
460 switch (dstFormat->unsizedFormat) {
461 case webgl::UnsizedFormat::A:
462 case webgl::UnsizedFormat::L:
463 case webgl::UnsizedFormat::LA:
464 case webgl::UnsizedFormat::R:
465 case webgl::UnsizedFormat::RG:
466 case webgl::UnsizedFormat::RGB:
467 case webgl::UnsizedFormat::RGBA:
468 return true;
469 default:
470 return false;
473 case webgl::UnsizedFormat::RGB:
474 switch (dstFormat->unsizedFormat) {
475 case webgl::UnsizedFormat::L:
476 case webgl::UnsizedFormat::R:
477 case webgl::UnsizedFormat::RG:
478 case webgl::UnsizedFormat::RGB:
479 return true;
480 default:
481 return false;
484 case webgl::UnsizedFormat::RG:
485 switch (dstFormat->unsizedFormat) {
486 case webgl::UnsizedFormat::L:
487 case webgl::UnsizedFormat::R:
488 case webgl::UnsizedFormat::RG:
489 return true;
490 default:
491 return false;
494 case webgl::UnsizedFormat::R:
495 switch (dstFormat->unsizedFormat) {
496 case webgl::UnsizedFormat::L:
497 case webgl::UnsizedFormat::R:
498 return true;
499 default:
500 return false;
503 default:
504 return false;
508 static bool EnsureImageDataInitializedForUpload(
509 WebGLTexture* tex, TexImageTarget target, uint32_t level,
510 const uvec3& offset, const uvec3& size, webgl::ImageInfo* imageInfo,
511 bool* const out_expectsInit = nullptr) {
512 if (out_expectsInit) {
513 *out_expectsInit = false;
515 if (!imageInfo->mUninitializedSlices) return true;
517 if (size.x == imageInfo->mWidth && size.y == imageInfo->mHeight) {
518 bool expectsInit = false;
519 auto& isSliceUninit = *imageInfo->mUninitializedSlices;
520 for (const auto z : IntegerRange(offset.z, offset.z + size.z)) {
521 if (!isSliceUninit[z]) continue;
522 expectsInit = true;
523 isSliceUninit[z] = false;
525 if (out_expectsInit) {
526 *out_expectsInit = expectsInit;
529 if (!expectsInit) return true;
531 bool hasUninitialized = false;
532 for (const auto z : IntegerRange(imageInfo->mDepth)) {
533 hasUninitialized |= isSliceUninit[z];
535 if (!hasUninitialized) {
536 imageInfo->mUninitializedSlices = Nothing();
538 return true;
541 WebGLContext* webgl = tex->mContext;
542 webgl->GenerateWarning(
543 "Texture has not been initialized prior to a"
544 " partial upload, forcing the browser to clear it."
545 " This may be slow.");
546 if (!tex->EnsureImageDataInitialized(target, level)) {
547 MOZ_ASSERT(false, "Unexpected failure to init image data.");
548 return false;
551 return true;
554 //////////////////////////////////////////////////////////////////////////////////////////
555 //////////////////////////////////////////////////////////////////////////////////////////
556 // Actual calls
558 static inline GLenum DoTexStorage(gl::GLContext* gl, TexTarget target,
559 GLsizei levels, GLenum sizedFormat,
560 GLsizei width, GLsizei height,
561 GLsizei depth) {
562 gl::GLContext::LocalErrorScope errorScope(*gl);
564 switch (target.get()) {
565 case LOCAL_GL_TEXTURE_2D:
566 case LOCAL_GL_TEXTURE_CUBE_MAP:
567 MOZ_ASSERT(depth == 1);
568 gl->fTexStorage2D(target.get(), levels, sizedFormat, width, height);
569 break;
571 case LOCAL_GL_TEXTURE_3D:
572 case LOCAL_GL_TEXTURE_2D_ARRAY:
573 gl->fTexStorage3D(target.get(), levels, sizedFormat, width, height,
574 depth);
575 break;
577 default:
578 MOZ_CRASH("GFX: bad target");
581 return errorScope.GetError();
584 bool IsTarget3D(TexImageTarget target) {
585 switch (target.get()) {
586 case LOCAL_GL_TEXTURE_2D:
587 case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
588 case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
589 case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
590 case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
591 case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
592 case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
593 return false;
595 case LOCAL_GL_TEXTURE_3D:
596 case LOCAL_GL_TEXTURE_2D_ARRAY:
597 return true;
599 default:
600 MOZ_CRASH("GFX: bad target");
604 GLenum DoTexImage(gl::GLContext* gl, TexImageTarget target, GLint level,
605 const webgl::DriverUnpackInfo* dui, GLsizei width,
606 GLsizei height, GLsizei depth, const void* data) {
607 const GLint border = 0;
609 gl::GLContext::LocalErrorScope errorScope(*gl);
611 if (IsTarget3D(target)) {
612 gl->fTexImage3D(target.get(), level, dui->internalFormat, width, height,
613 depth, border, dui->unpackFormat, dui->unpackType, data);
614 } else {
615 MOZ_ASSERT(depth == 1);
616 gl->fTexImage2D(target.get(), level, dui->internalFormat, width, height,
617 border, dui->unpackFormat, dui->unpackType, data);
620 return errorScope.GetError();
623 GLenum DoTexSubImage(gl::GLContext* gl, TexImageTarget target, GLint level,
624 GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width,
625 GLsizei height, GLsizei depth,
626 const webgl::PackingInfo& pi, const void* data) {
627 gl::GLContext::LocalErrorScope errorScope(*gl);
629 if (IsTarget3D(target)) {
630 gl->fTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset, width,
631 height, depth, pi.format, pi.type, data);
632 } else {
633 MOZ_ASSERT(zOffset == 0);
634 MOZ_ASSERT(depth == 1);
635 gl->fTexSubImage2D(target.get(), level, xOffset, yOffset, width, height,
636 pi.format, pi.type, data);
639 return errorScope.GetError();
642 static inline GLenum DoCompressedTexImage(gl::GLContext* gl,
643 TexImageTarget target, GLint level,
644 GLenum internalFormat, GLsizei width,
645 GLsizei height, GLsizei depth,
646 GLsizei dataSize, const void* data) {
647 const GLint border = 0;
649 gl::GLContext::LocalErrorScope errorScope(*gl);
651 if (IsTarget3D(target)) {
652 gl->fCompressedTexImage3D(target.get(), level, internalFormat, width,
653 height, depth, border, dataSize, data);
654 } else {
655 MOZ_ASSERT(depth == 1);
656 gl->fCompressedTexImage2D(target.get(), level, internalFormat, width,
657 height, border, dataSize, data);
660 return errorScope.GetError();
663 GLenum DoCompressedTexSubImage(gl::GLContext* gl, TexImageTarget target,
664 GLint level, GLint xOffset, GLint yOffset,
665 GLint zOffset, GLsizei width, GLsizei height,
666 GLsizei depth, GLenum sizedUnpackFormat,
667 GLsizei dataSize, const void* data) {
668 gl::GLContext::LocalErrorScope errorScope(*gl);
670 if (IsTarget3D(target)) {
671 gl->fCompressedTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset,
672 width, height, depth, sizedUnpackFormat,
673 dataSize, data);
674 } else {
675 MOZ_ASSERT(zOffset == 0);
676 MOZ_ASSERT(depth == 1);
677 gl->fCompressedTexSubImage2D(target.get(), level, xOffset, yOffset, width,
678 height, sizedUnpackFormat, dataSize, data);
681 return errorScope.GetError();
684 static inline GLenum DoCopyTexSubImage(gl::GLContext* gl, TexImageTarget target,
685 GLint level, GLint xOffset,
686 GLint yOffset, GLint zOffset, GLint x,
687 GLint y, GLsizei width, GLsizei height) {
688 gl::GLContext::LocalErrorScope errorScope(*gl);
690 if (IsTarget3D(target)) {
691 gl->fCopyTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset, x, y,
692 width, height);
693 } else {
694 MOZ_ASSERT(zOffset == 0);
695 gl->fCopyTexSubImage2D(target.get(), level, xOffset, yOffset, x, y, width,
696 height);
699 return errorScope.GetError();
702 //////////////////////////////////////////////////////////////////////////////////////////
703 //////////////////////////////////////////////////////////////////////////////////////////
704 // Actual (mostly generic) function implementations
706 static bool ValidateCompressedTexImageRestrictions(
707 const WebGLContext* webgl, TexImageTarget target, uint32_t level,
708 const webgl::FormatInfo* format, const uvec3& size) {
709 const auto fnIsDimValid_S3TC = [&](const char* const name, uint32_t levelSize,
710 uint32_t blockSize) {
711 const auto impliedBaseSize = levelSize << level;
712 if (impliedBaseSize % blockSize == 0) return true;
713 webgl->ErrorInvalidOperation(
714 "%u is never a valid %s for level %u, because it implies a base mip %s "
715 "of %u."
716 " %s requires that base mip levels have a %s multiple of %u.",
717 levelSize, name, level, name, impliedBaseSize, format->name, name,
718 blockSize);
719 return false;
722 switch (format->compression->family) {
723 case webgl::CompressionFamily::ASTC:
724 if (target == LOCAL_GL_TEXTURE_3D &&
725 !webgl->gl->IsExtensionSupported(
726 gl::GLContext::KHR_texture_compression_astc_hdr)) {
727 webgl->ErrorInvalidOperation("TEXTURE_3D requires ASTC's hdr profile.");
728 return false;
730 break;
732 case webgl::CompressionFamily::PVRTC:
733 if (!IsPowerOfTwo(size.x) || !IsPowerOfTwo(size.y)) {
734 webgl->ErrorInvalidValue("%s requires power-of-two width and height.",
735 format->name);
736 return false;
738 break;
740 case webgl::CompressionFamily::BPTC:
741 case webgl::CompressionFamily::RGTC:
742 case webgl::CompressionFamily::S3TC:
743 if (!fnIsDimValid_S3TC("width", size.x,
744 format->compression->blockWidth) ||
745 !fnIsDimValid_S3TC("height", size.y,
746 format->compression->blockHeight)) {
747 return false;
749 break;
751 // Default: There are no restrictions on CompressedTexImage.
752 case webgl::CompressionFamily::ES3:
753 case webgl::CompressionFamily::ETC1:
754 break;
757 return true;
760 static bool ValidateTargetForFormat(const WebGLContext* webgl,
761 TexImageTarget target,
762 const webgl::FormatInfo* format) {
763 // GLES 3.0.4 p127:
764 // "Textures with a base internal format of DEPTH_COMPONENT or DEPTH_STENCIL
765 // are supported by texture image specification commands only if `target` is
766 // TEXTURE_2D, TEXTURE_2D_ARRAY, or TEXTURE_CUBE_MAP. Using these formats in
767 // conjunction with any other `target` will result in an INVALID_OPERATION
768 // error."
769 const bool ok = [&]() {
770 if (bool(format->d) & (target == LOCAL_GL_TEXTURE_3D)) return false;
772 if (format->compression) {
773 switch (format->compression->family) {
774 case webgl::CompressionFamily::ES3:
775 case webgl::CompressionFamily::S3TC:
776 if (target == LOCAL_GL_TEXTURE_3D) return false;
777 break;
779 case webgl::CompressionFamily::ETC1:
780 case webgl::CompressionFamily::PVRTC:
781 case webgl::CompressionFamily::RGTC:
782 if (target == LOCAL_GL_TEXTURE_3D ||
783 target == LOCAL_GL_TEXTURE_2D_ARRAY) {
784 return false;
786 break;
787 default:
788 break;
791 return true;
792 }();
793 if (!ok) {
794 webgl->ErrorInvalidOperation("Format %s cannot be used with target %s.",
795 format->name, GetEnumName(target.get()));
796 return false;
799 return true;
802 void WebGLTexture::TexStorage(TexTarget target, uint32_t levels,
803 GLenum sizedFormat, const uvec3& size) {
804 // Check levels
805 if (levels < 1) {
806 mContext->ErrorInvalidValue("`levels` must be >= 1.");
807 return;
810 if (!size.x || !size.y || !size.z) {
811 mContext->ErrorInvalidValue("Dimensions must be non-zero.");
812 return;
815 const TexImageTarget testTarget =
816 IsCubeMap() ? LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X : target.get();
817 webgl::ImageInfo* baseImageInfo;
818 if (!ValidateTexImageSpecification(testTarget, 0, size, &baseImageInfo)) {
819 return;
821 MOZ_ALWAYS_TRUE(baseImageInfo);
823 auto dstUsage = mContext->mFormatUsage->GetSizedTexUsage(sizedFormat);
824 if (!dstUsage) {
825 mContext->ErrorInvalidEnumInfo("internalformat", sizedFormat);
826 return;
828 auto dstFormat = dstUsage->format;
830 if (!ValidateTargetForFormat(mContext, testTarget, dstFormat)) return;
832 if (dstFormat->compression) {
833 if (!ValidateCompressedTexImageRestrictions(mContext, testTarget, 0,
834 dstFormat, size)) {
835 return;
839 ////////////////////////////////////
841 const bool levelsOk = [&]() {
842 // Right-shift is only defined for bits-1, which is too large anyways.
843 const auto lastLevel = uint32_t(levels - 1);
844 if (lastLevel > 31) return false;
846 const auto lastLevelWidth = uint32_t(size.x) >> lastLevel;
847 const auto lastLevelHeight = uint32_t(size.y) >> lastLevel;
849 // If these are all zero, then some earlier level was the final 1x1(x1)
850 // level.
851 bool ok = lastLevelWidth || lastLevelHeight;
852 if (target == LOCAL_GL_TEXTURE_3D) {
853 const auto lastLevelDepth = uint32_t(size.z) >> lastLevel;
854 ok |= bool(lastLevelDepth);
856 return ok;
857 }();
858 if (!levelsOk) {
859 mContext->ErrorInvalidOperation(
860 "Too many levels requested for the given"
861 " dimensions. (levels: %u, width: %u, height: %u,"
862 " depth: %u)",
863 levels, size.x, size.y, size.z);
864 return;
867 ////////////////////////////////////
868 // Do the thing!
870 GLenum error = DoTexStorage(mContext->gl, target.get(), levels, sizedFormat,
871 size.x, size.y, size.z);
873 mContext->OnDataAllocCall();
875 if (error == LOCAL_GL_OUT_OF_MEMORY) {
876 mContext->ErrorOutOfMemory("Ran out of memory during texture allocation.");
877 Truncate();
878 return;
880 if (error) {
881 mContext->GenerateError(error, "Unexpected error from driver.");
882 const nsPrintfCString call(
883 "DoTexStorage(0x%04x, %i, 0x%04x, %i,%i,%i) -> 0x%04x", target.get(),
884 levels, sizedFormat, size.x, size.y, size.z, error);
885 gfxCriticalError() << "Unexpected error from driver: "
886 << call.BeginReading();
887 return;
890 ////////////////////////////////////
891 // Update our specification data.
893 auto uninitializedSlices = Some(std::vector<bool>(size.z, true));
894 const webgl::ImageInfo newInfo{dstUsage, size.x, size.y, size.z,
895 std::move(uninitializedSlices)};
898 const auto base_level = mBaseMipmapLevel;
899 mBaseMipmapLevel = 0;
901 ImageInfoAtFace(0, 0) = newInfo;
902 PopulateMipChain(levels - 1);
904 mBaseMipmapLevel = base_level;
907 mImmutable = true;
908 mImmutableLevelCount = AutoAssertCast(levels);
909 ClampLevelBaseAndMax();
912 ////////////////////////////////////////
913 // Tex(Sub)Image
915 // TexSubImage iff `!respectFormat`
916 void WebGLTexture::TexImage(uint32_t level, GLenum respecFormat,
917 const uvec3& offset, const webgl::PackingInfo& pi,
918 const webgl::TexUnpackBlobDesc& src) {
919 Maybe<RawBuffer<>> cpuDataView;
920 if (src.cpuData) {
921 cpuDataView = Some(RawBuffer<>{src.cpuData->Data()});
923 const auto srcViewDesc = webgl::TexUnpackBlobDesc{src.imageTarget,
924 src.size,
925 src.srcAlphaType,
926 std::move(cpuDataView),
927 src.pboOffset,
928 src.imageSize,
929 src.image,
930 src.sd,
931 src.dataSurf,
932 src.unpacking,
933 src.applyUnpackTransforms};
935 const auto blob = webgl::TexUnpackBlob::Create(srcViewDesc);
936 if (!blob) {
937 MOZ_ASSERT(false);
938 return;
941 const auto imageTarget = blob->mDesc.imageTarget;
942 auto size = blob->mDesc.size;
944 if (!IsTarget3D(imageTarget)) {
945 size.z = 1;
948 ////////////////////////////////////
949 // Get dest info
951 const auto& fua = mContext->mFormatUsage;
952 const auto fnValidateUnpackEnums = [&]() {
953 if (!fua->AreUnpackEnumsValid(pi.format, pi.type)) {
954 mContext->ErrorInvalidEnum("Invalid unpack format/type: %s/%s",
955 EnumString(pi.format).c_str(),
956 EnumString(pi.type).c_str());
957 return false;
959 return true;
962 webgl::ImageInfo* imageInfo;
963 const webgl::FormatUsageInfo* dstUsage;
964 if (respecFormat) {
965 if (!ValidateTexImageSpecification(imageTarget, level, size, &imageInfo))
966 return;
967 MOZ_ASSERT(imageInfo);
969 if (!fua->IsInternalFormatEnumValid(respecFormat)) {
970 mContext->ErrorInvalidValue("Invalid internalformat: 0x%04x",
971 respecFormat);
972 return;
975 dstUsage = fua->GetSizedTexUsage(respecFormat);
976 if (!dstUsage) {
977 if (respecFormat != pi.format) {
978 /* GL ES Version 3.0.4 - 3.8.3 Texture Image Specification
979 * "Specifying a combination of values for format, type, and
980 * internalformat that is not listed as a valid combination
981 * in tables 3.2 or 3.3 generates the error INVALID_OPERATION."
983 if (!fnValidateUnpackEnums()) return;
984 mContext->ErrorInvalidOperation(
985 "Unsized internalFormat must match"
986 " unpack format.");
987 return;
990 dstUsage = fua->GetUnsizedTexUsage(pi);
993 if (!dstUsage) {
994 if (!fnValidateUnpackEnums()) return;
995 mContext->ErrorInvalidOperation(
996 "Invalid internalformat/format/type:"
997 " 0x%04x/0x%04x/0x%04x",
998 respecFormat, pi.format, pi.type);
999 return;
1002 const auto& dstFormat = dstUsage->format;
1003 if (!ValidateTargetForFormat(mContext, imageTarget, dstFormat)) return;
1005 if (!mContext->IsWebGL2() && dstFormat->d) {
1006 if (imageTarget != LOCAL_GL_TEXTURE_2D || blob->HasData() || level != 0) {
1007 mContext->ErrorInvalidOperation(
1008 "With format %s, this function may only"
1009 " be called with target=TEXTURE_2D,"
1010 " data=null, and level=0.",
1011 dstFormat->name);
1012 return;
1015 } else {
1016 if (!ValidateTexImageSelection(imageTarget, level, offset, size,
1017 &imageInfo)) {
1018 return;
1020 MOZ_ASSERT(imageInfo);
1021 dstUsage = imageInfo->mFormat;
1023 const auto& dstFormat = dstUsage->format;
1024 if (!mContext->IsWebGL2() && dstFormat->d) {
1025 mContext->ErrorInvalidOperation(
1026 "Function may not be called on a texture of"
1027 " format %s.",
1028 dstFormat->name);
1029 return;
1033 ////////////////////////////////////
1034 // Get source info
1036 const webgl::DriverUnpackInfo* driverUnpackInfo;
1037 if (!dstUsage->IsUnpackValid(pi, &driverUnpackInfo)) {
1038 if (!fnValidateUnpackEnums()) return;
1039 mContext->ErrorInvalidOperation(
1040 "Mismatched internalFormat and format/type:"
1041 " 0x%04x and 0x%04x/0x%04x",
1042 respecFormat, pi.format, pi.type);
1043 return;
1046 if (!blob->Validate(mContext, pi)) return;
1048 ////////////////////////////////////
1049 // Do the thing!
1051 Maybe<webgl::ImageInfo> newImageInfo;
1052 bool isRespec = false;
1053 if (respecFormat) {
1054 // It's tempting to do allocation first, and TexSubImage second, but this is
1055 // generally slower.
1056 newImageInfo = Some(webgl::ImageInfo{dstUsage, size.x, size.y, size.z});
1057 if (!blob->HasData()) {
1058 newImageInfo->mUninitializedSlices =
1059 Some(std::vector<bool>(size.z, true));
1062 isRespec = (imageInfo->mWidth != newImageInfo->mWidth ||
1063 imageInfo->mHeight != newImageInfo->mHeight ||
1064 imageInfo->mDepth != newImageInfo->mDepth ||
1065 imageInfo->mFormat != newImageInfo->mFormat);
1066 } else {
1067 if (!blob->HasData()) {
1068 mContext->ErrorInvalidValue("`source` cannot be null.");
1069 return;
1071 if (!EnsureImageDataInitializedForUpload(this, imageTarget, level, offset,
1072 size, imageInfo)) {
1073 return;
1077 WebGLPixelStore::AssertDefault(*mContext->gl, mContext->IsWebGL2());
1079 blob->mDesc.unpacking.Apply(*mContext->gl, mContext->IsWebGL2(), size);
1080 const auto revertUnpacking = MakeScopeExit([&]() {
1081 const WebGLPixelStore defaultUnpacking;
1082 defaultUnpacking.Apply(*mContext->gl, mContext->IsWebGL2(), size);
1085 const bool isSubImage = !respecFormat;
1086 GLenum glError = 0;
1087 if (!blob->TexOrSubImage(isSubImage, isRespec, this, level, driverUnpackInfo,
1088 offset.x, offset.y, offset.z, pi, &glError)) {
1089 return;
1092 if (glError == LOCAL_GL_OUT_OF_MEMORY) {
1093 mContext->ErrorOutOfMemory("Driver ran out of memory during upload.");
1094 Truncate();
1095 return;
1098 if (glError) {
1099 const auto enumStr = EnumString(glError);
1100 const nsPrintfCString dui(
1101 "Unexpected error %s during upload. (dui: %x/%x/%x)", enumStr.c_str(),
1102 driverUnpackInfo->internalFormat, driverUnpackInfo->unpackFormat,
1103 driverUnpackInfo->unpackType);
1104 mContext->ErrorInvalidOperation("%s", dui.BeginReading());
1105 gfxCriticalError() << mContext->FuncName() << ": " << dui.BeginReading();
1106 return;
1109 ////////////////////////////////////
1110 // Update our specification data?
1112 if (respecFormat) {
1113 mContext->OnDataAllocCall();
1114 *imageInfo = *newImageInfo;
1115 InvalidateCaches();
1119 ////////////////////////////////////////
1120 // CompressedTex(Sub)Image
1122 static inline bool IsSubImageBlockAligned(
1123 const webgl::CompressedFormatInfo* compression,
1124 const webgl::ImageInfo* imageInfo, GLint xOffset, GLint yOffset,
1125 uint32_t width, uint32_t height) {
1126 if (xOffset % compression->blockWidth != 0 ||
1127 yOffset % compression->blockHeight != 0) {
1128 return false;
1131 if (width % compression->blockWidth != 0 &&
1132 xOffset + width != imageInfo->mWidth)
1133 return false;
1135 if (height % compression->blockHeight != 0 &&
1136 yOffset + height != imageInfo->mHeight)
1137 return false;
1139 return true;
1142 // CompressedTexSubImage iff `sub`
1143 void WebGLTexture::CompressedTexImage(bool sub, GLenum imageTarget,
1144 uint32_t level, GLenum formatEnum,
1145 const uvec3& offset, const uvec3& size,
1146 const Range<const uint8_t>& src,
1147 const uint32_t pboImageSize,
1148 const Maybe<uint64_t>& pboOffset) {
1149 auto imageSize = pboImageSize;
1150 if (pboOffset) {
1151 const auto& buffer =
1152 mContext->ValidateBufferSelection(LOCAL_GL_PIXEL_UNPACK_BUFFER);
1153 if (!buffer) return;
1154 auto availBytes = buffer->ByteLength();
1155 if (*pboOffset > availBytes) {
1156 mContext->GenerateError(
1157 LOCAL_GL_INVALID_OPERATION,
1158 "`offset` (%llu) must be <= PIXEL_UNPACK_BUFFER size (%llu).",
1159 *pboOffset, availBytes);
1160 return;
1162 availBytes -= *pboOffset;
1163 if (availBytes < pboImageSize) {
1164 mContext->GenerateError(
1165 LOCAL_GL_INVALID_OPERATION,
1166 "PIXEL_UNPACK_BUFFER size minus `offset` (%llu) too small for"
1167 " `pboImageSize` (%u).",
1168 availBytes, pboImageSize);
1169 return;
1171 } else {
1172 if (mContext->mBoundPixelUnpackBuffer) {
1173 mContext->GenerateError(LOCAL_GL_INVALID_OPERATION,
1174 "PIXEL_UNPACK_BUFFER is non-null.");
1175 return;
1177 imageSize = src.length();
1180 // -
1182 const auto usage = mContext->mFormatUsage->GetSizedTexUsage(formatEnum);
1183 if (!usage || !usage->format->compression) {
1184 mContext->ErrorInvalidEnumArg("format", formatEnum);
1185 return;
1188 webgl::ImageInfo* imageInfo;
1189 if (!sub) {
1190 if (!ValidateTexImageSpecification(imageTarget, level, size, &imageInfo)) {
1191 return;
1193 MOZ_ASSERT(imageInfo);
1195 if (!ValidateTargetForFormat(mContext, imageTarget, usage->format)) return;
1196 if (!ValidateCompressedTexImageRestrictions(mContext, imageTarget, level,
1197 usage->format, size)) {
1198 return;
1200 } else {
1201 if (!ValidateTexImageSelection(imageTarget, level, offset, size,
1202 &imageInfo))
1203 return;
1204 MOZ_ASSERT(imageInfo);
1206 const auto dstUsage = imageInfo->mFormat;
1207 if (usage != dstUsage) {
1208 mContext->ErrorInvalidOperation(
1209 "`format` must match the format of the"
1210 " existing texture image.");
1211 return;
1214 const auto& format = usage->format;
1215 switch (format->compression->family) {
1216 // Forbidden:
1217 case webgl::CompressionFamily::ETC1:
1218 mContext->ErrorInvalidOperation(
1219 "Format does not allow sub-image"
1220 " updates.");
1221 return;
1223 // Block-aligned:
1224 case webgl::CompressionFamily::ES3: // Yes, the ES3 formats don't match
1225 // the ES3
1226 case webgl::CompressionFamily::S3TC: // default behavior.
1227 case webgl::CompressionFamily::BPTC:
1228 case webgl::CompressionFamily::RGTC:
1229 if (!IsSubImageBlockAligned(format->compression, imageInfo, offset.x,
1230 offset.y, size.x, size.y)) {
1231 mContext->ErrorInvalidOperation(
1232 "Format requires block-aligned sub-image"
1233 " updates.");
1234 return;
1236 break;
1238 // Full-only: (The ES3 default)
1239 case webgl::CompressionFamily::ASTC:
1240 case webgl::CompressionFamily::PVRTC:
1241 if (offset.x || offset.y || size.x != imageInfo->mWidth ||
1242 size.y != imageInfo->mHeight) {
1243 mContext->ErrorInvalidOperation(
1244 "Format does not allow partial sub-image"
1245 " updates.");
1246 return;
1248 break;
1252 switch (usage->format->compression->family) {
1253 case webgl::CompressionFamily::BPTC:
1254 case webgl::CompressionFamily::RGTC:
1255 if (level == 0) {
1256 if (size.x % 4 != 0 || size.y % 4 != 0) {
1257 mContext->ErrorInvalidOperation(
1258 "For level == 0, width and height must be multiples of 4.");
1259 return;
1262 break;
1264 default:
1265 break;
1268 if (!ValidateCompressedTexUnpack(mContext, size, usage->format, imageSize))
1269 return;
1271 ////////////////////////////////////
1272 // Do the thing!
1274 if (sub) {
1275 if (!EnsureImageDataInitializedForUpload(this, imageTarget, level, offset,
1276 size, imageInfo)) {
1277 return;
1281 const ScopedLazyBind bindPBO(mContext->gl, LOCAL_GL_PIXEL_UNPACK_BUFFER,
1282 mContext->mBoundPixelUnpackBuffer);
1283 GLenum error;
1284 const void* ptr;
1285 if (pboOffset) {
1286 ptr = reinterpret_cast<const void*>(*pboOffset);
1287 } else {
1288 ptr = reinterpret_cast<const void*>(src.begin().get());
1291 if (!sub) {
1292 error = DoCompressedTexImage(mContext->gl, imageTarget, level, formatEnum,
1293 size.x, size.y, size.z, imageSize, ptr);
1294 } else {
1295 error = DoCompressedTexSubImage(mContext->gl, imageTarget, level, offset.x,
1296 offset.y, offset.z, size.x, size.y, size.z,
1297 formatEnum, imageSize, ptr);
1299 if (error == LOCAL_GL_OUT_OF_MEMORY) {
1300 mContext->ErrorOutOfMemory("Ran out of memory during upload.");
1301 Truncate();
1302 return;
1304 if (error) {
1305 mContext->GenerateError(error, "Unexpected error from driver.");
1306 nsCString call;
1307 if (!sub) {
1308 call = nsPrintfCString(
1309 "DoCompressedTexImage(0x%04x, %u, 0x%04x, %u,%u,%u, %u, %p)",
1310 imageTarget, level, formatEnum, size.x, size.y, size.z, imageSize,
1311 ptr);
1312 } else {
1313 call = nsPrintfCString(
1314 "DoCompressedTexSubImage(0x%04x, %u, %u,%u,%u, %u,%u,%u, 0x%04x, %u, "
1315 "%p)",
1316 imageTarget, level, offset.x, offset.y, offset.z, size.x, size.y,
1317 size.z, formatEnum, imageSize, ptr);
1319 gfxCriticalError() << "Unexpected error " << gfx::hexa(error)
1320 << " from driver: " << call.BeginReading();
1321 return;
1324 ////////////////////////////////////
1325 // Update our specification data?
1327 if (!sub) {
1328 const auto uninitializedSlices = Nothing();
1329 const webgl::ImageInfo newImageInfo{usage, size.x, size.y, size.z,
1330 uninitializedSlices};
1331 *imageInfo = newImageInfo;
1332 InvalidateCaches();
1336 ////////////////////////////////////////
1337 // CopyTex(Sub)Image
1339 static bool ValidateCopyTexImageFormats(WebGLContext* webgl,
1340 const webgl::FormatInfo* srcFormat,
1341 const webgl::FormatInfo* dstFormat) {
1342 MOZ_ASSERT(!srcFormat->compression);
1343 if (dstFormat->compression) {
1344 webgl->ErrorInvalidEnum(
1345 "Specified destination must not have a compressed"
1346 " format.");
1347 return false;
1350 if (dstFormat->effectiveFormat == webgl::EffectiveFormat::RGB9_E5) {
1351 webgl->ErrorInvalidOperation(
1352 "RGB9_E5 is an invalid destination for"
1353 " CopyTex(Sub)Image. (GLES 3.0.4 p145)");
1354 return false;
1357 if (!DoChannelsMatchForCopyTexImage(srcFormat, dstFormat)) {
1358 webgl->ErrorInvalidOperation(
1359 "Destination channels must be compatible with"
1360 " source channels. (GLES 3.0.4 p140 Table 3.16)");
1361 return false;
1364 return true;
1367 ////////////////////////////////////////////////////////////////////////////////
1369 class ScopedCopyTexImageSource {
1370 WebGLContext* const mWebGL;
1371 GLuint mRB;
1372 GLuint mFB;
1374 public:
1375 ScopedCopyTexImageSource(WebGLContext* webgl, uint32_t srcWidth,
1376 uint32_t srcHeight,
1377 const webgl::FormatInfo* srcFormat,
1378 const webgl::FormatUsageInfo* dstUsage);
1379 ~ScopedCopyTexImageSource();
1382 ScopedCopyTexImageSource::ScopedCopyTexImageSource(
1383 WebGLContext* webgl, uint32_t srcWidth, uint32_t srcHeight,
1384 const webgl::FormatInfo* srcFormat, const webgl::FormatUsageInfo* dstUsage)
1385 : mWebGL(webgl), mRB(0), mFB(0) {
1386 switch (dstUsage->format->unsizedFormat) {
1387 case webgl::UnsizedFormat::L:
1388 case webgl::UnsizedFormat::A:
1389 case webgl::UnsizedFormat::LA:
1390 webgl->GenerateWarning(
1391 "Copying to a LUMINANCE, ALPHA, or LUMINANCE_ALPHA"
1392 " is deprecated, and has severely reduced performance"
1393 " on some platforms.");
1394 break;
1396 default:
1397 MOZ_ASSERT(!dstUsage->textureSwizzleRGBA);
1398 return;
1401 if (!dstUsage->textureSwizzleRGBA) return;
1403 gl::GLContext* gl = webgl->gl;
1405 GLenum sizedFormat;
1407 switch (srcFormat->componentType) {
1408 case webgl::ComponentType::NormUInt:
1409 sizedFormat = LOCAL_GL_RGBA8;
1410 break;
1412 case webgl::ComponentType::Float:
1413 if (webgl->IsExtensionEnabled(
1414 WebGLExtensionID::WEBGL_color_buffer_float)) {
1415 sizedFormat = LOCAL_GL_RGBA32F;
1416 webgl->WarnIfImplicit(WebGLExtensionID::WEBGL_color_buffer_float);
1417 break;
1420 if (webgl->IsExtensionEnabled(
1421 WebGLExtensionID::EXT_color_buffer_half_float)) {
1422 sizedFormat = LOCAL_GL_RGBA16F;
1423 webgl->WarnIfImplicit(WebGLExtensionID::EXT_color_buffer_half_float);
1424 break;
1426 MOZ_CRASH("GFX: Should be able to request CopyTexImage from Float.");
1428 default:
1429 MOZ_CRASH("GFX: Should be able to request CopyTexImage from this type.");
1432 gl::ScopedTexture scopedTex(gl);
1433 gl::ScopedBindTexture scopedBindTex(gl, scopedTex.Texture(),
1434 LOCAL_GL_TEXTURE_2D);
1436 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER,
1437 LOCAL_GL_NEAREST);
1438 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER,
1439 LOCAL_GL_NEAREST);
1441 GLint blitSwizzle[4] = {LOCAL_GL_ZERO};
1442 switch (dstUsage->format->unsizedFormat) {
1443 case webgl::UnsizedFormat::L:
1444 blitSwizzle[0] = LOCAL_GL_RED;
1445 break;
1447 case webgl::UnsizedFormat::A:
1448 blitSwizzle[0] = LOCAL_GL_ALPHA;
1449 break;
1451 case webgl::UnsizedFormat::LA:
1452 blitSwizzle[0] = LOCAL_GL_RED;
1453 blitSwizzle[1] = LOCAL_GL_ALPHA;
1454 break;
1456 default:
1457 MOZ_CRASH("GFX: Unhandled unsizedFormat.");
1460 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_R,
1461 blitSwizzle[0]);
1462 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_G,
1463 blitSwizzle[1]);
1464 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_B,
1465 blitSwizzle[2]);
1466 gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_A,
1467 blitSwizzle[3]);
1469 gl->fCopyTexImage2D(LOCAL_GL_TEXTURE_2D, 0, sizedFormat, 0, 0, srcWidth,
1470 srcHeight, 0);
1472 // Now create the swizzled FB we'll be exposing.
1474 GLuint rgbaRB = 0;
1475 GLuint rgbaFB = 0;
1477 gl->fGenRenderbuffers(1, &rgbaRB);
1478 gl::ScopedBindRenderbuffer scopedRB(gl, rgbaRB);
1479 gl->fRenderbufferStorage(LOCAL_GL_RENDERBUFFER, sizedFormat, srcWidth,
1480 srcHeight);
1482 gl->fGenFramebuffers(1, &rgbaFB);
1483 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, rgbaFB);
1484 gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER,
1485 LOCAL_GL_COLOR_ATTACHMENT0,
1486 LOCAL_GL_RENDERBUFFER, rgbaRB);
1488 const GLenum status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
1489 if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
1490 MOZ_CRASH("GFX: Temp framebuffer is not complete.");
1494 // Draw-blit rgbaTex into rgbaFB.
1495 const gfx::IntSize srcSize(srcWidth, srcHeight);
1497 const gl::ScopedBindFramebuffer bindFB(gl, rgbaFB);
1498 gl->BlitHelper()->DrawBlitTextureToFramebuffer(scopedTex.Texture(), srcSize,
1499 srcSize);
1502 // Leave RB and FB alive, and FB bound.
1503 mRB = rgbaRB;
1504 mFB = rgbaFB;
1507 template <typename T>
1508 static inline GLenum ToGLHandle(const T& obj) {
1509 return (obj ? obj->mGLName : 0);
1512 ScopedCopyTexImageSource::~ScopedCopyTexImageSource() {
1513 if (!mFB) {
1514 MOZ_ASSERT(!mRB);
1515 return;
1517 MOZ_ASSERT(mRB);
1519 gl::GLContext* gl = mWebGL->gl;
1521 // If we're swizzling, it's because we're on a GL core (3.2+) profile, which
1522 // has split framebuffer support.
1523 gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER,
1524 ToGLHandle(mWebGL->mBoundDrawFramebuffer));
1525 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER,
1526 ToGLHandle(mWebGL->mBoundReadFramebuffer));
1528 gl->fDeleteFramebuffers(1, &mFB);
1529 gl->fDeleteRenderbuffers(1, &mRB);
1532 ////////////////////////////////////////////////////////////////////////////////
1534 static bool GetUnsizedFormatForCopy(GLenum internalFormat,
1535 webgl::UnsizedFormat* const out) {
1536 switch (internalFormat) {
1537 case LOCAL_GL_RED:
1538 *out = webgl::UnsizedFormat::R;
1539 break;
1540 case LOCAL_GL_RG:
1541 *out = webgl::UnsizedFormat::RG;
1542 break;
1543 case LOCAL_GL_RGB:
1544 *out = webgl::UnsizedFormat::RGB;
1545 break;
1546 case LOCAL_GL_RGBA:
1547 *out = webgl::UnsizedFormat::RGBA;
1548 break;
1549 case LOCAL_GL_LUMINANCE:
1550 *out = webgl::UnsizedFormat::L;
1551 break;
1552 case LOCAL_GL_ALPHA:
1553 *out = webgl::UnsizedFormat::A;
1554 break;
1555 case LOCAL_GL_LUMINANCE_ALPHA:
1556 *out = webgl::UnsizedFormat::LA;
1557 break;
1559 default:
1560 return false;
1563 return true;
1566 static const webgl::FormatUsageInfo* ValidateCopyDestUsage(
1567 WebGLContext* webgl, const webgl::FormatInfo* srcFormat,
1568 GLenum internalFormat) {
1569 const auto& fua = webgl->mFormatUsage;
1571 switch (internalFormat) {
1572 case LOCAL_GL_R8_SNORM:
1573 case LOCAL_GL_RG8_SNORM:
1574 case LOCAL_GL_RGB8_SNORM:
1575 case LOCAL_GL_RGBA8_SNORM:
1576 webgl->ErrorInvalidEnum("SNORM formats are invalid for CopyTexImage.");
1577 return nullptr;
1580 auto dstUsage = fua->GetSizedTexUsage(internalFormat);
1581 if (!dstUsage) {
1582 // Ok, maybe it's unsized.
1583 webgl::UnsizedFormat unsizedFormat;
1584 if (!GetUnsizedFormatForCopy(internalFormat, &unsizedFormat)) {
1585 webgl->ErrorInvalidEnumInfo("internalFormat", internalFormat);
1586 return nullptr;
1589 const auto dstFormat = srcFormat->GetCopyDecayFormat(unsizedFormat);
1590 if (dstFormat) {
1591 dstUsage = fua->GetUsage(dstFormat->effectiveFormat);
1593 if (!dstUsage) {
1594 webgl->ErrorInvalidOperation(
1595 "0x%04x is not a valid unsized format for"
1596 " source format %s.",
1597 internalFormat, srcFormat->name);
1598 return nullptr;
1601 return dstUsage;
1603 // Alright, it's sized.
1605 const auto dstFormat = dstUsage->format;
1607 if (dstFormat->componentType != srcFormat->componentType) {
1608 webgl->ErrorInvalidOperation(
1609 "For sized internalFormats, source and dest"
1610 " component types must match. (source: %s, dest:"
1611 " %s)",
1612 srcFormat->name, dstFormat->name);
1613 return nullptr;
1616 bool componentSizesMatch = true;
1617 if (dstFormat->r) {
1618 componentSizesMatch &= (dstFormat->r == srcFormat->r);
1620 if (dstFormat->g) {
1621 componentSizesMatch &= (dstFormat->g == srcFormat->g);
1623 if (dstFormat->b) {
1624 componentSizesMatch &= (dstFormat->b == srcFormat->b);
1626 if (dstFormat->a) {
1627 componentSizesMatch &= (dstFormat->a == srcFormat->a);
1630 if (!componentSizesMatch) {
1631 webgl->ErrorInvalidOperation(
1632 "For sized internalFormats, source and dest"
1633 " component sizes must match exactly. (source: %s,"
1634 " dest: %s)",
1635 srcFormat->name, dstFormat->name);
1636 return nullptr;
1639 return dstUsage;
1642 static bool ValidateCopyTexImageForFeedback(const WebGLContext& webgl,
1643 const WebGLTexture& tex,
1644 const uint32_t mipLevel,
1645 const uint32_t zLayer) {
1646 const auto& fb = webgl.BoundReadFb();
1647 if (fb) {
1648 MOZ_ASSERT(fb->ColorReadBuffer());
1649 const auto& attach = *fb->ColorReadBuffer();
1650 MOZ_ASSERT(attach.ZLayerCount() ==
1651 1); // Multiview invalid for copyTexImage.
1653 if (attach.Texture() == &tex && attach.Layer() == zLayer &&
1654 attach.MipLevel() == mipLevel) {
1655 // Note that the TexImageTargets *don't* have to match for this to be
1656 // undefined per GLES 3.0.4 p211, thus an INVALID_OP in WebGL.
1657 webgl.ErrorInvalidOperation(
1658 "Feedback loop detected, as this texture"
1659 " is already attached to READ_FRAMEBUFFER's"
1660 " READ_BUFFER-selected COLOR_ATTACHMENT%u.",
1661 attach.mAttachmentPoint);
1662 return false;
1665 return true;
1668 static bool DoCopyTexOrSubImage(WebGLContext* webgl, bool isSubImage,
1669 bool needsInit, WebGLTexture* const tex,
1670 const TexImageTarget target, GLint level,
1671 GLint xWithinSrc, GLint yWithinSrc,
1672 uint32_t srcTotalWidth, uint32_t srcTotalHeight,
1673 const webgl::FormatUsageInfo* srcUsage,
1674 GLint xOffset, GLint yOffset, GLint zOffset,
1675 uint32_t dstWidth, uint32_t dstHeight,
1676 const webgl::FormatUsageInfo* dstUsage) {
1677 const auto& gl = webgl->gl;
1679 ////
1681 int32_t readX, readY;
1682 int32_t writeX, writeY;
1683 int32_t rwWidth, rwHeight;
1684 if (!Intersect(srcTotalWidth, xWithinSrc, dstWidth, &readX, &writeX,
1685 &rwWidth) ||
1686 !Intersect(srcTotalHeight, yWithinSrc, dstHeight, &readY, &writeY,
1687 &rwHeight)) {
1688 webgl->ErrorOutOfMemory("Bad subrect selection.");
1689 return false;
1692 writeX += xOffset;
1693 writeY += yOffset;
1695 ////
1697 GLenum error = 0;
1698 nsCString errorText;
1699 do {
1700 const auto& idealUnpack = dstUsage->idealUnpack;
1701 const auto& pi = idealUnpack->ToPacking();
1703 UniqueBuffer zeros;
1704 const bool fullOverwrite =
1705 (uint32_t(rwWidth) == dstWidth && uint32_t(rwHeight) == dstHeight);
1706 if (needsInit && !fullOverwrite) {
1707 CheckedInt<size_t> byteCount = BytesPerPixel(pi);
1708 byteCount *= dstWidth;
1709 byteCount *= dstHeight;
1711 if (byteCount.isValid()) {
1712 zeros = calloc(1u, byteCount.value());
1715 if (!zeros.get()) {
1716 webgl->ErrorOutOfMemory("Ran out of memory allocating zeros.");
1717 return false;
1721 if (!isSubImage || zeros) {
1722 WebGLPixelStore::AssertDefault(*gl, webgl->IsWebGL2());
1724 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1);
1725 const auto revert = MakeScopeExit(
1726 [&]() { gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4); });
1727 if (!isSubImage) {
1728 error = DoTexImage(gl, target, level, idealUnpack, dstWidth, dstHeight,
1729 1, nullptr);
1730 if (error) {
1731 errorText = nsPrintfCString(
1732 "DoTexImage(0x%04x, %i, {0x%04x, 0x%04x, 0x%04x}, %u,%u,1) -> "
1733 "0x%04x",
1734 target.get(), level, idealUnpack->internalFormat,
1735 idealUnpack->unpackFormat, idealUnpack->unpackType, dstWidth,
1736 dstHeight, error);
1737 break;
1740 if (zeros) {
1741 error = DoTexSubImage(gl, target, level, xOffset, yOffset, zOffset,
1742 dstWidth, dstHeight, 1, pi, zeros.get());
1743 if (error) {
1744 errorText = nsPrintfCString(
1745 "DoTexSubImage(0x%04x, %i, %i,%i,%i, %u,%u,1, {0x%04x, 0x%04x}) "
1746 "-> "
1747 "0x%04x",
1748 target.get(), level, xOffset, yOffset, zOffset, dstWidth,
1749 dstHeight, idealUnpack->unpackFormat, idealUnpack->unpackType,
1750 error);
1751 break;
1756 if (!rwWidth || !rwHeight) {
1757 // There aren't any pixels to copy, so we're 'done'.
1758 return true;
1761 const auto& srcFormat = srcUsage->format;
1762 ScopedCopyTexImageSource maybeSwizzle(webgl, srcTotalWidth, srcTotalHeight,
1763 srcFormat, dstUsage);
1765 error = DoCopyTexSubImage(gl, target, level, writeX, writeY, zOffset, readX,
1766 readY, rwWidth, rwHeight);
1767 if (error) {
1768 errorText = nsPrintfCString(
1769 "DoCopyTexSubImage(0x%04x, %i, %i,%i,%i, %i,%i, %u,%u) -> 0x%04x",
1770 target.get(), level, writeX, writeY, zOffset, readX, readY, rwWidth,
1771 rwHeight, error);
1772 break;
1775 return true;
1776 } while (false);
1778 if (error == LOCAL_GL_OUT_OF_MEMORY) {
1779 webgl->ErrorOutOfMemory("Ran out of memory during texture copy.");
1780 tex->Truncate();
1781 return false;
1784 if (gl->IsANGLE() && error == LOCAL_GL_INVALID_OPERATION) {
1785 webgl->ErrorImplementationBug(
1786 "ANGLE is particular about CopyTexSubImage"
1787 " formats matching exactly.");
1788 return false;
1791 webgl->GenerateError(error, "Unexpected error from driver.");
1792 gfxCriticalError() << "Unexpected error from driver: "
1793 << errorText.BeginReading();
1794 return false;
1797 // CopyTexSubImage if `!respecFormat`
1798 void WebGLTexture::CopyTexImage(GLenum imageTarget, uint32_t level,
1799 GLenum respecFormat, const uvec3& dstOffset,
1800 const ivec2& srcOffset, const uvec2& size2) {
1801 ////////////////////////////////////
1802 // Get source info
1804 const webgl::FormatUsageInfo* srcUsage;
1805 uint32_t srcTotalWidth;
1806 uint32_t srcTotalHeight;
1807 if (!mContext->BindCurFBForColorRead(&srcUsage, &srcTotalWidth,
1808 &srcTotalHeight)) {
1809 return;
1811 const auto& srcFormat = srcUsage->format;
1813 if (!ValidateCopyTexImageForFeedback(*mContext, *this, level, dstOffset.z))
1814 return;
1816 const auto size = uvec3{size2.x, size2.y, 1};
1818 ////////////////////////////////////
1819 // Get dest info
1821 webgl::ImageInfo* imageInfo;
1822 const webgl::FormatUsageInfo* dstUsage;
1823 if (respecFormat) {
1824 if (!ValidateTexImageSpecification(imageTarget, level, size, &imageInfo))
1825 return;
1826 MOZ_ASSERT(imageInfo);
1828 dstUsage = ValidateCopyDestUsage(mContext, srcFormat, respecFormat);
1829 if (!dstUsage) return;
1831 if (!ValidateTargetForFormat(mContext, imageTarget, dstUsage->format))
1832 return;
1833 } else {
1834 if (!ValidateTexImageSelection(imageTarget, level, dstOffset, size,
1835 &imageInfo)) {
1836 return;
1838 MOZ_ASSERT(imageInfo);
1840 dstUsage = imageInfo->mFormat;
1841 MOZ_ASSERT(dstUsage);
1844 const auto& dstFormat = dstUsage->format;
1845 if (!mContext->IsWebGL2() && dstFormat->d) {
1846 mContext->ErrorInvalidOperation(
1847 "Function may not be called with format %s.", dstFormat->name);
1848 return;
1851 ////////////////////////////////////
1852 // Check that source and dest info are compatible
1854 if (!ValidateCopyTexImageFormats(mContext, srcFormat, dstFormat)) return;
1856 ////////////////////////////////////
1857 // Do the thing!
1859 const bool isSubImage = !respecFormat;
1860 bool expectsInit = true;
1861 if (isSubImage) {
1862 if (!EnsureImageDataInitializedForUpload(this, imageTarget, level,
1863 dstOffset, size, imageInfo,
1864 &expectsInit)) {
1865 return;
1869 if (!DoCopyTexOrSubImage(mContext, isSubImage, expectsInit, this, imageTarget,
1870 level, srcOffset.x, srcOffset.y, srcTotalWidth,
1871 srcTotalHeight, srcUsage, dstOffset.x, dstOffset.y,
1872 dstOffset.z, size.x, size.y, dstUsage)) {
1873 return;
1876 mContext->OnDataAllocCall();
1878 ////////////////////////////////////
1879 // Update our specification data?
1881 if (respecFormat) {
1882 const auto uninitializedSlices = Nothing();
1883 const webgl::ImageInfo newImageInfo{dstUsage, size.x, size.y, size.z,
1884 uninitializedSlices};
1885 *imageInfo = newImageInfo;
1886 InvalidateCaches();
1890 } // namespace mozilla