gfx/gl/GLContextProviderCGL.mm
author Phil Ringnalda <philringnalda@gmail.com>
Thu, 17 Aug 2017 21:06:05 -0700
changeset 375450 fdbcfc9b798737b01e06b953441c4a2ffb5c168a
parent 375412 caffdbefd4273f360152ccff085ec5ee8244046d
child 394033 538dfa8c4c3b5a8824e3f98bdc637718367b35fe
permissions -rw-r--r--
Backed out 7 changesets (bug 1390386) for Android crashes in testSettingsPages Backed out changeset 22b11f9aedd3 (bug 1390386) Backed out changeset caffdbefd427 (bug 1390386) Backed out changeset 3364e6589731 (bug 1390386) Backed out changeset ed6857bdc17b (bug 1390386) Backed out changeset 1314405cf812 (bug 1390386) Backed out changeset 9d16670edeb5 (bug 1390386) Backed out changeset d1286d0d2c79 (bug 1390386) MozReview-Commit-ID: 3V3ZFwrNaGE

/* -*- 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 "GLContextProvider.h"
#include "GLContextCGL.h"
#include "nsDebug.h"
#include "nsIWidget.h"
#include <OpenGL/gl.h>
#include "gfxFailure.h"
#include "gfxPrefs.h"
#include "prenv.h"
#include "GeckoProfiler.h"
#include "mozilla/gfx/MacIOSurface.h"
#include "mozilla/layers/CompositorOptions.h"
#include "mozilla/widget/CompositorWidget.h"

#include <OpenGL/OpenGL.h>

// When running inside a VM, creating an accelerated OpenGL context usually
// fails. Uncomment this line to emulate that behavior.
// #define EMULATE_VM

namespace mozilla {
namespace gl {

using namespace mozilla::gfx;
using namespace mozilla::widget;

class CGLLibrary
{
public:
    CGLLibrary()
        : mInitialized(false)
        , mUseDoubleBufferedWindows(true)
        , mOGLLibrary(nullptr)
    {}

    bool EnsureInitialized()
    {
        if (mInitialized) {
            return true;
        }
        if (!mOGLLibrary) {
            mOGLLibrary = PR_LoadLibrary("/System/Library/Frameworks/OpenGL.framework/OpenGL");
            if (!mOGLLibrary) {
                NS_WARNING("Couldn't load OpenGL Framework.");
                return false;
            }
        }

        const char* db = PR_GetEnv("MOZ_CGL_DB");
        if (db) {
            mUseDoubleBufferedWindows = *db != '0';
        }

        mInitialized = true;
        return true;
    }

    bool UseDoubleBufferedWindows() const {
        MOZ_ASSERT(mInitialized);
        return mUseDoubleBufferedWindows;
    }

private:
    bool mInitialized;
    bool mUseDoubleBufferedWindows;
    PRLibrary* mOGLLibrary;
};

CGLLibrary sCGLLibrary;

GLContextCGL::GLContextCGL(CreateContextFlags flags, const SurfaceCaps& caps,
                           NSOpenGLContext* context, bool isOffscreen)
    : GLContext(flags, caps, nullptr, isOffscreen)
    , mContext(context)
{
}

GLContextCGL::~GLContextCGL()
{
    MarkDestroyed();

    if (mContext) {
        if ([NSOpenGLContext currentContext] == mContext) {
            // Clear the current context before releasing. If we don't do
            // this, the next time we call [NSOpenGLContext currentContext],
            // "invalid context" will be printed to the console.
            [NSOpenGLContext clearCurrentContext];
        }
        [mContext release];
    }
}

bool
GLContextCGL::Init()
{
    if (!InitWithPrefix("gl", true))
        return false;

    return true;
}

CGLContextObj
GLContextCGL::GetCGLContext() const
{
    return static_cast<CGLContextObj>([mContext CGLContextObj]);
}

bool
GLContextCGL::MakeCurrentImpl(bool aForce)
{
    if (!aForce && [NSOpenGLContext currentContext] == mContext) {
        return true;
    }

    if (mContext) {
        [mContext makeCurrentContext];
        MOZ_ASSERT(IsCurrent());
        // Use non-blocking swap in "ASAP mode".
        // ASAP mode means that rendering is iterated as fast as possible.
        // ASAP mode is entered when layout.frame_rate=0 (requires restart).
        // If swapInt is 1, then glSwapBuffers will block and wait for a vblank signal.
        // When we're iterating as fast as possible, however, we want a non-blocking
        // glSwapBuffers, which will happen when swapInt==0.
        GLint swapInt = gfxPrefs::LayoutFrameRate() == 0 ? 0 : 1;
        [mContext setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
    }
    return true;
}

bool
GLContextCGL::IsCurrent() {
    return [NSOpenGLContext currentContext] == mContext;
}

GLenum
GLContextCGL::GetPreferredARGB32Format() const
{
    return LOCAL_GL_BGRA;
}

bool
GLContextCGL::SetupLookupFunction()
{
    return false;
}

bool
GLContextCGL::IsDoubleBuffered() const
{
  return sCGLLibrary.UseDoubleBufferedWindows();
}

bool
GLContextCGL::SwapBuffers()
{
  AUTO_PROFILER_LABEL("GLContextCGL::SwapBuffers", GRAPHICS);

  [mContext flushBuffer];
  return true;
}

void
GLContextCGL::GetWSIInfo(nsCString* const out) const
{
    out->AppendLiteral("CGL");
}

already_AddRefed<GLContext>
GLContextProviderCGL::CreateWrappingExisting(void*, void*)
{
    return nullptr;
}

static const NSOpenGLPixelFormatAttribute kAttribs_singleBuffered[] = {
    NSOpenGLPFAAllowOfflineRenderers,
    0
};

static const NSOpenGLPixelFormatAttribute kAttribs_singleBuffered_accel[] = {
    NSOpenGLPFAAccelerated,
    NSOpenGLPFAAllowOfflineRenderers,
    0
};

static const NSOpenGLPixelFormatAttribute kAttribs_doubleBuffered[] = {
    NSOpenGLPFAAllowOfflineRenderers,
    NSOpenGLPFADoubleBuffer,
    0
};

static const NSOpenGLPixelFormatAttribute kAttribs_doubleBuffered_accel[] = {
    NSOpenGLPFAAccelerated,
    NSOpenGLPFAAllowOfflineRenderers,
    NSOpenGLPFADoubleBuffer,
    0
};

static const NSOpenGLPixelFormatAttribute kAttribs_doubleBuffered_accel_webrender[] = {
    NSOpenGLPFAAccelerated,
    NSOpenGLPFAAllowOfflineRenderers,
    NSOpenGLPFADoubleBuffer,
    NSOpenGLPFAOpenGLProfile,
    NSOpenGLProfileVersion3_2Core,
    NSOpenGLPFADepthSize, 24,
    0
};

static const NSOpenGLPixelFormatAttribute kAttribs_offscreen[] = {
    0
};

static const NSOpenGLPixelFormatAttribute kAttribs_offscreen_allow_offline[] = {
    NSOpenGLPFAAllowOfflineRenderers,
    0
};

static const NSOpenGLPixelFormatAttribute kAttribs_offscreen_accel[] = {
    NSOpenGLPFAAccelerated,
    0
};

static const NSOpenGLPixelFormatAttribute kAttribs_offscreen_coreProfile[] = {
    NSOpenGLPFAAccelerated,
    NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
    0
};

static NSOpenGLContext*
CreateWithFormat(const NSOpenGLPixelFormatAttribute* attribs)
{
    NSOpenGLPixelFormat* format = [[NSOpenGLPixelFormat alloc]
                                   initWithAttributes:attribs];
    if (!format) {
        NS_WARNING("Failed to create NSOpenGLPixelFormat.");
        return nullptr;
    }

    NSOpenGLContext* context = [[NSOpenGLContext alloc] initWithFormat:format
                                shareContext:nullptr];

    [format release];

    return context;
}

already_AddRefed<GLContext>
GLContextProviderCGL::CreateForCompositorWidget(CompositorWidget* aCompositorWidget, bool aForceAccelerated)
{
    return CreateForWindow(aCompositorWidget->RealWidget(),
                           aCompositorWidget->GetCompositorOptions().UseWebRender(),
                           aForceAccelerated);
}

already_AddRefed<GLContext>
GLContextProviderCGL::CreateForWindow(nsIWidget* aWidget,
                                      bool aWebRender,
                                      bool aForceAccelerated)
{
    if (!sCGLLibrary.EnsureInitialized()) {
        return nullptr;
    }

#ifdef EMULATE_VM
    if (aForceAccelerated) {
        return nullptr;
    }
#endif

    const NSOpenGLPixelFormatAttribute* attribs;
    if (sCGLLibrary.UseDoubleBufferedWindows()) {
        if (aWebRender) {
            attribs = aForceAccelerated ? kAttribs_doubleBuffered_accel_webrender : kAttribs_doubleBuffered;
        } else {
            attribs = aForceAccelerated ? kAttribs_doubleBuffered_accel : kAttribs_doubleBuffered;
        }
    } else {
        attribs = aForceAccelerated ? kAttribs_singleBuffered_accel : kAttribs_singleBuffered;
    }
    NSOpenGLContext* context = CreateWithFormat(attribs);
    if (!context) {
        return nullptr;
    }

    // make the context transparent
    GLint opaque = 0;
    [context setValues:&opaque forParameter:NSOpenGLCPSurfaceOpacity];

    RefPtr<GLContextCGL> glContext = new GLContextCGL(CreateContextFlags::NONE,
                                                      SurfaceCaps::ForRGBA(), context,
                                                      false);

    if (!glContext->Init()) {
        glContext = nullptr;
        [context release];
        return nullptr;
    }

    return glContext.forget();
}

static already_AddRefed<GLContextCGL>
CreateOffscreenFBOContext(CreateContextFlags flags)
{
    if (!sCGLLibrary.EnsureInitialized()) {
        return nullptr;
    }

    NSOpenGLContext* context = nullptr;

    if (!(flags & CreateContextFlags::REQUIRE_COMPAT_PROFILE)) {
        context = CreateWithFormat(kAttribs_offscreen_coreProfile);
    }
    if (!context) {
        if (flags & CreateContextFlags::ALLOW_OFFLINE_RENDERER) {
          if (gfxPrefs::RequireHardwareGL())
              context = CreateWithFormat(kAttribs_singleBuffered);
          else
              context = CreateWithFormat(kAttribs_offscreen_allow_offline);

        } else {
          if (gfxPrefs::RequireHardwareGL())
              context = CreateWithFormat(kAttribs_offscreen_accel);
          else
              context = CreateWithFormat(kAttribs_offscreen);
        }
    }
    if (!context) {
        NS_WARNING("Failed to create NSOpenGLContext.");
        return nullptr;
    }

    RefPtr<GLContextCGL> glContext = new GLContextCGL(flags, SurfaceCaps::Any(), context,
                                                      true);

    if (gfxPrefs::GLMultithreaded()) {
        CGLEnable(glContext->GetCGLContext(), kCGLCEMPEngine);
    }
    return glContext.forget();
}

already_AddRefed<GLContext>
GLContextProviderCGL::CreateHeadless(CreateContextFlags flags,
                                     nsACString* const out_failureId)
{
    RefPtr<GLContextCGL> gl;
    gl = CreateOffscreenFBOContext(flags);
    if (!gl) {
        *out_failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_CGL_FBO");
        return nullptr;
    }

    if (!gl->Init()) {
        *out_failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_CGL_INIT");
        NS_WARNING("Failed during Init.");
        return nullptr;
    }

    return gl.forget();
}

already_AddRefed<GLContext>
GLContextProviderCGL::CreateOffscreen(const IntSize& size,
                                      const SurfaceCaps& minCaps,
                                      CreateContextFlags flags,
                                      nsACString* const out_failureId)
{
    RefPtr<GLContext> gl = CreateHeadless(flags, out_failureId);
    if (!gl) {
        return nullptr;
    }

    if (!gl->InitOffscreen(size, minCaps)) {
        *out_failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_CGL_INIT");
        return nullptr;
    }

    return gl.forget();
}

static RefPtr<GLContext> gGlobalContext;

GLContext*
GLContextProviderCGL::GetGlobalContext()
{
    static bool triedToCreateContext = false;
    if (!triedToCreateContext) {
        triedToCreateContext = true;

        MOZ_RELEASE_ASSERT(!gGlobalContext);
        nsCString discardFailureId;
        RefPtr<GLContext> temp = CreateHeadless(CreateContextFlags::NONE,
                                                &discardFailureId);
        gGlobalContext = temp;

        if (!gGlobalContext) {
            NS_WARNING("Couldn't init gGlobalContext.");
        }
    }

    return gGlobalContext;
}

void
GLContextProviderCGL::Shutdown()
{
    gGlobalContext = nullptr;
}

} /* namespace gl */
} /* namespace mozilla */