blob: ec1d957f80b22ee7a1d57a4c46f3ebc226511cdb [file] [log] [blame]
// Copyright (c) 2011 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.
// This file implements the ViewGLContext and PbufferGLContext classes.
#include "app/gfx/gl/gl_context.h"
extern "C" {
#include <X11/Xlib.h>
}
#include <GL/osmesa.h>
#include "app/gfx/gl/gl_bindings.h"
#include "app/gfx/gl/gl_context_egl.h"
#include "app/gfx/gl/gl_context_osmesa.h"
#include "app/gfx/gl/gl_context_stub.h"
#include "app/gfx/gl/gl_implementation.h"
#include "base/basictypes.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "ui/base/x/x11_util.h"
namespace {
Display* GetXDisplayHelper() {
static Display* display = NULL;
if (!display) {
display = XOpenDisplay(NULL);
CHECK(display);
}
return display;
}
} // namespace
namespace gfx {
typedef GLXContext GLContextHandle;
typedef GLXPbuffer PbufferHandle;
class BaseLinuxGLContext : public GLContext {
public:
virtual std::string GetExtensions();
};
// This class is a wrapper around a GL context that renders directly to a
// window.
class ViewGLContext : public GLContext {
public:
explicit ViewGLContext(gfx::PluginWindowHandle window)
: window_(window),
context_(NULL) {
DCHECK(window);
}
// Initializes the GL context.
bool Initialize(bool multisampled);
virtual void Destroy();
virtual bool MakeCurrent();
virtual bool IsCurrent();
virtual bool IsOffscreen();
virtual bool SwapBuffers();
virtual gfx::Size GetSize();
virtual void* GetHandle();
virtual void SetSwapInterval(int interval);
private:
gfx::PluginWindowHandle window_;
GLContextHandle context_;
DISALLOW_COPY_AND_ASSIGN(ViewGLContext);
};
// This class is a wrapper around a GL context that uses OSMesa to render
// to an offscreen buffer and then blits it to a window.
class OSMesaViewGLContext : public GLContext {
public:
explicit OSMesaViewGLContext(gfx::PluginWindowHandle window)
: window_graphics_context_(0),
window_(window),
pixmap_graphics_context_(0),
pixmap_(0) {
DCHECK(window);
}
// Initializes the GL context.
bool Initialize();
virtual void Destroy();
virtual bool MakeCurrent();
virtual bool IsCurrent();
virtual bool IsOffscreen();
virtual bool SwapBuffers();
virtual gfx::Size GetSize();
virtual void* GetHandle();
virtual void SetSwapInterval(int interval);
private:
bool UpdateSize();
GC window_graphics_context_;
gfx::PluginWindowHandle window_;
GC pixmap_graphics_context_;
Pixmap pixmap_;
OSMesaGLContext osmesa_context_;
DISALLOW_COPY_AND_ASSIGN(OSMesaViewGLContext);
};
// This class is a wrapper around a GL context used for offscreen rendering.
// It is initially backed by a 1x1 pbuffer. Use it to create an FBO to do useful
// rendering.
class PbufferGLContext : public GLContext {
public:
explicit PbufferGLContext()
: context_(NULL),
pbuffer_(0) {
}
// Initializes the GL context.
bool Initialize(GLContext* shared_context);
virtual void Destroy();
virtual bool MakeCurrent();
virtual bool IsCurrent();
virtual bool IsOffscreen();
virtual bool SwapBuffers();
virtual gfx::Size GetSize();
virtual void* GetHandle();
virtual void SetSwapInterval(int interval);
private:
GLContextHandle context_;
PbufferHandle pbuffer_;
DISALLOW_COPY_AND_ASSIGN(PbufferGLContext);
};
// Backup context if Pbuffers (GLX 1.3) aren't supported. May run slower...
class PixmapGLContext : public GLContext {
public:
explicit PixmapGLContext()
: context_(NULL),
pixmap_(0),
glx_pixmap_(0) {
}
// Initializes the GL context.
bool Initialize(GLContext* shared_context);
virtual void Destroy();
virtual bool MakeCurrent();
virtual bool IsCurrent();
virtual bool IsOffscreen();
virtual bool SwapBuffers();
virtual gfx::Size GetSize();
virtual void* GetHandle();
virtual void SetSwapInterval(int interval);
private:
GLContextHandle context_;
Pixmap pixmap_;
GLXPixmap glx_pixmap_;
DISALLOW_COPY_AND_ASSIGN(PixmapGLContext);
};
// scoped_ptr functor for XFree(). Use as follows:
// scoped_ptr_malloc<XVisualInfo, ScopedPtrXFree> foo(...);
// where "XVisualInfo" is any X type that is freed with XFree.
class ScopedPtrXFree {
public:
void operator()(void* x) const {
::XFree(x);
}
};
bool GLContext::InitializeOneOff() {
static bool initialized = false;
if (initialized)
return true;
static const GLImplementation kAllowedGLImplementations[] = {
kGLImplementationDesktopGL,
kGLImplementationEGLGLES2,
kGLImplementationOSMesaGL
};
if (!InitializeRequestedGLBindings(
kAllowedGLImplementations,
kAllowedGLImplementations + arraysize(kAllowedGLImplementations),
kGLImplementationDesktopGL)) {
LOG(ERROR) << "InitializeRequestedGLBindings failed.";
return false;
}
switch (GetGLImplementation()) {
case kGLImplementationDesktopGL: {
// Only check the GLX version if we are in fact using GLX. We might
// actually be using the mock GL implementation.
Display* display = GetXDisplayHelper();
int major, minor;
if (!glXQueryVersion(display, &major, &minor)) {
LOG(ERROR) << "glxQueryVersion failed";
return false;
}
if (major == 1 && minor < 3) {
LOG(WARNING) << "GLX 1.3 or later is recommended.";
}
break;
}
case kGLImplementationEGLGLES2:
if (!BaseEGLContext::InitializeOneOff()) {
LOG(ERROR) << "BaseEGLContext::InitializeOneOff failed.";
return false;
}
break;
default:
break;
}
initialized = true;
return true;
}
std::string BaseLinuxGLContext::GetExtensions() {
Display* display = GetXDisplayHelper();
const char* extensions = glXQueryExtensionsString(display, 0);
if (extensions) {
return GLContext::GetExtensions() + " " + extensions;
}
return GLContext::GetExtensions();
}
bool ViewGLContext::Initialize(bool multisampled) {
if (multisampled) {
LOG(WARNING) << "Multisampling not implemented.";
}
Display* display = GetXDisplayHelper();
XWindowAttributes attributes;
XGetWindowAttributes(display, window_, &attributes);
XVisualInfo visual_info_template;
visual_info_template.visualid = XVisualIDFromVisual(attributes.visual);
int visual_info_count = 0;
scoped_ptr_malloc<XVisualInfo, ScopedPtrXFree> visual_info_list(
XGetVisualInfo(display, VisualIDMask,
&visual_info_template,
&visual_info_count));
DCHECK(visual_info_list.get());
DCHECK_GT(visual_info_count, 0);
context_ = NULL;
for (int i = 0; i < visual_info_count; ++i) {
context_ = glXCreateContext(display, visual_info_list.get() + i, 0, True);
if (context_)
break;
}
if (!context_) {
LOG(ERROR) << "Couldn't create GL context.";
return false;
}
if (!MakeCurrent()) {
Destroy();
LOG(ERROR) << "Couldn't make context current for initialization.";
return false;
}
if (!InitializeCommon()) {
LOG(ERROR) << "GLContext::InitlializeCommon failed.";
Destroy();
return false;
}
return true;
}
void ViewGLContext::Destroy() {
Display* display = GetXDisplayHelper();
bool result = glXMakeCurrent(display, 0, 0);
// glXMakeCurrent isn't supposed to fail when unsetting the context, unless
// we have pending draws on an invalid window - which shouldn't be the case
// here.
DCHECK(result);
if (context_) {
glXDestroyContext(display, context_);
context_ = NULL;
}
}
bool ViewGLContext::MakeCurrent() {
if (IsCurrent()) {
return true;
}
Display* display = GetXDisplayHelper();
if (glXMakeCurrent(display, window_, context_) != True) {
glXDestroyContext(display, context_);
context_ = 0;
LOG(ERROR) << "Couldn't make context current.";
return false;
}
return true;
}
bool ViewGLContext::IsCurrent() {
return glXGetCurrentDrawable() == window_ &&
glXGetCurrentContext() == context_;
}
bool ViewGLContext::IsOffscreen() {
return false;
}
bool ViewGLContext::SwapBuffers() {
Display* display = GetXDisplayHelper();
glXSwapBuffers(display, window_);
return true;
}
gfx::Size ViewGLContext::GetSize() {
XWindowAttributes attributes;
Display* display = GetXDisplayHelper();
XGetWindowAttributes(display, window_, &attributes);
return gfx::Size(attributes.width, attributes.height);
}
void* ViewGLContext::GetHandle() {
return context_;
}
void ViewGLContext::SetSwapInterval(int interval) {
DCHECK(IsCurrent());
if (HasExtension("GLX_EXT_swap_control") && glXSwapIntervalEXT) {
Display* display = GetXDisplayHelper();
glXSwapIntervalEXT(display, window_, interval);
}
}
bool OSMesaViewGLContext::Initialize() {
if (!osmesa_context_.Initialize(OSMESA_BGRA, NULL)) {
LOG(ERROR) << "OSMesaGLContext::Initialize failed.";
Destroy();
return false;
}
window_graphics_context_ = XCreateGC(GetXDisplayHelper(),
window_,
0,
NULL);
if (!window_graphics_context_) {
LOG(ERROR) << "XCreateGC failed.";
Destroy();
return false;
}
UpdateSize();
return true;
}
void OSMesaViewGLContext::Destroy() {
osmesa_context_.Destroy();
Display* display = GetXDisplayHelper();
if (pixmap_graphics_context_) {
XFreeGC(display, pixmap_graphics_context_);
pixmap_graphics_context_ = NULL;
}
if (pixmap_) {
XFreePixmap(display, pixmap_);
pixmap_ = 0;
}
if (window_graphics_context_) {
XFreeGC(display, window_graphics_context_);
window_graphics_context_ = NULL;
}
}
bool OSMesaViewGLContext::MakeCurrent() {
// TODO(apatrick): This is a bit of a hack. The window might have had zero
// size when the context was initialized. Assume it has a valid size when
// MakeCurrent is called and resize the back buffer if necessary.
UpdateSize();
return osmesa_context_.MakeCurrent();
}
bool OSMesaViewGLContext::IsCurrent() {
return osmesa_context_.IsCurrent();
}
bool OSMesaViewGLContext::IsOffscreen() {
return false;
}
bool OSMesaViewGLContext::SwapBuffers() {
// Update the size before blitting so that the blit size is exactly the same
// as the window.
if (!UpdateSize()) {
LOG(ERROR) << "Failed to update size of OSMesaGLContext.";
return false;
}
gfx::Size size = osmesa_context_.GetSize();
Display* display = GetXDisplayHelper();
// Copy the frame into the pixmap.
XWindowAttributes attributes;
XGetWindowAttributes(display, window_, &attributes);
ui::PutARGBImage(display,
attributes.visual,
attributes.depth,
pixmap_,
pixmap_graphics_context_,
static_cast<const uint8*>(osmesa_context_.buffer()),
size.width(),
size.height());
// Copy the pixmap to the window.
XCopyArea(display,
pixmap_,
window_,
window_graphics_context_,
0, 0,
size.width(), size.height(),
0, 0);
return true;
}
gfx::Size OSMesaViewGLContext::GetSize() {
return osmesa_context_.GetSize();
}
void* OSMesaViewGLContext::GetHandle() {
return osmesa_context_.GetHandle();
}
void OSMesaViewGLContext::SetSwapInterval(int interval) {
DCHECK(IsCurrent());
// Fail silently. It is legitimate to set the swap interval on a view context
// but XLib does not have those semantics.
}
bool OSMesaViewGLContext::UpdateSize() {
// Get the window size.
XWindowAttributes attributes;
Display* display = GetXDisplayHelper();
XGetWindowAttributes(display, window_, &attributes);
gfx::Size window_size = gfx::Size(std::max(1, attributes.width),
std::max(1, attributes.height));
// Early out if the size has not changed.
gfx::Size osmesa_size = osmesa_context_.GetSize();
if (pixmap_graphics_context_ && pixmap_ && window_size == osmesa_size)
return true;
// Change osmesa surface size to that of window.
osmesa_context_.Resize(window_size);
// Destroy the previous pixmap and graphics context.
if (pixmap_graphics_context_) {
XFreeGC(display, pixmap_graphics_context_);
pixmap_graphics_context_ = NULL;
}
if (pixmap_) {
XFreePixmap(display, pixmap_);
pixmap_ = 0;
}
// Recreate a pixmap to hold the frame.
pixmap_ = XCreatePixmap(display,
window_,
window_size.width(),
window_size.height(),
attributes.depth);
if (!pixmap_) {
LOG(ERROR) << "XCreatePixmap failed.";
return false;
}
// Recreate a graphics context for the pixmap.
pixmap_graphics_context_ = XCreateGC(display, pixmap_, 0, NULL);
if (!pixmap_graphics_context_) {
LOG(ERROR) << "XCreateGC failed";
return false;
}
return true;
}
GLContext* GLContext::CreateViewGLContext(gfx::PluginWindowHandle window,
bool multisampled) {
switch (GetGLImplementation()) {
case kGLImplementationDesktopGL: {
scoped_ptr<ViewGLContext> context(new ViewGLContext(window));
if (!context->Initialize(multisampled))
return NULL;
return context.release();
}
case kGLImplementationEGLGLES2: {
scoped_ptr<NativeViewEGLContext> context(
new NativeViewEGLContext(reinterpret_cast<void *>(window)));
if (!context->Initialize())
return NULL;
return context.release();
}
case kGLImplementationOSMesaGL: {
scoped_ptr<OSMesaViewGLContext> context(new OSMesaViewGLContext(window));
if (!context->Initialize())
return NULL;
return context.release();
}
case kGLImplementationMockGL:
return new StubGLContext;
default:
NOTREACHED();
return NULL;
}
}
bool PbufferGLContext::Initialize(GLContext* shared_context) {
static const int config_attributes[] = {
GLX_DRAWABLE_TYPE,
GLX_PBUFFER_BIT,
GLX_RENDER_TYPE,
GLX_RGBA_BIT,
GLX_DOUBLEBUFFER,
0,
0
};
Display* display = GetXDisplayHelper();
int nelements = 0;
// TODO(kbr): figure out whether hardcoding screen to 0 is sufficient.
scoped_ptr_malloc<GLXFBConfig, ScopedPtrXFree> config(
glXChooseFBConfig(display, 0, config_attributes, &nelements));
if (!config.get()) {
LOG(ERROR) << "glXChooseFBConfig failed.";
return false;
}
if (!nelements) {
LOG(ERROR) << "glXChooseFBConfig returned 0 elements.";
return false;
}
GLContextHandle shared_handle = NULL;
if (shared_context)
shared_handle = static_cast<GLContextHandle>(shared_context->GetHandle());
context_ = glXCreateNewContext(display,
config.get()[0],
GLX_RGBA_TYPE,
shared_handle,
True);
if (!context_) {
LOG(ERROR) << "glXCreateNewContext failed.";
return false;
}
static const int pbuffer_attributes[] = {
GLX_PBUFFER_WIDTH,
1,
GLX_PBUFFER_HEIGHT,
1,
0
};
pbuffer_ = glXCreatePbuffer(display,
config.get()[0], pbuffer_attributes);
if (!pbuffer_) {
Destroy();
LOG(ERROR) << "glXCreatePbuffer failed.";
return false;
}
if (!MakeCurrent()) {
Destroy();
LOG(ERROR) << "Couldn't make context current for initialization.";
return false;
}
if (!InitializeCommon()) {
LOG(ERROR) << "GLContext::InitializeCommon failed.";
Destroy();
return false;
}
return true;
}
void PbufferGLContext::Destroy() {
Display* display = GetXDisplayHelper();
bool result = glXMakeCurrent(display, 0, 0);
// glXMakeCurrent isn't supposed to fail when unsetting the context, unless
// we have pending draws on an invalid window - which shouldn't be the case
// here.
DCHECK(result);
if (context_) {
glXDestroyContext(display, context_);
context_ = NULL;
}
if (pbuffer_) {
glXDestroyPbuffer(display, pbuffer_);
pbuffer_ = 0;
}
}
bool PbufferGLContext::MakeCurrent() {
if (IsCurrent()) {
return true;
}
Display* display = GetXDisplayHelper();
if (glXMakeCurrent(display, pbuffer_, context_) != True) {
glXDestroyContext(display, context_);
context_ = NULL;
LOG(ERROR) << "Couldn't make context current.";
return false;
}
return true;
}
bool PbufferGLContext::IsCurrent() {
return glXGetCurrentDrawable() == pbuffer_ &&
glXGetCurrentContext() == context_;
}
bool PbufferGLContext::IsOffscreen() {
return true;
}
bool PbufferGLContext::SwapBuffers() {
NOTREACHED() << "Attempted to call SwapBuffers on a pbuffer.";
return false;
}
gfx::Size PbufferGLContext::GetSize() {
NOTREACHED() << "Should not be requesting size of this pbuffer.";
return gfx::Size(1, 1);
}
void* PbufferGLContext::GetHandle() {
return context_;
}
void PbufferGLContext::SetSwapInterval(int interval) {
DCHECK(IsCurrent());
NOTREACHED();
}
bool PixmapGLContext::Initialize(GLContext* shared_context) {
VLOG(1) << "GL context: using pixmaps.";
static int attributes[] = {
GLX_RGBA,
0
};
Display* display = GetXDisplayHelper();
int screen = DefaultScreen(display);
scoped_ptr_malloc<XVisualInfo, ScopedPtrXFree> visual_info(
glXChooseVisual(display, screen, attributes));
if (!visual_info.get()) {
LOG(ERROR) << "glXChooseVisual failed.";
return false;
}
GLContextHandle shared_handle = NULL;
if (shared_context)
shared_handle = static_cast<GLContextHandle>(shared_context->GetHandle());
context_ = glXCreateContext(display, visual_info.get(), shared_handle, True);
if (!context_) {
LOG(ERROR) << "glXCreateContext failed.";
return false;
}
pixmap_ = XCreatePixmap(display, RootWindow(display, screen), 1, 1,
visual_info->depth);
if (!pixmap_) {
LOG(ERROR) << "XCreatePixmap failed.";
return false;
}
glx_pixmap_ = glXCreateGLXPixmap(display, visual_info.get(), pixmap_);
if (!glx_pixmap_) {
LOG(ERROR) << "XCreatePixmap failed.";
return false;
}
if (!MakeCurrent()) {
Destroy();
LOG(ERROR) << "Couldn't make context current for initialization.";
return false;
}
if (!InitializeCommon()) {
LOG(ERROR) << "GLContext::InitializeCommon failed.";
Destroy();
return false;
}
return true;
}
void PixmapGLContext::Destroy() {
Display* display = GetXDisplayHelper();
bool result = glXMakeCurrent(display, 0, 0);
// glXMakeCurrent isn't supposed to fail when unsetting the context, unless
// we have pending draws on an invalid window - which shouldn't be the case
// here.
DCHECK(result);
if (context_) {
glXDestroyContext(display, context_);
context_ = NULL;
}
if (glx_pixmap_) {
glXDestroyGLXPixmap(display, glx_pixmap_);
glx_pixmap_ = 0;
}
if (pixmap_) {
XFreePixmap(display, pixmap_);
pixmap_ = 0;
}
}
bool PixmapGLContext::MakeCurrent() {
if (IsCurrent()) {
return true;
}
Display* display = GetXDisplayHelper();
if (glXMakeCurrent(display, glx_pixmap_, context_) != True) {
glXDestroyContext(display, context_);
context_ = NULL;
LOG(ERROR) << "Couldn't make context current.";
return false;
}
return true;
}
bool PixmapGLContext::IsCurrent() {
return glXGetCurrentDrawable() == glx_pixmap_ &&
glXGetCurrentContext() == context_;
}
bool PixmapGLContext::IsOffscreen() {
return true;
}
bool PixmapGLContext::SwapBuffers() {
NOTREACHED() << "Attempted to call SwapBuffers on a pixmap.";
return false;
}
gfx::Size PixmapGLContext::GetSize() {
NOTREACHED() << "Should not be requesting size of this pixmap.";
return gfx::Size(1, 1);
}
void* PixmapGLContext::GetHandle() {
return context_;
}
void PixmapGLContext::SetSwapInterval(int interval) {
DCHECK(IsCurrent());
NOTREACHED();
}
GLContext* GLContext::CreateOffscreenGLContext(GLContext* shared_context) {
switch (GetGLImplementation()) {
case kGLImplementationDesktopGL: {
scoped_ptr<PbufferGLContext> context(new PbufferGLContext);
if (context->Initialize(shared_context))
return context.release();
scoped_ptr<PixmapGLContext> context_pixmap(new PixmapGLContext);
if (context_pixmap->Initialize(shared_context))
return context_pixmap.release();
return NULL;
}
case kGLImplementationEGLGLES2: {
scoped_ptr<SecondaryEGLContext> context(
new SecondaryEGLContext());
if (!context->Initialize(shared_context))
return NULL;
return context.release();
}
case kGLImplementationOSMesaGL: {
scoped_ptr<OSMesaGLContext> context(new OSMesaGLContext);
if (!context->Initialize(OSMESA_RGBA, shared_context))
return NULL;
return context.release();
}
case kGLImplementationMockGL:
return new StubGLContext;
default:
NOTREACHED();
return NULL;
}
}
} // namespace gfx