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