/src/mozilla-central/dom/canvas/WebGLTextureUpload.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
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/. */ |
5 | | |
6 | | #include "WebGLTexture.h" |
7 | | |
8 | | #include <algorithm> |
9 | | |
10 | | #include "CanvasUtils.h" |
11 | | #include "gfxPrefs.h" |
12 | | #include "GLBlitHelper.h" |
13 | | #include "GLContext.h" |
14 | | #include "mozilla/gfx/2D.h" |
15 | | #include "mozilla/dom/HTMLVideoElement.h" |
16 | | #include "mozilla/dom/ImageBitmap.h" |
17 | | #include "mozilla/dom/ImageData.h" |
18 | | #include "mozilla/MathAlgorithms.h" |
19 | | #include "mozilla/Scoped.h" |
20 | | #include "mozilla/Unused.h" |
21 | | #include "ScopedGLHelpers.h" |
22 | | #include "TexUnpackBlob.h" |
23 | | #include "WebGLBuffer.h" |
24 | | #include "WebGLContext.h" |
25 | | #include "WebGLContextUtils.h" |
26 | | #include "WebGLFramebuffer.h" |
27 | | #include "WebGLTexelConversions.h" |
28 | | |
29 | | namespace mozilla { |
30 | | |
31 | | /* This file handles: |
32 | | * TexStorage2D(texTarget, levels, internalFormat, width, height) |
33 | | * TexStorage3D(texTarget, levels, intenralFormat, width, height, depth) |
34 | | * |
35 | | * TexImage2D(texImageTarget, level, internalFormat, width, height, border, unpackFormat, |
36 | | * unpackType, data) |
37 | | * TexImage3D(texImageTarget, level, internalFormat, width, height, depth, border, |
38 | | * unpackFormat, unpackType, data) |
39 | | * TexSubImage2D(texImageTarget, level, xOffset, yOffset, width, height, unpackFormat, |
40 | | * unpackType, data) |
41 | | * TexSubImage3D(texImageTarget, level, xOffset, yOffset, zOffset, width, height, depth, |
42 | | * unpackFormat, unpackType, data) |
43 | | * |
44 | | * CompressedTexImage2D(texImageTarget, level, internalFormat, width, height, border, |
45 | | * imageSize, data) |
46 | | * CompressedTexImage3D(texImageTarget, level, internalFormat, width, height, depth, |
47 | | * border, imageSize, data) |
48 | | * CompressedTexSubImage2D(texImageTarget, level, xOffset, yOffset, width, height, |
49 | | * sizedUnpackFormat, imageSize, data) |
50 | | * CompressedTexSubImage3D(texImageTarget, level, xOffset, yOffset, zOffset, width, |
51 | | * height, depth, sizedUnpackFormat, imageSize, data) |
52 | | * |
53 | | * CopyTexImage2D(texImageTarget, level, internalFormat, x, y, width, height, border) |
54 | | * CopyTexImage3D - "Because the framebuffer is inhererntly two-dimensional, there is no |
55 | | * CopyTexImage3D command." |
56 | | * CopyTexSubImage2D(texImageTarget, level, xOffset, yOffset, x, y, width, height) |
57 | | * CopyTexSubImage3D(texImageTarget, level, xOffset, yOffset, zOffset, x, y, width, |
58 | | * height) |
59 | | */ |
60 | | |
61 | | static bool |
62 | | ValidateExtents(WebGLContext* webgl, GLsizei width, GLsizei height, |
63 | | GLsizei depth, GLint border, uint32_t* const out_width, |
64 | | uint32_t* const out_height, uint32_t* const out_depth) |
65 | 0 | { |
66 | 0 | // Check border |
67 | 0 | if (border != 0) { |
68 | 0 | webgl->ErrorInvalidValue("`border` must be 0."); |
69 | 0 | return false; |
70 | 0 | } |
71 | 0 | |
72 | 0 | if (width < 0 || height < 0 || depth < 0) { |
73 | 0 | /* GL ES Version 2.0.25 - 3.7.1 Texture Image Specification |
74 | 0 | * "If wt and ht are the specified image width and height, |
75 | 0 | * and if either wt or ht are less than zero, then the error |
76 | 0 | * INVALID_VALUE is generated." |
77 | 0 | */ |
78 | 0 | webgl->ErrorInvalidValue("`width`/`height`/`depth` must be >= 0."); |
79 | 0 | return false; |
80 | 0 | } |
81 | 0 | |
82 | 0 | *out_width = width; |
83 | 0 | *out_height = height; |
84 | 0 | *out_depth = depth; |
85 | 0 | return true; |
86 | 0 | } |
87 | | |
88 | | //////////////////////////////////////// |
89 | | // ArrayBufferView? |
90 | | |
91 | | static inline bool |
92 | | DoesJSTypeMatchUnpackType(GLenum unpackType, js::Scalar::Type jsType) |
93 | 0 | { |
94 | 0 | switch (unpackType) { |
95 | 0 | case LOCAL_GL_BYTE: |
96 | 0 | return jsType == js::Scalar::Type::Int8; |
97 | 0 |
|
98 | 0 | case LOCAL_GL_UNSIGNED_BYTE: |
99 | 0 | return jsType == js::Scalar::Type::Uint8 || |
100 | 0 | jsType == js::Scalar::Type::Uint8Clamped; |
101 | 0 |
|
102 | 0 | case LOCAL_GL_SHORT: |
103 | 0 | return jsType == js::Scalar::Type::Int16; |
104 | 0 |
|
105 | 0 | case LOCAL_GL_UNSIGNED_SHORT: |
106 | 0 | case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4: |
107 | 0 | case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1: |
108 | 0 | case LOCAL_GL_UNSIGNED_SHORT_5_6_5: |
109 | 0 | case LOCAL_GL_HALF_FLOAT: |
110 | 0 | case LOCAL_GL_HALF_FLOAT_OES: |
111 | 0 | return jsType == js::Scalar::Type::Uint16; |
112 | 0 |
|
113 | 0 | case LOCAL_GL_INT: |
114 | 0 | return jsType == js::Scalar::Type::Int32; |
115 | 0 |
|
116 | 0 | case LOCAL_GL_UNSIGNED_INT: |
117 | 0 | case LOCAL_GL_UNSIGNED_INT_2_10_10_10_REV: |
118 | 0 | case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV: |
119 | 0 | case LOCAL_GL_UNSIGNED_INT_5_9_9_9_REV: |
120 | 0 | case LOCAL_GL_UNSIGNED_INT_24_8: |
121 | 0 | return jsType == js::Scalar::Type::Uint32; |
122 | 0 |
|
123 | 0 | case LOCAL_GL_FLOAT: |
124 | 0 | return jsType == js::Scalar::Type::Float32; |
125 | 0 |
|
126 | 0 | default: |
127 | 0 | return false; |
128 | 0 | } |
129 | 0 | } |
130 | | |
131 | | static bool |
132 | | ValidateViewType(WebGLContext* webgl, GLenum unpackType, |
133 | | const TexImageSource& src) |
134 | 0 | { |
135 | 0 | if (!src.mView) |
136 | 0 | return true; |
137 | 0 | const auto& view = *(src.mView); |
138 | 0 |
|
139 | 0 | const auto& jsType = view.Type(); |
140 | 0 | if (!DoesJSTypeMatchUnpackType(unpackType, jsType)) { |
141 | 0 | webgl->ErrorInvalidOperation("ArrayBufferView type not compatible with" |
142 | 0 | " `type`."); |
143 | 0 | return false; |
144 | 0 | } |
145 | 0 | |
146 | 0 | return true; |
147 | 0 | } |
148 | | |
149 | | static bool |
150 | | ValidateUnpackInfo(WebGLContext* webgl, const webgl::PackingInfo& pi) |
151 | 0 | { |
152 | 0 | if (!webgl->mFormatUsage->AreUnpackEnumsValid(pi.format, pi.type)) { |
153 | 0 | webgl->ErrorInvalidEnum("Invalid unpack format/type: 0x%04x/0x%04x", |
154 | 0 | pi.format, pi.type); |
155 | 0 | return false; |
156 | 0 | } |
157 | 0 | |
158 | 0 | return true; |
159 | 0 | } |
160 | | |
161 | | //////////////////////////////////////////////////////////////////////////////// |
162 | | |
163 | | static UniquePtr<webgl::TexUnpackBytes> |
164 | | FromView(WebGLContext* webgl, TexImageTarget target, |
165 | | uint32_t width, uint32_t height, uint32_t depth, |
166 | | const dom::ArrayBufferView* view, GLuint viewElemOffset, |
167 | | GLuint viewElemLengthOverride) |
168 | 0 | { |
169 | 0 | const bool isClientData = true; |
170 | 0 | const uint8_t* bytes = nullptr; |
171 | 0 | size_t availByteCount = 0; |
172 | 0 | if (view) { |
173 | 0 | if (!webgl->ValidateArrayBufferView(*view, viewElemOffset, |
174 | 0 | viewElemLengthOverride, |
175 | 0 | const_cast<uint8_t**>(&bytes), |
176 | 0 | &availByteCount)) |
177 | 0 | { |
178 | 0 | return nullptr; |
179 | 0 | } |
180 | 0 | } |
181 | 0 | return MakeUnique<webgl::TexUnpackBytes>(webgl, target, width, height, depth, |
182 | 0 | isClientData, bytes, availByteCount); |
183 | 0 | } |
184 | | |
185 | | static UniquePtr<webgl::TexUnpackBytes> |
186 | | FromPboOffset(WebGLContext* webgl, TexImageTarget target, |
187 | | uint32_t width, uint32_t height, uint32_t depth, WebGLsizeiptr pboOffset, |
188 | | const Maybe<GLsizei>& expectedImageSize) |
189 | 0 | { |
190 | 0 | if (pboOffset < 0) { |
191 | 0 | webgl->ErrorInvalidValue("offset cannot be negative."); |
192 | 0 | return nullptr; |
193 | 0 | } |
194 | 0 | |
195 | 0 | const auto& buffer = webgl->ValidateBufferSelection(LOCAL_GL_PIXEL_UNPACK_BUFFER); |
196 | 0 | if (!buffer) |
197 | 0 | return nullptr; |
198 | 0 | |
199 | 0 | size_t availBufferBytes = buffer->ByteLength(); |
200 | 0 | if (size_t(pboOffset) > availBufferBytes) { |
201 | 0 | webgl->ErrorInvalidOperation("Offset is passed end of buffer."); |
202 | 0 | return nullptr; |
203 | 0 | } |
204 | 0 | availBufferBytes -= pboOffset; |
205 | 0 | if (expectedImageSize.isSome()) { |
206 | 0 | if (expectedImageSize.ref() < 0) { |
207 | 0 | webgl->ErrorInvalidValue("ImageSize can't be less than 0."); |
208 | 0 | return nullptr; |
209 | 0 | } |
210 | 0 | if (size_t(expectedImageSize.ref()) != availBufferBytes) { |
211 | 0 | webgl->ErrorInvalidOperation("ImageSize doesn't match the required upload byte size."); |
212 | 0 | return nullptr; |
213 | 0 | } |
214 | 0 | availBufferBytes = size_t(expectedImageSize.ref()); |
215 | 0 | } |
216 | 0 | const bool isClientData = false; |
217 | 0 | const auto ptr = (const uint8_t*)pboOffset; |
218 | 0 | return MakeUnique<webgl::TexUnpackBytes>(webgl, target, width, height, depth, |
219 | 0 | isClientData, ptr, availBufferBytes); |
220 | 0 | } |
221 | | |
222 | | static UniquePtr<webgl::TexUnpackBlob> |
223 | | FromImageBitmap(WebGLContext* webgl, TexImageTarget target, |
224 | | uint32_t width, uint32_t height, uint32_t depth, |
225 | | const dom::ImageBitmap& imageBitmap) |
226 | 0 | { |
227 | 0 | UniquePtr<dom::ImageBitmapCloneData> cloneData = imageBitmap.ToCloneData(); |
228 | 0 | if (!cloneData) { |
229 | 0 | return nullptr; |
230 | 0 | } |
231 | 0 | |
232 | 0 | const RefPtr<gfx::DataSourceSurface> surf = cloneData->mSurface; |
233 | 0 |
|
234 | 0 | if (!width) { |
235 | 0 | width = surf->GetSize().width; |
236 | 0 | } |
237 | 0 |
|
238 | 0 | if (!height) { |
239 | 0 | height = surf->GetSize().height; |
240 | 0 | } |
241 | 0 |
|
242 | 0 | // WhatWG "HTML Living Standard" (30 October 2015): |
243 | 0 | // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned as |
244 | 0 | // non-premultiplied alpha values." |
245 | 0 | return MakeUnique<webgl::TexUnpackSurface>(webgl, target, width, height, depth, surf, |
246 | 0 | cloneData->mAlphaType); |
247 | 0 | } |
248 | | |
249 | | static UniquePtr<webgl::TexUnpackBlob> |
250 | | FromImageData(WebGLContext* webgl, TexImageTarget target, |
251 | | uint32_t width, uint32_t height, uint32_t depth, |
252 | | const dom::ImageData& imageData, dom::Uint8ClampedArray* scopedArr) |
253 | 0 | { |
254 | 0 | DebugOnly<bool> inited = scopedArr->Init(imageData.GetDataObject()); |
255 | 0 | MOZ_ASSERT(inited); |
256 | 0 |
|
257 | 0 | scopedArr->ComputeLengthAndData(); |
258 | 0 | const DebugOnly<size_t> dataSize = scopedArr->Length(); |
259 | 0 | const void* const data = scopedArr->Data(); |
260 | 0 |
|
261 | 0 | const gfx::IntSize size(imageData.Width(), imageData.Height()); |
262 | 0 | const size_t stride = size.width * 4; |
263 | 0 | const gfx::SurfaceFormat surfFormat = gfx::SurfaceFormat::R8G8B8A8; |
264 | 0 |
|
265 | 0 | // WhatWG "HTML Living Standard" (30 October 2015): |
266 | 0 | // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned as |
267 | 0 | // non-premultiplied alpha values." |
268 | 0 | const auto alphaType = gfxAlphaType::NonPremult; |
269 | 0 |
|
270 | 0 | MOZ_ASSERT(dataSize == stride * size.height); |
271 | 0 |
|
272 | 0 | uint8_t* wrappableData = (uint8_t*)data; |
273 | 0 |
|
274 | 0 | const RefPtr<gfx::DataSourceSurface> surf = |
275 | 0 | gfx::Factory::CreateWrappingDataSourceSurface(wrappableData, stride, size, |
276 | 0 | surfFormat); |
277 | 0 | if (!surf) { |
278 | 0 | webgl->ErrorOutOfMemory("OOM in FromImageData."); |
279 | 0 | return nullptr; |
280 | 0 | } |
281 | 0 | |
282 | 0 | //// |
283 | 0 | |
284 | 0 | if (!width) { |
285 | 0 | width = imageData.Width(); |
286 | 0 | } |
287 | 0 |
|
288 | 0 | if (!height) { |
289 | 0 | height = imageData.Height(); |
290 | 0 | } |
291 | 0 |
|
292 | 0 | //// |
293 | 0 |
|
294 | 0 | return MakeUnique<webgl::TexUnpackSurface>(webgl, target, width, height, depth, surf, |
295 | 0 | alphaType); |
296 | 0 | } |
297 | | |
298 | | UniquePtr<webgl::TexUnpackBlob> |
299 | | WebGLContext::FromDomElem(TexImageTarget target, uint32_t width, |
300 | | uint32_t height, uint32_t depth, const dom::Element& elem, |
301 | | ErrorResult* const out_error) |
302 | 0 | { |
303 | 0 | // The canvas spec says that drawImage should draw the first frame of |
304 | 0 | // animated images. The webgl spec doesn't mention the issue, so we do the |
305 | 0 | // same as drawImage. |
306 | 0 | uint32_t flags = nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE | |
307 | 0 | nsLayoutUtils::SFE_WANT_IMAGE_SURFACE | |
308 | 0 | nsLayoutUtils::SFE_USE_ELEMENT_SIZE_IF_VECTOR; |
309 | 0 |
|
310 | 0 | if (mPixelStore_ColorspaceConversion == LOCAL_GL_NONE) |
311 | 0 | flags |= nsLayoutUtils::SFE_NO_COLORSPACE_CONVERSION; |
312 | 0 |
|
313 | 0 | if (!mPixelStore_PremultiplyAlpha) |
314 | 0 | flags |= nsLayoutUtils::SFE_PREFER_NO_PREMULTIPLY_ALPHA; |
315 | 0 |
|
316 | 0 | RefPtr<gfx::DrawTarget> idealDrawTarget = nullptr; // Don't care for now. |
317 | 0 | auto sfer = nsLayoutUtils::SurfaceFromElement(const_cast<dom::Element*>(&elem), flags, |
318 | 0 | idealDrawTarget); |
319 | 0 |
|
320 | 0 | ////// |
321 | 0 |
|
322 | 0 | uint32_t elemWidth = 0; |
323 | 0 | uint32_t elemHeight = 0; |
324 | 0 | layers::Image* layersImage = nullptr; |
325 | 0 | if (!gfxPrefs::WebGLDisableDOMBlitUploads() && sfer.mLayersImage) { |
326 | 0 | layersImage = sfer.mLayersImage; |
327 | 0 | elemWidth = layersImage->GetSize().width; |
328 | 0 | elemHeight = layersImage->GetSize().height; |
329 | 0 | } |
330 | 0 |
|
331 | 0 | RefPtr<gfx::DataSourceSurface> dataSurf; |
332 | 0 | if (!layersImage && sfer.GetSourceSurface()) { |
333 | 0 | const auto surf = sfer.GetSourceSurface(); |
334 | 0 | elemWidth = surf->GetSize().width; |
335 | 0 | elemHeight = surf->GetSize().height; |
336 | 0 |
|
337 | 0 | // WARNING: OSX can lose our MakeCurrent here. |
338 | 0 | dataSurf = surf->GetDataSurface(); |
339 | 0 | } |
340 | 0 |
|
341 | 0 | ////// |
342 | 0 |
|
343 | 0 | if (!width) { |
344 | 0 | width = elemWidth; |
345 | 0 | } |
346 | 0 |
|
347 | 0 | if (!height) { |
348 | 0 | height = elemHeight; |
349 | 0 | } |
350 | 0 |
|
351 | 0 | //// |
352 | 0 |
|
353 | 0 | if (!layersImage && !dataSurf) { |
354 | 0 | const bool isClientData = true; |
355 | 0 | return MakeUnique<webgl::TexUnpackBytes>(this, target, width, height, depth, |
356 | 0 | isClientData, nullptr, 0); |
357 | 0 | } |
358 | 0 | |
359 | 0 | ////// |
360 | 0 | |
361 | 0 | // While it's counter-intuitive, the shape of the SFEResult API means that we should |
362 | 0 | // try to pull out a surface first, and then, if we do pull out a surface, check |
363 | 0 | // CORS/write-only/etc.. |
364 | 0 | |
365 | 0 | if (!sfer.mCORSUsed) { |
366 | 0 | auto& srcPrincipal = sfer.mPrincipal; |
367 | 0 | nsIPrincipal* dstPrincipal = GetCanvas()->NodePrincipal(); |
368 | 0 |
|
369 | 0 | if (!dstPrincipal->Subsumes(srcPrincipal)) { |
370 | 0 | GenerateWarning("Cross-origin elements require CORS."); |
371 | 0 | out_error->Throw(NS_ERROR_DOM_SECURITY_ERR); |
372 | 0 | return nullptr; |
373 | 0 | } |
374 | 0 | } |
375 | 0 | |
376 | 0 | if (sfer.mIsWriteOnly) { |
377 | 0 | // mIsWriteOnly defaults to true, and so will be true even if SFE merely |
378 | 0 | // failed. Thus we must test mIsWriteOnly after successfully retrieving an |
379 | 0 | // Image or SourceSurface. |
380 | 0 | GenerateWarning("Element is write-only, thus cannot be uploaded."); |
381 | 0 | out_error->Throw(NS_ERROR_DOM_SECURITY_ERR); |
382 | 0 | return nullptr; |
383 | 0 | } |
384 | 0 | |
385 | 0 | ////// |
386 | 0 | // Ok, we're good! |
387 | 0 | |
388 | 0 | if (layersImage) { |
389 | 0 | return MakeUnique<webgl::TexUnpackImage>(this, target, width, height, depth, |
390 | 0 | layersImage, sfer.mAlphaType); |
391 | 0 | } |
392 | 0 | |
393 | 0 | MOZ_ASSERT(dataSurf); |
394 | 0 | return MakeUnique<webgl::TexUnpackSurface>(this, target, width, height, depth, |
395 | 0 | dataSurf, sfer.mAlphaType); |
396 | 0 | } |
397 | | |
398 | | //////////////////////////////////////// |
399 | | |
400 | | UniquePtr<webgl::TexUnpackBlob> |
401 | | WebGLContext::From(TexImageTarget target, GLsizei rawWidth, |
402 | | GLsizei rawHeight, GLsizei rawDepth, GLint border, |
403 | | const TexImageSource& src, dom::Uint8ClampedArray* const scopedArr) |
404 | 0 | { |
405 | 0 | uint32_t width, height, depth; |
406 | 0 | if (!ValidateExtents(this, rawWidth, rawHeight, rawDepth, border, &width, |
407 | 0 | &height, &depth)) |
408 | 0 | { |
409 | 0 | return nullptr; |
410 | 0 | } |
411 | 0 | |
412 | 0 | if (src.mPboOffset) { |
413 | 0 | return FromPboOffset(this, target, width, height, depth, |
414 | 0 | *(src.mPboOffset), Nothing()); |
415 | 0 | } |
416 | 0 | |
417 | 0 | if (mBoundPixelUnpackBuffer) { |
418 | 0 | ErrorInvalidOperation("PIXEL_UNPACK_BUFFER must be null."); |
419 | 0 | return nullptr; |
420 | 0 | } |
421 | 0 | |
422 | 0 | if (src.mImageBitmap) { |
423 | 0 | return FromImageBitmap(this, target, width, height, depth, |
424 | 0 | *(src.mImageBitmap)); |
425 | 0 | } |
426 | 0 | |
427 | 0 | if (src.mImageData) { |
428 | 0 | return FromImageData(this, target, width, height, depth, |
429 | 0 | *(src.mImageData), scopedArr); |
430 | 0 | } |
431 | 0 | |
432 | 0 | if (src.mDomElem) { |
433 | 0 | return FromDomElem(target, width, height, depth, *(src.mDomElem), |
434 | 0 | src.mOut_error); |
435 | 0 | } |
436 | 0 | |
437 | 0 | return FromView(this, target, width, height, depth, src.mView, |
438 | 0 | src.mViewElemOffset, src.mViewElemLengthOverride); |
439 | 0 | } |
440 | | |
441 | | //////////////////////////////////////////////////////////////////////////////// |
442 | | |
443 | | static UniquePtr<webgl::TexUnpackBlob> |
444 | | ValidateTexOrSubImage(WebGLContext* webgl, TexImageTarget target, |
445 | | GLsizei rawWidth, GLsizei rawHeight, GLsizei rawDepth, |
446 | | GLint border, const webgl::PackingInfo& pi, |
447 | | const TexImageSource& src, dom::Uint8ClampedArray* const scopedArr) |
448 | 0 | { |
449 | 0 | if (!ValidateUnpackInfo(webgl, pi)) |
450 | 0 | return nullptr; |
451 | 0 | |
452 | 0 | if (!ValidateViewType(webgl, pi.type, src)) |
453 | 0 | return nullptr; |
454 | 0 | |
455 | 0 | auto blob = webgl->From(target, rawWidth, rawHeight, rawDepth, border, src, |
456 | 0 | scopedArr); |
457 | 0 | if (!blob || !blob->Validate(webgl, pi)) |
458 | 0 | return nullptr; |
459 | 0 | |
460 | 0 | return blob; |
461 | 0 | } |
462 | | |
463 | | void |
464 | | WebGLTexture::TexImage(TexImageTarget target, GLint level, |
465 | | GLenum internalFormat, GLsizei width, GLsizei height, |
466 | | GLsizei depth, GLint border, const webgl::PackingInfo& pi, |
467 | | const TexImageSource& src) |
468 | 0 | { |
469 | 0 | dom::RootedSpiderMonkeyInterface<dom::Uint8ClampedArray> scopedArr(dom::RootingCx()); |
470 | 0 | const auto blob = ValidateTexOrSubImage(mContext, target, width, height, |
471 | 0 | depth, border, pi, src, &scopedArr); |
472 | 0 | if (!blob) |
473 | 0 | return; |
474 | 0 | |
475 | 0 | TexImage(target, level, internalFormat, pi, blob.get()); |
476 | 0 | } |
477 | | |
478 | | void |
479 | | WebGLTexture::TexSubImage(TexImageTarget target, GLint level, |
480 | | GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width, |
481 | | GLsizei height, GLsizei depth, |
482 | | const webgl::PackingInfo& pi, const TexImageSource& src) |
483 | 0 | { |
484 | 0 | const GLint border = 0; |
485 | 0 | dom::RootedSpiderMonkeyInterface<dom::Uint8ClampedArray> scopedArr(dom::RootingCx()); |
486 | 0 | const auto blob = ValidateTexOrSubImage(mContext, target, width, height, |
487 | 0 | depth, border, pi, src, &scopedArr); |
488 | 0 | if (!blob) |
489 | 0 | return; |
490 | 0 | |
491 | 0 | if (!blob->HasData()) { |
492 | 0 | mContext->ErrorInvalidValue("Source must not be null."); |
493 | 0 | return; |
494 | 0 | } |
495 | 0 | |
496 | 0 | TexSubImage(target, level, xOffset, yOffset, zOffset, pi, blob.get()); |
497 | 0 | } |
498 | | |
499 | | ////////////////////////////////////////////////////////////////////////////////////////// |
500 | | ////////////////////////////////////////////////////////////////////////////////////////// |
501 | | |
502 | | static bool |
503 | | ValidateTexImage(WebGLContext* webgl, WebGLTexture* texture, |
504 | | TexImageTarget target, GLint level, |
505 | | WebGLTexture::ImageInfo** const out_imageInfo) |
506 | 0 | { |
507 | 0 | // Check level |
508 | 0 | if (level < 0) { |
509 | 0 | webgl->ErrorInvalidValue("`level` must be >= 0."); |
510 | 0 | return false; |
511 | 0 | } |
512 | 0 | |
513 | 0 | if (level >= WebGLTexture::kMaxLevelCount) { |
514 | 0 | webgl->ErrorInvalidValue("`level` is too large."); |
515 | 0 | return false; |
516 | 0 | } |
517 | 0 | |
518 | 0 | WebGLTexture::ImageInfo& imageInfo = texture->ImageInfoAt(target, level); |
519 | 0 |
|
520 | 0 | *out_imageInfo = &imageInfo; |
521 | 0 | return true; |
522 | 0 | } |
523 | | |
524 | | // For *TexImage* |
525 | | bool |
526 | | WebGLTexture::ValidateTexImageSpecification(TexImageTarget target, |
527 | | GLint rawLevel, uint32_t width, |
528 | | uint32_t height, uint32_t depth, |
529 | | WebGLTexture::ImageInfo** const out_imageInfo) |
530 | 0 | { |
531 | 0 | if (mImmutable) { |
532 | 0 | mContext->ErrorInvalidOperation("Specified texture is immutable."); |
533 | 0 | return false; |
534 | 0 | } |
535 | 0 | |
536 | 0 | // Do this early to validate `level`. |
537 | 0 | WebGLTexture::ImageInfo* imageInfo; |
538 | 0 | if (!ValidateTexImage(mContext, this, target, rawLevel, &imageInfo)) |
539 | 0 | return false; |
540 | 0 | const uint32_t level(rawLevel); |
541 | 0 |
|
542 | 0 | if (mTarget == LOCAL_GL_TEXTURE_CUBE_MAP && |
543 | 0 | width != height) |
544 | 0 | { |
545 | 0 | mContext->ErrorInvalidValue("Cube map images must be square."); |
546 | 0 | return false; |
547 | 0 | } |
548 | 0 | |
549 | 0 | /* GLES 3.0.4, p133-134: |
550 | 0 | * GL_MAX_TEXTURE_SIZE is *not* the max allowed texture size. Rather, it is the |
551 | 0 | * max (width/height) size guaranteed not to generate an INVALID_VALUE for too-large |
552 | 0 | * dimensions. Sizes larger than GL_MAX_TEXTURE_SIZE *may or may not* result in an |
553 | 0 | * INVALID_VALUE, or possibly GL_OOM. |
554 | 0 | * |
555 | 0 | * However, we have needed to set our maximums lower in the past to prevent resource |
556 | 0 | * corruption. Therefore we have mGLMaxTextureSize, which is neither necessarily |
557 | 0 | * lower nor higher than MAX_TEXTURE_SIZE. |
558 | 0 | * |
559 | 0 | * Note that mGLMaxTextureSize must be >= than the advertized MAX_TEXTURE_SIZE. |
560 | 0 | * For simplicity, we advertize MAX_TEXTURE_SIZE as mGLMaxTextureSize. |
561 | 0 | */ |
562 | 0 | |
563 | 0 | uint32_t maxWidthHeight = 0; |
564 | 0 | uint32_t maxDepth = 0; |
565 | 0 | uint32_t maxLevel = 0; |
566 | 0 |
|
567 | 0 | MOZ_ASSERT(level <= 31); |
568 | 0 | switch (target.get()) { |
569 | 0 | case LOCAL_GL_TEXTURE_2D: |
570 | 0 | maxWidthHeight = mContext->mGLMaxTextureSize >> level; |
571 | 0 | maxDepth = 1; |
572 | 0 | maxLevel = CeilingLog2(mContext->mGLMaxTextureSize); |
573 | 0 | break; |
574 | 0 |
|
575 | 0 | case LOCAL_GL_TEXTURE_3D: |
576 | 0 | maxWidthHeight = mContext->mGLMax3DTextureSize >> level; |
577 | 0 | maxDepth = maxWidthHeight; |
578 | 0 | maxLevel = CeilingLog2(mContext->mGLMax3DTextureSize); |
579 | 0 | break; |
580 | 0 |
|
581 | 0 | case LOCAL_GL_TEXTURE_2D_ARRAY: |
582 | 0 | maxWidthHeight = mContext->mGLMaxTextureSize >> level; |
583 | 0 | // "The maximum number of layers for two-dimensional array textures (depth) |
584 | 0 | // must be at least MAX_ARRAY_TEXTURE_LAYERS for all levels." |
585 | 0 | maxDepth = mContext->mGLMaxArrayTextureLayers; |
586 | 0 | maxLevel = CeilingLog2(mContext->mGLMaxTextureSize); |
587 | 0 | break; |
588 | 0 |
|
589 | 0 | default: // cube maps |
590 | 0 | MOZ_ASSERT(IsCubeMap()); |
591 | 0 | maxWidthHeight = mContext->mGLMaxCubeMapTextureSize >> level; |
592 | 0 | maxDepth = 1; |
593 | 0 | maxLevel = CeilingLog2(mContext->mGLMaxCubeMapTextureSize); |
594 | 0 | break; |
595 | 0 | } |
596 | 0 |
|
597 | 0 | if (level > maxLevel) { |
598 | 0 | mContext->ErrorInvalidValue("Requested level is not supported for target."); |
599 | 0 | return false; |
600 | 0 | } |
601 | 0 | |
602 | 0 | if (width > maxWidthHeight || |
603 | 0 | height > maxWidthHeight || |
604 | 0 | depth > maxDepth) |
605 | 0 | { |
606 | 0 | mContext->ErrorInvalidValue("Requested size at this level is unsupported."); |
607 | 0 | return false; |
608 | 0 | } |
609 | 0 | |
610 | 0 | { |
611 | 0 | /* GL ES Version 2.0.25 - 3.7.1 Texture Image Specification |
612 | 0 | * "If level is greater than zero, and either width or |
613 | 0 | * height is not a power-of-two, the error INVALID_VALUE is |
614 | 0 | * generated." |
615 | 0 | * |
616 | 0 | * This restriction does not apply to GL ES Version 3.0+. |
617 | 0 | */ |
618 | 0 | bool requirePOT = (!mContext->IsWebGL2() && level != 0); |
619 | 0 |
|
620 | 0 | if (requirePOT) { |
621 | 0 | if (!IsPowerOfTwo(width) || !IsPowerOfTwo(height)) { |
622 | 0 | mContext->ErrorInvalidValue("For level > 0, width and height must be" |
623 | 0 | " powers of two."); |
624 | 0 | return false; |
625 | 0 | } |
626 | 0 | } |
627 | 0 | } |
628 | 0 | |
629 | 0 | *out_imageInfo = imageInfo; |
630 | 0 | return true; |
631 | 0 | } |
632 | | |
633 | | // For *TexSubImage* |
634 | | bool |
635 | | WebGLTexture::ValidateTexImageSelection(TexImageTarget target, |
636 | | GLint level, GLint xOffset, GLint yOffset, |
637 | | GLint zOffset, uint32_t width, uint32_t height, |
638 | | uint32_t depth, |
639 | | WebGLTexture::ImageInfo** const out_imageInfo) |
640 | 0 | { |
641 | 0 | // The conformance test wants bad arg checks before imageInfo checks. |
642 | 0 | if (xOffset < 0 || yOffset < 0 || zOffset < 0) { |
643 | 0 | mContext->ErrorInvalidValue("Offsets must be >=0."); |
644 | 0 | return false; |
645 | 0 | } |
646 | 0 | |
647 | 0 | WebGLTexture::ImageInfo* imageInfo; |
648 | 0 | if (!ValidateTexImage(mContext, this, target, level, &imageInfo)) |
649 | 0 | return false; |
650 | 0 | |
651 | 0 | if (!imageInfo->IsDefined()) { |
652 | 0 | mContext->ErrorInvalidOperation("The specified TexImage has not yet been" |
653 | 0 | " specified."); |
654 | 0 | return false; |
655 | 0 | } |
656 | 0 | |
657 | 0 | const auto totalX = CheckedUint32(xOffset) + width; |
658 | 0 | const auto totalY = CheckedUint32(yOffset) + height; |
659 | 0 | const auto totalZ = CheckedUint32(zOffset) + depth; |
660 | 0 |
|
661 | 0 | if (!totalX.isValid() || totalX.value() > imageInfo->mWidth || |
662 | 0 | !totalY.isValid() || totalY.value() > imageInfo->mHeight || |
663 | 0 | !totalZ.isValid() || totalZ.value() > imageInfo->mDepth) |
664 | 0 | { |
665 | 0 | mContext->ErrorInvalidValue("Offset+size must be <= the size of the existing" |
666 | 0 | " specified image."); |
667 | 0 | return false; |
668 | 0 | } |
669 | 0 | |
670 | 0 | *out_imageInfo = imageInfo; |
671 | 0 | return true; |
672 | 0 | } |
673 | | |
674 | | static bool |
675 | | ValidateCompressedTexUnpack(WebGLContext* webgl, GLsizei width, |
676 | | GLsizei height, GLsizei depth, |
677 | | const webgl::FormatInfo* format, size_t dataSize) |
678 | 0 | { |
679 | 0 | auto compression = format->compression; |
680 | 0 |
|
681 | 0 | auto bytesPerBlock = compression->bytesPerBlock; |
682 | 0 | auto blockWidth = compression->blockWidth; |
683 | 0 | auto blockHeight = compression->blockHeight; |
684 | 0 |
|
685 | 0 | auto widthInBlocks = CheckedUint32(width) / blockWidth; |
686 | 0 | auto heightInBlocks = CheckedUint32(height) / blockHeight; |
687 | 0 | if (width % blockWidth) widthInBlocks += 1; |
688 | 0 | if (height % blockHeight) heightInBlocks += 1; |
689 | 0 |
|
690 | 0 | const CheckedUint32 blocksPerImage = widthInBlocks * heightInBlocks; |
691 | 0 | const CheckedUint32 bytesPerImage = bytesPerBlock * blocksPerImage; |
692 | 0 | const CheckedUint32 bytesNeeded = bytesPerImage * depth; |
693 | 0 |
|
694 | 0 | if (!bytesNeeded.isValid()) { |
695 | 0 | webgl->ErrorOutOfMemory("Overflow while computing the needed buffer size."); |
696 | 0 | return false; |
697 | 0 | } |
698 | 0 | |
699 | 0 | if (dataSize != bytesNeeded.value()) { |
700 | 0 | webgl->ErrorInvalidValue("Provided buffer's size must match expected size." |
701 | 0 | " (needs %u, has %zu)", |
702 | 0 | bytesNeeded.value(), dataSize); |
703 | 0 | return false; |
704 | 0 | } |
705 | 0 | |
706 | 0 | return true; |
707 | 0 | } |
708 | | |
709 | | static bool |
710 | | DoChannelsMatchForCopyTexImage(const webgl::FormatInfo* srcFormat, |
711 | | const webgl::FormatInfo* dstFormat) |
712 | 0 | { |
713 | 0 | // GLES 3.0.4 p140 Table 3.16 "Valid CopyTexImage source framebuffer/destination |
714 | 0 | // texture base internal format combinations." |
715 | 0 |
|
716 | 0 | switch (srcFormat->unsizedFormat) { |
717 | 0 | case webgl::UnsizedFormat::RGBA: |
718 | 0 | switch (dstFormat->unsizedFormat) { |
719 | 0 | case webgl::UnsizedFormat::A: |
720 | 0 | case webgl::UnsizedFormat::L: |
721 | 0 | case webgl::UnsizedFormat::LA: |
722 | 0 | case webgl::UnsizedFormat::R: |
723 | 0 | case webgl::UnsizedFormat::RG: |
724 | 0 | case webgl::UnsizedFormat::RGB: |
725 | 0 | case webgl::UnsizedFormat::RGBA: |
726 | 0 | return true; |
727 | 0 | default: |
728 | 0 | return false; |
729 | 0 | } |
730 | 0 | |
731 | 0 | case webgl::UnsizedFormat::RGB: |
732 | 0 | switch (dstFormat->unsizedFormat) { |
733 | 0 | case webgl::UnsizedFormat::L: |
734 | 0 | case webgl::UnsizedFormat::R: |
735 | 0 | case webgl::UnsizedFormat::RG: |
736 | 0 | case webgl::UnsizedFormat::RGB: |
737 | 0 | return true; |
738 | 0 | default: |
739 | 0 | return false; |
740 | 0 | } |
741 | 0 | |
742 | 0 | case webgl::UnsizedFormat::RG: |
743 | 0 | switch (dstFormat->unsizedFormat) { |
744 | 0 | case webgl::UnsizedFormat::L: |
745 | 0 | case webgl::UnsizedFormat::R: |
746 | 0 | case webgl::UnsizedFormat::RG: |
747 | 0 | return true; |
748 | 0 | default: |
749 | 0 | return false; |
750 | 0 | } |
751 | 0 | |
752 | 0 | case webgl::UnsizedFormat::R: |
753 | 0 | switch (dstFormat->unsizedFormat) { |
754 | 0 | case webgl::UnsizedFormat::L: |
755 | 0 | case webgl::UnsizedFormat::R: |
756 | 0 | return true; |
757 | 0 | default: |
758 | 0 | return false; |
759 | 0 | } |
760 | 0 | |
761 | 0 | default: |
762 | 0 | return false; |
763 | 0 | } |
764 | 0 | } |
765 | | |
766 | | static bool |
767 | | EnsureImageDataInitializedForUpload(WebGLTexture* tex, |
768 | | TexImageTarget target, GLint level, GLint xOffset, |
769 | | GLint yOffset, GLint zOffset, uint32_t width, |
770 | | uint32_t height, uint32_t depth, |
771 | | WebGLTexture::ImageInfo* imageInfo, |
772 | | bool* const out_uploadWillInitialize) |
773 | 0 | { |
774 | 0 | *out_uploadWillInitialize = false; |
775 | 0 |
|
776 | 0 | if (!imageInfo->IsDataInitialized()) { |
777 | 0 | const bool isFullUpload = (!xOffset && !yOffset && !zOffset && |
778 | 0 | width == imageInfo->mWidth && |
779 | 0 | height == imageInfo->mHeight && |
780 | 0 | depth == imageInfo->mDepth); |
781 | 0 | if (isFullUpload) { |
782 | 0 | *out_uploadWillInitialize = true; |
783 | 0 | } else { |
784 | 0 | WebGLContext* webgl = tex->mContext; |
785 | 0 | webgl->GenerateWarning("Texture has not been initialized prior to a" |
786 | 0 | " partial upload, forcing the browser to clear it." |
787 | 0 | " This may be slow."); |
788 | 0 | if (!tex->InitializeImageData(target, level)) { |
789 | 0 | MOZ_ASSERT(false, "Unexpected failure to init image data."); |
790 | 0 | return false; |
791 | 0 | } |
792 | 0 | } |
793 | 0 | } |
794 | 0 |
|
795 | 0 | return true; |
796 | 0 | } |
797 | | |
798 | | ////////////////////////////////////////////////////////////////////////////////////////// |
799 | | ////////////////////////////////////////////////////////////////////////////////////////// |
800 | | // Actual calls |
801 | | |
802 | | static inline GLenum |
803 | | DoTexStorage(gl::GLContext* gl, TexTarget target, GLsizei levels, GLenum sizedFormat, |
804 | | GLsizei width, GLsizei height, GLsizei depth) |
805 | 0 | { |
806 | 0 | gl::GLContext::LocalErrorScope errorScope(*gl); |
807 | 0 |
|
808 | 0 | switch (target.get()) { |
809 | 0 | case LOCAL_GL_TEXTURE_2D: |
810 | 0 | case LOCAL_GL_TEXTURE_CUBE_MAP: |
811 | 0 | MOZ_ASSERT(depth == 1); |
812 | 0 | gl->fTexStorage2D(target.get(), levels, sizedFormat, width, height); |
813 | 0 | break; |
814 | 0 |
|
815 | 0 | case LOCAL_GL_TEXTURE_3D: |
816 | 0 | case LOCAL_GL_TEXTURE_2D_ARRAY: |
817 | 0 | gl->fTexStorage3D(target.get(), levels, sizedFormat, width, height, depth); |
818 | 0 | break; |
819 | 0 |
|
820 | 0 | default: |
821 | 0 | MOZ_CRASH("GFX: bad target"); |
822 | 0 | } |
823 | 0 |
|
824 | 0 | return errorScope.GetError(); |
825 | 0 | } |
826 | | |
827 | | bool |
828 | | IsTarget3D(TexImageTarget target) |
829 | 0 | { |
830 | 0 | switch (target.get()) { |
831 | 0 | case LOCAL_GL_TEXTURE_2D: |
832 | 0 | case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X: |
833 | 0 | case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X: |
834 | 0 | case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y: |
835 | 0 | case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: |
836 | 0 | case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z: |
837 | 0 | case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: |
838 | 0 | return false; |
839 | 0 |
|
840 | 0 | case LOCAL_GL_TEXTURE_3D: |
841 | 0 | case LOCAL_GL_TEXTURE_2D_ARRAY: |
842 | 0 | return true; |
843 | 0 |
|
844 | 0 | default: |
845 | 0 | MOZ_CRASH("GFX: bad target"); |
846 | 0 | } |
847 | 0 | } |
848 | | |
849 | | GLenum |
850 | | DoTexImage(gl::GLContext* gl, TexImageTarget target, GLint level, |
851 | | const webgl::DriverUnpackInfo* dui, GLsizei width, GLsizei height, |
852 | | GLsizei depth, const void* data) |
853 | 0 | { |
854 | 0 | const GLint border = 0; |
855 | 0 |
|
856 | 0 | gl::GLContext::LocalErrorScope errorScope(*gl); |
857 | 0 |
|
858 | 0 | if (IsTarget3D(target)) { |
859 | 0 | gl->fTexImage3D(target.get(), level, dui->internalFormat, width, height, depth, |
860 | 0 | border, dui->unpackFormat, dui->unpackType, data); |
861 | 0 | } else { |
862 | 0 | MOZ_ASSERT(depth == 1); |
863 | 0 | gl->fTexImage2D(target.get(), level, dui->internalFormat, width, height, border, |
864 | 0 | dui->unpackFormat, dui->unpackType, data); |
865 | 0 | } |
866 | 0 |
|
867 | 0 | return errorScope.GetError(); |
868 | 0 | } |
869 | | |
870 | | GLenum |
871 | | DoTexSubImage(gl::GLContext* gl, TexImageTarget target, GLint level, GLint xOffset, |
872 | | GLint yOffset, GLint zOffset, GLsizei width, GLsizei height, GLsizei depth, |
873 | | const webgl::PackingInfo& pi, const void* data) |
874 | 0 | { |
875 | 0 | gl::GLContext::LocalErrorScope errorScope(*gl); |
876 | 0 |
|
877 | 0 | if (IsTarget3D(target)) { |
878 | 0 | gl->fTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset, width, height, |
879 | 0 | depth, pi.format, pi.type, data); |
880 | 0 | } else { |
881 | 0 | MOZ_ASSERT(zOffset == 0); |
882 | 0 | MOZ_ASSERT(depth == 1); |
883 | 0 | gl->fTexSubImage2D(target.get(), level, xOffset, yOffset, width, height, |
884 | 0 | pi.format, pi.type, data); |
885 | 0 | } |
886 | 0 |
|
887 | 0 | return errorScope.GetError(); |
888 | 0 | } |
889 | | |
890 | | static inline GLenum |
891 | | DoCompressedTexImage(gl::GLContext* gl, TexImageTarget target, GLint level, |
892 | | GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, |
893 | | GLsizei dataSize, const void* data) |
894 | 0 | { |
895 | 0 | const GLint border = 0; |
896 | 0 |
|
897 | 0 | gl::GLContext::LocalErrorScope errorScope(*gl); |
898 | 0 |
|
899 | 0 | if (IsTarget3D(target)) { |
900 | 0 | gl->fCompressedTexImage3D(target.get(), level, internalFormat, width, height, |
901 | 0 | depth, border, dataSize, data); |
902 | 0 | } else { |
903 | 0 | MOZ_ASSERT(depth == 1); |
904 | 0 | gl->fCompressedTexImage2D(target.get(), level, internalFormat, width, height, |
905 | 0 | border, dataSize, data); |
906 | 0 | } |
907 | 0 |
|
908 | 0 | return errorScope.GetError(); |
909 | 0 | } |
910 | | |
911 | | GLenum |
912 | | DoCompressedTexSubImage(gl::GLContext* gl, TexImageTarget target, GLint level, |
913 | | GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width, |
914 | | GLsizei height, GLsizei depth, GLenum sizedUnpackFormat, |
915 | | GLsizei dataSize, const void* data) |
916 | 0 | { |
917 | 0 | gl::GLContext::LocalErrorScope errorScope(*gl); |
918 | 0 |
|
919 | 0 | if (IsTarget3D(target)) { |
920 | 0 | gl->fCompressedTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset, |
921 | 0 | width, height, depth, sizedUnpackFormat, dataSize, |
922 | 0 | data); |
923 | 0 | } else { |
924 | 0 | MOZ_ASSERT(zOffset == 0); |
925 | 0 | MOZ_ASSERT(depth == 1); |
926 | 0 | gl->fCompressedTexSubImage2D(target.get(), level, xOffset, yOffset, width, |
927 | 0 | height, sizedUnpackFormat, dataSize, data); |
928 | 0 | } |
929 | 0 |
|
930 | 0 | return errorScope.GetError(); |
931 | 0 | } |
932 | | |
933 | | static inline GLenum |
934 | | DoCopyTexSubImage(gl::GLContext* gl, TexImageTarget target, GLint level, GLint xOffset, |
935 | | GLint yOffset, GLint zOffset, GLint x, GLint y, GLsizei width, |
936 | | GLsizei height) |
937 | 0 | { |
938 | 0 | gl::GLContext::LocalErrorScope errorScope(*gl); |
939 | 0 |
|
940 | 0 | if (IsTarget3D(target)) { |
941 | 0 | gl->fCopyTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset, x, y, |
942 | 0 | width, height); |
943 | 0 | } else { |
944 | 0 | MOZ_ASSERT(zOffset == 0); |
945 | 0 | gl->fCopyTexSubImage2D(target.get(), level, xOffset, yOffset, x, y, width, |
946 | 0 | height); |
947 | 0 | } |
948 | 0 |
|
949 | 0 | return errorScope.GetError(); |
950 | 0 | } |
951 | | |
952 | | ////////////////////////////////////////////////////////////////////////////////////////// |
953 | | ////////////////////////////////////////////////////////////////////////////////////////// |
954 | | // Actual (mostly generic) function implementations |
955 | | |
956 | | static bool |
957 | | ValidateCompressedTexImageRestrictions(WebGLContext* webgl, |
958 | | TexImageTarget target, uint32_t level, |
959 | | const webgl::FormatInfo* format, uint32_t width, |
960 | | uint32_t height, uint32_t depth) |
961 | 0 | { |
962 | 0 | const auto fnIsDimValid_S3TC = [level](uint32_t size, uint32_t blockSize) { |
963 | 0 | if (size % blockSize == 0) |
964 | 0 | return true; |
965 | 0 | |
966 | 0 | if (level == 0) |
967 | 0 | return false; |
968 | 0 | |
969 | 0 | return (size == 0 || size == 1 || size == 2); |
970 | 0 | }; |
971 | 0 |
|
972 | 0 | switch (format->compression->family) { |
973 | 0 | case webgl::CompressionFamily::ASTC: |
974 | 0 | if (target == LOCAL_GL_TEXTURE_3D && |
975 | 0 | !webgl->gl->IsExtensionSupported(gl::GLContext::KHR_texture_compression_astc_hdr)) |
976 | 0 | { |
977 | 0 | webgl->ErrorInvalidOperation("TEXTURE_3D requires ASTC's hdr profile."); |
978 | 0 | return false; |
979 | 0 | } |
980 | 0 | break; |
981 | 0 |
|
982 | 0 | case webgl::CompressionFamily::PVRTC: |
983 | 0 | if (!IsPowerOfTwo(width) || !IsPowerOfTwo(height)) { |
984 | 0 | webgl->ErrorInvalidValue("%s requires power-of-two width and height.", |
985 | 0 | format->name); |
986 | 0 | return false; |
987 | 0 | } |
988 | 0 | |
989 | 0 | break; |
990 | 0 |
|
991 | 0 | case webgl::CompressionFamily::S3TC: |
992 | 0 | if (!fnIsDimValid_S3TC(width, format->compression->blockWidth) || |
993 | 0 | !fnIsDimValid_S3TC(height, format->compression->blockHeight)) |
994 | 0 | { |
995 | 0 | webgl->ErrorInvalidOperation("%s requires that width and height are" |
996 | 0 | " block-aligned, or, if level>0, equal to 0, 1," |
997 | 0 | " or 2.", |
998 | 0 | format->name); |
999 | 0 | return false; |
1000 | 0 | } |
1001 | 0 | |
1002 | 0 | break; |
1003 | 0 |
|
1004 | 0 | // Default: There are no restrictions on CompressedTexImage. |
1005 | 0 | default: // ATC, ETC1, ES3 |
1006 | 0 | break; |
1007 | 0 | } |
1008 | 0 | |
1009 | 0 | return true; |
1010 | 0 | } |
1011 | | |
1012 | | static bool |
1013 | | ValidateTargetForFormat(WebGLContext* webgl, TexImageTarget target, |
1014 | | const webgl::FormatInfo* format) |
1015 | 0 | { |
1016 | 0 | // GLES 3.0.4 p127: |
1017 | 0 | // "Textures with a base internal format of DEPTH_COMPONENT or DEPTH_STENCIL are |
1018 | 0 | // supported by texture image specification commands only if `target` is TEXTURE_2D, |
1019 | 0 | // TEXTURE_2D_ARRAY, or TEXTURE_CUBE_MAP. Using these formats in conjunction with any |
1020 | 0 | // other `target` will result in an INVALID_OPERATION error." |
1021 | 0 |
|
1022 | 0 | switch (format->effectiveFormat) { |
1023 | 0 | // TEXTURE_2D_ARRAY but not TEXTURE_3D: |
1024 | 0 | // D and DS formats |
1025 | 0 | case webgl::EffectiveFormat::DEPTH_COMPONENT16: |
1026 | 0 | case webgl::EffectiveFormat::DEPTH_COMPONENT24: |
1027 | 0 | case webgl::EffectiveFormat::DEPTH_COMPONENT32F: |
1028 | 0 | case webgl::EffectiveFormat::DEPTH24_STENCIL8: |
1029 | 0 | case webgl::EffectiveFormat::DEPTH32F_STENCIL8: |
1030 | 0 | // CompressionFamily::ES3 |
1031 | 0 | case webgl::EffectiveFormat::COMPRESSED_R11_EAC: |
1032 | 0 | case webgl::EffectiveFormat::COMPRESSED_SIGNED_R11_EAC: |
1033 | 0 | case webgl::EffectiveFormat::COMPRESSED_RG11_EAC: |
1034 | 0 | case webgl::EffectiveFormat::COMPRESSED_SIGNED_RG11_EAC: |
1035 | 0 | case webgl::EffectiveFormat::COMPRESSED_RGB8_ETC2: |
1036 | 0 | case webgl::EffectiveFormat::COMPRESSED_SRGB8_ETC2: |
1037 | 0 | case webgl::EffectiveFormat::COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2: |
1038 | 0 | case webgl::EffectiveFormat::COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2: |
1039 | 0 | case webgl::EffectiveFormat::COMPRESSED_RGBA8_ETC2_EAC: |
1040 | 0 | case webgl::EffectiveFormat::COMPRESSED_SRGB8_ALPHA8_ETC2_EAC: |
1041 | 0 | // CompressionFamily::S3TC |
1042 | 0 | case webgl::EffectiveFormat::COMPRESSED_RGB_S3TC_DXT1_EXT: |
1043 | 0 | case webgl::EffectiveFormat::COMPRESSED_RGBA_S3TC_DXT1_EXT: |
1044 | 0 | case webgl::EffectiveFormat::COMPRESSED_RGBA_S3TC_DXT3_EXT: |
1045 | 0 | case webgl::EffectiveFormat::COMPRESSED_RGBA_S3TC_DXT5_EXT: |
1046 | 0 | if (target == LOCAL_GL_TEXTURE_3D) { |
1047 | 0 | webgl->ErrorInvalidOperation("Format %s cannot be used with TEXTURE_3D.", |
1048 | 0 | format->name); |
1049 | 0 | return false; |
1050 | 0 | } |
1051 | 0 | break; |
1052 | 0 |
|
1053 | 0 | // No 3D targets: |
1054 | 0 | // CompressionFamily::ATC |
1055 | 0 | case webgl::EffectiveFormat::ATC_RGB_AMD: |
1056 | 0 | case webgl::EffectiveFormat::ATC_RGBA_EXPLICIT_ALPHA_AMD: |
1057 | 0 | case webgl::EffectiveFormat::ATC_RGBA_INTERPOLATED_ALPHA_AMD: |
1058 | 0 | // CompressionFamily::PVRTC |
1059 | 0 | case webgl::EffectiveFormat::COMPRESSED_RGB_PVRTC_4BPPV1: |
1060 | 0 | case webgl::EffectiveFormat::COMPRESSED_RGBA_PVRTC_4BPPV1: |
1061 | 0 | case webgl::EffectiveFormat::COMPRESSED_RGB_PVRTC_2BPPV1: |
1062 | 0 | case webgl::EffectiveFormat::COMPRESSED_RGBA_PVRTC_2BPPV1: |
1063 | 0 | // CompressionFamily::ETC1 |
1064 | 0 | case webgl::EffectiveFormat::ETC1_RGB8_OES: |
1065 | 0 | if (target == LOCAL_GL_TEXTURE_3D || |
1066 | 0 | target == LOCAL_GL_TEXTURE_2D_ARRAY) |
1067 | 0 | { |
1068 | 0 | webgl->ErrorInvalidOperation("Format %s cannot be used with TEXTURE_3D or" |
1069 | 0 | " TEXTURE_2D_ARRAY.", |
1070 | 0 | format->name); |
1071 | 0 | return false; |
1072 | 0 | } |
1073 | 0 | break; |
1074 | 0 |
|
1075 | 0 | default: |
1076 | 0 | break; |
1077 | 0 | } |
1078 | 0 | |
1079 | 0 | return true; |
1080 | 0 | } |
1081 | | |
1082 | | void |
1083 | | WebGLTexture::TexStorage(TexTarget target, GLsizei levels, |
1084 | | GLenum sizedFormat, GLsizei width, GLsizei height, GLsizei depth) |
1085 | 0 | { |
1086 | 0 | // Check levels |
1087 | 0 | if (levels < 1) { |
1088 | 0 | mContext->ErrorInvalidValue("`levels` must be >= 1."); |
1089 | 0 | return; |
1090 | 0 | } |
1091 | 0 | |
1092 | 0 | if (!width || !height || !depth) { |
1093 | 0 | mContext->ErrorInvalidValue("Dimensions must be non-zero."); |
1094 | 0 | return; |
1095 | 0 | } |
1096 | 0 | |
1097 | 0 | const TexImageTarget testTarget = IsCubeMap() ? LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X |
1098 | 0 | : target.get(); |
1099 | 0 | WebGLTexture::ImageInfo* baseImageInfo; |
1100 | 0 | if (!ValidateTexImageSpecification(testTarget, 0, width, height, depth, |
1101 | 0 | &baseImageInfo)) |
1102 | 0 | { |
1103 | 0 | return; |
1104 | 0 | } |
1105 | 0 | MOZ_ALWAYS_TRUE(baseImageInfo); |
1106 | 0 |
|
1107 | 0 | auto dstUsage = mContext->mFormatUsage->GetSizedTexUsage(sizedFormat); |
1108 | 0 | if (!dstUsage) { |
1109 | 0 | mContext->ErrorInvalidEnumInfo("internalformat", sizedFormat); |
1110 | 0 | return; |
1111 | 0 | } |
1112 | 0 | auto dstFormat = dstUsage->format; |
1113 | 0 |
|
1114 | 0 | if (!ValidateTargetForFormat(mContext, testTarget, dstFormat)) |
1115 | 0 | return; |
1116 | 0 | |
1117 | 0 | if (dstFormat->compression) { |
1118 | 0 | if (!ValidateCompressedTexImageRestrictions(mContext, testTarget, 0, |
1119 | 0 | dstFormat, width, height, depth)) |
1120 | 0 | { |
1121 | 0 | return; |
1122 | 0 | } |
1123 | 0 | } |
1124 | 0 | |
1125 | 0 | //////////////////////////////////// |
1126 | 0 | |
1127 | 0 | const bool levelsOk = [&]() { |
1128 | 0 | // Right-shift is only defined for bits-1, which is too large anyways. |
1129 | 0 | const auto lastLevel = uint32_t(levels - 1); |
1130 | 0 | if (lastLevel > 31) |
1131 | 0 | return false; |
1132 | 0 | |
1133 | 0 | const auto lastLevelWidth = uint32_t(width) >> lastLevel; |
1134 | 0 | const auto lastLevelHeight = uint32_t(height) >> lastLevel; |
1135 | 0 |
|
1136 | 0 | // If these are all zero, then some earlier level was the final 1x1(x1) level. |
1137 | 0 | bool ok = lastLevelWidth || lastLevelHeight; |
1138 | 0 | if (target == LOCAL_GL_TEXTURE_3D) { |
1139 | 0 | const auto lastLevelDepth = uint32_t(depth) >> lastLevel; |
1140 | 0 | ok |= bool(lastLevelDepth); |
1141 | 0 | } |
1142 | 0 | return ok; |
1143 | 0 | }(); |
1144 | 0 | if (!levelsOk) { |
1145 | 0 | mContext->ErrorInvalidOperation("Too many levels requested for the given" |
1146 | 0 | " dimensions. (levels: %u, width: %u, height: %u," |
1147 | 0 | " depth: %u)", |
1148 | 0 | levels, width, height, depth); |
1149 | 0 | return; |
1150 | 0 | } |
1151 | 0 | |
1152 | 0 | //////////////////////////////////// |
1153 | 0 | // Do the thing! |
1154 | 0 | |
1155 | 0 | GLenum error = DoTexStorage(mContext->gl, target.get(), levels, sizedFormat, width, |
1156 | 0 | height, depth); |
1157 | 0 |
|
1158 | 0 | mContext->OnDataAllocCall(); |
1159 | 0 |
|
1160 | 0 | if (error == LOCAL_GL_OUT_OF_MEMORY) { |
1161 | 0 | mContext->ErrorOutOfMemory("Ran out of memory during texture allocation."); |
1162 | 0 | return; |
1163 | 0 | } |
1164 | 0 | if (error) { |
1165 | 0 | MOZ_RELEASE_ASSERT(false, "GFX: We should have caught all other errors."); |
1166 | 0 | mContext->ErrorInvalidOperation("Unexpected error during texture allocation."); |
1167 | 0 | return; |
1168 | 0 | } |
1169 | 0 | |
1170 | 0 | //////////////////////////////////// |
1171 | 0 | // Update our specification data. |
1172 | 0 | |
1173 | 0 | const bool isDataInitialized = false; |
1174 | 0 | const WebGLTexture::ImageInfo newInfo(dstUsage, width, height, depth, |
1175 | 0 | isDataInitialized); |
1176 | 0 | SetImageInfosAtLevel(0, newInfo); |
1177 | 0 |
|
1178 | 0 | PopulateMipChain(0, levels-1); |
1179 | 0 |
|
1180 | 0 | mImmutable = true; |
1181 | 0 | mImmutableLevelCount = levels; |
1182 | 0 | } |
1183 | | |
1184 | | //////////////////////////////////////// |
1185 | | // Tex(Sub)Image |
1186 | | |
1187 | | void |
1188 | | WebGLTexture::TexImage(TexImageTarget target, GLint level, |
1189 | | GLenum internalFormat, const webgl::PackingInfo& pi, |
1190 | | const webgl::TexUnpackBlob* blob) |
1191 | 0 | { |
1192 | 0 | //////////////////////////////////// |
1193 | 0 | // Get dest info |
1194 | 0 |
|
1195 | 0 | WebGLTexture::ImageInfo* imageInfo; |
1196 | 0 | if (!ValidateTexImageSpecification(target, level, blob->mWidth, |
1197 | 0 | blob->mHeight, blob->mDepth, &imageInfo)) |
1198 | 0 | { |
1199 | 0 | return; |
1200 | 0 | } |
1201 | 0 | MOZ_ASSERT(imageInfo); |
1202 | 0 |
|
1203 | 0 | const auto& fua = mContext->mFormatUsage; |
1204 | 0 | if (!fua->IsInternalFormatEnumValid(internalFormat)) { |
1205 | 0 | mContext->ErrorInvalidValue("Invalid internalformat: 0x%04x", |
1206 | 0 | internalFormat); |
1207 | 0 | return; |
1208 | 0 | } |
1209 | 0 | |
1210 | 0 | auto dstUsage = fua->GetSizedTexUsage(internalFormat); |
1211 | 0 | if (!dstUsage) { |
1212 | 0 | if (internalFormat != pi.format) { |
1213 | 0 | /* GL ES Version 3.0.4 - 3.8.3 Texture Image Specification |
1214 | 0 | * "Specifying a combination of values for format, type, and |
1215 | 0 | * internalformat that is not listed as a valid combination |
1216 | 0 | * in tables 3.2 or 3.3 generates the error INVALID_OPERATION." |
1217 | 0 | */ |
1218 | 0 | mContext->ErrorInvalidOperation("Unsized internalFormat must match" |
1219 | 0 | " unpack format."); |
1220 | 0 | return; |
1221 | 0 | } |
1222 | 0 | |
1223 | 0 | dstUsage = fua->GetUnsizedTexUsage(pi); |
1224 | 0 | } |
1225 | 0 |
|
1226 | 0 | if (!dstUsage) { |
1227 | 0 | mContext->ErrorInvalidOperation("Invalid internalformat/format/type:" |
1228 | 0 | " 0x%04x/0x%04x/0x%04x", |
1229 | 0 | internalFormat, pi.format, pi.type); |
1230 | 0 | return; |
1231 | 0 | } |
1232 | 0 | |
1233 | 0 | const webgl::DriverUnpackInfo* driverUnpackInfo; |
1234 | 0 | if (!dstUsage->IsUnpackValid(pi, &driverUnpackInfo)) { |
1235 | 0 | mContext->ErrorInvalidOperation("Mismatched internalFormat and format/type:" |
1236 | 0 | " 0x%04x and 0x%04x/0x%04x", |
1237 | 0 | internalFormat, pi.format, pi.type); |
1238 | 0 | return; |
1239 | 0 | } |
1240 | 0 | |
1241 | 0 | //////////////////////////////////// |
1242 | 0 | // Check that source and dest info are compatible |
1243 | 0 | auto dstFormat = dstUsage->format; |
1244 | 0 |
|
1245 | 0 | if (!ValidateTargetForFormat(mContext, target, dstFormat)) |
1246 | 0 | return; |
1247 | 0 | |
1248 | 0 | if (!mContext->IsWebGL2() && dstFormat->d) { |
1249 | 0 | if (target != LOCAL_GL_TEXTURE_2D || |
1250 | 0 | blob->HasData() || |
1251 | 0 | level != 0) |
1252 | 0 | { |
1253 | 0 | mContext->ErrorInvalidOperation("With format %s, this function may only" |
1254 | 0 | " be called with target=TEXTURE_2D," |
1255 | 0 | " data=null, and level=0.", |
1256 | 0 | dstFormat->name); |
1257 | 0 | return; |
1258 | 0 | } |
1259 | 0 | } |
1260 | 0 | |
1261 | 0 | //////////////////////////////////// |
1262 | 0 | // Do the thing! |
1263 | 0 | |
1264 | 0 | // It's tempting to do allocation first, and TexSubImage second, but this is generally |
1265 | 0 | // slower. |
1266 | 0 | |
1267 | 0 | const ImageInfo newImageInfo(dstUsage, blob->mWidth, blob->mHeight, blob->mDepth, |
1268 | 0 | blob->HasData()); |
1269 | 0 |
|
1270 | 0 | const bool isSubImage = false; |
1271 | 0 | const bool needsRespec = (imageInfo->mWidth != newImageInfo.mWidth || |
1272 | 0 | imageInfo->mHeight != newImageInfo.mHeight || |
1273 | 0 | imageInfo->mDepth != newImageInfo.mDepth || |
1274 | 0 | imageInfo->mFormat != newImageInfo.mFormat); |
1275 | 0 | const GLint xOffset = 0; |
1276 | 0 | const GLint yOffset = 0; |
1277 | 0 | const GLint zOffset = 0; |
1278 | 0 |
|
1279 | 0 | GLenum glError; |
1280 | 0 | if (!blob->TexOrSubImage(isSubImage, needsRespec, this, target, level, |
1281 | 0 | driverUnpackInfo, xOffset, yOffset, zOffset, pi, &glError)) |
1282 | 0 | { |
1283 | 0 | return; |
1284 | 0 | } |
1285 | 0 | |
1286 | 0 | mContext->OnDataAllocCall(); |
1287 | 0 |
|
1288 | 0 | if (glError == LOCAL_GL_OUT_OF_MEMORY) { |
1289 | 0 | mContext->ErrorOutOfMemory("Driver ran out of memory during upload."); |
1290 | 0 | return; |
1291 | 0 | } |
1292 | 0 | |
1293 | 0 | if (glError) { |
1294 | 0 | const auto enumStr = EnumString(glError); |
1295 | 0 | const nsPrintfCString dui("Unexpected error %s during upload. (dui: %x/%x/%x)", |
1296 | 0 | enumStr.c_str(), driverUnpackInfo->internalFormat, |
1297 | 0 | driverUnpackInfo->unpackFormat, |
1298 | 0 | driverUnpackInfo->unpackType); |
1299 | 0 | mContext->ErrorInvalidOperation("%s", dui.BeginReading()); |
1300 | 0 | gfxCriticalError() << mContext->FuncName() << ": " << dui.BeginReading(); |
1301 | 0 | return; |
1302 | 0 | } |
1303 | 0 |
|
1304 | 0 | //////////////////////////////////// |
1305 | 0 | // Update our specification data. |
1306 | 0 |
|
1307 | 0 | SetImageInfo(imageInfo, newImageInfo); |
1308 | 0 | } |
1309 | | |
1310 | | void |
1311 | | WebGLTexture::TexSubImage(TexImageTarget target, GLint level, |
1312 | | GLint xOffset, GLint yOffset, GLint zOffset, |
1313 | | const webgl::PackingInfo& pi, const webgl::TexUnpackBlob* blob) |
1314 | 0 | { |
1315 | 0 | //////////////////////////////////// |
1316 | 0 | // Get dest info |
1317 | 0 |
|
1318 | 0 | WebGLTexture::ImageInfo* imageInfo; |
1319 | 0 | if (!ValidateTexImageSelection(target, level, xOffset, yOffset, zOffset, |
1320 | 0 | blob->mWidth, blob->mHeight, blob->mDepth, &imageInfo)) |
1321 | 0 | { |
1322 | 0 | return; |
1323 | 0 | } |
1324 | 0 | MOZ_ASSERT(imageInfo); |
1325 | 0 |
|
1326 | 0 | auto dstUsage = imageInfo->mFormat; |
1327 | 0 | auto dstFormat = dstUsage->format; |
1328 | 0 |
|
1329 | 0 | if (dstFormat->compression) { |
1330 | 0 | mContext->ErrorInvalidEnum("Specified TexImage must not be compressed."); |
1331 | 0 | return; |
1332 | 0 | } |
1333 | 0 | |
1334 | 0 | if (!mContext->IsWebGL2() && dstFormat->d) { |
1335 | 0 | mContext->ErrorInvalidOperation("Function may not be called on a texture of" |
1336 | 0 | " format %s.", |
1337 | 0 | dstFormat->name); |
1338 | 0 | return; |
1339 | 0 | } |
1340 | 0 | |
1341 | 0 | //////////////////////////////////// |
1342 | 0 | // Get source info |
1343 | 0 | |
1344 | 0 | const webgl::DriverUnpackInfo* driverUnpackInfo; |
1345 | 0 | if (!dstUsage->IsUnpackValid(pi, &driverUnpackInfo)) { |
1346 | 0 | mContext->ErrorInvalidOperation("Mismatched internalFormat and format/type:" |
1347 | 0 | " %s and 0x%04x/0x%04x", |
1348 | 0 | dstFormat->name, pi.format, pi.type); |
1349 | 0 | return; |
1350 | 0 | } |
1351 | 0 | |
1352 | 0 | //////////////////////////////////// |
1353 | 0 | // Do the thing! |
1354 | 0 | |
1355 | 0 | bool uploadWillInitialize; |
1356 | 0 | if (!EnsureImageDataInitializedForUpload(this, target, level, xOffset, |
1357 | 0 | yOffset, zOffset, blob->mWidth, |
1358 | 0 | blob->mHeight, blob->mDepth, imageInfo, |
1359 | 0 | &uploadWillInitialize)) |
1360 | 0 | { |
1361 | 0 | return; |
1362 | 0 | } |
1363 | 0 | |
1364 | 0 | const bool isSubImage = true; |
1365 | 0 | const bool needsRespec = false; |
1366 | 0 |
|
1367 | 0 | GLenum glError; |
1368 | 0 | if (!blob->TexOrSubImage(isSubImage, needsRespec, this, target, level, |
1369 | 0 | driverUnpackInfo, xOffset, yOffset, zOffset, pi, &glError)) |
1370 | 0 | { |
1371 | 0 | return; |
1372 | 0 | } |
1373 | 0 | |
1374 | 0 | if (glError == LOCAL_GL_OUT_OF_MEMORY) { |
1375 | 0 | mContext->ErrorOutOfMemory("Driver ran out of memory during upload."); |
1376 | 0 | return; |
1377 | 0 | } |
1378 | 0 | |
1379 | 0 | if (glError) { |
1380 | 0 | const auto enumStr = EnumString(glError); |
1381 | 0 | const nsPrintfCString dui("Unexpected error %s during upload. (dui: %x/%x/%x)", |
1382 | 0 | enumStr.c_str(), driverUnpackInfo->internalFormat, |
1383 | 0 | driverUnpackInfo->unpackFormat, |
1384 | 0 | driverUnpackInfo->unpackType); |
1385 | 0 | mContext->ErrorInvalidOperation("%s", dui.BeginReading()); |
1386 | 0 | gfxCriticalError() << mContext->FuncName() << ": " << dui.BeginReading(); |
1387 | 0 | return; |
1388 | 0 | } |
1389 | 0 |
|
1390 | 0 | //////////////////////////////////// |
1391 | 0 | // Update our specification data? |
1392 | 0 |
|
1393 | 0 | if (uploadWillInitialize) { |
1394 | 0 | imageInfo->SetIsDataInitialized(true, this); |
1395 | 0 | } |
1396 | 0 | } |
1397 | | |
1398 | | //////////////////////////////////////// |
1399 | | // CompressedTex(Sub)Image |
1400 | | |
1401 | | UniquePtr<webgl::TexUnpackBytes> |
1402 | | WebGLContext::FromCompressed(TexImageTarget target, |
1403 | | GLsizei rawWidth, GLsizei rawHeight, GLsizei rawDepth, |
1404 | | GLint border, const TexImageSource& src, |
1405 | | const Maybe<GLsizei>& expectedImageSize) |
1406 | 0 | { |
1407 | 0 | uint32_t width, height, depth; |
1408 | 0 | if (!ValidateExtents(this, rawWidth, rawHeight, rawDepth, border, &width, |
1409 | 0 | &height, &depth)) |
1410 | 0 | { |
1411 | 0 | return nullptr; |
1412 | 0 | } |
1413 | 0 | |
1414 | 0 | if (src.mPboOffset) { |
1415 | 0 | return FromPboOffset(this, target, width, height, depth, |
1416 | 0 | *(src.mPboOffset), expectedImageSize); |
1417 | 0 | } |
1418 | 0 | |
1419 | 0 | if (mBoundPixelUnpackBuffer) { |
1420 | 0 | ErrorInvalidOperation("PIXEL_UNPACK_BUFFER must be null."); |
1421 | 0 | return nullptr; |
1422 | 0 | } |
1423 | 0 | |
1424 | 0 | return FromView(this, target, width, height, depth, src.mView, |
1425 | 0 | src.mViewElemOffset, src.mViewElemLengthOverride); |
1426 | 0 | } |
1427 | | |
1428 | | void |
1429 | | WebGLTexture::CompressedTexImage(TexImageTarget target, GLint level, |
1430 | | GLenum internalFormat, GLsizei rawWidth, |
1431 | | GLsizei rawHeight, GLsizei rawDepth, GLint border, |
1432 | | const TexImageSource& src, const Maybe<GLsizei>& expectedImageSize) |
1433 | 0 | { |
1434 | 0 | const auto blob = mContext->FromCompressed(target, rawWidth, rawHeight, |
1435 | 0 | rawDepth, border, src, expectedImageSize); |
1436 | 0 | if (!blob) |
1437 | 0 | return; |
1438 | 0 | |
1439 | 0 | //////////////////////////////////// |
1440 | 0 | // Get dest info |
1441 | 0 | |
1442 | 0 | WebGLTexture::ImageInfo* imageInfo; |
1443 | 0 | if (!ValidateTexImageSpecification(target, level, blob->mWidth, |
1444 | 0 | blob->mHeight, blob->mDepth, &imageInfo)) |
1445 | 0 | { |
1446 | 0 | return; |
1447 | 0 | } |
1448 | 0 | MOZ_ASSERT(imageInfo); |
1449 | 0 |
|
1450 | 0 | auto usage = mContext->mFormatUsage->GetSizedTexUsage(internalFormat); |
1451 | 0 | if (!usage) { |
1452 | 0 | mContext->ErrorInvalidEnum("Invalid internalFormat: 0x%04x", |
1453 | 0 | internalFormat); |
1454 | 0 | return; |
1455 | 0 | } |
1456 | 0 | |
1457 | 0 | auto format = usage->format; |
1458 | 0 | if (!format->compression) { |
1459 | 0 | mContext->ErrorInvalidEnum("Specified internalFormat must be compressed."); |
1460 | 0 | return; |
1461 | 0 | } |
1462 | 0 | |
1463 | 0 | if (!ValidateTargetForFormat(mContext, target, format)) |
1464 | 0 | return; |
1465 | 0 | |
1466 | 0 | //////////////////////////////////// |
1467 | 0 | // Get source info |
1468 | 0 | |
1469 | 0 | if (!ValidateCompressedTexUnpack(mContext, blob->mWidth, blob->mHeight, |
1470 | 0 | blob->mDepth, format, blob->mAvailBytes)) |
1471 | 0 | { |
1472 | 0 | return; |
1473 | 0 | } |
1474 | 0 | |
1475 | 0 | //////////////////////////////////// |
1476 | 0 | // Check that source is compatible with dest |
1477 | 0 | |
1478 | 0 | if (!ValidateCompressedTexImageRestrictions(mContext, target, level, format, |
1479 | 0 | blob->mWidth, blob->mHeight, |
1480 | 0 | blob->mDepth)) |
1481 | 0 | { |
1482 | 0 | return; |
1483 | 0 | } |
1484 | 0 | |
1485 | 0 | //////////////////////////////////// |
1486 | 0 | // Do the thing! |
1487 | 0 | |
1488 | 0 | const ScopedLazyBind bindPBO(mContext->gl, LOCAL_GL_PIXEL_UNPACK_BUFFER, |
1489 | 0 | mContext->mBoundPixelUnpackBuffer); |
1490 | 0 |
|
1491 | 0 | // Warning: Possibly shared memory. See bug 1225033. |
1492 | 0 | GLenum error = DoCompressedTexImage(mContext->gl, target, level, internalFormat, |
1493 | 0 | blob->mWidth, blob->mHeight, blob->mDepth, |
1494 | 0 | blob->mAvailBytes, blob->mPtr); |
1495 | 0 | mContext->OnDataAllocCall(); |
1496 | 0 | if (error == LOCAL_GL_OUT_OF_MEMORY) { |
1497 | 0 | mContext->ErrorOutOfMemory("Ran out of memory during upload."); |
1498 | 0 | return; |
1499 | 0 | } |
1500 | 0 | if (error) { |
1501 | 0 | MOZ_RELEASE_ASSERT(false, "GFX: We should have caught all other errors."); |
1502 | 0 | mContext->GenerateWarning("Unexpected error during texture upload. Context" |
1503 | 0 | " lost."); |
1504 | 0 | mContext->ForceLoseContext(); |
1505 | 0 | return; |
1506 | 0 | } |
1507 | 0 | |
1508 | 0 | //////////////////////////////////// |
1509 | 0 | // Update our specification data. |
1510 | 0 | |
1511 | 0 | const bool isDataInitialized = true; |
1512 | 0 | const ImageInfo newImageInfo(usage, blob->mWidth, blob->mHeight, blob->mDepth, |
1513 | 0 | isDataInitialized); |
1514 | 0 | SetImageInfo(imageInfo, newImageInfo); |
1515 | 0 | } |
1516 | | |
1517 | | static inline bool |
1518 | | IsSubImageBlockAligned(const webgl::CompressedFormatInfo* compression, |
1519 | | const WebGLTexture::ImageInfo* imageInfo, GLint xOffset, |
1520 | | GLint yOffset, uint32_t width, uint32_t height) |
1521 | 0 | { |
1522 | 0 | if (xOffset % compression->blockWidth != 0 || |
1523 | 0 | yOffset % compression->blockHeight != 0) |
1524 | 0 | { |
1525 | 0 | return false; |
1526 | 0 | } |
1527 | 0 | |
1528 | 0 | if (width % compression->blockWidth != 0 && xOffset + width != imageInfo->mWidth) |
1529 | 0 | return false; |
1530 | 0 | |
1531 | 0 | if (height % compression->blockHeight != 0 && yOffset + height != imageInfo->mHeight) |
1532 | 0 | return false; |
1533 | 0 | |
1534 | 0 | return true; |
1535 | 0 | } |
1536 | | |
1537 | | void |
1538 | | WebGLTexture::CompressedTexSubImage(TexImageTarget target, |
1539 | | GLint level, GLint xOffset, GLint yOffset, |
1540 | | GLint zOffset, GLsizei rawWidth, GLsizei rawHeight, |
1541 | | GLsizei rawDepth, GLenum sizedUnpackFormat, |
1542 | | const TexImageSource& src, const Maybe<GLsizei>& expectedImageSize) |
1543 | 0 | { |
1544 | 0 | const GLint border = 0; |
1545 | 0 | const auto blob = mContext->FromCompressed(target, rawWidth, rawHeight, |
1546 | 0 | rawDepth, border, src, expectedImageSize); |
1547 | 0 | if (!blob) |
1548 | 0 | return; |
1549 | 0 | |
1550 | 0 | //////////////////////////////////// |
1551 | 0 | // Get dest info |
1552 | 0 | |
1553 | 0 | WebGLTexture::ImageInfo* imageInfo; |
1554 | 0 | if (!ValidateTexImageSelection(target, level, xOffset, yOffset, zOffset, |
1555 | 0 | blob->mWidth, blob->mHeight, blob->mDepth, &imageInfo)) |
1556 | 0 | { |
1557 | 0 | return; |
1558 | 0 | } |
1559 | 0 | MOZ_ASSERT(imageInfo); |
1560 | 0 |
|
1561 | 0 | auto dstUsage = imageInfo->mFormat; |
1562 | 0 | auto dstFormat = dstUsage->format; |
1563 | 0 |
|
1564 | 0 | //////////////////////////////////// |
1565 | 0 | // Get source info |
1566 | 0 |
|
1567 | 0 | auto srcUsage = mContext->mFormatUsage->GetSizedTexUsage(sizedUnpackFormat); |
1568 | 0 | if (!srcUsage->format->compression) { |
1569 | 0 | mContext->ErrorInvalidEnum("Specified format must be compressed."); |
1570 | 0 | return; |
1571 | 0 | } |
1572 | 0 | |
1573 | 0 | if (srcUsage != dstUsage) { |
1574 | 0 | mContext->ErrorInvalidOperation("`format` must match the format of the" |
1575 | 0 | " existing texture image."); |
1576 | 0 | return; |
1577 | 0 | } |
1578 | 0 | |
1579 | 0 | auto format = srcUsage->format; |
1580 | 0 | MOZ_ASSERT(format == dstFormat); |
1581 | 0 | if (!ValidateCompressedTexUnpack(mContext, blob->mWidth, blob->mHeight, |
1582 | 0 | blob->mDepth, format, blob->mAvailBytes)) |
1583 | 0 | { |
1584 | 0 | return; |
1585 | 0 | } |
1586 | 0 | |
1587 | 0 | //////////////////////////////////// |
1588 | 0 | // Check that source is compatible with dest |
1589 | 0 | |
1590 | 0 | switch (format->compression->family) { |
1591 | 0 | // Forbidden: |
1592 | 0 | case webgl::CompressionFamily::ETC1: |
1593 | 0 | case webgl::CompressionFamily::ATC: |
1594 | 0 | mContext->ErrorInvalidOperation("Format does not allow sub-image" |
1595 | 0 | " updates."); |
1596 | 0 | return; |
1597 | 0 |
|
1598 | 0 | // Block-aligned: |
1599 | 0 | case webgl::CompressionFamily::ES3: // Yes, the ES3 formats don't match the ES3 |
1600 | 0 | case webgl::CompressionFamily::S3TC: // default behavior. |
1601 | 0 | if (!IsSubImageBlockAligned(dstFormat->compression, imageInfo, xOffset, yOffset, |
1602 | 0 | blob->mWidth, blob->mHeight)) |
1603 | 0 | { |
1604 | 0 | mContext->ErrorInvalidOperation("Format requires block-aligned sub-image" |
1605 | 0 | " updates."); |
1606 | 0 | return; |
1607 | 0 | } |
1608 | 0 | break; |
1609 | 0 |
|
1610 | 0 | // Full-only: (The ES3 default) |
1611 | 0 | default: // PVRTC |
1612 | 0 | if (xOffset || yOffset || |
1613 | 0 | blob->mWidth != imageInfo->mWidth || |
1614 | 0 | blob->mHeight != imageInfo->mHeight) |
1615 | 0 | { |
1616 | 0 | mContext->ErrorInvalidOperation("Format does not allow partial sub-image" |
1617 | 0 | " updates."); |
1618 | 0 | return; |
1619 | 0 | } |
1620 | 0 | break; |
1621 | 0 | } |
1622 | 0 | |
1623 | 0 | //////////////////////////////////// |
1624 | 0 | // Do the thing! |
1625 | 0 | |
1626 | 0 | bool uploadWillInitialize; |
1627 | 0 | if (!EnsureImageDataInitializedForUpload(this, target, level, xOffset, |
1628 | 0 | yOffset, zOffset, blob->mWidth, |
1629 | 0 | blob->mHeight, blob->mDepth, imageInfo, |
1630 | 0 | &uploadWillInitialize)) |
1631 | 0 | { |
1632 | 0 | return; |
1633 | 0 | } |
1634 | 0 | |
1635 | 0 | const ScopedLazyBind bindPBO(mContext->gl, LOCAL_GL_PIXEL_UNPACK_BUFFER, |
1636 | 0 | mContext->mBoundPixelUnpackBuffer); |
1637 | 0 |
|
1638 | 0 | // Warning: Possibly shared memory. See bug 1225033. |
1639 | 0 | GLenum error = DoCompressedTexSubImage(mContext->gl, target, level, xOffset, yOffset, |
1640 | 0 | zOffset, blob->mWidth, blob->mHeight, |
1641 | 0 | blob->mDepth, sizedUnpackFormat, |
1642 | 0 | blob->mAvailBytes, blob->mPtr); |
1643 | 0 | if (error == LOCAL_GL_OUT_OF_MEMORY) { |
1644 | 0 | mContext->ErrorOutOfMemory("Ran out of memory during upload."); |
1645 | 0 | return; |
1646 | 0 | } |
1647 | 0 | if (error) { |
1648 | 0 | MOZ_RELEASE_ASSERT(false, "GFX: We should have caught all other errors."); |
1649 | 0 | mContext->GenerateWarning("Unexpected error during texture upload. Context" |
1650 | 0 | " lost."); |
1651 | 0 | mContext->ForceLoseContext(); |
1652 | 0 | return; |
1653 | 0 | } |
1654 | 0 | |
1655 | 0 | //////////////////////////////////// |
1656 | 0 | // Update our specification data? |
1657 | 0 | |
1658 | 0 | if (uploadWillInitialize) { |
1659 | 0 | imageInfo->SetIsDataInitialized(true, this); |
1660 | 0 | } |
1661 | 0 | } |
1662 | | |
1663 | | //////////////////////////////////////// |
1664 | | // CopyTex(Sub)Image |
1665 | | |
1666 | | static bool |
1667 | | ValidateCopyTexImageFormats(WebGLContext* webgl, |
1668 | | const webgl::FormatInfo* srcFormat, |
1669 | | const webgl::FormatInfo* dstFormat) |
1670 | 0 | { |
1671 | 0 | MOZ_ASSERT(!srcFormat->compression); |
1672 | 0 | if (dstFormat->compression) { |
1673 | 0 | webgl->ErrorInvalidEnum("Specified destination must not have a compressed" |
1674 | 0 | " format."); |
1675 | 0 | return false; |
1676 | 0 | } |
1677 | 0 | |
1678 | 0 | if (dstFormat->effectiveFormat == webgl::EffectiveFormat::RGB9_E5) { |
1679 | 0 | webgl->ErrorInvalidOperation("RGB9_E5 is an invalid destination for" |
1680 | 0 | " CopyTex(Sub)Image. (GLES 3.0.4 p145)"); |
1681 | 0 | return false; |
1682 | 0 | } |
1683 | 0 | |
1684 | 0 | if (!DoChannelsMatchForCopyTexImage(srcFormat, dstFormat)) { |
1685 | 0 | webgl->ErrorInvalidOperation("Destination channels must be compatible with" |
1686 | 0 | " source channels. (GLES 3.0.4 p140 Table 3.16)"); |
1687 | 0 | return false; |
1688 | 0 | } |
1689 | 0 | |
1690 | 0 | return true; |
1691 | 0 | } |
1692 | | |
1693 | | //////////////////////////////////////////////////////////////////////////////// |
1694 | | |
1695 | | class ScopedCopyTexImageSource |
1696 | | { |
1697 | | WebGLContext* const mWebGL; |
1698 | | GLuint mRB; |
1699 | | GLuint mFB; |
1700 | | |
1701 | | public: |
1702 | | ScopedCopyTexImageSource(WebGLContext* webgl, uint32_t srcWidth, |
1703 | | uint32_t srcHeight, const webgl::FormatInfo* srcFormat, |
1704 | | const webgl::FormatUsageInfo* dstUsage); |
1705 | | ~ScopedCopyTexImageSource(); |
1706 | | }; |
1707 | | |
1708 | | ScopedCopyTexImageSource::ScopedCopyTexImageSource(WebGLContext* webgl, |
1709 | | uint32_t srcWidth, uint32_t srcHeight, |
1710 | | const webgl::FormatInfo* srcFormat, |
1711 | | const webgl::FormatUsageInfo* dstUsage) |
1712 | | : mWebGL(webgl) |
1713 | | , mRB(0) |
1714 | | , mFB(0) |
1715 | 0 | { |
1716 | 0 | switch (dstUsage->format->unsizedFormat) { |
1717 | 0 | case webgl::UnsizedFormat::L: |
1718 | 0 | case webgl::UnsizedFormat::A: |
1719 | 0 | case webgl::UnsizedFormat::LA: |
1720 | 0 | webgl->GenerateWarning("Copying to a LUMINANCE, ALPHA, or LUMINANCE_ALPHA" |
1721 | 0 | " is deprecated, and has severely reduced performance" |
1722 | 0 | " on some platforms."); |
1723 | 0 | break; |
1724 | 0 |
|
1725 | 0 | default: |
1726 | 0 | MOZ_ASSERT(!dstUsage->textureSwizzleRGBA); |
1727 | 0 | return; |
1728 | 0 | } |
1729 | 0 |
|
1730 | 0 | if (!dstUsage->textureSwizzleRGBA) |
1731 | 0 | return; |
1732 | 0 | |
1733 | 0 | gl::GLContext* gl = webgl->gl; |
1734 | 0 |
|
1735 | 0 | GLenum sizedFormat; |
1736 | 0 |
|
1737 | 0 | switch (srcFormat->componentType) { |
1738 | 0 | case webgl::ComponentType::NormUInt: |
1739 | 0 | sizedFormat = LOCAL_GL_RGBA8; |
1740 | 0 | break; |
1741 | 0 |
|
1742 | 0 | case webgl::ComponentType::Float: |
1743 | 0 | if (webgl->IsExtensionEnabled(WebGLExtensionID::WEBGL_color_buffer_float)) { |
1744 | 0 | sizedFormat = LOCAL_GL_RGBA32F; |
1745 | 0 | break; |
1746 | 0 | } |
1747 | 0 |
|
1748 | 0 | if (webgl->IsExtensionEnabled(WebGLExtensionID::EXT_color_buffer_half_float)) { |
1749 | 0 | sizedFormat = LOCAL_GL_RGBA16F; |
1750 | 0 | break; |
1751 | 0 | } |
1752 | 0 | MOZ_CRASH("GFX: Should be able to request CopyTexImage from Float."); |
1753 | 0 |
|
1754 | 0 | default: |
1755 | 0 | MOZ_CRASH("GFX: Should be able to request CopyTexImage from this type."); |
1756 | 0 | } |
1757 | 0 |
|
1758 | 0 | gl::ScopedTexture scopedTex(gl); |
1759 | 0 | gl::ScopedBindTexture scopedBindTex(gl, scopedTex.Texture(), LOCAL_GL_TEXTURE_2D); |
1760 | 0 |
|
1761 | 0 | gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_NEAREST); |
1762 | 0 | gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_NEAREST); |
1763 | 0 |
|
1764 | 0 | GLint blitSwizzle[4] = {LOCAL_GL_ZERO}; |
1765 | 0 | switch (dstUsage->format->unsizedFormat) { |
1766 | 0 | case webgl::UnsizedFormat::L: |
1767 | 0 | blitSwizzle[0] = LOCAL_GL_RED; |
1768 | 0 | break; |
1769 | 0 |
|
1770 | 0 | case webgl::UnsizedFormat::A: |
1771 | 0 | blitSwizzle[0] = LOCAL_GL_ALPHA; |
1772 | 0 | break; |
1773 | 0 |
|
1774 | 0 | case webgl::UnsizedFormat::LA: |
1775 | 0 | blitSwizzle[0] = LOCAL_GL_RED; |
1776 | 0 | blitSwizzle[1] = LOCAL_GL_ALPHA; |
1777 | 0 | break; |
1778 | 0 |
|
1779 | 0 | default: |
1780 | 0 | MOZ_CRASH("GFX: Unhandled unsizedFormat."); |
1781 | 0 | } |
1782 | 0 |
|
1783 | 0 | gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_R, blitSwizzle[0]); |
1784 | 0 | gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_G, blitSwizzle[1]); |
1785 | 0 | gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_B, blitSwizzle[2]); |
1786 | 0 | gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_A, blitSwizzle[3]); |
1787 | 0 |
|
1788 | 0 | gl->fCopyTexImage2D(LOCAL_GL_TEXTURE_2D, 0, sizedFormat, 0, 0, srcWidth, |
1789 | 0 | srcHeight, 0); |
1790 | 0 |
|
1791 | 0 | // Now create the swizzled FB we'll be exposing. |
1792 | 0 |
|
1793 | 0 | GLuint rgbaRB = 0; |
1794 | 0 | gl->fGenRenderbuffers(1, &rgbaRB); |
1795 | 0 | gl::ScopedBindRenderbuffer scopedRB(gl, rgbaRB); |
1796 | 0 | gl->fRenderbufferStorage(LOCAL_GL_RENDERBUFFER, sizedFormat, srcWidth, srcHeight); |
1797 | 0 |
|
1798 | 0 | GLuint rgbaFB = 0; |
1799 | 0 | gl->fGenFramebuffers(1, &rgbaFB); |
1800 | 0 | gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, rgbaFB); |
1801 | 0 | gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, |
1802 | 0 | LOCAL_GL_RENDERBUFFER, rgbaRB); |
1803 | 0 |
|
1804 | 0 | const GLenum status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); |
1805 | 0 | if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) { |
1806 | 0 | MOZ_CRASH("GFX: Temp framebuffer is not complete."); |
1807 | 0 | } |
1808 | 0 |
|
1809 | 0 | // Restore RB binding. |
1810 | 0 | scopedRB.Unwrap(); // This function should really have a better name. |
1811 | 0 |
|
1812 | 0 | // Draw-blit rgbaTex into rgbaFB. |
1813 | 0 | const gfx::IntSize srcSize(srcWidth, srcHeight); |
1814 | 0 | { |
1815 | 0 | const gl::ScopedBindFramebuffer bindFB(gl, rgbaFB); |
1816 | 0 | gl->BlitHelper()->DrawBlitTextureToFramebuffer(scopedTex.Texture(), srcSize, |
1817 | 0 | srcSize); |
1818 | 0 | } |
1819 | 0 |
|
1820 | 0 | // Restore Tex2D binding and destroy the temp tex. |
1821 | 0 | scopedBindTex.Unwrap(); |
1822 | 0 | scopedTex.Unwrap(); |
1823 | 0 |
|
1824 | 0 | // Leave RB and FB alive, and FB bound. |
1825 | 0 | mRB = rgbaRB; |
1826 | 0 | mFB = rgbaFB; |
1827 | 0 | } |
1828 | | |
1829 | | template<typename T> |
1830 | | static inline GLenum |
1831 | | ToGLHandle(const T& obj) |
1832 | 0 | { |
1833 | 0 | return (obj ? obj->mGLName : 0); |
1834 | 0 | } |
1835 | | |
1836 | | ScopedCopyTexImageSource::~ScopedCopyTexImageSource() |
1837 | 0 | { |
1838 | 0 | if (!mFB) { |
1839 | 0 | MOZ_ASSERT(!mRB); |
1840 | 0 | return; |
1841 | 0 | } |
1842 | 0 | MOZ_ASSERT(mRB); |
1843 | 0 |
|
1844 | 0 | gl::GLContext* gl = mWebGL->gl; |
1845 | 0 |
|
1846 | 0 | // If we're swizzling, it's because we're on a GL core (3.2+) profile, which has |
1847 | 0 | // split framebuffer support. |
1848 | 0 | gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, |
1849 | 0 | ToGLHandle(mWebGL->mBoundDrawFramebuffer)); |
1850 | 0 | gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, |
1851 | 0 | ToGLHandle(mWebGL->mBoundReadFramebuffer)); |
1852 | 0 |
|
1853 | 0 | gl->fDeleteFramebuffers(1, &mFB); |
1854 | 0 | gl->fDeleteRenderbuffers(1, &mRB); |
1855 | 0 | } |
1856 | | |
1857 | | //////////////////////////////////////////////////////////////////////////////// |
1858 | | |
1859 | | static bool |
1860 | | GetUnsizedFormatForCopy(GLenum internalFormat, webgl::UnsizedFormat* const out) |
1861 | 0 | { |
1862 | 0 | switch (internalFormat) { |
1863 | 0 | case LOCAL_GL_RED: *out = webgl::UnsizedFormat::R; break; |
1864 | 0 | case LOCAL_GL_RG: *out = webgl::UnsizedFormat::RG; break; |
1865 | 0 | case LOCAL_GL_RGB: *out = webgl::UnsizedFormat::RGB; break; |
1866 | 0 | case LOCAL_GL_RGBA: *out = webgl::UnsizedFormat::RGBA; break; |
1867 | 0 | case LOCAL_GL_LUMINANCE: *out = webgl::UnsizedFormat::L; break; |
1868 | 0 | case LOCAL_GL_ALPHA: *out = webgl::UnsizedFormat::A; break; |
1869 | 0 | case LOCAL_GL_LUMINANCE_ALPHA: *out = webgl::UnsizedFormat::LA; break; |
1870 | 0 |
|
1871 | 0 | default: |
1872 | 0 | return false; |
1873 | 0 | } |
1874 | 0 | |
1875 | 0 | return true; |
1876 | 0 | } |
1877 | | |
1878 | | static const webgl::FormatUsageInfo* |
1879 | | ValidateCopyDestUsage(WebGLContext* webgl, |
1880 | | const webgl::FormatInfo* srcFormat, GLenum internalFormat) |
1881 | 0 | { |
1882 | 0 | const auto& fua = webgl->mFormatUsage; |
1883 | 0 |
|
1884 | 0 | auto dstUsage = fua->GetSizedTexUsage(internalFormat); |
1885 | 0 | if (!dstUsage) { |
1886 | 0 | // Ok, maybe it's unsized. |
1887 | 0 | webgl::UnsizedFormat unsizedFormat; |
1888 | 0 | if (!GetUnsizedFormatForCopy(internalFormat, &unsizedFormat)) { |
1889 | 0 | webgl->ErrorInvalidEnumInfo("internalFormat", internalFormat); |
1890 | 0 | return nullptr; |
1891 | 0 | } |
1892 | 0 | |
1893 | 0 | const auto dstFormat = srcFormat->GetCopyDecayFormat(unsizedFormat); |
1894 | 0 | if (dstFormat) { |
1895 | 0 | dstUsage = fua->GetUsage(dstFormat->effectiveFormat); |
1896 | 0 | } |
1897 | 0 | if (!dstUsage) { |
1898 | 0 | webgl->ErrorInvalidOperation("0x%04x is not a valid unsized format for" |
1899 | 0 | " source format %s.", |
1900 | 0 | internalFormat, srcFormat->name); |
1901 | 0 | return nullptr; |
1902 | 0 | } |
1903 | 0 | |
1904 | 0 | return dstUsage; |
1905 | 0 | } |
1906 | 0 | // Alright, it's sized. |
1907 | 0 | |
1908 | 0 | const auto dstFormat = dstUsage->format; |
1909 | 0 |
|
1910 | 0 | const auto fnNarrowType = [&](webgl::ComponentType type) { |
1911 | 0 | switch (type) { |
1912 | 0 | case webgl::ComponentType::NormInt: |
1913 | 0 | case webgl::ComponentType::NormUInt: |
1914 | 0 | // These both count as "fixed-point". |
1915 | 0 | return webgl::ComponentType::NormInt; |
1916 | 0 |
|
1917 | 0 | default: |
1918 | 0 | return type; |
1919 | 0 | } |
1920 | 0 | }; |
1921 | 0 |
|
1922 | 0 | const auto srcType = fnNarrowType(srcFormat->componentType); |
1923 | 0 | const auto dstType = fnNarrowType(dstFormat->componentType); |
1924 | 0 | if (dstType != srcType) { |
1925 | 0 | webgl->ErrorInvalidOperation("For sized internalFormats, source and dest" |
1926 | 0 | " component types must match. (source: %s, dest:" |
1927 | 0 | " %s)", |
1928 | 0 | srcFormat->name, dstFormat->name); |
1929 | 0 | return nullptr; |
1930 | 0 | } |
1931 | 0 | |
1932 | 0 | bool componentSizesMatch = true; |
1933 | 0 | if (dstFormat->r) { |
1934 | 0 | componentSizesMatch &= (dstFormat->r == srcFormat->r); |
1935 | 0 | } |
1936 | 0 | if (dstFormat->g) { |
1937 | 0 | componentSizesMatch &= (dstFormat->g == srcFormat->g); |
1938 | 0 | } |
1939 | 0 | if (dstFormat->b) { |
1940 | 0 | componentSizesMatch &= (dstFormat->b == srcFormat->b); |
1941 | 0 | } |
1942 | 0 | if (dstFormat->a) { |
1943 | 0 | componentSizesMatch &= (dstFormat->a == srcFormat->a); |
1944 | 0 | } |
1945 | 0 |
|
1946 | 0 | if (!componentSizesMatch) { |
1947 | 0 | webgl->ErrorInvalidOperation("For sized internalFormats, source and dest" |
1948 | 0 | " component sizes must match exactly. (source: %s," |
1949 | 0 | " dest: %s)", |
1950 | 0 | srcFormat->name, dstFormat->name); |
1951 | 0 | return nullptr; |
1952 | 0 | } |
1953 | 0 | |
1954 | 0 | return dstUsage; |
1955 | 0 | } |
1956 | | |
1957 | | bool |
1958 | | WebGLTexture::ValidateCopyTexImageForFeedback(uint32_t level, GLint layer) const |
1959 | 0 | { |
1960 | 0 | const auto& fb = mContext->mBoundReadFramebuffer; |
1961 | 0 | if (fb) { |
1962 | 0 | const auto& attach = fb->ColorReadBuffer(); |
1963 | 0 | MOZ_ASSERT(attach); |
1964 | 0 |
|
1965 | 0 | if (attach->Texture() == this && |
1966 | 0 | attach->Layer() == layer && |
1967 | 0 | uint32_t(attach->MipLevel()) == level) |
1968 | 0 | { |
1969 | 0 | // Note that the TexImageTargets *don't* have to match for this to be |
1970 | 0 | // undefined per GLES 3.0.4 p211, thus an INVALID_OP in WebGL. |
1971 | 0 | mContext->ErrorInvalidOperation("Feedback loop detected, as this texture" |
1972 | 0 | " is already attached to READ_FRAMEBUFFER's" |
1973 | 0 | " READ_BUFFER-selected COLOR_ATTACHMENT%u.", |
1974 | 0 | attach->mAttachmentPoint); |
1975 | 0 | return false; |
1976 | 0 | } |
1977 | 0 | } |
1978 | 0 | return true; |
1979 | 0 | } |
1980 | | |
1981 | | static bool |
1982 | | DoCopyTexOrSubImage(WebGLContext* webgl, bool isSubImage, |
1983 | | const WebGLTexture* tex, TexImageTarget target, GLint level, |
1984 | | GLint xWithinSrc, GLint yWithinSrc, |
1985 | | uint32_t srcTotalWidth, uint32_t srcTotalHeight, |
1986 | | const webgl::FormatUsageInfo* srcUsage, |
1987 | | GLint xOffset, GLint yOffset, GLint zOffset, |
1988 | | uint32_t dstWidth, uint32_t dstHeight, |
1989 | | const webgl::FormatUsageInfo* dstUsage) |
1990 | 0 | { |
1991 | 0 | const auto& gl = webgl->gl; |
1992 | 0 |
|
1993 | 0 | //// |
1994 | 0 |
|
1995 | 0 | int32_t readX, readY; |
1996 | 0 | int32_t writeX, writeY; |
1997 | 0 | int32_t rwWidth, rwHeight; |
1998 | 0 | if (!Intersect(srcTotalWidth, xWithinSrc, dstWidth, &readX, &writeX, &rwWidth) || |
1999 | 0 | !Intersect(srcTotalHeight, yWithinSrc, dstHeight, &readY, &writeY, &rwHeight)) |
2000 | 0 | { |
2001 | 0 | webgl->ErrorOutOfMemory("Bad subrect selection."); |
2002 | 0 | return false; |
2003 | 0 | } |
2004 | 0 | |
2005 | 0 | writeX += xOffset; |
2006 | 0 | writeY += yOffset; |
2007 | 0 |
|
2008 | 0 | //// |
2009 | 0 |
|
2010 | 0 | GLenum error = 0; |
2011 | 0 | do { |
2012 | 0 | const auto& idealUnpack = dstUsage->idealUnpack; |
2013 | 0 | if (!isSubImage) { |
2014 | 0 | UniqueBuffer buffer; |
2015 | 0 |
|
2016 | 0 | if (uint32_t(rwWidth) != dstWidth || uint32_t(rwHeight) != dstHeight) { |
2017 | 0 | const auto& pi = idealUnpack->ToPacking(); |
2018 | 0 | CheckedUint32 byteCount = BytesPerPixel(pi); |
2019 | 0 | byteCount *= dstWidth; |
2020 | 0 | byteCount *= dstHeight; |
2021 | 0 |
|
2022 | 0 | if (byteCount.isValid()) { |
2023 | 0 | buffer = calloc(1, byteCount.value()); |
2024 | 0 | } |
2025 | 0 |
|
2026 | 0 | if (!buffer.get()) { |
2027 | 0 | webgl->ErrorOutOfMemory("Ran out of memory allocating zeros."); |
2028 | 0 | return false; |
2029 | 0 | } |
2030 | 0 | } |
2031 | 0 | |
2032 | 0 | const ScopedUnpackReset unpackReset(webgl); |
2033 | 0 | gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1); |
2034 | 0 | error = DoTexImage(gl, target, level, idealUnpack, dstWidth, dstHeight, 1, |
2035 | 0 | buffer.get()); |
2036 | 0 | if (error) |
2037 | 0 | break; |
2038 | 0 | } |
2039 | 0 | |
2040 | 0 | if (!rwWidth || !rwHeight) { |
2041 | 0 | // There aren't any pixels to copy, so we're 'done'. |
2042 | 0 | return true; |
2043 | 0 | } |
2044 | 0 | |
2045 | 0 | const auto& srcFormat = srcUsage->format; |
2046 | 0 | ScopedCopyTexImageSource maybeSwizzle(webgl, srcTotalWidth, |
2047 | 0 | srcTotalHeight, srcFormat, dstUsage); |
2048 | 0 |
|
2049 | 0 | error = DoCopyTexSubImage(gl, target, level, writeX, writeY, zOffset, readX, |
2050 | 0 | readY, rwWidth, rwHeight); |
2051 | 0 | if (error) |
2052 | 0 | break; |
2053 | 0 | |
2054 | 0 | return true; |
2055 | 0 | } while (false); |
2056 | 0 |
|
2057 | 0 | if (error == LOCAL_GL_OUT_OF_MEMORY) { |
2058 | 0 | webgl->ErrorOutOfMemory("Ran out of memory during texture copy."); |
2059 | 0 | return false; |
2060 | 0 | } |
2061 | 0 | |
2062 | 0 | if (gl->IsANGLE() && error == LOCAL_GL_INVALID_OPERATION) { |
2063 | 0 | webgl->ErrorImplementationBug("ANGLE is particular about CopyTexSubImage" |
2064 | 0 | " formats matching exactly."); |
2065 | 0 | return false; |
2066 | 0 | } |
2067 | 0 | |
2068 | 0 | MOZ_RELEASE_ASSERT(false, "GFX: We should have caught all other errors."); |
2069 | 0 | webgl->GenerateWarning("Unexpected error during texture copy. Context lost."); |
2070 | 0 | webgl->ForceLoseContext(); |
2071 | 0 | return false; |
2072 | 0 | } |
2073 | | |
2074 | | // There is no CopyTexImage3D. |
2075 | | void |
2076 | | WebGLTexture::CopyTexImage2D(TexImageTarget target, GLint level, GLenum internalFormat, |
2077 | | GLint x, GLint y, GLsizei rawWidth, GLsizei rawHeight, |
2078 | | GLint border) |
2079 | 0 | { |
2080 | 0 | //////////////////////////////////// |
2081 | 0 | // Get dest info |
2082 | 0 |
|
2083 | 0 | uint32_t width, height, depth; |
2084 | 0 | if (!ValidateExtents(mContext, rawWidth, rawHeight, 1, border, &width, |
2085 | 0 | &height, &depth)) |
2086 | 0 | { |
2087 | 0 | return; |
2088 | 0 | } |
2089 | 0 | |
2090 | 0 | WebGLTexture::ImageInfo* imageInfo; |
2091 | 0 | if (!ValidateTexImageSpecification(target, level, width, height, depth, &imageInfo)) |
2092 | 0 | return; |
2093 | 0 | MOZ_ASSERT(imageInfo); |
2094 | 0 |
|
2095 | 0 | //////////////////////////////////// |
2096 | 0 | // Get source info |
2097 | 0 |
|
2098 | 0 | const webgl::FormatUsageInfo* srcUsage; |
2099 | 0 | uint32_t srcTotalWidth; |
2100 | 0 | uint32_t srcTotalHeight; |
2101 | 0 | if (!mContext->BindCurFBForColorRead(&srcUsage, &srcTotalWidth, |
2102 | 0 | &srcTotalHeight)) |
2103 | 0 | { |
2104 | 0 | return; |
2105 | 0 | } |
2106 | 0 | |
2107 | 0 | if (!ValidateCopyTexImageForFeedback(level)) |
2108 | 0 | return; |
2109 | 0 | |
2110 | 0 | //////////////////////////////////// |
2111 | 0 | // Check that source and dest info are compatible |
2112 | 0 | |
2113 | 0 | const auto& srcFormat = srcUsage->format; |
2114 | 0 | const auto dstUsage = ValidateCopyDestUsage(mContext, srcFormat, |
2115 | 0 | internalFormat); |
2116 | 0 | if (!dstUsage) |
2117 | 0 | return; |
2118 | 0 | |
2119 | 0 | const auto& dstFormat = dstUsage->format; |
2120 | 0 | if (!ValidateTargetForFormat(mContext, target, dstFormat)) |
2121 | 0 | return; |
2122 | 0 | |
2123 | 0 | if (!mContext->IsWebGL2() && dstFormat->d) { |
2124 | 0 | mContext->ErrorInvalidOperation("Function may not be called with format %s.", |
2125 | 0 | dstFormat->name); |
2126 | 0 | return; |
2127 | 0 | } |
2128 | 0 | |
2129 | 0 | if (!ValidateCopyTexImageFormats(mContext, srcFormat, dstFormat)) |
2130 | 0 | return; |
2131 | 0 | |
2132 | 0 | //////////////////////////////////// |
2133 | 0 | // Do the thing! |
2134 | 0 | |
2135 | 0 | const bool isSubImage = false; |
2136 | 0 | if (!DoCopyTexOrSubImage(mContext, isSubImage, this, target, level, x, y, |
2137 | 0 | srcTotalWidth, srcTotalHeight, srcUsage, 0, 0, 0, width, |
2138 | 0 | height, dstUsage)) |
2139 | 0 | { |
2140 | 0 | return; |
2141 | 0 | } |
2142 | 0 | |
2143 | 0 | mContext->OnDataAllocCall(); |
2144 | 0 |
|
2145 | 0 | //////////////////////////////////// |
2146 | 0 | // Update our specification data. |
2147 | 0 |
|
2148 | 0 | const bool isDataInitialized = true; |
2149 | 0 | const ImageInfo newImageInfo(dstUsage, width, height, depth, isDataInitialized); |
2150 | 0 | SetImageInfo(imageInfo, newImageInfo); |
2151 | 0 | } |
2152 | | |
2153 | | void |
2154 | | WebGLTexture::CopyTexSubImage(TexImageTarget target, GLint level, |
2155 | | GLint xOffset, GLint yOffset, GLint zOffset, GLint x, |
2156 | | GLint y, GLsizei rawWidth, GLsizei rawHeight) |
2157 | 0 | { |
2158 | 0 | uint32_t width, height, depth; |
2159 | 0 | if (!ValidateExtents(mContext, rawWidth, rawHeight, 1, 0, &width, |
2160 | 0 | &height, &depth)) |
2161 | 0 | { |
2162 | 0 | return; |
2163 | 0 | } |
2164 | 0 | |
2165 | 0 | //////////////////////////////////// |
2166 | 0 | // Get dest info |
2167 | 0 | |
2168 | 0 | WebGLTexture::ImageInfo* imageInfo; |
2169 | 0 | if (!ValidateTexImageSelection(target, level, xOffset, yOffset, zOffset, |
2170 | 0 | width, height, depth, &imageInfo)) |
2171 | 0 | { |
2172 | 0 | return; |
2173 | 0 | } |
2174 | 0 | MOZ_ASSERT(imageInfo); |
2175 | 0 |
|
2176 | 0 | auto dstUsage = imageInfo->mFormat; |
2177 | 0 | MOZ_ASSERT(dstUsage); |
2178 | 0 |
|
2179 | 0 | auto dstFormat = dstUsage->format; |
2180 | 0 | if (!mContext->IsWebGL2() && dstFormat->d) { |
2181 | 0 | mContext->ErrorInvalidOperation("Function may not be called on a texture of" |
2182 | 0 | " format %s.", |
2183 | 0 | dstFormat->name); |
2184 | 0 | return; |
2185 | 0 | } |
2186 | 0 | |
2187 | 0 | //////////////////////////////////// |
2188 | 0 | // Get source info |
2189 | 0 | |
2190 | 0 | const webgl::FormatUsageInfo* srcUsage; |
2191 | 0 | uint32_t srcTotalWidth; |
2192 | 0 | uint32_t srcTotalHeight; |
2193 | 0 | if (!mContext->BindCurFBForColorRead(&srcUsage, &srcTotalWidth, |
2194 | 0 | &srcTotalHeight)) |
2195 | 0 | { |
2196 | 0 | return; |
2197 | 0 | } |
2198 | 0 | |
2199 | 0 | if (!ValidateCopyTexImageForFeedback(level, zOffset)) |
2200 | 0 | return; |
2201 | 0 | |
2202 | 0 | //////////////////////////////////// |
2203 | 0 | // Check that source and dest info are compatible |
2204 | 0 | |
2205 | 0 | auto srcFormat = srcUsage->format; |
2206 | 0 | if (!ValidateCopyTexImageFormats(mContext, srcFormat, dstFormat)) |
2207 | 0 | return; |
2208 | 0 | |
2209 | 0 | //////////////////////////////////// |
2210 | 0 | // Do the thing! |
2211 | 0 | |
2212 | 0 | bool uploadWillInitialize; |
2213 | 0 | if (!EnsureImageDataInitializedForUpload(this, target, level, xOffset, |
2214 | 0 | yOffset, zOffset, width, height, depth, |
2215 | 0 | imageInfo, &uploadWillInitialize)) |
2216 | 0 | { |
2217 | 0 | return; |
2218 | 0 | } |
2219 | 0 | |
2220 | 0 | const bool isSubImage = true; |
2221 | 0 | if (!DoCopyTexOrSubImage(mContext, isSubImage, this, target, level, x, y, |
2222 | 0 | srcTotalWidth, srcTotalHeight, srcUsage, xOffset, yOffset, |
2223 | 0 | zOffset, width, height, dstUsage)) |
2224 | 0 | { |
2225 | 0 | return; |
2226 | 0 | } |
2227 | 0 | |
2228 | 0 | //////////////////////////////////// |
2229 | 0 | // Update our specification data? |
2230 | 0 | |
2231 | 0 | if (uploadWillInitialize) { |
2232 | 0 | imageInfo->SetIsDataInitialized(true, this); |
2233 | 0 | } |
2234 | 0 | } |
2235 | | |
2236 | | } // namespace mozilla |