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