Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / ui / gl / gl_surface_glx.cc
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
5 extern "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
32 namespace gfx {
33
34 namespace {
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.
39 struct ScopedPtrXFree {
40   void operator()(void* x) const {
41     ::XFree(x);
42   }
43 };
44
45 Display* g_display = NULL;
46 const char* g_glx_extensions = NULL;
47 bool g_glx_context_create = false;
48 bool g_glx_create_context_robustness_supported = false;
49 bool g_glx_texture_from_pixmap_supported = false;
50 bool 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.
55 bool g_glx_get_msc_rate_oml_supported = false;
56
57 bool g_glx_sgi_video_sync_supported = false;
58
59 static 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
72 class 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
110 class 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
141 class 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
244 class 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
321 SGIVideoSyncThread* 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).
326 Display* SGIVideoSyncProviderThreadShim::display_ = NULL;
327
328 }  // namespace
329
330 GLSurfaceGLX::GLSurfaceGLX() {}
331
332 bool 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
382 const char* GLSurfaceGLX::GetGLXExtensions() {
383   return g_glx_extensions;
384 }
385
386 // static
387 bool GLSurfaceGLX::HasGLXExtension(const char* name) {
388   return ExtensionsContain(GetGLXExtensions(), name);
389 }
390
391 // static
392 bool GLSurfaceGLX::IsCreateContextSupported() {
393   return g_glx_context_create;
394 }
395
396 // static
397 bool GLSurfaceGLX::IsCreateContextRobustnessSupported() {
398   return g_glx_create_context_robustness_supported;
399 }
400
401 // static
402 bool GLSurfaceGLX::IsTextureFromPixmapSupported() {
403   return g_glx_texture_from_pixmap_supported;
404 }
405
406 // static
407 bool GLSurfaceGLX::IsOMLSyncControlSupported() {
408   return g_glx_oml_sync_control_supported;
409 }
410
411 void* GLSurfaceGLX::GetDisplay() {
412   return g_display;
413 }
414
415 GLSurfaceGLX::~GLSurfaceGLX() {}
416
417 NativeViewGLSurfaceGLX::NativeViewGLSurfaceGLX(gfx::AcceleratedWidget window)
418   : parent_window_(window),
419     window_(0),
420     config_(NULL) {
421 }
422
423 gfx::AcceleratedWidget NativeViewGLSurfaceGLX::GetDrawableHandle() const {
424   return window_;
425 }
426
427 bool 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
472 void 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
483 bool NativeViewGLSurfaceGLX::CanDispatchEvent(const ui::PlatformEvent& event) {
484   return event->type == Expose && event->xexpose.window == window_;
485 }
486
487 uint32_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
496 bool 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
504 bool NativeViewGLSurfaceGLX::IsOffscreen() {
505   return false;
506 }
507
508 bool 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
517 gfx::Size NativeViewGLSurfaceGLX::GetSize() {
518   return size_;
519 }
520
521 void* NativeViewGLSurfaceGLX::GetHandle() {
522   return reinterpret_cast<void*>(GetDrawableHandle());
523 }
524
525 bool NativeViewGLSurfaceGLX::SupportsPostSubBuffer() {
526   return gfx::g_driver_glx.ext.b_GLX_MESA_copy_sub_buffer;
527 }
528
529 void* 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
590 bool 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
597 VSyncProvider* NativeViewGLSurfaceGLX::GetVSyncProvider() {
598   return vsync_provider_.get();
599 }
600
601 NativeViewGLSurfaceGLX::~NativeViewGLSurfaceGLX() {
602   Destroy();
603 }
604
605 PbufferGLSurfaceGLX::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
615 bool 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
664 void PbufferGLSurfaceGLX::Destroy() {
665   if (pbuffer_) {
666     glXDestroyPbuffer(g_display, pbuffer_);
667     pbuffer_ = 0;
668   }
669
670   config_ = NULL;
671 }
672
673 bool PbufferGLSurfaceGLX::IsOffscreen() {
674   return true;
675 }
676
677 bool PbufferGLSurfaceGLX::SwapBuffers() {
678   NOTREACHED() << "Attempted to call SwapBuffers on a pbuffer.";
679   return false;
680 }
681
682 gfx::Size PbufferGLSurfaceGLX::GetSize() {
683   return size_;
684 }
685
686 void* PbufferGLSurfaceGLX::GetHandle() {
687   return reinterpret_cast<void*>(pbuffer_);
688 }
689
690 void* PbufferGLSurfaceGLX::GetConfig() {
691   return config_;
692 }
693
694 PbufferGLSurfaceGLX::~PbufferGLSurfaceGLX() {
695   Destroy();
696 }
697
698 }  // namespace gfx