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