Merge lp:~vanvugt/mir/client-side-vsync into lp:mir
- client-side-vsync
- Merge into development-branch
Status: | Merged |
---|---|
Approved by: | Cemil Azizoglu |
Approved revision: | no longer in the source branch. |
Merged at revision: | 3952 |
Proposed branch: | lp:~vanvugt/mir/client-side-vsync |
Merge into: | lp:mir |
Prerequisite: | lp:~vanvugt/mir/fix-StaleFrames-test-race |
Diff against target: |
1547 lines (+1049/-22) 24 files modified
include/client/mir_toolkit/mir_buffer_stream.h (+0/-1) include/client/mir_toolkit/mir_surface.h (+0/-1) include/core/mir_toolkit/common.h (+7/-4) src/client/CMakeLists.txt (+1/-0) src/client/buffer_stream.cpp (+111/-6) src/client/buffer_stream.h (+12/-1) src/client/error_stream.cpp (+6/-0) src/client/error_stream.h (+2/-0) src/client/frame_clock.cpp (+156/-0) src/client/frame_clock.h (+73/-0) src/client/mir_buffer_stream_api.cpp (+8/-1) src/client/mir_surface.cpp (+113/-5) src/client/mir_surface.h (+10/-0) src/client/mir_surface_api.cpp (+1/-1) src/client/screencast_stream.cpp (+8/-0) src/client/screencast_stream.h (+2/-0) src/include/client/mir/mir_buffer_stream.h (+2/-0) tests/acceptance-tests/test_client_library.cpp (+5/-0) tests/acceptance-tests/test_latency.cpp (+27/-2) tests/include/mir/test/doubles/mock_mir_buffer_stream.h (+2/-0) tests/unit-tests/client/CMakeLists.txt (+1/-0) tests/unit-tests/client/test_client_mir_surface.cpp (+81/-0) tests/unit-tests/client/test_frame_clock.cpp (+420/-0) tests/unit-tests/platforms/android/server/test_hwc_device.cpp (+1/-0) |
To merge this branch: | bzr merge lp:~vanvugt/mir/client-side-vsync |
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Alan Griffiths | Approve | ||
Kevin DuBois (community) | Approve | ||
Mir CI Bot | continuous-integration | Approve | |
Cemil Azizoglu (community) | Approve | ||
Review via email: mp+314313@code.launchpad.net |
Commit message
Introducing client-side vsync, part 1.
For dramatically lower latency and improved smoothness.
Fixes LP: #1240909, LP: #1388490, LP: #1591328, LP: #1624188
Part 1 covers all users of:
eglSwapBuffers
mir_
Part 2 will cover the rest (mir_buffer_
in a hurry for it as that still has working server-side vsync. Plus I
think Xmir is the only user of that function at present.
Part 3 will introduce IPC to communicate hardware vsync timestamps to the
client for phase correction and increased precision (although that's not
a prerequisite to this).
Immediate benefits:
* Greatly reduced latency from the client to the screen. Theoretically
three frames lower latency for windowed apps of a nested server like
Unity8. And this has been confirmed with real measurements:
https:/
You can also confirm using your own eyes: mir_demo_
* No client/toolkit changes required.
* Latency and smoothness is no longer affected by process scheduling
randomness or poor server performance. Although we presently use input
resampling to work around some of the latency problem, that's not
immune to latency increasing again when the server has insufficient
time to keep up with the client. This approach is.
* Lower latency even than double buffering (when nested), and also
without the risk of decimation when a client takes slightly too long
to render.
Description of the change
Future benefits:
* We can remove Mir's input resampling now, since client-side vsync
should make the QML touch compression algorithm safely nestable.
* We get closer to not needing server-side queuing and multi-monitor
frame sync logic. Although some people would like to keep it for other
reasons, this approach (after mir_buffer_
ported) eliminates most of the need for it.
* Overall this approach could deprecate many more lines of code than it
adds (see above).
* Toolkits can have greater flexibility in how they choose to implement
their event loops without threading. If we shared timing information
in the client API in future then a toolkit could just choose interval 0
and do its own vsync baked into its event loop.
* Mir is free to support dropping in the system compositor if it wants,
and no longer requires additional fixing to throttle drivers (like
software renderers) that don't support page flipping.
Risks:
* We rely on the graphics driver to report accurate frame timing.
Although it's only a serious problem if the driver is out by orders of
magnitude (LP: #1639725). Otherwise only being slightly inaccurate is
actually the same problem as what we experience presently with the
59Hz input resampling when user input is driving animation.
Mir CI Bot (mir-ci-bot) wrote : | # |
Mir CI Bot (mir-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:3917
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
Click here to trigger a rebuild:
https:/
Daniel van Vugt (vanvugt) wrote : | # |
Cemil Azizoglu (cemil-azizoglu) wrote : | # |
Nice latency numbers.
Needs fixing:
MirSurface references should be MirWindow now.
Kevin DuBois (kdub) wrote : | # |
LGTM overall (still haven't fully understood the logic in FrameClock, so more comments on that later, perhaps)
discussion:
135 + * TODO: Remove these functions in future, after
136 + * mir_buffer_
137 + * client-side vsync, and the server has been told to always use
138 + * dropping for BufferStream. Then there will be no need to ever
139 + * change the server swap interval from zero.
Would prefer if this read "and the server has other ways to express buffer queuing other than swapinterval". Would leave room for some of the vulkan submission modes to work via server-side queuing (jury's still out on whether this is a good idea, but I don't know if we want to limit ourselves to client-side queuing only)
needs info:
1)
+void mcl::BufferStre
Other than BufferStream's currently-terrible constructor, why couldn't the frame clock be given as a build dependency via the constructor? One more parameter to the constructor seems better than a too-long constructor list + a 2step initialization
2)
Where the code currently is might be only useful to android, post-MirRenderS
nit:
348 +typedef std::unique_
IMHO, doesn't improve readability.
I guess that's mostly a 'discussion' vote.
Mir CI Bot (mir-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:3918
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
Click here to trigger a rebuild:
https:/
Daniel van Vugt (vanvugt) wrote : | # |
^^^
Bug 1523621
Mir CI Bot (mir-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:3919
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
Click here to trigger a rebuild:
https:/
Daniel van Vugt (vanvugt) wrote : | # |
^^^
Bug 1647244, and more random examples failures on krillin:
13:14:00 I: The following clients failed to execute successfully:
13:14:00 I: mir_demo_
13:14:00 I: Smoke testing complete with returncode -1
Mir CI Bot (mir-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:3920
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
Daniel van Vugt (vanvugt) wrote : | # |
^^^
Bug 1646558
Daniel van Vugt (vanvugt) wrote : | # |
kdub:
(0) Sure you can keep server-side queueing if you want it. So long as this code still has the ability to turn it off. Because having it queueing on will reintroduce the bufferbloat-
(1) No FrameClock is not set at construction. It's set (and changes) whenever the window moves to a new monitor (different FrameClock). We may also wish to change the FrameClock depending on occlusions in future (to replace the 10Hz dropping hack)...
(2) Yes, I expect the logic to move around with Cemil's future changes but where it is right now works for what we use right now. As for a public API, yes I agree that will be wanted; already mentioned it in the description above, but also a public API will be needed by Xmir to implement https:/
Daniel van Vugt (vanvugt) wrote : | # |
Also a public API would let clients do the equivalent of client-side predictive bypass for even lower latency: https:/
Mir CI Bot (mir-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:3921
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
Daniel van Vugt (vanvugt) wrote : | # |
^^^
Bug 1655929
Mir CI Bot (mir-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:3923
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
ABORTED: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
ABORTED: https:/
Click here to trigger a rebuild:
https:/
Cemil Azizoglu (cemil-azizoglu) wrote : | # |
Ok by me when CI is happy
Daniel van Vugt (vanvugt) wrote : | # |
^^^
CI aborts are bug 1655929
Mir CI Bot (mir-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:3924
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
Daniel van Vugt (vanvugt) wrote : | # |
^^^
Random krillin failures running mir-demos:
03:40:30 I: The following clients failed to execute successfully:
03:40:30 I: mir_demo_
03:40:30 I: mir_demo_
03:40:30 I: Smoke testing complete with returncode -1
Mir CI Bot (mir-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:3925
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
Daniel van Vugt (vanvugt) wrote : | # |
^^^
Bug 1616312
Mir CI Bot (mir-ci-bot) wrote : | # |
PASSED: Continuous integration, rev:3926
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
Kevin DuBois (kdub) wrote : | # |
> kdub:
>
> (0) Sure you can keep server-side queueing if you want it. So long as this
> code still has the ability to turn it off. Because having it queueing on will
> reintroduce the bufferbloat-
> So you probably don't want that, but up to you :)
Sure, we need an option to turn it off, it might just be useful to have it around.
> (2) Yes, I expect the logic to move around with Cemil's future changes but
> where it is right now works for what we use right now. As for a public API,
> yes I agree that will be wanted; already mentioned it in the description
> above, but also a public API will be needed by Xmir to implement
> https:/
Alright, so we might need some extensions for this to work in the "MirRenderSurface" world, and the driver would manage what BufferStream is managing now.
Kevin DuBois (kdub) wrote : | # |
well, lgtm. The frame clock management still seems like 2-step initialization, but given the state of BufferStream, doesn't bother me as much.
Alan Griffiths (alan-griffiths) wrote : | # |
+ * Copyright © 2016 Canonical Ltd.
Is the year really right?
Alan Griffiths (alan-griffiths) wrote : | # |
> well, lgtm. The frame clock management still seems like 2-step initialization,
> but given the state of BufferStream, doesn't bother me as much.
+1
Preview Diff
1 | === modified file 'include/client/mir_toolkit/mir_buffer_stream.h' | |||
2 | --- include/client/mir_toolkit/mir_buffer_stream.h 2016-12-17 00:52:45 +0000 | |||
3 | +++ include/client/mir_toolkit/mir_buffer_stream.h 2017-01-17 06:23:26 +0000 | |||
4 | @@ -219,7 +219,6 @@ | |||
5 | 219 | /** | 219 | /** |
6 | 220 | * Set the swapinterval for the stream. | 220 | * Set the swapinterval for the stream. |
7 | 221 | * \warning EGL users should use eglSwapInterval directly. | 221 | * \warning EGL users should use eglSwapInterval directly. |
8 | 222 | * \warning Only swapinterval of 0 or 1 is supported. | ||
9 | 223 | * \param [in] stream The buffer stream | 222 | * \param [in] stream The buffer stream |
10 | 224 | * \param [in] interval The number of vblank signals that | 223 | * \param [in] interval The number of vblank signals that |
11 | 225 | * mir_buffer_stream_swap_buffers will wait for | 224 | * mir_buffer_stream_swap_buffers will wait for |
12 | 226 | 225 | ||
13 | === modified file 'include/client/mir_toolkit/mir_surface.h' | |||
14 | --- include/client/mir_toolkit/mir_surface.h 2017-01-17 05:51:54 +0000 | |||
15 | +++ include/client/mir_toolkit/mir_surface.h 2017-01-17 06:23:26 +0000 | |||
16 | @@ -978,7 +978,6 @@ | |||
17 | 978 | /** | 978 | /** |
18 | 979 | * Set the swapinterval for the default stream. | 979 | * Set the swapinterval for the default stream. |
19 | 980 | * \warning EGL users should use eglSwapInterval directly. | 980 | * \warning EGL users should use eglSwapInterval directly. |
20 | 981 | * \warning Only swapinterval of 0 or 1 is supported. | ||
21 | 982 | * \warning If the surface was created with, or modified to have a | 981 | * \warning If the surface was created with, or modified to have a |
22 | 983 | * MirSurfaceSpec containing streams added through | 982 | * MirSurfaceSpec containing streams added through |
23 | 984 | * mir_window_spec_set_streams(), the default stream will | 983 | * mir_window_spec_set_streams(), the default stream will |
24 | 985 | 984 | ||
25 | === modified file 'include/core/mir_toolkit/common.h' | |||
26 | --- include/core/mir_toolkit/common.h 2017-01-17 05:51:54 +0000 | |||
27 | +++ include/core/mir_toolkit/common.h 2017-01-17 06:23:26 +0000 | |||
28 | @@ -37,9 +37,9 @@ | |||
29 | 37 | mir_surface_attrib_type, | 37 | mir_surface_attrib_type, |
30 | 38 | mir_surface_attrib_state, | 38 | mir_surface_attrib_state, |
31 | 39 | mir_surface_attrib_swapinterval, /**< \deprecated Do not listen for events | 39 | mir_surface_attrib_swapinterval, /**< \deprecated Do not listen for events |
35 | 40 | reporting this attribute. Use the | 40 | reporting this attribute. Use the |
36 | 41 | "mir_*_get_swapinterval()" functions | 41 | "mir_*_get_swapinterval()" functions |
37 | 42 | instead if you wish query its value */ | 42 | instead if you wish query its value */ |
38 | 43 | mir_surface_attrib_focus, | 43 | mir_surface_attrib_focus, |
39 | 44 | mir_surface_attrib_dpi, | 44 | mir_surface_attrib_dpi, |
40 | 45 | mir_surface_attrib_visibility, | 45 | mir_surface_attrib_visibility, |
41 | @@ -57,7 +57,10 @@ | |||
42 | 57 | /* Do not specify values...code relies on 0...N ordering. */ | 57 | /* Do not specify values...code relies on 0...N ordering. */ |
43 | 58 | mir_window_attrib_type, | 58 | mir_window_attrib_type, |
44 | 59 | mir_window_attrib_state, | 59 | mir_window_attrib_state, |
46 | 60 | mir_window_attrib_swapinterval, | 60 | mir_window_attrib_swapinterval, /**< \deprecated Do not listen for events |
47 | 61 | reporting this attribute. Use the | ||
48 | 62 | "mir_*_get_swapinterval()" functions | ||
49 | 63 | instead if you wish query its value */ | ||
50 | 61 | mir_window_attrib_focus, | 64 | mir_window_attrib_focus, |
51 | 62 | mir_window_attrib_dpi, | 65 | mir_window_attrib_dpi, |
52 | 63 | mir_window_attrib_visibility, | 66 | mir_window_attrib_visibility, |
53 | 64 | 67 | ||
54 | === modified file 'src/client/CMakeLists.txt' | |||
55 | --- src/client/CMakeLists.txt 2016-12-13 06:00:32 +0000 | |||
56 | +++ src/client/CMakeLists.txt 2017-01-17 06:23:26 +0000 | |||
57 | @@ -62,6 +62,7 @@ | |||
58 | 62 | default_connection_configuration.cpp | 62 | default_connection_configuration.cpp |
59 | 63 | connection_surface_map.cpp | 63 | connection_surface_map.cpp |
60 | 64 | private.cpp | 64 | private.cpp |
61 | 65 | frame_clock.cpp | ||
62 | 65 | mir_screencast.cpp | 66 | mir_screencast.cpp |
63 | 66 | mir_screencast_api.cpp | 67 | mir_screencast_api.cpp |
64 | 67 | mir_cursor_api.cpp | 68 | mir_cursor_api.cpp |
65 | 68 | 69 | ||
66 | === modified file 'src/client/buffer_stream.cpp' | |||
67 | --- src/client/buffer_stream.cpp 2017-01-17 05:51:54 +0000 | |||
68 | +++ src/client/buffer_stream.cpp 2017-01-17 06:23:26 +0000 | |||
69 | @@ -253,6 +253,7 @@ | |||
70 | 253 | client_platform(client_platform), | 253 | client_platform(client_platform), |
71 | 254 | protobuf_bs{mcl::make_protobuf_object<mir::protobuf::BufferStream>(a_protobuf_bs)}, | 254 | protobuf_bs{mcl::make_protobuf_object<mir::protobuf::BufferStream>(a_protobuf_bs)}, |
72 | 255 | user_swap_interval(parse_env_for_swap_interval()), | 255 | user_swap_interval(parse_env_for_swap_interval()), |
73 | 256 | using_client_side_vsync(false), | ||
74 | 256 | interval_config{server, frontend::BufferStreamId{a_protobuf_bs.id().value()}}, | 257 | interval_config{server, frontend::BufferStreamId{a_protobuf_bs.id().value()}}, |
75 | 257 | scale_(1.0f), | 258 | scale_(1.0f), |
76 | 258 | perf_report(perf_report), | 259 | perf_report(perf_report), |
77 | @@ -264,6 +265,16 @@ | |||
78 | 264 | factory(factory), | 265 | factory(factory), |
79 | 265 | render_surface_(render_surface) | 266 | render_surface_(render_surface) |
80 | 266 | { | 267 | { |
81 | 268 | /* | ||
82 | 269 | * Since we try to use client-side vsync where possible, a separate | ||
83 | 270 | * copy of the current swap interval is required to represent that | ||
84 | 271 | * the stream might have a non-zero interval while we want the server | ||
85 | 272 | * to use a zero interval. This is not stored inside interval_config | ||
86 | 273 | * because with some luck interval_config will eventually go away | ||
87 | 274 | * leaving just int current_swap_interval. | ||
88 | 275 | */ | ||
89 | 276 | current_swap_interval = interval_config.swap_interval(); | ||
90 | 277 | |||
91 | 267 | if (!protobuf_bs->has_id()) | 278 | if (!protobuf_bs->has_id()) |
92 | 268 | { | 279 | { |
93 | 269 | if (!protobuf_bs->has_error()) | 280 | if (!protobuf_bs->has_error()) |
94 | @@ -354,6 +365,10 @@ | |||
95 | 354 | std::unique_lock<decltype(mutex)> lock(mutex); | 365 | std::unique_lock<decltype(mutex)> lock(mutex); |
96 | 355 | perf_report->end_frame(id); | 366 | perf_report->end_frame(id); |
97 | 356 | 367 | ||
98 | 368 | if (!using_client_side_vsync && | ||
99 | 369 | current_swap_interval != interval_config.swap_interval()) | ||
100 | 370 | set_server_swap_interval(current_swap_interval); | ||
101 | 371 | |||
102 | 357 | secured_region.reset(); | 372 | secured_region.reset(); |
103 | 358 | 373 | ||
104 | 359 | // TODO: We can fix the strange "ID casting" used below in the second phase | 374 | // TODO: We can fix the strange "ID casting" used below in the second phase |
105 | @@ -406,9 +421,73 @@ | |||
106 | 406 | mir_display_output_id_invalid}; | 421 | mir_display_output_id_invalid}; |
107 | 407 | } | 422 | } |
108 | 408 | 423 | ||
109 | 424 | void mcl::BufferStream::wait_for_vsync() | ||
110 | 425 | { | ||
111 | 426 | mir::time::PosixTimestamp last, target; | ||
112 | 427 | std::shared_ptr<FrameClock> clock; | ||
113 | 428 | |||
114 | 429 | { | ||
115 | 430 | std::lock_guard<decltype(mutex)> lock(mutex); | ||
116 | 431 | if (!frame_clock) | ||
117 | 432 | return; | ||
118 | 433 | last = last_vsync; | ||
119 | 434 | clock = frame_clock; | ||
120 | 435 | } | ||
121 | 436 | |||
122 | 437 | target = clock->next_frame_after(last); | ||
123 | 438 | sleep_until(target); | ||
124 | 439 | |||
125 | 440 | { | ||
126 | 441 | std::lock_guard<decltype(mutex)> lock(mutex); | ||
127 | 442 | if (target > last_vsync) | ||
128 | 443 | last_vsync = target; | ||
129 | 444 | } | ||
130 | 445 | } | ||
131 | 446 | |||
132 | 447 | MirWaitHandle* mcl::BufferStream::set_server_swap_interval(int i) | ||
133 | 448 | { | ||
134 | 449 | /* | ||
135 | 450 | * TODO: Remove these functions in future, after | ||
136 | 451 | * mir_buffer_stream_swap_buffers has been converted to use | ||
137 | 452 | * client-side vsync, and the server has been told to always use | ||
138 | 453 | * dropping for BufferStream. Then there will be no need to ever | ||
139 | 454 | * change the server swap interval from zero. | ||
140 | 455 | */ | ||
141 | 456 | buffer_depository->set_interval(i); | ||
142 | 457 | return interval_config.set_swap_interval(i); | ||
143 | 458 | } | ||
144 | 459 | |||
145 | 409 | void mcl::BufferStream::swap_buffers_sync() | 460 | void mcl::BufferStream::swap_buffers_sync() |
146 | 410 | { | 461 | { |
147 | 462 | { | ||
148 | 463 | std::lock_guard<decltype(mutex)> lock(mutex); | ||
149 | 464 | using_client_side_vsync = true; | ||
150 | 465 | } | ||
151 | 466 | |||
152 | 467 | /* | ||
153 | 468 | * Until recently it seemed like server-side vsync could co-exist with | ||
154 | 469 | * client-side vsync. However my original theory that it was risky has | ||
155 | 470 | * proven to be true. If we leave server-side vsync throttling to interval | ||
156 | 471 | * one at the same time as using client-side, there's a risk the server | ||
157 | 472 | * will not get scheduled sufficiently to drain the queue as fast as | ||
158 | 473 | * we fill it, creating lag. The acceptance test `ClientLatency' has now | ||
159 | 474 | * proven this is a real problem so we must be sure to put the server in | ||
160 | 475 | * interval 0 when using client-side vsync. This guarantees that random | ||
161 | 476 | * scheduling imperfections won't create queuing lag. | ||
162 | 477 | */ | ||
163 | 478 | if (interval_config.swap_interval() != 0) | ||
164 | 479 | set_server_swap_interval(0); | ||
165 | 480 | |||
166 | 411 | swap_buffers([](){})->wait_for_all(); | 481 | swap_buffers([](){})->wait_for_all(); |
167 | 482 | |||
168 | 483 | { | ||
169 | 484 | std::lock_guard<decltype(mutex)> lock(mutex); | ||
170 | 485 | using_client_side_vsync = false; | ||
171 | 486 | } | ||
172 | 487 | |||
173 | 488 | int interval = swap_interval(); | ||
174 | 489 | for (int i = 0; i < interval; ++i) | ||
175 | 490 | wait_for_vsync(); | ||
176 | 412 | } | 491 | } |
177 | 413 | 492 | ||
178 | 414 | void mcl::BufferStream::request_and_wait_for_configure(MirWindowAttrib attrib, int interval) | 493 | void mcl::BufferStream::request_and_wait_for_configure(MirWindowAttrib attrib, int interval) |
179 | @@ -428,16 +507,42 @@ | |||
180 | 428 | 507 | ||
181 | 429 | int mcl::BufferStream::swap_interval() const | 508 | int mcl::BufferStream::swap_interval() const |
182 | 430 | { | 509 | { |
184 | 431 | return interval_config.swap_interval(); | 510 | std::lock_guard<decltype(mutex)> lock(mutex); |
185 | 511 | return current_swap_interval; | ||
186 | 432 | } | 512 | } |
187 | 433 | 513 | ||
188 | 434 | MirWaitHandle* mcl::BufferStream::set_swap_interval(int interval) | 514 | MirWaitHandle* mcl::BufferStream::set_swap_interval(int interval) |
189 | 435 | { | 515 | { |
195 | 436 | if (user_swap_interval.is_set()) | 516 | std::lock_guard<decltype(mutex)> lock(mutex); |
196 | 437 | interval = user_swap_interval.value(); | 517 | current_swap_interval = user_swap_interval.is_set() ? |
197 | 438 | 518 | user_swap_interval.value() : interval; | |
198 | 439 | buffer_depository->set_interval(interval); | 519 | |
199 | 440 | return interval_config.set_swap_interval(interval); | 520 | return set_server_swap_interval(current_swap_interval); |
200 | 521 | } | ||
201 | 522 | |||
202 | 523 | void mcl::BufferStream::adopted_by(MirWindow* win) | ||
203 | 524 | { | ||
204 | 525 | std::lock_guard<decltype(mutex)> lock(mutex); | ||
205 | 526 | /* | ||
206 | 527 | * Yes, we're storing raw pointers here. That's safe so long as MirWindow | ||
207 | 528 | * remembers to always call unadopted_by prior to its destruction. | ||
208 | 529 | * The alternative of MirWindow passing in a shared_ptr to itself is | ||
209 | 530 | * actually uglier than this... | ||
210 | 531 | */ | ||
211 | 532 | users.insert(win); | ||
212 | 533 | if (!frame_clock) | ||
213 | 534 | frame_clock = win->get_frame_clock(); | ||
214 | 535 | } | ||
215 | 536 | |||
216 | 537 | void mcl::BufferStream::unadopted_by(MirWindow* win) | ||
217 | 538 | { | ||
218 | 539 | std::lock_guard<decltype(mutex)> lock(mutex); | ||
219 | 540 | users.erase(win); | ||
220 | 541 | if (frame_clock == win->get_frame_clock()) | ||
221 | 542 | { | ||
222 | 543 | frame_clock = users.empty() ? nullptr | ||
223 | 544 | : (*users.begin())->get_frame_clock(); | ||
224 | 545 | } | ||
225 | 441 | } | 546 | } |
226 | 442 | 547 | ||
227 | 443 | MirNativeBuffer* mcl::BufferStream::get_current_buffer_package() | 548 | MirNativeBuffer* mcl::BufferStream::get_current_buffer_package() |
228 | 444 | 549 | ||
229 | === modified file 'src/client/buffer_stream.h' | |||
230 | --- src/client/buffer_stream.h 2017-01-17 05:51:54 +0000 | |||
231 | +++ src/client/buffer_stream.h 2017-01-17 06:23:26 +0000 | |||
232 | @@ -26,11 +26,13 @@ | |||
233 | 26 | #include "mir/geometry/size.h" | 26 | #include "mir/geometry/size.h" |
234 | 27 | #include "mir/optional_value.h" | 27 | #include "mir/optional_value.h" |
235 | 28 | #include "buffer_stream_configuration.h" | 28 | #include "buffer_stream_configuration.h" |
236 | 29 | #include "frame_clock.h" | ||
237 | 29 | 30 | ||
238 | 30 | #include "mir_toolkit/client_types.h" | 31 | #include "mir_toolkit/client_types.h" |
239 | 31 | 32 | ||
240 | 32 | #include <EGL/eglplatform.h> | 33 | #include <EGL/eglplatform.h> |
241 | 33 | 34 | ||
242 | 35 | #include <unordered_set> | ||
243 | 34 | #include <queue> | 36 | #include <queue> |
244 | 35 | #include <memory> | 37 | #include <memory> |
245 | 36 | #include <mutex> | 38 | #include <mutex> |
246 | @@ -97,6 +99,8 @@ | |||
247 | 97 | 99 | ||
248 | 98 | int swap_interval() const override; | 100 | int swap_interval() const override; |
249 | 99 | MirWaitHandle* set_swap_interval(int interval) override; | 101 | MirWaitHandle* set_swap_interval(int interval) override; |
250 | 102 | void adopted_by(MirWindow*) override; | ||
251 | 103 | void unadopted_by(MirWindow*) override; | ||
252 | 100 | void set_buffer_cache_size(unsigned int) override; | 104 | void set_buffer_cache_size(unsigned int) override; |
253 | 101 | 105 | ||
254 | 102 | EGLNativeWindowType egl_native_window() override; | 106 | EGLNativeWindowType egl_native_window() override; |
255 | @@ -133,8 +137,9 @@ | |||
256 | 133 | void process_buffer(protobuf::Buffer const& buffer, std::unique_lock<std::mutex>&); | 137 | void process_buffer(protobuf::Buffer const& buffer, std::unique_lock<std::mutex>&); |
257 | 134 | void on_scale_set(float scale); | 138 | void on_scale_set(float scale); |
258 | 135 | void release_cpu_region(); | 139 | void release_cpu_region(); |
260 | 136 | MirWaitHandle* force_swap_interval(int interval); | 140 | MirWaitHandle* set_server_swap_interval(int i); |
261 | 137 | void init_swap_interval(); | 141 | void init_swap_interval(); |
262 | 142 | void wait_for_vsync(); | ||
263 | 138 | 143 | ||
264 | 139 | mutable std::mutex mutex; // Protects all members of *this | 144 | mutable std::mutex mutex; // Protects all members of *this |
265 | 140 | 145 | ||
266 | @@ -143,6 +148,8 @@ | |||
267 | 143 | std::unique_ptr<mir::protobuf::BufferStream> protobuf_bs; | 148 | std::unique_ptr<mir::protobuf::BufferStream> protobuf_bs; |
268 | 144 | 149 | ||
269 | 145 | optional_value<int> user_swap_interval; | 150 | optional_value<int> user_swap_interval; |
270 | 151 | int current_swap_interval; | ||
271 | 152 | bool using_client_side_vsync; | ||
272 | 146 | BufferStreamConfiguration interval_config; | 153 | BufferStreamConfiguration interval_config; |
273 | 147 | float scale_; | 154 | float scale_; |
274 | 148 | 155 | ||
275 | @@ -161,6 +168,10 @@ | |||
276 | 161 | std::weak_ptr<SurfaceMap> const map; | 168 | std::weak_ptr<SurfaceMap> const map; |
277 | 162 | std::shared_ptr<AsyncBufferFactory> const factory; | 169 | std::shared_ptr<AsyncBufferFactory> const factory; |
278 | 163 | MirRenderSurface* render_surface_; | 170 | MirRenderSurface* render_surface_; |
279 | 171 | |||
280 | 172 | std::unordered_set<MirWindow*> users; | ||
281 | 173 | std::shared_ptr<FrameClock> frame_clock; | ||
282 | 174 | mir::time::PosixTimestamp last_vsync; | ||
283 | 164 | }; | 175 | }; |
284 | 165 | 176 | ||
285 | 166 | } | 177 | } |
286 | 167 | 178 | ||
287 | === modified file 'src/client/error_stream.cpp' | |||
288 | --- src/client/error_stream.cpp 2017-01-17 05:51:54 +0000 | |||
289 | +++ src/client/error_stream.cpp 2017-01-17 06:23:26 +0000 | |||
290 | @@ -94,6 +94,12 @@ | |||
291 | 94 | { | 94 | { |
292 | 95 | BOOST_THROW_EXCEPTION(std::runtime_error(error)); | 95 | BOOST_THROW_EXCEPTION(std::runtime_error(error)); |
293 | 96 | } | 96 | } |
294 | 97 | void mcl::ErrorStream::adopted_by(MirWindow*) | ||
295 | 98 | { | ||
296 | 99 | } | ||
297 | 100 | void mcl::ErrorStream::unadopted_by(MirWindow*) | ||
298 | 101 | { | ||
299 | 102 | } | ||
300 | 97 | MirNativeBuffer* mcl::ErrorStream::get_current_buffer_package() | 103 | MirNativeBuffer* mcl::ErrorStream::get_current_buffer_package() |
301 | 98 | { | 104 | { |
302 | 99 | BOOST_THROW_EXCEPTION(std::runtime_error(error)); | 105 | BOOST_THROW_EXCEPTION(std::runtime_error(error)); |
303 | 100 | 106 | ||
304 | === modified file 'src/client/error_stream.h' | |||
305 | --- src/client/error_stream.h 2017-01-17 05:51:54 +0000 | |||
306 | +++ src/client/error_stream.h 2017-01-17 06:23:26 +0000 | |||
307 | @@ -41,6 +41,8 @@ | |||
308 | 41 | std::shared_ptr<MemoryRegion> secure_for_cpu_write() override; | 41 | std::shared_ptr<MemoryRegion> secure_for_cpu_write() override; |
309 | 42 | int swap_interval() const override; | 42 | int swap_interval() const override; |
310 | 43 | MirWaitHandle* set_swap_interval(int interval) override; | 43 | MirWaitHandle* set_swap_interval(int interval) override; |
311 | 44 | void adopted_by(MirWindow*) override; | ||
312 | 45 | void unadopted_by(MirWindow*) override; | ||
313 | 44 | MirNativeBuffer* get_current_buffer_package() override; | 46 | MirNativeBuffer* get_current_buffer_package() override; |
314 | 45 | MirPlatformType platform_type() override; | 47 | MirPlatformType platform_type() override; |
315 | 46 | frontend::BufferStreamId rpc_id() const override; | 48 | frontend::BufferStreamId rpc_id() const override; |
316 | 47 | 49 | ||
317 | === added file 'src/client/frame_clock.cpp' | |||
318 | --- src/client/frame_clock.cpp 1970-01-01 00:00:00 +0000 | |||
319 | +++ src/client/frame_clock.cpp 2017-01-17 06:23:26 +0000 | |||
320 | @@ -0,0 +1,156 @@ | |||
321 | 1 | /* | ||
322 | 2 | * Copyright © 2016 Canonical Ltd. | ||
323 | 3 | * | ||
324 | 4 | * This program is free software: you can redistribute it and/or modify it | ||
325 | 5 | * under the terms of the GNU Lesser General Public License version 3, | ||
326 | 6 | * as published by the Free Software Foundation. | ||
327 | 7 | * | ||
328 | 8 | * This program is distributed in the hope that it will be useful, | ||
329 | 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
330 | 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
331 | 11 | * GNU Lesser General Public License for more details. | ||
332 | 12 | * | ||
333 | 13 | * You should have received a copy of the GNU Lesser General Public License | ||
334 | 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
335 | 15 | * | ||
336 | 16 | * Authored by: Daniel van Vugt <daniel.van.vugt@canonical.com> | ||
337 | 17 | */ | ||
338 | 18 | |||
339 | 19 | #include "frame_clock.h" | ||
340 | 20 | #include <stdexcept> | ||
341 | 21 | #include <cassert> | ||
342 | 22 | |||
343 | 23 | using mir::client::FrameClock; | ||
344 | 24 | using mir::time::PosixTimestamp; | ||
345 | 25 | |||
346 | 26 | namespace | ||
347 | 27 | { | ||
348 | 28 | typedef std::unique_lock<std::mutex> Lock; | ||
349 | 29 | } // namespace | ||
350 | 30 | |||
351 | 31 | FrameClock::FrameClock(FrameClock::GetCurrentTime gct) | ||
352 | 32 | : get_current_time{gct} | ||
353 | 33 | , config_changed{false} | ||
354 | 34 | , period{0} | ||
355 | 35 | , resync_callback{std::bind(&FrameClock::fallback_resync_callback, this)} | ||
356 | 36 | { | ||
357 | 37 | } | ||
358 | 38 | |||
359 | 39 | void FrameClock::set_period(std::chrono::nanoseconds ns) | ||
360 | 40 | { | ||
361 | 41 | Lock lock(mutex); | ||
362 | 42 | period = ns; | ||
363 | 43 | config_changed = true; | ||
364 | 44 | } | ||
365 | 45 | |||
366 | 46 | void FrameClock::set_resync_callback(ResyncCallback cb) | ||
367 | 47 | { | ||
368 | 48 | Lock lock(mutex); | ||
369 | 49 | resync_callback = cb; | ||
370 | 50 | config_changed = true; | ||
371 | 51 | } | ||
372 | 52 | |||
373 | 53 | PosixTimestamp FrameClock::fallback_resync_callback() const | ||
374 | 54 | { | ||
375 | 55 | auto const now = get_current_time(PosixTimestamp().clock_id); | ||
376 | 56 | Lock lock(mutex); | ||
377 | 57 | /* | ||
378 | 58 | * The result here needs to be in phase for all processes that call it, | ||
379 | 59 | * so that nesting servers does not add lag. | ||
380 | 60 | */ | ||
381 | 61 | return period != period.zero() ? now - (now % period) : now; | ||
382 | 62 | } | ||
383 | 63 | |||
384 | 64 | PosixTimestamp FrameClock::next_frame_after(PosixTimestamp prev) const | ||
385 | 65 | { | ||
386 | 66 | Lock lock(mutex); | ||
387 | 67 | /* | ||
388 | 68 | * Unthrottled is an option too. But why?... Because a stream might exist | ||
389 | 69 | * that's not bound to a surface. And if it's not bound to a surface then | ||
390 | 70 | * it has no physical screen to sync to. Hence not throttled at all. | ||
391 | 71 | */ | ||
392 | 72 | if (period == period.zero()) | ||
393 | 73 | return prev; | ||
394 | 74 | |||
395 | 75 | /* | ||
396 | 76 | * Regardless of render times and scheduling delays, we should always | ||
397 | 77 | * target a perfectly even interval. This results in the greatest | ||
398 | 78 | * visual smoothness as well as providing a catch-up for cases where | ||
399 | 79 | * the client's render time was a little too long. | ||
400 | 80 | */ | ||
401 | 81 | auto target = prev + period; | ||
402 | 82 | |||
403 | 83 | /* | ||
404 | 84 | * Count missed frames. Note how the target time would need to be more than | ||
405 | 85 | * one frame in the past to count as a missed frame. If the target time | ||
406 | 86 | * is less than one frame in the past then there's still a chance to catch | ||
407 | 87 | * up by rendering the next frame without delay. This avoids decimating | ||
408 | 88 | * to half frame rate. | ||
409 | 89 | * Compared to triple buffering, this approach is reactive rather than | ||
410 | 90 | * proactive. Both approaches have the same catch-up ability to avoid | ||
411 | 91 | * half frame rate, but this approach has the added benefit of only | ||
412 | 92 | * one frame of lag to the compositor compared to triple buffering's two | ||
413 | 93 | * frames of lag. But we're also doing better than double buffering here | ||
414 | 94 | * in that this approach avoids any additive lag when nested. | ||
415 | 95 | */ | ||
416 | 96 | auto now = get_current_time(target.clock_id); | ||
417 | 97 | long const missed_frames = now > target ? (now - target) / period : 0L; | ||
418 | 98 | |||
419 | 99 | /* | ||
420 | 100 | * On the first frame and any resumption frame (after the client goes | ||
421 | 101 | * from idle to busy) this will trigger a query to ask for the latest | ||
422 | 102 | * hardware vsync timestamp. So we get in phase with the display. | ||
423 | 103 | * Crucially this is not required on most frames, so that even if it is | ||
424 | 104 | * implemented as a round trip to the server, that won't happen often. | ||
425 | 105 | */ | ||
426 | 106 | if (missed_frames > 1 || config_changed) | ||
427 | 107 | { | ||
428 | 108 | lock.unlock(); | ||
429 | 109 | auto const server_frame = resync_callback(); | ||
430 | 110 | lock.lock(); | ||
431 | 111 | |||
432 | 112 | /* | ||
433 | 113 | * Avoid mismatches (which will throw) and ensure we're always | ||
434 | 114 | * comparing timestamps of the same clock ID. This means our result | ||
435 | 115 | * 'target' might have a different clock to that of 'prev'. And that's | ||
436 | 116 | * OK... we want to use whatever clock ID the driver is using for | ||
437 | 117 | * its hardware vsync timestamps. We support migrating between clocks. | ||
438 | 118 | */ | ||
439 | 119 | now = get_current_time(server_frame.clock_id); | ||
440 | 120 | |||
441 | 121 | /* | ||
442 | 122 | * It's important to target a future time and not allow 'now'. This | ||
443 | 123 | * is because we will have some buffer queues for the time being that | ||
444 | 124 | * have swapinterval 1, which if overfilled by being too agressive will | ||
445 | 125 | * cause visual lag. After all buffer queues move to always dropping | ||
446 | 126 | * (mailbox mode), this delay won't be required as a safety. | ||
447 | 127 | */ | ||
448 | 128 | if (server_frame > now) | ||
449 | 129 | { | ||
450 | 130 | target = server_frame; | ||
451 | 131 | } | ||
452 | 132 | else | ||
453 | 133 | { | ||
454 | 134 | auto const age_ns = now - server_frame; | ||
455 | 135 | /* | ||
456 | 136 | * Ensure age_frames gets truncated if not already. | ||
457 | 137 | * C++ just guarantees a "signed integer type of at least 64 bits" | ||
458 | 138 | * for std::chrono::nanoseconds::rep | ||
459 | 139 | */ | ||
460 | 140 | auto const age_frames = age_ns / period; | ||
461 | 141 | target = server_frame + (age_frames + 1) * period; | ||
462 | 142 | } | ||
463 | 143 | assert(target > now); | ||
464 | 144 | config_changed = false; | ||
465 | 145 | } | ||
466 | 146 | else if (missed_frames > 0) | ||
467 | 147 | { | ||
468 | 148 | /* | ||
469 | 149 | * Low number of missed frames. Try catching up without missing more... | ||
470 | 150 | */ | ||
471 | 151 | // FIXME? Missing += operator | ||
472 | 152 | target = target + missed_frames * period; | ||
473 | 153 | } | ||
474 | 154 | |||
475 | 155 | return target; | ||
476 | 156 | } | ||
477 | 0 | 157 | ||
478 | === added file 'src/client/frame_clock.h' | |||
479 | --- src/client/frame_clock.h 1970-01-01 00:00:00 +0000 | |||
480 | +++ src/client/frame_clock.h 2017-01-17 06:23:26 +0000 | |||
481 | @@ -0,0 +1,73 @@ | |||
482 | 1 | /* | ||
483 | 2 | * Copyright © 2016 Canonical Ltd. | ||
484 | 3 | * | ||
485 | 4 | * This program is free software: you can redistribute it and/or modify it | ||
486 | 5 | * under the terms of the GNU Lesser General Public License version 3, | ||
487 | 6 | * as published by the Free Software Foundation. | ||
488 | 7 | * | ||
489 | 8 | * This program is distributed in the hope that it will be useful, | ||
490 | 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
491 | 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
492 | 11 | * GNU Lesser General Public License for more details. | ||
493 | 12 | * | ||
494 | 13 | * You should have received a copy of the GNU Lesser General Public License | ||
495 | 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
496 | 15 | * | ||
497 | 16 | * Authored by: Daniel van Vugt <daniel.van.vugt@canonical.com> | ||
498 | 17 | */ | ||
499 | 18 | |||
500 | 19 | #ifndef MIR_CLIENT_FRAME_CLOCK_H_ | ||
501 | 20 | #define MIR_CLIENT_FRAME_CLOCK_H_ | ||
502 | 21 | |||
503 | 22 | #include "mir/time/posix_timestamp.h" | ||
504 | 23 | #include <chrono> | ||
505 | 24 | #include <functional> | ||
506 | 25 | #include <mutex> | ||
507 | 26 | |||
508 | 27 | namespace mir { namespace client { | ||
509 | 28 | |||
510 | 29 | class FrameClock | ||
511 | 30 | { | ||
512 | 31 | public: | ||
513 | 32 | typedef std::function<time::PosixTimestamp()> ResyncCallback; | ||
514 | 33 | typedef std::function<time::PosixTimestamp(clockid_t)> GetCurrentTime; | ||
515 | 34 | |||
516 | 35 | FrameClock(GetCurrentTime); | ||
517 | 36 | FrameClock() : FrameClock(mir::time::PosixTimestamp::now) {} | ||
518 | 37 | |||
519 | 38 | /** | ||
520 | 39 | * Set the precise frame period in nanoseconds (1000000000/Hz). | ||
521 | 40 | */ | ||
522 | 41 | void set_period(std::chrono::nanoseconds); | ||
523 | 42 | |||
524 | 43 | /** | ||
525 | 44 | * Optionally set a callback that obtains the latest hardware vsync | ||
526 | 45 | * timestamp. This provides phase correction for increased precision but is | ||
527 | 46 | * not strictly required. | ||
528 | 47 | * Lowest precision: Don't provide a callback. | ||
529 | 48 | * Medium precision: Provide a callback which returns a recent timestamp. | ||
530 | 49 | * Highest precision: Provide a callback that queries the server. | ||
531 | 50 | */ | ||
532 | 51 | void set_resync_callback(ResyncCallback); | ||
533 | 52 | |||
534 | 53 | /** | ||
535 | 54 | * Return the next timestamp to sleep_until, which comes after the last one | ||
536 | 55 | * that was slept till. On the first frame you can just provide an | ||
537 | 56 | * uninitialized timestamp. | ||
538 | 57 | */ | ||
539 | 58 | time::PosixTimestamp next_frame_after(time::PosixTimestamp prev) const; | ||
540 | 59 | |||
541 | 60 | private: | ||
542 | 61 | time::PosixTimestamp fallback_resync_callback() const; | ||
543 | 62 | |||
544 | 63 | GetCurrentTime const get_current_time; | ||
545 | 64 | |||
546 | 65 | mutable std::mutex mutex; // Protects below fields: | ||
547 | 66 | mutable bool config_changed; | ||
548 | 67 | std::chrono::nanoseconds period; | ||
549 | 68 | ResyncCallback resync_callback; | ||
550 | 69 | }; | ||
551 | 70 | |||
552 | 71 | }} // namespace mir::client | ||
553 | 72 | |||
554 | 73 | #endif // MIR_CLIENT_FRAME_CLOCK_H_ | ||
555 | 0 | 74 | ||
556 | === modified file 'src/client/mir_buffer_stream_api.cpp' | |||
557 | --- src/client/mir_buffer_stream_api.cpp 2017-01-17 05:51:54 +0000 | |||
558 | +++ src/client/mir_buffer_stream_api.cpp 2017-01-17 06:23:26 +0000 | |||
559 | @@ -113,6 +113,13 @@ | |||
560 | 113 | void* context) | 113 | void* context) |
561 | 114 | try | 114 | try |
562 | 115 | { | 115 | { |
563 | 116 | /* | ||
564 | 117 | * TODO: Add client-side vsync support for mir_buffer_stream_swap_buffers() | ||
565 | 118 | * Not in a hurry though, because the old server-side vsync is still | ||
566 | 119 | * present and AFAIK the only user of swap_buffers callbacks is Xmir. | ||
567 | 120 | * There are many ways to approach the problem and some more | ||
568 | 121 | * contentious than others, so do it later. | ||
569 | 122 | */ | ||
570 | 116 | return buffer_stream->swap_buffers([buffer_stream, callback, context]{ | 123 | return buffer_stream->swap_buffers([buffer_stream, callback, context]{ |
571 | 117 | if (callback) | 124 | if (callback) |
572 | 118 | callback(buffer_stream, context); | 125 | callback(buffer_stream, context); |
573 | @@ -209,7 +216,7 @@ | |||
574 | 209 | MirWaitHandle* mir_buffer_stream_set_swapinterval(MirBufferStream* buffer_stream, int interval) | 216 | MirWaitHandle* mir_buffer_stream_set_swapinterval(MirBufferStream* buffer_stream, int interval) |
575 | 210 | try | 217 | try |
576 | 211 | { | 218 | { |
578 | 212 | if ((interval < 0) || (interval > 1)) | 219 | if (interval < 0) |
579 | 213 | return nullptr; | 220 | return nullptr; |
580 | 214 | 221 | ||
581 | 215 | if (!buffer_stream) | 222 | if (!buffer_stream) |
582 | 216 | 223 | ||
583 | === modified file 'src/client/mir_surface.cpp' | |||
584 | --- src/client/mir_surface.cpp 2017-01-17 05:51:54 +0000 | |||
585 | +++ src/client/mir_surface.cpp 2017-01-17 06:23:26 +0000 | |||
586 | @@ -21,6 +21,7 @@ | |||
587 | 21 | #include "cursor_configuration.h" | 21 | #include "cursor_configuration.h" |
588 | 22 | #include "make_protobuf_object.h" | 22 | #include "make_protobuf_object.h" |
589 | 23 | #include "mir_protobuf.pb.h" | 23 | #include "mir_protobuf.pb.h" |
590 | 24 | #include "connection_surface_map.h" | ||
591 | 24 | 25 | ||
592 | 25 | #include "mir_toolkit/mir_client_library.h" | 26 | #include "mir_toolkit/mir_client_library.h" |
593 | 26 | #include "mir/frontend/client_constants.h" | 27 | #include "mir/frontend/client_constants.h" |
594 | @@ -31,6 +32,7 @@ | |||
595 | 31 | #include "mir/input/xkb_mapper.h" | 32 | #include "mir/input/xkb_mapper.h" |
596 | 32 | #include "mir/cookie/cookie.h" | 33 | #include "mir/cookie/cookie.h" |
597 | 33 | #include "mir_cookie.h" | 34 | #include "mir_cookie.h" |
598 | 35 | #include "mir/time/posix_timestamp.h" | ||
599 | 34 | 36 | ||
600 | 35 | #include <cassert> | 37 | #include <cassert> |
601 | 36 | #include <unistd.h> | 38 | #include <unistd.h> |
602 | @@ -46,6 +48,8 @@ | |||
603 | 46 | namespace gp = google::protobuf; | 48 | namespace gp = google::protobuf; |
604 | 47 | namespace md = mir::dispatch; | 49 | namespace md = mir::dispatch; |
605 | 48 | 50 | ||
606 | 51 | using mir::client::FrameClock; | ||
607 | 52 | |||
608 | 49 | namespace | 53 | namespace |
609 | 50 | { | 54 | { |
610 | 51 | std::mutex handle_mutex; | 55 | std::mutex handle_mutex; |
611 | @@ -96,6 +100,7 @@ | |||
612 | 96 | std::shared_ptr<MirWaitHandle> const& handle) : | 100 | std::shared_ptr<MirWaitHandle> const& handle) : |
613 | 97 | surface{mcl::make_protobuf_object<mir::protobuf::Surface>()}, | 101 | surface{mcl::make_protobuf_object<mir::protobuf::Surface>()}, |
614 | 98 | connection_(conn), | 102 | connection_(conn), |
615 | 103 | frame_clock(std::make_shared<FrameClock>()), | ||
616 | 99 | creation_handle(handle) | 104 | creation_handle(handle) |
617 | 100 | { | 105 | { |
618 | 101 | surface->set_error(error); | 106 | surface->set_error(error); |
619 | @@ -103,6 +108,8 @@ | |||
620 | 103 | 108 | ||
621 | 104 | std::lock_guard<decltype(handle_mutex)> lock(handle_mutex); | 109 | std::lock_guard<decltype(handle_mutex)> lock(handle_mutex); |
622 | 105 | valid_surfaces.insert(this); | 110 | valid_surfaces.insert(this); |
623 | 111 | |||
624 | 112 | configure_frame_clock(); | ||
625 | 106 | } | 113 | } |
626 | 107 | 114 | ||
627 | 108 | MirSurface::MirSurface( | 115 | MirSurface::MirSurface( |
628 | @@ -126,12 +133,19 @@ | |||
629 | 126 | input_platform(input_platform), | 133 | input_platform(input_platform), |
630 | 127 | keymapper(std::make_shared<mircv::XKBMapper>()), | 134 | keymapper(std::make_shared<mircv::XKBMapper>()), |
631 | 128 | configure_result{mcl::make_protobuf_object<mir::protobuf::SurfaceSetting>()}, | 135 | configure_result{mcl::make_protobuf_object<mir::protobuf::SurfaceSetting>()}, |
632 | 136 | frame_clock(std::make_shared<FrameClock>()), | ||
633 | 129 | creation_handle(handle), | 137 | creation_handle(handle), |
634 | 130 | size({surface_proto.width(), surface_proto.height()}), | 138 | size({surface_proto.width(), surface_proto.height()}), |
635 | 131 | format(static_cast<MirPixelFormat>(surface_proto.pixel_format())), | 139 | format(static_cast<MirPixelFormat>(surface_proto.pixel_format())), |
636 | 132 | usage(static_cast<MirBufferUsage>(surface_proto.buffer_usage())), | 140 | usage(static_cast<MirBufferUsage>(surface_proto.buffer_usage())), |
637 | 133 | output_id(spec.output_id.is_set() ? spec.output_id.value() : static_cast<uint32_t>(mir_display_output_id_invalid)) | 141 | output_id(spec.output_id.is_set() ? spec.output_id.value() : static_cast<uint32_t>(mir_display_output_id_invalid)) |
638 | 134 | { | 142 | { |
639 | 143 | if (default_stream) | ||
640 | 144 | { | ||
641 | 145 | streams.insert(default_stream); | ||
642 | 146 | default_stream->adopted_by(this); | ||
643 | 147 | } | ||
644 | 148 | |||
645 | 135 | for(int i = 0; i < surface_proto.attributes_size(); i++) | 149 | for(int i = 0; i < surface_proto.attributes_size(); i++) |
646 | 136 | { | 150 | { |
647 | 137 | auto const& attrib = surface_proto.attributes(i); | 151 | auto const& attrib = surface_proto.attributes(i); |
648 | @@ -155,10 +169,23 @@ | |||
649 | 155 | 169 | ||
650 | 156 | std::lock_guard<decltype(handle_mutex)> lock(handle_mutex); | 170 | std::lock_guard<decltype(handle_mutex)> lock(handle_mutex); |
651 | 157 | valid_surfaces.insert(this); | 171 | valid_surfaces.insert(this); |
652 | 172 | |||
653 | 173 | configure_frame_clock(); | ||
654 | 158 | } | 174 | } |
655 | 159 | 175 | ||
656 | 160 | MirSurface::~MirSurface() | 176 | MirSurface::~MirSurface() |
657 | 161 | { | 177 | { |
658 | 178 | StreamSet old_streams; | ||
659 | 179 | |||
660 | 180 | { | ||
661 | 181 | std::lock_guard<decltype(mutex)> lock(mutex); | ||
662 | 182 | old_streams = std::move(streams); | ||
663 | 183 | } | ||
664 | 184 | |||
665 | 185 | // Do callbacks without holding the lock | ||
666 | 186 | for (auto const& s : old_streams) | ||
667 | 187 | s->unadopted_by(this); | ||
668 | 188 | |||
669 | 162 | { | 189 | { |
670 | 163 | std::lock_guard<decltype(handle_mutex)> lock(handle_mutex); | 190 | std::lock_guard<decltype(handle_mutex)> lock(handle_mutex); |
671 | 164 | valid_surfaces.erase(this); | 191 | valid_surfaces.erase(this); |
672 | @@ -172,6 +199,18 @@ | |||
673 | 172 | close(surface->fd(i)); | 199 | close(surface->fd(i)); |
674 | 173 | } | 200 | } |
675 | 174 | 201 | ||
676 | 202 | void MirSurface::configure_frame_clock() | ||
677 | 203 | { | ||
678 | 204 | /* | ||
679 | 205 | * TODO: Implement frame_clock->set_resync_callback(...) when IPC to get | ||
680 | 206 | * timestamps from the server exists. | ||
681 | 207 | * Until we do, client-side vsync will perform suboptimally (it is | ||
682 | 208 | * randomly up to one frame out of phase with the real display). | ||
683 | 209 | * However even while it's suboptimal it's dramatically lower latency | ||
684 | 210 | * than the old approach and still totally eliminates nesting lag. | ||
685 | 211 | */ | ||
686 | 212 | } | ||
687 | 213 | |||
688 | 175 | MirWindowParameters MirSurface::get_parameters() const | 214 | MirWindowParameters MirSurface::get_parameters() const |
689 | 176 | { | 215 | { |
690 | 177 | std::lock_guard<decltype(mutex)> lock(mutex); | 216 | std::lock_guard<decltype(mutex)> lock(mutex); |
691 | @@ -458,6 +497,36 @@ | |||
692 | 458 | default_stream->set_size(size); | 497 | default_stream->set_size(size); |
693 | 459 | break; | 498 | break; |
694 | 460 | } | 499 | } |
695 | 500 | case mir_event_type_surface_output: | ||
696 | 501 | { | ||
697 | 502 | /* | ||
698 | 503 | * We set the frame clock according to the (maximum) refresh rate of | ||
699 | 504 | * the display. Note that we don't do the obvious thing and measure | ||
700 | 505 | * previous vsyncs mainly because that's a very unreliable predictor | ||
701 | 506 | * of the desired maximum refresh rate in the case of GSync/FreeSync | ||
702 | 507 | * and panel self-refresh. You can't assume the previous frame | ||
703 | 508 | * represents the display running at full native speed and so should | ||
704 | 509 | * not measure it. Instead we conveniently have | ||
705 | 510 | * mir_surface_output_event_get_refresh_rate that tells us the full | ||
706 | 511 | * native speed of the most relevant output... | ||
707 | 512 | */ | ||
708 | 513 | auto soevent = mir_event_get_surface_output_event(&e); | ||
709 | 514 | auto rate = mir_surface_output_event_get_refresh_rate(soevent); | ||
710 | 515 | if (rate > 10.0) // should be >0, but 10 to workaround LP: #1639725 | ||
711 | 516 | { | ||
712 | 517 | std::chrono::nanoseconds const ns( | ||
713 | 518 | static_cast<long>(1000000000L / rate)); | ||
714 | 519 | frame_clock->set_period(ns); | ||
715 | 520 | } | ||
716 | 521 | /* else: The graphics driver has not provided valid timing so we will | ||
717 | 522 | * default to swap interval 0 behaviour. | ||
718 | 523 | * Or would people prefer some different fallback? | ||
719 | 524 | * - Re-enable server-side vsync? | ||
720 | 525 | * - Rate limit to 60Hz? | ||
721 | 526 | * - Just fix LP: #1639725? | ||
722 | 527 | */ | ||
723 | 528 | break; | ||
724 | 529 | } | ||
725 | 461 | default: | 530 | default: |
726 | 462 | break; | 531 | break; |
727 | 463 | }; | 532 | }; |
728 | @@ -609,17 +678,30 @@ | |||
729 | 609 | 678 | ||
730 | 610 | if (spec.streams.is_set()) | 679 | if (spec.streams.is_set()) |
731 | 611 | { | 680 | { |
732 | 681 | std::shared_ptr<mir::client::ConnectionSurfaceMap> map; | ||
733 | 682 | StreamSet old_streams, new_streams; | ||
734 | 683 | |||
735 | 612 | /* | 684 | /* |
741 | 613 | * Note that we don't check for errors from modify_surface. So in | 685 | * Note that we are updating our local copy of 'streams' before the |
742 | 614 | * updating default_stream here, we're just assuming it will succeed. | 686 | * server has updated its copy. That's mainly because we have never yet |
743 | 615 | * Seems to be a harmless assumption to have made so far and much | 687 | * implemented any kind of client-side error handling for when |
744 | 616 | * simpler than communicating back suceessful mappings from the server, | 688 | * modify_surface fails and is rejected. There's a prototype started |
745 | 617 | * but there is a prototype started for that: lp:~vanvugt/mir/adoption | 689 | * along those lines in lp:~vanvugt/mir/adoption, but it does not seem |
746 | 690 | * to be important to block on... | ||
747 | 691 | * The worst case is still perfectly acceptable: That is if the server | ||
748 | 692 | * rejects the surface spec our local copy of streams is wrong. That's | ||
749 | 693 | * a harmless consequence which just means the stream is now | ||
750 | 694 | * synchronized to the output the client intended even though the server | ||
751 | 695 | * is refusing to display it. | ||
752 | 618 | */ | 696 | */ |
753 | 619 | { | 697 | { |
754 | 620 | std::lock_guard<decltype(mutex)> lock(mutex); | 698 | std::lock_guard<decltype(mutex)> lock(mutex); |
755 | 621 | default_stream = nullptr; | 699 | default_stream = nullptr; |
756 | 700 | old_streams = std::move(streams); | ||
757 | 701 | streams.clear(); // Leave the container valid before we unlock | ||
758 | 702 | map = connection_->connection_surface_map(); | ||
759 | 622 | } | 703 | } |
760 | 704 | |||
761 | 623 | for(auto const& stream : spec.streams.value()) | 705 | for(auto const& stream : spec.streams.value()) |
762 | 624 | { | 706 | { |
763 | 625 | auto const new_stream = surface_specification->add_stream(); | 707 | auto const new_stream = surface_specification->add_stream(); |
764 | @@ -631,6 +713,27 @@ | |||
765 | 631 | new_stream->set_width(stream.size.value().width.as_int()); | 713 | new_stream->set_width(stream.size.value().width.as_int()); |
766 | 632 | new_stream->set_height(stream.size.value().height.as_int()); | 714 | new_stream->set_height(stream.size.value().height.as_int()); |
767 | 633 | } | 715 | } |
768 | 716 | |||
769 | 717 | mir::frontend::BufferStreamId id(stream.stream_id); | ||
770 | 718 | if (auto bs = map->stream(id)) | ||
771 | 719 | new_streams.insert(bs); | ||
772 | 720 | /* else: we could implement stream ID validation here, which we've | ||
773 | 721 | * never had before. | ||
774 | 722 | * The only problem with that plan is that we have some tests | ||
775 | 723 | * that rely on the ability to pass in invalid stream IDs so those | ||
776 | 724 | * need fixing. | ||
777 | 725 | */ | ||
778 | 726 | } | ||
779 | 727 | |||
780 | 728 | // Worth optimizing and avoiding the intersection of both sets?.... | ||
781 | 729 | for (auto const& old_stream : old_streams) | ||
782 | 730 | old_stream->unadopted_by(this); | ||
783 | 731 | for (auto const& new_stream : new_streams) | ||
784 | 732 | new_stream->adopted_by(this); | ||
785 | 733 | // ... probably not, since we can std::move(new_streams) this way... | ||
786 | 734 | { | ||
787 | 735 | std::unique_lock<decltype(mutex)> lock(mutex); | ||
788 | 736 | streams = std::move(new_streams); | ||
789 | 634 | } | 737 | } |
790 | 635 | } | 738 | } |
791 | 636 | 739 | ||
792 | @@ -672,3 +775,8 @@ | |||
793 | 672 | { | 775 | { |
794 | 673 | return connection_; | 776 | return connection_; |
795 | 674 | } | 777 | } |
796 | 778 | |||
797 | 779 | std::shared_ptr<FrameClock> MirSurface::get_frame_clock() const | ||
798 | 780 | { | ||
799 | 781 | return frame_clock; | ||
800 | 782 | } | ||
801 | 675 | 783 | ||
802 | === modified file 'src/client/mir_surface.h' | |||
803 | --- src/client/mir_surface.h 2017-01-17 05:51:54 +0000 | |||
804 | +++ src/client/mir_surface.h 2017-01-17 06:23:26 +0000 | |||
805 | @@ -21,6 +21,7 @@ | |||
806 | 21 | #include "cursor_configuration.h" | 21 | #include "cursor_configuration.h" |
807 | 22 | #include "mir/mir_buffer_stream.h" | 22 | #include "mir/mir_buffer_stream.h" |
808 | 23 | #include "mir_wait_handle.h" | 23 | #include "mir_wait_handle.h" |
809 | 24 | #include "frame_clock.h" | ||
810 | 24 | #include "rpc/mir_display_server.h" | 25 | #include "rpc/mir_display_server.h" |
811 | 25 | #include "rpc/mir_display_server_debug.h" | 26 | #include "rpc/mir_display_server_debug.h" |
812 | 26 | 27 | ||
813 | @@ -32,6 +33,7 @@ | |||
814 | 32 | #include "mir_toolkit/common.h" | 33 | #include "mir_toolkit/common.h" |
815 | 33 | #include "mir_toolkit/mir_client_library.h" | 34 | #include "mir_toolkit/mir_client_library.h" |
816 | 34 | #include "mir/graphics/native_buffer.h" | 35 | #include "mir/graphics/native_buffer.h" |
817 | 36 | #include "mir/time/posix_timestamp.h" | ||
818 | 35 | 37 | ||
819 | 36 | #include <memory> | 38 | #include <memory> |
820 | 37 | #include <functional> | 39 | #include <functional> |
821 | @@ -214,9 +216,13 @@ | |||
822 | 214 | 216 | ||
823 | 215 | MirWaitHandle* request_persistent_id(mir_surface_id_callback callback, void* context); | 217 | MirWaitHandle* request_persistent_id(mir_surface_id_callback callback, void* context); |
824 | 216 | MirConnection* connection() const; | 218 | MirConnection* connection() const; |
825 | 219 | |||
826 | 220 | std::shared_ptr<mir::client::FrameClock> get_frame_clock() const; | ||
827 | 221 | |||
828 | 217 | private: | 222 | private: |
829 | 218 | std::mutex mutable mutex; // Protects all members of *this | 223 | std::mutex mutable mutex; // Protects all members of *this |
830 | 219 | 224 | ||
831 | 225 | void configure_frame_clock(); | ||
832 | 220 | void on_configured(); | 226 | void on_configured(); |
833 | 221 | void on_cursor_configured(); | 227 | void on_cursor_configured(); |
834 | 222 | void acquired_persistent_id(mir_surface_id_callback callback, void* context); | 228 | void acquired_persistent_id(mir_surface_id_callback callback, void* context); |
835 | @@ -242,6 +248,8 @@ | |||
836 | 242 | 248 | ||
837 | 243 | //Deprecated functions can cause MirSurfaces to be created with a default stream | 249 | //Deprecated functions can cause MirSurfaces to be created with a default stream |
838 | 244 | std::shared_ptr<MirBufferStream> default_stream; | 250 | std::shared_ptr<MirBufferStream> default_stream; |
839 | 251 | typedef std::unordered_set<std::shared_ptr<MirBufferStream>> StreamSet; | ||
840 | 252 | StreamSet streams; | ||
841 | 245 | std::shared_ptr<mir::input::receiver::InputPlatform> const input_platform; | 253 | std::shared_ptr<mir::input::receiver::InputPlatform> const input_platform; |
842 | 246 | std::shared_ptr<mir::input::receiver::XKBMapper> const keymapper; | 254 | std::shared_ptr<mir::input::receiver::XKBMapper> const keymapper; |
843 | 247 | 255 | ||
844 | @@ -251,6 +259,8 @@ | |||
845 | 251 | int attrib_cache[mir_window_attribs]; | 259 | int attrib_cache[mir_window_attribs]; |
846 | 252 | MirOrientation orientation = mir_orientation_normal; | 260 | MirOrientation orientation = mir_orientation_normal; |
847 | 253 | 261 | ||
848 | 262 | std::shared_ptr<mir::client::FrameClock> const frame_clock; | ||
849 | 263 | |||
850 | 254 | std::function<void(MirEvent const*)> handle_event_callback; | 264 | std::function<void(MirEvent const*)> handle_event_callback; |
851 | 255 | std::shared_ptr<mir::dispatch::ThreadedDispatcher> input_thread; | 265 | std::shared_ptr<mir::dispatch::ThreadedDispatcher> input_thread; |
852 | 256 | 266 | ||
853 | 257 | 267 | ||
854 | === modified file 'src/client/mir_surface_api.cpp' | |||
855 | --- src/client/mir_surface_api.cpp 2017-01-17 05:51:54 +0000 | |||
856 | +++ src/client/mir_surface_api.cpp 2017-01-17 06:23:26 +0000 | |||
857 | @@ -1204,7 +1204,7 @@ | |||
858 | 1204 | 1204 | ||
859 | 1205 | MirWaitHandle* mir_surface_set_swapinterval(MirSurface* surf, int interval) | 1205 | MirWaitHandle* mir_surface_set_swapinterval(MirSurface* surf, int interval) |
860 | 1206 | { | 1206 | { |
862 | 1207 | if ((interval < 0) || (interval > 1)) | 1207 | if (interval < 0) |
863 | 1208 | return nullptr; | 1208 | return nullptr; |
864 | 1209 | 1209 | ||
865 | 1210 | try | 1210 | try |
866 | 1211 | 1211 | ||
867 | === modified file 'src/client/screencast_stream.cpp' | |||
868 | --- src/client/screencast_stream.cpp 2017-01-17 05:51:54 +0000 | |||
869 | +++ src/client/screencast_stream.cpp 2017-01-17 06:23:26 +0000 | |||
870 | @@ -264,6 +264,14 @@ | |||
871 | 264 | BOOST_THROW_EXCEPTION(std::logic_error("Attempt to set swap interval on screencast is invalid")); | 264 | BOOST_THROW_EXCEPTION(std::logic_error("Attempt to set swap interval on screencast is invalid")); |
872 | 265 | } | 265 | } |
873 | 266 | 266 | ||
874 | 267 | void mcl::ScreencastStream::adopted_by(MirWindow*) | ||
875 | 268 | { | ||
876 | 269 | } | ||
877 | 270 | |||
878 | 271 | void mcl::ScreencastStream::unadopted_by(MirWindow*) | ||
879 | 272 | { | ||
880 | 273 | } | ||
881 | 274 | |||
882 | 267 | void mcl::ScreencastStream::set_size(geom::Size) | 275 | void mcl::ScreencastStream::set_size(geom::Size) |
883 | 268 | { | 276 | { |
884 | 269 | BOOST_THROW_EXCEPTION(std::logic_error("Attempt to set size on screencast is invalid")); | 277 | BOOST_THROW_EXCEPTION(std::logic_error("Attempt to set size on screencast is invalid")); |
885 | 270 | 278 | ||
886 | === modified file 'src/client/screencast_stream.h' | |||
887 | --- src/client/screencast_stream.h 2017-01-17 05:51:54 +0000 | |||
888 | +++ src/client/screencast_stream.h 2017-01-17 06:23:26 +0000 | |||
889 | @@ -78,6 +78,8 @@ | |||
890 | 78 | uint32_t get_current_buffer_id() override; | 78 | uint32_t get_current_buffer_id() override; |
891 | 79 | int swap_interval() const override; | 79 | int swap_interval() const override; |
892 | 80 | MirWaitHandle* set_swap_interval(int interval) override; | 80 | MirWaitHandle* set_swap_interval(int interval) override; |
893 | 81 | void adopted_by(MirWindow*) override; | ||
894 | 82 | void unadopted_by(MirWindow*) override; | ||
895 | 81 | void set_buffer_cache_size(unsigned int) override; | 83 | void set_buffer_cache_size(unsigned int) override; |
896 | 82 | 84 | ||
897 | 83 | EGLNativeWindowType egl_native_window() override; | 85 | EGLNativeWindowType egl_native_window() override; |
898 | 84 | 86 | ||
899 | === modified file 'src/include/client/mir/mir_buffer_stream.h' | |||
900 | --- src/include/client/mir/mir_buffer_stream.h 2017-01-17 05:51:54 +0000 | |||
901 | +++ src/include/client/mir/mir_buffer_stream.h 2017-01-17 06:23:26 +0000 | |||
902 | @@ -88,6 +88,8 @@ | |||
903 | 88 | 88 | ||
904 | 89 | virtual int swap_interval() const = 0; | 89 | virtual int swap_interval() const = 0; |
905 | 90 | virtual MirWaitHandle* set_swap_interval(int interval) = 0; | 90 | virtual MirWaitHandle* set_swap_interval(int interval) = 0; |
906 | 91 | virtual void adopted_by(MirWindow*) = 0; | ||
907 | 92 | virtual void unadopted_by(MirWindow*) = 0; | ||
908 | 91 | 93 | ||
909 | 92 | virtual MirNativeBuffer* get_current_buffer_package() = 0; | 94 | virtual MirNativeBuffer* get_current_buffer_package() = 0; |
910 | 93 | virtual MirPlatformType platform_type() = 0; | 95 | virtual MirPlatformType platform_type() = 0; |
911 | 94 | 96 | ||
912 | === modified file 'tests/acceptance-tests/test_client_library.cpp' | |||
913 | --- tests/acceptance-tests/test_client_library.cpp 2017-01-17 05:51:54 +0000 | |||
914 | +++ tests/acceptance-tests/test_client_library.cpp 2017-01-17 06:23:26 +0000 | |||
915 | @@ -809,6 +809,11 @@ | |||
916 | 809 | 809 | ||
917 | 810 | buffers = 0; | 810 | buffers = 0; |
918 | 811 | 811 | ||
919 | 812 | // StubDisplayConfiguration is set to 60Hz and we now actually honour | ||
920 | 813 | // that. So to avoid 1024 frames taking 17 seconds or so... | ||
921 | 814 | mir_buffer_stream_set_swapinterval(mir_surface_get_buffer_stream(surf_one), 0); | ||
922 | 815 | mir_buffer_stream_set_swapinterval(mir_surface_get_buffer_stream(surf_two), 0); | ||
923 | 816 | |||
924 | 812 | while (buffers < 1024) | 817 | while (buffers < 1024) |
925 | 813 | { | 818 | { |
926 | 814 | mir_buffer_stream_swap_buffers_sync( | 819 | mir_buffer_stream_swap_buffers_sync( |
927 | 815 | 820 | ||
928 | === modified file 'tests/acceptance-tests/test_latency.cpp' | |||
929 | --- tests/acceptance-tests/test_latency.cpp 2017-01-17 05:51:54 +0000 | |||
930 | +++ tests/acceptance-tests/test_latency.cpp 2017-01-17 06:23:26 +0000 | |||
931 | @@ -24,6 +24,7 @@ | |||
932 | 24 | #include "mir/test/doubles/null_display.h" | 24 | #include "mir/test/doubles/null_display.h" |
933 | 25 | #include "mir/test/doubles/null_display_buffer.h" | 25 | #include "mir/test/doubles/null_display_buffer.h" |
934 | 26 | #include "mir/test/doubles/null_display_sync_group.h" | 26 | #include "mir/test/doubles/null_display_sync_group.h" |
935 | 27 | #include "mir/test/doubles/stub_display_configuration.h" | ||
936 | 27 | #include "mir/test/fake_shared.h" | 28 | #include "mir/test/fake_shared.h" |
937 | 28 | #include "mir_test_framework/visible_surface.h" | 29 | #include "mir_test_framework/visible_surface.h" |
938 | 29 | #include "mir/options/option.h" | 30 | #include "mir/options/option.h" |
939 | @@ -240,12 +241,36 @@ | |||
940 | 240 | TimeTrackingDisplay(Stats& stats) | 241 | TimeTrackingDisplay(Stats& stats) |
941 | 241 | : group{stats} | 242 | : group{stats} |
942 | 242 | { | 243 | { |
943 | 244 | mg::DisplayConfigurationOutput output{ | ||
944 | 245 | mg::DisplayConfigurationOutputId{1}, | ||
945 | 246 | mg::DisplayConfigurationCardId{1}, | ||
946 | 247 | mg::DisplayConfigurationOutputType::hdmia, | ||
947 | 248 | std::vector<MirPixelFormat>{mir_pixel_format_abgr_8888}, | ||
948 | 249 | {{{1920,1080}, refresh_rate}}, // <=== Refresh rate must be valid | ||
949 | 250 | 0, mir::geometry::Size{}, true, true, mir::geometry::Point{}, 0, | ||
950 | 251 | mir_pixel_format_abgr_8888, mir_power_mode_on, | ||
951 | 252 | mir_orientation_normal, | ||
952 | 253 | 1.0f, | ||
953 | 254 | mir_form_factor_monitor, | ||
954 | 255 | mir_subpixel_arrangement_unknown, | ||
955 | 256 | {}, | ||
956 | 257 | mir_output_gamma_unsupported, | ||
957 | 258 | {} | ||
958 | 259 | }; | ||
959 | 260 | outputs.push_back(output); | ||
960 | 261 | } | ||
961 | 262 | |||
962 | 263 | std::unique_ptr<mg::DisplayConfiguration> configuration() const override | ||
963 | 264 | { | ||
964 | 265 | return std::make_unique<mtd::StubDisplayConfig>(outputs); | ||
965 | 243 | } | 266 | } |
966 | 244 | 267 | ||
967 | 245 | void for_each_display_sync_group(std::function<void(mg::DisplaySyncGroup&)> const& f) override | 268 | void for_each_display_sync_group(std::function<void(mg::DisplaySyncGroup&)> const& f) override |
968 | 246 | { | 269 | { |
969 | 247 | f(group); | 270 | f(group); |
970 | 248 | } | 271 | } |
971 | 272 | |||
972 | 273 | std::vector<mg::DisplayConfigurationOutput> outputs; | ||
973 | 249 | TimeTrackingGroup group; | 274 | TimeTrackingGroup group; |
974 | 250 | }; | 275 | }; |
975 | 251 | 276 | ||
976 | @@ -288,7 +313,7 @@ | |||
977 | 288 | }; | 313 | }; |
978 | 289 | } | 314 | } |
979 | 290 | 315 | ||
981 | 291 | TEST_F(ClientLatency, average_latency_is_less_than_nbuffers) | 316 | TEST_F(ClientLatency, average_swap_buffers_sync_latency_is_one_frame) |
982 | 292 | { | 317 | { |
983 | 293 | auto stream = mir_window_get_buffer_stream(surface); | 318 | auto stream = mir_window_get_buffer_stream(surface); |
984 | 294 | auto const deadline = steady_clock::now() + 60s; | 319 | auto const deadline = steady_clock::now() + 60s; |
985 | @@ -308,7 +333,7 @@ | |||
986 | 308 | 333 | ||
987 | 309 | auto average_latency = display.group.average_latency(); | 334 | auto average_latency = display.group.average_latency(); |
988 | 310 | 335 | ||
990 | 311 | EXPECT_THAT(average_latency, Lt(expected_client_buffers)); | 336 | EXPECT_NEAR(1.0f, average_latency, error_margin); |
991 | 312 | } | 337 | } |
992 | 313 | 338 | ||
993 | 314 | TEST_F(ClientLatency, max_latency_is_limited_to_nbuffers) | 339 | TEST_F(ClientLatency, max_latency_is_limited_to_nbuffers) |
994 | 315 | 340 | ||
995 | === modified file 'tests/include/mir/test/doubles/mock_mir_buffer_stream.h' | |||
996 | --- tests/include/mir/test/doubles/mock_mir_buffer_stream.h 2017-01-17 05:51:54 +0000 | |||
997 | +++ tests/include/mir/test/doubles/mock_mir_buffer_stream.h 2017-01-17 06:23:26 +0000 | |||
998 | @@ -43,6 +43,8 @@ | |||
999 | 43 | MOCK_METHOD0(secure_for_cpu_write, std::shared_ptr<client::MemoryRegion>()); | 43 | MOCK_METHOD0(secure_for_cpu_write, std::shared_ptr<client::MemoryRegion>()); |
1000 | 44 | MOCK_CONST_METHOD0(swap_interval, int()); | 44 | MOCK_CONST_METHOD0(swap_interval, int()); |
1001 | 45 | MOCK_METHOD1(set_swap_interval, MirWaitHandle*(int)); | 45 | MOCK_METHOD1(set_swap_interval, MirWaitHandle*(int)); |
1002 | 46 | MOCK_METHOD1(adopted_by, void(MirWindow*)); | ||
1003 | 47 | MOCK_METHOD1(unadopted_by, void(MirWindow*)); | ||
1004 | 46 | MOCK_METHOD0(platform_type, MirPlatformType(void)); | 48 | MOCK_METHOD0(platform_type, MirPlatformType(void)); |
1005 | 47 | MOCK_METHOD0(get_current_buffer_package, MirNativeBuffer*(void)); | 49 | MOCK_METHOD0(get_current_buffer_package, MirNativeBuffer*(void)); |
1006 | 48 | MOCK_METHOD0(get_create_wait_handle, MirWaitHandle*(void)); | 50 | MOCK_METHOD0(get_create_wait_handle, MirWaitHandle*(void)); |
1007 | 49 | 51 | ||
1008 | === modified file 'tests/unit-tests/client/CMakeLists.txt' | |||
1009 | --- tests/unit-tests/client/CMakeLists.txt 2017-01-17 05:51:54 +0000 | |||
1010 | +++ tests/unit-tests/client/CMakeLists.txt 2017-01-17 06:23:26 +0000 | |||
1011 | @@ -24,6 +24,7 @@ | |||
1012 | 24 | ${CMAKE_CURRENT_SOURCE_DIR}/test_client_mir_error.cpp | 24 | ${CMAKE_CURRENT_SOURCE_DIR}/test_client_mir_error.cpp |
1013 | 25 | ${CMAKE_CURRENT_SOURCE_DIR}/test_no_tls_future.cpp | 25 | ${CMAKE_CURRENT_SOURCE_DIR}/test_no_tls_future.cpp |
1014 | 26 | ${CMAKE_CURRENT_SOURCE_DIR}/test_mir_render_surface.cpp | 26 | ${CMAKE_CURRENT_SOURCE_DIR}/test_mir_render_surface.cpp |
1015 | 27 | ${CMAKE_CURRENT_SOURCE_DIR}/test_frame_clock.cpp | ||
1016 | 27 | ) | 28 | ) |
1017 | 28 | 29 | ||
1018 | 29 | if (NOT MIR_DISABLE_INPUT) | 30 | if (NOT MIR_DISABLE_INPUT) |
1019 | 30 | 31 | ||
1020 | === modified file 'tests/unit-tests/client/test_client_mir_surface.cpp' | |||
1021 | --- tests/unit-tests/client/test_client_mir_surface.cpp 2017-01-17 05:51:54 +0000 | |||
1022 | +++ tests/unit-tests/client/test_client_mir_surface.cpp 2017-01-17 06:23:26 +0000 | |||
1023 | @@ -416,6 +416,87 @@ | |||
1024 | 416 | EXPECT_TRUE(dispatched->wait_for(std::chrono::seconds{5})); | 416 | EXPECT_TRUE(dispatched->wait_for(std::chrono::seconds{5})); |
1025 | 417 | } | 417 | } |
1026 | 418 | 418 | ||
1027 | 419 | TEST_F(MirClientSurfaceTest, adopts_the_default_stream) | ||
1028 | 420 | { | ||
1029 | 421 | using namespace ::testing; | ||
1030 | 422 | |||
1031 | 423 | auto mock_input_platform = std::make_shared<MockClientInputPlatform>(); | ||
1032 | 424 | auto mock_stream = std::make_shared<mtd::MockMirBufferStream>(); | ||
1033 | 425 | |||
1034 | 426 | MirWindow* adopted_by = nullptr; | ||
1035 | 427 | MirWindow* unadopted_by = nullptr; | ||
1036 | 428 | EXPECT_CALL(*mock_stream, adopted_by(_)) | ||
1037 | 429 | .WillOnce(SaveArg<0>(&adopted_by)); | ||
1038 | 430 | EXPECT_CALL(*mock_stream, unadopted_by(_)) | ||
1039 | 431 | .WillOnce(SaveArg<0>(&unadopted_by)); | ||
1040 | 432 | |||
1041 | 433 | { | ||
1042 | 434 | MirWindow win{connection.get(), *client_comm_channel, nullptr, | ||
1043 | 435 | mock_stream, mock_input_platform, spec, surface_proto, wh}; | ||
1044 | 436 | EXPECT_EQ(&win, adopted_by); | ||
1045 | 437 | EXPECT_EQ(nullptr, unadopted_by); | ||
1046 | 438 | } | ||
1047 | 439 | |||
1048 | 440 | EXPECT_NE(nullptr, unadopted_by); | ||
1049 | 441 | EXPECT_EQ(adopted_by, unadopted_by); | ||
1050 | 442 | } | ||
1051 | 443 | |||
1052 | 444 | TEST_F(MirClientSurfaceTest, adopts_custom_streams_if_set) | ||
1053 | 445 | { | ||
1054 | 446 | using namespace testing; | ||
1055 | 447 | |||
1056 | 448 | auto mock_input_platform = std::make_shared<MockClientInputPlatform>(); | ||
1057 | 449 | |||
1058 | 450 | mir::frontend::BufferStreamId const mock_old_stream_id(11); | ||
1059 | 451 | auto mock_old_stream = std::make_shared<mtd::MockMirBufferStream>(); | ||
1060 | 452 | MirWindow* old_adopted_by = nullptr; | ||
1061 | 453 | MirWindow* old_unadopted_by = nullptr; | ||
1062 | 454 | EXPECT_CALL(*mock_old_stream, adopted_by(_)) | ||
1063 | 455 | .WillOnce(SaveArg<0>(&old_adopted_by)); | ||
1064 | 456 | EXPECT_CALL(*mock_old_stream, unadopted_by(_)) | ||
1065 | 457 | .WillOnce(SaveArg<0>(&old_unadopted_by)); | ||
1066 | 458 | ON_CALL(*mock_old_stream, rpc_id()) | ||
1067 | 459 | .WillByDefault(Return(mock_old_stream_id)); | ||
1068 | 460 | |||
1069 | 461 | mir::frontend::BufferStreamId const mock_new_stream_id(22); | ||
1070 | 462 | auto mock_new_stream = std::make_shared<mtd::MockMirBufferStream>(); | ||
1071 | 463 | MirWindow* new_adopted_by = nullptr; | ||
1072 | 464 | MirWindow* new_unadopted_by = nullptr; | ||
1073 | 465 | EXPECT_CALL(*mock_new_stream, adopted_by(_)) | ||
1074 | 466 | .WillOnce(SaveArg<0>(&new_adopted_by)); | ||
1075 | 467 | EXPECT_CALL(*mock_new_stream, unadopted_by(_)) | ||
1076 | 468 | .WillOnce(SaveArg<0>(&new_unadopted_by)); | ||
1077 | 469 | ON_CALL(*mock_new_stream, rpc_id()) | ||
1078 | 470 | .WillByDefault(Return(mock_new_stream_id)); | ||
1079 | 471 | |||
1080 | 472 | surface_map->insert(mock_old_stream_id, mock_old_stream); | ||
1081 | 473 | surface_map->insert(mock_new_stream_id, mock_new_stream); | ||
1082 | 474 | { | ||
1083 | 475 | MirWindow win{connection.get(), *client_comm_channel, nullptr, | ||
1084 | 476 | mock_old_stream, mock_input_platform, spec, surface_proto, wh}; | ||
1085 | 477 | |||
1086 | 478 | EXPECT_EQ(&win, old_adopted_by); | ||
1087 | 479 | EXPECT_EQ(nullptr, old_unadopted_by); | ||
1088 | 480 | EXPECT_EQ(nullptr, new_adopted_by); | ||
1089 | 481 | EXPECT_EQ(nullptr, new_unadopted_by); | ||
1090 | 482 | |||
1091 | 483 | MirWindowSpec spec; | ||
1092 | 484 | std::vector<ContentInfo> replacements | ||
1093 | 485 | { | ||
1094 | 486 | {geom::Displacement{0,0}, mock_new_stream_id.as_value(), geom::Size{1,1}} | ||
1095 | 487 | }; | ||
1096 | 488 | spec.streams = replacements; | ||
1097 | 489 | win.modify(spec)->wait_for_all(); | ||
1098 | 490 | |||
1099 | 491 | EXPECT_EQ(&win, old_unadopted_by); | ||
1100 | 492 | EXPECT_EQ(&win, new_adopted_by); | ||
1101 | 493 | EXPECT_EQ(nullptr, new_unadopted_by); | ||
1102 | 494 | } | ||
1103 | 495 | EXPECT_EQ(new_adopted_by, new_unadopted_by); | ||
1104 | 496 | surface_map->erase(mock_old_stream_id); | ||
1105 | 497 | surface_map->erase(mock_new_stream_id); | ||
1106 | 498 | } | ||
1107 | 499 | |||
1108 | 419 | TEST_F(MirClientSurfaceTest, replacing_delegate_with_nullptr_prevents_further_dispatch) | 500 | TEST_F(MirClientSurfaceTest, replacing_delegate_with_nullptr_prevents_further_dispatch) |
1109 | 420 | { | 501 | { |
1110 | 421 | using namespace ::testing; | 502 | using namespace ::testing; |
1111 | 422 | 503 | ||
1112 | === added file 'tests/unit-tests/client/test_frame_clock.cpp' | |||
1113 | --- tests/unit-tests/client/test_frame_clock.cpp 1970-01-01 00:00:00 +0000 | |||
1114 | +++ tests/unit-tests/client/test_frame_clock.cpp 2017-01-17 06:23:26 +0000 | |||
1115 | @@ -0,0 +1,420 @@ | |||
1116 | 1 | /* | ||
1117 | 2 | * Copyright © 2016 Canonical Ltd. | ||
1118 | 3 | * | ||
1119 | 4 | * This program is free software: you can redistribute it and/or modify | ||
1120 | 5 | * it under the terms of the GNU General Public License version 3 as | ||
1121 | 6 | * published by the Free Software Foundation. | ||
1122 | 7 | * | ||
1123 | 8 | * This program is distributed in the hope that it will be useful, | ||
1124 | 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
1125 | 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
1126 | 11 | * GNU General Public License for more details. | ||
1127 | 12 | * | ||
1128 | 13 | * You should have received a copy of the GNU General Public License | ||
1129 | 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
1130 | 15 | * | ||
1131 | 16 | * Authored by: Daniel van Vugt <daniel.van.vugt@canonical.com> | ||
1132 | 17 | */ | ||
1133 | 18 | |||
1134 | 19 | #include "src/client/frame_clock.h" | ||
1135 | 20 | #include <gtest/gtest.h> | ||
1136 | 21 | #include <unordered_map> | ||
1137 | 22 | |||
1138 | 23 | using namespace ::testing; | ||
1139 | 24 | using namespace ::std::chrono_literals; | ||
1140 | 25 | using mir::time::PosixTimestamp; | ||
1141 | 26 | using mir::client::FrameClock; | ||
1142 | 27 | |||
1143 | 28 | class FrameClockTest : public Test | ||
1144 | 29 | { | ||
1145 | 30 | public: | ||
1146 | 31 | FrameClockTest() | ||
1147 | 32 | { | ||
1148 | 33 | with_fake_time = [this](clockid_t clk){ return fake_time[clk]; }; | ||
1149 | 34 | } | ||
1150 | 35 | |||
1151 | 36 | void SetUp() | ||
1152 | 37 | { | ||
1153 | 38 | for (auto& c : {CLOCK_MONOTONIC, CLOCK_REALTIME, | ||
1154 | 39 | CLOCK_MONOTONIC_RAW, CLOCK_REALTIME_COARSE, | ||
1155 | 40 | CLOCK_MONOTONIC_COARSE, CLOCK_BOOTTIME}) | ||
1156 | 41 | fake_time[c] = PosixTimestamp(c, c * c * 1234567ns); | ||
1157 | 42 | int const hz = 60; | ||
1158 | 43 | one_frame = std::chrono::nanoseconds(1000000000L/hz); | ||
1159 | 44 | } | ||
1160 | 45 | |||
1161 | 46 | void fake_sleep_for(std::chrono::nanoseconds ns) | ||
1162 | 47 | { | ||
1163 | 48 | for (auto& f : fake_time) | ||
1164 | 49 | f.second.nanoseconds += ns; | ||
1165 | 50 | } | ||
1166 | 51 | |||
1167 | 52 | void fake_sleep_until(PosixTimestamp t) | ||
1168 | 53 | { | ||
1169 | 54 | auto& now = fake_time[t.clock_id]; | ||
1170 | 55 | if (t > now) | ||
1171 | 56 | { | ||
1172 | 57 | auto delta = t.nanoseconds - now.nanoseconds; | ||
1173 | 58 | fake_sleep_for(delta); | ||
1174 | 59 | } | ||
1175 | 60 | } | ||
1176 | 61 | |||
1177 | 62 | protected: | ||
1178 | 63 | FrameClock::GetCurrentTime with_fake_time; | ||
1179 | 64 | std::unordered_map<clockid_t,PosixTimestamp> fake_time; | ||
1180 | 65 | std::chrono::nanoseconds one_frame; | ||
1181 | 66 | }; | ||
1182 | 67 | |||
1183 | 68 | TEST_F(FrameClockTest, unthrottled_without_a_period) | ||
1184 | 69 | { | ||
1185 | 70 | FrameClock clock(with_fake_time); | ||
1186 | 71 | |||
1187 | 72 | PosixTimestamp a; | ||
1188 | 73 | auto b = clock.next_frame_after(a); | ||
1189 | 74 | EXPECT_EQ(a, b); | ||
1190 | 75 | auto c = clock.next_frame_after(b); | ||
1191 | 76 | EXPECT_EQ(b, c); | ||
1192 | 77 | } | ||
1193 | 78 | |||
1194 | 79 | TEST_F(FrameClockTest, first_frame_is_within_one_frame) | ||
1195 | 80 | { | ||
1196 | 81 | auto& now = fake_time[CLOCK_MONOTONIC]; | ||
1197 | 82 | auto a = now; | ||
1198 | 83 | FrameClock clock(with_fake_time); | ||
1199 | 84 | clock.set_period(one_frame); | ||
1200 | 85 | |||
1201 | 86 | PosixTimestamp b; | ||
1202 | 87 | auto c = clock.next_frame_after(b); | ||
1203 | 88 | EXPECT_GE(c, a); | ||
1204 | 89 | EXPECT_LE(c-a, one_frame); | ||
1205 | 90 | } | ||
1206 | 91 | |||
1207 | 92 | TEST_F(FrameClockTest, interval_is_perfectly_smooth) | ||
1208 | 93 | { | ||
1209 | 94 | FrameClock clock(with_fake_time); | ||
1210 | 95 | clock.set_period(one_frame); | ||
1211 | 96 | |||
1212 | 97 | PosixTimestamp a; | ||
1213 | 98 | auto b = clock.next_frame_after(a); | ||
1214 | 99 | |||
1215 | 100 | fake_sleep_until(b); | ||
1216 | 101 | fake_sleep_for(one_frame/13); // short render time | ||
1217 | 102 | |||
1218 | 103 | auto c = clock.next_frame_after(b); | ||
1219 | 104 | EXPECT_EQ(one_frame, c - b); | ||
1220 | 105 | |||
1221 | 106 | fake_sleep_until(c); | ||
1222 | 107 | fake_sleep_for(one_frame/7); // short render time | ||
1223 | 108 | |||
1224 | 109 | auto d = clock.next_frame_after(c); | ||
1225 | 110 | EXPECT_EQ(one_frame, d - c); | ||
1226 | 111 | |||
1227 | 112 | fake_sleep_until(d); | ||
1228 | 113 | fake_sleep_for(one_frame/5); // short render time | ||
1229 | 114 | |||
1230 | 115 | auto e = clock.next_frame_after(d); | ||
1231 | 116 | EXPECT_EQ(one_frame, e - d); | ||
1232 | 117 | } | ||
1233 | 118 | |||
1234 | 119 | TEST_F(FrameClockTest, long_render_time_is_recoverable_without_decimation) | ||
1235 | 120 | { | ||
1236 | 121 | FrameClock clock(with_fake_time); | ||
1237 | 122 | clock.set_period(one_frame); | ||
1238 | 123 | |||
1239 | 124 | auto& now = fake_time[CLOCK_MONOTONIC]; | ||
1240 | 125 | auto a = now; | ||
1241 | 126 | auto b = clock.next_frame_after(a); | ||
1242 | 127 | |||
1243 | 128 | fake_sleep_until(b); | ||
1244 | 129 | fake_sleep_for(one_frame * 5 / 4); // long render time; over a frame | ||
1245 | 130 | |||
1246 | 131 | auto c = clock.next_frame_after(b); | ||
1247 | 132 | EXPECT_EQ(one_frame, c - b); | ||
1248 | 133 | |||
1249 | 134 | fake_sleep_until(c); | ||
1250 | 135 | fake_sleep_for(one_frame * 7 / 6); // long render time; over a frame | ||
1251 | 136 | |||
1252 | 137 | auto d = clock.next_frame_after(c); | ||
1253 | 138 | EXPECT_EQ(one_frame, d - c); | ||
1254 | 139 | |||
1255 | 140 | EXPECT_LT(d, now); | ||
1256 | 141 | |||
1257 | 142 | fake_sleep_until(d); | ||
1258 | 143 | fake_sleep_for(one_frame/4); // short render time | ||
1259 | 144 | |||
1260 | 145 | // We can recover since we had a short render time... | ||
1261 | 146 | auto e = clock.next_frame_after(d); | ||
1262 | 147 | EXPECT_EQ(one_frame, e - d); | ||
1263 | 148 | } | ||
1264 | 149 | |||
1265 | 150 | TEST_F(FrameClockTest, resuming_from_sleep_targets_the_future) | ||
1266 | 151 | { | ||
1267 | 152 | FrameClock clock(with_fake_time); | ||
1268 | 153 | clock.set_period(one_frame); | ||
1269 | 154 | |||
1270 | 155 | auto& now = fake_time[CLOCK_MONOTONIC]; | ||
1271 | 156 | PosixTimestamp a = now; | ||
1272 | 157 | auto b = clock.next_frame_after(a); | ||
1273 | 158 | fake_sleep_until(b); | ||
1274 | 159 | auto c = clock.next_frame_after(b); | ||
1275 | 160 | EXPECT_EQ(one_frame, c - b); | ||
1276 | 161 | fake_sleep_until(c); | ||
1277 | 162 | |||
1278 | 163 | // Client idles for a while without producing new frames: | ||
1279 | 164 | fake_sleep_for(567 * one_frame); | ||
1280 | 165 | |||
1281 | 166 | auto d = clock.next_frame_after(c); | ||
1282 | 167 | EXPECT_GT(d, now); // Resumption must be in the future | ||
1283 | 168 | EXPECT_LE(d, now+one_frame); // But not too far in the future | ||
1284 | 169 | } | ||
1285 | 170 | |||
1286 | 171 | TEST_F(FrameClockTest, multiple_streams_in_sync) | ||
1287 | 172 | { | ||
1288 | 173 | FrameClock clock(with_fake_time); | ||
1289 | 174 | clock.set_period(one_frame); | ||
1290 | 175 | |||
1291 | 176 | PosixTimestamp left; | ||
1292 | 177 | left = clock.next_frame_after(left); | ||
1293 | 178 | |||
1294 | 179 | fake_sleep_for(one_frame / 9); | ||
1295 | 180 | |||
1296 | 181 | PosixTimestamp right; | ||
1297 | 182 | right = clock.next_frame_after(right); | ||
1298 | 183 | |||
1299 | 184 | ASSERT_EQ(left, right); | ||
1300 | 185 | fake_sleep_until(left); | ||
1301 | 186 | fake_sleep_until(right); | ||
1302 | 187 | |||
1303 | 188 | left = clock.next_frame_after(left); | ||
1304 | 189 | fake_sleep_for(one_frame / 5); | ||
1305 | 190 | right = clock.next_frame_after(right); | ||
1306 | 191 | |||
1307 | 192 | ASSERT_EQ(left, right); | ||
1308 | 193 | fake_sleep_until(left); | ||
1309 | 194 | fake_sleep_until(right); | ||
1310 | 195 | |||
1311 | 196 | left = clock.next_frame_after(left); | ||
1312 | 197 | fake_sleep_for(one_frame / 7); | ||
1313 | 198 | right = clock.next_frame_after(right); | ||
1314 | 199 | |||
1315 | 200 | ASSERT_EQ(left, right); | ||
1316 | 201 | } | ||
1317 | 202 | |||
1318 | 203 | TEST_F(FrameClockTest, moving_between_displays_adapts_to_new_rate) | ||
1319 | 204 | { | ||
1320 | 205 | FrameClock clock(with_fake_time); | ||
1321 | 206 | clock.set_period(one_frame); | ||
1322 | 207 | |||
1323 | 208 | auto const one_tv_frame = std::chrono::nanoseconds(1000000000L / 25); | ||
1324 | 209 | ASSERT_NE(one_frame, one_tv_frame); | ||
1325 | 210 | |||
1326 | 211 | PosixTimestamp a; | ||
1327 | 212 | auto b = clock.next_frame_after(a); | ||
1328 | 213 | fake_sleep_until(b); | ||
1329 | 214 | auto c = clock.next_frame_after(b); | ||
1330 | 215 | EXPECT_EQ(one_frame, c - b); | ||
1331 | 216 | |||
1332 | 217 | fake_sleep_until(c); | ||
1333 | 218 | |||
1334 | 219 | // Window moves to a new slower display: | ||
1335 | 220 | clock.set_period(one_tv_frame); | ||
1336 | 221 | auto d = clock.next_frame_after(c); | ||
1337 | 222 | // Clock keeps ticking into the future for the new display: | ||
1338 | 223 | EXPECT_GT(d, c); | ||
1339 | 224 | // But not too far in the future: | ||
1340 | 225 | EXPECT_LE(d, c + one_tv_frame); | ||
1341 | 226 | |||
1342 | 227 | // Vsync is now at the slower rate of the TV: | ||
1343 | 228 | fake_sleep_until(d); | ||
1344 | 229 | auto e = clock.next_frame_after(d); | ||
1345 | 230 | EXPECT_EQ(one_tv_frame, e - d); | ||
1346 | 231 | fake_sleep_until(e); | ||
1347 | 232 | auto f = clock.next_frame_after(e); | ||
1348 | 233 | EXPECT_EQ(one_tv_frame, f - e); | ||
1349 | 234 | |||
1350 | 235 | fake_sleep_until(f); | ||
1351 | 236 | |||
1352 | 237 | // Window moves back to the faster display: | ||
1353 | 238 | clock.set_period(one_frame); | ||
1354 | 239 | auto g = clock.next_frame_after(f); | ||
1355 | 240 | // Clock keeps ticking into the future for the new display: | ||
1356 | 241 | EXPECT_GT(g, f); | ||
1357 | 242 | // But not too far in the future: | ||
1358 | 243 | EXPECT_LE(g, f + one_tv_frame); | ||
1359 | 244 | |||
1360 | 245 | // Vsync is now at the faster rate again: | ||
1361 | 246 | fake_sleep_until(g); | ||
1362 | 247 | auto h = clock.next_frame_after(g); | ||
1363 | 248 | EXPECT_EQ(one_frame, h - g); | ||
1364 | 249 | fake_sleep_until(e); | ||
1365 | 250 | auto i = clock.next_frame_after(h); | ||
1366 | 251 | EXPECT_EQ(one_frame, i - h); | ||
1367 | 252 | } | ||
1368 | 253 | |||
1369 | 254 | TEST_F(FrameClockTest, resuming_comes_in_phase_with_server_vsync) | ||
1370 | 255 | { | ||
1371 | 256 | FrameClock clock(with_fake_time); | ||
1372 | 257 | clock.set_period(one_frame); | ||
1373 | 258 | |||
1374 | 259 | auto& now = fake_time[CLOCK_MONOTONIC]; | ||
1375 | 260 | PosixTimestamp a = now; | ||
1376 | 261 | auto b = clock.next_frame_after(a); | ||
1377 | 262 | fake_sleep_until(b); | ||
1378 | 263 | auto c = clock.next_frame_after(b); | ||
1379 | 264 | EXPECT_EQ(one_frame, c - b); | ||
1380 | 265 | fake_sleep_until(c); | ||
1381 | 266 | |||
1382 | 267 | // Client idles for a while without producing new frames: | ||
1383 | 268 | fake_sleep_for(789 * one_frame); | ||
1384 | 269 | |||
1385 | 270 | auto last_server_frame = now - 556677ns; | ||
1386 | 271 | clock.set_resync_callback([last_server_frame](){return last_server_frame;}); | ||
1387 | 272 | |||
1388 | 273 | auto d = clock.next_frame_after(c); | ||
1389 | 274 | EXPECT_GT(d, now); // Resumption must be in the future | ||
1390 | 275 | EXPECT_LE(d, now+one_frame); // But not too far in the future | ||
1391 | 276 | |||
1392 | 277 | auto server_phase = last_server_frame % one_frame; | ||
1393 | 278 | EXPECT_NE(server_phase, c % one_frame); // wasn't in phase before | ||
1394 | 279 | EXPECT_EQ(server_phase, d % one_frame); // but is in phase now | ||
1395 | 280 | |||
1396 | 281 | // Not only did we come in phase but we're targeting the soonest frame | ||
1397 | 282 | EXPECT_EQ(last_server_frame+one_frame, d); | ||
1398 | 283 | } | ||
1399 | 284 | |||
1400 | 285 | TEST_F(FrameClockTest, switches_to_the_server_clock_on_startup) | ||
1401 | 286 | { | ||
1402 | 287 | FrameClock clock(with_fake_time); | ||
1403 | 288 | clock.set_period(one_frame); | ||
1404 | 289 | |||
1405 | 290 | PosixTimestamp a(CLOCK_MONOTONIC_COARSE, 0ns); | ||
1406 | 291 | |||
1407 | 292 | PosixTimestamp b; | ||
1408 | 293 | EXPECT_NO_THROW({ | ||
1409 | 294 | b = clock.next_frame_after(a); | ||
1410 | 295 | }); | ||
1411 | 296 | |||
1412 | 297 | // The default resync callback uses CLOCK_MONOTONIC... | ||
1413 | 298 | EXPECT_NE(a.clock_id, b.clock_id); | ||
1414 | 299 | } | ||
1415 | 300 | |||
1416 | 301 | TEST_F(FrameClockTest, can_migrate_between_different_driver_clocks) | ||
1417 | 302 | { | ||
1418 | 303 | FrameClock clock(with_fake_time); | ||
1419 | 304 | clock.set_period(one_frame); | ||
1420 | 305 | |||
1421 | 306 | PosixTimestamp a = fake_time[CLOCK_MONOTONIC]; | ||
1422 | 307 | auto b = clock.next_frame_after(a); | ||
1423 | 308 | fake_sleep_until(b); | ||
1424 | 309 | auto c = clock.next_frame_after(b); | ||
1425 | 310 | EXPECT_EQ(one_frame, c - b); | ||
1426 | 311 | |||
1427 | 312 | fake_sleep_until(c); | ||
1428 | 313 | |||
1429 | 314 | // Window moves between displays and in the glorious future that might even | ||
1430 | 315 | // mean it switches cards, with different drivers, different clocks... | ||
1431 | 316 | auto last_server_frame = fake_time[CLOCK_MONOTONIC_COARSE] - 556677ns; | ||
1432 | 317 | clock.set_resync_callback([last_server_frame](){return last_server_frame;}); | ||
1433 | 318 | |||
1434 | 319 | EXPECT_EQ(CLOCK_MONOTONIC, c.clock_id); | ||
1435 | 320 | auto d = clock.next_frame_after(c); | ||
1436 | 321 | EXPECT_EQ(CLOCK_MONOTONIC_COARSE, d.clock_id); | ||
1437 | 322 | |||
1438 | 323 | EXPECT_GT(d, fake_time[CLOCK_MONOTONIC_COARSE]); | ||
1439 | 324 | EXPECT_LE(d, fake_time[CLOCK_MONOTONIC_COARSE]+one_frame); | ||
1440 | 325 | |||
1441 | 326 | auto server_phase = last_server_frame % one_frame; | ||
1442 | 327 | EXPECT_NE(server_phase, c % one_frame); // wasn't in phase before | ||
1443 | 328 | EXPECT_EQ(server_phase, d % one_frame); // but is in phase now | ||
1444 | 329 | |||
1445 | 330 | // Not only did we come in phase but we're targeting the soonest frame | ||
1446 | 331 | EXPECT_EQ(last_server_frame+one_frame, d); | ||
1447 | 332 | } | ||
1448 | 333 | |||
1449 | 334 | TEST_F(FrameClockTest, does_not_resync_on_most_frames) | ||
1450 | 335 | { | ||
1451 | 336 | int callbacks = 0; | ||
1452 | 337 | |||
1453 | 338 | FrameClock clock(with_fake_time); | ||
1454 | 339 | clock.set_period(one_frame); | ||
1455 | 340 | clock.set_resync_callback([&callbacks] | ||
1456 | 341 | { | ||
1457 | 342 | ++callbacks; | ||
1458 | 343 | return PosixTimestamp(); | ||
1459 | 344 | }); | ||
1460 | 345 | |||
1461 | 346 | PosixTimestamp v; | ||
1462 | 347 | v = clock.next_frame_after(v); | ||
1463 | 348 | EXPECT_EQ(1, callbacks); // sync with server happens on first frame | ||
1464 | 349 | |||
1465 | 350 | fake_sleep_until(v); | ||
1466 | 351 | |||
1467 | 352 | v = clock.next_frame_after(v); | ||
1468 | 353 | EXPECT_EQ(1, callbacks); | ||
1469 | 354 | |||
1470 | 355 | fake_sleep_until(v); | ||
1471 | 356 | |||
1472 | 357 | v = clock.next_frame_after(v); | ||
1473 | 358 | EXPECT_EQ(1, callbacks); | ||
1474 | 359 | |||
1475 | 360 | fake_sleep_until(v); | ||
1476 | 361 | |||
1477 | 362 | v = clock.next_frame_after(v); | ||
1478 | 363 | EXPECT_EQ(1, callbacks); | ||
1479 | 364 | |||
1480 | 365 | fake_sleep_for(one_frame * 10); | ||
1481 | 366 | |||
1482 | 367 | clock.next_frame_after(v); | ||
1483 | 368 | EXPECT_EQ(2, callbacks); // resync because we went idle too long | ||
1484 | 369 | } | ||
1485 | 370 | |||
1486 | 371 | TEST_F(FrameClockTest, one_frame_skipped_only_after_2_frames_take_3_periods) | ||
1487 | 372 | { | ||
1488 | 373 | FrameClock clock(with_fake_time); | ||
1489 | 374 | clock.set_period(one_frame); | ||
1490 | 375 | |||
1491 | 376 | PosixTimestamp a; | ||
1492 | 377 | auto b = clock.next_frame_after(a); | ||
1493 | 378 | |||
1494 | 379 | fake_sleep_until(b); | ||
1495 | 380 | fake_sleep_for(one_frame * 3 / 2); // Render time: 1.5 frames | ||
1496 | 381 | |||
1497 | 382 | auto c = clock.next_frame_after(b); | ||
1498 | 383 | EXPECT_EQ(one_frame, c - b); // No frame skipped | ||
1499 | 384 | |||
1500 | 385 | fake_sleep_until(c); | ||
1501 | 386 | fake_sleep_for(one_frame * 8 / 5); // Render time: 1.6 frames | ||
1502 | 387 | |||
1503 | 388 | auto d = clock.next_frame_after(c); | ||
1504 | 389 | EXPECT_EQ(2*one_frame, d - c); // One frame skipped | ||
1505 | 390 | |||
1506 | 391 | fake_sleep_until(d); | ||
1507 | 392 | fake_sleep_for(one_frame/4); // Short render time, immediate recovery | ||
1508 | 393 | |||
1509 | 394 | auto e = clock.next_frame_after(d); | ||
1510 | 395 | EXPECT_EQ(one_frame, e - d); // No frame skipped. We have recovered. | ||
1511 | 396 | } | ||
1512 | 397 | |||
1513 | 398 | TEST_F(FrameClockTest, nesting_adds_zero_lag) | ||
1514 | 399 | { | ||
1515 | 400 | FrameClock inner(with_fake_time); | ||
1516 | 401 | FrameClock outer(with_fake_time); | ||
1517 | 402 | inner.set_period(one_frame); | ||
1518 | 403 | outer.set_period(one_frame); | ||
1519 | 404 | |||
1520 | 405 | PosixTimestamp in0, out0; | ||
1521 | 406 | auto in1 = inner.next_frame_after(in0); | ||
1522 | 407 | fake_sleep_for(1234567ns); // scheduling and IPC delay | ||
1523 | 408 | auto out1 = outer.next_frame_after(out0); | ||
1524 | 409 | EXPECT_EQ(in1, out1); | ||
1525 | 410 | |||
1526 | 411 | fake_sleep_until(in1); | ||
1527 | 412 | fake_sleep_until(out1); | ||
1528 | 413 | |||
1529 | 414 | auto in2 = inner.next_frame_after(in1); | ||
1530 | 415 | fake_sleep_for(8765432ns); // scheduling and IPC delay | ||
1531 | 416 | auto out2 = outer.next_frame_after(out1); | ||
1532 | 417 | EXPECT_EQ(in2, out2); | ||
1533 | 418 | EXPECT_EQ(one_frame, in2 - in1); | ||
1534 | 419 | EXPECT_EQ(one_frame, out2 - out1); | ||
1535 | 420 | } | ||
1536 | 0 | 421 | ||
1537 | === modified file 'tests/unit-tests/platforms/android/server/test_hwc_device.cpp' | |||
1538 | --- tests/unit-tests/platforms/android/server/test_hwc_device.cpp 2016-12-09 02:54:31 +0000 | |||
1539 | +++ tests/unit-tests/platforms/android/server/test_hwc_device.cpp 2017-01-17 06:23:26 +0000 | |||
1540 | @@ -601,6 +601,7 @@ | |||
1541 | 601 | 601 | ||
1542 | 602 | //LP: #1369763. We could get swapinterval 0 to work with overlays, but we'd need | 602 | //LP: #1369763. We could get swapinterval 0 to work with overlays, but we'd need |
1543 | 603 | //the vsync signal from hwc to reach the client, so the client can rate-limit its submissions. | 603 | //the vsync signal from hwc to reach the client, so the client can rate-limit its submissions. |
1544 | 604 | // Update: We have client-side vsync now so it should be possible to do this... | ||
1545 | 604 | TEST_F(HwcDevice, rejects_list_containing_interval_0) | 605 | TEST_F(HwcDevice, rejects_list_containing_interval_0) |
1546 | 605 | { | 606 | { |
1547 | 606 | mga::HwcDevice device(mock_device); | 607 | mga::HwcDevice device(mock_device); |
PASSED: Continuous integration, rev:3911 /mir-jenkins. ubuntu. com/job/ mir-ci/ 2578/ /mir-jenkins. ubuntu. com/job/ build-mir/ 3358 /mir-jenkins. ubuntu. com/job/ build-0- fetch/3425 /mir-jenkins. ubuntu. com/job/ build-1- sourcepkg/ release= vivid+overlay/ 3417 /mir-jenkins. ubuntu. com/job/ build-1- sourcepkg/ release= xenial+ overlay/ 3417 /mir-jenkins. ubuntu. com/job/ build-1- sourcepkg/ release= yakkety/ 3417 /mir-jenkins. ubuntu. com/job/ build-2- binpkg- mir/arch= amd64,compiler= clang,platform= mesa,release= yakkety/ 3387 /mir-jenkins. ubuntu. com/job/ build-2- binpkg- mir/arch= amd64,compiler= clang,platform= mesa,release= yakkety/ 3387/artifact/ output/ *zip*/output. zip /mir-jenkins. ubuntu. com/job/ build-2- binpkg- mir/arch= amd64,compiler= gcc,platform= mesa,release= xenial+ overlay/ 3387 /mir-jenkins. ubuntu. com/job/ build-2- binpkg- mir/arch= amd64,compiler= gcc,platform= mesa,release= xenial+ overlay/ 3387/artifact/ output/ *zip*/output. zip /mir-jenkins. ubuntu. com/job/ build-2- binpkg- mir/arch= amd64,compiler= gcc,platform= mesa,release= yakkety/ 3387 /mir-jenkins. ubuntu. com/job/ build-2- binpkg- mir/arch= amd64,compiler= gcc,platform= mesa,release= yakkety/ 3387/artifact/ output/ *zip*/output. zip /mir-jenkins. ubuntu. com/job/ build-2- binpkg- mir/arch= cross-armhf, compiler= gcc,platform= android, release= vivid+overlay/ 3387 /mir-jenkins. ubuntu. com/job/ build-2- binpkg- mir/arch= cross-armhf, compiler= gcc,platform= android, release= vivid+overlay/ 3387/artifact/ output/ *zip*/output. zip /mir-jenkins. ubuntu. com/job/ build-2- binpkg- mir/arch= i386,compiler= gcc,platform= android, release= vivid+overlay/ 3387 /mir-jenkins. ubuntu. com/job/ build-2- binpkg- mir/arch= i386,compiler= gcc,platform= android, release= vivid+overlay/ 3387/artifact/ output/ *zip*/output. zip /mir-jenkins. ubuntu. com/job/ build-2- binpkg- mir/arch= i386,compiler= gcc,platform= mesa,release= xenial+ overlay/ 3387 /mir-jenkins. ubuntu. com/job/ build-2- binpkg- mir/arch= i386,compiler= gcc,platform= mesa,release= xenial+ overlay/ 3387/artifact/ output/ *zip*/output. zip
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild: /mir-jenkins. ubuntu. com/job/ mir-ci/ 2578/rebuild
https:/