blob: 9e679799ff9b3d8a7d1e676e5b48430b24437720 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/gl/gl_surface_egl.h"
#if defined(OS_ANDROID)
#include <android/native_window_jni.h>
#endif
#include "base/command_line.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gl/egl_util.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/gl_implementation.h"
#include "ui/gl/gl_surface_stub.h"
#include "ui/gl/gl_switches.h"
#include "ui/gl/scoped_make_current.h"
#include "ui/gl/sync_control_vsync_provider.h"
#if defined(USE_X11)
extern "C" {
#include <X11/Xlib.h>
}
#endif
#if defined (USE_OZONE)
#include "ui/ozone/public/surface_factory_ozone.h"
#endif
#if !defined(EGL_FIXED_SIZE_ANGLE)
#define EGL_FIXED_SIZE_ANGLE 0x3201
#endif
#if !defined(EGL_OPENGL_ES3_BIT)
#define EGL_OPENGL_ES3_BIT 0x00000040
#endif
using ui::GetLastEGLErrorString;
namespace gfx {
namespace {
EGLDisplay g_display;
EGLNativeDisplayType g_native_display_type;
// In the Cast environment, we need to destroy the EGLNativeDisplayType and
// EGLDisplay returned by the GPU platform when we switch to an external app
// which will temporarily own all screen and GPU resources.
// Even though Chromium is still in the background.
// As such, it must be reinitialized each time we come back to the foreground.
bool g_initialized = false;
int g_num_surfaces = 0;
bool g_terminate_pending = false;
const char* g_egl_extensions = NULL;
bool g_egl_create_context_robustness_supported = false;
bool g_egl_sync_control_supported = false;
bool g_egl_window_fixed_size_supported = false;
bool g_egl_surfaceless_context_supported = false;
class EGLSyncControlVSyncProvider
: public gfx::SyncControlVSyncProvider {
public:
explicit EGLSyncControlVSyncProvider(EGLSurface surface)
: SyncControlVSyncProvider(),
surface_(surface) {
}
~EGLSyncControlVSyncProvider() override {}
protected:
bool GetSyncValues(int64* system_time,
int64* media_stream_counter,
int64* swap_buffer_counter) override {
uint64 u_system_time, u_media_stream_counter, u_swap_buffer_counter;
bool result = eglGetSyncValuesCHROMIUM(
g_display, surface_, &u_system_time,
&u_media_stream_counter, &u_swap_buffer_counter) == EGL_TRUE;
if (result) {
*system_time = static_cast<int64>(u_system_time);
*media_stream_counter = static_cast<int64>(u_media_stream_counter);
*swap_buffer_counter = static_cast<int64>(u_swap_buffer_counter);
}
return result;
}
bool GetMscRate(int32* numerator, int32* denominator) override {
return false;
}
private:
EGLSurface surface_;
DISALLOW_COPY_AND_ASSIGN(EGLSyncControlVSyncProvider);
};
void DeinitializeEgl() {
if (g_initialized) {
g_initialized = false;
eglTerminate(g_display);
}
}
} // namespace
GLSurfaceEGL::GLSurfaceEGL(
const gfx::SurfaceConfiguration requested_configuration)
: GLSurface(requested_configuration) {
++g_num_surfaces;
if (!g_initialized) {
bool result = GLSurfaceEGL::InitializeOneOff();
DCHECK(result);
DCHECK(g_initialized);
}
}
void* GetEGLConfig(const EGLNativeWindowType window,
const gfx::SurfaceConfiguration configuration,
bool allow_window_bit) {
// Choose an EGL configuration.
// On X this is only used for PBuffer surfaces.
EGLConfig config = {0};
#if defined(USE_X11)
XWindowAttributes win_attribs;
if (!XGetWindowAttributes(GLSurfaceEGL::GetNativeDisplay(),
window,
&win_attribs)) {
return nullptr;
}
#endif
EGLint renderable_type = EGL_OPENGL_ES2_BIT;
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableUnsafeES3APIs)) {
renderable_type = EGL_OPENGL_ES3_BIT;
}
EGLint config_attribs[] = {
EGL_BUFFER_SIZE, configuration.alpha_bits +
configuration.red_bits +
configuration.green_bits +
configuration.blue_bits,
EGL_ALPHA_SIZE, configuration.alpha_bits,
EGL_BLUE_SIZE, configuration.blue_bits,
EGL_GREEN_SIZE, configuration.green_bits,
EGL_RED_SIZE, configuration.red_bits,
EGL_DEPTH_SIZE, configuration.depth_bits,
EGL_STENCIL_SIZE, configuration.stencil_bits,
EGL_RENDERABLE_TYPE, renderable_type,
EGL_SURFACE_TYPE, (allow_window_bit ?
(EGL_WINDOW_BIT | EGL_PBUFFER_BIT) :
EGL_PBUFFER_BIT),
EGL_NONE
};
#if defined(USE_OZONE)
config_attribs =
ui::SurfaceFactoryOzone::GetInstance()->GetEGLSurfaceProperties(
config_attribs);
#elif defined(USE_X11)
// Try matching the window depth with an alpha channel,
// because we're worried the destination alpha width could
// constrain blending precision.
const int kBufferSizeOffset = 1;
const int kAlphaSizeOffset = 3;
config_attribs[kBufferSizeOffset] = win_attribs.depth;
#endif
EGLint num_configs;
if (!eglChooseConfig(g_display,
config_attribs,
NULL,
0,
&num_configs)) {
LOG(ERROR) << "eglChooseConfig failed with error "
<< GetLastEGLErrorString();
return nullptr;
}
if (!eglChooseConfig(g_display,
config_attribs,
&config,
1,
&num_configs)) {
LOG(ERROR) << "eglChooseConfig failed with error "
<< GetLastEGLErrorString();
return nullptr;
}
#if defined(USE_X11)
if (num_configs) {
EGLint config_depth;
if (!eglGetConfigAttrib(g_display,
config,
EGL_BUFFER_SIZE,
&config_depth)) {
LOG(ERROR) << "eglGetConfigAttrib failed with error "
<< GetLastEGLErrorString();
return nullptr;
}
if (config_depth == win_attribs.depth) {
return config;
}
}
// Try without an alpha channel.
config_attribs[kAlphaSizeOffset] = 0;
if (!eglChooseConfig(g_display,
config_attribs,
&config,
1,
&num_configs)) {
LOG(ERROR) << "eglChooseConfig failed with error "
<< GetLastEGLErrorString();
return nullptr;
}
#endif
if (num_configs == 0) {
LOG(ERROR) << "No suitable EGL configs found.";
return nullptr;
}
return config;
}
bool GLSurfaceEGL::InitializeOneOff() {
if (g_initialized)
return true;
g_native_display_type = GetPlatformDefaultEGLNativeDisplay();
g_display = eglGetDisplay(g_native_display_type);
if (!g_display) {
LOG(ERROR) << "eglGetDisplay failed with error " << GetLastEGLErrorString();
return false;
}
if (!eglInitialize(g_display, NULL, NULL)) {
LOG(ERROR) << "eglInitialize failed with error " << GetLastEGLErrorString();
return false;
}
g_egl_extensions = eglQueryString(g_display, EGL_EXTENSIONS);
g_egl_create_context_robustness_supported =
HasEGLExtension("EGL_EXT_create_context_robustness");
g_egl_sync_control_supported =
HasEGLExtension("EGL_CHROMIUM_sync_control");
g_egl_window_fixed_size_supported =
HasEGLExtension("EGL_ANGLE_window_fixed_size");
// We always succeed beyond this point so set g_initialized here to avoid
// infinite recursion through CreateGLContext and GetDisplay
// if g_egl_surfaceless_context_supported.
g_initialized = true;
g_terminate_pending = false;
// TODO(oetuaho@nvidia.com): Surfaceless is disabled on Android as a temporary
// workaround, since code written for Android WebView takes different paths
// based on whether GL surface objects have underlying EGL surface handles,
// conflicting with the use of surfaceless. See https://crbug.com/382349
#if defined(OS_ANDROID)
DCHECK(!g_egl_surfaceless_context_supported);
#else
// Check if SurfacelessEGL is supported.
g_egl_surfaceless_context_supported =
HasEGLExtension("EGL_KHR_surfaceless_context");
if (g_egl_surfaceless_context_supported) {
// EGL_KHR_surfaceless_context is supported but ensure
// GL_OES_surfaceless_context is also supported. We need a current context
// to query for supported GL extensions.
scoped_refptr<GLSurface> surface = new SurfacelessEGL(
Size(1, 1), SurfaceConfiguration());
scoped_refptr<GLContext> context = GLContext::CreateGLContext(
NULL, surface.get(), PreferIntegratedGpu);
if (!context->MakeCurrent(surface.get()))
g_egl_surfaceless_context_supported = false;
// Ensure context supports GL_OES_surfaceless_context.
if (g_egl_surfaceless_context_supported) {
g_egl_surfaceless_context_supported = context->HasExtension(
"GL_OES_surfaceless_context");
context->ReleaseCurrent(surface.get());
}
}
#endif
return true;
}
EGLDisplay GLSurfaceEGL::GetDisplay() {
DCHECK(g_initialized);
return g_display;
}
// static
EGLDisplay GLSurfaceEGL::GetHardwareDisplay() {
if (!g_initialized) {
bool result = GLSurfaceEGL::InitializeOneOff();
DCHECK(result);
}
return g_display;
}
// static
EGLNativeDisplayType GLSurfaceEGL::GetNativeDisplay() {
if (!g_initialized) {
bool result = GLSurfaceEGL::InitializeOneOff();
DCHECK(result);
}
return g_native_display_type;
}
const char* GLSurfaceEGL::GetEGLExtensions() {
// No need for InitializeOneOff. Assume that extensions will not change
// after the first initialization.
return g_egl_extensions;
}
bool GLSurfaceEGL::HasEGLExtension(const char* name) {
return ExtensionsContain(GetEGLExtensions(), name);
}
bool GLSurfaceEGL::IsCreateContextRobustnessSupported() {
return g_egl_create_context_robustness_supported;
}
bool GLSurfaceEGL::IsEGLSurfacelessContextSupported() {
return g_egl_surfaceless_context_supported;
}
void GLSurfaceEGL::DestroyAndTerminateDisplay() {
DCHECK(g_initialized);
DCHECK_EQ(g_num_surfaces, 1);
Destroy();
g_terminate_pending = true;
}
GLSurfaceEGL::~GLSurfaceEGL() {
DCHECK_GT(g_num_surfaces, 0) << "Bad surface count";
if (--g_num_surfaces == 0 && g_terminate_pending) {
DeinitializeEgl();
g_terminate_pending = false;
}
}
NativeViewGLSurfaceEGL::NativeViewGLSurfaceEGL(
EGLNativeWindowType window,
const gfx::SurfaceConfiguration requested_configuration)
: GLSurfaceEGL(requested_configuration),
window_(window),
surface_(NULL),
supports_post_sub_buffer_(false),
config_(NULL),
size_(1, 1),
swap_interval_(1) {
#if defined(OS_ANDROID)
if (window)
ANativeWindow_acquire(window);
#endif
}
bool NativeViewGLSurfaceEGL::Initialize() {
return Initialize(nullptr);
}
bool NativeViewGLSurfaceEGL::Initialize(
scoped_ptr<VSyncProvider> sync_provider) {
DCHECK(!surface_);
if (!GetDisplay()) {
LOG(ERROR) << "Trying to create surface with invalid display.";
return false;
}
std::vector<EGLint> egl_window_attributes;
if (g_egl_window_fixed_size_supported) {
egl_window_attributes.push_back(EGL_FIXED_SIZE_ANGLE);
egl_window_attributes.push_back(EGL_TRUE);
egl_window_attributes.push_back(EGL_WIDTH);
egl_window_attributes.push_back(size_.width());
egl_window_attributes.push_back(EGL_HEIGHT);
egl_window_attributes.push_back(size_.height());
}
if (gfx::g_driver_egl.ext.b_EGL_NV_post_sub_buffer) {
egl_window_attributes.push_back(EGL_POST_SUB_BUFFER_SUPPORTED_NV);
egl_window_attributes.push_back(EGL_TRUE);
}
egl_window_attributes.push_back(EGL_NONE);
// Create a surface for the native window.
surface_ = eglCreateWindowSurface(
GetDisplay(), GetConfig(), window_, &egl_window_attributes[0]);
if (!surface_) {
LOG(ERROR) << "eglCreateWindowSurface failed with error "
<< GetLastEGLErrorString();
Destroy();
return false;
}
if (gfx::g_driver_egl.ext.b_EGL_NV_post_sub_buffer) {
EGLint surfaceVal;
EGLBoolean retVal = eglQuerySurface(
GetDisplay(), surface_, EGL_POST_SUB_BUFFER_SUPPORTED_NV, &surfaceVal);
supports_post_sub_buffer_ = (surfaceVal && retVal) == EGL_TRUE;
}
if (sync_provider)
vsync_provider_.reset(sync_provider.release());
else if (g_egl_sync_control_supported)
vsync_provider_.reset(new EGLSyncControlVSyncProvider(surface_));
return true;
}
void NativeViewGLSurfaceEGL::Destroy() {
if (surface_) {
if (!eglDestroySurface(GetDisplay(), surface_)) {
LOG(ERROR) << "eglDestroySurface failed with error "
<< GetLastEGLErrorString();
}
surface_ = NULL;
}
}
EGLConfig NativeViewGLSurfaceEGL::GetConfig() {
if (!config_) {
DCHECK(window_);
config_ = GetEGLConfig(window_, this->get_surface_configuration(), true);
}
return config_;
}
bool NativeViewGLSurfaceEGL::IsOffscreen() {
return false;
}
bool NativeViewGLSurfaceEGL::SwapBuffers() {
TRACE_EVENT2("gpu", "NativeViewGLSurfaceEGL:RealSwapBuffers",
"width", GetSize().width(),
"height", GetSize().height());
if (!eglSwapBuffers(GetDisplay(), surface_)) {
DVLOG(1) << "eglSwapBuffers failed with error "
<< GetLastEGLErrorString();
return false;
}
return true;
}
gfx::Size NativeViewGLSurfaceEGL::GetSize() {
EGLint width;
EGLint height;
if (!eglQuerySurface(GetDisplay(), surface_, EGL_WIDTH, &width) ||
!eglQuerySurface(GetDisplay(), surface_, EGL_HEIGHT, &height)) {
NOTREACHED() << "eglQuerySurface failed with error "
<< GetLastEGLErrorString();
return gfx::Size();
}
return gfx::Size(width, height);
}
bool NativeViewGLSurfaceEGL::Resize(const gfx::Size& size) {
if (size == GetSize())
return true;
size_ = size;
scoped_ptr<ui::ScopedMakeCurrent> scoped_make_current;
GLContext* current_context = GLContext::GetCurrent();
bool was_current =
current_context && current_context->IsCurrent(this);
if (was_current) {
scoped_make_current.reset(
new ui::ScopedMakeCurrent(current_context, this));
current_context->ReleaseCurrent(this);
}
Destroy();
if (!Initialize()) {
LOG(ERROR) << "Failed to resize window.";
return false;
}
return true;
}
bool NativeViewGLSurfaceEGL::Recreate() {
Destroy();
if (!Initialize()) {
LOG(ERROR) << "Failed to create surface.";
return false;
}
return true;
}
EGLSurface NativeViewGLSurfaceEGL::GetHandle() {
return surface_;
}
bool NativeViewGLSurfaceEGL::SupportsPostSubBuffer() {
return supports_post_sub_buffer_;
}
bool NativeViewGLSurfaceEGL::PostSubBuffer(
int x, int y, int width, int height) {
DCHECK(supports_post_sub_buffer_);
if (!eglPostSubBufferNV(GetDisplay(), surface_, x, y, width, height)) {
DVLOG(1) << "eglPostSubBufferNV failed with error "
<< GetLastEGLErrorString();
return false;
}
return true;
}
VSyncProvider* NativeViewGLSurfaceEGL::GetVSyncProvider() {
return vsync_provider_.get();
}
void NativeViewGLSurfaceEGL::OnSetSwapInterval(int interval) {
swap_interval_ = interval;
}
NativeViewGLSurfaceEGL::~NativeViewGLSurfaceEGL() {
Destroy();
#if defined(OS_ANDROID)
if (window_)
ANativeWindow_release(window_);
#endif
}
PbufferGLSurfaceEGL::PbufferGLSurfaceEGL(
const gfx::Size& size,
const gfx::SurfaceConfiguration requested_configuration)
: GLSurfaceEGL(requested_configuration),
size_(size),
surface_(nullptr),
config_(nullptr) {
// Some implementations of Pbuffer do not support having a 0 size. For such
// cases use a (1, 1) surface.
if (size_.GetArea() == 0)
size_.SetSize(1, 1);
}
bool PbufferGLSurfaceEGL::Initialize() {
EGLSurface old_surface = surface_;
EGLDisplay display = GetDisplay();
if (!display) {
LOG(ERROR) << "Trying to create surface with invalid display.";
return false;
}
// Allocate the new pbuffer surface before freeing the old one to ensure
// they have different addresses. If they have the same address then a
// future call to MakeCurrent might early out because it appears the current
// context and surface have not changed.
const EGLint pbuffer_attribs[] = {
EGL_WIDTH, size_.width(),
EGL_HEIGHT, size_.height(),
EGL_NONE
};
EGLSurface new_surface = eglCreatePbufferSurface(display,
GetConfig(),
pbuffer_attribs);
if (!new_surface) {
LOG(ERROR) << "eglCreatePbufferSurface failed with error "
<< GetLastEGLErrorString();
return false;
}
if (old_surface)
eglDestroySurface(display, old_surface);
surface_ = new_surface;
return true;
}
void PbufferGLSurfaceEGL::Destroy() {
if (surface_) {
if (!eglDestroySurface(GetDisplay(), surface_)) {
LOG(ERROR) << "eglDestroySurface failed with error "
<< GetLastEGLErrorString();
}
surface_ = NULL;
}
}
EGLConfig PbufferGLSurfaceEGL::GetConfig() {
if (!config_) {
config_ = GetEGLConfig((EGLNativeWindowType)nullptr,
this->get_surface_configuration(),
false);
}
return config_;
}
bool PbufferGLSurfaceEGL::IsOffscreen() {
return true;
}
bool PbufferGLSurfaceEGL::SwapBuffers() {
NOTREACHED() << "Attempted to call SwapBuffers on a PbufferGLSurfaceEGL.";
return false;
}
gfx::Size PbufferGLSurfaceEGL::GetSize() {
return size_;
}
bool PbufferGLSurfaceEGL::Resize(const gfx::Size& size) {
if (size == size_)
return true;
scoped_ptr<ui::ScopedMakeCurrent> scoped_make_current;
GLContext* current_context = GLContext::GetCurrent();
bool was_current =
current_context && current_context->IsCurrent(this);
if (was_current) {
scoped_make_current.reset(
new ui::ScopedMakeCurrent(current_context, this));
}
size_ = size;
if (!Initialize()) {
LOG(ERROR) << "Failed to resize pbuffer.";
return false;
}
return true;
}
EGLSurface PbufferGLSurfaceEGL::GetHandle() {
return surface_;
}
void* PbufferGLSurfaceEGL::GetShareHandle() {
#if defined(OS_ANDROID)
NOTREACHED();
return NULL;
#else
if (!gfx::g_driver_egl.ext.b_EGL_ANGLE_query_surface_pointer)
return NULL;
if (!gfx::g_driver_egl.ext.b_EGL_ANGLE_surface_d3d_texture_2d_share_handle)
return NULL;
void* handle;
if (!eglQuerySurfacePointerANGLE(g_display,
GetHandle(),
EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE,
&handle)) {
return NULL;
}
return handle;
#endif
}
PbufferGLSurfaceEGL::~PbufferGLSurfaceEGL() {
Destroy();
}
SurfacelessEGL::SurfacelessEGL(
const gfx::Size& size,
const gfx::SurfaceConfiguration requested_configuration)
: GLSurfaceEGL(requested_configuration), size_(size) {
}
bool SurfacelessEGL::Initialize() {
return true;
}
void SurfacelessEGL::Destroy() {
}
EGLConfig SurfacelessEGL::GetConfig() {
return NULL;
}
bool SurfacelessEGL::IsOffscreen() {
return true;
}
bool SurfacelessEGL::IsSurfaceless() const {
return true;
}
bool SurfacelessEGL::SwapBuffers() {
LOG(ERROR) << "Attempted to call SwapBuffers with SurfacelessEGL.";
return false;
}
gfx::Size SurfacelessEGL::GetSize() {
return size_;
}
bool SurfacelessEGL::Resize(const gfx::Size& size) {
size_ = size;
return true;
}
EGLSurface SurfacelessEGL::GetHandle() {
return EGL_NO_SURFACE;
}
void* SurfacelessEGL::GetShareHandle() {
return NULL;
}
SurfacelessEGL::~SurfacelessEGL() {
}
} // namespace gfx