blob: 8f0fe89e715f3c332ab4d1b87ac52e11b1a66a8e [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.
extern "C" {
#include <X11/Xlib.h>
}
#include "ui/gl/gl_surface_glx.h"
#include "base/basictypes.h"
#include "base/debug/trace_event.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/synchronization/cancellation_flag.h"
#include "base/synchronization/lock.h"
#include "base/threading/non_thread_safe.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
#include "third_party/mesa/src/include/GL/osmesa.h"
#include "ui/gfx/x/x11_types.h"
#include "ui/gl/gl_bindings.h"
#include "ui/gl/gl_implementation.h"
#include "ui/gl/sync_control_vsync_provider.h"
namespace gfx {
namespace {
// 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);
}
};
Display* g_display = NULL;
const char* g_glx_extensions = NULL;
bool g_glx_context_create = false;
bool g_glx_create_context_robustness_supported = false;
bool g_glx_texture_from_pixmap_supported = false;
bool g_glx_oml_sync_control_supported = false;
// Track support of glXGetMscRateOML separately from GLX_OML_sync_control as a
// whole since on some platforms (e.g. crosbug.com/34585), glXGetMscRateOML
// always fails even though GLX_OML_sync_control is reported as being supported.
bool g_glx_get_msc_rate_oml_supported = false;
bool g_glx_sgi_video_sync_supported = false;
class OMLSyncControlVSyncProvider
: public gfx::SyncControlVSyncProvider {
public:
explicit OMLSyncControlVSyncProvider(gfx::AcceleratedWidget window)
: SyncControlVSyncProvider(),
window_(window) {
}
virtual ~OMLSyncControlVSyncProvider() { }
protected:
virtual bool GetSyncValues(int64* system_time,
int64* media_stream_counter,
int64* swap_buffer_counter) OVERRIDE {
return glXGetSyncValuesOML(g_display, window_, system_time,
media_stream_counter, swap_buffer_counter);
}
virtual bool GetMscRate(int32* numerator, int32* denominator) OVERRIDE {
if (!g_glx_get_msc_rate_oml_supported)
return false;
if (!glXGetMscRateOML(g_display, window_, numerator, denominator)) {
// Once glXGetMscRateOML has been found to fail, don't try again,
// since each failing call may spew an error message.
g_glx_get_msc_rate_oml_supported = false;
return false;
}
return true;
}
private:
XID window_;
DISALLOW_COPY_AND_ASSIGN(OMLSyncControlVSyncProvider);
};
class SGIVideoSyncThread
: public base::Thread,
public base::NonThreadSafe,
public base::RefCounted<SGIVideoSyncThread> {
public:
static scoped_refptr<SGIVideoSyncThread> Create() {
if (!g_video_sync_thread) {
g_video_sync_thread = new SGIVideoSyncThread();
g_video_sync_thread->Start();
}
return g_video_sync_thread;
}
private:
friend class base::RefCounted<SGIVideoSyncThread>;
SGIVideoSyncThread() : base::Thread("SGI_video_sync") {
DCHECK(CalledOnValidThread());
}
virtual ~SGIVideoSyncThread() {
DCHECK(CalledOnValidThread());
g_video_sync_thread = NULL;
Stop();
}
static SGIVideoSyncThread* g_video_sync_thread;
DISALLOW_COPY_AND_ASSIGN(SGIVideoSyncThread);
};
class SGIVideoSyncProviderThreadShim {
public:
explicit SGIVideoSyncProviderThreadShim(XID window)
: window_(window),
context_(NULL),
message_loop_(base::MessageLoopProxy::current()),
cancel_vsync_flag_(),
vsync_lock_() {
// This ensures that creation of |window_| has occured when this shim
// is executing in the same process as the call to create |window_|.
XSync(g_display, False);
}
virtual ~SGIVideoSyncProviderThreadShim() {
if (context_) {
glXDestroyContext(display_, context_);
context_ = NULL;
}
}
base::CancellationFlag* cancel_vsync_flag() {
return &cancel_vsync_flag_;
}
base::Lock* vsync_lock() {
return &vsync_lock_;
}
void Initialize() {
DCHECK(display_);
XWindowAttributes attributes;
if (!XGetWindowAttributes(display_, window_, &attributes)) {
LOG(ERROR) << "XGetWindowAttributes failed for window " <<
window_ << ".";
return;
}
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());
if (visual_info_count == 0) {
LOG(ERROR) << "No visual info for visual ID.";
return;
}
context_ = glXCreateContext(display_, visual_info_list.get(), NULL, True);
DCHECK(NULL != context_);
}
void GetVSyncParameters(const VSyncProvider::UpdateVSyncCallback& callback) {
base::TimeTicks now;
{
// Don't allow |window_| destruction while we're probing vsync.
base::AutoLock locked(vsync_lock_);
if (!context_ || cancel_vsync_flag_.IsSet())
return;
glXMakeCurrent(display_, window_, context_);
unsigned int retrace_count = 0;
if (glXWaitVideoSyncSGI(1, 0, &retrace_count) != 0)
return;
TRACE_EVENT_INSTANT0("gpu", "vblank", TRACE_EVENT_SCOPE_THREAD);
now = base::TimeTicks::HighResNow();
glXMakeCurrent(display_, 0, 0);
}
const base::TimeDelta kDefaultInterval =
base::TimeDelta::FromSeconds(1) / 60;
message_loop_->PostTask(
FROM_HERE, base::Bind(callback, now, kDefaultInterval));
}
private:
// For initialization of display_ in GLSurface::InitializeOneOff before
// the sandbox goes up.
friend class gfx::GLSurfaceGLX;
static Display* display_;
XID window_;
GLXContext context_;
scoped_refptr<base::MessageLoopProxy> message_loop_;
base::CancellationFlag cancel_vsync_flag_;
base::Lock vsync_lock_;
DISALLOW_COPY_AND_ASSIGN(SGIVideoSyncProviderThreadShim);
};
class SGIVideoSyncVSyncProvider
: public gfx::VSyncProvider,
public base::SupportsWeakPtr<SGIVideoSyncVSyncProvider> {
public:
explicit SGIVideoSyncVSyncProvider(gfx::AcceleratedWidget window)
: vsync_thread_(SGIVideoSyncThread::Create()),
shim_(new SGIVideoSyncProviderThreadShim(window)),
cancel_vsync_flag_(shim_->cancel_vsync_flag()),
vsync_lock_(shim_->vsync_lock()) {
vsync_thread_->message_loop()->PostTask(
FROM_HERE,
base::Bind(&SGIVideoSyncProviderThreadShim::Initialize,
base::Unretained(shim_.get())));
}
virtual ~SGIVideoSyncVSyncProvider() {
{
base::AutoLock locked(*vsync_lock_);
cancel_vsync_flag_->Set();
}
// Hand-off |shim_| to be deleted on the |vsync_thread_|.
vsync_thread_->message_loop()->DeleteSoon(
FROM_HERE,
shim_.release());
}
virtual void GetVSyncParameters(
const VSyncProvider::UpdateVSyncCallback& callback) OVERRIDE {
// Only one outstanding request per surface.
if (!pending_callback_) {
pending_callback_.reset(
new VSyncProvider::UpdateVSyncCallback(callback));
vsync_thread_->message_loop()->PostTask(
FROM_HERE,
base::Bind(&SGIVideoSyncProviderThreadShim::GetVSyncParameters,
base::Unretained(shim_.get()),
base::Bind(
&SGIVideoSyncVSyncProvider::PendingCallbackRunner,
AsWeakPtr())));
}
}
private:
void PendingCallbackRunner(const base::TimeTicks timebase,
const base::TimeDelta interval) {
DCHECK(pending_callback_);
pending_callback_->Run(timebase, interval);
pending_callback_.reset();
}
scoped_refptr<SGIVideoSyncThread> vsync_thread_;
// Thread shim through which the sync provider is accessed on |vsync_thread_|.
scoped_ptr<SGIVideoSyncProviderThreadShim> shim_;
scoped_ptr<VSyncProvider::UpdateVSyncCallback> pending_callback_;
// Raw pointers to sync primitives owned by the shim_.
// These will only be referenced before we post a task to destroy
// the shim_, so they are safe to access.
base::CancellationFlag* cancel_vsync_flag_;
base::Lock* vsync_lock_;
DISALLOW_COPY_AND_ASSIGN(SGIVideoSyncVSyncProvider);
};
SGIVideoSyncThread* SGIVideoSyncThread::g_video_sync_thread = NULL;
// In order to take advantage of GLX_SGI_video_sync, we need a display
// for use on a separate thread. We must allocate this before the sandbox
// goes up (rather than on-demand when we start the thread).
Display* SGIVideoSyncProviderThreadShim::display_ = NULL;
#if defined(TOOLKIT_GTK)
// A mechanism for forwarding XExpose events from one window to another.
// Because in the workaround for http://crbug.com/145600 the child window
// is placed on top of the parent window, only the child window will receive
// all expose events. These need to be forwared to the parent window to inform
// it that it should paint.
class XExposeEventForwarder : public base::MessagePumpObserver {
public:
XExposeEventForwarder() {}
virtual ~XExposeEventForwarder() {
DCHECK(child_to_parent_map_.empty());
}
void AddParentChildPair(gfx::AcceleratedWidget parent_window,
gfx::AcceleratedWidget child_window) {
if (child_to_parent_map_.empty())
base::MessagePumpX11::Current()->AddObserver(this);
DCHECK(child_to_parent_map_.find(child_window) ==
child_to_parent_map_.end());
child_to_parent_map_.insert(std::make_pair(
child_window, parent_window));
}
void RemoveParentChildPair(gfx::AcceleratedWidget parent_window,
gfx::AcceleratedWidget child_window) {
DCHECK(child_to_parent_map_.find(child_window) !=
child_to_parent_map_.end());
child_to_parent_map_.erase(child_window);
if (child_to_parent_map_.empty())
base::MessagePumpX11::Current()->RemoveObserver(this);
}
private:
virtual base::EventStatus WillProcessEvent (
const base::NativeEvent& xevent) OVERRIDE {
if (xevent->type != Expose)
return base::EVENT_CONTINUE;
WindowMap::const_iterator found = child_to_parent_map_.find(
xevent->xexpose.window);
if (found == child_to_parent_map_.end())
return base::EVENT_CONTINUE;
gfx::AcceleratedWidget target_window = found->second;
XEvent forwarded_event = *xevent;
forwarded_event.xexpose.window = target_window;
XSendEvent(g_display, target_window, False, ExposureMask,
&forwarded_event);
return base::EVENT_CONTINUE;
}
virtual void DidProcessEvent(const base::NativeEvent& xevent) OVERRIDE {
}
typedef std::map<gfx::AcceleratedWidget, gfx::AcceleratedWidget> WindowMap;
WindowMap child_to_parent_map_;
DISALLOW_COPY_AND_ASSIGN(XExposeEventForwarder);
};
static base::LazyInstance<XExposeEventForwarder> g_xexpose_event_forwarder =
LAZY_INSTANCE_INITIALIZER;
// Do not use this workaround when running in test harnesses that do not have
// a message loop or do not have a TYPE_GPU message loop.
bool g_create_child_windows = false;
#endif
} // namespace
GLSurfaceGLX::GLSurfaceGLX() {}
bool GLSurfaceGLX::InitializeOneOff() {
static bool initialized = false;
if (initialized)
return true;
// http://crbug.com/245466
setenv("force_s3tc_enable", "true", 1);
// SGIVideoSyncProviderShim (if instantiated) will issue X commands on
// it's own thread.
XInitThreads();
#if defined(TOOLKIT_GTK)
// Be sure to use the X display handle and not the GTK display handle if this
// is the GPU process.
g_create_child_windows =
base::MessageLoop::current() &&
base::MessageLoop::current()->type() == base::MessageLoop::TYPE_GPU;
if (g_create_child_windows)
g_display = base::MessagePumpX11::GetDefaultXDisplay();
else
g_display = base::MessagePumpForUI::GetDefaultXDisplay();
#else
g_display = base::MessagePumpForUI::GetDefaultXDisplay();
#endif
if (!g_display) {
LOG(ERROR) << "XOpenDisplay failed.";
return false;
}
int major, minor;
if (!glXQueryVersion(g_display, &major, &minor)) {
LOG(ERROR) << "glxQueryVersion failed";
return false;
}
if (major == 1 && minor < 3) {
LOG(ERROR) << "GLX 1.3 or later is required.";
return false;
}
g_glx_extensions = glXQueryExtensionsString(g_display, 0);
g_glx_context_create =
HasGLXExtension("GLX_ARB_create_context");
g_glx_create_context_robustness_supported =
HasGLXExtension("GLX_ARB_create_context_robustness");
g_glx_texture_from_pixmap_supported =
HasGLXExtension("GLX_EXT_texture_from_pixmap");
g_glx_oml_sync_control_supported =
HasGLXExtension("GLX_OML_sync_control");
g_glx_get_msc_rate_oml_supported = g_glx_oml_sync_control_supported;
g_glx_sgi_video_sync_supported =
HasGLXExtension("GLX_SGI_video_sync");
if (!g_glx_get_msc_rate_oml_supported && g_glx_sgi_video_sync_supported)
SGIVideoSyncProviderThreadShim::display_ = XOpenDisplay(NULL);
initialized = true;
return true;
}
// static
const char* GLSurfaceGLX::GetGLXExtensions() {
return g_glx_extensions;
}
// static
bool GLSurfaceGLX::HasGLXExtension(const char* name) {
return ExtensionsContain(GetGLXExtensions(), name);
}
// static
bool GLSurfaceGLX::IsCreateContextSupported() {
return g_glx_context_create;
}
// static
bool GLSurfaceGLX::IsCreateContextRobustnessSupported() {
return g_glx_create_context_robustness_supported;
}
// static
bool GLSurfaceGLX::IsTextureFromPixmapSupported() {
return g_glx_texture_from_pixmap_supported;
}
// static
bool GLSurfaceGLX::IsOMLSyncControlSupported() {
return g_glx_oml_sync_control_supported;
}
void* GLSurfaceGLX::GetDisplay() {
return g_display;
}
GLSurfaceGLX::~GLSurfaceGLX() {}
#if defined(TOOLKIT_GTK)
bool NativeViewGLSurfaceGLX::SetBackbufferAllocation(bool allocated) {
backbuffer_allocated_ = allocated;
AdjustBufferAllocation();
return true;
}
void NativeViewGLSurfaceGLX::SetFrontbufferAllocation(bool allocated) {
frontbuffer_allocated_ = allocated;
AdjustBufferAllocation();
}
void NativeViewGLSurfaceGLX::AdjustBufferAllocation() {
if (!g_create_child_windows)
return;
if (frontbuffer_allocated_ || backbuffer_allocated_)
CreateChildWindow();
else
DestroyChildWindow();
}
void NativeViewGLSurfaceGLX::CreateChildWindow() {
DCHECK(g_create_child_windows);
if (child_window_)
return;
XSetWindowAttributes set_window_attributes;
set_window_attributes.event_mask = ExposureMask;
child_window_ = XCreateWindow(
g_display, parent_window_, 0, 0, size_.width(), size_.height(), 0,
CopyFromParent, InputOutput, CopyFromParent, CWEventMask,
&set_window_attributes);
g_xexpose_event_forwarder.Pointer()->AddParentChildPair(
parent_window_, child_window_);
XMapWindow(g_display, child_window_);
XFlush(g_display);
}
void NativeViewGLSurfaceGLX::DestroyChildWindow() {
if (!child_window_)
return;
g_xexpose_event_forwarder.Pointer()->RemoveParentChildPair(
parent_window_, child_window_);
XDestroyWindow(g_display, child_window_);
XFlush(g_display);
child_window_ = 0;
}
#endif
NativeViewGLSurfaceGLX::NativeViewGLSurfaceGLX(gfx::AcceleratedWidget window)
: parent_window_(window),
#if defined(TOOLKIT_GTK)
child_window_(0),
dummy_window_(0),
backbuffer_allocated_(true),
frontbuffer_allocated_(true),
#endif
config_(NULL) {
}
gfx::AcceleratedWidget NativeViewGLSurfaceGLX::GetDrawableHandle() const {
#if defined(TOOLKIT_GTK)
if (g_create_child_windows) {
if (child_window_)
return child_window_;
return dummy_window_;
}
#endif
return parent_window_;
}
bool NativeViewGLSurfaceGLX::Initialize() {
XWindowAttributes attributes;
if (!XGetWindowAttributes(g_display, parent_window_, &attributes)) {
LOG(ERROR) << "XGetWindowAttributes failed for window " << parent_window_
<< ".";
return false;
}
size_ = gfx::Size(attributes.width, attributes.height);
gfx::AcceleratedWidget window_for_vsync = parent_window_;
#if defined(TOOLKIT_GTK)
if (g_create_child_windows) {
dummy_window_ = XCreateWindow(
g_display,
RootWindow(g_display, XScreenNumberOfScreen(attributes.screen)),
0, 0, 1, 1, 0, CopyFromParent, InputOutput, attributes.visual, 0, NULL);
window_for_vsync = dummy_window_;
CreateChildWindow();
}
#endif
if (g_glx_oml_sync_control_supported)
vsync_provider_.reset(new OMLSyncControlVSyncProvider(window_for_vsync));
else if (g_glx_sgi_video_sync_supported)
vsync_provider_.reset(new SGIVideoSyncVSyncProvider(window_for_vsync));
return true;
}
void NativeViewGLSurfaceGLX::Destroy() {
#if defined(TOOLKIT_GTK)
DestroyChildWindow();
if (dummy_window_)
XDestroyWindow(g_display, dummy_window_);
dummy_window_ = 0;
#endif
}
bool NativeViewGLSurfaceGLX::Resize(const gfx::Size& size) {
#if defined(TOOLKIT_GTK)
if (child_window_) {
XResizeWindow(g_display, child_window_, size.width(), size.height());
XFlush(g_display);
}
#endif
size_ = size;
return true;
}
bool NativeViewGLSurfaceGLX::IsOffscreen() {
return false;
}
bool NativeViewGLSurfaceGLX::SwapBuffers() {
glXSwapBuffers(g_display, GetDrawableHandle());
return true;
}
gfx::Size NativeViewGLSurfaceGLX::GetSize() {
return size_;
}
void* NativeViewGLSurfaceGLX::GetHandle() {
return reinterpret_cast<void*>(GetDrawableHandle());
}
std::string NativeViewGLSurfaceGLX::GetExtensions() {
std::string extensions = GLSurface::GetExtensions();
if (gfx::g_driver_glx.ext.b_GLX_MESA_copy_sub_buffer) {
extensions += extensions.empty() ? "" : " ";
extensions += "GL_CHROMIUM_post_sub_buffer";
}
return extensions;
}
void* NativeViewGLSurfaceGLX::GetConfig() {
if (!config_) {
// This code path is expensive, but we only take it when
// attempting to use GLX_ARB_create_context_robustness, in which
// case we need a GLXFBConfig for the window in order to create a
// context for it.
//
// TODO(kbr): this is not a reliable code path. On platforms which
// support it, we should use glXChooseFBConfig in the browser
// process to choose the FBConfig and from there the X Visual to
// use when creating the window in the first place. Then we can
// pass that FBConfig down rather than attempting to reconstitute
// it.
XWindowAttributes attributes;
if (!XGetWindowAttributes(
g_display,
parent_window_,
&attributes)) {
LOG(ERROR) << "XGetWindowAttributes failed for window " <<
parent_window_ << ".";
return NULL;
}
int visual_id = XVisualIDFromVisual(attributes.visual);
int num_elements = 0;
scoped_ptr_malloc<GLXFBConfig, ScopedPtrXFree> configs(
glXGetFBConfigs(g_display,
DefaultScreen(g_display),
&num_elements));
if (!configs.get()) {
LOG(ERROR) << "glXGetFBConfigs failed.";
return NULL;
}
if (!num_elements) {
LOG(ERROR) << "glXGetFBConfigs returned 0 elements.";
return NULL;
}
bool found = false;
int i;
for (i = 0; i < num_elements; ++i) {
int value;
if (glXGetFBConfigAttrib(
g_display, configs.get()[i], GLX_VISUAL_ID, &value)) {
LOG(ERROR) << "glXGetFBConfigAttrib failed.";
return NULL;
}
if (value == visual_id) {
found = true;
break;
}
}
if (found) {
config_ = configs.get()[i];
}
}
return config_;
}
bool NativeViewGLSurfaceGLX::PostSubBuffer(
int x, int y, int width, int height) {
DCHECK(gfx::g_driver_glx.ext.b_GLX_MESA_copy_sub_buffer);
glXCopySubBufferMESA(g_display, GetDrawableHandle(), x, y, width, height);
return true;
}
VSyncProvider* NativeViewGLSurfaceGLX::GetVSyncProvider() {
return vsync_provider_.get();
}
NativeViewGLSurfaceGLX::NativeViewGLSurfaceGLX()
: parent_window_(0),
#if defined(TOOLKIT_GTK)
child_window_(0),
dummy_window_(0),
backbuffer_allocated_(true),
frontbuffer_allocated_(true),
#endif
config_(NULL) {
}
NativeViewGLSurfaceGLX::~NativeViewGLSurfaceGLX() {
Destroy();
}
PbufferGLSurfaceGLX::PbufferGLSurfaceGLX(const gfx::Size& size)
: size_(size),
config_(NULL),
pbuffer_(0) {
}
bool PbufferGLSurfaceGLX::Initialize() {
DCHECK(!pbuffer_);
static const int config_attributes[] = {
GLX_BUFFER_SIZE, 32,
GLX_ALPHA_SIZE, 8,
GLX_BLUE_SIZE, 8,
GLX_GREEN_SIZE, 8,
GLX_RED_SIZE, 8,
GLX_RENDER_TYPE, GLX_RGBA_BIT,
GLX_DRAWABLE_TYPE, GLX_PBUFFER_BIT,
GLX_DOUBLEBUFFER, False,
0
};
int num_elements = 0;
scoped_ptr_malloc<GLXFBConfig, ScopedPtrXFree> configs(
glXChooseFBConfig(g_display,
DefaultScreen(g_display),
config_attributes,
&num_elements));
if (!configs.get()) {
LOG(ERROR) << "glXChooseFBConfig failed.";
return false;
}
if (!num_elements) {
LOG(ERROR) << "glXChooseFBConfig returned 0 elements.";
return false;
}
config_ = configs.get()[0];
const int pbuffer_attributes[] = {
GLX_PBUFFER_WIDTH, size_.width(),
GLX_PBUFFER_HEIGHT, size_.height(),
0
};
pbuffer_ = glXCreatePbuffer(g_display,
static_cast<GLXFBConfig>(config_),
pbuffer_attributes);
if (!pbuffer_) {
Destroy();
LOG(ERROR) << "glXCreatePbuffer failed.";
return false;
}
return true;
}
void PbufferGLSurfaceGLX::Destroy() {
if (pbuffer_) {
glXDestroyPbuffer(g_display, pbuffer_);
pbuffer_ = 0;
}
config_ = NULL;
}
bool PbufferGLSurfaceGLX::IsOffscreen() {
return true;
}
bool PbufferGLSurfaceGLX::SwapBuffers() {
NOTREACHED() << "Attempted to call SwapBuffers on a pbuffer.";
return false;
}
gfx::Size PbufferGLSurfaceGLX::GetSize() {
return size_;
}
void* PbufferGLSurfaceGLX::GetHandle() {
return reinterpret_cast<void*>(pbuffer_);
}
void* PbufferGLSurfaceGLX::GetConfig() {
return config_;
}
PbufferGLSurfaceGLX::~PbufferGLSurfaceGLX() {
Destroy();
}
} // namespace gfx