michael@0: /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "WebGLContext.h" michael@0: #include "WebGL1Context.h" michael@0: #include "WebGLObjectModel.h" michael@0: #include "WebGLExtensions.h" michael@0: #include "WebGLContextUtils.h" michael@0: #include "WebGLBuffer.h" michael@0: #include "WebGLVertexAttribData.h" michael@0: #include "WebGLMemoryTracker.h" michael@0: #include "WebGLFramebuffer.h" michael@0: #include "WebGLVertexArray.h" michael@0: #include "WebGLQuery.h" michael@0: michael@0: #include "AccessCheck.h" michael@0: #include "nsIConsoleService.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsIClassInfoImpl.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsIXPConnect.h" michael@0: #include "nsError.h" michael@0: #include "nsIGfxInfo.h" michael@0: #include "nsIWidget.h" michael@0: michael@0: #include "nsIVariant.h" michael@0: michael@0: #include "ImageEncoder.h" michael@0: michael@0: #include "gfxContext.h" michael@0: #include "gfxPattern.h" michael@0: #include "gfxUtils.h" michael@0: michael@0: #include "CanvasUtils.h" michael@0: #include "nsDisplayList.h" michael@0: michael@0: #include "GLContextProvider.h" michael@0: #include "GLContext.h" michael@0: #include "ScopedGLHelpers.h" michael@0: #include "GLReadTexImageHelper.h" michael@0: michael@0: #include "gfxCrashReporterUtils.h" michael@0: michael@0: #include "nsSVGEffects.h" michael@0: michael@0: #include "prenv.h" michael@0: michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/Services.h" michael@0: #include "mozilla/Telemetry.h" michael@0: michael@0: #include "nsIObserverService.h" michael@0: #include "mozilla/Services.h" michael@0: #include "mozilla/dom/WebGLRenderingContextBinding.h" michael@0: #include "mozilla/dom/BindingUtils.h" michael@0: #include "mozilla/dom/ImageData.h" michael@0: #include "mozilla/ProcessPriorityManager.h" michael@0: #include "mozilla/EnumeratedArrayCycleCollection.h" michael@0: michael@0: #include "Layers.h" michael@0: michael@0: #ifdef MOZ_WIDGET_GONK michael@0: #include "mozilla/layers/ShadowLayers.h" michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: using namespace mozilla::gfx; michael@0: using namespace mozilla::gl; michael@0: using namespace mozilla::layers; michael@0: michael@0: NS_IMETHODIMP michael@0: WebGLMemoryPressureObserver::Observe(nsISupports* aSubject, michael@0: const char* aTopic, michael@0: const char16_t* aSomeData) michael@0: { michael@0: if (strcmp(aTopic, "memory-pressure")) michael@0: return NS_OK; michael@0: michael@0: bool wantToLoseContext = true; michael@0: michael@0: if (!mContext->mCanLoseContextInForeground && michael@0: ProcessPriorityManager::CurrentProcessIsForeground()) michael@0: wantToLoseContext = false; michael@0: else if (!nsCRT::strcmp(aSomeData, michael@0: MOZ_UTF16("heap-minimize"))) michael@0: wantToLoseContext = mContext->mLoseContextOnHeapMinimize; michael@0: michael@0: if (wantToLoseContext) michael@0: mContext->ForceLoseContext(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: WebGLContextOptions::WebGLContextOptions() michael@0: : alpha(true), depth(true), stencil(false), michael@0: premultipliedAlpha(true), antialias(true), michael@0: preserveDrawingBuffer(false) michael@0: { michael@0: // Set default alpha state based on preference. michael@0: if (Preferences::GetBool("webgl.default-no-alpha", false)) michael@0: alpha = false; michael@0: } michael@0: michael@0: WebGLContext::WebGLContext() michael@0: : gl(nullptr) michael@0: { michael@0: SetIsDOMBinding(); michael@0: michael@0: mGeneration = 0; michael@0: mInvalidated = false; michael@0: mShouldPresent = true; michael@0: mResetLayer = true; michael@0: mOptionsFrozen = false; michael@0: michael@0: mActiveTexture = 0; michael@0: mPixelStoreFlipY = false; michael@0: mPixelStorePremultiplyAlpha = false; michael@0: mPixelStoreColorspaceConversion = BROWSER_DEFAULT_WEBGL; michael@0: michael@0: mShaderValidation = true; michael@0: michael@0: mFakeBlackStatus = WebGLContextFakeBlackStatus::NotNeeded; michael@0: michael@0: mVertexAttrib0Vector[0] = 0; michael@0: mVertexAttrib0Vector[1] = 0; michael@0: mVertexAttrib0Vector[2] = 0; michael@0: mVertexAttrib0Vector[3] = 1; michael@0: mFakeVertexAttrib0BufferObjectVector[0] = 0; michael@0: mFakeVertexAttrib0BufferObjectVector[1] = 0; michael@0: mFakeVertexAttrib0BufferObjectVector[2] = 0; michael@0: mFakeVertexAttrib0BufferObjectVector[3] = 1; michael@0: mFakeVertexAttrib0BufferObjectSize = 0; michael@0: mFakeVertexAttrib0BufferObject = 0; michael@0: mFakeVertexAttrib0BufferStatus = WebGLVertexAttrib0Status::Default; michael@0: michael@0: mViewportX = 0; michael@0: mViewportY = 0; michael@0: mViewportWidth = 0; michael@0: mViewportHeight = 0; michael@0: michael@0: mScissorTestEnabled = 0; michael@0: mDitherEnabled = 1; michael@0: mRasterizerDiscardEnabled = 0; // OpenGL ES 3.0 spec p244 michael@0: michael@0: // initialize some GL values: we're going to get them from the GL and use them as the sizes of arrays, michael@0: // so in case glGetIntegerv leaves them uninitialized because of a GL bug, we would have very weird crashes. michael@0: mGLMaxVertexAttribs = 0; michael@0: mGLMaxTextureUnits = 0; michael@0: mGLMaxTextureSize = 0; michael@0: mGLMaxCubeMapTextureSize = 0; michael@0: mGLMaxRenderbufferSize = 0; michael@0: mGLMaxTextureImageUnits = 0; michael@0: mGLMaxVertexTextureImageUnits = 0; michael@0: mGLMaxVaryingVectors = 0; michael@0: mGLMaxFragmentUniformVectors = 0; michael@0: mGLMaxVertexUniformVectors = 0; michael@0: mGLMaxColorAttachments = 1; michael@0: mGLMaxDrawBuffers = 1; michael@0: mGLMaxTransformFeedbackSeparateAttribs = 0; michael@0: michael@0: // See OpenGL ES 2.0.25 spec, 6.2 State Tables, table 6.13 michael@0: mPixelStorePackAlignment = 4; michael@0: mPixelStoreUnpackAlignment = 4; michael@0: michael@0: WebGLMemoryTracker::AddWebGLContext(this); michael@0: michael@0: mAllowRestore = true; michael@0: mContextLossTimerRunning = false; michael@0: mDrawSinceContextLossTimerSet = false; michael@0: mContextRestorer = do_CreateInstance("@mozilla.org/timer;1"); michael@0: mContextStatus = ContextNotLost; michael@0: mContextLostErrorSet = false; michael@0: mLoseContextOnHeapMinimize = false; michael@0: mCanLoseContextInForeground = true; michael@0: michael@0: mAlreadyGeneratedWarnings = 0; michael@0: mAlreadyWarnedAboutFakeVertexAttrib0 = false; michael@0: mAlreadyWarnedAboutViewportLargerThanDest = false; michael@0: mMaxWarnings = Preferences::GetInt("webgl.max-warnings-per-context", 32); michael@0: if (mMaxWarnings < -1) michael@0: { michael@0: GenerateWarning("webgl.max-warnings-per-context size is too large (seems like a negative value wrapped)"); michael@0: mMaxWarnings = 0; michael@0: } michael@0: michael@0: mLastUseIndex = 0; michael@0: michael@0: InvalidateBufferFetching(); michael@0: michael@0: mBackbufferNeedsClear = true; michael@0: michael@0: mDisableFragHighP = false; michael@0: michael@0: mDrawCallsSinceLastFlush = 0; michael@0: } michael@0: michael@0: WebGLContext::~WebGLContext() michael@0: { michael@0: DestroyResourcesAndContext(); michael@0: WebGLMemoryTracker::RemoveWebGLContext(this); michael@0: TerminateContextLossTimer(); michael@0: mContextRestorer = nullptr; michael@0: } michael@0: michael@0: void michael@0: WebGLContext::DestroyResourcesAndContext() michael@0: { michael@0: if (mMemoryPressureObserver) { michael@0: nsCOMPtr observerService michael@0: = mozilla::services::GetObserverService(); michael@0: if (observerService) { michael@0: observerService->RemoveObserver(mMemoryPressureObserver, michael@0: "memory-pressure"); michael@0: } michael@0: mMemoryPressureObserver = nullptr; michael@0: } michael@0: michael@0: if (!gl) michael@0: return; michael@0: michael@0: gl->MakeCurrent(); michael@0: michael@0: mBound2DTextures.Clear(); michael@0: mBoundCubeMapTextures.Clear(); michael@0: mBoundArrayBuffer = nullptr; michael@0: mBoundTransformFeedbackBuffer = nullptr; michael@0: mCurrentProgram = nullptr; michael@0: mBoundFramebuffer = nullptr; michael@0: mActiveOcclusionQuery = nullptr; michael@0: mBoundRenderbuffer = nullptr; michael@0: mBoundVertexArray = nullptr; michael@0: mDefaultVertexArray = nullptr; michael@0: michael@0: while (!mTextures.isEmpty()) michael@0: mTextures.getLast()->DeleteOnce(); michael@0: while (!mVertexArrays.isEmpty()) michael@0: mVertexArrays.getLast()->DeleteOnce(); michael@0: while (!mBuffers.isEmpty()) michael@0: mBuffers.getLast()->DeleteOnce(); michael@0: while (!mRenderbuffers.isEmpty()) michael@0: mRenderbuffers.getLast()->DeleteOnce(); michael@0: while (!mFramebuffers.isEmpty()) michael@0: mFramebuffers.getLast()->DeleteOnce(); michael@0: while (!mShaders.isEmpty()) michael@0: mShaders.getLast()->DeleteOnce(); michael@0: while (!mPrograms.isEmpty()) michael@0: mPrograms.getLast()->DeleteOnce(); michael@0: while (!mQueries.isEmpty()) michael@0: mQueries.getLast()->DeleteOnce(); michael@0: michael@0: mBlackOpaqueTexture2D = nullptr; michael@0: mBlackOpaqueTextureCubeMap = nullptr; michael@0: mBlackTransparentTexture2D = nullptr; michael@0: mBlackTransparentTextureCubeMap = nullptr; michael@0: michael@0: if (mFakeVertexAttrib0BufferObject) { michael@0: gl->fDeleteBuffers(1, &mFakeVertexAttrib0BufferObject); michael@0: } michael@0: michael@0: // disable all extensions except "WEBGL_lose_context". see bug #927969 michael@0: // spec: http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2 michael@0: for (size_t i = 0; i < size_t(WebGLExtensionID::Max); ++i) { michael@0: WebGLExtensionID extension = WebGLExtensionID(i); michael@0: michael@0: if (!IsExtensionEnabled(extension) || (extension == WebGLExtensionID::WEBGL_lose_context)) michael@0: continue; michael@0: michael@0: mExtensions[extension]->MarkLost(); michael@0: mExtensions[extension] = nullptr; michael@0: } michael@0: michael@0: // We just got rid of everything, so the context had better michael@0: // have been going away. michael@0: #ifdef DEBUG michael@0: if (gl->DebugMode()) { michael@0: printf_stderr("--- WebGL context destroyed: %p\n", gl.get()); michael@0: } michael@0: #endif michael@0: michael@0: gl = nullptr; michael@0: } michael@0: michael@0: void michael@0: WebGLContext::Invalidate() michael@0: { michael@0: if (mInvalidated) michael@0: return; michael@0: michael@0: if (!mCanvasElement) michael@0: return; michael@0: michael@0: nsSVGEffects::InvalidateDirectRenderingObservers(mCanvasElement); michael@0: michael@0: mInvalidated = true; michael@0: mCanvasElement->InvalidateCanvasContent(nullptr); michael@0: } michael@0: michael@0: // michael@0: // nsICanvasRenderingContextInternal michael@0: // michael@0: michael@0: NS_IMETHODIMP michael@0: WebGLContext::SetContextOptions(JSContext* aCx, JS::Handle aOptions) michael@0: { michael@0: if (aOptions.isNullOrUndefined() && mOptionsFrozen) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: WebGLContextAttributes attributes; michael@0: NS_ENSURE_TRUE(attributes.Init(aCx, aOptions), NS_ERROR_UNEXPECTED); michael@0: michael@0: WebGLContextOptions newOpts; michael@0: michael@0: newOpts.stencil = attributes.mStencil; michael@0: newOpts.depth = attributes.mDepth; michael@0: newOpts.premultipliedAlpha = attributes.mPremultipliedAlpha; michael@0: newOpts.antialias = attributes.mAntialias; michael@0: newOpts.preserveDrawingBuffer = attributes.mPreserveDrawingBuffer; michael@0: if (attributes.mAlpha.WasPassed()) { michael@0: newOpts.alpha = attributes.mAlpha.Value(); michael@0: } michael@0: michael@0: // enforce that if stencil is specified, we also give back depth michael@0: newOpts.depth |= newOpts.stencil; michael@0: michael@0: #if 0 michael@0: GenerateWarning("aaHint: %d stencil: %d depth: %d alpha: %d premult: %d preserve: %d\n", michael@0: newOpts.antialias ? 1 : 0, michael@0: newOpts.stencil ? 1 : 0, michael@0: newOpts.depth ? 1 : 0, michael@0: newOpts.alpha ? 1 : 0, michael@0: newOpts.premultipliedAlpha ? 1 : 0, michael@0: newOpts.preserveDrawingBuffer ? 1 : 0); michael@0: #endif michael@0: michael@0: if (mOptionsFrozen && newOpts != mOptions) { michael@0: // Error if the options are already frozen, and the ones that were asked for michael@0: // aren't the same as what they were originally. michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: mOptions = newOpts; michael@0: return NS_OK; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: int32_t michael@0: WebGLContext::GetWidth() const michael@0: { michael@0: return mWidth; michael@0: } michael@0: michael@0: int32_t michael@0: WebGLContext::GetHeight() const michael@0: { michael@0: return mHeight; michael@0: } michael@0: #endif michael@0: michael@0: NS_IMETHODIMP michael@0: WebGLContext::SetDimensions(int32_t width, int32_t height) michael@0: { michael@0: // Early error return cases michael@0: michael@0: if (width < 0 || height < 0) { michael@0: GenerateWarning("Canvas size is too large (seems like a negative value wrapped)"); michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: if (!GetCanvas()) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Early success return cases michael@0: michael@0: GetCanvas()->InvalidateCanvas(); michael@0: michael@0: if (gl && mWidth == width && mHeight == height) michael@0: return NS_OK; michael@0: michael@0: // Zero-sized surfaces can cause problems. michael@0: if (width == 0) { michael@0: width = 1; michael@0: } michael@0: if (height == 0) { michael@0: height = 1; michael@0: } michael@0: michael@0: // If we already have a gl context, then we just need to resize it michael@0: if (gl) { michael@0: MakeContextCurrent(); michael@0: michael@0: // If we've already drawn, we should commit the current buffer. michael@0: PresentScreenBuffer(); michael@0: michael@0: // ResizeOffscreen scraps the current prod buffer before making a new one. michael@0: gl->ResizeOffscreen(gfx::IntSize(width, height)); // Doesn't matter if it succeeds (soft-fail) michael@0: // It's unlikely that we'll get a proper-sized context if we recreate if we didn't on resize michael@0: michael@0: // everything's good, we're done here michael@0: mWidth = gl->OffscreenSize().width; michael@0: mHeight = gl->OffscreenSize().height; michael@0: mResetLayer = true; michael@0: michael@0: mBackbufferNeedsClear = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // End of early return cases. michael@0: // At this point we know that we're not just resizing an existing context, michael@0: // we are initializing a new context. michael@0: michael@0: // if we exceeded either the global or the per-principal limit for WebGL contexts, michael@0: // lose the oldest-used context now to free resources. Note that we can't do that michael@0: // in the WebGLContext constructor as we don't have a canvas element yet there. michael@0: // Here is the right place to do so, as we are about to create the OpenGL context michael@0: // and that is what can fail if we already have too many. michael@0: LoseOldestWebGLContextIfLimitExceeded(); michael@0: michael@0: // Get some prefs for some preferred/overriden things michael@0: NS_ENSURE_TRUE(Preferences::GetRootBranch(), NS_ERROR_FAILURE); michael@0: michael@0: #ifdef XP_WIN michael@0: bool preferEGL = michael@0: Preferences::GetBool("webgl.prefer-egl", false); michael@0: bool preferOpenGL = michael@0: Preferences::GetBool("webgl.prefer-native-gl", false); michael@0: #endif michael@0: bool forceEnabled = michael@0: Preferences::GetBool("webgl.force-enabled", false); michael@0: bool disabled = michael@0: Preferences::GetBool("webgl.disabled", false); michael@0: bool prefer16bit = michael@0: Preferences::GetBool("webgl.prefer-16bpp", false); michael@0: michael@0: ScopedGfxFeatureReporter reporter("WebGL", forceEnabled); michael@0: michael@0: if (disabled) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // We're going to create an entirely new context. If our michael@0: // generation is not 0 right now (that is, if this isn't the first michael@0: // context we're creating), we may have to dispatch a context lost michael@0: // event. michael@0: michael@0: // If incrementing the generation would cause overflow, michael@0: // don't allow it. Allowing this would allow us to use michael@0: // resource handles created from older context generations. michael@0: if (!(mGeneration + 1).isValid()) michael@0: return NS_ERROR_FAILURE; // exit without changing the value of mGeneration michael@0: michael@0: SurfaceCaps caps; michael@0: michael@0: caps.color = true; michael@0: caps.alpha = mOptions.alpha; michael@0: caps.depth = mOptions.depth; michael@0: caps.stencil = mOptions.stencil; michael@0: michael@0: // we should really have this behind a michael@0: // |gfxPlatform::GetPlatform()->GetScreenDepth() == 16| check, but michael@0: // for now it's just behind a pref for testing/evaluation. michael@0: caps.bpp16 = prefer16bit; michael@0: michael@0: caps.preserve = mOptions.preserveDrawingBuffer; michael@0: michael@0: #ifdef MOZ_WIDGET_GONK michael@0: nsIWidget *docWidget = nsContentUtils::WidgetForDocument(mCanvasElement->OwnerDoc()); michael@0: if (docWidget) { michael@0: layers::LayerManager *layerManager = docWidget->GetLayerManager(); michael@0: if (layerManager) { michael@0: // XXX we really want "AsSurfaceAllocator" here for generality michael@0: layers::ShadowLayerForwarder *forwarder = layerManager->AsShadowForwarder(); michael@0: if (forwarder) { michael@0: caps.surfaceAllocator = static_cast(forwarder); michael@0: } michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: bool forceMSAA = michael@0: Preferences::GetBool("webgl.msaa-force", false); michael@0: michael@0: int32_t status; michael@0: nsCOMPtr gfxInfo = do_GetService("@mozilla.org/gfx/info;1"); michael@0: if (mOptions.antialias && michael@0: gfxInfo && michael@0: NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_WEBGL_MSAA, &status))) { michael@0: if (status == nsIGfxInfo::FEATURE_NO_INFO || forceMSAA) { michael@0: caps.antialias = true; michael@0: } michael@0: } michael@0: michael@0: #ifdef XP_WIN michael@0: if (PR_GetEnv("MOZ_WEBGL_PREFER_EGL")) { michael@0: preferEGL = true; michael@0: } michael@0: #endif michael@0: michael@0: // Ask GfxInfo about what we should use michael@0: bool useOpenGL = true; michael@0: michael@0: #ifdef XP_WIN michael@0: bool useANGLE = true; michael@0: #endif michael@0: michael@0: if (gfxInfo && !forceEnabled) { michael@0: if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_WEBGL_OPENGL, &status))) { michael@0: if (status != nsIGfxInfo::FEATURE_NO_INFO) { michael@0: useOpenGL = false; michael@0: } michael@0: } michael@0: #ifdef XP_WIN michael@0: if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_WEBGL_ANGLE, &status))) { michael@0: if (status != nsIGfxInfo::FEATURE_NO_INFO) { michael@0: useANGLE = false; michael@0: } michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: #ifdef XP_WIN michael@0: // allow forcing GL and not EGL/ANGLE michael@0: if (PR_GetEnv("MOZ_WEBGL_FORCE_OPENGL")) { michael@0: preferEGL = false; michael@0: useANGLE = false; michael@0: useOpenGL = true; michael@0: } michael@0: #endif michael@0: michael@0: gfxIntSize size(width, height); michael@0: michael@0: #ifdef XP_WIN michael@0: // if we want EGL, try it now michael@0: if (!gl && (preferEGL || useANGLE) && !preferOpenGL) { michael@0: gl = gl::GLContextProviderEGL::CreateOffscreen(size, caps); michael@0: if (!gl || !InitAndValidateGL()) { michael@0: GenerateWarning("Error during ANGLE OpenGL ES initialization"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: // try the default provider, whatever that is michael@0: if (!gl && useOpenGL) { michael@0: gl = gl::GLContextProvider::CreateOffscreen(size, caps); michael@0: if (gl && !InitAndValidateGL()) { michael@0: GenerateWarning("Error during OpenGL initialization"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: michael@0: if (!gl) { michael@0: GenerateWarning("Can't get a usable WebGL context"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: if (gl->DebugMode()) { michael@0: printf_stderr("--- WebGL context created: %p\n", gl.get()); michael@0: } michael@0: #endif michael@0: michael@0: mWidth = width; michael@0: mHeight = height; michael@0: mViewportWidth = width; michael@0: mViewportHeight = height; michael@0: mResetLayer = true; michael@0: mOptionsFrozen = true; michael@0: michael@0: mHasRobustness = gl->HasRobustness(); michael@0: michael@0: // increment the generation number michael@0: ++mGeneration; michael@0: michael@0: #if 0 michael@0: if (mGeneration > 0) { michael@0: // XXX dispatch context lost event michael@0: } michael@0: #endif michael@0: michael@0: MakeContextCurrent(); michael@0: michael@0: // Make sure that we clear this out, otherwise michael@0: // we'll end up displaying random memory michael@0: gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0); michael@0: michael@0: gl->fClearColor(0.0f, 0.0f, 0.0f, 0.0f); michael@0: gl->fClearDepth(1.0f); michael@0: gl->fClearStencil(0); michael@0: michael@0: mBackbufferNeedsClear = true; michael@0: michael@0: // Clear immediately, because we need to present the cleared initial michael@0: // buffer. michael@0: ClearBackbufferIfNeeded(); michael@0: michael@0: mShouldPresent = true; michael@0: michael@0: MOZ_ASSERT(gl->Caps().color == caps.color); michael@0: MOZ_ASSERT(gl->Caps().alpha == caps.alpha); michael@0: MOZ_ASSERT(gl->Caps().depth == caps.depth || !gl->Caps().depth); michael@0: MOZ_ASSERT(gl->Caps().stencil == caps.stencil || !gl->Caps().stencil); michael@0: MOZ_ASSERT(gl->Caps().antialias == caps.antialias || !gl->Caps().antialias); michael@0: MOZ_ASSERT(gl->Caps().preserve == caps.preserve); michael@0: michael@0: reporter.SetSuccessful(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: WebGLContext::ClearBackbufferIfNeeded() michael@0: { michael@0: if (!mBackbufferNeedsClear) michael@0: return; michael@0: michael@0: #ifdef DEBUG michael@0: gl->MakeCurrent(); michael@0: michael@0: GLuint fb = 0; michael@0: gl->GetUIntegerv(LOCAL_GL_FRAMEBUFFER_BINDING, &fb); michael@0: MOZ_ASSERT(fb == 0); michael@0: #endif michael@0: michael@0: ClearScreen(); michael@0: michael@0: mBackbufferNeedsClear = false; michael@0: } michael@0: michael@0: void WebGLContext::LoseOldestWebGLContextIfLimitExceeded() michael@0: { michael@0: #ifdef MOZ_GFX_OPTIMIZE_MOBILE michael@0: // some mobile devices can't have more than 8 GL contexts overall michael@0: const size_t kMaxWebGLContextsPerPrincipal = 2; michael@0: const size_t kMaxWebGLContexts = 4; michael@0: #else michael@0: const size_t kMaxWebGLContextsPerPrincipal = 16; michael@0: const size_t kMaxWebGLContexts = 32; michael@0: #endif michael@0: MOZ_ASSERT(kMaxWebGLContextsPerPrincipal < kMaxWebGLContexts); michael@0: michael@0: // it's important to update the index on a new context before losing old contexts, michael@0: // otherwise new unused contexts would all have index 0 and we couldn't distinguish older ones michael@0: // when choosing which one to lose first. michael@0: UpdateLastUseIndex(); michael@0: michael@0: WebGLMemoryTracker::ContextsArrayType &contexts michael@0: = WebGLMemoryTracker::Contexts(); michael@0: michael@0: // quick exit path, should cover a majority of cases michael@0: if (contexts.Length() <= kMaxWebGLContextsPerPrincipal) { michael@0: return; michael@0: } michael@0: michael@0: // note that here by "context" we mean "non-lost context". See the check for michael@0: // IsContextLost() below. Indeed, the point of this function is to maybe lose michael@0: // some currently non-lost context. michael@0: michael@0: uint64_t oldestIndex = UINT64_MAX; michael@0: uint64_t oldestIndexThisPrincipal = UINT64_MAX; michael@0: const WebGLContext *oldestContext = nullptr; michael@0: const WebGLContext *oldestContextThisPrincipal = nullptr; michael@0: size_t numContexts = 0; michael@0: size_t numContextsThisPrincipal = 0; michael@0: michael@0: for(size_t i = 0; i < contexts.Length(); ++i) { michael@0: michael@0: // don't want to lose ourselves. michael@0: if (contexts[i] == this) michael@0: continue; michael@0: michael@0: if (contexts[i]->IsContextLost()) michael@0: continue; michael@0: michael@0: if (!contexts[i]->GetCanvas()) { michael@0: // Zombie context: the canvas is already destroyed, but something else michael@0: // (typically the compositor) is still holding on to the context. michael@0: // Killing zombies is a no-brainer. michael@0: const_cast(contexts[i])->LoseContext(); michael@0: continue; michael@0: } michael@0: michael@0: numContexts++; michael@0: if (contexts[i]->mLastUseIndex < oldestIndex) { michael@0: oldestIndex = contexts[i]->mLastUseIndex; michael@0: oldestContext = contexts[i]; michael@0: } michael@0: michael@0: nsIPrincipal *ourPrincipal = GetCanvas()->NodePrincipal(); michael@0: nsIPrincipal *theirPrincipal = contexts[i]->GetCanvas()->NodePrincipal(); michael@0: bool samePrincipal; michael@0: nsresult rv = ourPrincipal->Equals(theirPrincipal, &samePrincipal); michael@0: if (NS_SUCCEEDED(rv) && samePrincipal) { michael@0: numContextsThisPrincipal++; michael@0: if (contexts[i]->mLastUseIndex < oldestIndexThisPrincipal) { michael@0: oldestIndexThisPrincipal = contexts[i]->mLastUseIndex; michael@0: oldestContextThisPrincipal = contexts[i]; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (numContextsThisPrincipal > kMaxWebGLContextsPerPrincipal) { michael@0: GenerateWarning("Exceeded %d live WebGL contexts for this principal, losing the " michael@0: "least recently used one.", kMaxWebGLContextsPerPrincipal); michael@0: MOZ_ASSERT(oldestContextThisPrincipal); // if we reach this point, this can't be null michael@0: const_cast(oldestContextThisPrincipal)->LoseContext(); michael@0: } else if (numContexts > kMaxWebGLContexts) { michael@0: GenerateWarning("Exceeded %d live WebGL contexts, losing the least recently used one.", michael@0: kMaxWebGLContexts); michael@0: MOZ_ASSERT(oldestContext); // if we reach this point, this can't be null michael@0: const_cast(oldestContext)->LoseContext(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: WebGLContext::GetImageBuffer(uint8_t** aImageBuffer, int32_t* aFormat) michael@0: { michael@0: *aImageBuffer = nullptr; michael@0: *aFormat = 0; michael@0: michael@0: // Use GetSurfaceSnapshot() to make sure that appropriate y-flip gets applied michael@0: bool premult; michael@0: RefPtr snapshot = michael@0: GetSurfaceSnapshot(mOptions.premultipliedAlpha ? nullptr : &premult); michael@0: if (!snapshot) { michael@0: return; michael@0: } michael@0: MOZ_ASSERT(mOptions.premultipliedAlpha || !premult, "We must get unpremult when we ask for it!"); michael@0: michael@0: RefPtr dataSurface = snapshot->GetDataSurface(); michael@0: michael@0: DataSourceSurface::MappedSurface map; michael@0: if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) { michael@0: return; michael@0: } michael@0: michael@0: static const fallible_t fallible = fallible_t(); michael@0: uint8_t* imageBuffer = new (fallible) uint8_t[mWidth * mHeight * 4]; michael@0: if (!imageBuffer) { michael@0: dataSurface->Unmap(); michael@0: return; michael@0: } michael@0: memcpy(imageBuffer, map.mData, mWidth * mHeight * 4); michael@0: michael@0: dataSurface->Unmap(); michael@0: michael@0: int32_t format = imgIEncoder::INPUT_FORMAT_HOSTARGB; michael@0: if (!mOptions.premultipliedAlpha) { michael@0: // We need to convert to INPUT_FORMAT_RGBA, otherwise michael@0: // we are automatically considered premult, and unpremult'd. michael@0: // Yes, it is THAT silly. michael@0: // Except for different lossy conversions by color, michael@0: // we could probably just change the label, and not change the data. michael@0: gfxUtils::ConvertBGRAtoRGBA(imageBuffer, mWidth * mHeight * 4); michael@0: format = imgIEncoder::INPUT_FORMAT_RGBA; michael@0: } michael@0: michael@0: *aImageBuffer = imageBuffer; michael@0: *aFormat = format; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: WebGLContext::GetInputStream(const char* aMimeType, michael@0: const char16_t* aEncoderOptions, michael@0: nsIInputStream **aStream) michael@0: { michael@0: NS_ASSERTION(gl, "GetInputStream on invalid context?"); michael@0: if (!gl) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsCString enccid("@mozilla.org/image/encoder;2?type="); michael@0: enccid += aMimeType; michael@0: nsCOMPtr encoder = do_CreateInstance(enccid.get()); michael@0: if (!encoder) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsAutoArrayPtr imageBuffer; michael@0: int32_t format = 0; michael@0: GetImageBuffer(getter_Transfers(imageBuffer), &format); michael@0: if (!imageBuffer) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return ImageEncoder::GetInputStream(mWidth, mHeight, imageBuffer, format, michael@0: encoder, aEncoderOptions, aStream); michael@0: } michael@0: michael@0: void WebGLContext::UpdateLastUseIndex() michael@0: { michael@0: static CheckedInt sIndex = 0; michael@0: michael@0: sIndex++; michael@0: michael@0: // should never happen with 64-bit; trying to handle this would be riskier than michael@0: // not handling it as the handler code would never get exercised. michael@0: if (!sIndex.isValid()) { michael@0: NS_RUNTIMEABORT("Can't believe it's been 2^64 transactions already!"); michael@0: } michael@0: michael@0: mLastUseIndex = sIndex.value(); michael@0: } michael@0: michael@0: static uint8_t gWebGLLayerUserData; michael@0: michael@0: namespace mozilla { michael@0: michael@0: class WebGLContextUserData : public LayerUserData { michael@0: public: michael@0: WebGLContextUserData(HTMLCanvasElement *aContent) michael@0: : mContent(aContent) michael@0: {} michael@0: michael@0: /* PreTransactionCallback gets called by the Layers code every time the michael@0: * WebGL canvas is going to be composited. michael@0: */ michael@0: static void PreTransactionCallback(void* data) michael@0: { michael@0: WebGLContextUserData* userdata = static_cast(data); michael@0: HTMLCanvasElement* canvas = userdata->mContent; michael@0: WebGLContext* context = static_cast(canvas->GetContextAtIndex(0)); michael@0: michael@0: // Present our screenbuffer, if needed. michael@0: context->PresentScreenBuffer(); michael@0: context->mDrawCallsSinceLastFlush = 0; michael@0: } michael@0: michael@0: /** DidTransactionCallback gets called by the Layers code everytime the WebGL canvas gets composite, michael@0: * so it really is the right place to put actions that have to be performed upon compositing michael@0: */ michael@0: static void DidTransactionCallback(void* aData) michael@0: { michael@0: WebGLContextUserData *userdata = static_cast(aData); michael@0: HTMLCanvasElement *canvas = userdata->mContent; michael@0: WebGLContext *context = static_cast(canvas->GetContextAtIndex(0)); michael@0: michael@0: // Mark ourselves as no longer invalidated. michael@0: context->MarkContextClean(); michael@0: michael@0: context->UpdateLastUseIndex(); michael@0: } michael@0: michael@0: private: michael@0: nsRefPtr mContent; michael@0: }; michael@0: michael@0: } // end namespace mozilla michael@0: michael@0: already_AddRefed michael@0: WebGLContext::GetCanvasLayer(nsDisplayListBuilder* aBuilder, michael@0: CanvasLayer *aOldLayer, michael@0: LayerManager *aManager) michael@0: { michael@0: if (IsContextLost()) michael@0: return nullptr; michael@0: michael@0: if (!mResetLayer && aOldLayer && michael@0: aOldLayer->HasUserData(&gWebGLLayerUserData)) { michael@0: nsRefPtr ret = aOldLayer; michael@0: return ret.forget(); michael@0: } michael@0: michael@0: nsRefPtr canvasLayer = aManager->CreateCanvasLayer(); michael@0: if (!canvasLayer) { michael@0: NS_WARNING("CreateCanvasLayer returned null!"); michael@0: return nullptr; michael@0: } michael@0: WebGLContextUserData *userData = nullptr; michael@0: if (aBuilder->IsPaintingToWindow()) { michael@0: // Make the layer tell us whenever a transaction finishes (including michael@0: // the current transaction), so we can clear our invalidation state and michael@0: // start invalidating again. We need to do this for the layer that is michael@0: // being painted to a window (there shouldn't be more than one at a time, michael@0: // and if there is, flushing the invalidation state more often than michael@0: // necessary is harmless). michael@0: michael@0: // The layer will be destroyed when we tear down the presentation michael@0: // (at the latest), at which time this userData will be destroyed, michael@0: // releasing the reference to the element. michael@0: // The userData will receive DidTransactionCallbacks, which flush the michael@0: // the invalidation state to indicate that the canvas is up to date. michael@0: userData = new WebGLContextUserData(mCanvasElement); michael@0: canvasLayer->SetDidTransactionCallback( michael@0: WebGLContextUserData::DidTransactionCallback, userData); michael@0: canvasLayer->SetPreTransactionCallback( michael@0: WebGLContextUserData::PreTransactionCallback, userData); michael@0: } michael@0: canvasLayer->SetUserData(&gWebGLLayerUserData, userData); michael@0: michael@0: CanvasLayer::Data data; michael@0: data.mGLContext = gl; michael@0: data.mSize = nsIntSize(mWidth, mHeight); michael@0: data.mIsGLAlphaPremult = IsPremultAlpha(); michael@0: michael@0: canvasLayer->Initialize(data); michael@0: uint32_t flags = gl->Caps().alpha ? 0 : Layer::CONTENT_OPAQUE; michael@0: canvasLayer->SetContentFlags(flags); michael@0: canvasLayer->Updated(); michael@0: michael@0: mResetLayer = false; michael@0: michael@0: return canvasLayer.forget(); michael@0: } michael@0: michael@0: void michael@0: WebGLContext::GetContextAttributes(Nullable &retval) michael@0: { michael@0: retval.SetNull(); michael@0: if (IsContextLost()) michael@0: return; michael@0: michael@0: dom::WebGLContextAttributes& result = retval.SetValue(); michael@0: michael@0: const PixelBufferFormat& format = gl->GetPixelFormat(); michael@0: michael@0: result.mAlpha.Construct(format.alpha > 0); michael@0: result.mDepth = format.depth > 0; michael@0: result.mStencil = format.stencil > 0; michael@0: result.mAntialias = format.samples > 1; michael@0: result.mPremultipliedAlpha = mOptions.premultipliedAlpha; michael@0: result.mPreserveDrawingBuffer = mOptions.preserveDrawingBuffer; michael@0: } michael@0: michael@0: /* [noscript] DOMString mozGetUnderlyingParamString(in GLenum pname); */ michael@0: NS_IMETHODIMP michael@0: WebGLContext::MozGetUnderlyingParamString(uint32_t pname, nsAString& retval) michael@0: { michael@0: if (IsContextLost()) michael@0: return NS_OK; michael@0: michael@0: retval.SetIsVoid(true); michael@0: michael@0: MakeContextCurrent(); michael@0: michael@0: switch (pname) { michael@0: case LOCAL_GL_VENDOR: michael@0: case LOCAL_GL_RENDERER: michael@0: case LOCAL_GL_VERSION: michael@0: case LOCAL_GL_SHADING_LANGUAGE_VERSION: michael@0: case LOCAL_GL_EXTENSIONS: { michael@0: const char *s = (const char *) gl->fGetString(pname); michael@0: retval.Assign(NS_ConvertASCIItoUTF16(nsDependentCString(s))); michael@0: } michael@0: break; michael@0: michael@0: default: michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: WebGLContext::ClearScreen() michael@0: { michael@0: bool colorAttachmentsMask[WebGLContext::sMaxColorAttachments] = {false}; michael@0: michael@0: MakeContextCurrent(); michael@0: ScopedBindFramebuffer autoFB(gl, 0); michael@0: michael@0: GLbitfield clearMask = LOCAL_GL_COLOR_BUFFER_BIT; michael@0: if (mOptions.depth) michael@0: clearMask |= LOCAL_GL_DEPTH_BUFFER_BIT; michael@0: if (mOptions.stencil) michael@0: clearMask |= LOCAL_GL_STENCIL_BUFFER_BIT; michael@0: michael@0: colorAttachmentsMask[0] = true; michael@0: michael@0: ForceClearFramebufferWithDefaultValues(clearMask, colorAttachmentsMask); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: // For NaNs, etc. michael@0: static bool IsShadowCorrect(float shadow, float actual) { michael@0: if (IsNaN(shadow)) { michael@0: // GL is allowed to do anything it wants for NaNs, so if we're shadowing michael@0: // a NaN, then whatever `actual` is might be correct. michael@0: return true; michael@0: } michael@0: michael@0: return shadow == actual; michael@0: } michael@0: #endif michael@0: michael@0: void michael@0: WebGLContext::ForceClearFramebufferWithDefaultValues(GLbitfield mask, const bool colorAttachmentsMask[sMaxColorAttachments]) michael@0: { michael@0: MakeContextCurrent(); michael@0: michael@0: bool initializeColorBuffer = 0 != (mask & LOCAL_GL_COLOR_BUFFER_BIT); michael@0: bool initializeDepthBuffer = 0 != (mask & LOCAL_GL_DEPTH_BUFFER_BIT); michael@0: bool initializeStencilBuffer = 0 != (mask & LOCAL_GL_STENCIL_BUFFER_BIT); michael@0: bool drawBuffersIsEnabled = IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers); michael@0: michael@0: GLenum currentDrawBuffers[WebGLContext::sMaxColorAttachments]; michael@0: michael@0: // Fun GL fact: No need to worry about the viewport here, glViewport is just michael@0: // setting up a coordinates transformation, it doesn't affect glClear at all. michael@0: michael@0: #ifdef DEBUG michael@0: // Scope to hide our variables. michael@0: { michael@0: // Sanity-check that all our state is set properly. Otherwise, when we michael@0: // reset out state to what we *think* it is, we'll get it wrong. michael@0: michael@0: // Dither shouldn't matter when we're clearing to {0,0,0,0}. michael@0: MOZ_ASSERT(gl->fIsEnabled(LOCAL_GL_SCISSOR_TEST) == mScissorTestEnabled); michael@0: michael@0: if (initializeColorBuffer) { michael@0: realGLboolean colorWriteMask[4] = {2, 2, 2, 2}; michael@0: GLfloat colorClearValue[4] = {-1.0f, -1.0f, -1.0f, -1.0f}; michael@0: michael@0: gl->fGetBooleanv(LOCAL_GL_COLOR_WRITEMASK, colorWriteMask); michael@0: gl->fGetFloatv(LOCAL_GL_COLOR_CLEAR_VALUE, colorClearValue); michael@0: michael@0: MOZ_ASSERT(colorWriteMask[0] == mColorWriteMask[0] && michael@0: colorWriteMask[1] == mColorWriteMask[1] && michael@0: colorWriteMask[2] == mColorWriteMask[2] && michael@0: colorWriteMask[3] == mColorWriteMask[3]); michael@0: MOZ_ASSERT(IsShadowCorrect(mColorClearValue[0], colorClearValue[0]) && michael@0: IsShadowCorrect(mColorClearValue[1], colorClearValue[1]) && michael@0: IsShadowCorrect(mColorClearValue[2], colorClearValue[2]) && michael@0: IsShadowCorrect(mColorClearValue[3], colorClearValue[3])); michael@0: } michael@0: michael@0: if (initializeDepthBuffer) { michael@0: realGLboolean depthWriteMask = 2; michael@0: GLfloat depthClearValue = -1.0f; michael@0: michael@0: michael@0: gl->fGetBooleanv(LOCAL_GL_DEPTH_WRITEMASK, &depthWriteMask); michael@0: gl->fGetFloatv(LOCAL_GL_DEPTH_CLEAR_VALUE, &depthClearValue); michael@0: michael@0: MOZ_ASSERT(depthWriteMask == mDepthWriteMask); michael@0: MOZ_ASSERT(IsShadowCorrect(mDepthClearValue, depthClearValue)); michael@0: } michael@0: michael@0: if (initializeStencilBuffer) { michael@0: GLuint stencilWriteMaskFront = 0xdeadbad1; michael@0: GLuint stencilWriteMaskBack = 0xdeadbad1; michael@0: GLuint stencilClearValue = 0xdeadbad1; michael@0: michael@0: gl->GetUIntegerv(LOCAL_GL_STENCIL_WRITEMASK, &stencilWriteMaskFront); michael@0: gl->GetUIntegerv(LOCAL_GL_STENCIL_BACK_WRITEMASK, &stencilWriteMaskBack); michael@0: gl->GetUIntegerv(LOCAL_GL_STENCIL_CLEAR_VALUE, &stencilClearValue); michael@0: michael@0: GLuint stencilBits = 0; michael@0: gl->GetUIntegerv(LOCAL_GL_STENCIL_BITS, &stencilBits); michael@0: GLuint stencilMask = (GLuint(1) << stencilBits) - 1; michael@0: michael@0: MOZ_ASSERT( ( stencilWriteMaskFront & stencilMask) == michael@0: (mStencilWriteMaskFront & stencilMask) ); michael@0: MOZ_ASSERT( ( stencilWriteMaskBack & stencilMask) == michael@0: (mStencilWriteMaskBack & stencilMask) ); michael@0: MOZ_ASSERT( ( stencilClearValue & stencilMask) == michael@0: (mStencilClearValue & stencilMask) ); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: // Prepare GL state for clearing. michael@0: gl->fDisable(LOCAL_GL_SCISSOR_TEST); michael@0: michael@0: if (initializeColorBuffer) { michael@0: michael@0: if (drawBuffersIsEnabled) { michael@0: michael@0: GLenum drawBuffersCommand[WebGLContext::sMaxColorAttachments] = { LOCAL_GL_NONE }; michael@0: michael@0: for(int32_t i = 0; i < mGLMaxDrawBuffers; i++) { michael@0: GLint temp; michael@0: gl->fGetIntegerv(LOCAL_GL_DRAW_BUFFER0 + i, &temp); michael@0: currentDrawBuffers[i] = temp; michael@0: michael@0: if (colorAttachmentsMask[i]) { michael@0: drawBuffersCommand[i] = LOCAL_GL_COLOR_ATTACHMENT0 + i; michael@0: } michael@0: } michael@0: michael@0: gl->fDrawBuffers(mGLMaxDrawBuffers, drawBuffersCommand); michael@0: } michael@0: michael@0: gl->fColorMask(1, 1, 1, 1); michael@0: gl->fClearColor(0.0f, 0.0f, 0.0f, 0.0f); michael@0: } michael@0: michael@0: if (initializeDepthBuffer) { michael@0: gl->fDepthMask(1); michael@0: gl->fClearDepth(1.0f); michael@0: } michael@0: michael@0: if (initializeStencilBuffer) { michael@0: // "The clear operation always uses the front stencil write mask michael@0: // when clearing the stencil buffer." michael@0: gl->fStencilMaskSeparate(LOCAL_GL_FRONT, 0xffffffff); michael@0: gl->fStencilMaskSeparate(LOCAL_GL_BACK, 0xffffffff); michael@0: gl->fClearStencil(0); michael@0: } michael@0: michael@0: if (mRasterizerDiscardEnabled) { michael@0: gl->fDisable(LOCAL_GL_RASTERIZER_DISCARD); michael@0: } michael@0: michael@0: // Do the clear! michael@0: gl->fClear(mask); michael@0: michael@0: // And reset! michael@0: if (mScissorTestEnabled) michael@0: gl->fEnable(LOCAL_GL_SCISSOR_TEST); michael@0: michael@0: if (mRasterizerDiscardEnabled) { michael@0: gl->fEnable(LOCAL_GL_RASTERIZER_DISCARD); michael@0: } michael@0: michael@0: // Restore GL state after clearing. michael@0: if (initializeColorBuffer) { michael@0: if (drawBuffersIsEnabled) { michael@0: gl->fDrawBuffers(mGLMaxDrawBuffers, currentDrawBuffers); michael@0: } michael@0: michael@0: gl->fColorMask(mColorWriteMask[0], michael@0: mColorWriteMask[1], michael@0: mColorWriteMask[2], michael@0: mColorWriteMask[3]); michael@0: gl->fClearColor(mColorClearValue[0], michael@0: mColorClearValue[1], michael@0: mColorClearValue[2], michael@0: mColorClearValue[3]); michael@0: } michael@0: michael@0: if (initializeDepthBuffer) { michael@0: gl->fDepthMask(mDepthWriteMask); michael@0: gl->fClearDepth(mDepthClearValue); michael@0: } michael@0: michael@0: if (initializeStencilBuffer) { michael@0: gl->fStencilMaskSeparate(LOCAL_GL_FRONT, mStencilWriteMaskFront); michael@0: gl->fStencilMaskSeparate(LOCAL_GL_BACK, mStencilWriteMaskBack); michael@0: gl->fClearStencil(mStencilClearValue); michael@0: } michael@0: } michael@0: michael@0: // For an overview of how WebGL compositing works, see: michael@0: // https://wiki.mozilla.org/Platform/GFX/WebGL/Compositing michael@0: bool michael@0: WebGLContext::PresentScreenBuffer() michael@0: { michael@0: if (IsContextLost()) { michael@0: return false; michael@0: } michael@0: michael@0: if (!mShouldPresent) { michael@0: return false; michael@0: } michael@0: michael@0: gl->MakeCurrent(); michael@0: MOZ_ASSERT(!mBackbufferNeedsClear); michael@0: if (!gl->PublishFrame()) { michael@0: this->ForceLoseContext(); michael@0: return false; michael@0: } michael@0: michael@0: if (!mOptions.preserveDrawingBuffer) { michael@0: mBackbufferNeedsClear = true; michael@0: } michael@0: michael@0: mShouldPresent = false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: WebGLContext::DummyFramebufferOperation(const char *info) michael@0: { michael@0: GLenum status = CheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); michael@0: if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) michael@0: ErrorInvalidFramebufferOperation("%s: incomplete framebuffer", info); michael@0: } michael@0: michael@0: // We use this timer for many things. Here are the things that it is activated for: michael@0: // 1) If a script is using the MOZ_WEBGL_lose_context extension. michael@0: // 2) If we are using EGL and _NOT ANGLE_, we query periodically to see if the michael@0: // CONTEXT_LOST_WEBGL error has been triggered. michael@0: // 3) If we are using ANGLE, or anything that supports ARB_robustness, query the michael@0: // GPU periodically to see if the reset status bit has been set. michael@0: // In all of these situations, we use this timer to send the script context lost michael@0: // and restored events asynchronously. For example, if it triggers a context loss, michael@0: // the webglcontextlost event will be sent to it the next time the robustness timer michael@0: // fires. michael@0: // Note that this timer mechanism is not used unless one of these 3 criteria michael@0: // are met. michael@0: // At a bare minimum, from context lost to context restores, it would take 3 michael@0: // full timer iterations: detection, webglcontextlost, webglcontextrestored. michael@0: void michael@0: WebGLContext::RobustnessTimerCallback(nsITimer* timer) michael@0: { michael@0: TerminateContextLossTimer(); michael@0: michael@0: if (!mCanvasElement) { michael@0: // the canvas is gone. That happens when the page was closed before we got michael@0: // this timer event. In this case, there's nothing to do here, just don't crash. michael@0: return; michael@0: } michael@0: michael@0: // If the context has been lost and we're waiting for it to be restored, do michael@0: // that now. michael@0: if (mContextStatus == ContextLostAwaitingEvent) { michael@0: bool defaultAction; michael@0: nsContentUtils::DispatchTrustedEvent(mCanvasElement->OwnerDoc(), michael@0: static_cast(mCanvasElement), michael@0: NS_LITERAL_STRING("webglcontextlost"), michael@0: true, michael@0: true, michael@0: &defaultAction); michael@0: michael@0: // If the script didn't handle the event, we don't allow restores. michael@0: if (defaultAction) michael@0: mAllowRestore = false; michael@0: michael@0: // If the script handled the event and we are allowing restores, then michael@0: // mark it to be restored. Otherwise, leave it as context lost michael@0: // (unusable). michael@0: if (!defaultAction && mAllowRestore) { michael@0: ForceRestoreContext(); michael@0: // Restart the timer so that it will be restored on the next michael@0: // callback. michael@0: SetupContextLossTimer(); michael@0: } else { michael@0: mContextStatus = ContextLost; michael@0: } michael@0: } else if (mContextStatus == ContextLostAwaitingRestore) { michael@0: // Try to restore the context. If it fails, try again later. michael@0: if (NS_FAILED(SetDimensions(mWidth, mHeight))) { michael@0: SetupContextLossTimer(); michael@0: return; michael@0: } michael@0: mContextStatus = ContextNotLost; michael@0: nsContentUtils::DispatchTrustedEvent(mCanvasElement->OwnerDoc(), michael@0: static_cast(mCanvasElement), michael@0: NS_LITERAL_STRING("webglcontextrestored"), michael@0: true, michael@0: true); michael@0: // Set all flags back to the state they were in before the context was michael@0: // lost. michael@0: mEmitContextLostErrorOnce = true; michael@0: mAllowRestore = true; michael@0: } michael@0: michael@0: MaybeRestoreContext(); michael@0: return; michael@0: } michael@0: michael@0: void michael@0: WebGLContext::MaybeRestoreContext() michael@0: { michael@0: // Don't try to handle it if we already know it's busted. michael@0: if (mContextStatus != ContextNotLost || gl == nullptr) michael@0: return; michael@0: michael@0: bool isEGL = gl->GetContextType() == gl::GLContextType::EGL, michael@0: isANGLE = gl->IsANGLE(); michael@0: michael@0: GLContext::ContextResetARB resetStatus = GLContext::CONTEXT_NO_ERROR; michael@0: if (mHasRobustness) { michael@0: gl->MakeCurrent(); michael@0: resetStatus = (GLContext::ContextResetARB) gl->fGetGraphicsResetStatus(); michael@0: } else if (isEGL) { michael@0: // Simulate a ARB_robustness guilty context loss for when we michael@0: // get an EGL_CONTEXT_LOST error. It may not actually be guilty, michael@0: // but we can't make any distinction, so we must assume the worst michael@0: // case. michael@0: if (!gl->MakeCurrent(true) && gl->IsContextLost()) { michael@0: resetStatus = GLContext::CONTEXT_GUILTY_CONTEXT_RESET_ARB; michael@0: } michael@0: } michael@0: michael@0: if (resetStatus != GLContext::CONTEXT_NO_ERROR) { michael@0: // It's already lost, but clean up after it and signal to JS that it is michael@0: // lost. michael@0: ForceLoseContext(); michael@0: } michael@0: michael@0: switch (resetStatus) { michael@0: case GLContext::CONTEXT_NO_ERROR: michael@0: // If there has been activity since the timer was set, it's possible michael@0: // that we did or are going to miss something, so clear this flag and michael@0: // run it again some time later. michael@0: if (mDrawSinceContextLossTimerSet) michael@0: SetupContextLossTimer(); michael@0: break; michael@0: case GLContext::CONTEXT_GUILTY_CONTEXT_RESET_ARB: michael@0: NS_WARNING("WebGL content on the page caused the graphics card to reset; not restoring the context"); michael@0: mAllowRestore = false; michael@0: break; michael@0: case GLContext::CONTEXT_INNOCENT_CONTEXT_RESET_ARB: michael@0: break; michael@0: case GLContext::CONTEXT_UNKNOWN_CONTEXT_RESET_ARB: michael@0: NS_WARNING("WebGL content on the page might have caused the graphics card to reset"); michael@0: if (isEGL && isANGLE) { michael@0: // If we're using ANGLE, we ONLY get back UNKNOWN context resets, including for guilty contexts. michael@0: // This means that we can't restore it or risk restoring a guilty context. Should this ever change, michael@0: // we can get rid of the whole IsANGLE() junk from GLContext.h since, as of writing, this is the michael@0: // only use for it. See ANGLE issue 261. michael@0: mAllowRestore = false; michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: michael@0: void michael@0: WebGLContext::ForceLoseContext() michael@0: { michael@0: if (mContextStatus == ContextLostAwaitingEvent) michael@0: return; michael@0: michael@0: mContextStatus = ContextLostAwaitingEvent; michael@0: // Queue up a task to restore the event. michael@0: SetupContextLossTimer(); michael@0: DestroyResourcesAndContext(); michael@0: } michael@0: michael@0: void michael@0: WebGLContext::ForceRestoreContext() michael@0: { michael@0: mContextStatus = ContextLostAwaitingRestore; michael@0: } michael@0: michael@0: void michael@0: WebGLContext::MakeContextCurrent() const { gl->MakeCurrent(); } michael@0: michael@0: mozilla::TemporaryRef michael@0: WebGLContext::GetSurfaceSnapshot(bool* aPremultAlpha) michael@0: { michael@0: if (!gl) michael@0: return nullptr; michael@0: michael@0: nsRefPtr surf = new gfxImageSurface(gfxIntSize(mWidth, mHeight), michael@0: gfxImageFormat::ARGB32, michael@0: mWidth * 4, 0, false); michael@0: if (surf->CairoStatus() != 0) { michael@0: return nullptr; michael@0: } michael@0: michael@0: gl->MakeCurrent(); michael@0: { michael@0: ScopedBindFramebuffer autoFB(gl, 0); michael@0: ClearBackbufferIfNeeded(); michael@0: ReadPixelsIntoImageSurface(gl, surf); michael@0: } michael@0: michael@0: if (aPremultAlpha) { michael@0: *aPremultAlpha = true; michael@0: } michael@0: bool srcPremultAlpha = mOptions.premultipliedAlpha; michael@0: if (!srcPremultAlpha) { michael@0: if (aPremultAlpha) { michael@0: *aPremultAlpha = false; michael@0: } else { michael@0: gfxUtils::PremultiplyImageSurface(surf); michael@0: surf->MarkDirty(); michael@0: } michael@0: } michael@0: michael@0: RefPtr dt = michael@0: Factory::CreateDrawTarget(BackendType::CAIRO, michael@0: IntSize(mWidth, mHeight), michael@0: SurfaceFormat::B8G8R8A8); michael@0: michael@0: if (!dt) { michael@0: return nullptr; michael@0: } michael@0: michael@0: RefPtr source = gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(dt, surf); michael@0: michael@0: Matrix m; michael@0: m.Translate(0.0, mHeight); michael@0: m.Scale(1.0, -1.0); michael@0: dt->SetTransform(m); michael@0: michael@0: dt->DrawSurface(source, michael@0: Rect(0, 0, mWidth, mHeight), michael@0: Rect(0, 0, mWidth, mHeight), michael@0: DrawSurfaceOptions(), michael@0: DrawOptions(1.0f, CompositionOp::OP_SOURCE)); michael@0: michael@0: return dt->Snapshot(); michael@0: } michael@0: michael@0: // michael@0: // XPCOM goop michael@0: // michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(WebGLContext) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(WebGLContext) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_13(WebGLContext, michael@0: mCanvasElement, michael@0: mExtensions, michael@0: mBound2DTextures, michael@0: mBoundCubeMapTextures, michael@0: mBoundArrayBuffer, michael@0: mBoundTransformFeedbackBuffer, michael@0: mCurrentProgram, michael@0: mBoundFramebuffer, michael@0: mBoundRenderbuffer, michael@0: mBoundVertexArray, michael@0: mDefaultVertexArray, michael@0: mActiveOcclusionQuery, michael@0: mActiveTransformFeedbackQuery) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebGLContext) michael@0: NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY michael@0: NS_INTERFACE_MAP_ENTRY(nsIDOMWebGLRenderingContext) michael@0: NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) michael@0: // If the exact way we cast to nsISupports here ever changes, fix our michael@0: // ToSupports() method. michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMWebGLRenderingContext) michael@0: NS_INTERFACE_MAP_END