Merge lp:~vanvugt/mir/async-page-flips into lp:mir
- async-page-flips
- Merge into development-branch
Status: | Merged |
---|---|
Approved by: | Daniel van Vugt |
Approved revision: | no longer in the source branch. |
Merged at revision: | 1380 |
Proposed branch: | lp:~vanvugt/mir/async-page-flips |
Merge into: | lp:mir |
Diff against target: |
496 lines (+218/-60) 8 files modified
src/platform/graphics/mesa/display.cpp (+11/-0) src/platform/graphics/mesa/display_buffer.cpp (+72/-31) src/platform/graphics/mesa/display_buffer.h (+4/-1) tests/unit-tests/graphics/mesa/mock_kms_output.h (+52/-0) tests/unit-tests/graphics/mesa/test_cursor.cpp (+2/-19) tests/unit-tests/graphics/mesa/test_display.cpp (+5/-7) tests/unit-tests/graphics/mesa/test_display_buffer.cpp (+62/-0) tests/unit-tests/graphics/mesa/test_display_multi_monitor.cpp (+10/-2) |
To merge this branch: | bzr merge lp:~vanvugt/mir/async-page-flips |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Alexandros Frantzis (community) | Approve | ||
Alan Griffiths | Approve | ||
PS Jenkins bot (community) | continuous-integration | Approve | |
Review via email:
|
Commit message
Parallelize page flipping with rendering (of the next frame).
Blocking rendering of the next frame until your page flip has finished is
pretty harmless on a single-headed system. However, in the case of cloned
outputs, waiting for multiple flips can take up to the whole frame time
(as monitors could easily be 16ms out of phase). This left little or no time
for rendering, resulting in dropped frames (stuttering) with cloned outputs
(LP: #1213801).
The solution is to wait for the page flips in parallel to rendering the next
frame. So it doesn't matter if your cloned outputs are many milliseconds
out of phase. That will no longer take away from your rendering time.
Description of the change
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1381
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Daniel van Vugt (vanvugt) wrote : | # |
Alexandros,
You are mistaken and I am indeed aware of the perils of touching the currently-
What makes this proposal safe is that we hold both the buffer being scheduled and the buffer currently visible ("last_flipped_"), to ensure neither of them ever get modified until they have been retired by a later page flip.
Buffers are only released when they are neither scheduled, nor being scanned out:
// Presently visible is either last_flipped_bufobj (composited), last_flipped_
// scheduled_bufobj (recently flipped which replaced last_flipped_
wait_for_
// Now presently visible is either scheduled_bufobj (composited) or last_flipped_
// those are the ones we need to continue holding references to.
if (scheduled_bufobj) // switched from bypass to now compositing
last_
// last_flipped_bufobj is now guaranteed no longer on screen, since wait_for_page_flip
if (last_flipped_
last_
// Hold the reference so we don't release/reuse the on-screen buffer until something replaces it
last_flipped_bufobj = scheduled_bufobj;
It's confusing I know. But I believe it is correct.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Daniel van Vugt (vanvugt) wrote : | # |
Alexandros,
I think I see where you're getting confused. You're assuming that GBM only has two buffers (A and B). Therefore you're also assuming that GBM can willingly violate its own lock semantics [1] and reuse a buffer as the back while Mir still has it locked as a front buffer. Either that or you're assuming that I'm violating GBM lock semantics. None of those are true.
GBM will not allow a currently locked front buffer to be reused as a back buffer. You have to release it first, which we only do after a replacement has been flipped. In fact, if you look at the Mesa source, it allocates a fixed limit of 3-4 color buffers [2] depending on which version of Mesa you have (trusty has 3, upstream Mesa has 4).
The 3 buffers we use are:
A: last_flipped_bufobj
B: scheduled_bufobj
C: The EGL back buffer currently being rendered to (there is no GBM function to access this until you swap and it becomes front).
Thus, yes, this proposal will trigger the internal use of 3 color buffers (as opposed to 2 at present) in Mesa. But it does so safely and guarantees that the visible buffer can not get reused as a back buffer while it is visible.
[1] http://
[2] http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Alexandros Frantzis (afrantzis) wrote : | # |
> I think I see where you're getting confused. You're assuming that GBM only has two buffers (A and B).
...
Excellent, that was indeed my incorrect assumption. With more than 2 buffers this optimization is safe.
I haven't yet processed the interaction with bypass, but for normal rendering the optimization seems ok, apart from a small glitch in the set up: I think that in the constructor we should be storing the locked front buffer in scheduled_bufobj instead of last_flipped_
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Daniel van Vugt (vanvugt) wrote : | # |
Alexandros:
Thanks. Constructor issue fixed.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1384
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Alan Griffiths (alan-griffiths) wrote : | # |
This looks like "no obvious problems" - I can't help wondering if there's a way to achieve "obviously no problems".
If I get any good ideas I'll be back.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Alan Griffiths (alan-griffiths) wrote : | # |
> This looks like "no obvious problems" - I can't help wondering if there's a
> way to achieve "obviously no problems".
>
> If I get any good ideas I'll be back.
The more I think about this the clearer the code looks. That's probably an illusion, but I'll go along with it.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Daniel van Vugt (vanvugt) wrote : | # |
No new issues raised since the last one was fixed 7 days ago... Top approving.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Alexandros Frantzis (afrantzis) : | # |
Preview Diff
1 | === modified file 'src/platform/graphics/mesa/display.cpp' |
2 | --- src/platform/graphics/mesa/display.cpp 2014-01-22 08:32:55 +0000 |
3 | +++ src/platform/graphics/mesa/display.cpp 2014-02-03 10:43:40 +0000 |
4 | @@ -132,6 +132,17 @@ |
5 | auto const& kms_conf = dynamic_cast<RealKMSDisplayConfiguration const&>(conf); |
6 | std::vector<std::unique_ptr<DisplayBuffer>> display_buffers_new; |
7 | |
8 | + /* |
9 | + * Notice for a little while here we will have duplicate |
10 | + * DisplayBuffers attached to each output, and the display_buffers_new |
11 | + * will take over the outputs before the old display_buffers are |
12 | + * destroyed. So to avoid page flipping confusion in-between, make |
13 | + * sure we wait for all pending page flips to finish before the |
14 | + * display_buffers_new are created and take control of the outputs. |
15 | + */ |
16 | + for (auto& db : display_buffers) |
17 | + db->wait_for_page_flip(); |
18 | + |
19 | /* Reset the state of all outputs */ |
20 | kms_conf.for_each_output([&](DisplayConfigurationOutput const& conf_output) |
21 | { |
22 | |
23 | === modified file 'src/platform/graphics/mesa/display_buffer.cpp' |
24 | --- src/platform/graphics/mesa/display_buffer.cpp 2014-01-23 17:26:51 +0000 |
25 | +++ src/platform/graphics/mesa/display_buffer.cpp 2014-02-03 10:43:40 +0000 |
26 | @@ -103,6 +103,7 @@ |
27 | MirOrientation rot, |
28 | EGLContext shared_context) |
29 | : last_flipped_bufobj{nullptr}, |
30 | + scheduled_bufobj{nullptr}, |
31 | platform(platform), |
32 | listener(listener), |
33 | drm(platform->drm), |
34 | @@ -110,7 +111,8 @@ |
35 | surface_gbm{std::move(surface_gbm_param)}, |
36 | area(area), |
37 | rotation(rot), |
38 | - needs_set_crtc{false} |
39 | + needs_set_crtc{false}, |
40 | + page_flips_pending{false} |
41 | { |
42 | uint32_t area_width = area.size.width.as_uint32_t(); |
43 | uint32_t area_height = area.size.height.as_uint32_t(); |
44 | @@ -142,13 +144,13 @@ |
45 | |
46 | listener->report_successful_egl_buffer_swap_on_construction(); |
47 | |
48 | - last_flipped_bufobj = get_front_buffer_object(); |
49 | - if (!last_flipped_bufobj) |
50 | + scheduled_bufobj = get_front_buffer_object(); |
51 | + if (!scheduled_bufobj) |
52 | BOOST_THROW_EXCEPTION(std::runtime_error("Failed to get frontbuffer")); |
53 | |
54 | for (auto& output : outputs) |
55 | { |
56 | - if (!output->set_crtc(last_flipped_bufobj->get_drm_fb_id())) |
57 | + if (!output->set_crtc(scheduled_bufobj->get_drm_fb_id())) |
58 | BOOST_THROW_EXCEPTION(std::runtime_error("Failed to set DRM crtc")); |
59 | } |
60 | |
61 | @@ -171,6 +173,9 @@ |
62 | */ |
63 | if (last_flipped_bufobj) |
64 | last_flipped_bufobj->release(); |
65 | + |
66 | + if (scheduled_bufobj) |
67 | + scheduled_bufobj->release(); |
68 | } |
69 | |
70 | geom::Rectangle mgm::DisplayBuffer::view_area() const |
71 | @@ -206,6 +211,31 @@ |
72 | std::shared_ptr<graphics::Buffer> bypass_buf) |
73 | { |
74 | /* |
75 | + * If the last frame was composited then we haven't waited for the |
76 | + * page flips yet. This is good because it maximizes the time available |
77 | + * to spend rendering each frame. However we have to wait here, because |
78 | + * it will be unsafe to swap_buffers before guaranteeing the previous |
79 | + * page flip finished. |
80 | + */ |
81 | + wait_for_page_flip(); |
82 | + |
83 | + /* |
84 | + * Switching from bypass to compositing? Now is the earliest safe time |
85 | + * we can unreference the bypass buffer... |
86 | + */ |
87 | + if (scheduled_bufobj) |
88 | + last_flipped_bypass_buf = nullptr; |
89 | + /* |
90 | + * Release the last flipped buffer object (which is not displayed anymore) |
91 | + * to make it available for future rendering. |
92 | + */ |
93 | + if (last_flipped_bufobj) |
94 | + last_flipped_bufobj->release(); |
95 | + |
96 | + last_flipped_bufobj = scheduled_bufobj; |
97 | + scheduled_bufobj = nullptr; |
98 | + |
99 | + /* |
100 | * Bring the back buffer to the front and get the buffer object |
101 | * corresponding to the front buffer. |
102 | */ |
103 | @@ -234,7 +264,7 @@ |
104 | * If the flip fails, release the buffer object to make it available |
105 | * for future rendering. |
106 | */ |
107 | - if (!needs_set_crtc && !schedule_and_wait_for_page_flip(bufobj)) |
108 | + if (!needs_set_crtc && !schedule_page_flip(bufobj)) |
109 | { |
110 | if (!bypass_buf) |
111 | bufobj->release(); |
112 | @@ -250,22 +280,32 @@ |
113 | needs_set_crtc = false; |
114 | } |
115 | |
116 | - /* |
117 | - * Release the last flipped buffer object (which is not displayed anymore) |
118 | - * to make it available for future rendering. |
119 | - */ |
120 | - if (last_flipped_bufobj) |
121 | - last_flipped_bufobj->release(); |
122 | - |
123 | - last_flipped_bufobj = bypass_buf ? nullptr : bufobj; |
124 | - |
125 | - /* |
126 | - * Keep a reference to the buffer being bypassed for the entire duration |
127 | - * of the frame. This ensures the buffer doesn't get reused by the client |
128 | - * prematurely, which would be seen as tearing. |
129 | - * If not bypassing, then bypass_buf will be nullptr. |
130 | - */ |
131 | - last_flipped_bypass_buf = bypass_buf; |
132 | + if (bypass_buf) |
133 | + { |
134 | + /* |
135 | + * For composited frames we defer wait_for_page_flip till just before |
136 | + * the next frame, but not for bypass frames. Deferring the flip of |
137 | + * bypass frames would increase the time we held |
138 | + * last_flipped_bypass_buf unacceptably, resulting in client stuttering |
139 | + * unless we allocate quad-buffers (which I'm trying to avoid). |
140 | + * Also, bypass does not need the deferred page flip because it has |
141 | + * no compositing/rendering step for which to save time for. |
142 | + */ |
143 | + wait_for_page_flip(); |
144 | + scheduled_bufobj = nullptr; |
145 | + |
146 | + /* |
147 | + * Keep a reference to the buffer being bypassed for the entire |
148 | + * duration of the frame. This ensures the buffer doesn't get reused by |
149 | + * the client while its on-screen, which would be seen as tearing or |
150 | + * worse. |
151 | + */ |
152 | + last_flipped_bypass_buf = bypass_buf; |
153 | + } |
154 | + else |
155 | + { |
156 | + scheduled_bufobj = bufobj; |
157 | + } |
158 | } |
159 | |
160 | mgm::BufferObject* mgm::DisplayBuffer::get_front_buffer_object() |
161 | @@ -311,10 +351,8 @@ |
162 | } |
163 | |
164 | |
165 | -bool mgm::DisplayBuffer::schedule_and_wait_for_page_flip(BufferObject* bufobj) |
166 | +bool mgm::DisplayBuffer::schedule_page_flip(BufferObject* bufobj) |
167 | { |
168 | - int page_flips_pending{0}; |
169 | - |
170 | /* |
171 | * Schedule the current front buffer object for display. Note that |
172 | * the page flip is asynchronous and synchronized with vertical refresh. |
173 | @@ -322,18 +360,21 @@ |
174 | for (auto& output : outputs) |
175 | { |
176 | if (output->schedule_page_flip(bufobj->get_drm_fb_id())) |
177 | - ++page_flips_pending; |
178 | + page_flips_pending = true; |
179 | } |
180 | |
181 | - if (page_flips_pending == 0) |
182 | - return false; |
183 | + return page_flips_pending; |
184 | +} |
185 | |
186 | - for (auto& output : outputs) |
187 | +void mgm::DisplayBuffer::wait_for_page_flip() |
188 | +{ |
189 | + if (page_flips_pending) |
190 | { |
191 | - output->wait_for_page_flip(); |
192 | + for (auto& output : outputs) |
193 | + output->wait_for_page_flip(); |
194 | + |
195 | + page_flips_pending = false; |
196 | } |
197 | - |
198 | - return true; |
199 | } |
200 | |
201 | void mgm::DisplayBuffer::make_current() |
202 | |
203 | === modified file 'src/platform/graphics/mesa/display_buffer.h' |
204 | --- src/platform/graphics/mesa/display_buffer.h 2014-01-23 17:26:51 +0000 |
205 | +++ src/platform/graphics/mesa/display_buffer.h 2014-02-03 10:43:40 +0000 |
206 | @@ -63,13 +63,15 @@ |
207 | std::function<void(Renderable const&)> const& render_fn); |
208 | MirOrientation orientation() const override; |
209 | void schedule_set_crtc(); |
210 | + void wait_for_page_flip(); |
211 | |
212 | private: |
213 | BufferObject* get_front_buffer_object(); |
214 | BufferObject* get_buffer_object(struct gbm_bo *bo); |
215 | - bool schedule_and_wait_for_page_flip(BufferObject* bufobj); |
216 | + bool schedule_page_flip(BufferObject* bufobj); |
217 | |
218 | BufferObject* last_flipped_bufobj; |
219 | + BufferObject* scheduled_bufobj; |
220 | std::shared_ptr<graphics::Buffer> last_flipped_bypass_buf; |
221 | std::shared_ptr<Platform> const platform; |
222 | std::shared_ptr<DisplayReport> const listener; |
223 | @@ -82,6 +84,7 @@ |
224 | uint32_t fb_width, fb_height; |
225 | MirOrientation rotation; |
226 | std::atomic<bool> needs_set_crtc; |
227 | + bool page_flips_pending; |
228 | }; |
229 | |
230 | } |
231 | |
232 | === added file 'tests/unit-tests/graphics/mesa/mock_kms_output.h' |
233 | --- tests/unit-tests/graphics/mesa/mock_kms_output.h 1970-01-01 00:00:00 +0000 |
234 | +++ tests/unit-tests/graphics/mesa/mock_kms_output.h 2014-02-03 10:43:40 +0000 |
235 | @@ -0,0 +1,52 @@ |
236 | +/* |
237 | + * Copyright © 2013 Canonical Ltd. |
238 | + * |
239 | + * This program is free software: you can redistribute it and/or modify |
240 | + * it under the terms of the GNU General Public License version 3 as |
241 | + * published by the Free Software Foundation. |
242 | + * |
243 | + * This program is distributed in the hope that it will be useful, |
244 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
245 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
246 | + * GNU General Public License for more details. |
247 | + * |
248 | + * You should have received a copy of the GNU General Public License |
249 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
250 | + * |
251 | + * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com> |
252 | + */ |
253 | + |
254 | +#ifndef MOCK_KMS_OUTPUT_H_ |
255 | +#define MOCK_KMS_OUTPUT_H_ |
256 | + |
257 | +#include "src/platform/graphics/mesa/kms_output.h" |
258 | +#include <gmock/gmock.h> |
259 | + |
260 | +namespace mir |
261 | +{ |
262 | +namespace test |
263 | +{ |
264 | + |
265 | +struct MockKMSOutput : public graphics::mesa::KMSOutput |
266 | +{ |
267 | + MOCK_METHOD0(reset, void()); |
268 | + MOCK_METHOD2(configure, void(geometry::Displacement, size_t)); |
269 | + MOCK_CONST_METHOD0(size, geometry::Size()); |
270 | + |
271 | + MOCK_METHOD1(set_crtc, bool(uint32_t)); |
272 | + MOCK_METHOD0(clear_crtc, void()); |
273 | + MOCK_METHOD1(schedule_page_flip, bool(uint32_t)); |
274 | + MOCK_METHOD0(wait_for_page_flip, void()); |
275 | + |
276 | + MOCK_METHOD1(set_cursor, void(gbm_bo*)); |
277 | + MOCK_METHOD1(move_cursor, void(geometry::Point)); |
278 | + MOCK_METHOD0(clear_cursor, void()); |
279 | + MOCK_CONST_METHOD0(has_cursor, bool()); |
280 | + |
281 | + MOCK_METHOD1(set_power_mode, void(MirPowerMode)); |
282 | +}; |
283 | + |
284 | +} // namespace test |
285 | +} // namespace mir |
286 | + |
287 | +#endif // MOCK_KMS_OUTPUT_H_ |
288 | |
289 | === modified file 'tests/unit-tests/graphics/mesa/test_cursor.cpp' |
290 | --- tests/unit-tests/graphics/mesa/test_cursor.cpp 2014-01-13 06:12:33 +0000 |
291 | +++ tests/unit-tests/graphics/mesa/test_cursor.cpp 2014-02-03 10:43:40 +0000 |
292 | @@ -22,6 +22,7 @@ |
293 | #include "src/platform/graphics/mesa/kms_display_configuration.h" |
294 | |
295 | #include "mir_test_doubles/mock_gbm.h" |
296 | +#include "mock_kms_output.h" |
297 | |
298 | #include <gtest/gtest.h> |
299 | #include <gmock/gmock.h> |
300 | @@ -32,29 +33,11 @@ |
301 | namespace mgm = mir::graphics::mesa; |
302 | namespace geom = mir::geometry; |
303 | namespace mtd = mir::test::doubles; |
304 | +using mir::test::MockKMSOutput; |
305 | |
306 | namespace |
307 | { |
308 | |
309 | -struct MockKMSOutput : public mgm::KMSOutput |
310 | -{ |
311 | - MOCK_METHOD0(reset, void()); |
312 | - MOCK_METHOD2(configure, void(geom::Displacement, size_t)); |
313 | - MOCK_CONST_METHOD0(size, geom::Size()); |
314 | - |
315 | - MOCK_METHOD1(set_crtc, bool(uint32_t)); |
316 | - MOCK_METHOD0(clear_crtc, void()); |
317 | - MOCK_METHOD1(schedule_page_flip, bool(uint32_t)); |
318 | - MOCK_METHOD0(wait_for_page_flip, void()); |
319 | - |
320 | - MOCK_METHOD1(set_cursor, void(gbm_bo*)); |
321 | - MOCK_METHOD1(move_cursor, void(geom::Point)); |
322 | - MOCK_METHOD0(clear_cursor, void()); |
323 | - MOCK_CONST_METHOD0(has_cursor, bool()); |
324 | - |
325 | - MOCK_METHOD1(set_power_mode, void(MirPowerMode)); |
326 | -}; |
327 | - |
328 | struct StubKMSOutputContainer : public mgm::KMSOutputContainer |
329 | { |
330 | StubKMSOutputContainer() |
331 | |
332 | === modified file 'tests/unit-tests/graphics/mesa/test_display.cpp' |
333 | --- tests/unit-tests/graphics/mesa/test_display.cpp 2014-01-13 06:12:33 +0000 |
334 | +++ tests/unit-tests/graphics/mesa/test_display.cpp 2014-02-03 10:43:40 +0000 |
335 | @@ -428,14 +428,12 @@ |
336 | |
337 | /* Handle the flip event */ |
338 | EXPECT_CALL(mock_drm, drmHandleEvent(mock_drm.fake_drm.fd(), _)) |
339 | - .Times(1) |
340 | - .WillOnce(DoAll(InvokePageFlipHandler(&user_data), Return(0))); |
341 | + .Times(0); |
342 | |
343 | - /* Release the current FB */ |
344 | + /* Release last_flipped_bufobj (at destruction time) */ |
345 | EXPECT_CALL(mock_gbm, gbm_surface_release_buffer(mock_gbm.fake_gbm.surface, fake.bo1)) |
346 | .Times(Exactly(1)); |
347 | - |
348 | - /* Release the new FB (at destruction time) */ |
349 | + /* Release scheduled_bufobj (at destruction time) */ |
350 | EXPECT_CALL(mock_gbm, gbm_surface_release_buffer(mock_gbm.fake_gbm.surface, fake.bo2)) |
351 | .Times(Exactly(1)); |
352 | } |
353 | @@ -468,11 +466,11 @@ |
354 | .Times(Exactly(1)) |
355 | .WillOnce(Return(-1)); |
356 | |
357 | - /* Release the new (not flipped) BO */ |
358 | + /* Release failed bufobj */ |
359 | EXPECT_CALL(mock_gbm, gbm_surface_release_buffer(mock_gbm.fake_gbm.surface, fake.bo2)) |
360 | .Times(Exactly(1)); |
361 | |
362 | - /* Release the current FB (at destruction time) */ |
363 | + /* Release scheduled_bufobj (at destruction time) */ |
364 | EXPECT_CALL(mock_gbm, gbm_surface_release_buffer(mock_gbm.fake_gbm.surface, fake.bo1)) |
365 | .Times(Exactly(1)); |
366 | } |
367 | |
368 | === modified file 'tests/unit-tests/graphics/mesa/test_display_buffer.cpp' |
369 | --- tests/unit-tests/graphics/mesa/test_display_buffer.cpp 2014-01-21 06:27:40 +0000 |
370 | +++ tests/unit-tests/graphics/mesa/test_display_buffer.cpp 2014-02-03 10:43:40 +0000 |
371 | @@ -25,6 +25,7 @@ |
372 | #include "mir_test_doubles/mock_drm.h" |
373 | #include "mir_test_doubles/mock_gbm.h" |
374 | #include "mir_test_framework/udev_environment.h" |
375 | +#include "mock_kms_output.h" |
376 | |
377 | #include <gtest/gtest.h> |
378 | #include <gmock/gmock.h> |
379 | @@ -33,8 +34,10 @@ |
380 | using namespace testing; |
381 | using namespace mir; |
382 | using namespace std; |
383 | +using namespace mir::test; |
384 | using namespace mir::test::doubles; |
385 | using namespace mir::mir_test_framework; |
386 | +using namespace mir::graphics; |
387 | |
388 | class MesaDisplayBufferTest : public Test |
389 | { |
390 | @@ -66,6 +69,12 @@ |
391 | .WillByDefault(Return(456)); |
392 | |
393 | fake_devices.add_standard_drm_devices(); |
394 | + |
395 | + mock_kms_output = std::make_shared<NiceMock<MockKMSOutput>>(); |
396 | + ON_CALL(*mock_kms_output, set_crtc(_)) |
397 | + .WillByDefault(Return(true)); |
398 | + ON_CALL(*mock_kms_output, schedule_page_flip(_)) |
399 | + .WillByDefault(Return(true)); |
400 | } |
401 | |
402 | // The platform has an implicit dependency on mock_gbm etc so must be |
403 | @@ -85,6 +94,7 @@ |
404 | gbm_bo* fake_bo; |
405 | gbm_bo_handle fake_handle; |
406 | UdevEnvironment fake_devices; |
407 | + std::shared_ptr<MockKMSOutput> mock_kms_output; |
408 | }; |
409 | |
410 | TEST_F(MesaDisplayBufferTest, unrotated_view_area_is_untouched) |
411 | @@ -234,3 +244,55 @@ |
412 | mir_orientation_right, |
413 | mock_egl.fake_egl_context); |
414 | } |
415 | + |
416 | +TEST_F(MesaDisplayBufferTest, first_post_flips_but_no_wait) |
417 | +{ |
418 | + geometry::Rectangle const area{{12,34}, {56,78}}; |
419 | + |
420 | + EXPECT_CALL(*mock_kms_output, schedule_page_flip(_)) |
421 | + .Times(1); |
422 | + EXPECT_CALL(*mock_kms_output, wait_for_page_flip()) |
423 | + .Times(0); |
424 | + |
425 | + graphics::mesa::DisplayBuffer db( |
426 | + create_platform(), |
427 | + make_shared<graphics::NullDisplayReport>(), |
428 | + {mock_kms_output}, |
429 | + nullptr, |
430 | + area, |
431 | + mir_orientation_normal, |
432 | + mock_egl.fake_egl_context); |
433 | + |
434 | + db.post_update(); |
435 | +} |
436 | + |
437 | +TEST_F(MesaDisplayBufferTest, waits_for_page_flip_on_second_post) |
438 | +{ |
439 | + geometry::Rectangle const area{{12,34}, {56,78}}; |
440 | + |
441 | + InSequence seq; |
442 | + |
443 | + EXPECT_CALL(*mock_kms_output, wait_for_page_flip()) |
444 | + .Times(0); |
445 | + EXPECT_CALL(*mock_kms_output, schedule_page_flip(_)) |
446 | + .Times(1); |
447 | + EXPECT_CALL(*mock_kms_output, wait_for_page_flip()) |
448 | + .Times(1); |
449 | + EXPECT_CALL(*mock_kms_output, schedule_page_flip(_)) |
450 | + .Times(1); |
451 | + EXPECT_CALL(*mock_kms_output, wait_for_page_flip()) |
452 | + .Times(0); |
453 | + |
454 | + graphics::mesa::DisplayBuffer db( |
455 | + create_platform(), |
456 | + make_shared<graphics::NullDisplayReport>(), |
457 | + {mock_kms_output}, |
458 | + nullptr, |
459 | + area, |
460 | + mir_orientation_normal, |
461 | + mock_egl.fake_egl_context); |
462 | + |
463 | + db.post_update(); |
464 | + db.post_update(); |
465 | +} |
466 | + |
467 | |
468 | === modified file 'tests/unit-tests/graphics/mesa/test_display_multi_monitor.cpp' |
469 | --- tests/unit-tests/graphics/mesa/test_display_multi_monitor.cpp 2014-01-13 06:12:33 +0000 |
470 | +++ tests/unit-tests/graphics/mesa/test_display_multi_monitor.cpp 2014-02-03 10:43:40 +0000 |
471 | @@ -349,8 +349,8 @@ |
472 | EXPECT_CALL(mock_drm, drmModePageFlip(mock_drm.fake_drm.fd(), |
473 | crtc_ids[i], fb_id, |
474 | _, _)) |
475 | - .Times(1) |
476 | - .WillOnce(DoAll(SaveArg<4>(&user_data[i]), Return(0))); |
477 | + .Times(2) |
478 | + .WillRepeatedly(DoAll(SaveArg<4>(&user_data[i]), Return(0))); |
479 | |
480 | /* Emit fake DRM page-flip events */ |
481 | EXPECT_EQ(1, write(mock_drm.fake_drm.write_fd(), "a", 1)); |
482 | @@ -365,6 +365,14 @@ |
483 | |
484 | auto display = create_display_cloned(create_platform()); |
485 | |
486 | + /* First frame: Page flips are scheduled, but not waited for */ |
487 | + display->for_each_display_buffer([](mg::DisplayBuffer& buffer) |
488 | + { |
489 | + buffer.post_update(); |
490 | + }); |
491 | + |
492 | + /* Second frame: Previous page flips finish (drmHandleEvent) and new ones |
493 | + are scheduled */ |
494 | display->for_each_display_buffer([](mg::DisplayBuffer& buffer) |
495 | { |
496 | buffer.post_update(); |
> The solution is to wait for the page flips in parallel to rendering the next frame
Unless I am missing something, this is not safe. After we schedule a page flip we can't start rendering to the buffer that is currently visible before the page flip is complete. It's important to note that when we flip a buffer, the buffer itself becomes the display framebuffer (i.e. where the display hardware reads from for the whole duration of the frame), its contents are not copied to some other internal framebuffer. Here is a sample run with the proposed changes:
(Front,Back): which buffer is front/back from gbm's point of view
== Initial setup ==
make_current: (A,B) current rendering target is B
draw/clear: (A,B) clearing B
swap: (B,A)
set_crtc: (B,A) B is immediately visible on screen
== First iter ==
make_current: (B,A) current rendering target is A
draw: (B,A) drawing to A
wait_for_page_flip: (B,A) nothing to wait for
swap: (A,B)
schedule_page_flip: (A,B) scheduling for A to become visible on screen
== Second iter ==
make_current: (A,B) current rendering target is B
*** draw: (A,B) drawing to B => there is no guarantee that the page flip has happened, so B may still be visible on screen!
Disapproving, although I do hope I am missing something and thus proved wrong.