Bumping manifests a=b2g-bump
[gecko.git] / dom / canvas / WebGLContext.cpp
blob543cd4e47f30d6f19ad6833f65ceee4d1c1cf446
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"
8 #include "WebGLContextLossHandler.h"
9 #include "WebGL1Context.h"
10 #include "WebGLObjectModel.h"
11 #include "WebGLExtensions.h"
12 #include "WebGLContextUtils.h"
13 #include "WebGLBuffer.h"
14 #include "WebGLVertexAttribData.h"
15 #include "WebGLMemoryTracker.h"
16 #include "WebGLFramebuffer.h"
17 #include "WebGLVertexArray.h"
18 #include "WebGLQuery.h"
20 #include "GLBlitHelper.h"
21 #include "AccessCheck.h"
22 #include "nsIConsoleService.h"
23 #include "nsServiceManagerUtils.h"
24 #include "nsIClassInfoImpl.h"
25 #include "nsContentUtils.h"
26 #include "nsIXPConnect.h"
27 #include "nsError.h"
28 #include "nsIGfxInfo.h"
29 #include "nsIWidget.h"
31 #include "nsIVariant.h"
33 #include "ImageEncoder.h"
34 #include "ImageContainer.h"
36 #include "gfxContext.h"
37 #include "gfxPattern.h"
38 #include "gfxPrefs.h"
39 #include "gfxUtils.h"
41 #include "CanvasUtils.h"
42 #include "nsDisplayList.h"
44 #include "GLContextProvider.h"
45 #include "GLContext.h"
46 #include "ScopedGLHelpers.h"
47 #include "GLReadTexImageHelper.h"
49 #include "gfxCrashReporterUtils.h"
51 #include "nsSVGEffects.h"
53 #include "prenv.h"
55 #include "mozilla/Preferences.h"
56 #include "mozilla/Services.h"
57 #include "mozilla/Telemetry.h"
59 #include "nsIObserverService.h"
60 #include "nsIDOMEvent.h"
61 #include "mozilla/Services.h"
62 #include "mozilla/dom/WebGLRenderingContextBinding.h"
63 #include "mozilla/dom/BindingUtils.h"
64 #include "mozilla/dom/HTMLVideoElement.h"
65 #include "mozilla/dom/ImageData.h"
66 #include "mozilla/ProcessPriorityManager.h"
67 #include "mozilla/EnumeratedArrayCycleCollection.h"
69 #include "Layers.h"
71 #ifdef MOZ_WIDGET_GONK
72 #include "mozilla/layers/ShadowLayers.h"
73 #endif
75 #include <queue>
77 using namespace mozilla;
78 using namespace mozilla::dom;
79 using namespace mozilla::gfx;
80 using namespace mozilla::gl;
81 using namespace mozilla::layers;
83 WebGLObserver::WebGLObserver(WebGLContext* aContext)
84 : mContext(aContext)
88 WebGLObserver::~WebGLObserver()
92 void
93 WebGLObserver::Destroy()
95 UnregisterMemoryPressureEvent();
96 UnregisterVisibilityChangeEvent();
97 mContext = nullptr;
100 void
101 WebGLObserver::RegisterVisibilityChangeEvent()
103 if (!mContext) {
104 return;
107 HTMLCanvasElement* canvasElement = mContext->GetCanvas();
109 MOZ_ASSERT(canvasElement);
111 if (canvasElement) {
112 nsIDocument* document = canvasElement->OwnerDoc();
114 document->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"),
115 this,
116 true,
117 false);
121 void
122 WebGLObserver::UnregisterVisibilityChangeEvent()
124 if (!mContext) {
125 return;
128 HTMLCanvasElement* canvasElement = mContext->GetCanvas();
130 if (canvasElement) {
131 nsIDocument* document = canvasElement->OwnerDoc();
133 document->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"),
134 this,
135 true);
139 void
140 WebGLObserver::RegisterMemoryPressureEvent()
142 if (!mContext) {
143 return;
146 nsCOMPtr<nsIObserverService> observerService =
147 mozilla::services::GetObserverService();
149 MOZ_ASSERT(observerService);
151 if (observerService) {
152 observerService->AddObserver(this, "memory-pressure", false);
156 void
157 WebGLObserver::UnregisterMemoryPressureEvent()
159 if (!mContext) {
160 return;
163 nsCOMPtr<nsIObserverService> observerService =
164 mozilla::services::GetObserverService();
166 // Do not assert on observerService here. This might be triggered by
167 // the cycle collector at a late enough time, that XPCOM services are
168 // no longer available. See bug 1029504.
169 if (observerService) {
170 observerService->RemoveObserver(this, "memory-pressure");
174 NS_IMETHODIMP
175 WebGLObserver::Observe(nsISupports* aSubject,
176 const char* aTopic,
177 const char16_t* aSomeData)
179 if (!mContext || strcmp(aTopic, "memory-pressure")) {
180 return NS_OK;
183 bool wantToLoseContext = mContext->mLoseContextOnMemoryPressure;
185 if (!mContext->mCanLoseContextInForeground &&
186 ProcessPriorityManager::CurrentProcessIsForeground())
188 wantToLoseContext = false;
191 if (wantToLoseContext) {
192 mContext->ForceLoseContext();
195 return NS_OK;
198 NS_IMETHODIMP
199 WebGLObserver::HandleEvent(nsIDOMEvent* aEvent)
201 nsAutoString type;
202 aEvent->GetType(type);
203 if (!mContext || !type.EqualsLiteral("visibilitychange")) {
204 return NS_OK;
207 HTMLCanvasElement* canvasElement = mContext->GetCanvas();
209 MOZ_ASSERT(canvasElement);
211 if (canvasElement && !canvasElement->OwnerDoc()->Hidden()) {
212 mContext->ForceRestoreContext();
215 return NS_OK;
218 WebGLContextOptions::WebGLContextOptions()
219 : alpha(true), depth(true), stencil(false),
220 premultipliedAlpha(true), antialias(true),
221 preserveDrawingBuffer(false)
223 // Set default alpha state based on preference.
224 if (Preferences::GetBool("webgl.default-no-alpha", false))
225 alpha = false;
228 WebGLContext::WebGLContext()
229 : gl(nullptr)
231 SetIsDOMBinding();
233 mGeneration = 0;
234 mInvalidated = false;
235 mShouldPresent = true;
236 mResetLayer = true;
237 mOptionsFrozen = false;
239 mActiveTexture = 0;
240 mPixelStoreFlipY = false;
241 mPixelStorePremultiplyAlpha = false;
242 mPixelStoreColorspaceConversion = BROWSER_DEFAULT_WEBGL;
244 mShaderValidation = true;
246 mFakeBlackStatus = WebGLContextFakeBlackStatus::NotNeeded;
248 mVertexAttrib0Vector[0] = 0;
249 mVertexAttrib0Vector[1] = 0;
250 mVertexAttrib0Vector[2] = 0;
251 mVertexAttrib0Vector[3] = 1;
252 mFakeVertexAttrib0BufferObjectVector[0] = 0;
253 mFakeVertexAttrib0BufferObjectVector[1] = 0;
254 mFakeVertexAttrib0BufferObjectVector[2] = 0;
255 mFakeVertexAttrib0BufferObjectVector[3] = 1;
256 mFakeVertexAttrib0BufferObjectSize = 0;
257 mFakeVertexAttrib0BufferObject = 0;
258 mFakeVertexAttrib0BufferStatus = WebGLVertexAttrib0Status::Default;
260 mViewportX = 0;
261 mViewportY = 0;
262 mViewportWidth = 0;
263 mViewportHeight = 0;
265 mScissorTestEnabled = 0;
266 mDitherEnabled = 1;
267 mRasterizerDiscardEnabled = 0; // OpenGL ES 3.0 spec p244
269 // initialize some GL values: we're going to get them from the GL and use them as the sizes of arrays,
270 // so in case glGetIntegerv leaves them uninitialized because of a GL bug, we would have very weird crashes.
271 mGLMaxVertexAttribs = 0;
272 mGLMaxTextureUnits = 0;
273 mGLMaxTextureSize = 0;
274 mGLMaxCubeMapTextureSize = 0;
275 mGLMaxRenderbufferSize = 0;
276 mGLMaxTextureImageUnits = 0;
277 mGLMaxVertexTextureImageUnits = 0;
278 mGLMaxVaryingVectors = 0;
279 mGLMaxFragmentUniformVectors = 0;
280 mGLMaxVertexUniformVectors = 0;
281 mGLMaxColorAttachments = 1;
282 mGLMaxDrawBuffers = 1;
283 mGLMaxTransformFeedbackSeparateAttribs = 0;
285 // See OpenGL ES 2.0.25 spec, 6.2 State Tables, table 6.13
286 mPixelStorePackAlignment = 4;
287 mPixelStoreUnpackAlignment = 4;
289 WebGLMemoryTracker::AddWebGLContext(this);
291 mAllowContextRestore = true;
292 mLastLossWasSimulated = false;
293 mContextLossHandler = new WebGLContextLossHandler(this);
294 mContextStatus = ContextNotLost;
295 mLoseContextOnMemoryPressure = false;
296 mCanLoseContextInForeground = true;
297 mRestoreWhenVisible = false;
299 mAlreadyGeneratedWarnings = 0;
300 mAlreadyWarnedAboutFakeVertexAttrib0 = false;
301 mAlreadyWarnedAboutViewportLargerThanDest = false;
302 mMaxWarnings = Preferences::GetInt("webgl.max-warnings-per-context", 32);
303 if (mMaxWarnings < -1)
305 GenerateWarning("webgl.max-warnings-per-context size is too large (seems like a negative value wrapped)");
306 mMaxWarnings = 0;
309 mContextObserver = new WebGLObserver(this);
310 MOZ_RELEASE_ASSERT(mContextObserver, "Can't alloc WebGLContextObserver");
312 mLastUseIndex = 0;
314 InvalidateBufferFetching();
316 mBackbufferNeedsClear = true;
318 mDisableFragHighP = false;
320 mDrawCallsSinceLastFlush = 0;
323 WebGLContext::~WebGLContext()
325 mContextObserver->Destroy();
327 DestroyResourcesAndContext();
328 WebGLMemoryTracker::RemoveWebGLContext(this);
330 mContextLossHandler->DisableTimer();
331 mContextLossHandler = nullptr;
334 void
335 WebGLContext::DestroyResourcesAndContext()
337 mContextObserver->UnregisterMemoryPressureEvent();
339 if (!gl)
340 return;
342 gl->MakeCurrent();
344 mBound2DTextures.Clear();
345 mBoundCubeMapTextures.Clear();
346 mBoundArrayBuffer = nullptr;
347 mBoundTransformFeedbackBuffer = nullptr;
348 mCurrentProgram = nullptr;
349 mBoundFramebuffer = nullptr;
350 mActiveOcclusionQuery = nullptr;
351 mBoundRenderbuffer = nullptr;
352 mBoundVertexArray = nullptr;
353 mDefaultVertexArray = nullptr;
355 while (!mTextures.isEmpty())
356 mTextures.getLast()->DeleteOnce();
357 while (!mVertexArrays.isEmpty())
358 mVertexArrays.getLast()->DeleteOnce();
359 while (!mBuffers.isEmpty())
360 mBuffers.getLast()->DeleteOnce();
361 while (!mRenderbuffers.isEmpty())
362 mRenderbuffers.getLast()->DeleteOnce();
363 while (!mFramebuffers.isEmpty())
364 mFramebuffers.getLast()->DeleteOnce();
365 while (!mShaders.isEmpty())
366 mShaders.getLast()->DeleteOnce();
367 while (!mPrograms.isEmpty())
368 mPrograms.getLast()->DeleteOnce();
369 while (!mQueries.isEmpty())
370 mQueries.getLast()->DeleteOnce();
372 mBlackOpaqueTexture2D = nullptr;
373 mBlackOpaqueTextureCubeMap = nullptr;
374 mBlackTransparentTexture2D = nullptr;
375 mBlackTransparentTextureCubeMap = nullptr;
377 if (mFakeVertexAttrib0BufferObject) {
378 gl->fDeleteBuffers(1, &mFakeVertexAttrib0BufferObject);
381 // disable all extensions except "WEBGL_lose_context". see bug #927969
382 // spec: http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
383 for (size_t i = 0; i < size_t(WebGLExtensionID::Max); ++i) {
384 WebGLExtensionID extension = WebGLExtensionID(i);
386 if (!IsExtensionEnabled(extension) || (extension == WebGLExtensionID::WEBGL_lose_context))
387 continue;
389 mExtensions[extension]->MarkLost();
390 mExtensions[extension] = nullptr;
393 // We just got rid of everything, so the context had better
394 // have been going away.
395 #ifdef DEBUG
396 if (gl->DebugMode()) {
397 printf_stderr("--- WebGL context destroyed: %p\n", gl.get());
399 #endif
401 gl = nullptr;
404 void
405 WebGLContext::Invalidate()
407 if (mInvalidated)
408 return;
410 if (!mCanvasElement)
411 return;
413 nsSVGEffects::InvalidateDirectRenderingObservers(mCanvasElement);
415 mInvalidated = true;
416 mCanvasElement->InvalidateCanvasContent(nullptr);
420 // nsICanvasRenderingContextInternal
423 NS_IMETHODIMP
424 WebGLContext::SetContextOptions(JSContext* aCx, JS::Handle<JS::Value> aOptions)
426 if (aOptions.isNullOrUndefined() && mOptionsFrozen) {
427 return NS_OK;
430 WebGLContextAttributes attributes;
431 NS_ENSURE_TRUE(attributes.Init(aCx, aOptions), NS_ERROR_UNEXPECTED);
433 WebGLContextOptions newOpts;
435 newOpts.stencil = attributes.mStencil;
436 newOpts.depth = attributes.mDepth;
437 newOpts.premultipliedAlpha = attributes.mPremultipliedAlpha;
438 newOpts.antialias = attributes.mAntialias;
439 newOpts.preserveDrawingBuffer = attributes.mPreserveDrawingBuffer;
441 if (attributes.mAlpha.WasPassed()) {
442 newOpts.alpha = attributes.mAlpha.Value();
445 // Don't do antialiasing if we've disabled MSAA.
446 if (!gfxPrefs::MSAALevel()) {
447 newOpts.antialias = false;
450 #if 0
451 GenerateWarning("aaHint: %d stencil: %d depth: %d alpha: %d premult: %d preserve: %d\n",
452 newOpts.antialias ? 1 : 0,
453 newOpts.stencil ? 1 : 0,
454 newOpts.depth ? 1 : 0,
455 newOpts.alpha ? 1 : 0,
456 newOpts.premultipliedAlpha ? 1 : 0,
457 newOpts.preserveDrawingBuffer ? 1 : 0);
458 #endif
460 if (mOptionsFrozen && newOpts != mOptions) {
461 // Error if the options are already frozen, and the ones that were asked for
462 // aren't the same as what they were originally.
463 return NS_ERROR_FAILURE;
466 mOptions = newOpts;
467 return NS_OK;
470 #ifdef DEBUG
471 int32_t
472 WebGLContext::GetWidth() const
474 return mWidth;
477 int32_t
478 WebGLContext::GetHeight() const
480 return mHeight;
482 #endif
484 /* So there are a number of points of failure here. We might fail based
485 * on EGL vs. WGL, or we might fail to alloc a too-large size, or we
486 * might not be able to create a context with a certain combo of context
487 * creation attribs.
489 * We don't want to test the complete fallback matrix. (for now, at
490 * least) Instead, attempt creation in this order:
491 * 1. By platform API. (e.g. EGL vs. WGL)
492 * 2. By context creation attribs.
493 * 3. By size.
495 * That is, try to create headless contexts based on the platform API.
496 * Next, create dummy-sized backbuffers for the contexts with the right
497 * caps. Finally, resize the backbuffer to an acceptable size given the
498 * requested size.
501 static bool
502 IsFeatureInBlacklist(const nsCOMPtr<nsIGfxInfo>& gfxInfo, int32_t feature)
504 int32_t status;
505 if (!NS_SUCCEEDED(gfxInfo->GetFeatureStatus(feature, &status)))
506 return false;
508 return status != nsIGfxInfo::FEATURE_STATUS_OK;
511 static already_AddRefed<GLContext>
512 CreateHeadlessNativeGL(bool forceEnabled,
513 const nsCOMPtr<nsIGfxInfo>& gfxInfo,
514 WebGLContext* webgl)
516 if (!forceEnabled &&
517 IsFeatureInBlacklist(gfxInfo, nsIGfxInfo::FEATURE_WEBGL_OPENGL))
519 webgl->GenerateWarning("Refused to create native OpenGL context"
520 " because of blacklisting.");
521 return nullptr;
524 nsRefPtr<GLContext> gl = gl::GLContextProvider::CreateHeadless();
525 if (!gl) {
526 webgl->GenerateWarning("Error during native OpenGL init.");
527 return nullptr;
529 MOZ_ASSERT(!gl->IsANGLE());
531 return gl.forget();
534 // Note that we have a separate call for ANGLE and EGL, even though
535 // right now, we get ANGLE implicitly by using EGL on Windows.
536 // Eventually, we want to be able to pick ANGLE-EGL or native EGL.
537 static already_AddRefed<GLContext>
538 CreateHeadlessANGLE(bool forceEnabled,
539 const nsCOMPtr<nsIGfxInfo>& gfxInfo,
540 WebGLContext* webgl)
542 nsRefPtr<GLContext> gl;
544 #ifdef XP_WIN
545 if (!forceEnabled &&
546 IsFeatureInBlacklist(gfxInfo, nsIGfxInfo::FEATURE_WEBGL_ANGLE))
548 webgl->GenerateWarning("Refused to create ANGLE OpenGL context"
549 " because of blacklisting.");
550 return nullptr;
553 gl = gl::GLContextProviderEGL::CreateHeadless();
554 if (!gl) {
555 webgl->GenerateWarning("Error during ANGLE OpenGL init.");
556 return nullptr;
558 MOZ_ASSERT(gl->IsANGLE());
559 #endif
561 return gl.forget();
564 static already_AddRefed<GLContext>
565 CreateHeadlessEGL(bool forceEnabled,
566 const nsCOMPtr<nsIGfxInfo>& gfxInfo,
567 WebGLContext* webgl)
569 nsRefPtr<GLContext> gl;
571 #ifdef ANDROID
572 gl = gl::GLContextProviderEGL::CreateHeadless();
573 if (!gl) {
574 webgl->GenerateWarning("Error during EGL OpenGL init.");
575 return nullptr;
577 MOZ_ASSERT(!gl->IsANGLE());
578 #endif
580 return gl.forget();
584 static already_AddRefed<GLContext>
585 CreateHeadlessGL(bool forceEnabled,
586 const nsCOMPtr<nsIGfxInfo>& gfxInfo,
587 WebGLContext* webgl)
589 bool preferEGL = PR_GetEnv("MOZ_WEBGL_PREFER_EGL");
590 bool disableANGLE = Preferences::GetBool("webgl.disable-angle", false);
592 if (PR_GetEnv("MOZ_WEBGL_FORCE_OPENGL")) {
593 disableANGLE = true;
596 nsRefPtr<GLContext> gl;
598 if (preferEGL)
599 gl = CreateHeadlessEGL(forceEnabled, gfxInfo, webgl);
601 if (!gl && !disableANGLE)
602 gl = CreateHeadlessANGLE(forceEnabled, gfxInfo, webgl);
604 if (!gl)
605 gl = CreateHeadlessNativeGL(forceEnabled, gfxInfo, webgl);
607 return gl.forget();
610 // Try to create a dummy offscreen with the given caps.
611 static bool
612 CreateOffscreenWithCaps(GLContext* gl, const SurfaceCaps& caps)
614 gfx::IntSize dummySize(16, 16);
615 return gl->InitOffscreen(dummySize, caps);
618 static void
619 PopulateCapFallbackQueue(const SurfaceCaps& baseCaps,
620 std::queue<SurfaceCaps>* fallbackCaps)
622 fallbackCaps->push(baseCaps);
624 // Dropping antialias drops our quality, but not our correctness.
625 // The user basically doesn't have to handle if this fails, they
626 // just get reduced quality.
627 if (baseCaps.antialias) {
628 SurfaceCaps nextCaps(baseCaps);
629 nextCaps.antialias = false;
630 PopulateCapFallbackQueue(nextCaps, fallbackCaps);
633 // If we have to drop one of depth or stencil, we'd prefer to keep
634 // depth. However, the client app will need to handle if this
635 // doesn't work.
636 if (baseCaps.stencil) {
637 SurfaceCaps nextCaps(baseCaps);
638 nextCaps.stencil = false;
639 PopulateCapFallbackQueue(nextCaps, fallbackCaps);
642 if (baseCaps.depth) {
643 SurfaceCaps nextCaps(baseCaps);
644 nextCaps.depth = false;
645 PopulateCapFallbackQueue(nextCaps, fallbackCaps);
649 static bool
650 CreateOffscreen(GLContext* gl,
651 const WebGLContextOptions& options,
652 const nsCOMPtr<nsIGfxInfo>& gfxInfo,
653 WebGLContext* webgl,
654 layers::ISurfaceAllocator* surfAllocator)
656 SurfaceCaps baseCaps;
658 baseCaps.color = true;
659 baseCaps.alpha = options.alpha;
660 baseCaps.antialias = options.antialias;
661 baseCaps.depth = options.depth;
662 baseCaps.preserve = options.preserveDrawingBuffer;
663 baseCaps.stencil = options.stencil;
665 // we should really have this behind a
666 // |gfxPlatform::GetPlatform()->GetScreenDepth() == 16| check, but
667 // for now it's just behind a pref for testing/evaluation.
668 baseCaps.bpp16 = Preferences::GetBool("webgl.prefer-16bpp", false);
670 #ifdef MOZ_WIDGET_GONK
671 baseCaps.surfaceAllocator = surfAllocator;
672 #endif
674 // Done with baseCaps construction.
676 bool forceAllowAA = Preferences::GetBool("webgl.msaa-force", false);
677 if (!forceAllowAA &&
678 IsFeatureInBlacklist(gfxInfo, nsIGfxInfo::FEATURE_WEBGL_MSAA))
680 webgl->GenerateWarning("Disallowing antialiased backbuffers due"
681 " to blacklisting.");
682 baseCaps.antialias = false;
685 std::queue<SurfaceCaps> fallbackCaps;
686 PopulateCapFallbackQueue(baseCaps, &fallbackCaps);
688 bool created = false;
689 while (!fallbackCaps.empty()) {
690 SurfaceCaps& caps = fallbackCaps.front();
692 created = CreateOffscreenWithCaps(gl, caps);
693 if (created)
694 break;
696 fallbackCaps.pop();
699 return created;
702 bool
703 WebGLContext::CreateOffscreenGL(bool forceEnabled)
705 nsCOMPtr<nsIGfxInfo> gfxInfo = do_GetService("@mozilla.org/gfx/info;1");
707 layers::ISurfaceAllocator* surfAllocator = nullptr;
708 #ifdef MOZ_WIDGET_GONK
709 nsIWidget* docWidget = nsContentUtils::WidgetForDocument(mCanvasElement->OwnerDoc());
710 if (docWidget) {
711 layers::LayerManager* layerManager = docWidget->GetLayerManager();
712 if (layerManager) {
713 // XXX we really want "AsSurfaceAllocator" here for generality
714 layers::ShadowLayerForwarder* forwarder = layerManager->AsShadowForwarder();
715 if (forwarder) {
716 surfAllocator = static_cast<layers::ISurfaceAllocator*>(forwarder);
720 #endif
722 gl = CreateHeadlessGL(forceEnabled, gfxInfo, this);
724 do {
725 if (!gl)
726 break;
728 if (!CreateOffscreen(gl, mOptions, gfxInfo, this, surfAllocator))
729 break;
731 if (!InitAndValidateGL())
732 break;
734 return true;
735 } while (false);
737 gl = nullptr;
738 return false;
741 // Fallback for resizes:
742 bool
743 WebGLContext::ResizeBackbuffer(uint32_t requestedWidth, uint32_t requestedHeight)
745 uint32_t width = requestedWidth;
746 uint32_t height = requestedHeight;
748 bool resized = false;
749 while (width || height) {
750 width = width ? width : 1;
751 height = height ? height : 1;
753 gfx::IntSize curSize(width, height);
754 if (gl->ResizeOffscreen(curSize)) {
755 resized = true;
756 break;
759 width /= 2;
760 height /= 2;
763 if (!resized)
764 return false;
766 mWidth = gl->OffscreenSize().width;
767 mHeight = gl->OffscreenSize().height;
768 MOZ_ASSERT((uint32_t)mWidth == width);
769 MOZ_ASSERT((uint32_t)mHeight == height);
771 if (width != requestedWidth ||
772 height != requestedHeight)
774 GenerateWarning("Requested size %dx%d was too large, but resize"
775 " to %dx%d succeeded.",
776 requestedWidth, requestedHeight,
777 width, height);
779 return true;
783 NS_IMETHODIMP
784 WebGLContext::SetDimensions(int32_t sWidth, int32_t sHeight)
786 // Early error return cases
787 if (!GetCanvas())
788 return NS_ERROR_FAILURE;
790 if (sWidth < 0 || sHeight < 0) {
791 GenerateWarning("Canvas size is too large (seems like a negative value wrapped)");
792 return NS_ERROR_OUT_OF_MEMORY;
795 uint32_t width = sWidth;
796 uint32_t height = sHeight;
798 // Early success return cases
799 GetCanvas()->InvalidateCanvas();
801 // Zero-sized surfaces can cause problems.
802 if (width == 0) {
803 width = 1;
805 if (height == 0) {
806 height = 1;
809 // If we already have a gl context, then we just need to resize it
810 if (gl) {
811 if ((uint32_t)mWidth == width &&
812 (uint32_t)mHeight == height)
814 return NS_OK;
817 if (IsContextLost())
818 return NS_OK;
820 MakeContextCurrent();
822 // If we've already drawn, we should commit the current buffer.
823 PresentScreenBuffer();
825 // ResizeOffscreen scraps the current prod buffer before making a new one.
826 if (!ResizeBackbuffer(width, height)) {
827 GenerateWarning("WebGL context failed to resize.");
828 ForceLoseContext();
829 return NS_OK;
832 // everything's good, we're done here
833 mResetLayer = true;
834 mBackbufferNeedsClear = true;
836 return NS_OK;
839 // End of early return cases.
840 // At this point we know that we're not just resizing an existing context,
841 // we are initializing a new context.
843 // if we exceeded either the global or the per-principal limit for WebGL contexts,
844 // lose the oldest-used context now to free resources. Note that we can't do that
845 // in the WebGLContext constructor as we don't have a canvas element yet there.
846 // Here is the right place to do so, as we are about to create the OpenGL context
847 // and that is what can fail if we already have too many.
848 LoseOldestWebGLContextIfLimitExceeded();
850 // We're going to create an entirely new context. If our
851 // generation is not 0 right now (that is, if this isn't the first
852 // context we're creating), we may have to dispatch a context lost
853 // event.
855 // If incrementing the generation would cause overflow,
856 // don't allow it. Allowing this would allow us to use
857 // resource handles created from older context generations.
858 if (!(mGeneration + 1).isValid()) {
859 GenerateWarning("Too many WebGL contexts created this run.");
860 return NS_ERROR_FAILURE; // exit without changing the value of mGeneration
863 // Get some prefs for some preferred/overriden things
864 NS_ENSURE_TRUE(Preferences::GetRootBranch(), NS_ERROR_FAILURE);
866 bool disabled = Preferences::GetBool("webgl.disabled", false);
867 if (disabled) {
868 GenerateWarning("WebGL creation is disabled, and so disallowed here.");
869 return NS_ERROR_FAILURE;
872 // Alright, now let's start trying.
873 bool forceEnabled = Preferences::GetBool("webgl.force-enabled", false);
874 ScopedGfxFeatureReporter reporter("WebGL", forceEnabled);
876 if (!CreateOffscreenGL(forceEnabled)) {
877 GenerateWarning("WebGL creation failed.");
878 return NS_ERROR_FAILURE;
880 MOZ_ASSERT(gl);
882 if (!ResizeBackbuffer(width, height)) {
883 GenerateWarning("Initializing WebGL backbuffer failed.");
884 return NS_ERROR_FAILURE;
887 #ifdef DEBUG
888 if (gl->DebugMode()) {
889 printf_stderr("--- WebGL context created: %p\n", gl.get());
891 #endif
893 mResetLayer = true;
894 mOptionsFrozen = true;
896 // increment the generation number
897 ++mGeneration;
899 MakeContextCurrent();
901 gl->fViewport(0, 0, mWidth, mHeight);
902 mViewportWidth = mWidth;
903 mViewportHeight = mHeight;
905 // Make sure that we clear this out, otherwise
906 // we'll end up displaying random memory
907 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0);
909 AssertCachedBindings();
910 AssertCachedState();
912 // Clear immediately, because we need to present the cleared initial
913 // buffer.
914 mBackbufferNeedsClear = true;
915 ClearBackbufferIfNeeded();
917 mShouldPresent = true;
919 MOZ_ASSERT(gl->Caps().color);
920 MOZ_ASSERT(gl->Caps().alpha == mOptions.alpha);
921 MOZ_ASSERT(gl->Caps().depth == mOptions.depth || !gl->Caps().depth);
922 MOZ_ASSERT(gl->Caps().stencil == mOptions.stencil || !gl->Caps().stencil);
923 MOZ_ASSERT(gl->Caps().antialias == mOptions.antialias || !gl->Caps().antialias);
924 MOZ_ASSERT(gl->Caps().preserve == mOptions.preserveDrawingBuffer);
926 AssertCachedBindings();
927 AssertCachedState();
929 reporter.SetSuccessful();
930 return NS_OK;
933 void
934 WebGLContext::ClearBackbufferIfNeeded()
936 if (!mBackbufferNeedsClear)
937 return;
939 #ifdef DEBUG
940 gl->MakeCurrent();
942 GLuint fb = 0;
943 gl->GetUIntegerv(LOCAL_GL_FRAMEBUFFER_BINDING, &fb);
944 MOZ_ASSERT(fb == 0);
945 #endif
947 ClearScreen();
949 mBackbufferNeedsClear = false;
952 void WebGLContext::LoseOldestWebGLContextIfLimitExceeded()
954 #ifdef MOZ_GFX_OPTIMIZE_MOBILE
955 // some mobile devices can't have more than 8 GL contexts overall
956 const size_t kMaxWebGLContextsPerPrincipal = 2;
957 const size_t kMaxWebGLContexts = 4;
958 #else
959 const size_t kMaxWebGLContextsPerPrincipal = 16;
960 const size_t kMaxWebGLContexts = 32;
961 #endif
962 MOZ_ASSERT(kMaxWebGLContextsPerPrincipal < kMaxWebGLContexts);
964 // it's important to update the index on a new context before losing old contexts,
965 // otherwise new unused contexts would all have index 0 and we couldn't distinguish older ones
966 // when choosing which one to lose first.
967 UpdateLastUseIndex();
969 WebGLMemoryTracker::ContextsArrayType &contexts
970 = WebGLMemoryTracker::Contexts();
972 // quick exit path, should cover a majority of cases
973 if (contexts.Length() <= kMaxWebGLContextsPerPrincipal) {
974 return;
977 // note that here by "context" we mean "non-lost context". See the check for
978 // IsContextLost() below. Indeed, the point of this function is to maybe lose
979 // some currently non-lost context.
981 uint64_t oldestIndex = UINT64_MAX;
982 uint64_t oldestIndexThisPrincipal = UINT64_MAX;
983 const WebGLContext *oldestContext = nullptr;
984 const WebGLContext *oldestContextThisPrincipal = nullptr;
985 size_t numContexts = 0;
986 size_t numContextsThisPrincipal = 0;
988 for(size_t i = 0; i < contexts.Length(); ++i) {
990 // don't want to lose ourselves.
991 if (contexts[i] == this)
992 continue;
994 if (contexts[i]->IsContextLost())
995 continue;
997 if (!contexts[i]->GetCanvas()) {
998 // Zombie context: the canvas is already destroyed, but something else
999 // (typically the compositor) is still holding on to the context.
1000 // Killing zombies is a no-brainer.
1001 const_cast<WebGLContext*>(contexts[i])->LoseContext();
1002 continue;
1005 numContexts++;
1006 if (contexts[i]->mLastUseIndex < oldestIndex) {
1007 oldestIndex = contexts[i]->mLastUseIndex;
1008 oldestContext = contexts[i];
1011 nsIPrincipal *ourPrincipal = GetCanvas()->NodePrincipal();
1012 nsIPrincipal *theirPrincipal = contexts[i]->GetCanvas()->NodePrincipal();
1013 bool samePrincipal;
1014 nsresult rv = ourPrincipal->Equals(theirPrincipal, &samePrincipal);
1015 if (NS_SUCCEEDED(rv) && samePrincipal) {
1016 numContextsThisPrincipal++;
1017 if (contexts[i]->mLastUseIndex < oldestIndexThisPrincipal) {
1018 oldestIndexThisPrincipal = contexts[i]->mLastUseIndex;
1019 oldestContextThisPrincipal = contexts[i];
1024 if (numContextsThisPrincipal > kMaxWebGLContextsPerPrincipal) {
1025 GenerateWarning("Exceeded %d live WebGL contexts for this principal, losing the "
1026 "least recently used one.", kMaxWebGLContextsPerPrincipal);
1027 MOZ_ASSERT(oldestContextThisPrincipal); // if we reach this point, this can't be null
1028 const_cast<WebGLContext*>(oldestContextThisPrincipal)->LoseContext();
1029 } else if (numContexts > kMaxWebGLContexts) {
1030 GenerateWarning("Exceeded %d live WebGL contexts, losing the least recently used one.",
1031 kMaxWebGLContexts);
1032 MOZ_ASSERT(oldestContext); // if we reach this point, this can't be null
1033 const_cast<WebGLContext*>(oldestContext)->LoseContext();
1037 void
1038 WebGLContext::GetImageBuffer(uint8_t** aImageBuffer, int32_t* aFormat)
1040 *aImageBuffer = nullptr;
1041 *aFormat = 0;
1043 // Use GetSurfaceSnapshot() to make sure that appropriate y-flip gets applied
1044 bool premult;
1045 RefPtr<SourceSurface> snapshot =
1046 GetSurfaceSnapshot(mOptions.premultipliedAlpha ? nullptr : &premult);
1047 if (!snapshot) {
1048 return;
1050 MOZ_ASSERT(mOptions.premultipliedAlpha || !premult, "We must get unpremult when we ask for it!");
1052 RefPtr<DataSourceSurface> dataSurface = snapshot->GetDataSurface();
1054 DataSourceSurface::MappedSurface map;
1055 if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) {
1056 return;
1059 static const fallible_t fallible = fallible_t();
1060 uint8_t* imageBuffer = new (fallible) uint8_t[mWidth * mHeight * 4];
1061 if (!imageBuffer) {
1062 dataSurface->Unmap();
1063 return;
1065 memcpy(imageBuffer, map.mData, mWidth * mHeight * 4);
1067 dataSurface->Unmap();
1069 int32_t format = imgIEncoder::INPUT_FORMAT_HOSTARGB;
1070 if (!mOptions.premultipliedAlpha) {
1071 // We need to convert to INPUT_FORMAT_RGBA, otherwise
1072 // we are automatically considered premult, and unpremult'd.
1073 // Yes, it is THAT silly.
1074 // Except for different lossy conversions by color,
1075 // we could probably just change the label, and not change the data.
1076 gfxUtils::ConvertBGRAtoRGBA(imageBuffer, mWidth * mHeight * 4);
1077 format = imgIEncoder::INPUT_FORMAT_RGBA;
1080 *aImageBuffer = imageBuffer;
1081 *aFormat = format;
1084 NS_IMETHODIMP
1085 WebGLContext::GetInputStream(const char* aMimeType,
1086 const char16_t* aEncoderOptions,
1087 nsIInputStream **aStream)
1089 NS_ASSERTION(gl, "GetInputStream on invalid context?");
1090 if (!gl)
1091 return NS_ERROR_FAILURE;
1093 nsCString enccid("@mozilla.org/image/encoder;2?type=");
1094 enccid += aMimeType;
1095 nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get());
1096 if (!encoder) {
1097 return NS_ERROR_FAILURE;
1100 nsAutoArrayPtr<uint8_t> imageBuffer;
1101 int32_t format = 0;
1102 GetImageBuffer(getter_Transfers(imageBuffer), &format);
1103 if (!imageBuffer) {
1104 return NS_ERROR_FAILURE;
1107 return ImageEncoder::GetInputStream(mWidth, mHeight, imageBuffer, format,
1108 encoder, aEncoderOptions, aStream);
1111 void WebGLContext::UpdateLastUseIndex()
1113 static CheckedInt<uint64_t> sIndex = 0;
1115 sIndex++;
1117 // should never happen with 64-bit; trying to handle this would be riskier than
1118 // not handling it as the handler code would never get exercised.
1119 if (!sIndex.isValid()) {
1120 NS_RUNTIMEABORT("Can't believe it's been 2^64 transactions already!");
1123 mLastUseIndex = sIndex.value();
1126 static uint8_t gWebGLLayerUserData;
1128 namespace mozilla {
1130 class WebGLContextUserData : public LayerUserData {
1131 public:
1132 WebGLContextUserData(HTMLCanvasElement *aContent)
1133 : mContent(aContent)
1136 /* PreTransactionCallback gets called by the Layers code every time the
1137 * WebGL canvas is going to be composited.
1139 static void PreTransactionCallback(void* data)
1141 WebGLContextUserData* userdata = static_cast<WebGLContextUserData*>(data);
1142 HTMLCanvasElement* canvas = userdata->mContent;
1143 WebGLContext* context = static_cast<WebGLContext*>(canvas->GetContextAtIndex(0));
1145 // Present our screenbuffer, if needed.
1146 context->PresentScreenBuffer();
1147 context->mDrawCallsSinceLastFlush = 0;
1150 /** DidTransactionCallback gets called by the Layers code everytime the WebGL canvas gets composite,
1151 * so it really is the right place to put actions that have to be performed upon compositing
1153 static void DidTransactionCallback(void* aData)
1155 WebGLContextUserData *userdata = static_cast<WebGLContextUserData*>(aData);
1156 HTMLCanvasElement *canvas = userdata->mContent;
1157 WebGLContext *context = static_cast<WebGLContext*>(canvas->GetContextAtIndex(0));
1159 // Mark ourselves as no longer invalidated.
1160 context->MarkContextClean();
1162 context->UpdateLastUseIndex();
1165 private:
1166 nsRefPtr<HTMLCanvasElement> mContent;
1169 } // end namespace mozilla
1171 already_AddRefed<layers::CanvasLayer>
1172 WebGLContext::GetCanvasLayer(nsDisplayListBuilder* aBuilder,
1173 CanvasLayer *aOldLayer,
1174 LayerManager *aManager)
1176 if (IsContextLost())
1177 return nullptr;
1179 if (!mResetLayer && aOldLayer &&
1180 aOldLayer->HasUserData(&gWebGLLayerUserData)) {
1181 nsRefPtr<layers::CanvasLayer> ret = aOldLayer;
1182 return ret.forget();
1185 nsRefPtr<CanvasLayer> canvasLayer = aManager->CreateCanvasLayer();
1186 if (!canvasLayer) {
1187 NS_WARNING("CreateCanvasLayer returned null!");
1188 return nullptr;
1190 WebGLContextUserData *userData = nullptr;
1191 if (aBuilder->IsPaintingToWindow()) {
1192 // Make the layer tell us whenever a transaction finishes (including
1193 // the current transaction), so we can clear our invalidation state and
1194 // start invalidating again. We need to do this for the layer that is
1195 // being painted to a window (there shouldn't be more than one at a time,
1196 // and if there is, flushing the invalidation state more often than
1197 // necessary is harmless).
1199 // The layer will be destroyed when we tear down the presentation
1200 // (at the latest), at which time this userData will be destroyed,
1201 // releasing the reference to the element.
1202 // The userData will receive DidTransactionCallbacks, which flush the
1203 // the invalidation state to indicate that the canvas is up to date.
1204 userData = new WebGLContextUserData(mCanvasElement);
1205 canvasLayer->SetDidTransactionCallback(
1206 WebGLContextUserData::DidTransactionCallback, userData);
1207 canvasLayer->SetPreTransactionCallback(
1208 WebGLContextUserData::PreTransactionCallback, userData);
1210 canvasLayer->SetUserData(&gWebGLLayerUserData, userData);
1212 CanvasLayer::Data data;
1213 data.mGLContext = gl;
1214 data.mSize = nsIntSize(mWidth, mHeight);
1215 data.mHasAlpha = gl->Caps().alpha;
1216 data.mIsGLAlphaPremult = IsPremultAlpha() || !data.mHasAlpha;
1218 canvasLayer->Initialize(data);
1219 uint32_t flags = gl->Caps().alpha ? 0 : Layer::CONTENT_OPAQUE;
1220 canvasLayer->SetContentFlags(flags);
1221 canvasLayer->Updated();
1223 mResetLayer = false;
1225 return canvasLayer.forget();
1228 void
1229 WebGLContext::GetContextAttributes(Nullable<dom::WebGLContextAttributes> &retval)
1231 retval.SetNull();
1232 if (IsContextLost())
1233 return;
1235 dom::WebGLContextAttributes& result = retval.SetValue();
1237 const PixelBufferFormat& format = gl->GetPixelFormat();
1239 result.mAlpha.Construct(format.alpha > 0);
1240 result.mDepth = format.depth > 0;
1241 result.mStencil = format.stencil > 0;
1242 result.mAntialias = format.samples > 1;
1243 result.mPremultipliedAlpha = mOptions.premultipliedAlpha;
1244 result.mPreserveDrawingBuffer = mOptions.preserveDrawingBuffer;
1247 /* [noscript] DOMString mozGetUnderlyingParamString(in GLenum pname); */
1248 NS_IMETHODIMP
1249 WebGLContext::MozGetUnderlyingParamString(uint32_t pname, nsAString& retval)
1251 if (IsContextLost())
1252 return NS_OK;
1254 retval.SetIsVoid(true);
1256 MakeContextCurrent();
1258 switch (pname) {
1259 case LOCAL_GL_VENDOR:
1260 case LOCAL_GL_RENDERER:
1261 case LOCAL_GL_VERSION:
1262 case LOCAL_GL_SHADING_LANGUAGE_VERSION:
1263 case LOCAL_GL_EXTENSIONS: {
1264 const char *s = (const char *) gl->fGetString(pname);
1265 retval.Assign(NS_ConvertASCIItoUTF16(nsDependentCString(s)));
1267 break;
1269 default:
1270 return NS_ERROR_INVALID_ARG;
1273 return NS_OK;
1276 void
1277 WebGLContext::ClearScreen()
1279 bool colorAttachmentsMask[WebGLContext::kMaxColorAttachments] = {false};
1281 MakeContextCurrent();
1282 ScopedBindFramebuffer autoFB(gl, 0);
1284 GLbitfield clearMask = LOCAL_GL_COLOR_BUFFER_BIT;
1285 if (mOptions.depth)
1286 clearMask |= LOCAL_GL_DEPTH_BUFFER_BIT;
1287 if (mOptions.stencil)
1288 clearMask |= LOCAL_GL_STENCIL_BUFFER_BIT;
1290 colorAttachmentsMask[0] = true;
1292 ForceClearFramebufferWithDefaultValues(clearMask, colorAttachmentsMask);
1295 void
1296 WebGLContext::ForceClearFramebufferWithDefaultValues(GLbitfield mask, const bool colorAttachmentsMask[kMaxColorAttachments])
1298 MakeContextCurrent();
1300 bool initializeColorBuffer = 0 != (mask & LOCAL_GL_COLOR_BUFFER_BIT);
1301 bool initializeDepthBuffer = 0 != (mask & LOCAL_GL_DEPTH_BUFFER_BIT);
1302 bool initializeStencilBuffer = 0 != (mask & LOCAL_GL_STENCIL_BUFFER_BIT);
1303 bool drawBuffersIsEnabled = IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers);
1304 bool shouldOverrideDrawBuffers = false;
1306 GLenum currentDrawBuffers[WebGLContext::kMaxColorAttachments];
1308 // Fun GL fact: No need to worry about the viewport here, glViewport is just
1309 // setting up a coordinates transformation, it doesn't affect glClear at all.
1310 AssertCachedState(); // Can't check cached bindings, as we could
1311 // have a different FB bound temporarily.
1313 // Prepare GL state for clearing.
1314 gl->fDisable(LOCAL_GL_SCISSOR_TEST);
1316 if (initializeColorBuffer) {
1318 if (drawBuffersIsEnabled) {
1320 GLenum drawBuffersCommand[WebGLContext::kMaxColorAttachments] = { LOCAL_GL_NONE };
1322 for(int32_t i = 0; i < mGLMaxDrawBuffers; i++) {
1323 GLint temp;
1324 gl->fGetIntegerv(LOCAL_GL_DRAW_BUFFER0 + i, &temp);
1325 currentDrawBuffers[i] = temp;
1327 if (colorAttachmentsMask[i]) {
1328 drawBuffersCommand[i] = LOCAL_GL_COLOR_ATTACHMENT0 + i;
1330 if (currentDrawBuffers[i] != drawBuffersCommand[i])
1331 shouldOverrideDrawBuffers = true;
1333 // calling draw buffers can cause resolves on adreno drivers so
1334 // we try to avoid calling it
1335 if (shouldOverrideDrawBuffers)
1336 gl->fDrawBuffers(mGLMaxDrawBuffers, drawBuffersCommand);
1339 gl->fColorMask(1, 1, 1, 1);
1340 gl->fClearColor(0.0f, 0.0f, 0.0f, 0.0f);
1343 if (initializeDepthBuffer) {
1344 gl->fDepthMask(1);
1345 gl->fClearDepth(1.0f);
1348 if (initializeStencilBuffer) {
1349 // "The clear operation always uses the front stencil write mask
1350 // when clearing the stencil buffer."
1351 gl->fStencilMaskSeparate(LOCAL_GL_FRONT, 0xffffffff);
1352 gl->fStencilMaskSeparate(LOCAL_GL_BACK, 0xffffffff);
1353 gl->fClearStencil(0);
1356 if (mRasterizerDiscardEnabled) {
1357 gl->fDisable(LOCAL_GL_RASTERIZER_DISCARD);
1360 // Do the clear!
1361 gl->fClear(mask);
1363 // And reset!
1364 if (mScissorTestEnabled)
1365 gl->fEnable(LOCAL_GL_SCISSOR_TEST);
1367 if (mRasterizerDiscardEnabled) {
1368 gl->fEnable(LOCAL_GL_RASTERIZER_DISCARD);
1371 // Restore GL state after clearing.
1372 if (initializeColorBuffer) {
1373 if (shouldOverrideDrawBuffers) {
1374 gl->fDrawBuffers(mGLMaxDrawBuffers, currentDrawBuffers);
1377 gl->fColorMask(mColorWriteMask[0],
1378 mColorWriteMask[1],
1379 mColorWriteMask[2],
1380 mColorWriteMask[3]);
1381 gl->fClearColor(mColorClearValue[0],
1382 mColorClearValue[1],
1383 mColorClearValue[2],
1384 mColorClearValue[3]);
1387 if (initializeDepthBuffer) {
1388 gl->fDepthMask(mDepthWriteMask);
1389 gl->fClearDepth(mDepthClearValue);
1392 if (initializeStencilBuffer) {
1393 gl->fStencilMaskSeparate(LOCAL_GL_FRONT, mStencilWriteMaskFront);
1394 gl->fStencilMaskSeparate(LOCAL_GL_BACK, mStencilWriteMaskBack);
1395 gl->fClearStencil(mStencilClearValue);
1399 // For an overview of how WebGL compositing works, see:
1400 // https://wiki.mozilla.org/Platform/GFX/WebGL/Compositing
1401 bool
1402 WebGLContext::PresentScreenBuffer()
1404 if (IsContextLost()) {
1405 return false;
1408 if (!mShouldPresent) {
1409 return false;
1412 gl->MakeCurrent();
1413 MOZ_ASSERT(!mBackbufferNeedsClear);
1414 if (!gl->PublishFrame()) {
1415 ForceLoseContext();
1416 return false;
1419 if (!mOptions.preserveDrawingBuffer) {
1420 mBackbufferNeedsClear = true;
1423 mShouldPresent = false;
1425 return true;
1428 void
1429 WebGLContext::DummyFramebufferOperation(const char *info)
1431 GLenum status = CheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
1432 if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE)
1433 ErrorInvalidFramebufferOperation("%s: incomplete framebuffer", info);
1436 static bool
1437 CheckContextLost(GLContext* gl, bool* out_isGuilty)
1439 MOZ_ASSERT(gl);
1440 MOZ_ASSERT(out_isGuilty);
1442 bool isEGL = gl->GetContextType() == gl::GLContextType::EGL;
1444 GLenum resetStatus = LOCAL_GL_NO_ERROR;
1445 if (gl->HasRobustness()) {
1446 gl->MakeCurrent();
1447 resetStatus = gl->fGetGraphicsResetStatus();
1448 } else if (isEGL) {
1449 // Simulate a ARB_robustness guilty context loss for when we
1450 // get an EGL_CONTEXT_LOST error. It may not actually be guilty,
1451 // but we can't make any distinction.
1452 if (!gl->MakeCurrent(true) && gl->IsContextLost()) {
1453 resetStatus = LOCAL_GL_UNKNOWN_CONTEXT_RESET_ARB;
1457 if (resetStatus == LOCAL_GL_NO_ERROR) {
1458 *out_isGuilty = false;
1459 return false;
1462 // Assume guilty unless we find otherwise!
1463 bool isGuilty = true;
1464 switch (resetStatus) {
1465 case LOCAL_GL_INNOCENT_CONTEXT_RESET_ARB:
1466 // Either nothing wrong, or not our fault.
1467 isGuilty = false;
1468 break;
1469 case LOCAL_GL_GUILTY_CONTEXT_RESET_ARB:
1470 NS_WARNING("WebGL content on the page definitely caused the graphics"
1471 " card to reset.");
1472 break;
1473 case LOCAL_GL_UNKNOWN_CONTEXT_RESET_ARB:
1474 NS_WARNING("WebGL content on the page might have caused the graphics"
1475 " card to reset");
1476 // If we can't tell, assume guilty.
1477 break;
1478 default:
1479 MOZ_ASSERT(false, "Unreachable.");
1480 // If we do get here, let's pretend to be guilty as an escape plan.
1481 break;
1484 if (isGuilty) {
1485 NS_WARNING("WebGL context on this page is considered guilty, and will"
1486 " not be restored.");
1489 *out_isGuilty = isGuilty;
1490 return true;
1493 bool
1494 WebGLContext::TryToRestoreContext()
1496 if (NS_FAILED(SetDimensions(mWidth, mHeight)))
1497 return false;
1499 return true;
1502 void
1503 WebGLContext::RunContextLossTimer()
1505 mContextLossHandler->RunTimer();
1508 class UpdateContextLossStatusTask : public nsRunnable
1510 nsRefPtr<WebGLContext> mContext;
1512 public:
1513 UpdateContextLossStatusTask(WebGLContext* context)
1514 : mContext(context)
1518 NS_IMETHOD Run() {
1519 mContext->UpdateContextLossStatus();
1521 return NS_OK;
1525 void
1526 WebGLContext::EnqueueUpdateContextLossStatus()
1528 nsCOMPtr<nsIRunnable> task = new UpdateContextLossStatusTask(this);
1529 NS_DispatchToCurrentThread(task);
1532 // We use this timer for many things. Here are the things that it is activated for:
1533 // 1) If a script is using the MOZ_WEBGL_lose_context extension.
1534 // 2) If we are using EGL and _NOT ANGLE_, we query periodically to see if the
1535 // CONTEXT_LOST_WEBGL error has been triggered.
1536 // 3) If we are using ANGLE, or anything that supports ARB_robustness, query the
1537 // GPU periodically to see if the reset status bit has been set.
1538 // In all of these situations, we use this timer to send the script context lost
1539 // and restored events asynchronously. For example, if it triggers a context loss,
1540 // the webglcontextlost event will be sent to it the next time the robustness timer
1541 // fires.
1542 // Note that this timer mechanism is not used unless one of these 3 criteria
1543 // are met.
1544 // At a bare minimum, from context lost to context restores, it would take 3
1545 // full timer iterations: detection, webglcontextlost, webglcontextrestored.
1546 void
1547 WebGLContext::UpdateContextLossStatus()
1549 if (!mCanvasElement) {
1550 // the canvas is gone. That happens when the page was closed before we got
1551 // this timer event. In this case, there's nothing to do here, just don't crash.
1552 return;
1554 if (mContextStatus == ContextNotLost) {
1555 // We don't know that we're lost, but we might be, so we need to
1556 // check. If we're guilty, don't allow restores, though.
1558 bool isGuilty = true;
1559 MOZ_ASSERT(gl); // Shouldn't be missing gl if we're NotLost.
1560 bool isContextLost = CheckContextLost(gl, &isGuilty);
1562 if (isContextLost) {
1563 if (isGuilty)
1564 mAllowContextRestore = false;
1566 ForceLoseContext();
1569 // Fall through.
1572 if (mContextStatus == ContextLostAwaitingEvent) {
1573 // The context has been lost and we haven't yet triggered the
1574 // callback, so do that now.
1576 bool useDefaultHandler;
1577 nsContentUtils::DispatchTrustedEvent(mCanvasElement->OwnerDoc(),
1578 static_cast<nsIDOMHTMLCanvasElement*>(mCanvasElement),
1579 NS_LITERAL_STRING("webglcontextlost"),
1580 true,
1581 true,
1582 &useDefaultHandler);
1583 // We sent the callback, so we're just 'regular lost' now.
1584 mContextStatus = ContextLost;
1585 // If we're told to use the default handler, it means the script
1586 // didn't bother to handle the event. In this case, we shouldn't
1587 // auto-restore the context.
1588 if (useDefaultHandler)
1589 mAllowContextRestore = false;
1591 // Fall through.
1594 if (mContextStatus == ContextLost) {
1595 // Context is lost, and we've already sent the callback. We
1596 // should try to restore the context if we're both allowed to,
1597 // and supposed to.
1599 // Are we allowed to restore the context?
1600 if (!mAllowContextRestore)
1601 return;
1603 // If we're only simulated-lost, we shouldn't auto-restore, and
1604 // instead we should wait for restoreContext() to be called.
1605 if (mLastLossWasSimulated)
1606 return;
1608 // Restore when the app is visible
1609 if (mRestoreWhenVisible)
1610 return;
1612 ForceRestoreContext();
1613 return;
1616 if (mContextStatus == ContextLostAwaitingRestore) {
1617 // Context is lost, but we should try to restore it.
1619 if (!mAllowContextRestore) {
1620 // We might decide this after thinking we'd be OK restoring
1621 // the context, so downgrade.
1622 mContextStatus = ContextLost;
1623 return;
1626 if (!TryToRestoreContext()) {
1627 // Failed to restore. Try again later.
1628 mContextLossHandler->RunTimer();
1629 return;
1632 // Revival!
1633 mContextStatus = ContextNotLost;
1634 nsContentUtils::DispatchTrustedEvent(mCanvasElement->OwnerDoc(),
1635 static_cast<nsIDOMHTMLCanvasElement*>(mCanvasElement),
1636 NS_LITERAL_STRING("webglcontextrestored"),
1637 true,
1638 true);
1639 mEmitContextLostErrorOnce = true;
1640 return;
1644 void
1645 WebGLContext::ForceLoseContext(bool simulateLosing)
1647 printf_stderr("WebGL(%p)::ForceLoseContext\n", this);
1648 MOZ_ASSERT(!IsContextLost());
1649 mContextStatus = ContextLostAwaitingEvent;
1650 mContextLostErrorSet = false;
1652 // Burn it all!
1653 DestroyResourcesAndContext();
1654 mLastLossWasSimulated = simulateLosing;
1656 // Register visibility change observer to defer the context restoring.
1657 // Restore the context when the app is visible.
1658 if (mRestoreWhenVisible && !mLastLossWasSimulated) {
1659 mContextObserver->RegisterVisibilityChangeEvent();
1662 // Queue up a task, since we know the status changed.
1663 EnqueueUpdateContextLossStatus();
1666 void
1667 WebGLContext::ForceRestoreContext()
1669 printf_stderr("WebGL(%p)::ForceRestoreContext\n", this);
1670 mContextStatus = ContextLostAwaitingRestore;
1671 mAllowContextRestore = true; // Hey, you did say 'force'.
1673 mContextObserver->UnregisterVisibilityChangeEvent();
1675 // Queue up a task, since we know the status changed.
1676 EnqueueUpdateContextLossStatus();
1679 void
1680 WebGLContext::MakeContextCurrent() const { gl->MakeCurrent(); }
1682 mozilla::TemporaryRef<mozilla::gfx::SourceSurface>
1683 WebGLContext::GetSurfaceSnapshot(bool* aPremultAlpha)
1685 if (!gl)
1686 return nullptr;
1688 bool hasAlpha = mOptions.alpha;
1689 SurfaceFormat surfFormat = hasAlpha ? SurfaceFormat::B8G8R8A8
1690 : SurfaceFormat::B8G8R8X8;
1691 RefPtr<DataSourceSurface> surf;
1692 surf = Factory::CreateDataSourceSurfaceWithStride(IntSize(mWidth, mHeight),
1693 surfFormat,
1694 mWidth * 4);
1695 if (NS_WARN_IF(!surf)) {
1696 return nullptr;
1699 gl->MakeCurrent();
1701 ScopedBindFramebuffer autoFB(gl, 0);
1702 ClearBackbufferIfNeeded();
1703 ReadPixelsIntoDataSurface(gl, surf);
1706 if (aPremultAlpha) {
1707 *aPremultAlpha = true;
1709 bool srcPremultAlpha = mOptions.premultipliedAlpha;
1710 if (!srcPremultAlpha) {
1711 if (aPremultAlpha) {
1712 *aPremultAlpha = false;
1713 } else {
1714 gfxUtils::PremultiplyDataSurface(surf, surf);
1718 RefPtr<DrawTarget> dt =
1719 Factory::CreateDrawTarget(BackendType::CAIRO,
1720 IntSize(mWidth, mHeight),
1721 SurfaceFormat::B8G8R8A8);
1723 if (!dt) {
1724 return nullptr;
1727 Matrix m;
1728 m.Translate(0.0, mHeight);
1729 m.Scale(1.0, -1.0);
1730 dt->SetTransform(m);
1732 dt->DrawSurface(surf,
1733 Rect(0, 0, mWidth, mHeight),
1734 Rect(0, 0, mWidth, mHeight),
1735 DrawSurfaceOptions(),
1736 DrawOptions(1.0f, CompositionOp::OP_SOURCE));
1738 return dt->Snapshot();
1741 bool WebGLContext::TexImageFromVideoElement(GLenum target, GLint level,
1742 GLenum internalformat, GLenum format, GLenum type,
1743 mozilla::dom::Element& elt)
1745 HTMLVideoElement* video = HTMLVideoElement::FromContentOrNull(&elt);
1746 if (!video) {
1747 return false;
1750 uint16_t readyState;
1751 if (NS_SUCCEEDED(video->GetReadyState(&readyState)) &&
1752 readyState < nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA)
1754 //No frame inside, just return
1755 return false;
1758 // If it doesn't have a principal, just bail
1759 nsCOMPtr<nsIPrincipal> principal = video->GetCurrentPrincipal();
1760 if (!principal) {
1761 return false;
1764 mozilla::layers::ImageContainer* container = video->GetImageContainer();
1765 if (!container) {
1766 return false;
1769 if (video->GetCORSMode() == CORS_NONE) {
1770 bool subsumes;
1771 nsresult rv = mCanvasElement->NodePrincipal()->Subsumes(principal, &subsumes);
1772 if (NS_FAILED(rv) || !subsumes) {
1773 GenerateWarning("It is forbidden to load a WebGL texture from a cross-domain element that has not been validated with CORS. "
1774 "See https://developer.mozilla.org/en/WebGL/Cross-Domain_Textures");
1775 return false;
1779 gl->MakeCurrent();
1780 nsRefPtr<mozilla::layers::Image> srcImage = container->LockCurrentImage();
1781 WebGLTexture* tex = activeBoundTextureForTarget(target);
1783 const WebGLTexture::ImageInfo& info = tex->ImageInfoAt(target, 0);
1784 bool dimensionsMatch = info.Width() == srcImage->GetSize().width &&
1785 info.Height() == srcImage->GetSize().height;
1786 if (!dimensionsMatch) {
1787 // we need to allocation
1788 gl->fTexImage2D(target, level, internalformat, srcImage->GetSize().width, srcImage->GetSize().height, 0, format, type, nullptr);
1790 bool ok = gl->BlitHelper()->BlitImageToTexture(srcImage.get(), srcImage->GetSize(), tex->GLName(), target, mPixelStoreFlipY);
1791 if (ok) {
1792 tex->SetImageInfo(target, level, srcImage->GetSize().width, srcImage->GetSize().height, format, type, WebGLImageDataStatus::InitializedImageData);
1793 tex->Bind(target);
1795 srcImage = nullptr;
1796 container->UnlockCurrentImage();
1797 return ok;
1801 // XPCOM goop
1804 NS_IMPL_CYCLE_COLLECTING_ADDREF(WebGLContext)
1805 NS_IMPL_CYCLE_COLLECTING_RELEASE(WebGLContext)
1807 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLContext,
1808 mCanvasElement,
1809 mExtensions,
1810 mBound2DTextures,
1811 mBoundCubeMapTextures,
1812 mBoundArrayBuffer,
1813 mBoundTransformFeedbackBuffer,
1814 mCurrentProgram,
1815 mBoundFramebuffer,
1816 mBoundRenderbuffer,
1817 mBoundVertexArray,
1818 mDefaultVertexArray,
1819 mActiveOcclusionQuery,
1820 mActiveTransformFeedbackQuery)
1822 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebGLContext)
1823 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
1824 NS_INTERFACE_MAP_ENTRY(nsIDOMWebGLRenderingContext)
1825 NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal)
1826 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
1827 // If the exact way we cast to nsISupports here ever changes, fix our
1828 // ToSupports() method.
1829 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMWebGLRenderingContext)
1830 NS_INTERFACE_MAP_END