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