/src/mozilla-central/dom/canvas/WebGLTexture.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 | | #include "GLContext.h" |
10 | | #include "mozilla/dom/WebGLRenderingContextBinding.h" |
11 | | #include "mozilla/gfx/Logging.h" |
12 | | #include "mozilla/MathAlgorithms.h" |
13 | | #include "mozilla/Scoped.h" |
14 | | #include "mozilla/Unused.h" |
15 | | #include "ScopedGLHelpers.h" |
16 | | #include "WebGLContext.h" |
17 | | #include "WebGLContextUtils.h" |
18 | | #include "WebGLFramebuffer.h" |
19 | | #include "WebGLSampler.h" |
20 | | #include "WebGLTexelConversions.h" |
21 | | |
22 | | namespace mozilla { |
23 | | |
24 | | /*static*/ const WebGLTexture::ImageInfo WebGLTexture::ImageInfo::kUndefined; |
25 | | |
26 | | //////////////////////////////////////// |
27 | | |
28 | | template <typename T> |
29 | | static inline T& |
30 | | Mutable(const T& x) |
31 | 0 | { |
32 | 0 | return const_cast<T&>(x); |
33 | 0 | } Unexecuted instantiation: Unified_cpp_dom_canvas5.cpp:mozilla::webgl::FormatUsageInfo const*& mozilla::Mutable<mozilla::webgl::FormatUsageInfo const*>(mozilla::webgl::FormatUsageInfo const* const&) Unexecuted instantiation: Unified_cpp_dom_canvas5.cpp:unsigned int& mozilla::Mutable<unsigned int>(unsigned int const&) |
34 | | |
35 | | void |
36 | | WebGLTexture::ImageInfo::Clear() |
37 | 0 | { |
38 | 0 | if (!IsDefined()) |
39 | 0 | return; |
40 | 0 | |
41 | 0 | OnRespecify(); |
42 | 0 |
|
43 | 0 | Mutable(mFormat) = LOCAL_GL_NONE; |
44 | 0 | Mutable(mWidth) = 0; |
45 | 0 | Mutable(mHeight) = 0; |
46 | 0 | Mutable(mDepth) = 0; |
47 | 0 |
|
48 | 0 | MOZ_ASSERT(!IsDefined()); |
49 | 0 | } |
50 | | |
51 | | void |
52 | | WebGLTexture::ImageInfo::Set(const ImageInfo& a) |
53 | 0 | { |
54 | 0 | MOZ_ASSERT(a.IsDefined()); |
55 | 0 |
|
56 | 0 | Mutable(mFormat) = a.mFormat; |
57 | 0 | Mutable(mWidth) = a.mWidth; |
58 | 0 | Mutable(mHeight) = a.mHeight; |
59 | 0 | Mutable(mDepth) = a.mDepth; |
60 | 0 |
|
61 | 0 | mIsDataInitialized = a.mIsDataInitialized; |
62 | 0 |
|
63 | 0 | // But *don't* transfer mAttachPoints! |
64 | 0 | MOZ_ASSERT(a.mAttachPoints.empty()); |
65 | 0 | OnRespecify(); |
66 | 0 | } |
67 | | |
68 | | bool |
69 | | WebGLTexture::ImageInfo::IsPowerOfTwo() const |
70 | 0 | { |
71 | 0 | return mozilla::IsPowerOfTwo(mWidth) && |
72 | 0 | mozilla::IsPowerOfTwo(mHeight) && |
73 | 0 | mozilla::IsPowerOfTwo(mDepth); |
74 | 0 | } |
75 | | |
76 | | void |
77 | | WebGLTexture::ImageInfo::AddAttachPoint(WebGLFBAttachPoint* attachPoint) |
78 | 0 | { |
79 | 0 | const auto pair = mAttachPoints.insert(attachPoint); |
80 | 0 | DebugOnly<bool> didInsert = pair.second; |
81 | 0 | MOZ_ASSERT(didInsert); |
82 | 0 | } |
83 | | |
84 | | void |
85 | | WebGLTexture::ImageInfo::RemoveAttachPoint(WebGLFBAttachPoint* attachPoint) |
86 | 0 | { |
87 | 0 | DebugOnly<size_t> numElemsErased = mAttachPoints.erase(attachPoint); |
88 | 0 | MOZ_ASSERT_IF(IsDefined(), numElemsErased == 1); |
89 | 0 | } |
90 | | |
91 | | void |
92 | | WebGLTexture::ImageInfo::OnRespecify() const |
93 | 0 | { |
94 | 0 | for (auto cur : mAttachPoints) { |
95 | 0 | cur->OnBackingStoreRespecified(); |
96 | 0 | } |
97 | 0 | } |
98 | | |
99 | | size_t |
100 | | WebGLTexture::ImageInfo::MemoryUsage() const |
101 | 0 | { |
102 | 0 | if (!IsDefined()) |
103 | 0 | return 0; |
104 | 0 | |
105 | 0 | const auto bytesPerTexel = mFormat->format->estimatedBytesPerPixel; |
106 | 0 | return size_t(mWidth) * size_t(mHeight) * size_t(mDepth) * bytesPerTexel; |
107 | 0 | } |
108 | | |
109 | | void |
110 | | WebGLTexture::ImageInfo::SetIsDataInitialized(bool isDataInitialized, WebGLTexture* tex) |
111 | 0 | { |
112 | 0 | MOZ_ASSERT(tex); |
113 | 0 | MOZ_ASSERT(this >= &tex->mImageInfoArr[0]); |
114 | 0 | MOZ_ASSERT(this < &tex->mImageInfoArr[kMaxLevelCount * kMaxFaceCount]); |
115 | 0 |
|
116 | 0 | mIsDataInitialized = isDataInitialized; |
117 | 0 | tex->InvalidateResolveCache(); |
118 | 0 | } |
119 | | |
120 | | //////////////////////////////////////// |
121 | | |
122 | | JSObject* |
123 | 0 | WebGLTexture::WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto) { |
124 | 0 | return dom::WebGLTexture_Binding::Wrap(cx, this, givenProto); |
125 | 0 | } |
126 | | |
127 | | WebGLTexture::WebGLTexture(WebGLContext* webgl, GLuint tex) |
128 | | : WebGLRefCountedObject(webgl) |
129 | | , mGLName(tex) |
130 | | , mTarget(LOCAL_GL_NONE) |
131 | | , mFaceCount(0) |
132 | | , mImmutable(false) |
133 | | , mImmutableLevelCount(0) |
134 | | , mBaseMipmapLevel(0) |
135 | | , mMaxMipmapLevel(1000) |
136 | | , mIsResolved(false) |
137 | | , mResolved_FakeBlack(FakeBlackType::None) |
138 | | , mResolved_Swizzle(nullptr) |
139 | 0 | { |
140 | 0 | mContext->mTextures.insertBack(this); |
141 | 0 | } |
142 | | |
143 | | void |
144 | | WebGLTexture::Delete() |
145 | 0 | { |
146 | 0 | for (auto& cur : mImageInfoArr) { |
147 | 0 | cur.Clear(); |
148 | 0 | } |
149 | 0 |
|
150 | 0 | mContext->gl->fDeleteTextures(1, &mGLName); |
151 | 0 |
|
152 | 0 | LinkedListElement<WebGLTexture>::removeFrom(mContext->mTextures); |
153 | 0 | } |
154 | | |
155 | | size_t |
156 | | WebGLTexture::MemoryUsage() const |
157 | 0 | { |
158 | 0 | if (IsDeleted()) |
159 | 0 | return 0; |
160 | 0 | |
161 | 0 | size_t accum = 0; |
162 | 0 | for (const auto& cur : mImageInfoArr) { |
163 | 0 | accum += cur.MemoryUsage(); |
164 | 0 | } |
165 | 0 | return accum; |
166 | 0 | } |
167 | | |
168 | | void |
169 | | WebGLTexture::SetImageInfo(ImageInfo* target, |
170 | | const ImageInfo& newInfo) |
171 | 0 | { |
172 | 0 | target->Set(newInfo); |
173 | 0 |
|
174 | 0 | InvalidateResolveCache(); |
175 | 0 | } |
176 | | |
177 | | void |
178 | | WebGLTexture::SetImageInfosAtLevel(uint32_t level, |
179 | | const ImageInfo& newInfo) |
180 | 0 | { |
181 | 0 | for (uint8_t i = 0; i < mFaceCount; i++) { |
182 | 0 | ImageInfoAtFace(i, level).Set(newInfo); |
183 | 0 | } |
184 | 0 |
|
185 | 0 | InvalidateResolveCache(); |
186 | 0 | } |
187 | | |
188 | | bool |
189 | | WebGLTexture::IsMipmapComplete(uint32_t texUnit, |
190 | | bool* const out_initFailed) |
191 | 0 | { |
192 | 0 | *out_initFailed = false; |
193 | 0 | // GLES 3.0.4, p161 |
194 | 0 |
|
195 | 0 | uint32_t maxLevel; |
196 | 0 | if (!MaxEffectiveMipmapLevel(texUnit, &maxLevel)) |
197 | 0 | return false; |
198 | 0 | |
199 | 0 | // "* `level_base <= level_max`" |
200 | 0 | if (mBaseMipmapLevel > maxLevel) |
201 | 0 | return false; |
202 | 0 | |
203 | 0 | // Make a copy so we can modify it. |
204 | 0 | const ImageInfo& baseImageInfo = BaseImageInfo(); |
205 | 0 |
|
206 | 0 | // Reference dimensions based on the current level. |
207 | 0 | uint32_t refWidth = baseImageInfo.mWidth; |
208 | 0 | uint32_t refHeight = baseImageInfo.mHeight; |
209 | 0 | uint32_t refDepth = baseImageInfo.mDepth; |
210 | 0 | MOZ_ASSERT(refWidth && refHeight && refDepth); |
211 | 0 |
|
212 | 0 | for (uint32_t level = mBaseMipmapLevel; level <= maxLevel; level++) { |
213 | 0 | if (!EnsureLevelInitialized(level)) { |
214 | 0 | *out_initFailed = true; |
215 | 0 | return false; |
216 | 0 | } |
217 | 0 | |
218 | 0 | // "A cube map texture is mipmap complete if each of the six texture images, |
219 | 0 | // considered individually, is mipmap complete." |
220 | 0 | |
221 | 0 | for (uint8_t face = 0; face < mFaceCount; face++) { |
222 | 0 | const ImageInfo& cur = ImageInfoAtFace(face, level); |
223 | 0 |
|
224 | 0 | // "* The set of mipmap arrays `level_base` through `q` (where `q` is defined |
225 | 0 | // the "Mipmapping" discussion of section 3.8.10) were each specified with |
226 | 0 | // the same effective internal format." |
227 | 0 |
|
228 | 0 | // "* The dimensions of the arrays follow the sequence described in the |
229 | 0 | // "Mipmapping" discussion of section 3.8.10." |
230 | 0 |
|
231 | 0 | if (cur.mWidth != refWidth || |
232 | 0 | cur.mHeight != refHeight || |
233 | 0 | cur.mDepth != refDepth || |
234 | 0 | cur.mFormat != baseImageInfo.mFormat) |
235 | 0 | { |
236 | 0 | return false; |
237 | 0 | } |
238 | 0 | } |
239 | 0 |
|
240 | 0 | // GLES 3.0.4, p158: |
241 | 0 | // "[...] until the last array is reached with dimension 1 x 1 x 1." |
242 | 0 | if (mTarget == LOCAL_GL_TEXTURE_3D) { |
243 | 0 | if (refWidth == 1 && |
244 | 0 | refHeight == 1 && |
245 | 0 | refDepth == 1) |
246 | 0 | { |
247 | 0 | break; |
248 | 0 | } |
249 | 0 | |
250 | 0 | refDepth = std::max(uint32_t(1), refDepth / 2); |
251 | 0 | } else { |
252 | 0 | // TEXTURE_2D_ARRAY may have depth != 1, but that's normal. |
253 | 0 | if (refWidth == 1 && |
254 | 0 | refHeight == 1) |
255 | 0 | { |
256 | 0 | break; |
257 | 0 | } |
258 | 0 | } |
259 | 0 | |
260 | 0 | refWidth = std::max(uint32_t(1), refWidth / 2); |
261 | 0 | refHeight = std::max(uint32_t(1), refHeight / 2); |
262 | 0 | } |
263 | 0 |
|
264 | 0 | return true; |
265 | 0 | } |
266 | | |
267 | | bool |
268 | | WebGLTexture::IsCubeComplete() const |
269 | 0 | { |
270 | 0 | // GLES 3.0.4, p161 |
271 | 0 | // "[...] a cube map texture is cube complete if the following conditions all hold |
272 | 0 | // true: |
273 | 0 | // * The `level_base` arrays of each of the six texture images making up the cube map |
274 | 0 | // have identical, positive, and square dimensions. |
275 | 0 | // * The `level_base` arrays were each specified with the same effective internal |
276 | 0 | // format." |
277 | 0 |
|
278 | 0 | // Note that "cube complete" does not imply "mipmap complete". |
279 | 0 |
|
280 | 0 | const ImageInfo& reference = BaseImageInfo(); |
281 | 0 | if (!reference.IsDefined()) |
282 | 0 | return false; |
283 | 0 | |
284 | 0 | auto refWidth = reference.mWidth; |
285 | 0 | auto refFormat = reference.mFormat; |
286 | 0 |
|
287 | 0 | for (uint8_t face = 0; face < mFaceCount; face++) { |
288 | 0 | const ImageInfo& cur = ImageInfoAtFace(face, mBaseMipmapLevel); |
289 | 0 | if (!cur.IsDefined()) |
290 | 0 | return false; |
291 | 0 | |
292 | 0 | MOZ_ASSERT(cur.mDepth == 1); |
293 | 0 | if (cur.mFormat != refFormat || // Check effective formats. |
294 | 0 | cur.mWidth != refWidth || // Check both width and height against refWidth to |
295 | 0 | cur.mHeight != refWidth) // to enforce positive and square dimensions. |
296 | 0 | { |
297 | 0 | return false; |
298 | 0 | } |
299 | 0 | } |
300 | 0 |
|
301 | 0 | return true; |
302 | 0 | } |
303 | | |
304 | | bool |
305 | | WebGLTexture::IsComplete(uint32_t texUnit, |
306 | | const char** const out_reason, bool* const out_initFailed) |
307 | 0 | { |
308 | 0 | *out_initFailed = false; |
309 | 0 |
|
310 | 0 | const auto maxLevel = kMaxLevelCount - 1; |
311 | 0 | if (mBaseMipmapLevel > maxLevel) { |
312 | 0 | *out_reason = "`level_base` too high."; |
313 | 0 | return false; |
314 | 0 | } |
315 | 0 | |
316 | 0 | // Texture completeness is established at GLES 3.0.4, p160-161. |
317 | 0 | // "[A] texture is complete unless any of the following conditions hold true:" |
318 | 0 | |
319 | 0 | // "* Any dimension of the `level_base` array is not positive." |
320 | 0 | const ImageInfo& baseImageInfo = BaseImageInfo(); |
321 | 0 | if (!baseImageInfo.IsDefined()) { |
322 | 0 | // In case of undefined texture image, we don't print any message because this is |
323 | 0 | // a very common and often legitimate case (asynchronous texture loading). |
324 | 0 | *out_reason = nullptr; |
325 | 0 | return false; |
326 | 0 | } |
327 | 0 | |
328 | 0 | if (!baseImageInfo.mWidth || !baseImageInfo.mHeight || !baseImageInfo.mDepth) { |
329 | 0 | *out_reason = "The dimensions of `level_base` are not all positive."; |
330 | 0 | return false; |
331 | 0 | } |
332 | 0 | |
333 | 0 | // "* The texture is a cube map texture, and is not cube complete." |
334 | 0 | if (IsCubeMap() && !IsCubeComplete()) { |
335 | 0 | *out_reason = "Cubemaps must be \"cube complete\"."; |
336 | 0 | return false; |
337 | 0 | } |
338 | 0 | |
339 | 0 | const auto* samplingState = &mSamplingState; |
340 | 0 | const auto& sampler = mContext->mBoundSamplers[texUnit]; |
341 | 0 | if (sampler) { |
342 | 0 | samplingState = &(sampler->State()); |
343 | 0 | } |
344 | 0 |
|
345 | 0 | const auto& minFilter = samplingState->minFilter; |
346 | 0 | const auto& magFilter = samplingState->magFilter; |
347 | 0 |
|
348 | 0 | // "* The minification filter requires a mipmap (is neither NEAREST nor LINEAR) and |
349 | 0 | // the texture is not mipmap complete." |
350 | 0 | const bool requiresMipmap = (minFilter != LOCAL_GL_NEAREST && |
351 | 0 | minFilter != LOCAL_GL_LINEAR); |
352 | 0 | if (requiresMipmap && !IsMipmapComplete(texUnit, out_initFailed)) { |
353 | 0 | if (*out_initFailed) |
354 | 0 | return false; |
355 | 0 | |
356 | 0 | *out_reason = "Because the minification filter requires mipmapping, the texture" |
357 | 0 | " must be \"mipmap complete\"."; |
358 | 0 | return false; |
359 | 0 | } |
360 | 0 | |
361 | 0 | const bool isMinFilteringNearest = (minFilter == LOCAL_GL_NEAREST || |
362 | 0 | minFilter == LOCAL_GL_NEAREST_MIPMAP_NEAREST); |
363 | 0 | const bool isMagFilteringNearest = (magFilter == LOCAL_GL_NEAREST); |
364 | 0 | const bool isFilteringNearestOnly = (isMinFilteringNearest && isMagFilteringNearest); |
365 | 0 | if (!isFilteringNearestOnly) { |
366 | 0 | auto formatUsage = baseImageInfo.mFormat; |
367 | 0 | auto format = formatUsage->format; |
368 | 0 |
|
369 | 0 | bool isFilterable = formatUsage->isFilterable; |
370 | 0 |
|
371 | 0 | // "* The effective internal format specified for the texture arrays is a sized |
372 | 0 | // internal depth or depth and stencil format, the value of |
373 | 0 | // TEXTURE_COMPARE_MODE is NONE[1], and either the magnification filter is not |
374 | 0 | // NEAREST, or the minification filter is neither NEAREST nor |
375 | 0 | // NEAREST_MIPMAP_NEAREST." |
376 | 0 | // [1]: This sounds suspect, but is explicitly noted in the change log for GLES |
377 | 0 | // 3.0.1: |
378 | 0 | // "* Clarify that a texture is incomplete if it has a depth component, no |
379 | 0 | // shadow comparison, and linear filtering (also Bug 9481)." |
380 | 0 | if (format->d && samplingState->compareMode != LOCAL_GL_NONE) { |
381 | 0 | isFilterable = true; |
382 | 0 | } |
383 | 0 |
|
384 | 0 | // "* The effective internal format specified for the texture arrays is a sized |
385 | 0 | // internal color format that is not texture-filterable, and either the |
386 | 0 | // magnification filter is not NEAREST or the minification filter is neither |
387 | 0 | // NEAREST nor NEAREST_MIPMAP_NEAREST." |
388 | 0 | // Since all (GLES3) unsized color formats are filterable just like their sized |
389 | 0 | // equivalents, we don't have to care whether its sized or not. |
390 | 0 | if (!isFilterable) { |
391 | 0 | *out_reason = "Because minification or magnification filtering is not NEAREST" |
392 | 0 | " or NEAREST_MIPMAP_NEAREST, and the texture's format must be" |
393 | 0 | " \"texture-filterable\"."; |
394 | 0 | return false; |
395 | 0 | } |
396 | 0 | } |
397 | 0 | |
398 | 0 | // Texture completeness is effectively (though not explicitly) amended for GLES2 by |
399 | 0 | // the "Texture Access" section under $3.8 "Fragment Shaders". This also applies to |
400 | 0 | // vertex shaders, as noted on GLES 2.0.25, p41. |
401 | 0 | if (!mContext->IsWebGL2()) { |
402 | 0 | // GLES 2.0.25, p87-88: |
403 | 0 | // "Calling a sampler from a fragment shader will return (R,G,B,A)=(0,0,0,1) if |
404 | 0 | // any of the following conditions are true:" |
405 | 0 |
|
406 | 0 | // "* A two-dimensional sampler is called, the minification filter is one that |
407 | 0 | // requires a mipmap[...], and the sampler's associated texture object is not |
408 | 0 | // complete[.]" |
409 | 0 | // (already covered) |
410 | 0 |
|
411 | 0 | // "* A two-dimensional sampler is called, the minification filter is not one that |
412 | 0 | // requires a mipmap (either NEAREST nor[sic] LINEAR), and either dimension of |
413 | 0 | // the level zero array of the associated texture object is not positive." |
414 | 0 | // (already covered) |
415 | 0 |
|
416 | 0 | // "* A two-dimensional sampler is called, the corresponding texture image is a |
417 | 0 | // non-power-of-two image[...], and either the texture wrap mode is not |
418 | 0 | // CLAMP_TO_EDGE, or the minification filter is neither NEAREST nor LINEAR." |
419 | 0 |
|
420 | 0 | // "* A cube map sampler is called, any of the corresponding texture images are |
421 | 0 | // non-power-of-two images, and either the texture wrap mode is not |
422 | 0 | // CLAMP_TO_EDGE, or the minification filter is neither NEAREST nor LINEAR." |
423 | 0 | if (!baseImageInfo.IsPowerOfTwo()) { |
424 | 0 | // "either the texture wrap mode is not CLAMP_TO_EDGE" |
425 | 0 | if (samplingState->wrapS != LOCAL_GL_CLAMP_TO_EDGE || |
426 | 0 | samplingState->wrapT != LOCAL_GL_CLAMP_TO_EDGE) |
427 | 0 | { |
428 | 0 | *out_reason = "Non-power-of-two textures must have a wrap mode of" |
429 | 0 | " CLAMP_TO_EDGE."; |
430 | 0 | return false; |
431 | 0 | } |
432 | 0 | |
433 | 0 | // "or the minification filter is neither NEAREST nor LINEAR" |
434 | 0 | if (requiresMipmap) { |
435 | 0 | *out_reason = "Mipmapping requires power-of-two textures."; |
436 | 0 | return false; |
437 | 0 | } |
438 | 0 | } |
439 | 0 | |
440 | 0 | // "* A cube map sampler is called, and either the corresponding cube map texture |
441 | 0 | // image is not cube complete, or TEXTURE_MIN_FILTER is one that requires a |
442 | 0 | // mipmap and the texture is not mipmap cube complete." |
443 | 0 | // (already covered) |
444 | 0 | } |
445 | 0 | |
446 | 0 | if (!EnsureLevelInitialized(mBaseMipmapLevel)) { |
447 | 0 | *out_initFailed = true; |
448 | 0 | return false; |
449 | 0 | } |
450 | 0 | |
451 | 0 | return true; |
452 | 0 | } |
453 | | |
454 | | bool |
455 | | WebGLTexture::MaxEffectiveMipmapLevel(uint32_t texUnit, uint32_t* const out) const |
456 | 0 | { |
457 | 0 | const auto* samplingState = &mSamplingState; |
458 | 0 | const auto& sampler = mContext->mBoundSamplers[texUnit]; |
459 | 0 | if (sampler) { |
460 | 0 | samplingState = &(sampler->State()); |
461 | 0 | } |
462 | 0 |
|
463 | 0 | const auto& minFilter = samplingState->minFilter; |
464 | 0 | if (minFilter == LOCAL_GL_NEAREST || |
465 | 0 | minFilter == LOCAL_GL_LINEAR) |
466 | 0 | { |
467 | 0 | // No extra mips used. |
468 | 0 | *out = mBaseMipmapLevel; |
469 | 0 | return true; |
470 | 0 | } |
471 | 0 | |
472 | 0 | const auto& imageInfo = BaseImageInfo(); |
473 | 0 | if (!imageInfo.IsDefined()) |
474 | 0 | return false; |
475 | 0 | |
476 | 0 | uint32_t maxLevelBySize = mBaseMipmapLevel + imageInfo.PossibleMipmapLevels() - 1; |
477 | 0 | *out = std::min<uint32_t>(maxLevelBySize, mMaxMipmapLevel); |
478 | 0 | return true; |
479 | 0 | } |
480 | | |
481 | | bool |
482 | | WebGLTexture::GetFakeBlackType(uint32_t texUnit, |
483 | | FakeBlackType* const out_fakeBlack) |
484 | 0 | { |
485 | 0 | const char* incompleteReason; |
486 | 0 | bool initFailed = false; |
487 | 0 | if (!IsComplete(texUnit, &incompleteReason, &initFailed)) { |
488 | 0 | if (initFailed) { |
489 | 0 | mContext->ErrorOutOfMemory("Failed to initialize texture data."); |
490 | 0 | return false; // The world just exploded. |
491 | 0 | } |
492 | 0 | |
493 | 0 | if (incompleteReason) { |
494 | 0 | mContext->GenerateWarning("Active texture %u for target 0x%04x is" |
495 | 0 | " 'incomplete', and will be rendered as" |
496 | 0 | " RGBA(0,0,0,1), as per the GLES 2.0.24 $3.8.2: %s", |
497 | 0 | texUnit, mTarget.get(), |
498 | 0 | incompleteReason); |
499 | 0 | } |
500 | 0 | *out_fakeBlack = FakeBlackType::RGBA0001; |
501 | 0 | return true; |
502 | 0 | } |
503 | 0 |
|
504 | 0 |
|
505 | 0 | *out_fakeBlack = FakeBlackType::None; |
506 | 0 | return true; |
507 | 0 | } |
508 | | |
509 | | static void |
510 | | SetSwizzle(gl::GLContext* gl, TexTarget target, const GLint* swizzle) |
511 | 0 | { |
512 | 0 | static const GLint kNoSwizzle[4] = { LOCAL_GL_RED, LOCAL_GL_GREEN, LOCAL_GL_BLUE, |
513 | 0 | LOCAL_GL_ALPHA }; |
514 | 0 | if (!swizzle) { |
515 | 0 | swizzle = kNoSwizzle; |
516 | 0 | } else if (!gl->IsSupported(gl::GLFeature::texture_swizzle)) { |
517 | 0 | MOZ_CRASH("GFX: Needs swizzle feature to swizzle!"); |
518 | 0 | } |
519 | 0 |
|
520 | 0 | gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_R, swizzle[0]); |
521 | 0 | gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_G, swizzle[1]); |
522 | 0 | gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_B, swizzle[2]); |
523 | 0 | gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_A, swizzle[3]); |
524 | 0 | } |
525 | | |
526 | | bool |
527 | | WebGLTexture::ResolveForDraw(uint32_t texUnit, |
528 | | FakeBlackType* const out_fakeBlack) |
529 | 0 | { |
530 | 0 | if (!mIsResolved) { |
531 | 0 | if (!GetFakeBlackType(texUnit, &mResolved_FakeBlack)) |
532 | 0 | return false; |
533 | 0 | |
534 | 0 | // Check which swizzle we should use. Since the texture must be complete at this |
535 | 0 | // point, just grab the format off any valid image. |
536 | 0 | const GLint* newSwizzle = nullptr; |
537 | 0 | if (mResolved_FakeBlack == FakeBlackType::None) { |
538 | 0 | const auto& cur = ImageInfoAtFace(0, mBaseMipmapLevel); |
539 | 0 | newSwizzle = cur.mFormat->textureSwizzleRGBA; |
540 | 0 | } |
541 | 0 |
|
542 | 0 | // Only set the swizzle if it changed since last time we did it. |
543 | 0 | if (newSwizzle != mResolved_Swizzle) { |
544 | 0 | mResolved_Swizzle = newSwizzle; |
545 | 0 |
|
546 | 0 | // Set the new swizzle! |
547 | 0 | mContext->gl->fActiveTexture(LOCAL_GL_TEXTURE0 + texUnit); |
548 | 0 | SetSwizzle(mContext->gl, mTarget, mResolved_Swizzle); |
549 | 0 | mContext->gl->fActiveTexture(LOCAL_GL_TEXTURE0 + mContext->mActiveTexture); |
550 | 0 | } |
551 | 0 |
|
552 | 0 | mIsResolved = true; |
553 | 0 | } |
554 | 0 |
|
555 | 0 | *out_fakeBlack = mResolved_FakeBlack; |
556 | 0 | return true; |
557 | 0 | } |
558 | | |
559 | | bool |
560 | | WebGLTexture::EnsureImageDataInitialized(TexImageTarget target, |
561 | | uint32_t level) |
562 | 0 | { |
563 | 0 | auto& imageInfo = ImageInfoAt(target, level); |
564 | 0 | if (!imageInfo.IsDefined()) |
565 | 0 | return true; |
566 | 0 | |
567 | 0 | if (imageInfo.IsDataInitialized()) |
568 | 0 | return true; |
569 | 0 | |
570 | 0 | return InitializeImageData(target, level); |
571 | 0 | } |
572 | | |
573 | | bool |
574 | | WebGLTexture::EnsureLevelInitialized(uint32_t level) |
575 | 0 | { |
576 | 0 | if (mTarget != LOCAL_GL_TEXTURE_CUBE_MAP) |
577 | 0 | return EnsureImageDataInitialized(mTarget.get(), level); |
578 | 0 | |
579 | 0 | for (GLenum texImageTarget = LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X; |
580 | 0 | texImageTarget <= LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z; |
581 | 0 | ++texImageTarget) |
582 | 0 | { |
583 | 0 | if (!EnsureImageDataInitialized(texImageTarget, level)) |
584 | 0 | return false; |
585 | 0 | } |
586 | 0 | return true; |
587 | 0 | } |
588 | | |
589 | | static void |
590 | | ZeroANGLEDepthTexture(WebGLContext* webgl, GLuint tex, |
591 | | const webgl::FormatUsageInfo* usage, uint32_t width, |
592 | | uint32_t height) |
593 | 0 | { |
594 | 0 | const auto& format = usage->format; |
595 | 0 | GLenum attachPoint = 0; |
596 | 0 | GLbitfield clearBits = 0; |
597 | 0 |
|
598 | 0 | if (format->d) { |
599 | 0 | attachPoint = LOCAL_GL_DEPTH_ATTACHMENT; |
600 | 0 | clearBits |= LOCAL_GL_DEPTH_BUFFER_BIT; |
601 | 0 | } |
602 | 0 |
|
603 | 0 | if (format->s) { |
604 | 0 | attachPoint = (format->d ? LOCAL_GL_DEPTH_STENCIL_ATTACHMENT |
605 | 0 | : LOCAL_GL_STENCIL_ATTACHMENT); |
606 | 0 | clearBits |= LOCAL_GL_STENCIL_BUFFER_BIT; |
607 | 0 | } |
608 | 0 |
|
609 | 0 | MOZ_RELEASE_ASSERT(attachPoint && clearBits, "GFX: No bits cleared."); |
610 | 0 |
|
611 | 0 | //// |
612 | 0 | const auto& gl = webgl->gl; |
613 | 0 | MOZ_ASSERT(gl->IsCurrent()); |
614 | 0 |
|
615 | 0 | gl::ScopedFramebuffer scopedFB(gl); |
616 | 0 | const gl::ScopedBindFramebuffer scopedBindFB(gl, scopedFB.FB()); |
617 | 0 |
|
618 | 0 | gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, attachPoint, LOCAL_GL_TEXTURE_2D, |
619 | 0 | tex, 0); |
620 | 0 |
|
621 | 0 | const auto& status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); |
622 | 0 | MOZ_RELEASE_ASSERT(status == LOCAL_GL_FRAMEBUFFER_COMPLETE); |
623 | 0 |
|
624 | 0 | //// |
625 | 0 |
|
626 | 0 | const bool fakeNoAlpha = false; |
627 | 0 | webgl->ForceClearFramebufferWithDefaultValues(clearBits, fakeNoAlpha); |
628 | 0 | } |
629 | | |
630 | | static bool |
631 | | ZeroTextureData(WebGLContext* webgl, GLuint tex, |
632 | | TexImageTarget target, uint32_t level, |
633 | | const webgl::FormatUsageInfo* usage, uint32_t width, uint32_t height, |
634 | | uint32_t depth) |
635 | 0 | { |
636 | 0 | // This has two usecases: |
637 | 0 | // 1. Lazy zeroing of uninitialized textures: |
638 | 0 | // a. Before draw, when FakeBlack isn't viable. (TexStorage + Draw*) |
639 | 0 | // b. Before partial upload. (TexStorage + TexSubImage) |
640 | 0 | // 2. Zero subrects from out-of-bounds blits. (CopyTex(Sub)Image) |
641 | 0 |
|
642 | 0 | // We have no sympathy for any of these cases. |
643 | 0 |
|
644 | 0 | // "Doctor, it hurts when I do this!" "Well don't do that!" |
645 | 0 | webgl->GenerateWarning("This operation requires zeroing texture data. This is" |
646 | 0 | " slow."); |
647 | 0 |
|
648 | 0 | gl::GLContext* gl = webgl->GL(); |
649 | 0 |
|
650 | 0 | GLenum scopeBindTarget; |
651 | 0 | switch (target.get()) { |
652 | 0 | case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X: |
653 | 0 | case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X: |
654 | 0 | case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y: |
655 | 0 | case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: |
656 | 0 | case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z: |
657 | 0 | case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: |
658 | 0 | scopeBindTarget = LOCAL_GL_TEXTURE_CUBE_MAP; |
659 | 0 | break; |
660 | 0 | default: |
661 | 0 | scopeBindTarget = target.get(); |
662 | 0 | break; |
663 | 0 | } |
664 | 0 | const gl::ScopedBindTexture scopeBindTexture(gl, tex, scopeBindTarget); |
665 | 0 | auto compression = usage->format->compression; |
666 | 0 | if (compression) { |
667 | 0 | auto sizedFormat = usage->format->sizedFormat; |
668 | 0 | MOZ_RELEASE_ASSERT(sizedFormat, "GFX: texture sized format not set"); |
669 | 0 |
|
670 | 0 | const auto fnSizeInBlocks = [](CheckedUint32 pixels, uint8_t pixelsPerBlock) { |
671 | 0 | return RoundUpToMultipleOf(pixels, pixelsPerBlock) / pixelsPerBlock; |
672 | 0 | }; |
673 | 0 |
|
674 | 0 | const auto widthBlocks = fnSizeInBlocks(width, compression->blockWidth); |
675 | 0 | const auto heightBlocks = fnSizeInBlocks(height, compression->blockHeight); |
676 | 0 |
|
677 | 0 | CheckedUint32 checkedByteCount = compression->bytesPerBlock; |
678 | 0 | checkedByteCount *= widthBlocks; |
679 | 0 | checkedByteCount *= heightBlocks; |
680 | 0 | checkedByteCount *= depth; |
681 | 0 |
|
682 | 0 | if (!checkedByteCount.isValid()) |
683 | 0 | return false; |
684 | 0 | |
685 | 0 | const size_t byteCount = checkedByteCount.value(); |
686 | 0 |
|
687 | 0 | UniqueBuffer zeros = calloc(1, byteCount); |
688 | 0 | if (!zeros) |
689 | 0 | return false; |
690 | 0 | |
691 | 0 | ScopedUnpackReset scopedReset(webgl); |
692 | 0 | gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1); // Don't bother with striding it |
693 | 0 | // well. |
694 | 0 |
|
695 | 0 | const auto error = DoCompressedTexSubImage(gl, target.get(), level, 0, 0, 0, |
696 | 0 | width, height, depth, sizedFormat, |
697 | 0 | byteCount, zeros.get()); |
698 | 0 | return !error; |
699 | 0 | } |
700 | 0 |
|
701 | 0 | const auto driverUnpackInfo = usage->idealUnpack; |
702 | 0 | MOZ_RELEASE_ASSERT(driverUnpackInfo, "GFX: ideal unpack info not set."); |
703 | 0 |
|
704 | 0 | if (webgl->IsExtensionEnabled(WebGLExtensionID::WEBGL_depth_texture) && |
705 | 0 | gl->IsANGLE() && |
706 | 0 | usage->format->d) |
707 | 0 | { |
708 | 0 | // ANGLE_depth_texture does not allow uploads, so we have to clear. |
709 | 0 | // (Restriction because of D3D9) |
710 | 0 | MOZ_ASSERT(target == LOCAL_GL_TEXTURE_2D); |
711 | 0 | MOZ_ASSERT(level == 0); |
712 | 0 | ZeroANGLEDepthTexture(webgl, tex, usage, width, height); |
713 | 0 | return true; |
714 | 0 | } |
715 | 0 |
|
716 | 0 | const webgl::PackingInfo packing = driverUnpackInfo->ToPacking(); |
717 | 0 |
|
718 | 0 | const auto bytesPerPixel = webgl::BytesPerPixel(packing); |
719 | 0 |
|
720 | 0 | CheckedUint32 checkedByteCount = bytesPerPixel; |
721 | 0 | checkedByteCount *= width; |
722 | 0 | checkedByteCount *= height; |
723 | 0 | checkedByteCount *= depth; |
724 | 0 |
|
725 | 0 | if (!checkedByteCount.isValid()) |
726 | 0 | return false; |
727 | 0 | |
728 | 0 | const size_t byteCount = checkedByteCount.value(); |
729 | 0 |
|
730 | 0 | UniqueBuffer zeros = calloc(1, byteCount); |
731 | 0 | if (!zeros) |
732 | 0 | return false; |
733 | 0 | |
734 | 0 | ScopedUnpackReset scopedReset(webgl); |
735 | 0 | gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1); // Don't bother with striding it well. |
736 | 0 | const auto error = DoTexSubImage(gl, target, level, 0, 0, 0, width, height, depth, |
737 | 0 | packing, zeros.get()); |
738 | 0 | return !error; |
739 | 0 | } |
740 | | |
741 | | bool |
742 | | WebGLTexture::InitializeImageData(TexImageTarget target, |
743 | | uint32_t level) |
744 | 0 | { |
745 | 0 | auto& imageInfo = ImageInfoAt(target, level); |
746 | 0 | MOZ_ASSERT(imageInfo.IsDefined()); |
747 | 0 | MOZ_ASSERT(!imageInfo.IsDataInitialized()); |
748 | 0 |
|
749 | 0 | const auto& usage = imageInfo.mFormat; |
750 | 0 | const auto& width = imageInfo.mWidth; |
751 | 0 | const auto& height = imageInfo.mHeight; |
752 | 0 | const auto& depth = imageInfo.mDepth; |
753 | 0 |
|
754 | 0 | if (!ZeroTextureData(mContext, mGLName, target, level, usage, width, height, |
755 | 0 | depth)) |
756 | 0 | { |
757 | 0 | return false; |
758 | 0 | } |
759 | 0 | |
760 | 0 | imageInfo.SetIsDataInitialized(true, this); |
761 | 0 | return true; |
762 | 0 | } |
763 | | |
764 | | void |
765 | | WebGLTexture::ClampLevelBaseAndMax() |
766 | 0 | { |
767 | 0 | if (!mImmutable) |
768 | 0 | return; |
769 | 0 | |
770 | 0 | // GLES 3.0.4, p158: |
771 | 0 | // "For immutable-format textures, `level_base` is clamped to the range |
772 | 0 | // `[0, levels-1]`, `level_max` is then clamped to the range ` |
773 | 0 | // `[level_base, levels-1]`, where `levels` is the parameter passed to |
774 | 0 | // TexStorage* for the texture object." |
775 | 0 | mBaseMipmapLevel = Clamp<uint32_t>(mBaseMipmapLevel, 0, mImmutableLevelCount - 1); |
776 | 0 | mMaxMipmapLevel = Clamp<uint32_t>(mMaxMipmapLevel, mBaseMipmapLevel, |
777 | 0 | mImmutableLevelCount - 1); |
778 | 0 | } |
779 | | |
780 | | void |
781 | | WebGLTexture::PopulateMipChain(uint32_t firstLevel, |
782 | | uint32_t lastLevel) |
783 | 0 | { |
784 | 0 | const ImageInfo& baseImageInfo = ImageInfoAtFace(0, firstLevel); |
785 | 0 | MOZ_ASSERT(baseImageInfo.IsDefined()); |
786 | 0 |
|
787 | 0 | uint32_t refWidth = baseImageInfo.mWidth; |
788 | 0 | uint32_t refHeight = baseImageInfo.mHeight; |
789 | 0 | uint32_t refDepth = baseImageInfo.mDepth; |
790 | 0 | if (!refWidth || !refHeight || !refDepth) |
791 | 0 | return; |
792 | 0 | |
793 | 0 | for (uint32_t level = firstLevel + 1; level <= lastLevel; level++) { |
794 | 0 | bool isMinimal = (refWidth == 1 && |
795 | 0 | refHeight == 1); |
796 | 0 | if (mTarget == LOCAL_GL_TEXTURE_3D) { |
797 | 0 | isMinimal &= (refDepth == 1); |
798 | 0 | } |
799 | 0 |
|
800 | 0 | // Higher levels are unaffected. |
801 | 0 | if (isMinimal) |
802 | 0 | break; |
803 | 0 | |
804 | 0 | refWidth = std::max(uint32_t(1), refWidth / 2); |
805 | 0 | refHeight = std::max(uint32_t(1), refHeight / 2); |
806 | 0 | if (mTarget == LOCAL_GL_TEXTURE_3D) { // But not TEXTURE_2D_ARRAY! |
807 | 0 | refDepth = std::max(uint32_t(1), refDepth / 2); |
808 | 0 | } |
809 | 0 |
|
810 | 0 | const ImageInfo cur(baseImageInfo.mFormat, refWidth, refHeight, refDepth, |
811 | 0 | baseImageInfo.IsDataInitialized()); |
812 | 0 |
|
813 | 0 | SetImageInfosAtLevel(level, cur); |
814 | 0 | } |
815 | 0 | } |
816 | | |
817 | | ////////////////////////////////////////////////////////////////////////////////////////// |
818 | | // GL calls |
819 | | |
820 | | bool |
821 | | WebGLTexture::BindTexture(TexTarget texTarget) |
822 | 0 | { |
823 | 0 | if (IsDeleted()) { |
824 | 0 | mContext->ErrorInvalidOperation("bindTexture: Cannot bind a deleted object."); |
825 | 0 | return false; |
826 | 0 | } |
827 | 0 | |
828 | 0 | const bool isFirstBinding = !HasEverBeenBound(); |
829 | 0 | if (!isFirstBinding && mTarget != texTarget) { |
830 | 0 | mContext->ErrorInvalidOperation("bindTexture: This texture has already been bound" |
831 | 0 | " to a different target."); |
832 | 0 | return false; |
833 | 0 | } |
834 | 0 | |
835 | 0 | mTarget = texTarget; |
836 | 0 |
|
837 | 0 | mContext->gl->fBindTexture(mTarget.get(), mGLName); |
838 | 0 |
|
839 | 0 | if (isFirstBinding) { |
840 | 0 | mFaceCount = IsCubeMap() ? 6 : 1; |
841 | 0 |
|
842 | 0 | gl::GLContext* gl = mContext->gl; |
843 | 0 |
|
844 | 0 | // Thanks to the WebKit people for finding this out: GL_TEXTURE_WRAP_R |
845 | 0 | // is not present in GLES 2, but is present in GL and it seems as if for |
846 | 0 | // cube maps we need to set it to GL_CLAMP_TO_EDGE to get the expected |
847 | 0 | // GLES behavior. |
848 | 0 | // If we are WebGL 2 though, we'll want to leave it as REPEAT. |
849 | 0 | const bool hasWrapR = gl->IsSupported(gl::GLFeature::texture_3D); |
850 | 0 | if (IsCubeMap() && hasWrapR && !mContext->IsWebGL2()) { |
851 | 0 | gl->fTexParameteri(texTarget.get(), LOCAL_GL_TEXTURE_WRAP_R, |
852 | 0 | LOCAL_GL_CLAMP_TO_EDGE); |
853 | 0 | } |
854 | 0 | } |
855 | 0 |
|
856 | 0 | return true; |
857 | 0 | } |
858 | | |
859 | | |
860 | | void |
861 | | WebGLTexture::GenerateMipmap(TexTarget texTarget) |
862 | 0 | {\ |
863 | 0 | // GLES 3.0.4 p160: |
864 | 0 | // "Mipmap generation replaces texel array levels level base + 1 through q with arrays |
865 | 0 | // derived from the level base array, regardless of their previous contents. All |
866 | 0 | // other mipmap arrays, including the level base array, are left unchanged by this |
867 | 0 | // computation." |
868 | 0 | const ImageInfo& baseImageInfo = BaseImageInfo(); |
869 | 0 | if (!baseImageInfo.IsDefined()) { |
870 | 0 | mContext->ErrorInvalidOperation("The base level of the texture is not" |
871 | 0 | " defined."); |
872 | 0 | return; |
873 | 0 | } |
874 | 0 | |
875 | 0 | if (IsCubeMap() && !IsCubeComplete()) { |
876 | 0 | mContext->ErrorInvalidOperation("Cube maps must be \"cube complete\"."); |
877 | 0 | return; |
878 | 0 | } |
879 | 0 | |
880 | 0 | const auto format = baseImageInfo.mFormat->format; |
881 | 0 | if (!mContext->IsWebGL2()) { |
882 | 0 | if (!baseImageInfo.IsPowerOfTwo()) { |
883 | 0 | mContext->ErrorInvalidOperation("The base level of the texture does not" |
884 | 0 | " have power-of-two dimensions."); |
885 | 0 | return; |
886 | 0 | } |
887 | 0 | if (format->isSRGB) { |
888 | 0 | mContext->ErrorInvalidOperation("EXT_sRGB forbids GenerateMipmap with" |
889 | 0 | " sRGB."); |
890 | 0 | return; |
891 | 0 | } |
892 | 0 | } |
893 | 0 | |
894 | 0 | if (format->compression) { |
895 | 0 | mContext->ErrorInvalidOperation("Texture data at base level is compressed."); |
896 | 0 | return; |
897 | 0 | } |
898 | 0 | |
899 | 0 | if (format->d) { |
900 | 0 | mContext->ErrorInvalidOperation("Depth textures are not supported."); |
901 | 0 | return; |
902 | 0 | } |
903 | 0 | |
904 | 0 | // OpenGL ES 3.0.4 p160: |
905 | 0 | // If the level base array was not specified with an unsized internal format from |
906 | 0 | // table 3.3 or a sized internal format that is both color-renderable and |
907 | 0 | // texture-filterable according to table 3.13, an INVALID_OPERATION error |
908 | 0 | // is generated. |
909 | 0 | const auto usage = baseImageInfo.mFormat; |
910 | 0 | bool canGenerateMipmap = (usage->IsRenderable() && usage->isFilterable); |
911 | 0 | switch (usage->format->effectiveFormat) { |
912 | 0 | case webgl::EffectiveFormat::Luminance8: |
913 | 0 | case webgl::EffectiveFormat::Alpha8: |
914 | 0 | case webgl::EffectiveFormat::Luminance8Alpha8: |
915 | 0 | // Non-color-renderable formats from Table 3.3. |
916 | 0 | canGenerateMipmap = true; |
917 | 0 | break; |
918 | 0 | default: |
919 | 0 | break; |
920 | 0 | } |
921 | 0 | |
922 | 0 | if (!canGenerateMipmap) { |
923 | 0 | mContext->ErrorInvalidOperation("Texture at base level is not unsized" |
924 | 0 | " internal format or is not" |
925 | 0 | " color-renderable or texture-filterable."); |
926 | 0 | return; |
927 | 0 | } |
928 | 0 | |
929 | 0 | // Done with validation. Do the operation. |
930 | 0 | |
931 | 0 | gl::GLContext* gl = mContext->gl; |
932 | 0 |
|
933 | 0 | if (gl->WorkAroundDriverBugs()) { |
934 | 0 | // bug 696495 - to work around failures in the texture-mips.html test on various drivers, we |
935 | 0 | // set the minification filter before calling glGenerateMipmap. This should not carry a significant performance |
936 | 0 | // overhead so we do it unconditionally. |
937 | 0 | // |
938 | 0 | // note that the choice of GL_NEAREST_MIPMAP_NEAREST really matters. See Chromium bug 101105. |
939 | 0 | gl->fTexParameteri(texTarget.get(), LOCAL_GL_TEXTURE_MIN_FILTER, |
940 | 0 | LOCAL_GL_NEAREST_MIPMAP_NEAREST); |
941 | 0 | gl->fGenerateMipmap(texTarget.get()); |
942 | 0 | gl->fTexParameteri(texTarget.get(), LOCAL_GL_TEXTURE_MIN_FILTER, |
943 | 0 | mSamplingState.minFilter.get()); |
944 | 0 | } else { |
945 | 0 | gl->fGenerateMipmap(texTarget.get()); |
946 | 0 | } |
947 | 0 |
|
948 | 0 | // Record the results. |
949 | 0 | // Note that we don't use MaxEffectiveMipmapLevel() here, since that returns |
950 | 0 | // mBaseMipmapLevel if the min filter doesn't require mipmaps. |
951 | 0 | const uint32_t maxLevel = mBaseMipmapLevel + baseImageInfo.PossibleMipmapLevels() - 1; |
952 | 0 | PopulateMipChain(mBaseMipmapLevel, maxLevel); |
953 | 0 | } |
954 | | |
955 | | JS::Value |
956 | | WebGLTexture::GetTexParameter(TexTarget texTarget, GLenum pname) |
957 | 0 | { |
958 | 0 | GLint i = 0; |
959 | 0 | GLfloat f = 0.0f; |
960 | 0 |
|
961 | 0 | switch (pname) { |
962 | 0 | case LOCAL_GL_TEXTURE_BASE_LEVEL: |
963 | 0 | return JS::NumberValue(mBaseMipmapLevel); |
964 | 0 |
|
965 | 0 | case LOCAL_GL_TEXTURE_MAX_LEVEL: |
966 | 0 | return JS::NumberValue(mMaxMipmapLevel); |
967 | 0 |
|
968 | 0 | case LOCAL_GL_TEXTURE_IMMUTABLE_FORMAT: |
969 | 0 | return JS::BooleanValue(mImmutable); |
970 | 0 |
|
971 | 0 | case LOCAL_GL_TEXTURE_IMMUTABLE_LEVELS: |
972 | 0 | return JS::NumberValue(uint32_t(mImmutableLevelCount)); |
973 | 0 |
|
974 | 0 | case LOCAL_GL_TEXTURE_MIN_FILTER: |
975 | 0 | case LOCAL_GL_TEXTURE_MAG_FILTER: |
976 | 0 | case LOCAL_GL_TEXTURE_WRAP_S: |
977 | 0 | case LOCAL_GL_TEXTURE_WRAP_T: |
978 | 0 | case LOCAL_GL_TEXTURE_WRAP_R: |
979 | 0 | case LOCAL_GL_TEXTURE_COMPARE_MODE: |
980 | 0 | case LOCAL_GL_TEXTURE_COMPARE_FUNC: |
981 | 0 | mContext->gl->fGetTexParameteriv(texTarget.get(), pname, &i); |
982 | 0 | return JS::NumberValue(uint32_t(i)); |
983 | 0 |
|
984 | 0 | case LOCAL_GL_TEXTURE_MAX_ANISOTROPY_EXT: |
985 | 0 | case LOCAL_GL_TEXTURE_MAX_LOD: |
986 | 0 | case LOCAL_GL_TEXTURE_MIN_LOD: |
987 | 0 | mContext->gl->fGetTexParameterfv(texTarget.get(), pname, &f); |
988 | 0 | return JS::NumberValue(float(f)); |
989 | 0 |
|
990 | 0 | default: |
991 | 0 | MOZ_CRASH("GFX: Unhandled pname."); |
992 | 0 | } |
993 | 0 | } |
994 | | |
995 | | bool |
996 | | WebGLTexture::IsTexture() const |
997 | 0 | { |
998 | 0 | return HasEverBeenBound() && !IsDeleted(); |
999 | 0 | } |
1000 | | |
1001 | | // Here we have to support all pnames with both int and float params. |
1002 | | // See this discussion: |
1003 | | // https://www.khronos.org/webgl/public-mailing-list/archives/1008/msg00014.html |
1004 | | void |
1005 | | WebGLTexture::TexParameter(TexTarget texTarget, GLenum pname, const FloatOrInt& param) |
1006 | 0 | { |
1007 | 0 | bool isPNameValid = false; |
1008 | 0 | switch (pname) { |
1009 | 0 | // GLES 2.0.25 p76: |
1010 | 0 | case LOCAL_GL_TEXTURE_WRAP_S: |
1011 | 0 | case LOCAL_GL_TEXTURE_WRAP_T: |
1012 | 0 | case LOCAL_GL_TEXTURE_MIN_FILTER: |
1013 | 0 | case LOCAL_GL_TEXTURE_MAG_FILTER: |
1014 | 0 | isPNameValid = true; |
1015 | 0 | break; |
1016 | 0 |
|
1017 | 0 | // GLES 3.0.4 p149-150: |
1018 | 0 | case LOCAL_GL_TEXTURE_BASE_LEVEL: |
1019 | 0 | case LOCAL_GL_TEXTURE_COMPARE_MODE: |
1020 | 0 | case LOCAL_GL_TEXTURE_COMPARE_FUNC: |
1021 | 0 | case LOCAL_GL_TEXTURE_MAX_LEVEL: |
1022 | 0 | case LOCAL_GL_TEXTURE_MAX_LOD: |
1023 | 0 | case LOCAL_GL_TEXTURE_MIN_LOD: |
1024 | 0 | case LOCAL_GL_TEXTURE_WRAP_R: |
1025 | 0 | if (mContext->IsWebGL2()) |
1026 | 0 | isPNameValid = true; |
1027 | 0 | break; |
1028 | 0 |
|
1029 | 0 | case LOCAL_GL_TEXTURE_MAX_ANISOTROPY_EXT: |
1030 | 0 | if (mContext->IsExtensionEnabled(WebGLExtensionID::EXT_texture_filter_anisotropic)) |
1031 | 0 | isPNameValid = true; |
1032 | 0 | break; |
1033 | 0 | } |
1034 | 0 |
|
1035 | 0 | if (!isPNameValid) { |
1036 | 0 | mContext->ErrorInvalidEnumInfo("texParameter: pname", pname); |
1037 | 0 | return; |
1038 | 0 | } |
1039 | 0 | |
1040 | 0 | //////////////// |
1041 | 0 | // Validate params and invalidate if needed. |
1042 | 0 | |
1043 | 0 | bool paramBadEnum = false; |
1044 | 0 | bool paramBadValue = false; |
1045 | 0 |
|
1046 | 0 | switch (pname) { |
1047 | 0 | case LOCAL_GL_TEXTURE_BASE_LEVEL: |
1048 | 0 | case LOCAL_GL_TEXTURE_MAX_LEVEL: |
1049 | 0 | paramBadValue = (param.i < 0); |
1050 | 0 | break; |
1051 | 0 |
|
1052 | 0 | case LOCAL_GL_TEXTURE_COMPARE_MODE: |
1053 | 0 | paramBadValue = (param.i != LOCAL_GL_NONE && |
1054 | 0 | param.i != LOCAL_GL_COMPARE_REF_TO_TEXTURE); |
1055 | 0 | break; |
1056 | 0 |
|
1057 | 0 | case LOCAL_GL_TEXTURE_COMPARE_FUNC: |
1058 | 0 | switch (param.i) { |
1059 | 0 | case LOCAL_GL_LEQUAL: |
1060 | 0 | case LOCAL_GL_GEQUAL: |
1061 | 0 | case LOCAL_GL_LESS: |
1062 | 0 | case LOCAL_GL_GREATER: |
1063 | 0 | case LOCAL_GL_EQUAL: |
1064 | 0 | case LOCAL_GL_NOTEQUAL: |
1065 | 0 | case LOCAL_GL_ALWAYS: |
1066 | 0 | case LOCAL_GL_NEVER: |
1067 | 0 | break; |
1068 | 0 |
|
1069 | 0 | default: |
1070 | 0 | paramBadValue = true; |
1071 | 0 | break; |
1072 | 0 | } |
1073 | 0 | break; |
1074 | 0 |
|
1075 | 0 | case LOCAL_GL_TEXTURE_MIN_FILTER: |
1076 | 0 | switch (param.i) { |
1077 | 0 | case LOCAL_GL_NEAREST: |
1078 | 0 | case LOCAL_GL_LINEAR: |
1079 | 0 | case LOCAL_GL_NEAREST_MIPMAP_NEAREST: |
1080 | 0 | case LOCAL_GL_LINEAR_MIPMAP_NEAREST: |
1081 | 0 | case LOCAL_GL_NEAREST_MIPMAP_LINEAR: |
1082 | 0 | case LOCAL_GL_LINEAR_MIPMAP_LINEAR: |
1083 | 0 | break; |
1084 | 0 |
|
1085 | 0 | default: |
1086 | 0 | paramBadEnum = true; |
1087 | 0 | break; |
1088 | 0 | } |
1089 | 0 | break; |
1090 | 0 |
|
1091 | 0 | case LOCAL_GL_TEXTURE_MAG_FILTER: |
1092 | 0 | switch (param.i) { |
1093 | 0 | case LOCAL_GL_NEAREST: |
1094 | 0 | case LOCAL_GL_LINEAR: |
1095 | 0 | break; |
1096 | 0 |
|
1097 | 0 | default: |
1098 | 0 | paramBadEnum = true; |
1099 | 0 | break; |
1100 | 0 | } |
1101 | 0 | break; |
1102 | 0 |
|
1103 | 0 | case LOCAL_GL_TEXTURE_WRAP_S: |
1104 | 0 | case LOCAL_GL_TEXTURE_WRAP_T: |
1105 | 0 | case LOCAL_GL_TEXTURE_WRAP_R: |
1106 | 0 | switch (param.i) { |
1107 | 0 | case LOCAL_GL_CLAMP_TO_EDGE: |
1108 | 0 | case LOCAL_GL_MIRRORED_REPEAT: |
1109 | 0 | case LOCAL_GL_REPEAT: |
1110 | 0 | break; |
1111 | 0 |
|
1112 | 0 | default: |
1113 | 0 | paramBadEnum = true; |
1114 | 0 | break; |
1115 | 0 | } |
1116 | 0 | break; |
1117 | 0 |
|
1118 | 0 | case LOCAL_GL_TEXTURE_MAX_ANISOTROPY_EXT: |
1119 | 0 | if (param.f < 1.0f) |
1120 | 0 | paramBadValue = true; |
1121 | 0 |
|
1122 | 0 | break; |
1123 | 0 | } |
1124 | 0 |
|
1125 | 0 | if (paramBadEnum) { |
1126 | 0 | if (!param.isFloat) { |
1127 | 0 | mContext->ErrorInvalidEnum("pname 0x%04x: Invalid param" |
1128 | 0 | " 0x%04x.", |
1129 | 0 | pname, param.i); |
1130 | 0 | } else { |
1131 | 0 | mContext->ErrorInvalidEnum("pname 0x%04x: Invalid param %g.", |
1132 | 0 | pname, param.f); |
1133 | 0 | } |
1134 | 0 | return; |
1135 | 0 | } |
1136 | 0 |
|
1137 | 0 | if (paramBadValue) { |
1138 | 0 | if (!param.isFloat) { |
1139 | 0 | mContext->ErrorInvalidValue("pname 0x%04x: Invalid param %i" |
1140 | 0 | " (0x%x).", |
1141 | 0 | pname, param.i, param.i); |
1142 | 0 | } else { |
1143 | 0 | mContext->ErrorInvalidValue("pname 0x%04x: Invalid param %g.", |
1144 | 0 | pname, param.f); |
1145 | 0 | } |
1146 | 0 | return; |
1147 | 0 | } |
1148 | 0 |
|
1149 | 0 | //////////////// |
1150 | 0 | // Store any needed values |
1151 | 0 |
|
1152 | 0 | FloatOrInt clamped = param; |
1153 | 0 | bool invalidateCaches = true; |
1154 | 0 | switch (pname) { |
1155 | 0 | case LOCAL_GL_TEXTURE_BASE_LEVEL: |
1156 | 0 | mBaseMipmapLevel = clamped.i; |
1157 | 0 | ClampLevelBaseAndMax(); |
1158 | 0 | clamped = FloatOrInt(GLint(mBaseMipmapLevel)); |
1159 | 0 | break; |
1160 | 0 |
|
1161 | 0 | case LOCAL_GL_TEXTURE_MAX_LEVEL: |
1162 | 0 | mMaxMipmapLevel = clamped.i; |
1163 | 0 | ClampLevelBaseAndMax(); |
1164 | 0 | clamped = FloatOrInt(GLint(mMaxMipmapLevel)); |
1165 | 0 | break; |
1166 | 0 |
|
1167 | 0 | case LOCAL_GL_TEXTURE_MIN_FILTER: |
1168 | 0 | mSamplingState.minFilter = clamped.i; |
1169 | 0 | break; |
1170 | 0 |
|
1171 | 0 | case LOCAL_GL_TEXTURE_MAG_FILTER: |
1172 | 0 | mSamplingState.magFilter = clamped.i; |
1173 | 0 | break; |
1174 | 0 |
|
1175 | 0 | case LOCAL_GL_TEXTURE_WRAP_S: |
1176 | 0 | mSamplingState.wrapS = clamped.i; |
1177 | 0 | break; |
1178 | 0 |
|
1179 | 0 | case LOCAL_GL_TEXTURE_WRAP_T: |
1180 | 0 | mSamplingState.wrapT = clamped.i; |
1181 | 0 | break; |
1182 | 0 |
|
1183 | 0 | case LOCAL_GL_TEXTURE_COMPARE_MODE: |
1184 | 0 | mSamplingState.compareMode = clamped.i; |
1185 | 0 | break; |
1186 | 0 |
|
1187 | 0 | // Only a couple of pnames don't need to invalidate our resolve status cache. |
1188 | 0 | case LOCAL_GL_TEXTURE_MAX_ANISOTROPY_EXT: |
1189 | 0 | case LOCAL_GL_TEXTURE_WRAP_R: |
1190 | 0 | case LOCAL_GL_TEXTURE_COMPARE_FUNC: |
1191 | 0 | invalidateCaches = false; |
1192 | 0 | break; |
1193 | 0 | } |
1194 | 0 |
|
1195 | 0 | if (invalidateCaches) { |
1196 | 0 | InvalidateResolveCache(); |
1197 | 0 | } |
1198 | 0 |
|
1199 | 0 | //////////////// |
1200 | 0 |
|
1201 | 0 | if (!clamped.isFloat) |
1202 | 0 | mContext->gl->fTexParameteri(texTarget.get(), pname, clamped.i); |
1203 | 0 | else |
1204 | 0 | mContext->gl->fTexParameterf(texTarget.get(), pname, clamped.f); |
1205 | 0 | } |
1206 | | |
1207 | | //////////////////////////////////////////////////////////////////////////////// |
1208 | | |
1209 | | NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLTexture) |
1210 | | |
1211 | | NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGLTexture, AddRef) |
1212 | | NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebGLTexture, Release) |
1213 | | |
1214 | | } // namespace mozilla |