Thu, 15 Jan 2015 21:03:48 +0100
Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)
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/. */
6 #include "WebGLContext.h"
7 #include "WebGLContextUtils.h"
8 #include "WebGLTexture.h"
9 #include "GLContext.h"
10 #include "ScopedGLHelpers.h"
11 #include "WebGLTexelConversions.h"
12 #include "mozilla/dom/WebGLRenderingContextBinding.h"
13 #include <algorithm>
15 using namespace mozilla;
17 JSObject*
18 WebGLTexture::WrapObject(JSContext *cx) {
19 return dom::WebGLTextureBinding::Wrap(cx, this);
20 }
22 WebGLTexture::WebGLTexture(WebGLContext *context)
23 : WebGLContextBoundObject(context)
24 , mHasEverBeenBound(false)
25 , mTarget(0)
26 , mMinFilter(LOCAL_GL_NEAREST_MIPMAP_LINEAR)
27 , mMagFilter(LOCAL_GL_LINEAR)
28 , mWrapS(LOCAL_GL_REPEAT)
29 , mWrapT(LOCAL_GL_REPEAT)
30 , mFacesCount(0)
31 , mMaxLevelWithCustomImages(0)
32 , mHaveGeneratedMipmap(false)
33 , mFakeBlackStatus(WebGLTextureFakeBlackStatus::IncompleteTexture)
34 {
35 SetIsDOMBinding();
36 mContext->MakeContextCurrent();
37 mContext->gl->fGenTextures(1, &mGLName);
38 mContext->mTextures.insertBack(this);
39 }
41 void
42 WebGLTexture::Delete() {
43 mImageInfos.Clear();
44 mContext->MakeContextCurrent();
45 mContext->gl->fDeleteTextures(1, &mGLName);
46 LinkedListElement<WebGLTexture>::removeFrom(mContext->mTextures);
47 }
49 int64_t
50 WebGLTexture::ImageInfo::MemoryUsage() const {
51 if (mImageDataStatus == WebGLImageDataStatus::NoImageData)
52 return 0;
53 int64_t bitsPerTexel = WebGLContext::GetBitsPerTexel(mWebGLFormat, mWebGLType);
54 return int64_t(mWidth) * int64_t(mHeight) * bitsPerTexel/8;
55 }
57 int64_t
58 WebGLTexture::MemoryUsage() const {
59 if (IsDeleted())
60 return 0;
61 int64_t result = 0;
62 for(size_t face = 0; face < mFacesCount; face++) {
63 if (mHaveGeneratedMipmap) {
64 // Each mipmap level is 1/4 the size of the previous level
65 // 1 + x + x^2 + ... = 1/(1-x)
66 // for x = 1/4, we get 1/(1-1/4) = 4/3
67 result += ImageInfoAtFace(face, 0).MemoryUsage() * 4 / 3;
68 } else {
69 for(size_t level = 0; level <= mMaxLevelWithCustomImages; level++)
70 result += ImageInfoAtFace(face, level).MemoryUsage();
71 }
72 }
73 return result;
74 }
76 bool
77 WebGLTexture::DoesTexture2DMipmapHaveAllLevelsConsistentlyDefined(GLenum texImageTarget) const {
78 if (mHaveGeneratedMipmap)
79 return true;
81 // We want a copy here so we can modify it temporarily.
82 ImageInfo expected = ImageInfoAt(texImageTarget, 0);
84 // checks if custom level>0 images are all defined up to the highest level defined
85 // and have the expected dimensions
86 for (size_t level = 0; level <= mMaxLevelWithCustomImages; ++level) {
87 const ImageInfo& actual = ImageInfoAt(texImageTarget, level);
88 if (actual != expected)
89 return false;
90 expected.mWidth = std::max(1, expected.mWidth >> 1);
91 expected.mHeight = std::max(1, expected.mHeight >> 1);
93 // if the current level has size 1x1, we can stop here: the spec doesn't seem to forbid the existence
94 // of extra useless levels.
95 if (actual.mWidth == 1 && actual.mHeight == 1)
96 return true;
97 }
99 // if we're here, we've exhausted all levels without finding a 1x1 image
100 return false;
101 }
103 void
104 WebGLTexture::Bind(GLenum aTarget) {
105 // this function should only be called by bindTexture().
106 // it assumes that the GL context is already current.
108 bool firstTimeThisTextureIsBound = !mHasEverBeenBound;
110 if (!firstTimeThisTextureIsBound && aTarget != mTarget) {
111 mContext->ErrorInvalidOperation("bindTexture: this texture has already been bound to a different target");
112 // very important to return here before modifying texture state! This was the place when I lost a whole day figuring
113 // very strange 'invalid write' crashes.
114 return;
115 }
117 mTarget = aTarget;
119 mContext->gl->fBindTexture(mTarget, mGLName);
121 if (firstTimeThisTextureIsBound) {
122 mFacesCount = (mTarget == LOCAL_GL_TEXTURE_2D) ? 1 : 6;
123 EnsureMaxLevelWithCustomImagesAtLeast(0);
124 SetFakeBlackStatus(WebGLTextureFakeBlackStatus::Unknown);
126 // thanks to the WebKit people for finding this out: GL_TEXTURE_WRAP_R is not
127 // present in GLES 2, but is present in GL and it seems as if for cube maps
128 // we need to set it to GL_CLAMP_TO_EDGE to get the expected GLES behavior.
129 if (mTarget == LOCAL_GL_TEXTURE_CUBE_MAP && !mContext->gl->IsGLES())
130 mContext->gl->fTexParameteri(mTarget, LOCAL_GL_TEXTURE_WRAP_R, LOCAL_GL_CLAMP_TO_EDGE);
131 }
133 mHasEverBeenBound = true;
134 }
136 void
137 WebGLTexture::SetImageInfo(GLenum aTarget, GLint aLevel,
138 GLsizei aWidth, GLsizei aHeight,
139 GLenum aFormat, GLenum aType, WebGLImageDataStatus aStatus)
140 {
141 if ( (aTarget == LOCAL_GL_TEXTURE_2D) != (mTarget == LOCAL_GL_TEXTURE_2D) )
142 return;
144 EnsureMaxLevelWithCustomImagesAtLeast(aLevel);
146 ImageInfoAt(aTarget, aLevel) = ImageInfo(aWidth, aHeight, aFormat, aType, aStatus);
148 if (aLevel > 0)
149 SetCustomMipmap();
151 // Invalidate framebuffer status cache
152 NotifyFBsStatusChanged();
154 SetFakeBlackStatus(WebGLTextureFakeBlackStatus::Unknown);
155 }
157 void
158 WebGLTexture::SetGeneratedMipmap() {
159 if (!mHaveGeneratedMipmap) {
160 mHaveGeneratedMipmap = true;
161 SetFakeBlackStatus(WebGLTextureFakeBlackStatus::Unknown);
162 }
163 }
165 void
166 WebGLTexture::SetCustomMipmap() {
167 if (mHaveGeneratedMipmap) {
168 // if we were in GeneratedMipmap mode and are now switching to CustomMipmap mode,
169 // we need to compute now all the mipmap image info.
171 // since we were in GeneratedMipmap mode, we know that the level 0 images all have the same info,
172 // and are power-of-two.
173 ImageInfo imageInfo = ImageInfoAtFace(0, 0);
174 NS_ASSERTION(imageInfo.IsPowerOfTwo(), "this texture is NPOT, so how could GenerateMipmap() ever accept it?");
176 GLsizei size = std::max(imageInfo.mWidth, imageInfo.mHeight);
178 // so, the size is a power of two, let's find its log in base 2.
179 size_t maxLevel = 0;
180 for (GLsizei n = size; n > 1; n >>= 1)
181 ++maxLevel;
183 EnsureMaxLevelWithCustomImagesAtLeast(maxLevel);
185 for (size_t level = 1; level <= maxLevel; ++level) {
186 // again, since the sizes are powers of two, no need for any max(1,x) computation
187 imageInfo.mWidth >>= 1;
188 imageInfo.mHeight >>= 1;
189 for(size_t face = 0; face < mFacesCount; ++face)
190 ImageInfoAtFace(face, level) = imageInfo;
191 }
192 }
193 mHaveGeneratedMipmap = false;
194 }
196 bool
197 WebGLTexture::AreAllLevel0ImageInfosEqual() const {
198 for (size_t face = 1; face < mFacesCount; ++face) {
199 if (ImageInfoAtFace(face, 0) != ImageInfoAtFace(0, 0))
200 return false;
201 }
202 return true;
203 }
205 bool
206 WebGLTexture::IsMipmapTexture2DComplete() const {
207 if (mTarget != LOCAL_GL_TEXTURE_2D)
208 return false;
209 if (!ImageInfoAt(LOCAL_GL_TEXTURE_2D, 0).IsPositive())
210 return false;
211 if (mHaveGeneratedMipmap)
212 return true;
213 return DoesTexture2DMipmapHaveAllLevelsConsistentlyDefined(LOCAL_GL_TEXTURE_2D);
214 }
216 bool
217 WebGLTexture::IsCubeComplete() const {
218 if (mTarget != LOCAL_GL_TEXTURE_CUBE_MAP)
219 return false;
220 const ImageInfo &first = ImageInfoAt(LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0);
221 if (!first.IsPositive() || !first.IsSquare())
222 return false;
223 return AreAllLevel0ImageInfosEqual();
224 }
226 static GLenum
227 GLCubeMapFaceById(int id)
228 {
229 GLenum result = LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + id;
230 MOZ_ASSERT(result >= LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X &&
231 result <= LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z);
232 return result;
233 }
235 bool
236 WebGLTexture::IsMipmapCubeComplete() const {
237 if (!IsCubeComplete()) // in particular, this checks that this is a cube map
238 return false;
239 for (int i = 0; i < 6; i++) {
240 GLenum face = GLCubeMapFaceById(i);
241 if (!DoesTexture2DMipmapHaveAllLevelsConsistentlyDefined(face))
242 return false;
243 }
244 return true;
245 }
247 WebGLTextureFakeBlackStatus
248 WebGLTexture::ResolvedFakeBlackStatus() {
249 if (MOZ_LIKELY(mFakeBlackStatus != WebGLTextureFakeBlackStatus::Unknown)) {
250 return mFakeBlackStatus;
251 }
253 // Determine if the texture needs to be faked as a black texture.
254 // See 3.8.2 Shader Execution in the OpenGL ES 2.0.24 spec.
256 for (size_t face = 0; face < mFacesCount; ++face) {
257 if (ImageInfoAtFace(face, 0).mImageDataStatus == WebGLImageDataStatus::NoImageData) {
258 // In case of undefined texture image, we don't print any message because this is a very common
259 // and often legitimate case (asynchronous texture loading).
260 mFakeBlackStatus = WebGLTextureFakeBlackStatus::IncompleteTexture;
261 return mFakeBlackStatus;
262 }
263 }
265 const char *msg_rendering_as_black
266 = "A texture is going to be rendered as if it were black, as per the OpenGL ES 2.0.24 spec section 3.8.2, "
267 "because it";
269 if (mTarget == LOCAL_GL_TEXTURE_2D)
270 {
271 if (DoesMinFilterRequireMipmap())
272 {
273 if (!IsMipmapTexture2DComplete()) {
274 mContext->GenerateWarning
275 ("%s is a 2D texture, with a minification filter requiring a mipmap, "
276 "and is not mipmap complete (as defined in section 3.7.10).", msg_rendering_as_black);
277 mFakeBlackStatus = WebGLTextureFakeBlackStatus::IncompleteTexture;
278 } else if (!ImageInfoAt(mTarget, 0).IsPowerOfTwo()) {
279 mContext->GenerateWarning
280 ("%s is a 2D texture, with a minification filter requiring a mipmap, "
281 "and either its width or height is not a power of two.", msg_rendering_as_black);
282 mFakeBlackStatus = WebGLTextureFakeBlackStatus::IncompleteTexture;
283 }
284 }
285 else // no mipmap required
286 {
287 if (!ImageInfoAt(mTarget, 0).IsPositive()) {
288 mContext->GenerateWarning
289 ("%s is a 2D texture and its width or height is equal to zero.",
290 msg_rendering_as_black);
291 mFakeBlackStatus = WebGLTextureFakeBlackStatus::IncompleteTexture;
292 } else if (!AreBothWrapModesClampToEdge() && !ImageInfoAt(mTarget, 0).IsPowerOfTwo()) {
293 mContext->GenerateWarning
294 ("%s is a 2D texture, with a minification filter not requiring a mipmap, "
295 "with its width or height not a power of two, and with a wrap mode "
296 "different from CLAMP_TO_EDGE.", msg_rendering_as_black);
297 mFakeBlackStatus = WebGLTextureFakeBlackStatus::IncompleteTexture;
298 }
299 }
300 }
301 else // cube map
302 {
303 bool areAllLevel0ImagesPOT = true;
304 for (size_t face = 0; face < mFacesCount; ++face)
305 areAllLevel0ImagesPOT &= ImageInfoAtFace(face, 0).IsPowerOfTwo();
307 if (DoesMinFilterRequireMipmap())
308 {
309 if (!IsMipmapCubeComplete()) {
310 mContext->GenerateWarning("%s is a cube map texture, with a minification filter requiring a mipmap, "
311 "and is not mipmap cube complete (as defined in section 3.7.10).",
312 msg_rendering_as_black);
313 mFakeBlackStatus = WebGLTextureFakeBlackStatus::IncompleteTexture;
314 } else if (!areAllLevel0ImagesPOT) {
315 mContext->GenerateWarning("%s is a cube map texture, with a minification filter requiring a mipmap, "
316 "and either the width or the height of some level 0 image is not a power of two.",
317 msg_rendering_as_black);
318 mFakeBlackStatus = WebGLTextureFakeBlackStatus::IncompleteTexture;
319 }
320 }
321 else // no mipmap required
322 {
323 if (!IsCubeComplete()) {
324 mContext->GenerateWarning("%s is a cube map texture, with a minification filter not requiring a mipmap, "
325 "and is not cube complete (as defined in section 3.7.10).",
326 msg_rendering_as_black);
327 mFakeBlackStatus = WebGLTextureFakeBlackStatus::IncompleteTexture;
328 } else if (!AreBothWrapModesClampToEdge() && !areAllLevel0ImagesPOT) {
329 mContext->GenerateWarning("%s is a cube map texture, with a minification filter not requiring a mipmap, "
330 "with some level 0 image having width or height not a power of two, and with a wrap mode "
331 "different from CLAMP_TO_EDGE.", msg_rendering_as_black);
332 mFakeBlackStatus = WebGLTextureFakeBlackStatus::IncompleteTexture;
333 }
334 }
335 }
337 if (ImageInfoBase().mWebGLType == LOCAL_GL_FLOAT &&
338 !Context()->IsExtensionEnabled(WebGLExtensionID::OES_texture_float_linear))
339 {
340 if (mMinFilter == LOCAL_GL_LINEAR ||
341 mMinFilter == LOCAL_GL_LINEAR_MIPMAP_LINEAR ||
342 mMinFilter == LOCAL_GL_LINEAR_MIPMAP_NEAREST ||
343 mMinFilter == LOCAL_GL_NEAREST_MIPMAP_LINEAR)
344 {
345 mContext->GenerateWarning("%s is a texture with a linear minification filter, "
346 "which is not compatible with gl.FLOAT by default. "
347 "Try enabling the OES_texture_float_linear extension if supported.", msg_rendering_as_black);
348 mFakeBlackStatus = WebGLTextureFakeBlackStatus::IncompleteTexture;
349 }
350 else if (mMagFilter == LOCAL_GL_LINEAR)
351 {
352 mContext->GenerateWarning("%s is a texture with a linear magnification filter, "
353 "which is not compatible with gl.FLOAT by default. "
354 "Try enabling the OES_texture_float_linear extension if supported.", msg_rendering_as_black);
355 mFakeBlackStatus = WebGLTextureFakeBlackStatus::IncompleteTexture;
356 }
357 } else if (ImageInfoBase().mWebGLType == LOCAL_GL_HALF_FLOAT_OES &&
358 !Context()->IsExtensionEnabled(WebGLExtensionID::OES_texture_half_float_linear))
359 {
360 if (mMinFilter == LOCAL_GL_LINEAR ||
361 mMinFilter == LOCAL_GL_LINEAR_MIPMAP_LINEAR ||
362 mMinFilter == LOCAL_GL_LINEAR_MIPMAP_NEAREST ||
363 mMinFilter == LOCAL_GL_NEAREST_MIPMAP_LINEAR)
364 {
365 mContext->GenerateWarning("%s is a texture with a linear minification filter, "
366 "which is not compatible with gl.HALF_FLOAT by default. "
367 "Try enabling the OES_texture_half_float_linear extension if supported.", msg_rendering_as_black);
368 mFakeBlackStatus = WebGLTextureFakeBlackStatus::IncompleteTexture;
369 }
370 else if (mMagFilter == LOCAL_GL_LINEAR)
371 {
372 mContext->GenerateWarning("%s is a texture with a linear magnification filter, "
373 "which is not compatible with gl.HALF_FLOAT by default. "
374 "Try enabling the OES_texture_half_float_linear extension if supported.", msg_rendering_as_black);
375 mFakeBlackStatus = WebGLTextureFakeBlackStatus::IncompleteTexture;
376 }
377 }
379 // We have exhausted all cases of incomplete textures, where we would need opaque black.
380 // We may still need transparent black in case of uninitialized image data.
381 bool hasUninitializedImageData = false;
382 for (size_t level = 0; level <= mMaxLevelWithCustomImages; ++level) {
383 for (size_t face = 0; face < mFacesCount; ++face) {
384 hasUninitializedImageData |= (ImageInfoAtFace(face, level).mImageDataStatus == WebGLImageDataStatus::UninitializedImageData);
385 }
386 }
388 if (hasUninitializedImageData) {
389 bool hasAnyInitializedImageData = false;
390 for (size_t level = 0; level <= mMaxLevelWithCustomImages; ++level) {
391 for (size_t face = 0; face < mFacesCount; ++face) {
392 if (ImageInfoAtFace(face, level).mImageDataStatus == WebGLImageDataStatus::InitializedImageData) {
393 hasAnyInitializedImageData = true;
394 break;
395 }
396 }
397 if (hasAnyInitializedImageData) {
398 break;
399 }
400 }
402 if (hasAnyInitializedImageData) {
403 // The texture contains some initialized image data, and some uninitialized image data.
404 // In this case, we have no choice but to initialize all image data now. Fortunately,
405 // in this case we know that we can't be dealing with a depth texture per WEBGL_depth_texture
406 // and ANGLE_depth_texture (which allow only one image per texture) so we can assume that
407 // glTexImage2D is able to upload data to images.
408 for (size_t level = 0; level <= mMaxLevelWithCustomImages; ++level) {
409 for (size_t face = 0; face < mFacesCount; ++face) {
410 GLenum imageTarget = mTarget == LOCAL_GL_TEXTURE_2D
411 ? LOCAL_GL_TEXTURE_2D
412 : LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + face;
413 const ImageInfo& imageInfo = ImageInfoAt(imageTarget, level);
414 if (imageInfo.mImageDataStatus == WebGLImageDataStatus::UninitializedImageData) {
415 DoDeferredImageInitialization(imageTarget, level);
416 }
417 }
418 }
419 mFakeBlackStatus = WebGLTextureFakeBlackStatus::NotNeeded;
420 } else {
421 // The texture only contains uninitialized image data. In this case,
422 // we can use a black texture for it.
423 mFakeBlackStatus = WebGLTextureFakeBlackStatus::UninitializedImageData;
424 }
425 }
427 // we have exhausted all cases where we do need fakeblack, so if the status is still unknown,
428 // that means that we do NOT need it.
429 if (mFakeBlackStatus == WebGLTextureFakeBlackStatus::Unknown) {
430 mFakeBlackStatus = WebGLTextureFakeBlackStatus::NotNeeded;
431 }
433 MOZ_ASSERT(mFakeBlackStatus != WebGLTextureFakeBlackStatus::Unknown);
434 return mFakeBlackStatus;
435 }
437 void
438 WebGLTexture::DoDeferredImageInitialization(GLenum imageTarget, GLint level)
439 {
440 const ImageInfo& imageInfo = ImageInfoAt(imageTarget, level);
441 MOZ_ASSERT(imageInfo.mImageDataStatus == WebGLImageDataStatus::UninitializedImageData);
443 mContext->MakeContextCurrent();
444 gl::ScopedBindTexture autoBindTex(mContext->gl, GLName(), mTarget);
446 GLenum format = imageInfo.mWebGLFormat;
447 GLenum type = imageInfo.mWebGLType;
448 WebGLTexelFormat texelformat = GetWebGLTexelFormat(format, type);
449 uint32_t texelsize = WebGLTexelConversions::TexelBytesForFormat(texelformat);
450 CheckedUint32 checked_byteLength
451 = WebGLContext::GetImageSize(
452 imageInfo.mHeight,
453 imageInfo.mWidth,
454 texelsize,
455 mContext->mPixelStoreUnpackAlignment);
456 MOZ_ASSERT(checked_byteLength.isValid()); // should have been checked earlier
457 void *zeros = calloc(1, checked_byteLength.value());
459 gl::GLContext* gl = mContext->gl;
460 GLenum driverType = DriverTypeFromType(gl, type);
461 GLenum driverInternalFormat = LOCAL_GL_NONE;
462 GLenum driverFormat = LOCAL_GL_NONE;
463 DriverFormatsFromFormatAndType(gl, format, type, &driverInternalFormat, &driverFormat);
465 mContext->GetAndFlushUnderlyingGLErrors();
466 gl->fTexImage2D(imageTarget, level, driverInternalFormat,
467 imageInfo.mWidth, imageInfo.mHeight,
468 0, driverFormat, driverType,
469 zeros);
470 GLenum error = mContext->GetAndFlushUnderlyingGLErrors();
472 free(zeros);
473 SetImageDataStatus(imageTarget, level, WebGLImageDataStatus::InitializedImageData);
475 if (error) {
476 // Should only be OUT_OF_MEMORY. Anyway, there's no good way to recover from this here.
477 MOZ_CRASH(); // errors on texture upload have been related to video memory exposure in the past.
478 return;
479 }
480 }
482 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLTexture)
484 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGLTexture, AddRef)
485 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebGLTexture, Release)