1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5extern "C" {
6#include <X11/Xlib.h>
7}
8
9#include "ui/gl/gl_surface_glx.h"
10
11#include "base/basictypes.h"
12#include "base/debug/trace_event.h"
13#include "base/logging.h"
14#include "base/memory/scoped_ptr.h"
15#include "base/memory/weak_ptr.h"
16#include "base/message_loop/message_loop.h"
17#include "base/synchronization/cancellation_flag.h"
18#include "base/synchronization/lock.h"
19#include "base/threading/non_thread_safe.h"
20#include "base/threading/thread.h"
21#include "base/time/time.h"
22#include "third_party/mesa/src/include/GL/osmesa.h"
23#include "ui/base/x/x11_util.h"
24#include "ui/gl/gl_bindings.h"
25#include "ui/gl/gl_implementation.h"
26#include "ui/gl/vsync_provider.h"
27
28namespace gfx {
29
30namespace {
31
32// scoped_ptr functor for XFree(). Use as follows:
33//   scoped_ptr_malloc<XVisualInfo, ScopedPtrXFree> foo(...);
34// where "XVisualInfo" is any X type that is freed with XFree.
35class ScopedPtrXFree {
36 public:
37  void operator()(void* x) const {
38    ::XFree(x);
39  }
40};
41
42Display* g_display;
43const char* g_glx_extensions = NULL;
44bool g_glx_context_create = false;
45bool g_glx_create_context_robustness_supported = false;
46bool g_glx_texture_from_pixmap_supported = false;
47bool g_glx_oml_sync_control_supported = false;
48
49// Track support of glXGetMscRateOML separately from GLX_OML_sync_control as a
50// whole since on some platforms (e.g. crosbug.com/34585), glXGetMscRateOML
51// always fails even though GLX_OML_sync_control is reported as being supported.
52bool g_glx_get_msc_rate_oml_supported = false;
53
54bool g_glx_sgi_video_sync_supported = false;
55
56class OMLSyncControlVSyncProvider
57    : public gfx::SyncControlVSyncProvider {
58 public:
59  explicit OMLSyncControlVSyncProvider(gfx::AcceleratedWidget window)
60      : SyncControlVSyncProvider(),
61        window_(window) {
62  }
63
64  virtual ~OMLSyncControlVSyncProvider() { }
65
66 protected:
67  virtual bool GetSyncValues(int64* system_time,
68                             int64* media_stream_counter,
69                             int64* swap_buffer_counter) OVERRIDE {
70    return glXGetSyncValuesOML(g_display, window_, system_time,
71                               media_stream_counter, swap_buffer_counter);
72  }
73
74  virtual bool GetMscRate(int32* numerator, int32* denominator) OVERRIDE {
75    if (!g_glx_get_msc_rate_oml_supported)
76      return false;
77
78    if (!glXGetMscRateOML(g_display, window_, numerator, denominator)) {
79      // Once glXGetMscRateOML has been found to fail, don't try again,
80      // since each failing call may spew an error message.
81      g_glx_get_msc_rate_oml_supported = false;
82      return false;
83    }
84
85    return true;
86  }
87
88 private:
89  XID window_;
90
91  DISALLOW_COPY_AND_ASSIGN(OMLSyncControlVSyncProvider);
92};
93
94class SGIVideoSyncThread
95     : public base::Thread,
96       public base::NonThreadSafe,
97       public base::RefCounted<SGIVideoSyncThread> {
98 public:
99  static scoped_refptr<SGIVideoSyncThread> Create() {
100    if (!g_video_sync_thread) {
101      g_video_sync_thread = new SGIVideoSyncThread();
102      g_video_sync_thread->Start();
103    }
104    return g_video_sync_thread;
105  }
106
107 private:
108  friend class base::RefCounted<SGIVideoSyncThread>;
109
110  SGIVideoSyncThread() : base::Thread("SGI_video_sync") {
111    DCHECK(CalledOnValidThread());
112  }
113
114  virtual ~SGIVideoSyncThread() {
115    DCHECK(CalledOnValidThread());
116    g_video_sync_thread = NULL;
117    Stop();
118  }
119
120  static SGIVideoSyncThread* g_video_sync_thread;
121
122  DISALLOW_COPY_AND_ASSIGN(SGIVideoSyncThread);
123};
124
125class SGIVideoSyncProviderThreadShim {
126 public:
127  explicit SGIVideoSyncProviderThreadShim(XID window)
128      : window_(window),
129        context_(NULL),
130        message_loop_(base::MessageLoopProxy::current()),
131        cancel_vsync_flag_(),
132        vsync_lock_() {
133    // This ensures that creation of |window_| has occured when this shim
134    // is executing in the same process as the call to create |window_|.
135    XSync(g_display, False);
136  }
137
138  virtual ~SGIVideoSyncProviderThreadShim() {
139    if (context_) {
140      glXDestroyContext(display_, context_);
141      context_ = NULL;
142    }
143  }
144
145  base::CancellationFlag* cancel_vsync_flag() {
146    return &cancel_vsync_flag_;
147  }
148
149  base::Lock* vsync_lock() {
150    return &vsync_lock_;
151  }
152
153  void Initialize() {
154    DCHECK(display_);
155
156    XWindowAttributes attributes;
157    if (!XGetWindowAttributes(display_, window_, &attributes)) {
158      LOG(ERROR) << "XGetWindowAttributes failed for window " <<
159        window_ << ".";
160      return;
161    }
162
163    XVisualInfo visual_info_template;
164    visual_info_template.visualid = XVisualIDFromVisual(attributes.visual);
165
166    int visual_info_count = 0;
167    scoped_ptr_malloc<XVisualInfo, ScopedPtrXFree> visual_info_list(
168        XGetVisualInfo(display_, VisualIDMask,
169                       &visual_info_template, &visual_info_count));
170
171    DCHECK(visual_info_list.get());
172    if (visual_info_count == 0) {
173      LOG(ERROR) << "No visual info for visual ID.";
174      return;
175    }
176
177    context_ = glXCreateContext(display_, visual_info_list.get(), NULL, True);
178
179    DCHECK(NULL != context_);
180  }
181
182  void GetVSyncParameters(const VSyncProvider::UpdateVSyncCallback& callback) {
183    base::TimeTicks now;
184    {
185      // Don't allow |window_| destruction while we're probing vsync.
186      base::AutoLock locked(vsync_lock_);
187
188      if (!context_ || cancel_vsync_flag_.IsSet())
189        return;
190
191      glXMakeCurrent(display_, window_, context_);
192
193      unsigned int retrace_count = 0;
194      if (glXWaitVideoSyncSGI(1, 0, &retrace_count) != 0)
195        return;
196
197      TRACE_EVENT_INSTANT0("gpu", "vblank", TRACE_EVENT_SCOPE_THREAD);
198      now = base::TimeTicks::HighResNow();
199
200      glXMakeCurrent(display_, 0, 0);
201    }
202
203    const base::TimeDelta kDefaultInterval =
204        base::TimeDelta::FromSeconds(1) / 60;
205
206    message_loop_->PostTask(
207        FROM_HERE, base::Bind(callback, now, kDefaultInterval));
208  }
209
210 private:
211  // For initialization of display_ in GLSurface::InitializeOneOff before
212  // the sandbox goes up.
213  friend class gfx::GLSurfaceGLX;
214
215  static Display* display_;
216
217  XID window_;
218  GLXContext context_;
219
220  scoped_refptr<base::MessageLoopProxy> message_loop_;
221
222  base::CancellationFlag cancel_vsync_flag_;
223  base::Lock vsync_lock_;
224
225  DISALLOW_COPY_AND_ASSIGN(SGIVideoSyncProviderThreadShim);
226};
227
228class SGIVideoSyncVSyncProvider
229    : public gfx::VSyncProvider,
230      public base::SupportsWeakPtr<SGIVideoSyncVSyncProvider> {
231 public:
232  explicit SGIVideoSyncVSyncProvider(gfx::AcceleratedWidget window)
233      : vsync_thread_(SGIVideoSyncThread::Create()),
234        shim_(new SGIVideoSyncProviderThreadShim(window)),
235        cancel_vsync_flag_(shim_->cancel_vsync_flag()),
236        vsync_lock_(shim_->vsync_lock()) {
237    vsync_thread_->message_loop()->PostTask(
238        FROM_HERE,
239        base::Bind(&SGIVideoSyncProviderThreadShim::Initialize,
240                   base::Unretained(shim_.get())));
241  }
242
243  virtual ~SGIVideoSyncVSyncProvider() {
244    {
245      base::AutoLock locked(*vsync_lock_);
246      cancel_vsync_flag_->Set();
247    }
248
249    // Hand-off |shim_| to be deleted on the |vsync_thread_|.
250    vsync_thread_->message_loop()->DeleteSoon(
251        FROM_HERE,
252        shim_.release());
253  }
254
255  virtual void GetVSyncParameters(
256      const VSyncProvider::UpdateVSyncCallback& callback) OVERRIDE {
257    // Only one outstanding request per surface.
258    if (!pending_callback_) {
259      pending_callback_.reset(
260          new VSyncProvider::UpdateVSyncCallback(callback));
261      vsync_thread_->message_loop()->PostTask(
262          FROM_HERE,
263          base::Bind(&SGIVideoSyncProviderThreadShim::GetVSyncParameters,
264                     base::Unretained(shim_.get()),
265                     base::Bind(
266                         &SGIVideoSyncVSyncProvider::PendingCallbackRunner,
267                         AsWeakPtr())));
268    }
269  }
270
271 private:
272  void PendingCallbackRunner(const base::TimeTicks timebase,
273                             const base::TimeDelta interval) {
274    DCHECK(pending_callback_);
275    pending_callback_->Run(timebase, interval);
276    pending_callback_.reset();
277  }
278
279  scoped_refptr<SGIVideoSyncThread> vsync_thread_;
280
281  // Thread shim through which the sync provider is accessed on |vsync_thread_|.
282  scoped_ptr<SGIVideoSyncProviderThreadShim> shim_;
283
284  scoped_ptr<VSyncProvider::UpdateVSyncCallback> pending_callback_;
285
286  // Raw pointers to sync primitives owned by the shim_.
287  // These will only be referenced before we post a task to destroy
288  // the shim_, so they are safe to access.
289  base::CancellationFlag* cancel_vsync_flag_;
290  base::Lock* vsync_lock_;
291
292  DISALLOW_COPY_AND_ASSIGN(SGIVideoSyncVSyncProvider);
293};
294
295SGIVideoSyncThread* SGIVideoSyncThread::g_video_sync_thread = NULL;
296
297// In order to take advantage of GLX_SGI_video_sync, we need a display
298// for use on a separate thread. We must allocate this before the sandbox
299// goes up (rather than on-demand when we start the thread).
300Display* SGIVideoSyncProviderThreadShim::display_ = NULL;
301
302}  // namespace
303
304GLSurfaceGLX::GLSurfaceGLX() {}
305
306bool GLSurfaceGLX::InitializeOneOff() {
307  static bool initialized = false;
308  if (initialized)
309    return true;
310
311  // http://crbug.com/245466
312  setenv("force_s3tc_enable", "true", 1);
313
314  // SGIVideoSyncProviderShim (if instantiated) will issue X commands on
315  // it's own thread.
316  XInitThreads();
317
318  g_display = base::MessagePumpForUI::GetDefaultXDisplay();
319  if (!g_display) {
320    LOG(ERROR) << "XOpenDisplay failed.";
321    return false;
322  }
323
324  int major, minor;
325  if (!glXQueryVersion(g_display, &major, &minor)) {
326    LOG(ERROR) << "glxQueryVersion failed";
327    return false;
328  }
329
330  if (major == 1 && minor < 3) {
331    LOG(ERROR) << "GLX 1.3 or later is required.";
332    return false;
333  }
334
335  g_glx_extensions = glXQueryExtensionsString(g_display, 0);
336  g_glx_context_create =
337      HasGLXExtension("GLX_ARB_create_context");
338  g_glx_create_context_robustness_supported =
339      HasGLXExtension("GLX_ARB_create_context_robustness");
340  g_glx_texture_from_pixmap_supported =
341      HasGLXExtension("GLX_EXT_texture_from_pixmap");
342  g_glx_oml_sync_control_supported =
343      HasGLXExtension("GLX_OML_sync_control");
344  g_glx_get_msc_rate_oml_supported = g_glx_oml_sync_control_supported;
345  g_glx_sgi_video_sync_supported =
346      HasGLXExtension("GLX_SGI_video_sync");
347
348  if (!g_glx_get_msc_rate_oml_supported && g_glx_sgi_video_sync_supported)
349    SGIVideoSyncProviderThreadShim::display_ = XOpenDisplay(NULL);
350
351  initialized = true;
352  return true;
353}
354
355// static
356const char* GLSurfaceGLX::GetGLXExtensions() {
357  return g_glx_extensions;
358}
359
360// static
361bool GLSurfaceGLX::HasGLXExtension(const char* name) {
362  return ExtensionsContain(GetGLXExtensions(), name);
363}
364
365// static
366bool GLSurfaceGLX::IsCreateContextSupported() {
367  return g_glx_context_create;
368}
369
370// static
371bool GLSurfaceGLX::IsCreateContextRobustnessSupported() {
372  return g_glx_create_context_robustness_supported;
373}
374
375// static
376bool GLSurfaceGLX::IsTextureFromPixmapSupported() {
377  return g_glx_texture_from_pixmap_supported;
378}
379
380// static
381bool GLSurfaceGLX::IsOMLSyncControlSupported() {
382  return g_glx_oml_sync_control_supported;
383}
384
385void* GLSurfaceGLX::GetDisplay() {
386  return g_display;
387}
388
389GLSurfaceGLX::~GLSurfaceGLX() {}
390
391NativeViewGLSurfaceGLX::NativeViewGLSurfaceGLX(gfx::AcceleratedWidget window)
392  : window_(window),
393    config_(NULL) {
394}
395
396bool NativeViewGLSurfaceGLX::Initialize() {
397  XWindowAttributes attributes;
398  if (!XGetWindowAttributes(g_display, window_, &attributes)) {
399    LOG(ERROR) << "XGetWindowAttributes failed for window " << window_ << ".";
400    return false;
401  }
402  size_ = gfx::Size(attributes.width, attributes.height);
403
404  if (g_glx_oml_sync_control_supported)
405    vsync_provider_.reset(new OMLSyncControlVSyncProvider(window_));
406  else if (g_glx_sgi_video_sync_supported)
407    vsync_provider_.reset(new SGIVideoSyncVSyncProvider(window_));
408
409  return true;
410}
411
412void NativeViewGLSurfaceGLX::Destroy() {
413}
414
415bool NativeViewGLSurfaceGLX::Resize(const gfx::Size& size) {
416  size_ = size;
417  return true;
418}
419
420bool NativeViewGLSurfaceGLX::IsOffscreen() {
421  return false;
422}
423
424bool NativeViewGLSurfaceGLX::SwapBuffers() {
425  glXSwapBuffers(g_display, window_);
426  return true;
427}
428
429gfx::Size NativeViewGLSurfaceGLX::GetSize() {
430  return size_;
431}
432
433void* NativeViewGLSurfaceGLX::GetHandle() {
434  return reinterpret_cast<void*>(window_);
435}
436
437std::string NativeViewGLSurfaceGLX::GetExtensions() {
438  std::string extensions = GLSurface::GetExtensions();
439  if (gfx::g_driver_glx.ext.b_GLX_MESA_copy_sub_buffer) {
440    extensions += extensions.empty() ? "" : " ";
441    extensions += "GL_CHROMIUM_post_sub_buffer";
442  }
443  return extensions;
444}
445
446void* NativeViewGLSurfaceGLX::GetConfig() {
447  if (!config_) {
448    // This code path is expensive, but we only take it when
449    // attempting to use GLX_ARB_create_context_robustness, in which
450    // case we need a GLXFBConfig for the window in order to create a
451    // context for it.
452    //
453    // TODO(kbr): this is not a reliable code path. On platforms which
454    // support it, we should use glXChooseFBConfig in the browser
455    // process to choose the FBConfig and from there the X Visual to
456    // use when creating the window in the first place. Then we can
457    // pass that FBConfig down rather than attempting to reconstitute
458    // it.
459
460    XWindowAttributes attributes;
461    if (!XGetWindowAttributes(
462        g_display,
463        window_,
464        &attributes)) {
465      LOG(ERROR) << "XGetWindowAttributes failed for window " <<
466          window_ << ".";
467      return NULL;
468    }
469
470    int visual_id = XVisualIDFromVisual(attributes.visual);
471
472    int num_elements = 0;
473    scoped_ptr_malloc<GLXFBConfig, ScopedPtrXFree> configs(
474        glXGetFBConfigs(g_display,
475                        DefaultScreen(g_display),
476                        &num_elements));
477    if (!configs.get()) {
478      LOG(ERROR) << "glXGetFBConfigs failed.";
479      return NULL;
480    }
481    if (!num_elements) {
482      LOG(ERROR) << "glXGetFBConfigs returned 0 elements.";
483      return NULL;
484    }
485    bool found = false;
486    int i;
487    for (i = 0; i < num_elements; ++i) {
488      int value;
489      if (glXGetFBConfigAttrib(
490              g_display, configs.get()[i], GLX_VISUAL_ID, &value)) {
491        LOG(ERROR) << "glXGetFBConfigAttrib failed.";
492        return NULL;
493      }
494      if (value == visual_id) {
495        found = true;
496        break;
497      }
498    }
499    if (found) {
500      config_ = configs.get()[i];
501    }
502  }
503
504  return config_;
505}
506
507bool NativeViewGLSurfaceGLX::PostSubBuffer(
508    int x, int y, int width, int height) {
509  DCHECK(gfx::g_driver_glx.ext.b_GLX_MESA_copy_sub_buffer);
510  glXCopySubBufferMESA(g_display, window_, x, y, width, height);
511  return true;
512}
513
514VSyncProvider* NativeViewGLSurfaceGLX::GetVSyncProvider() {
515  return vsync_provider_.get();
516}
517
518NativeViewGLSurfaceGLX::NativeViewGLSurfaceGLX()
519  : window_(0),
520    config_(NULL) {
521}
522
523NativeViewGLSurfaceGLX::~NativeViewGLSurfaceGLX() {
524  Destroy();
525}
526
527PbufferGLSurfaceGLX::PbufferGLSurfaceGLX(const gfx::Size& size)
528  : size_(size),
529    config_(NULL),
530    pbuffer_(0) {
531}
532
533bool PbufferGLSurfaceGLX::Initialize() {
534  DCHECK(!pbuffer_);
535
536  static const int config_attributes[] = {
537    GLX_BUFFER_SIZE, 32,
538    GLX_ALPHA_SIZE, 8,
539    GLX_BLUE_SIZE, 8,
540    GLX_GREEN_SIZE, 8,
541    GLX_RED_SIZE, 8,
542    GLX_RENDER_TYPE, GLX_RGBA_BIT,
543    GLX_DRAWABLE_TYPE, GLX_PBUFFER_BIT,
544    GLX_DOUBLEBUFFER, False,
545    0
546  };
547
548  int num_elements = 0;
549  scoped_ptr_malloc<GLXFBConfig, ScopedPtrXFree> configs(
550      glXChooseFBConfig(g_display,
551                        DefaultScreen(g_display),
552                        config_attributes,
553                        &num_elements));
554  if (!configs.get()) {
555    LOG(ERROR) << "glXChooseFBConfig failed.";
556    return false;
557  }
558  if (!num_elements) {
559    LOG(ERROR) << "glXChooseFBConfig returned 0 elements.";
560    return false;
561  }
562
563  config_ = configs.get()[0];
564
565  const int pbuffer_attributes[] = {
566    GLX_PBUFFER_WIDTH, size_.width(),
567    GLX_PBUFFER_HEIGHT, size_.height(),
568    0
569  };
570  pbuffer_ = glXCreatePbuffer(g_display,
571                              static_cast<GLXFBConfig>(config_),
572                              pbuffer_attributes);
573  if (!pbuffer_) {
574    Destroy();
575    LOG(ERROR) << "glXCreatePbuffer failed.";
576    return false;
577  }
578
579  return true;
580}
581
582void PbufferGLSurfaceGLX::Destroy() {
583  if (pbuffer_) {
584    glXDestroyPbuffer(g_display, pbuffer_);
585    pbuffer_ = 0;
586  }
587
588  config_ = NULL;
589}
590
591bool PbufferGLSurfaceGLX::IsOffscreen() {
592  return true;
593}
594
595bool PbufferGLSurfaceGLX::SwapBuffers() {
596  NOTREACHED() << "Attempted to call SwapBuffers on a pbuffer.";
597  return false;
598}
599
600gfx::Size PbufferGLSurfaceGLX::GetSize() {
601  return size_;
602}
603
604void* PbufferGLSurfaceGLX::GetHandle() {
605  return reinterpret_cast<void*>(pbuffer_);
606}
607
608void* PbufferGLSurfaceGLX::GetConfig() {
609  return config_;
610}
611
612PbufferGLSurfaceGLX::~PbufferGLSurfaceGLX() {
613  Destroy();
614}
615
616}  // namespace gfx
617