|
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 "WebGLContext.h" |
|
7 #include "WebGL1Context.h" |
|
8 #include "WebGLObjectModel.h" |
|
9 #include "WebGLExtensions.h" |
|
10 #include "WebGLContextUtils.h" |
|
11 #include "WebGLBuffer.h" |
|
12 #include "WebGLVertexAttribData.h" |
|
13 #include "WebGLMemoryTracker.h" |
|
14 #include "WebGLFramebuffer.h" |
|
15 #include "WebGLVertexArray.h" |
|
16 #include "WebGLQuery.h" |
|
17 |
|
18 #include "AccessCheck.h" |
|
19 #include "nsIConsoleService.h" |
|
20 #include "nsServiceManagerUtils.h" |
|
21 #include "nsIClassInfoImpl.h" |
|
22 #include "nsContentUtils.h" |
|
23 #include "nsIXPConnect.h" |
|
24 #include "nsError.h" |
|
25 #include "nsIGfxInfo.h" |
|
26 #include "nsIWidget.h" |
|
27 |
|
28 #include "nsIVariant.h" |
|
29 |
|
30 #include "ImageEncoder.h" |
|
31 |
|
32 #include "gfxContext.h" |
|
33 #include "gfxPattern.h" |
|
34 #include "gfxUtils.h" |
|
35 |
|
36 #include "CanvasUtils.h" |
|
37 #include "nsDisplayList.h" |
|
38 |
|
39 #include "GLContextProvider.h" |
|
40 #include "GLContext.h" |
|
41 #include "ScopedGLHelpers.h" |
|
42 #include "GLReadTexImageHelper.h" |
|
43 |
|
44 #include "gfxCrashReporterUtils.h" |
|
45 |
|
46 #include "nsSVGEffects.h" |
|
47 |
|
48 #include "prenv.h" |
|
49 |
|
50 #include "mozilla/Preferences.h" |
|
51 #include "mozilla/Services.h" |
|
52 #include "mozilla/Telemetry.h" |
|
53 |
|
54 #include "nsIObserverService.h" |
|
55 #include "mozilla/Services.h" |
|
56 #include "mozilla/dom/WebGLRenderingContextBinding.h" |
|
57 #include "mozilla/dom/BindingUtils.h" |
|
58 #include "mozilla/dom/ImageData.h" |
|
59 #include "mozilla/ProcessPriorityManager.h" |
|
60 #include "mozilla/EnumeratedArrayCycleCollection.h" |
|
61 |
|
62 #include "Layers.h" |
|
63 |
|
64 #ifdef MOZ_WIDGET_GONK |
|
65 #include "mozilla/layers/ShadowLayers.h" |
|
66 #endif |
|
67 |
|
68 using namespace mozilla; |
|
69 using namespace mozilla::dom; |
|
70 using namespace mozilla::gfx; |
|
71 using namespace mozilla::gl; |
|
72 using namespace mozilla::layers; |
|
73 |
|
74 NS_IMETHODIMP |
|
75 WebGLMemoryPressureObserver::Observe(nsISupports* aSubject, |
|
76 const char* aTopic, |
|
77 const char16_t* aSomeData) |
|
78 { |
|
79 if (strcmp(aTopic, "memory-pressure")) |
|
80 return NS_OK; |
|
81 |
|
82 bool wantToLoseContext = true; |
|
83 |
|
84 if (!mContext->mCanLoseContextInForeground && |
|
85 ProcessPriorityManager::CurrentProcessIsForeground()) |
|
86 wantToLoseContext = false; |
|
87 else if (!nsCRT::strcmp(aSomeData, |
|
88 MOZ_UTF16("heap-minimize"))) |
|
89 wantToLoseContext = mContext->mLoseContextOnHeapMinimize; |
|
90 |
|
91 if (wantToLoseContext) |
|
92 mContext->ForceLoseContext(); |
|
93 |
|
94 return NS_OK; |
|
95 } |
|
96 |
|
97 |
|
98 WebGLContextOptions::WebGLContextOptions() |
|
99 : alpha(true), depth(true), stencil(false), |
|
100 premultipliedAlpha(true), antialias(true), |
|
101 preserveDrawingBuffer(false) |
|
102 { |
|
103 // Set default alpha state based on preference. |
|
104 if (Preferences::GetBool("webgl.default-no-alpha", false)) |
|
105 alpha = false; |
|
106 } |
|
107 |
|
108 WebGLContext::WebGLContext() |
|
109 : gl(nullptr) |
|
110 { |
|
111 SetIsDOMBinding(); |
|
112 |
|
113 mGeneration = 0; |
|
114 mInvalidated = false; |
|
115 mShouldPresent = true; |
|
116 mResetLayer = true; |
|
117 mOptionsFrozen = false; |
|
118 |
|
119 mActiveTexture = 0; |
|
120 mPixelStoreFlipY = false; |
|
121 mPixelStorePremultiplyAlpha = false; |
|
122 mPixelStoreColorspaceConversion = BROWSER_DEFAULT_WEBGL; |
|
123 |
|
124 mShaderValidation = true; |
|
125 |
|
126 mFakeBlackStatus = WebGLContextFakeBlackStatus::NotNeeded; |
|
127 |
|
128 mVertexAttrib0Vector[0] = 0; |
|
129 mVertexAttrib0Vector[1] = 0; |
|
130 mVertexAttrib0Vector[2] = 0; |
|
131 mVertexAttrib0Vector[3] = 1; |
|
132 mFakeVertexAttrib0BufferObjectVector[0] = 0; |
|
133 mFakeVertexAttrib0BufferObjectVector[1] = 0; |
|
134 mFakeVertexAttrib0BufferObjectVector[2] = 0; |
|
135 mFakeVertexAttrib0BufferObjectVector[3] = 1; |
|
136 mFakeVertexAttrib0BufferObjectSize = 0; |
|
137 mFakeVertexAttrib0BufferObject = 0; |
|
138 mFakeVertexAttrib0BufferStatus = WebGLVertexAttrib0Status::Default; |
|
139 |
|
140 mViewportX = 0; |
|
141 mViewportY = 0; |
|
142 mViewportWidth = 0; |
|
143 mViewportHeight = 0; |
|
144 |
|
145 mScissorTestEnabled = 0; |
|
146 mDitherEnabled = 1; |
|
147 mRasterizerDiscardEnabled = 0; // OpenGL ES 3.0 spec p244 |
|
148 |
|
149 // initialize some GL values: we're going to get them from the GL and use them as the sizes of arrays, |
|
150 // so in case glGetIntegerv leaves them uninitialized because of a GL bug, we would have very weird crashes. |
|
151 mGLMaxVertexAttribs = 0; |
|
152 mGLMaxTextureUnits = 0; |
|
153 mGLMaxTextureSize = 0; |
|
154 mGLMaxCubeMapTextureSize = 0; |
|
155 mGLMaxRenderbufferSize = 0; |
|
156 mGLMaxTextureImageUnits = 0; |
|
157 mGLMaxVertexTextureImageUnits = 0; |
|
158 mGLMaxVaryingVectors = 0; |
|
159 mGLMaxFragmentUniformVectors = 0; |
|
160 mGLMaxVertexUniformVectors = 0; |
|
161 mGLMaxColorAttachments = 1; |
|
162 mGLMaxDrawBuffers = 1; |
|
163 mGLMaxTransformFeedbackSeparateAttribs = 0; |
|
164 |
|
165 // See OpenGL ES 2.0.25 spec, 6.2 State Tables, table 6.13 |
|
166 mPixelStorePackAlignment = 4; |
|
167 mPixelStoreUnpackAlignment = 4; |
|
168 |
|
169 WebGLMemoryTracker::AddWebGLContext(this); |
|
170 |
|
171 mAllowRestore = true; |
|
172 mContextLossTimerRunning = false; |
|
173 mDrawSinceContextLossTimerSet = false; |
|
174 mContextRestorer = do_CreateInstance("@mozilla.org/timer;1"); |
|
175 mContextStatus = ContextNotLost; |
|
176 mContextLostErrorSet = false; |
|
177 mLoseContextOnHeapMinimize = false; |
|
178 mCanLoseContextInForeground = true; |
|
179 |
|
180 mAlreadyGeneratedWarnings = 0; |
|
181 mAlreadyWarnedAboutFakeVertexAttrib0 = false; |
|
182 mAlreadyWarnedAboutViewportLargerThanDest = false; |
|
183 mMaxWarnings = Preferences::GetInt("webgl.max-warnings-per-context", 32); |
|
184 if (mMaxWarnings < -1) |
|
185 { |
|
186 GenerateWarning("webgl.max-warnings-per-context size is too large (seems like a negative value wrapped)"); |
|
187 mMaxWarnings = 0; |
|
188 } |
|
189 |
|
190 mLastUseIndex = 0; |
|
191 |
|
192 InvalidateBufferFetching(); |
|
193 |
|
194 mBackbufferNeedsClear = true; |
|
195 |
|
196 mDisableFragHighP = false; |
|
197 |
|
198 mDrawCallsSinceLastFlush = 0; |
|
199 } |
|
200 |
|
201 WebGLContext::~WebGLContext() |
|
202 { |
|
203 DestroyResourcesAndContext(); |
|
204 WebGLMemoryTracker::RemoveWebGLContext(this); |
|
205 TerminateContextLossTimer(); |
|
206 mContextRestorer = nullptr; |
|
207 } |
|
208 |
|
209 void |
|
210 WebGLContext::DestroyResourcesAndContext() |
|
211 { |
|
212 if (mMemoryPressureObserver) { |
|
213 nsCOMPtr<nsIObserverService> observerService |
|
214 = mozilla::services::GetObserverService(); |
|
215 if (observerService) { |
|
216 observerService->RemoveObserver(mMemoryPressureObserver, |
|
217 "memory-pressure"); |
|
218 } |
|
219 mMemoryPressureObserver = nullptr; |
|
220 } |
|
221 |
|
222 if (!gl) |
|
223 return; |
|
224 |
|
225 gl->MakeCurrent(); |
|
226 |
|
227 mBound2DTextures.Clear(); |
|
228 mBoundCubeMapTextures.Clear(); |
|
229 mBoundArrayBuffer = nullptr; |
|
230 mBoundTransformFeedbackBuffer = nullptr; |
|
231 mCurrentProgram = nullptr; |
|
232 mBoundFramebuffer = nullptr; |
|
233 mActiveOcclusionQuery = nullptr; |
|
234 mBoundRenderbuffer = nullptr; |
|
235 mBoundVertexArray = nullptr; |
|
236 mDefaultVertexArray = nullptr; |
|
237 |
|
238 while (!mTextures.isEmpty()) |
|
239 mTextures.getLast()->DeleteOnce(); |
|
240 while (!mVertexArrays.isEmpty()) |
|
241 mVertexArrays.getLast()->DeleteOnce(); |
|
242 while (!mBuffers.isEmpty()) |
|
243 mBuffers.getLast()->DeleteOnce(); |
|
244 while (!mRenderbuffers.isEmpty()) |
|
245 mRenderbuffers.getLast()->DeleteOnce(); |
|
246 while (!mFramebuffers.isEmpty()) |
|
247 mFramebuffers.getLast()->DeleteOnce(); |
|
248 while (!mShaders.isEmpty()) |
|
249 mShaders.getLast()->DeleteOnce(); |
|
250 while (!mPrograms.isEmpty()) |
|
251 mPrograms.getLast()->DeleteOnce(); |
|
252 while (!mQueries.isEmpty()) |
|
253 mQueries.getLast()->DeleteOnce(); |
|
254 |
|
255 mBlackOpaqueTexture2D = nullptr; |
|
256 mBlackOpaqueTextureCubeMap = nullptr; |
|
257 mBlackTransparentTexture2D = nullptr; |
|
258 mBlackTransparentTextureCubeMap = nullptr; |
|
259 |
|
260 if (mFakeVertexAttrib0BufferObject) { |
|
261 gl->fDeleteBuffers(1, &mFakeVertexAttrib0BufferObject); |
|
262 } |
|
263 |
|
264 // disable all extensions except "WEBGL_lose_context". see bug #927969 |
|
265 // spec: http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2 |
|
266 for (size_t i = 0; i < size_t(WebGLExtensionID::Max); ++i) { |
|
267 WebGLExtensionID extension = WebGLExtensionID(i); |
|
268 |
|
269 if (!IsExtensionEnabled(extension) || (extension == WebGLExtensionID::WEBGL_lose_context)) |
|
270 continue; |
|
271 |
|
272 mExtensions[extension]->MarkLost(); |
|
273 mExtensions[extension] = nullptr; |
|
274 } |
|
275 |
|
276 // We just got rid of everything, so the context had better |
|
277 // have been going away. |
|
278 #ifdef DEBUG |
|
279 if (gl->DebugMode()) { |
|
280 printf_stderr("--- WebGL context destroyed: %p\n", gl.get()); |
|
281 } |
|
282 #endif |
|
283 |
|
284 gl = nullptr; |
|
285 } |
|
286 |
|
287 void |
|
288 WebGLContext::Invalidate() |
|
289 { |
|
290 if (mInvalidated) |
|
291 return; |
|
292 |
|
293 if (!mCanvasElement) |
|
294 return; |
|
295 |
|
296 nsSVGEffects::InvalidateDirectRenderingObservers(mCanvasElement); |
|
297 |
|
298 mInvalidated = true; |
|
299 mCanvasElement->InvalidateCanvasContent(nullptr); |
|
300 } |
|
301 |
|
302 // |
|
303 // nsICanvasRenderingContextInternal |
|
304 // |
|
305 |
|
306 NS_IMETHODIMP |
|
307 WebGLContext::SetContextOptions(JSContext* aCx, JS::Handle<JS::Value> aOptions) |
|
308 { |
|
309 if (aOptions.isNullOrUndefined() && mOptionsFrozen) { |
|
310 return NS_OK; |
|
311 } |
|
312 |
|
313 WebGLContextAttributes attributes; |
|
314 NS_ENSURE_TRUE(attributes.Init(aCx, aOptions), NS_ERROR_UNEXPECTED); |
|
315 |
|
316 WebGLContextOptions newOpts; |
|
317 |
|
318 newOpts.stencil = attributes.mStencil; |
|
319 newOpts.depth = attributes.mDepth; |
|
320 newOpts.premultipliedAlpha = attributes.mPremultipliedAlpha; |
|
321 newOpts.antialias = attributes.mAntialias; |
|
322 newOpts.preserveDrawingBuffer = attributes.mPreserveDrawingBuffer; |
|
323 if (attributes.mAlpha.WasPassed()) { |
|
324 newOpts.alpha = attributes.mAlpha.Value(); |
|
325 } |
|
326 |
|
327 // enforce that if stencil is specified, we also give back depth |
|
328 newOpts.depth |= newOpts.stencil; |
|
329 |
|
330 #if 0 |
|
331 GenerateWarning("aaHint: %d stencil: %d depth: %d alpha: %d premult: %d preserve: %d\n", |
|
332 newOpts.antialias ? 1 : 0, |
|
333 newOpts.stencil ? 1 : 0, |
|
334 newOpts.depth ? 1 : 0, |
|
335 newOpts.alpha ? 1 : 0, |
|
336 newOpts.premultipliedAlpha ? 1 : 0, |
|
337 newOpts.preserveDrawingBuffer ? 1 : 0); |
|
338 #endif |
|
339 |
|
340 if (mOptionsFrozen && newOpts != mOptions) { |
|
341 // Error if the options are already frozen, and the ones that were asked for |
|
342 // aren't the same as what they were originally. |
|
343 return NS_ERROR_FAILURE; |
|
344 } |
|
345 |
|
346 mOptions = newOpts; |
|
347 return NS_OK; |
|
348 } |
|
349 |
|
350 #ifdef DEBUG |
|
351 int32_t |
|
352 WebGLContext::GetWidth() const |
|
353 { |
|
354 return mWidth; |
|
355 } |
|
356 |
|
357 int32_t |
|
358 WebGLContext::GetHeight() const |
|
359 { |
|
360 return mHeight; |
|
361 } |
|
362 #endif |
|
363 |
|
364 NS_IMETHODIMP |
|
365 WebGLContext::SetDimensions(int32_t width, int32_t height) |
|
366 { |
|
367 // Early error return cases |
|
368 |
|
369 if (width < 0 || height < 0) { |
|
370 GenerateWarning("Canvas size is too large (seems like a negative value wrapped)"); |
|
371 return NS_ERROR_OUT_OF_MEMORY; |
|
372 } |
|
373 |
|
374 if (!GetCanvas()) |
|
375 return NS_ERROR_FAILURE; |
|
376 |
|
377 // Early success return cases |
|
378 |
|
379 GetCanvas()->InvalidateCanvas(); |
|
380 |
|
381 if (gl && mWidth == width && mHeight == height) |
|
382 return NS_OK; |
|
383 |
|
384 // Zero-sized surfaces can cause problems. |
|
385 if (width == 0) { |
|
386 width = 1; |
|
387 } |
|
388 if (height == 0) { |
|
389 height = 1; |
|
390 } |
|
391 |
|
392 // If we already have a gl context, then we just need to resize it |
|
393 if (gl) { |
|
394 MakeContextCurrent(); |
|
395 |
|
396 // If we've already drawn, we should commit the current buffer. |
|
397 PresentScreenBuffer(); |
|
398 |
|
399 // ResizeOffscreen scraps the current prod buffer before making a new one. |
|
400 gl->ResizeOffscreen(gfx::IntSize(width, height)); // Doesn't matter if it succeeds (soft-fail) |
|
401 // It's unlikely that we'll get a proper-sized context if we recreate if we didn't on resize |
|
402 |
|
403 // everything's good, we're done here |
|
404 mWidth = gl->OffscreenSize().width; |
|
405 mHeight = gl->OffscreenSize().height; |
|
406 mResetLayer = true; |
|
407 |
|
408 mBackbufferNeedsClear = true; |
|
409 |
|
410 return NS_OK; |
|
411 } |
|
412 |
|
413 // End of early return cases. |
|
414 // At this point we know that we're not just resizing an existing context, |
|
415 // we are initializing a new context. |
|
416 |
|
417 // if we exceeded either the global or the per-principal limit for WebGL contexts, |
|
418 // lose the oldest-used context now to free resources. Note that we can't do that |
|
419 // in the WebGLContext constructor as we don't have a canvas element yet there. |
|
420 // Here is the right place to do so, as we are about to create the OpenGL context |
|
421 // and that is what can fail if we already have too many. |
|
422 LoseOldestWebGLContextIfLimitExceeded(); |
|
423 |
|
424 // Get some prefs for some preferred/overriden things |
|
425 NS_ENSURE_TRUE(Preferences::GetRootBranch(), NS_ERROR_FAILURE); |
|
426 |
|
427 #ifdef XP_WIN |
|
428 bool preferEGL = |
|
429 Preferences::GetBool("webgl.prefer-egl", false); |
|
430 bool preferOpenGL = |
|
431 Preferences::GetBool("webgl.prefer-native-gl", false); |
|
432 #endif |
|
433 bool forceEnabled = |
|
434 Preferences::GetBool("webgl.force-enabled", false); |
|
435 bool disabled = |
|
436 Preferences::GetBool("webgl.disabled", false); |
|
437 bool prefer16bit = |
|
438 Preferences::GetBool("webgl.prefer-16bpp", false); |
|
439 |
|
440 ScopedGfxFeatureReporter reporter("WebGL", forceEnabled); |
|
441 |
|
442 if (disabled) |
|
443 return NS_ERROR_FAILURE; |
|
444 |
|
445 // We're going to create an entirely new context. If our |
|
446 // generation is not 0 right now (that is, if this isn't the first |
|
447 // context we're creating), we may have to dispatch a context lost |
|
448 // event. |
|
449 |
|
450 // If incrementing the generation would cause overflow, |
|
451 // don't allow it. Allowing this would allow us to use |
|
452 // resource handles created from older context generations. |
|
453 if (!(mGeneration + 1).isValid()) |
|
454 return NS_ERROR_FAILURE; // exit without changing the value of mGeneration |
|
455 |
|
456 SurfaceCaps caps; |
|
457 |
|
458 caps.color = true; |
|
459 caps.alpha = mOptions.alpha; |
|
460 caps.depth = mOptions.depth; |
|
461 caps.stencil = mOptions.stencil; |
|
462 |
|
463 // we should really have this behind a |
|
464 // |gfxPlatform::GetPlatform()->GetScreenDepth() == 16| check, but |
|
465 // for now it's just behind a pref for testing/evaluation. |
|
466 caps.bpp16 = prefer16bit; |
|
467 |
|
468 caps.preserve = mOptions.preserveDrawingBuffer; |
|
469 |
|
470 #ifdef MOZ_WIDGET_GONK |
|
471 nsIWidget *docWidget = nsContentUtils::WidgetForDocument(mCanvasElement->OwnerDoc()); |
|
472 if (docWidget) { |
|
473 layers::LayerManager *layerManager = docWidget->GetLayerManager(); |
|
474 if (layerManager) { |
|
475 // XXX we really want "AsSurfaceAllocator" here for generality |
|
476 layers::ShadowLayerForwarder *forwarder = layerManager->AsShadowForwarder(); |
|
477 if (forwarder) { |
|
478 caps.surfaceAllocator = static_cast<layers::ISurfaceAllocator*>(forwarder); |
|
479 } |
|
480 } |
|
481 } |
|
482 #endif |
|
483 |
|
484 bool forceMSAA = |
|
485 Preferences::GetBool("webgl.msaa-force", false); |
|
486 |
|
487 int32_t status; |
|
488 nsCOMPtr<nsIGfxInfo> gfxInfo = do_GetService("@mozilla.org/gfx/info;1"); |
|
489 if (mOptions.antialias && |
|
490 gfxInfo && |
|
491 NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_WEBGL_MSAA, &status))) { |
|
492 if (status == nsIGfxInfo::FEATURE_NO_INFO || forceMSAA) { |
|
493 caps.antialias = true; |
|
494 } |
|
495 } |
|
496 |
|
497 #ifdef XP_WIN |
|
498 if (PR_GetEnv("MOZ_WEBGL_PREFER_EGL")) { |
|
499 preferEGL = true; |
|
500 } |
|
501 #endif |
|
502 |
|
503 // Ask GfxInfo about what we should use |
|
504 bool useOpenGL = true; |
|
505 |
|
506 #ifdef XP_WIN |
|
507 bool useANGLE = true; |
|
508 #endif |
|
509 |
|
510 if (gfxInfo && !forceEnabled) { |
|
511 if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_WEBGL_OPENGL, &status))) { |
|
512 if (status != nsIGfxInfo::FEATURE_NO_INFO) { |
|
513 useOpenGL = false; |
|
514 } |
|
515 } |
|
516 #ifdef XP_WIN |
|
517 if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_WEBGL_ANGLE, &status))) { |
|
518 if (status != nsIGfxInfo::FEATURE_NO_INFO) { |
|
519 useANGLE = false; |
|
520 } |
|
521 } |
|
522 #endif |
|
523 } |
|
524 |
|
525 #ifdef XP_WIN |
|
526 // allow forcing GL and not EGL/ANGLE |
|
527 if (PR_GetEnv("MOZ_WEBGL_FORCE_OPENGL")) { |
|
528 preferEGL = false; |
|
529 useANGLE = false; |
|
530 useOpenGL = true; |
|
531 } |
|
532 #endif |
|
533 |
|
534 gfxIntSize size(width, height); |
|
535 |
|
536 #ifdef XP_WIN |
|
537 // if we want EGL, try it now |
|
538 if (!gl && (preferEGL || useANGLE) && !preferOpenGL) { |
|
539 gl = gl::GLContextProviderEGL::CreateOffscreen(size, caps); |
|
540 if (!gl || !InitAndValidateGL()) { |
|
541 GenerateWarning("Error during ANGLE OpenGL ES initialization"); |
|
542 return NS_ERROR_FAILURE; |
|
543 } |
|
544 } |
|
545 #endif |
|
546 |
|
547 // try the default provider, whatever that is |
|
548 if (!gl && useOpenGL) { |
|
549 gl = gl::GLContextProvider::CreateOffscreen(size, caps); |
|
550 if (gl && !InitAndValidateGL()) { |
|
551 GenerateWarning("Error during OpenGL initialization"); |
|
552 return NS_ERROR_FAILURE; |
|
553 } |
|
554 } |
|
555 |
|
556 if (!gl) { |
|
557 GenerateWarning("Can't get a usable WebGL context"); |
|
558 return NS_ERROR_FAILURE; |
|
559 } |
|
560 |
|
561 #ifdef DEBUG |
|
562 if (gl->DebugMode()) { |
|
563 printf_stderr("--- WebGL context created: %p\n", gl.get()); |
|
564 } |
|
565 #endif |
|
566 |
|
567 mWidth = width; |
|
568 mHeight = height; |
|
569 mViewportWidth = width; |
|
570 mViewportHeight = height; |
|
571 mResetLayer = true; |
|
572 mOptionsFrozen = true; |
|
573 |
|
574 mHasRobustness = gl->HasRobustness(); |
|
575 |
|
576 // increment the generation number |
|
577 ++mGeneration; |
|
578 |
|
579 #if 0 |
|
580 if (mGeneration > 0) { |
|
581 // XXX dispatch context lost event |
|
582 } |
|
583 #endif |
|
584 |
|
585 MakeContextCurrent(); |
|
586 |
|
587 // Make sure that we clear this out, otherwise |
|
588 // we'll end up displaying random memory |
|
589 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0); |
|
590 |
|
591 gl->fClearColor(0.0f, 0.0f, 0.0f, 0.0f); |
|
592 gl->fClearDepth(1.0f); |
|
593 gl->fClearStencil(0); |
|
594 |
|
595 mBackbufferNeedsClear = true; |
|
596 |
|
597 // Clear immediately, because we need to present the cleared initial |
|
598 // buffer. |
|
599 ClearBackbufferIfNeeded(); |
|
600 |
|
601 mShouldPresent = true; |
|
602 |
|
603 MOZ_ASSERT(gl->Caps().color == caps.color); |
|
604 MOZ_ASSERT(gl->Caps().alpha == caps.alpha); |
|
605 MOZ_ASSERT(gl->Caps().depth == caps.depth || !gl->Caps().depth); |
|
606 MOZ_ASSERT(gl->Caps().stencil == caps.stencil || !gl->Caps().stencil); |
|
607 MOZ_ASSERT(gl->Caps().antialias == caps.antialias || !gl->Caps().antialias); |
|
608 MOZ_ASSERT(gl->Caps().preserve == caps.preserve); |
|
609 |
|
610 reporter.SetSuccessful(); |
|
611 return NS_OK; |
|
612 } |
|
613 |
|
614 void |
|
615 WebGLContext::ClearBackbufferIfNeeded() |
|
616 { |
|
617 if (!mBackbufferNeedsClear) |
|
618 return; |
|
619 |
|
620 #ifdef DEBUG |
|
621 gl->MakeCurrent(); |
|
622 |
|
623 GLuint fb = 0; |
|
624 gl->GetUIntegerv(LOCAL_GL_FRAMEBUFFER_BINDING, &fb); |
|
625 MOZ_ASSERT(fb == 0); |
|
626 #endif |
|
627 |
|
628 ClearScreen(); |
|
629 |
|
630 mBackbufferNeedsClear = false; |
|
631 } |
|
632 |
|
633 void WebGLContext::LoseOldestWebGLContextIfLimitExceeded() |
|
634 { |
|
635 #ifdef MOZ_GFX_OPTIMIZE_MOBILE |
|
636 // some mobile devices can't have more than 8 GL contexts overall |
|
637 const size_t kMaxWebGLContextsPerPrincipal = 2; |
|
638 const size_t kMaxWebGLContexts = 4; |
|
639 #else |
|
640 const size_t kMaxWebGLContextsPerPrincipal = 16; |
|
641 const size_t kMaxWebGLContexts = 32; |
|
642 #endif |
|
643 MOZ_ASSERT(kMaxWebGLContextsPerPrincipal < kMaxWebGLContexts); |
|
644 |
|
645 // it's important to update the index on a new context before losing old contexts, |
|
646 // otherwise new unused contexts would all have index 0 and we couldn't distinguish older ones |
|
647 // when choosing which one to lose first. |
|
648 UpdateLastUseIndex(); |
|
649 |
|
650 WebGLMemoryTracker::ContextsArrayType &contexts |
|
651 = WebGLMemoryTracker::Contexts(); |
|
652 |
|
653 // quick exit path, should cover a majority of cases |
|
654 if (contexts.Length() <= kMaxWebGLContextsPerPrincipal) { |
|
655 return; |
|
656 } |
|
657 |
|
658 // note that here by "context" we mean "non-lost context". See the check for |
|
659 // IsContextLost() below. Indeed, the point of this function is to maybe lose |
|
660 // some currently non-lost context. |
|
661 |
|
662 uint64_t oldestIndex = UINT64_MAX; |
|
663 uint64_t oldestIndexThisPrincipal = UINT64_MAX; |
|
664 const WebGLContext *oldestContext = nullptr; |
|
665 const WebGLContext *oldestContextThisPrincipal = nullptr; |
|
666 size_t numContexts = 0; |
|
667 size_t numContextsThisPrincipal = 0; |
|
668 |
|
669 for(size_t i = 0; i < contexts.Length(); ++i) { |
|
670 |
|
671 // don't want to lose ourselves. |
|
672 if (contexts[i] == this) |
|
673 continue; |
|
674 |
|
675 if (contexts[i]->IsContextLost()) |
|
676 continue; |
|
677 |
|
678 if (!contexts[i]->GetCanvas()) { |
|
679 // Zombie context: the canvas is already destroyed, but something else |
|
680 // (typically the compositor) is still holding on to the context. |
|
681 // Killing zombies is a no-brainer. |
|
682 const_cast<WebGLContext*>(contexts[i])->LoseContext(); |
|
683 continue; |
|
684 } |
|
685 |
|
686 numContexts++; |
|
687 if (contexts[i]->mLastUseIndex < oldestIndex) { |
|
688 oldestIndex = contexts[i]->mLastUseIndex; |
|
689 oldestContext = contexts[i]; |
|
690 } |
|
691 |
|
692 nsIPrincipal *ourPrincipal = GetCanvas()->NodePrincipal(); |
|
693 nsIPrincipal *theirPrincipal = contexts[i]->GetCanvas()->NodePrincipal(); |
|
694 bool samePrincipal; |
|
695 nsresult rv = ourPrincipal->Equals(theirPrincipal, &samePrincipal); |
|
696 if (NS_SUCCEEDED(rv) && samePrincipal) { |
|
697 numContextsThisPrincipal++; |
|
698 if (contexts[i]->mLastUseIndex < oldestIndexThisPrincipal) { |
|
699 oldestIndexThisPrincipal = contexts[i]->mLastUseIndex; |
|
700 oldestContextThisPrincipal = contexts[i]; |
|
701 } |
|
702 } |
|
703 } |
|
704 |
|
705 if (numContextsThisPrincipal > kMaxWebGLContextsPerPrincipal) { |
|
706 GenerateWarning("Exceeded %d live WebGL contexts for this principal, losing the " |
|
707 "least recently used one.", kMaxWebGLContextsPerPrincipal); |
|
708 MOZ_ASSERT(oldestContextThisPrincipal); // if we reach this point, this can't be null |
|
709 const_cast<WebGLContext*>(oldestContextThisPrincipal)->LoseContext(); |
|
710 } else if (numContexts > kMaxWebGLContexts) { |
|
711 GenerateWarning("Exceeded %d live WebGL contexts, losing the least recently used one.", |
|
712 kMaxWebGLContexts); |
|
713 MOZ_ASSERT(oldestContext); // if we reach this point, this can't be null |
|
714 const_cast<WebGLContext*>(oldestContext)->LoseContext(); |
|
715 } |
|
716 } |
|
717 |
|
718 void |
|
719 WebGLContext::GetImageBuffer(uint8_t** aImageBuffer, int32_t* aFormat) |
|
720 { |
|
721 *aImageBuffer = nullptr; |
|
722 *aFormat = 0; |
|
723 |
|
724 // Use GetSurfaceSnapshot() to make sure that appropriate y-flip gets applied |
|
725 bool premult; |
|
726 RefPtr<SourceSurface> snapshot = |
|
727 GetSurfaceSnapshot(mOptions.premultipliedAlpha ? nullptr : &premult); |
|
728 if (!snapshot) { |
|
729 return; |
|
730 } |
|
731 MOZ_ASSERT(mOptions.premultipliedAlpha || !premult, "We must get unpremult when we ask for it!"); |
|
732 |
|
733 RefPtr<DataSourceSurface> dataSurface = snapshot->GetDataSurface(); |
|
734 |
|
735 DataSourceSurface::MappedSurface map; |
|
736 if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) { |
|
737 return; |
|
738 } |
|
739 |
|
740 static const fallible_t fallible = fallible_t(); |
|
741 uint8_t* imageBuffer = new (fallible) uint8_t[mWidth * mHeight * 4]; |
|
742 if (!imageBuffer) { |
|
743 dataSurface->Unmap(); |
|
744 return; |
|
745 } |
|
746 memcpy(imageBuffer, map.mData, mWidth * mHeight * 4); |
|
747 |
|
748 dataSurface->Unmap(); |
|
749 |
|
750 int32_t format = imgIEncoder::INPUT_FORMAT_HOSTARGB; |
|
751 if (!mOptions.premultipliedAlpha) { |
|
752 // We need to convert to INPUT_FORMAT_RGBA, otherwise |
|
753 // we are automatically considered premult, and unpremult'd. |
|
754 // Yes, it is THAT silly. |
|
755 // Except for different lossy conversions by color, |
|
756 // we could probably just change the label, and not change the data. |
|
757 gfxUtils::ConvertBGRAtoRGBA(imageBuffer, mWidth * mHeight * 4); |
|
758 format = imgIEncoder::INPUT_FORMAT_RGBA; |
|
759 } |
|
760 |
|
761 *aImageBuffer = imageBuffer; |
|
762 *aFormat = format; |
|
763 } |
|
764 |
|
765 NS_IMETHODIMP |
|
766 WebGLContext::GetInputStream(const char* aMimeType, |
|
767 const char16_t* aEncoderOptions, |
|
768 nsIInputStream **aStream) |
|
769 { |
|
770 NS_ASSERTION(gl, "GetInputStream on invalid context?"); |
|
771 if (!gl) |
|
772 return NS_ERROR_FAILURE; |
|
773 |
|
774 nsCString enccid("@mozilla.org/image/encoder;2?type="); |
|
775 enccid += aMimeType; |
|
776 nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get()); |
|
777 if (!encoder) { |
|
778 return NS_ERROR_FAILURE; |
|
779 } |
|
780 |
|
781 nsAutoArrayPtr<uint8_t> imageBuffer; |
|
782 int32_t format = 0; |
|
783 GetImageBuffer(getter_Transfers(imageBuffer), &format); |
|
784 if (!imageBuffer) { |
|
785 return NS_ERROR_FAILURE; |
|
786 } |
|
787 |
|
788 return ImageEncoder::GetInputStream(mWidth, mHeight, imageBuffer, format, |
|
789 encoder, aEncoderOptions, aStream); |
|
790 } |
|
791 |
|
792 void WebGLContext::UpdateLastUseIndex() |
|
793 { |
|
794 static CheckedInt<uint64_t> sIndex = 0; |
|
795 |
|
796 sIndex++; |
|
797 |
|
798 // should never happen with 64-bit; trying to handle this would be riskier than |
|
799 // not handling it as the handler code would never get exercised. |
|
800 if (!sIndex.isValid()) { |
|
801 NS_RUNTIMEABORT("Can't believe it's been 2^64 transactions already!"); |
|
802 } |
|
803 |
|
804 mLastUseIndex = sIndex.value(); |
|
805 } |
|
806 |
|
807 static uint8_t gWebGLLayerUserData; |
|
808 |
|
809 namespace mozilla { |
|
810 |
|
811 class WebGLContextUserData : public LayerUserData { |
|
812 public: |
|
813 WebGLContextUserData(HTMLCanvasElement *aContent) |
|
814 : mContent(aContent) |
|
815 {} |
|
816 |
|
817 /* PreTransactionCallback gets called by the Layers code every time the |
|
818 * WebGL canvas is going to be composited. |
|
819 */ |
|
820 static void PreTransactionCallback(void* data) |
|
821 { |
|
822 WebGLContextUserData* userdata = static_cast<WebGLContextUserData*>(data); |
|
823 HTMLCanvasElement* canvas = userdata->mContent; |
|
824 WebGLContext* context = static_cast<WebGLContext*>(canvas->GetContextAtIndex(0)); |
|
825 |
|
826 // Present our screenbuffer, if needed. |
|
827 context->PresentScreenBuffer(); |
|
828 context->mDrawCallsSinceLastFlush = 0; |
|
829 } |
|
830 |
|
831 /** DidTransactionCallback gets called by the Layers code everytime the WebGL canvas gets composite, |
|
832 * so it really is the right place to put actions that have to be performed upon compositing |
|
833 */ |
|
834 static void DidTransactionCallback(void* aData) |
|
835 { |
|
836 WebGLContextUserData *userdata = static_cast<WebGLContextUserData*>(aData); |
|
837 HTMLCanvasElement *canvas = userdata->mContent; |
|
838 WebGLContext *context = static_cast<WebGLContext*>(canvas->GetContextAtIndex(0)); |
|
839 |
|
840 // Mark ourselves as no longer invalidated. |
|
841 context->MarkContextClean(); |
|
842 |
|
843 context->UpdateLastUseIndex(); |
|
844 } |
|
845 |
|
846 private: |
|
847 nsRefPtr<HTMLCanvasElement> mContent; |
|
848 }; |
|
849 |
|
850 } // end namespace mozilla |
|
851 |
|
852 already_AddRefed<layers::CanvasLayer> |
|
853 WebGLContext::GetCanvasLayer(nsDisplayListBuilder* aBuilder, |
|
854 CanvasLayer *aOldLayer, |
|
855 LayerManager *aManager) |
|
856 { |
|
857 if (IsContextLost()) |
|
858 return nullptr; |
|
859 |
|
860 if (!mResetLayer && aOldLayer && |
|
861 aOldLayer->HasUserData(&gWebGLLayerUserData)) { |
|
862 nsRefPtr<layers::CanvasLayer> ret = aOldLayer; |
|
863 return ret.forget(); |
|
864 } |
|
865 |
|
866 nsRefPtr<CanvasLayer> canvasLayer = aManager->CreateCanvasLayer(); |
|
867 if (!canvasLayer) { |
|
868 NS_WARNING("CreateCanvasLayer returned null!"); |
|
869 return nullptr; |
|
870 } |
|
871 WebGLContextUserData *userData = nullptr; |
|
872 if (aBuilder->IsPaintingToWindow()) { |
|
873 // Make the layer tell us whenever a transaction finishes (including |
|
874 // the current transaction), so we can clear our invalidation state and |
|
875 // start invalidating again. We need to do this for the layer that is |
|
876 // being painted to a window (there shouldn't be more than one at a time, |
|
877 // and if there is, flushing the invalidation state more often than |
|
878 // necessary is harmless). |
|
879 |
|
880 // The layer will be destroyed when we tear down the presentation |
|
881 // (at the latest), at which time this userData will be destroyed, |
|
882 // releasing the reference to the element. |
|
883 // The userData will receive DidTransactionCallbacks, which flush the |
|
884 // the invalidation state to indicate that the canvas is up to date. |
|
885 userData = new WebGLContextUserData(mCanvasElement); |
|
886 canvasLayer->SetDidTransactionCallback( |
|
887 WebGLContextUserData::DidTransactionCallback, userData); |
|
888 canvasLayer->SetPreTransactionCallback( |
|
889 WebGLContextUserData::PreTransactionCallback, userData); |
|
890 } |
|
891 canvasLayer->SetUserData(&gWebGLLayerUserData, userData); |
|
892 |
|
893 CanvasLayer::Data data; |
|
894 data.mGLContext = gl; |
|
895 data.mSize = nsIntSize(mWidth, mHeight); |
|
896 data.mIsGLAlphaPremult = IsPremultAlpha(); |
|
897 |
|
898 canvasLayer->Initialize(data); |
|
899 uint32_t flags = gl->Caps().alpha ? 0 : Layer::CONTENT_OPAQUE; |
|
900 canvasLayer->SetContentFlags(flags); |
|
901 canvasLayer->Updated(); |
|
902 |
|
903 mResetLayer = false; |
|
904 |
|
905 return canvasLayer.forget(); |
|
906 } |
|
907 |
|
908 void |
|
909 WebGLContext::GetContextAttributes(Nullable<dom::WebGLContextAttributes> &retval) |
|
910 { |
|
911 retval.SetNull(); |
|
912 if (IsContextLost()) |
|
913 return; |
|
914 |
|
915 dom::WebGLContextAttributes& result = retval.SetValue(); |
|
916 |
|
917 const PixelBufferFormat& format = gl->GetPixelFormat(); |
|
918 |
|
919 result.mAlpha.Construct(format.alpha > 0); |
|
920 result.mDepth = format.depth > 0; |
|
921 result.mStencil = format.stencil > 0; |
|
922 result.mAntialias = format.samples > 1; |
|
923 result.mPremultipliedAlpha = mOptions.premultipliedAlpha; |
|
924 result.mPreserveDrawingBuffer = mOptions.preserveDrawingBuffer; |
|
925 } |
|
926 |
|
927 /* [noscript] DOMString mozGetUnderlyingParamString(in GLenum pname); */ |
|
928 NS_IMETHODIMP |
|
929 WebGLContext::MozGetUnderlyingParamString(uint32_t pname, nsAString& retval) |
|
930 { |
|
931 if (IsContextLost()) |
|
932 return NS_OK; |
|
933 |
|
934 retval.SetIsVoid(true); |
|
935 |
|
936 MakeContextCurrent(); |
|
937 |
|
938 switch (pname) { |
|
939 case LOCAL_GL_VENDOR: |
|
940 case LOCAL_GL_RENDERER: |
|
941 case LOCAL_GL_VERSION: |
|
942 case LOCAL_GL_SHADING_LANGUAGE_VERSION: |
|
943 case LOCAL_GL_EXTENSIONS: { |
|
944 const char *s = (const char *) gl->fGetString(pname); |
|
945 retval.Assign(NS_ConvertASCIItoUTF16(nsDependentCString(s))); |
|
946 } |
|
947 break; |
|
948 |
|
949 default: |
|
950 return NS_ERROR_INVALID_ARG; |
|
951 } |
|
952 |
|
953 return NS_OK; |
|
954 } |
|
955 |
|
956 void |
|
957 WebGLContext::ClearScreen() |
|
958 { |
|
959 bool colorAttachmentsMask[WebGLContext::sMaxColorAttachments] = {false}; |
|
960 |
|
961 MakeContextCurrent(); |
|
962 ScopedBindFramebuffer autoFB(gl, 0); |
|
963 |
|
964 GLbitfield clearMask = LOCAL_GL_COLOR_BUFFER_BIT; |
|
965 if (mOptions.depth) |
|
966 clearMask |= LOCAL_GL_DEPTH_BUFFER_BIT; |
|
967 if (mOptions.stencil) |
|
968 clearMask |= LOCAL_GL_STENCIL_BUFFER_BIT; |
|
969 |
|
970 colorAttachmentsMask[0] = true; |
|
971 |
|
972 ForceClearFramebufferWithDefaultValues(clearMask, colorAttachmentsMask); |
|
973 } |
|
974 |
|
975 #ifdef DEBUG |
|
976 // For NaNs, etc. |
|
977 static bool IsShadowCorrect(float shadow, float actual) { |
|
978 if (IsNaN(shadow)) { |
|
979 // GL is allowed to do anything it wants for NaNs, so if we're shadowing |
|
980 // a NaN, then whatever `actual` is might be correct. |
|
981 return true; |
|
982 } |
|
983 |
|
984 return shadow == actual; |
|
985 } |
|
986 #endif |
|
987 |
|
988 void |
|
989 WebGLContext::ForceClearFramebufferWithDefaultValues(GLbitfield mask, const bool colorAttachmentsMask[sMaxColorAttachments]) |
|
990 { |
|
991 MakeContextCurrent(); |
|
992 |
|
993 bool initializeColorBuffer = 0 != (mask & LOCAL_GL_COLOR_BUFFER_BIT); |
|
994 bool initializeDepthBuffer = 0 != (mask & LOCAL_GL_DEPTH_BUFFER_BIT); |
|
995 bool initializeStencilBuffer = 0 != (mask & LOCAL_GL_STENCIL_BUFFER_BIT); |
|
996 bool drawBuffersIsEnabled = IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers); |
|
997 |
|
998 GLenum currentDrawBuffers[WebGLContext::sMaxColorAttachments]; |
|
999 |
|
1000 // Fun GL fact: No need to worry about the viewport here, glViewport is just |
|
1001 // setting up a coordinates transformation, it doesn't affect glClear at all. |
|
1002 |
|
1003 #ifdef DEBUG |
|
1004 // Scope to hide our variables. |
|
1005 { |
|
1006 // Sanity-check that all our state is set properly. Otherwise, when we |
|
1007 // reset out state to what we *think* it is, we'll get it wrong. |
|
1008 |
|
1009 // Dither shouldn't matter when we're clearing to {0,0,0,0}. |
|
1010 MOZ_ASSERT(gl->fIsEnabled(LOCAL_GL_SCISSOR_TEST) == mScissorTestEnabled); |
|
1011 |
|
1012 if (initializeColorBuffer) { |
|
1013 realGLboolean colorWriteMask[4] = {2, 2, 2, 2}; |
|
1014 GLfloat colorClearValue[4] = {-1.0f, -1.0f, -1.0f, -1.0f}; |
|
1015 |
|
1016 gl->fGetBooleanv(LOCAL_GL_COLOR_WRITEMASK, colorWriteMask); |
|
1017 gl->fGetFloatv(LOCAL_GL_COLOR_CLEAR_VALUE, colorClearValue); |
|
1018 |
|
1019 MOZ_ASSERT(colorWriteMask[0] == mColorWriteMask[0] && |
|
1020 colorWriteMask[1] == mColorWriteMask[1] && |
|
1021 colorWriteMask[2] == mColorWriteMask[2] && |
|
1022 colorWriteMask[3] == mColorWriteMask[3]); |
|
1023 MOZ_ASSERT(IsShadowCorrect(mColorClearValue[0], colorClearValue[0]) && |
|
1024 IsShadowCorrect(mColorClearValue[1], colorClearValue[1]) && |
|
1025 IsShadowCorrect(mColorClearValue[2], colorClearValue[2]) && |
|
1026 IsShadowCorrect(mColorClearValue[3], colorClearValue[3])); |
|
1027 } |
|
1028 |
|
1029 if (initializeDepthBuffer) { |
|
1030 realGLboolean depthWriteMask = 2; |
|
1031 GLfloat depthClearValue = -1.0f; |
|
1032 |
|
1033 |
|
1034 gl->fGetBooleanv(LOCAL_GL_DEPTH_WRITEMASK, &depthWriteMask); |
|
1035 gl->fGetFloatv(LOCAL_GL_DEPTH_CLEAR_VALUE, &depthClearValue); |
|
1036 |
|
1037 MOZ_ASSERT(depthWriteMask == mDepthWriteMask); |
|
1038 MOZ_ASSERT(IsShadowCorrect(mDepthClearValue, depthClearValue)); |
|
1039 } |
|
1040 |
|
1041 if (initializeStencilBuffer) { |
|
1042 GLuint stencilWriteMaskFront = 0xdeadbad1; |
|
1043 GLuint stencilWriteMaskBack = 0xdeadbad1; |
|
1044 GLuint stencilClearValue = 0xdeadbad1; |
|
1045 |
|
1046 gl->GetUIntegerv(LOCAL_GL_STENCIL_WRITEMASK, &stencilWriteMaskFront); |
|
1047 gl->GetUIntegerv(LOCAL_GL_STENCIL_BACK_WRITEMASK, &stencilWriteMaskBack); |
|
1048 gl->GetUIntegerv(LOCAL_GL_STENCIL_CLEAR_VALUE, &stencilClearValue); |
|
1049 |
|
1050 GLuint stencilBits = 0; |
|
1051 gl->GetUIntegerv(LOCAL_GL_STENCIL_BITS, &stencilBits); |
|
1052 GLuint stencilMask = (GLuint(1) << stencilBits) - 1; |
|
1053 |
|
1054 MOZ_ASSERT( ( stencilWriteMaskFront & stencilMask) == |
|
1055 (mStencilWriteMaskFront & stencilMask) ); |
|
1056 MOZ_ASSERT( ( stencilWriteMaskBack & stencilMask) == |
|
1057 (mStencilWriteMaskBack & stencilMask) ); |
|
1058 MOZ_ASSERT( ( stencilClearValue & stencilMask) == |
|
1059 (mStencilClearValue & stencilMask) ); |
|
1060 } |
|
1061 } |
|
1062 #endif |
|
1063 |
|
1064 // Prepare GL state for clearing. |
|
1065 gl->fDisable(LOCAL_GL_SCISSOR_TEST); |
|
1066 |
|
1067 if (initializeColorBuffer) { |
|
1068 |
|
1069 if (drawBuffersIsEnabled) { |
|
1070 |
|
1071 GLenum drawBuffersCommand[WebGLContext::sMaxColorAttachments] = { LOCAL_GL_NONE }; |
|
1072 |
|
1073 for(int32_t i = 0; i < mGLMaxDrawBuffers; i++) { |
|
1074 GLint temp; |
|
1075 gl->fGetIntegerv(LOCAL_GL_DRAW_BUFFER0 + i, &temp); |
|
1076 currentDrawBuffers[i] = temp; |
|
1077 |
|
1078 if (colorAttachmentsMask[i]) { |
|
1079 drawBuffersCommand[i] = LOCAL_GL_COLOR_ATTACHMENT0 + i; |
|
1080 } |
|
1081 } |
|
1082 |
|
1083 gl->fDrawBuffers(mGLMaxDrawBuffers, drawBuffersCommand); |
|
1084 } |
|
1085 |
|
1086 gl->fColorMask(1, 1, 1, 1); |
|
1087 gl->fClearColor(0.0f, 0.0f, 0.0f, 0.0f); |
|
1088 } |
|
1089 |
|
1090 if (initializeDepthBuffer) { |
|
1091 gl->fDepthMask(1); |
|
1092 gl->fClearDepth(1.0f); |
|
1093 } |
|
1094 |
|
1095 if (initializeStencilBuffer) { |
|
1096 // "The clear operation always uses the front stencil write mask |
|
1097 // when clearing the stencil buffer." |
|
1098 gl->fStencilMaskSeparate(LOCAL_GL_FRONT, 0xffffffff); |
|
1099 gl->fStencilMaskSeparate(LOCAL_GL_BACK, 0xffffffff); |
|
1100 gl->fClearStencil(0); |
|
1101 } |
|
1102 |
|
1103 if (mRasterizerDiscardEnabled) { |
|
1104 gl->fDisable(LOCAL_GL_RASTERIZER_DISCARD); |
|
1105 } |
|
1106 |
|
1107 // Do the clear! |
|
1108 gl->fClear(mask); |
|
1109 |
|
1110 // And reset! |
|
1111 if (mScissorTestEnabled) |
|
1112 gl->fEnable(LOCAL_GL_SCISSOR_TEST); |
|
1113 |
|
1114 if (mRasterizerDiscardEnabled) { |
|
1115 gl->fEnable(LOCAL_GL_RASTERIZER_DISCARD); |
|
1116 } |
|
1117 |
|
1118 // Restore GL state after clearing. |
|
1119 if (initializeColorBuffer) { |
|
1120 if (drawBuffersIsEnabled) { |
|
1121 gl->fDrawBuffers(mGLMaxDrawBuffers, currentDrawBuffers); |
|
1122 } |
|
1123 |
|
1124 gl->fColorMask(mColorWriteMask[0], |
|
1125 mColorWriteMask[1], |
|
1126 mColorWriteMask[2], |
|
1127 mColorWriteMask[3]); |
|
1128 gl->fClearColor(mColorClearValue[0], |
|
1129 mColorClearValue[1], |
|
1130 mColorClearValue[2], |
|
1131 mColorClearValue[3]); |
|
1132 } |
|
1133 |
|
1134 if (initializeDepthBuffer) { |
|
1135 gl->fDepthMask(mDepthWriteMask); |
|
1136 gl->fClearDepth(mDepthClearValue); |
|
1137 } |
|
1138 |
|
1139 if (initializeStencilBuffer) { |
|
1140 gl->fStencilMaskSeparate(LOCAL_GL_FRONT, mStencilWriteMaskFront); |
|
1141 gl->fStencilMaskSeparate(LOCAL_GL_BACK, mStencilWriteMaskBack); |
|
1142 gl->fClearStencil(mStencilClearValue); |
|
1143 } |
|
1144 } |
|
1145 |
|
1146 // For an overview of how WebGL compositing works, see: |
|
1147 // https://wiki.mozilla.org/Platform/GFX/WebGL/Compositing |
|
1148 bool |
|
1149 WebGLContext::PresentScreenBuffer() |
|
1150 { |
|
1151 if (IsContextLost()) { |
|
1152 return false; |
|
1153 } |
|
1154 |
|
1155 if (!mShouldPresent) { |
|
1156 return false; |
|
1157 } |
|
1158 |
|
1159 gl->MakeCurrent(); |
|
1160 MOZ_ASSERT(!mBackbufferNeedsClear); |
|
1161 if (!gl->PublishFrame()) { |
|
1162 this->ForceLoseContext(); |
|
1163 return false; |
|
1164 } |
|
1165 |
|
1166 if (!mOptions.preserveDrawingBuffer) { |
|
1167 mBackbufferNeedsClear = true; |
|
1168 } |
|
1169 |
|
1170 mShouldPresent = false; |
|
1171 |
|
1172 return true; |
|
1173 } |
|
1174 |
|
1175 void |
|
1176 WebGLContext::DummyFramebufferOperation(const char *info) |
|
1177 { |
|
1178 GLenum status = CheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); |
|
1179 if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) |
|
1180 ErrorInvalidFramebufferOperation("%s: incomplete framebuffer", info); |
|
1181 } |
|
1182 |
|
1183 // We use this timer for many things. Here are the things that it is activated for: |
|
1184 // 1) If a script is using the MOZ_WEBGL_lose_context extension. |
|
1185 // 2) If we are using EGL and _NOT ANGLE_, we query periodically to see if the |
|
1186 // CONTEXT_LOST_WEBGL error has been triggered. |
|
1187 // 3) If we are using ANGLE, or anything that supports ARB_robustness, query the |
|
1188 // GPU periodically to see if the reset status bit has been set. |
|
1189 // In all of these situations, we use this timer to send the script context lost |
|
1190 // and restored events asynchronously. For example, if it triggers a context loss, |
|
1191 // the webglcontextlost event will be sent to it the next time the robustness timer |
|
1192 // fires. |
|
1193 // Note that this timer mechanism is not used unless one of these 3 criteria |
|
1194 // are met. |
|
1195 // At a bare minimum, from context lost to context restores, it would take 3 |
|
1196 // full timer iterations: detection, webglcontextlost, webglcontextrestored. |
|
1197 void |
|
1198 WebGLContext::RobustnessTimerCallback(nsITimer* timer) |
|
1199 { |
|
1200 TerminateContextLossTimer(); |
|
1201 |
|
1202 if (!mCanvasElement) { |
|
1203 // the canvas is gone. That happens when the page was closed before we got |
|
1204 // this timer event. In this case, there's nothing to do here, just don't crash. |
|
1205 return; |
|
1206 } |
|
1207 |
|
1208 // If the context has been lost and we're waiting for it to be restored, do |
|
1209 // that now. |
|
1210 if (mContextStatus == ContextLostAwaitingEvent) { |
|
1211 bool defaultAction; |
|
1212 nsContentUtils::DispatchTrustedEvent(mCanvasElement->OwnerDoc(), |
|
1213 static_cast<nsIDOMHTMLCanvasElement*>(mCanvasElement), |
|
1214 NS_LITERAL_STRING("webglcontextlost"), |
|
1215 true, |
|
1216 true, |
|
1217 &defaultAction); |
|
1218 |
|
1219 // If the script didn't handle the event, we don't allow restores. |
|
1220 if (defaultAction) |
|
1221 mAllowRestore = false; |
|
1222 |
|
1223 // If the script handled the event and we are allowing restores, then |
|
1224 // mark it to be restored. Otherwise, leave it as context lost |
|
1225 // (unusable). |
|
1226 if (!defaultAction && mAllowRestore) { |
|
1227 ForceRestoreContext(); |
|
1228 // Restart the timer so that it will be restored on the next |
|
1229 // callback. |
|
1230 SetupContextLossTimer(); |
|
1231 } else { |
|
1232 mContextStatus = ContextLost; |
|
1233 } |
|
1234 } else if (mContextStatus == ContextLostAwaitingRestore) { |
|
1235 // Try to restore the context. If it fails, try again later. |
|
1236 if (NS_FAILED(SetDimensions(mWidth, mHeight))) { |
|
1237 SetupContextLossTimer(); |
|
1238 return; |
|
1239 } |
|
1240 mContextStatus = ContextNotLost; |
|
1241 nsContentUtils::DispatchTrustedEvent(mCanvasElement->OwnerDoc(), |
|
1242 static_cast<nsIDOMHTMLCanvasElement*>(mCanvasElement), |
|
1243 NS_LITERAL_STRING("webglcontextrestored"), |
|
1244 true, |
|
1245 true); |
|
1246 // Set all flags back to the state they were in before the context was |
|
1247 // lost. |
|
1248 mEmitContextLostErrorOnce = true; |
|
1249 mAllowRestore = true; |
|
1250 } |
|
1251 |
|
1252 MaybeRestoreContext(); |
|
1253 return; |
|
1254 } |
|
1255 |
|
1256 void |
|
1257 WebGLContext::MaybeRestoreContext() |
|
1258 { |
|
1259 // Don't try to handle it if we already know it's busted. |
|
1260 if (mContextStatus != ContextNotLost || gl == nullptr) |
|
1261 return; |
|
1262 |
|
1263 bool isEGL = gl->GetContextType() == gl::GLContextType::EGL, |
|
1264 isANGLE = gl->IsANGLE(); |
|
1265 |
|
1266 GLContext::ContextResetARB resetStatus = GLContext::CONTEXT_NO_ERROR; |
|
1267 if (mHasRobustness) { |
|
1268 gl->MakeCurrent(); |
|
1269 resetStatus = (GLContext::ContextResetARB) gl->fGetGraphicsResetStatus(); |
|
1270 } else if (isEGL) { |
|
1271 // Simulate a ARB_robustness guilty context loss for when we |
|
1272 // get an EGL_CONTEXT_LOST error. It may not actually be guilty, |
|
1273 // but we can't make any distinction, so we must assume the worst |
|
1274 // case. |
|
1275 if (!gl->MakeCurrent(true) && gl->IsContextLost()) { |
|
1276 resetStatus = GLContext::CONTEXT_GUILTY_CONTEXT_RESET_ARB; |
|
1277 } |
|
1278 } |
|
1279 |
|
1280 if (resetStatus != GLContext::CONTEXT_NO_ERROR) { |
|
1281 // It's already lost, but clean up after it and signal to JS that it is |
|
1282 // lost. |
|
1283 ForceLoseContext(); |
|
1284 } |
|
1285 |
|
1286 switch (resetStatus) { |
|
1287 case GLContext::CONTEXT_NO_ERROR: |
|
1288 // If there has been activity since the timer was set, it's possible |
|
1289 // that we did or are going to miss something, so clear this flag and |
|
1290 // run it again some time later. |
|
1291 if (mDrawSinceContextLossTimerSet) |
|
1292 SetupContextLossTimer(); |
|
1293 break; |
|
1294 case GLContext::CONTEXT_GUILTY_CONTEXT_RESET_ARB: |
|
1295 NS_WARNING("WebGL content on the page caused the graphics card to reset; not restoring the context"); |
|
1296 mAllowRestore = false; |
|
1297 break; |
|
1298 case GLContext::CONTEXT_INNOCENT_CONTEXT_RESET_ARB: |
|
1299 break; |
|
1300 case GLContext::CONTEXT_UNKNOWN_CONTEXT_RESET_ARB: |
|
1301 NS_WARNING("WebGL content on the page might have caused the graphics card to reset"); |
|
1302 if (isEGL && isANGLE) { |
|
1303 // If we're using ANGLE, we ONLY get back UNKNOWN context resets, including for guilty contexts. |
|
1304 // This means that we can't restore it or risk restoring a guilty context. Should this ever change, |
|
1305 // we can get rid of the whole IsANGLE() junk from GLContext.h since, as of writing, this is the |
|
1306 // only use for it. See ANGLE issue 261. |
|
1307 mAllowRestore = false; |
|
1308 } |
|
1309 break; |
|
1310 } |
|
1311 } |
|
1312 |
|
1313 void |
|
1314 WebGLContext::ForceLoseContext() |
|
1315 { |
|
1316 if (mContextStatus == ContextLostAwaitingEvent) |
|
1317 return; |
|
1318 |
|
1319 mContextStatus = ContextLostAwaitingEvent; |
|
1320 // Queue up a task to restore the event. |
|
1321 SetupContextLossTimer(); |
|
1322 DestroyResourcesAndContext(); |
|
1323 } |
|
1324 |
|
1325 void |
|
1326 WebGLContext::ForceRestoreContext() |
|
1327 { |
|
1328 mContextStatus = ContextLostAwaitingRestore; |
|
1329 } |
|
1330 |
|
1331 void |
|
1332 WebGLContext::MakeContextCurrent() const { gl->MakeCurrent(); } |
|
1333 |
|
1334 mozilla::TemporaryRef<mozilla::gfx::SourceSurface> |
|
1335 WebGLContext::GetSurfaceSnapshot(bool* aPremultAlpha) |
|
1336 { |
|
1337 if (!gl) |
|
1338 return nullptr; |
|
1339 |
|
1340 nsRefPtr<gfxImageSurface> surf = new gfxImageSurface(gfxIntSize(mWidth, mHeight), |
|
1341 gfxImageFormat::ARGB32, |
|
1342 mWidth * 4, 0, false); |
|
1343 if (surf->CairoStatus() != 0) { |
|
1344 return nullptr; |
|
1345 } |
|
1346 |
|
1347 gl->MakeCurrent(); |
|
1348 { |
|
1349 ScopedBindFramebuffer autoFB(gl, 0); |
|
1350 ClearBackbufferIfNeeded(); |
|
1351 ReadPixelsIntoImageSurface(gl, surf); |
|
1352 } |
|
1353 |
|
1354 if (aPremultAlpha) { |
|
1355 *aPremultAlpha = true; |
|
1356 } |
|
1357 bool srcPremultAlpha = mOptions.premultipliedAlpha; |
|
1358 if (!srcPremultAlpha) { |
|
1359 if (aPremultAlpha) { |
|
1360 *aPremultAlpha = false; |
|
1361 } else { |
|
1362 gfxUtils::PremultiplyImageSurface(surf); |
|
1363 surf->MarkDirty(); |
|
1364 } |
|
1365 } |
|
1366 |
|
1367 RefPtr<DrawTarget> dt = |
|
1368 Factory::CreateDrawTarget(BackendType::CAIRO, |
|
1369 IntSize(mWidth, mHeight), |
|
1370 SurfaceFormat::B8G8R8A8); |
|
1371 |
|
1372 if (!dt) { |
|
1373 return nullptr; |
|
1374 } |
|
1375 |
|
1376 RefPtr<SourceSurface> source = gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(dt, surf); |
|
1377 |
|
1378 Matrix m; |
|
1379 m.Translate(0.0, mHeight); |
|
1380 m.Scale(1.0, -1.0); |
|
1381 dt->SetTransform(m); |
|
1382 |
|
1383 dt->DrawSurface(source, |
|
1384 Rect(0, 0, mWidth, mHeight), |
|
1385 Rect(0, 0, mWidth, mHeight), |
|
1386 DrawSurfaceOptions(), |
|
1387 DrawOptions(1.0f, CompositionOp::OP_SOURCE)); |
|
1388 |
|
1389 return dt->Snapshot(); |
|
1390 } |
|
1391 |
|
1392 // |
|
1393 // XPCOM goop |
|
1394 // |
|
1395 |
|
1396 NS_IMPL_CYCLE_COLLECTING_ADDREF(WebGLContext) |
|
1397 NS_IMPL_CYCLE_COLLECTING_RELEASE(WebGLContext) |
|
1398 |
|
1399 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_13(WebGLContext, |
|
1400 mCanvasElement, |
|
1401 mExtensions, |
|
1402 mBound2DTextures, |
|
1403 mBoundCubeMapTextures, |
|
1404 mBoundArrayBuffer, |
|
1405 mBoundTransformFeedbackBuffer, |
|
1406 mCurrentProgram, |
|
1407 mBoundFramebuffer, |
|
1408 mBoundRenderbuffer, |
|
1409 mBoundVertexArray, |
|
1410 mDefaultVertexArray, |
|
1411 mActiveOcclusionQuery, |
|
1412 mActiveTransformFeedbackQuery) |
|
1413 |
|
1414 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebGLContext) |
|
1415 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY |
|
1416 NS_INTERFACE_MAP_ENTRY(nsIDOMWebGLRenderingContext) |
|
1417 NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal) |
|
1418 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) |
|
1419 // If the exact way we cast to nsISupports here ever changes, fix our |
|
1420 // ToSupports() method. |
|
1421 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMWebGLRenderingContext) |
|
1422 NS_INTERFACE_MAP_END |