1 // Copyright 2013 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 #include "ui/gfx/ozone/dri/dri_surface_factory.h" 6 7 #include <drm.h> 8 #include <errno.h> 9 #include <xf86drm.h> 10 11 #include "base/message_loop/message_loop.h" 12 #include "third_party/skia/include/core/SkBitmap.h" 13 #include "third_party/skia/include/core/SkDevice.h" 14 #include "ui/gfx/native_widget_types.h" 15 #include "ui/gfx/ozone/dri/dri_skbitmap.h" 16 #include "ui/gfx/ozone/dri/dri_surface.h" 17 #include "ui/gfx/ozone/dri/dri_vsync_provider.h" 18 #include "ui/gfx/ozone/dri/dri_wrapper.h" 19 #include "ui/gfx/ozone/dri/hardware_display_controller.h" 20 21 namespace gfx { 22 23 namespace { 24 25 const char kDefaultGraphicsCardPath[] = "/dev/dri/card0"; 26 const char kDPMSProperty[] = "DPMS"; 27 28 const gfx::AcceleratedWidget kDefaultWidgetHandle = 1; 29 30 // DRM callback on page flip events. This callback is triggered after the 31 // page flip has happened and the backbuffer is now the new frontbuffer 32 // The old frontbuffer is no longer used by the hardware and can be used for 33 // future draw operations. 34 // 35 // |device| will contain a reference to the |DriSurface| object which 36 // the event belongs to. 37 // 38 // TODO(dnicoara) When we have a FD handler for the DRM calls in the message 39 // loop, we can move this function in the handler. 40 void HandlePageFlipEvent(int fd, 41 unsigned int frame, 42 unsigned int seconds, 43 unsigned int useconds, 44 void* controller) { 45 static_cast<HardwareDisplayController*>(controller) 46 ->OnPageFlipEvent(frame, seconds, useconds); 47 } 48 49 uint32_t GetDriProperty(int fd, drmModeConnector* connector, const char* name) { 50 for (int i = 0; i < connector->count_props; ++i) { 51 drmModePropertyPtr property = drmModeGetProperty(fd, connector->props[i]); 52 if (!property) 53 continue; 54 55 if (strcmp(property->name, name) == 0) { 56 uint32_t id = property->prop_id; 57 drmModeFreeProperty(property); 58 return id; 59 } 60 61 drmModeFreeProperty(property); 62 } 63 return 0; 64 } 65 66 uint32_t GetCrtc(int fd, drmModeRes* resources, drmModeConnector* connector) { 67 // If the connector already has an encoder try to re-use. 68 if (connector->encoder_id) { 69 drmModeEncoder* encoder = drmModeGetEncoder(fd, connector->encoder_id); 70 if (encoder) { 71 if (encoder->crtc_id) { 72 uint32_t crtc = encoder->crtc_id; 73 drmModeFreeEncoder(encoder); 74 return crtc; 75 } 76 drmModeFreeEncoder(encoder); 77 } 78 } 79 80 // Try to find an encoder for the connector. 81 for (int i = 0; i < connector->count_encoders; ++i) { 82 drmModeEncoder* encoder = drmModeGetEncoder(fd, connector->encoders[i]); 83 if (!encoder) 84 continue; 85 86 for (int j = 0; j < resources->count_crtcs; ++j) { 87 // Check if the encoder is compatible with this CRTC 88 if (!(encoder->possible_crtcs & (1 << j))) 89 continue; 90 91 drmModeFreeEncoder(encoder); 92 return resources->crtcs[j]; 93 } 94 } 95 96 return 0; 97 } 98 99 } // namespace 100 101 DriSurfaceFactory::DriSurfaceFactory() 102 : drm_(), 103 state_(UNINITIALIZED), 104 controller_() { 105 } 106 107 DriSurfaceFactory::~DriSurfaceFactory() { 108 if (state_ == INITIALIZED) 109 ShutdownHardware(); 110 } 111 112 SurfaceFactoryOzone::HardwareState 113 DriSurfaceFactory::InitializeHardware() { 114 CHECK(state_ == UNINITIALIZED); 115 116 // TODO(dnicoara): Short-cut right now. What we want is to look at all the 117 // graphics devices available and select the primary one. 118 drm_.reset(CreateWrapper()); 119 if (drm_->get_fd() < 0) { 120 LOG(ERROR) << "Cannot open graphics card '" 121 << kDefaultGraphicsCardPath << "': " << strerror(errno); 122 state_ = FAILED; 123 return state_; 124 } 125 126 state_ = INITIALIZED; 127 return state_; 128 } 129 130 void DriSurfaceFactory::ShutdownHardware() { 131 CHECK(state_ == INITIALIZED); 132 133 controller_.reset(); 134 drm_.reset(); 135 136 state_ = UNINITIALIZED; 137 } 138 139 gfx::AcceleratedWidget DriSurfaceFactory::GetAcceleratedWidget() { 140 CHECK(state_ != FAILED); 141 142 // TODO(dnicoara) When there's more information on which display we want, 143 // then we can return the widget associated with the display. 144 // For now just assume we have 1 display device and return it. 145 if (!controller_.get()) 146 controller_.reset(new HardwareDisplayController()); 147 148 // TODO(dnicoara) We only have 1 display for now, so only 1 AcceleratedWidget. 149 // When we'll support multiple displays this needs to be changed to return a 150 // different handle for every display. 151 return kDefaultWidgetHandle; 152 } 153 154 gfx::AcceleratedWidget DriSurfaceFactory::RealizeAcceleratedWidget( 155 gfx::AcceleratedWidget w) { 156 CHECK(state_ == INITIALIZED); 157 // TODO(dnicoara) Once we can handle multiple displays this needs to be 158 // changed. 159 CHECK(w == kDefaultWidgetHandle); 160 161 CHECK(controller_->get_state() == 162 HardwareDisplayController::UNASSOCIATED); 163 164 // Until now the controller is just a stub. Initializing it will link it to a 165 // hardware display. 166 if (!InitializeControllerForPrimaryDisplay(drm_.get(), controller_.get())) { 167 LOG(ERROR) << "Failed to initialize controller"; 168 return gfx::kNullAcceleratedWidget; 169 } 170 171 // Create a surface suitable for the current controller. 172 scoped_ptr<DriSurface> surface(CreateSurface(controller_.get())); 173 174 if (!surface->Initialize()) { 175 LOG(ERROR) << "Failed to initialize surface"; 176 return gfx::kNullAcceleratedWidget; 177 } 178 179 // Bind the surface to the controller. This will register the backing buffers 180 // with the hardware CRTC such that we can show the buffers. The controller 181 // takes ownership of the surface. 182 if (!controller_->BindSurfaceToController(surface.Pass())) { 183 LOG(ERROR) << "Failed to bind surface to controller"; 184 return gfx::kNullAcceleratedWidget; 185 } 186 187 return reinterpret_cast<gfx::AcceleratedWidget>(controller_->get_surface()); 188 } 189 190 bool DriSurfaceFactory::LoadEGLGLES2Bindings( 191 AddGLLibraryCallback add_gl_library, 192 SetGLGetProcAddressProcCallback set_gl_get_proc_address) { 193 return false; 194 } 195 196 bool DriSurfaceFactory::AttemptToResizeAcceleratedWidget( 197 gfx::AcceleratedWidget w, 198 const gfx::Rect& bounds) { 199 return false; 200 } 201 202 bool DriSurfaceFactory::SchedulePageFlip(gfx::AcceleratedWidget w) { 203 CHECK(state_ == INITIALIZED); 204 // TODO(dnicoara) Change this CHECK once we're running with the threaded 205 // compositor. 206 CHECK(base::MessageLoop::current()->type() == base::MessageLoop::TYPE_UI); 207 208 // TODO(dnicoara) Once we can handle multiple displays this needs to be 209 // changed. 210 CHECK(w == kDefaultWidgetHandle); 211 212 if (!controller_->SchedulePageFlip()) 213 return false; 214 215 // Only wait for the page flip event to finish if it was properly scheduled. 216 // 217 // TODO(dnicoara) The following call will wait for the page flip event to 218 // complete. This means that it will block until the next VSync. Ideally the 219 // wait should happen in the message loop. The message loop would then 220 // schedule the next draw event. Alternatively, the VSyncProvider could be 221 // used to schedule the next draw. Unfortunately, at this point, 222 // DriOutputDevice does not provide any means to use any of the above 223 // solutions. Note that if the DRM callback does not schedule the next draw, 224 // then some sort of synchronization needs to take place since starting a new 225 // draw before the page flip happened is considered an error. However we can 226 // not use any lock constructs unless we're using the threaded compositor. 227 // Note that the following call does not use any locks, so it is safe to be 228 // made on the UI thread (thought not ideal). 229 WaitForPageFlipEvent(drm_->get_fd()); 230 231 return true; 232 } 233 234 SkCanvas* DriSurfaceFactory::GetCanvasForWidget( 235 gfx::AcceleratedWidget w) { 236 CHECK(state_ == INITIALIZED); 237 return reinterpret_cast<DriSurface*>(w)->GetDrawableForWidget(); 238 } 239 240 gfx::VSyncProvider* DriSurfaceFactory::GetVSyncProvider( 241 gfx::AcceleratedWidget w) { 242 CHECK(state_ == INITIALIZED); 243 return new DriVSyncProvider(controller_.get()); 244 } 245 246 //////////////////////////////////////////////////////////////////////////////// 247 // DriSurfaceFactory private 248 249 DriSurface* DriSurfaceFactory::CreateSurface( 250 HardwareDisplayController* controller) { 251 return new DriSurface(controller); 252 } 253 254 DriWrapper* DriSurfaceFactory::CreateWrapper() { 255 return new DriWrapper(kDefaultGraphicsCardPath); 256 } 257 258 bool DriSurfaceFactory::InitializeControllerForPrimaryDisplay( 259 DriWrapper* drm, 260 HardwareDisplayController* controller) { 261 CHECK(state_ == SurfaceFactoryOzone::INITIALIZED); 262 263 drmModeRes* resources = drmModeGetResources(drm->get_fd()); 264 265 // Search for an active connector. 266 for (int i = 0; i < resources->count_connectors; ++i) { 267 drmModeConnector* connector = drmModeGetConnector( 268 drm->get_fd(), 269 resources->connectors[i]); 270 271 if (!connector) 272 continue; 273 274 if (connector->connection != DRM_MODE_CONNECTED || 275 connector->count_modes == 0) { 276 drmModeFreeConnector(connector); 277 continue; 278 } 279 280 uint32_t crtc = GetCrtc(drm->get_fd(), resources, connector); 281 282 if (!crtc) 283 continue; 284 285 uint32_t dpms_property_id = GetDriProperty(drm->get_fd(), 286 connector, 287 kDPMSProperty); 288 289 // TODO(dnicoara) Select one mode for now. In the future we may need to 290 // save all the modes and allow the user to choose a specific mode. Or 291 // even some fullscreen applications may need to change the mode. 292 controller->SetControllerInfo( 293 drm, 294 connector->connector_id, 295 crtc, 296 dpms_property_id, 297 connector->modes[0]); 298 299 drmModeFreeConnector(connector); 300 301 return true; 302 } 303 304 return false; 305 } 306 307 void DriSurfaceFactory::WaitForPageFlipEvent(int fd) { 308 drmEventContext drm_event; 309 drm_event.version = DRM_EVENT_CONTEXT_VERSION; 310 drm_event.page_flip_handler = HandlePageFlipEvent; 311 drm_event.vblank_handler = NULL; 312 313 // Wait for the page-flip to complete. 314 drmHandleEvent(fd, &drm_event); 315 } 316 317 } // namespace gfx 318