// Copyright (c) 2010 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/x11_util.h" #include "base/logging.h" #include "base/scoped_ptr.h" #include "app/gfx/gl/gl_bindings.h" #include "app/gfx/gl/gl_context.h" #include "app/gfx/gl/gl_context_osmesa.h" #include "app/gfx/gl/gl_context_stub.h" #include "app/gfx/gl/gl_implementation.h" namespace gfx { typedef GLXContext GLContextHandle; typedef GLXPbuffer PbufferHandle; // 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 void SwapBuffers(); virtual gfx::Size GetSize(); virtual void* GetHandle(); private: gfx::PluginWindowHandle window_; GLContextHandle context_; DISALLOW_COPY_AND_ASSIGN(ViewGLContext); }; // 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 void SwapBuffers(); virtual gfx::Size GetSize(); virtual void* GetHandle(); 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 void SwapBuffers(); virtual gfx::Size GetSize(); virtual void* GetHandle(); private: GLContextHandle context_; Pixmap pixmap_; GLXPixmap glx_pixmap_; DISALLOW_COPY_AND_ASSIGN(PixmapGLContext); }; // scoped_ptr functor for XFree(). Use as follows: // scoped_ptr_malloc foo(...); // where "XVisualInfo" is any X type that is freed with XFree. class ScopedPtrXFree { public: void operator()(void* x) const { ::XFree(x); } }; static bool InitializeOneOff() { static bool initialized = false; if (initialized) return true; // Initialize the GL bindings if they haven't already been initialized. If // the GPU unit tests are running, the mock GL implementation will already // have been initialized. if (!InitializeGLBindings(kGLImplementationDesktopGL)) { LOG(ERROR) << "Could not initialize GL."; return false; } // Only check the GLX version if we are in fact using GLX. We might actually // be using the mock GL implementation. if (GetGLImplementation() == kGLImplementationDesktopGL) { Display* display = x11_util::GetXDisplay(); 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."; } } initialized = true; return true; } bool ViewGLContext::Initialize(bool multisampled) { if (multisampled) { LOG(WARNING) << "Multisampling not implemented."; } Display* display = x11_util::GetXDisplay(); 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 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()) { Destroy(); return false; } return true; } void ViewGLContext::Destroy() { Display* display = x11_util::GetXDisplay(); 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 = x11_util::GetXDisplay(); 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; } void ViewGLContext::SwapBuffers() { Display* display = x11_util::GetXDisplay(); glXSwapBuffers(display, window_); } gfx::Size ViewGLContext::GetSize() { XWindowAttributes attributes; Display* display = x11_util::GetXDisplay(); XGetWindowAttributes(display, window_, &attributes); return gfx::Size(attributes.width, attributes.height); } void* ViewGLContext::GetHandle() { return context_; } GLContext* GLContext::CreateViewGLContext(gfx::PluginWindowHandle window, bool multisampled) { if (!InitializeOneOff()) return NULL; switch (GetGLImplementation()) { case kGLImplementationDesktopGL: { scoped_ptr context(new ViewGLContext(window)); if (!context->Initialize(multisampled)) 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 = x11_util::GetXDisplay(); int nelements = 0; // TODO(kbr): figure out whether hardcoding screen to 0 is sufficient. scoped_ptr_malloc 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(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()) { Destroy(); return false; } return true; } void PbufferGLContext::Destroy() { Display* display = x11_util::GetXDisplay(); 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 = x11_util::GetXDisplay(); 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; } void PbufferGLContext::SwapBuffers() { NOTREACHED() << "Attempted to call SwapBuffers on a pbuffer."; } gfx::Size PbufferGLContext::GetSize() { NOTREACHED() << "Should not be requesting size of this pbuffer."; return gfx::Size(1, 1); } void* PbufferGLContext::GetHandle() { return context_; } bool PixmapGLContext::Initialize(GLContext* shared_context) { LOG(INFO) << "GL context: using pixmaps."; static int attributes[] = { GLX_RGBA, 0 }; Display* display = x11_util::GetXDisplay(); int screen = DefaultScreen(display); scoped_ptr_malloc 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(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()) { Destroy(); return false; } return true; } void PixmapGLContext::Destroy() { Display* display = x11_util::GetXDisplay(); 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 = x11_util::GetXDisplay(); 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; } void PixmapGLContext::SwapBuffers() { NOTREACHED() << "Attempted to call SwapBuffers on a pixmap."; } gfx::Size PixmapGLContext::GetSize() { NOTREACHED() << "Should not be requesting size of this pixmap."; return gfx::Size(1, 1); } void* PixmapGLContext::GetHandle() { return context_; } GLContext* GLContext::CreateOffscreenGLContext(GLContext* shared_context) { if (!InitializeOneOff()) return NULL; switch (GetGLImplementation()) { case kGLImplementationDesktopGL: { scoped_ptr context(new PbufferGLContext); if (context->Initialize(shared_context)) return context.release(); scoped_ptr context_pixmap(new PixmapGLContext); if (context_pixmap->Initialize(shared_context)) return context_pixmap.release(); return NULL; } case kGLImplementationMockGL: return new StubGLContext; default: NOTREACHED(); return NULL; } } } // namespace gfx