Merge lp:~vanvugt/mir/predictive-bypass-v3 into lp:mir
- predictive-bypass-v3
- Merge into development-branch
Status: | Merged |
---|---|
Approved by: | Daniel van Vugt |
Approved revision: | no longer in the source branch. |
Merged at revision: | 2719 |
Proposed branch: | lp:~vanvugt/mir/predictive-bypass-v3 |
Merge into: | lp:mir |
Diff against target: |
1162 lines (+404/-23) 38 files modified
benchmarks/frame-uniformity/vsync_simulating_graphics_platform.cpp (+5/-0) include/platform/mir/graphics/display.h (+11/-0) include/test/mir/test/doubles/null_display_sync_group.h (+11/-0) src/include/platform/mir/options/configuration.h (+1/-0) src/platform/options/default_configuration.cpp (+6/-0) src/platform/symbols.map (+10/-0) src/platforms/android/server/display_device.h (+3/-0) src/platforms/android/server/display_group.cpp (+5/-0) src/platforms/android/server/display_group.h (+1/-0) src/platforms/android/server/fb_device.cpp (+5/-0) src/platforms/android/server/fb_device.h (+1/-0) src/platforms/android/server/hwc_device.cpp (+19/-0) src/platforms/android/server/hwc_device.h (+2/-0) src/platforms/android/server/hwc_fb_device.cpp (+5/-0) src/platforms/android/server/hwc_fb_device.h (+1/-0) src/platforms/mesa/server/kms/display_buffer.cpp (+33/-0) src/platforms/mesa/server/kms/display_buffer.h (+2/-0) src/platforms/mesa/server/kms/kms_output.h (+8/-0) src/platforms/mesa/server/kms/real_kms_output.cpp (+8/-0) src/platforms/mesa/server/kms/real_kms_output.h (+1/-0) src/server/compositor/default_configuration.cpp (+4/-0) src/server/compositor/multi_threaded_compositor.cpp (+18/-1) src/server/compositor/multi_threaded_compositor.h (+2/-0) src/server/graphics/nested/display.cpp (+8/-0) src/server/graphics/nested/display.h (+1/-0) src/server/graphics/offscreen/display.cpp (+6/-0) src/server/graphics/offscreen/display.h (+1/-0) tests/include/mir/test/doubles/mock_display_device.h (+1/-0) tests/integration-tests/test_surface_stack_with_compositor.cpp (+12/-9) tests/mir_test_doubles/mock_drm.cpp (+5/-0) tests/unit-tests/compositor/test_multi_threaded_compositor.cpp (+71/-13) tests/unit-tests/graphics/android/test_fb_device.cpp (+3/-0) tests/unit-tests/graphics/android/test_hwc_device.cpp (+43/-0) tests/unit-tests/graphics/android/test_hwc_fb_device.cpp (+3/-0) tests/unit-tests/graphics/mesa/kms/mock_kms_output.h (+1/-0) tests/unit-tests/graphics/mesa/kms/test_display_buffer.cpp (+54/-0) tests/unit-tests/graphics/nested/test_nested_display.cpp (+17/-0) tests/unit-tests/graphics/offscreen/test_offscreen_display.cpp (+16/-0) |
To merge this branch: | bzr merge lp:~vanvugt/mir/predictive-bypass-v3 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot (community) | continuous-integration | Needs Fixing | |
Daniel van Vugt | Approve | ||
Chris Halse Rogers | Approve | ||
Kevin DuBois (community) | Approve | ||
Review via email:
|
Commit message
Introducing "predictive bypass"; this provides a constant ~10ms reduction
in latency when fully bypassed/overlayed. This benefit is in addition to
any lag reductions provided by other branches.
Additional unexpected benefits (free!):
* In some cases even smoothness/frame rate is improved by this branch
(LP: #1447896).
* Software cursors/touchspots appear to "stick" to the client app better
with this branch. Because the underlying client surface has the
additional time it needs to update for the new cursor/touch position
before the frame is posted.
Description of the change
This is version 3. Based on changes requested/suggested from version 2:
https:/
The sleep is still only allowed during bypass/overlaying so that time is never taken away from GL rendering. But you can force a specific sleep duration always-on using --composite-
which is equivalent to what Weston does:
https:/
Conversely, to turn off predictive bypass: --composite-delay=0
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Kevin DuBois (kdub) wrote : | # |
my complaints from v1 and v2 look addressed, lgtm!
Further improvements might be:
1) We can detect the device name on the android platform (via mga::DeviceQuirks), so we could have device-specific times.
2) This is passive (the MTC asks the DisplayGroup how long to wait), but I wouldn't mind the display hardware telling the MTC its ready to composite. (and mtc gets final say whether it does composite or not). This would also make the test for MTC less timing dependendent.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Kevin DuBois (kdub) wrote : | # |
nit:
102 + (composite_
103 + "Compositor frame delay in milliseconds (how long to wait for new "
104 + "frames from clients before compositing). Higher values result in "
105 + "lower latency. Default: Automatically determined.")
Instead of "higher values result in lower latency", perhaps "a properly tuned value can result in lower latency"? The current phrasing makes it tempting to set to a very high value.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Chris Halse Rogers (raof) wrote : | # |
This seems good to me. Kevin's DisplayGroup driven composite idea seems good, too, but can be done later.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Daniel van Vugt (vanvugt) wrote : | # |
I'm intentionally steering away from that right now. Because it's only an ideal time to post the next frame. Priority must still be given to the compositor which knows whether there needs to be a next frame at all. If nothing in the scene (or the "view") is changing then we need to stay asleep, and ignore what the recommended delay to the next frame is.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Daniel van Vugt (vanvugt) : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Chris Halse Rogers (raof) wrote : | # |
Hmm. It seems that we've been unclear.
The active display wouldn't *drive* the MTC. It would just replace the "you should probably wait at least $X ms" data member with a "you should probably wait at least until I signal you" signal. The MTC can still choose to wait longer (if there has been no scene change) or less time if it wanted to.
-----Original Message-----
From: "Daniel van Vugt" <email address hidden>
Sent: 30/06/2015 16:49
To: "Daniel van Vugt" <email address hidden>
Subject: Re: [Merge] lp:~vanvugt/mir/predictive-bypass-v3 into lp:mir
I'm intentionally steering away from that right now. Because it's only an ideal time to post the next frame. Priority must still be given to the compositor which knows whether there needs to be a next frame at all. If nothing in the scene (or the "view") is changing then we need to stay asleep, and ignore what the recommended delay to the next frame is.
--
https:/
You are reviewing the proposed merge of lp:~vanvugt/mir/predictive-bypass-v3 into lp:mir.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:2690
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Chris Halse Rogers (raof) wrote : | # |
Did Jenkins just merge this after it failed CI?
--
Sent using Dekko from my Ubuntu device
Preview Diff
1 | === modified file 'benchmarks/frame-uniformity/vsync_simulating_graphics_platform.cpp' | |||
2 | --- benchmarks/frame-uniformity/vsync_simulating_graphics_platform.cpp 2015-06-25 03:00:08 +0000 | |||
3 | +++ benchmarks/frame-uniformity/vsync_simulating_graphics_platform.cpp 2015-06-30 06:52:15 +0000 | |||
4 | @@ -60,6 +60,11 @@ | |||
5 | 60 | 60 | ||
6 | 61 | last_sync = now; | 61 | last_sync = now; |
7 | 62 | } | 62 | } |
8 | 63 | |||
9 | 64 | std::chrono::milliseconds recommended_sleep() const override | ||
10 | 65 | { | ||
11 | 66 | return std::chrono::milliseconds::zero(); | ||
12 | 67 | } | ||
13 | 63 | 68 | ||
14 | 64 | double const vsync_rate_in_hz; | 69 | double const vsync_rate_in_hz; |
15 | 65 | 70 | ||
16 | 66 | 71 | ||
17 | === modified file 'include/platform/mir/graphics/display.h' | |||
18 | --- include/platform/mir/graphics/display.h 2015-06-17 05:20:42 +0000 | |||
19 | +++ include/platform/mir/graphics/display.h 2015-06-30 06:52:15 +0000 | |||
20 | @@ -21,6 +21,7 @@ | |||
21 | 21 | 21 | ||
22 | 22 | #include <memory> | 22 | #include <memory> |
23 | 23 | #include <functional> | 23 | #include <functional> |
24 | 24 | #include <chrono> | ||
25 | 24 | 25 | ||
26 | 25 | namespace mir | 26 | namespace mir |
27 | 26 | { | 27 | { |
28 | @@ -63,6 +64,16 @@ | |||
29 | 63 | * in the near future. On some platforms, this may wait a potentially long time for vsync. | 64 | * in the near future. On some platforms, this may wait a potentially long time for vsync. |
30 | 64 | **/ | 65 | **/ |
31 | 65 | virtual void post() = 0; | 66 | virtual void post() = 0; |
32 | 67 | |||
33 | 68 | /** | ||
34 | 69 | * Returns a recommendation to the compositor as to how long it should | ||
35 | 70 | * wait before sampling the scene for the next frame. Sampling the | ||
36 | 71 | * scene too early results in up to one whole frame of extra lag if | ||
37 | 72 | * rendering is fast or skipped altogether (bypass/overlays). But sampling | ||
38 | 73 | * too late and we might miss the deadline. If unsure just return zero. | ||
39 | 74 | */ | ||
40 | 75 | virtual std::chrono::milliseconds recommended_sleep() const = 0; | ||
41 | 76 | |||
42 | 66 | virtual ~DisplaySyncGroup() = default; | 77 | virtual ~DisplaySyncGroup() = default; |
43 | 67 | protected: | 78 | protected: |
44 | 68 | DisplaySyncGroup() = default; | 79 | DisplaySyncGroup() = default; |
45 | 69 | 80 | ||
46 | === modified file 'include/test/mir/test/doubles/null_display_sync_group.h' | |||
47 | --- include/test/mir/test/doubles/null_display_sync_group.h 2015-06-25 03:00:08 +0000 | |||
48 | +++ include/test/mir/test/doubles/null_display_sync_group.h 2015-06-30 06:52:15 +0000 | |||
49 | @@ -55,6 +55,11 @@ | |||
50 | 55 | std::this_thread::yield(); | 55 | std::this_thread::yield(); |
51 | 56 | } | 56 | } |
52 | 57 | 57 | ||
53 | 58 | std::chrono::milliseconds recommended_sleep() const override | ||
54 | 59 | { | ||
55 | 60 | return std::chrono::milliseconds::zero(); | ||
56 | 61 | } | ||
57 | 62 | |||
58 | 58 | private: | 63 | private: |
59 | 59 | std::vector<geometry::Rectangle> const output_rects; | 64 | std::vector<geometry::Rectangle> const output_rects; |
60 | 60 | std::vector<StubDisplayBuffer> display_buffers; | 65 | std::vector<StubDisplayBuffer> display_buffers; |
61 | @@ -71,6 +76,12 @@ | |||
62 | 71 | /* yield() is needed to ensure reasonable runtime under valgrind for some tests */ | 76 | /* yield() is needed to ensure reasonable runtime under valgrind for some tests */ |
63 | 72 | std::this_thread::yield(); | 77 | std::this_thread::yield(); |
64 | 73 | } | 78 | } |
65 | 79 | |||
66 | 80 | std::chrono::milliseconds recommended_sleep() const override | ||
67 | 81 | { | ||
68 | 82 | return std::chrono::milliseconds::zero(); | ||
69 | 83 | } | ||
70 | 84 | |||
71 | 74 | NullDisplayBuffer db; | 85 | NullDisplayBuffer db; |
72 | 75 | }; | 86 | }; |
73 | 76 | 87 | ||
74 | 77 | 88 | ||
75 | === modified file 'src/include/platform/mir/options/configuration.h' | |||
76 | --- src/include/platform/mir/options/configuration.h 2015-06-17 05:20:42 +0000 | |||
77 | +++ src/include/platform/mir/options/configuration.h 2015-06-30 06:52:15 +0000 | |||
78 | @@ -47,6 +47,7 @@ | |||
79 | 47 | extern char const* const fatal_abort_opt; | 47 | extern char const* const fatal_abort_opt; |
80 | 48 | extern char const* const debug_opt; | 48 | extern char const* const debug_opt; |
81 | 49 | extern char const* const nbuffers_opt; | 49 | extern char const* const nbuffers_opt; |
82 | 50 | extern char const* const composite_delay_opt; | ||
83 | 50 | extern char const* const enable_key_repeat_opt; | 51 | extern char const* const enable_key_repeat_opt; |
84 | 51 | 52 | ||
85 | 52 | extern char const* const name_opt; | 53 | extern char const* const name_opt; |
86 | 53 | 54 | ||
87 | === modified file 'src/platform/options/default_configuration.cpp' | |||
88 | --- src/platform/options/default_configuration.cpp 2015-06-17 05:20:42 +0000 | |||
89 | +++ src/platform/options/default_configuration.cpp 2015-06-30 06:52:15 +0000 | |||
90 | @@ -49,6 +49,7 @@ | |||
91 | 49 | char const* const mo::fatal_abort_opt = "on-fatal-error-abort"; | 49 | char const* const mo::fatal_abort_opt = "on-fatal-error-abort"; |
92 | 50 | char const* const mo::debug_opt = "debug"; | 50 | char const* const mo::debug_opt = "debug"; |
93 | 51 | char const* const mo::nbuffers_opt = "nbuffers"; | 51 | char const* const mo::nbuffers_opt = "nbuffers"; |
94 | 52 | char const* const mo::composite_delay_opt = "composite-delay"; | ||
95 | 52 | char const* const mo::enable_key_repeat_opt = "enable-key-repeat"; | 53 | char const* const mo::enable_key_repeat_opt = "enable-key-repeat"; |
96 | 53 | 54 | ||
97 | 54 | char const* const mo::off_opt_value = "off"; | 55 | char const* const mo::off_opt_value = "off"; |
98 | @@ -173,6 +174,11 @@ | |||
99 | 173 | "threads in frontend thread pool.") | 174 | "threads in frontend thread pool.") |
100 | 174 | (nbuffers_opt, po::value<int>()->default_value(3), | 175 | (nbuffers_opt, po::value<int>()->default_value(3), |
101 | 175 | "Number of buffers per surface.") | 176 | "Number of buffers per surface.") |
102 | 177 | (composite_delay_opt, po::value<int>()->default_value(-1), | ||
103 | 178 | "Compositor frame delay in milliseconds (how long to wait for new " | ||
104 | 179 | "frames from clients before compositing). Higher values result in " | ||
105 | 180 | "lower latency but risk causing frame skipping. " | ||
106 | 181 | "Default: A negative value means decide automatically.") | ||
107 | 176 | (name_opt, po::value<std::string>(), | 182 | (name_opt, po::value<std::string>(), |
108 | 177 | "When nested, the name Mir uses when registering with the host.") | 183 | "When nested, the name Mir uses when registering with the host.") |
109 | 178 | (offscreen_opt, | 184 | (offscreen_opt, |
110 | 179 | 185 | ||
111 | === modified file 'src/platform/symbols.map' | |||
112 | --- src/platform/symbols.map 2015-06-17 05:20:42 +0000 | |||
113 | +++ src/platform/symbols.map 2015-06-30 06:52:15 +0000 | |||
114 | @@ -276,3 +276,13 @@ | |||
115 | 276 | }; | 276 | }; |
116 | 277 | local: *; | 277 | local: *; |
117 | 278 | }; | 278 | }; |
118 | 279 | |||
119 | 280 | # Why are server-only options here in libmirplatform?... | ||
120 | 281 | MIRPLATFORM_7.1 { | ||
121 | 282 | global: | ||
122 | 283 | extern "C++" { | ||
123 | 284 | mir::options::composite_delay_opt*; | ||
124 | 285 | }; | ||
125 | 286 | local: *; | ||
126 | 287 | } MIRPLATFORM_7; | ||
127 | 288 | |||
128 | 279 | 289 | ||
129 | === modified file 'src/platforms/android/server/display_device.h' | |||
130 | --- src/platforms/android/server/display_device.h 2015-06-17 05:20:42 +0000 | |||
131 | +++ src/platforms/android/server/display_device.h 2015-06-30 06:52:15 +0000 | |||
132 | @@ -25,6 +25,7 @@ | |||
133 | 25 | #include "display_name.h" | 25 | #include "display_name.h" |
134 | 26 | #include <EGL/egl.h> | 26 | #include <EGL/egl.h> |
135 | 27 | #include <list> | 27 | #include <list> |
136 | 28 | #include <chrono> | ||
137 | 28 | 29 | ||
138 | 29 | namespace mir | 30 | namespace mir |
139 | 30 | { | 31 | { |
140 | @@ -66,6 +67,8 @@ | |||
141 | 66 | //notify the DisplayDevice that the screen content was cleared in a way other than the above fns | 67 | //notify the DisplayDevice that the screen content was cleared in a way other than the above fns |
142 | 67 | virtual void content_cleared() = 0; | 68 | virtual void content_cleared() = 0; |
143 | 68 | 69 | ||
144 | 70 | virtual std::chrono::milliseconds recommended_sleep() const = 0; | ||
145 | 71 | |||
146 | 69 | protected: | 72 | protected: |
147 | 70 | DisplayDevice() = default; | 73 | DisplayDevice() = default; |
148 | 71 | DisplayDevice& operator=(DisplayDevice const&) = delete; | 74 | DisplayDevice& operator=(DisplayDevice const&) = delete; |
149 | 72 | 75 | ||
150 | === modified file 'src/platforms/android/server/display_group.cpp' | |||
151 | --- src/platforms/android/server/display_group.cpp 2015-05-19 17:17:39 +0000 | |||
152 | +++ src/platforms/android/server/display_group.cpp 2015-06-30 06:52:15 +0000 | |||
153 | @@ -81,3 +81,8 @@ | |||
154 | 81 | contents.emplace_back(db.second->contents()); | 81 | contents.emplace_back(db.second->contents()); |
155 | 82 | device->commit(contents); | 82 | device->commit(contents); |
156 | 83 | } | 83 | } |
157 | 84 | |||
158 | 85 | std::chrono::milliseconds mga::DisplayGroup::recommended_sleep() const | ||
159 | 86 | { | ||
160 | 87 | return device->recommended_sleep(); | ||
161 | 88 | } | ||
162 | 84 | 89 | ||
163 | === modified file 'src/platforms/android/server/display_group.h' | |||
164 | --- src/platforms/android/server/display_group.h 2015-05-19 17:16:12 +0000 | |||
165 | +++ src/platforms/android/server/display_group.h 2015-06-30 06:52:15 +0000 | |||
166 | @@ -44,6 +44,7 @@ | |||
167 | 44 | 44 | ||
168 | 45 | void for_each_display_buffer(std::function<void(graphics::DisplayBuffer&)> const& f) override; | 45 | void for_each_display_buffer(std::function<void(graphics::DisplayBuffer&)> const& f) override; |
169 | 46 | void post() override; | 46 | void post() override; |
170 | 47 | std::chrono::milliseconds recommended_sleep() const override; | ||
171 | 47 | 48 | ||
172 | 48 | void add(DisplayName name, std::unique_ptr<ConfigurableDisplayBuffer> buffer); | 49 | void add(DisplayName name, std::unique_ptr<ConfigurableDisplayBuffer> buffer); |
173 | 49 | void remove(DisplayName name); | 50 | void remove(DisplayName name); |
174 | 50 | 51 | ||
175 | === modified file 'src/platforms/android/server/fb_device.cpp' | |||
176 | --- src/platforms/android/server/fb_device.cpp 2015-06-17 05:20:42 +0000 | |||
177 | +++ src/platforms/android/server/fb_device.cpp 2015-06-30 06:52:15 +0000 | |||
178 | @@ -115,3 +115,8 @@ | |||
179 | 115 | void mga::FBDevice::content_cleared() | 115 | void mga::FBDevice::content_cleared() |
180 | 116 | { | 116 | { |
181 | 117 | } | 117 | } |
182 | 118 | |||
183 | 119 | std::chrono::milliseconds mga::FBDevice::recommended_sleep() const | ||
184 | 120 | { | ||
185 | 121 | return std::chrono::milliseconds::zero(); | ||
186 | 122 | } | ||
187 | 118 | 123 | ||
188 | === modified file 'src/platforms/android/server/fb_device.h' | |||
189 | --- src/platforms/android/server/fb_device.h 2015-06-17 05:20:42 +0000 | |||
190 | +++ src/platforms/android/server/fb_device.h 2015-06-30 06:52:15 +0000 | |||
191 | @@ -51,6 +51,7 @@ | |||
192 | 51 | 51 | ||
193 | 52 | bool compatible_renderlist(RenderableList const& renderlist) override; | 52 | bool compatible_renderlist(RenderableList const& renderlist) override; |
194 | 53 | void commit(std::list<DisplayContents> const& contents) override; | 53 | void commit(std::list<DisplayContents> const& contents) override; |
195 | 54 | std::chrono::milliseconds recommended_sleep() const override; | ||
196 | 54 | 55 | ||
197 | 55 | private: | 56 | private: |
198 | 56 | std::shared_ptr<framebuffer_device_t> const fb_device; | 57 | std::shared_ptr<framebuffer_device_t> const fb_device; |
199 | 57 | 58 | ||
200 | === modified file 'src/platforms/android/server/hwc_device.cpp' | |||
201 | --- src/platforms/android/server/hwc_device.cpp 2015-06-17 05:20:42 +0000 | |||
202 | +++ src/platforms/android/server/hwc_device.cpp 2015-06-30 06:52:15 +0000 | |||
203 | @@ -27,6 +27,8 @@ | |||
204 | 27 | #include "mir/raii.h" | 27 | #include "mir/raii.h" |
205 | 28 | #include <limits> | 28 | #include <limits> |
206 | 29 | #include <algorithm> | 29 | #include <algorithm> |
207 | 30 | #include <chrono> | ||
208 | 31 | #include <thread> | ||
209 | 30 | 32 | ||
210 | 31 | namespace mg = mir::graphics; | 33 | namespace mg = mir::graphics; |
211 | 32 | namespace mga=mir::graphics::android; | 34 | namespace mga=mir::graphics::android; |
212 | @@ -97,6 +99,8 @@ | |||
213 | 97 | 99 | ||
214 | 98 | hwc_wrapper->prepare(lists); | 100 | hwc_wrapper->prepare(lists); |
215 | 99 | 101 | ||
216 | 102 | bool purely_overlays = true; | ||
217 | 103 | |||
218 | 100 | for (auto& content : contents) | 104 | for (auto& content : contents) |
219 | 101 | { | 105 | { |
220 | 102 | if (content.list.needs_swapbuffers()) | 106 | if (content.list.needs_swapbuffers()) |
221 | @@ -111,6 +115,7 @@ | |||
222 | 111 | content.compositor.render(std::move(rejected_renderables), content.list_offset, content.context); | 115 | content.compositor.render(std::move(rejected_renderables), content.list_offset, content.context); |
223 | 112 | content.list.setup_fb(content.context.last_rendered_buffer()); | 116 | content.list.setup_fb(content.context.last_rendered_buffer()); |
224 | 113 | content.list.swap_occurred(); | 117 | content.list.swap_occurred(); |
225 | 118 | purely_overlays = false; | ||
226 | 114 | } | 119 | } |
227 | 115 | 120 | ||
228 | 116 | //setup overlays | 121 | //setup overlays |
229 | @@ -136,6 +141,20 @@ | |||
230 | 136 | 141 | ||
231 | 137 | mir::Fd retire_fd(content.list.retirement_fence()); | 142 | mir::Fd retire_fd(content.list.retirement_fence()); |
232 | 138 | } | 143 | } |
233 | 144 | |||
234 | 145 | /* | ||
235 | 146 | * Test results (how long can we sleep for without missing a frame?): | ||
236 | 147 | * arale: 10ms (TODO: Find out why arale is so slow) | ||
237 | 148 | * mako: 15ms | ||
238 | 149 | * krillin: 11ms (to be fair, the display is 67Hz) | ||
239 | 150 | */ | ||
240 | 151 | using namespace std; | ||
241 | 152 | recommend_sleep = purely_overlays ? 10ms : 0ms; | ||
242 | 153 | } | ||
243 | 154 | |||
244 | 155 | std::chrono::milliseconds mga::HwcDevice::recommended_sleep() const | ||
245 | 156 | { | ||
246 | 157 | return recommend_sleep; | ||
247 | 139 | } | 158 | } |
248 | 140 | 159 | ||
249 | 141 | void mga::HwcDevice::content_cleared() | 160 | void mga::HwcDevice::content_cleared() |
250 | 142 | 161 | ||
251 | === modified file 'src/platforms/android/server/hwc_device.h' | |||
252 | --- src/platforms/android/server/hwc_device.h 2015-04-28 07:54:10 +0000 | |||
253 | +++ src/platforms/android/server/hwc_device.h 2015-06-30 06:52:15 +0000 | |||
254 | @@ -46,6 +46,7 @@ | |||
255 | 46 | bool compatible_renderlist(RenderableList const& renderlist) override; | 46 | bool compatible_renderlist(RenderableList const& renderlist) override; |
256 | 47 | void commit(std::list<DisplayContents> const& contents) override; | 47 | void commit(std::list<DisplayContents> const& contents) override; |
257 | 48 | void content_cleared() override; | 48 | void content_cleared() override; |
258 | 49 | std::chrono::milliseconds recommended_sleep() const override; | ||
259 | 49 | 50 | ||
260 | 50 | private: | 51 | private: |
261 | 51 | bool buffer_is_onscreen(Buffer const&) const; | 52 | bool buffer_is_onscreen(Buffer const&) const; |
262 | @@ -53,6 +54,7 @@ | |||
263 | 53 | 54 | ||
264 | 54 | std::shared_ptr<HwcWrapper> const hwc_wrapper; | 55 | std::shared_ptr<HwcWrapper> const hwc_wrapper; |
265 | 55 | std::shared_ptr<SyncFileOps> const sync_ops; | 56 | std::shared_ptr<SyncFileOps> const sync_ops; |
266 | 57 | std::chrono::milliseconds recommend_sleep{0}; | ||
267 | 56 | }; | 58 | }; |
268 | 57 | 59 | ||
269 | 58 | } | 60 | } |
270 | 59 | 61 | ||
271 | === modified file 'src/platforms/android/server/hwc_fb_device.cpp' | |||
272 | --- src/platforms/android/server/hwc_fb_device.cpp 2015-06-17 05:20:42 +0000 | |||
273 | +++ src/platforms/android/server/hwc_fb_device.cpp 2015-06-30 06:52:15 +0000 | |||
274 | @@ -113,3 +113,8 @@ | |||
275 | 113 | void mga::HwcFbDevice::content_cleared() | 113 | void mga::HwcFbDevice::content_cleared() |
276 | 114 | { | 114 | { |
277 | 115 | } | 115 | } |
278 | 116 | |||
279 | 117 | std::chrono::milliseconds mga::HwcFbDevice::recommended_sleep() const | ||
280 | 118 | { | ||
281 | 119 | return std::chrono::milliseconds::zero(); | ||
282 | 120 | } | ||
283 | 116 | 121 | ||
284 | === modified file 'src/platforms/android/server/hwc_fb_device.h' | |||
285 | --- src/platforms/android/server/hwc_fb_device.h 2015-04-28 07:54:10 +0000 | |||
286 | +++ src/platforms/android/server/hwc_fb_device.h 2015-06-30 06:52:15 +0000 | |||
287 | @@ -43,6 +43,7 @@ | |||
288 | 43 | 43 | ||
289 | 44 | bool compatible_renderlist(RenderableList const& renderlist) override; | 44 | bool compatible_renderlist(RenderableList const& renderlist) override; |
290 | 45 | void commit(std::list<DisplayContents> const& contents) override; | 45 | void commit(std::list<DisplayContents> const& contents) override; |
291 | 46 | std::chrono::milliseconds recommended_sleep() const override; | ||
292 | 46 | 47 | ||
293 | 47 | private: | 48 | private: |
294 | 48 | void content_cleared() override; | 49 | void content_cleared() override; |
295 | 49 | 50 | ||
296 | === modified file 'src/platforms/mesa/server/kms/display_buffer.cpp' | |||
297 | --- src/platforms/mesa/server/kms/display_buffer.cpp 2015-06-17 05:20:42 +0000 | |||
298 | +++ src/platforms/mesa/server/kms/display_buffer.cpp 2015-06-30 06:52:15 +0000 | |||
299 | @@ -28,6 +28,8 @@ | |||
300 | 28 | #include <GLES2/gl2.h> | 28 | #include <GLES2/gl2.h> |
301 | 29 | 29 | ||
302 | 30 | #include <stdexcept> | 30 | #include <stdexcept> |
303 | 31 | #include <chrono> | ||
304 | 32 | #include <thread> | ||
305 | 31 | 33 | ||
306 | 32 | namespace mgm = mir::graphics::mesa; | 34 | namespace mgm = mir::graphics::mesa; |
307 | 33 | namespace geom = mir::geometry; | 35 | namespace geom = mir::geometry; |
308 | @@ -282,6 +284,11 @@ | |||
309 | 282 | needs_set_crtc = false; | 284 | needs_set_crtc = false; |
310 | 283 | } | 285 | } |
311 | 284 | 286 | ||
312 | 287 | using namespace std; // For operator""ms() | ||
313 | 288 | |||
314 | 289 | // Predicted worst case render time for the next frame... | ||
315 | 290 | auto predicted_render_time = 50ms; | ||
316 | 291 | |||
317 | 285 | if (bypass_buf) | 292 | if (bypass_buf) |
318 | 286 | { | 293 | { |
319 | 287 | /* | 294 | /* |
320 | @@ -295,6 +302,10 @@ | |||
321 | 295 | */ | 302 | */ |
322 | 296 | scheduled_bypass_frame = bypass_buf; | 303 | scheduled_bypass_frame = bypass_buf; |
323 | 297 | wait_for_page_flip(); | 304 | wait_for_page_flip(); |
324 | 305 | |||
325 | 306 | // It's very likely the next frame will be bypassed like this one so | ||
326 | 307 | // we only need time for kernel page flip scheduling... | ||
327 | 308 | predicted_render_time = 5ms; | ||
328 | 298 | } | 309 | } |
329 | 299 | else | 310 | else |
330 | 300 | { | 311 | { |
331 | @@ -306,11 +317,33 @@ | |||
332 | 306 | scheduled_composite_frame = bufobj; | 317 | scheduled_composite_frame = bufobj; |
333 | 307 | if (outputs.size() == 1) | 318 | if (outputs.size() == 1) |
334 | 308 | wait_for_page_flip(); | 319 | wait_for_page_flip(); |
335 | 320 | |||
336 | 321 | /* | ||
337 | 322 | * TODO: If you're optimistic about your GPU performance and/or | ||
338 | 323 | * measure it carefully you may wish to set predicted_render_time | ||
339 | 324 | * to a lower value here for lower latency. | ||
340 | 325 | * | ||
341 | 326 | *predicted_render_time = 9ms; // e.g. about the same as Weston | ||
342 | 327 | */ | ||
343 | 309 | } | 328 | } |
344 | 310 | 329 | ||
345 | 311 | // Buffer lifetimes are managed exclusively by scheduled*/visible* now | 330 | // Buffer lifetimes are managed exclusively by scheduled*/visible* now |
346 | 312 | bypass_buf = nullptr; | 331 | bypass_buf = nullptr; |
347 | 313 | bypass_bufobj = nullptr; | 332 | bypass_bufobj = nullptr; |
348 | 333 | |||
349 | 334 | recommend_sleep = 0ms; | ||
350 | 335 | if (outputs.size() == 1) | ||
351 | 336 | { | ||
352 | 337 | auto const& output = outputs.front(); | ||
353 | 338 | auto const min_frame_interval = 1000ms / output->max_refresh_rate(); | ||
354 | 339 | if (predicted_render_time < min_frame_interval) | ||
355 | 340 | recommend_sleep = min_frame_interval - predicted_render_time; | ||
356 | 341 | } | ||
357 | 342 | } | ||
358 | 343 | |||
359 | 344 | std::chrono::milliseconds mgm::DisplayBuffer::recommended_sleep() const | ||
360 | 345 | { | ||
361 | 346 | return recommend_sleep; | ||
362 | 314 | } | 347 | } |
363 | 315 | 348 | ||
364 | 316 | mgm::BufferObject* mgm::DisplayBuffer::get_front_buffer_object() | 349 | mgm::BufferObject* mgm::DisplayBuffer::get_front_buffer_object() |
365 | 317 | 350 | ||
366 | === modified file 'src/platforms/mesa/server/kms/display_buffer.h' | |||
367 | --- src/platforms/mesa/server/kms/display_buffer.h 2015-06-17 05:20:42 +0000 | |||
368 | +++ src/platforms/mesa/server/kms/display_buffer.h 2015-06-30 06:52:15 +0000 | |||
369 | @@ -65,6 +65,7 @@ | |||
370 | 65 | void for_each_display_buffer( | 65 | void for_each_display_buffer( |
371 | 66 | std::function<void(graphics::DisplayBuffer&)> const& f) override; | 66 | std::function<void(graphics::DisplayBuffer&)> const& f) override; |
372 | 67 | void post() override; | 67 | void post() override; |
373 | 68 | std::chrono::milliseconds recommended_sleep() const override; | ||
374 | 68 | 69 | ||
375 | 69 | MirOrientation orientation() const override; | 70 | MirOrientation orientation() const override; |
376 | 70 | void set_orientation(MirOrientation const rot, geometry::Rectangle const& a); | 71 | void set_orientation(MirOrientation const rot, geometry::Rectangle const& a); |
377 | @@ -93,6 +94,7 @@ | |||
378 | 93 | uint32_t fb_width, fb_height; | 94 | uint32_t fb_width, fb_height; |
379 | 94 | MirOrientation rotation; | 95 | MirOrientation rotation; |
380 | 95 | std::atomic<bool> needs_set_crtc; | 96 | std::atomic<bool> needs_set_crtc; |
381 | 97 | std::chrono::milliseconds recommend_sleep{0}; | ||
382 | 96 | bool page_flips_pending; | 98 | bool page_flips_pending; |
383 | 97 | }; | 99 | }; |
384 | 98 | 100 | ||
385 | 99 | 101 | ||
386 | === modified file 'src/platforms/mesa/server/kms/kms_output.h' | |||
387 | --- src/platforms/mesa/server/kms/kms_output.h 2015-06-17 05:20:42 +0000 | |||
388 | +++ src/platforms/mesa/server/kms/kms_output.h 2015-06-30 06:52:15 +0000 | |||
389 | @@ -43,6 +43,14 @@ | |||
390 | 43 | virtual void configure(geometry::Displacement fb_offset, size_t kms_mode_index) = 0; | 43 | virtual void configure(geometry::Displacement fb_offset, size_t kms_mode_index) = 0; |
391 | 44 | virtual geometry::Size size() const = 0; | 44 | virtual geometry::Size size() const = 0; |
392 | 45 | 45 | ||
393 | 46 | /** | ||
394 | 47 | * Approximate maximum refresh rate of this output to within 1Hz. | ||
395 | 48 | * Typically the rate is fixed (e.g. 60Hz) but it may also be variable as | ||
396 | 49 | * in Nvidia G-Sync/AMD FreeSync/VESA Adaptive Sync. So this function | ||
397 | 50 | * returns the maximum rate to expect. | ||
398 | 51 | */ | ||
399 | 52 | virtual int max_refresh_rate() const = 0; | ||
400 | 53 | |||
401 | 46 | virtual bool set_crtc(uint32_t fb_id) = 0; | 54 | virtual bool set_crtc(uint32_t fb_id) = 0; |
402 | 47 | virtual void clear_crtc() = 0; | 55 | virtual void clear_crtc() = 0; |
403 | 48 | virtual bool schedule_page_flip(uint32_t fb_id) = 0; | 56 | virtual bool schedule_page_flip(uint32_t fb_id) = 0; |
404 | 49 | 57 | ||
405 | === modified file 'src/platforms/mesa/server/kms/real_kms_output.cpp' | |||
406 | --- src/platforms/mesa/server/kms/real_kms_output.cpp 2015-06-17 05:20:42 +0000 | |||
407 | +++ src/platforms/mesa/server/kms/real_kms_output.cpp 2015-06-30 06:52:15 +0000 | |||
408 | @@ -188,6 +188,14 @@ | |||
409 | 188 | return {mode.hdisplay, mode.vdisplay}; | 188 | return {mode.hdisplay, mode.vdisplay}; |
410 | 189 | } | 189 | } |
411 | 190 | 190 | ||
412 | 191 | int mgm::RealKMSOutput::max_refresh_rate() const | ||
413 | 192 | { | ||
414 | 193 | // TODO: In future when DRM exposes FreeSync/Adaptive Sync/G-Sync info | ||
415 | 194 | // this value may be calculated differently. | ||
416 | 195 | drmModeModeInfo const& current_mode = connector->modes[mode_index]; | ||
417 | 196 | return current_mode.vrefresh; | ||
418 | 197 | } | ||
419 | 198 | |||
420 | 191 | void mgm::RealKMSOutput::configure(geom::Displacement offset, size_t kms_mode_index) | 199 | void mgm::RealKMSOutput::configure(geom::Displacement offset, size_t kms_mode_index) |
421 | 192 | { | 200 | { |
422 | 193 | fb_offset = offset; | 201 | fb_offset = offset; |
423 | 194 | 202 | ||
424 | === modified file 'src/platforms/mesa/server/kms/real_kms_output.h' | |||
425 | --- src/platforms/mesa/server/kms/real_kms_output.h 2015-06-17 05:20:42 +0000 | |||
426 | +++ src/platforms/mesa/server/kms/real_kms_output.h 2015-06-30 06:52:15 +0000 | |||
427 | @@ -44,6 +44,7 @@ | |||
428 | 44 | void reset(); | 44 | void reset(); |
429 | 45 | void configure(geometry::Displacement fb_offset, size_t kms_mode_index); | 45 | void configure(geometry::Displacement fb_offset, size_t kms_mode_index); |
430 | 46 | geometry::Size size() const; | 46 | geometry::Size size() const; |
431 | 47 | int max_refresh_rate() const; | ||
432 | 47 | 48 | ||
433 | 48 | bool set_crtc(uint32_t fb_id); | 49 | bool set_crtc(uint32_t fb_id); |
434 | 49 | void clear_crtc(); | 50 | void clear_crtc(); |
435 | 50 | 51 | ||
436 | === modified file 'src/server/compositor/default_configuration.cpp' | |||
437 | --- src/server/compositor/default_configuration.cpp 2015-06-17 05:20:42 +0000 | |||
438 | +++ src/server/compositor/default_configuration.cpp 2015-06-30 06:52:15 +0000 | |||
439 | @@ -84,12 +84,16 @@ | |||
440 | 84 | return compositor( | 84 | return compositor( |
441 | 85 | [this]() | 85 | [this]() |
442 | 86 | { | 86 | { |
443 | 87 | std::chrono::milliseconds const composite_delay( | ||
444 | 88 | the_options()->get<int>(options::composite_delay_opt)); | ||
445 | 89 | |||
446 | 87 | return std::make_shared<mc::MultiThreadedCompositor>( | 90 | return std::make_shared<mc::MultiThreadedCompositor>( |
447 | 88 | the_display(), | 91 | the_display(), |
448 | 89 | the_scene(), | 92 | the_scene(), |
449 | 90 | the_display_buffer_compositor_factory(), | 93 | the_display_buffer_compositor_factory(), |
450 | 91 | the_shell(), | 94 | the_shell(), |
451 | 92 | the_compositor_report(), | 95 | the_compositor_report(), |
452 | 96 | composite_delay, | ||
453 | 93 | !the_options()->is_set(options::host_socket_opt)); | 97 | !the_options()->is_set(options::host_socket_opt)); |
454 | 94 | }); | 98 | }); |
455 | 95 | } | 99 | } |
456 | 96 | 100 | ||
457 | === modified file 'src/server/compositor/multi_threaded_compositor.cpp' | |||
458 | --- src/server/compositor/multi_threaded_compositor.cpp 2015-06-18 14:33:10 +0000 | |||
459 | +++ src/server/compositor/multi_threaded_compositor.cpp 2015-06-30 06:52:15 +0000 | |||
460 | @@ -101,12 +101,14 @@ | |||
461 | 101 | mg::DisplaySyncGroup& group, | 101 | mg::DisplaySyncGroup& group, |
462 | 102 | std::shared_ptr<mc::Scene> const& scene, | 102 | std::shared_ptr<mc::Scene> const& scene, |
463 | 103 | std::shared_ptr<DisplayListener> const& display_listener, | 103 | std::shared_ptr<DisplayListener> const& display_listener, |
464 | 104 | std::chrono::milliseconds fixed_composite_delay, | ||
465 | 104 | std::shared_ptr<CompositorReport> const& report) : | 105 | std::shared_ptr<CompositorReport> const& report) : |
466 | 105 | compositor_factory{db_compositor_factory}, | 106 | compositor_factory{db_compositor_factory}, |
467 | 106 | group(group), | 107 | group(group), |
468 | 107 | scene(scene), | 108 | scene(scene), |
469 | 108 | running{true}, | 109 | running{true}, |
470 | 109 | frames_scheduled{0}, | 110 | frames_scheduled{0}, |
471 | 111 | force_sleep{fixed_composite_delay}, | ||
472 | 110 | display_listener{display_listener}, | 112 | display_listener{display_listener}, |
473 | 111 | report{report}, | 113 | report{report}, |
474 | 112 | started_future{started.get_future()} | 114 | started_future{started.get_future()} |
475 | @@ -182,6 +184,17 @@ | |||
476 | 182 | } | 184 | } |
477 | 183 | group.post(); | 185 | group.post(); |
478 | 184 | 186 | ||
479 | 187 | /* | ||
480 | 188 | * "Predictive bypass" optimization: If the last frame was | ||
481 | 189 | * bypassed/overlayed or you simply have a fast GPU, it is | ||
482 | 190 | * beneficial to sleep for most of the next frame. This reduces | ||
483 | 191 | * the latency between snapshotting the scene and post() | ||
484 | 192 | * completing by almost a whole frame. | ||
485 | 193 | */ | ||
486 | 194 | auto delay = force_sleep >= std::chrono::milliseconds::zero() ? | ||
487 | 195 | force_sleep : group.recommended_sleep(); | ||
488 | 196 | std::this_thread::sleep_for(delay); | ||
489 | 197 | |||
490 | 185 | lock.lock(); | 198 | lock.lock(); |
491 | 186 | 199 | ||
492 | 187 | /* | 200 | /* |
493 | @@ -231,6 +244,7 @@ | |||
494 | 231 | std::shared_ptr<mc::Scene> const scene; | 244 | std::shared_ptr<mc::Scene> const scene; |
495 | 232 | bool running; | 245 | bool running; |
496 | 233 | int frames_scheduled; | 246 | int frames_scheduled; |
497 | 247 | std::chrono::milliseconds force_sleep{-1}; | ||
498 | 234 | std::mutex run_mutex; | 248 | std::mutex run_mutex; |
499 | 235 | std::condition_variable run_cv; | 249 | std::condition_variable run_cv; |
500 | 236 | std::shared_ptr<DisplayListener> const display_listener; | 250 | std::shared_ptr<DisplayListener> const display_listener; |
501 | @@ -248,6 +262,7 @@ | |||
502 | 248 | std::shared_ptr<DisplayBufferCompositorFactory> const& db_compositor_factory, | 262 | std::shared_ptr<DisplayBufferCompositorFactory> const& db_compositor_factory, |
503 | 249 | std::shared_ptr<DisplayListener> const& display_listener, | 263 | std::shared_ptr<DisplayListener> const& display_listener, |
504 | 250 | std::shared_ptr<CompositorReport> const& compositor_report, | 264 | std::shared_ptr<CompositorReport> const& compositor_report, |
505 | 265 | std::chrono::milliseconds fixed_composite_delay, | ||
506 | 251 | bool compose_on_start) | 266 | bool compose_on_start) |
507 | 252 | : display{display}, | 267 | : display{display}, |
508 | 253 | scene{scene}, | 268 | scene{scene}, |
509 | @@ -255,6 +270,7 @@ | |||
510 | 255 | display_listener{display_listener}, | 270 | display_listener{display_listener}, |
511 | 256 | report{compositor_report}, | 271 | report{compositor_report}, |
512 | 257 | state{CompositorState::stopped}, | 272 | state{CompositorState::stopped}, |
513 | 273 | fixed_composite_delay{fixed_composite_delay}, | ||
514 | 258 | compose_on_start{compose_on_start}, | 274 | compose_on_start{compose_on_start}, |
515 | 259 | thread_pool{1} | 275 | thread_pool{1} |
516 | 260 | { | 276 | { |
517 | @@ -348,7 +364,8 @@ | |||
518 | 348 | display->for_each_display_sync_group([this](mg::DisplaySyncGroup& group) | 364 | display->for_each_display_sync_group([this](mg::DisplaySyncGroup& group) |
519 | 349 | { | 365 | { |
520 | 350 | auto thread_functor = std::make_unique<mc::CompositingFunctor>( | 366 | auto thread_functor = std::make_unique<mc::CompositingFunctor>( |
522 | 351 | display_buffer_compositor_factory, group, scene, display_listener, report); | 367 | display_buffer_compositor_factory, group, scene, display_listener, |
523 | 368 | fixed_composite_delay, report); | ||
524 | 352 | 369 | ||
525 | 353 | futures.push_back(thread_pool.run(std::ref(*thread_functor), &group)); | 370 | futures.push_back(thread_pool.run(std::ref(*thread_functor), &group)); |
526 | 354 | thread_functors.push_back(std::move(thread_functor)); | 371 | thread_functors.push_back(std::move(thread_functor)); |
527 | 355 | 372 | ||
528 | === modified file 'src/server/compositor/multi_threaded_compositor.h' | |||
529 | --- src/server/compositor/multi_threaded_compositor.h 2015-06-17 05:20:42 +0000 | |||
530 | +++ src/server/compositor/multi_threaded_compositor.h 2015-06-30 06:52:15 +0000 | |||
531 | @@ -65,6 +65,7 @@ | |||
532 | 65 | std::shared_ptr<DisplayBufferCompositorFactory> const& db_compositor_factory, | 65 | std::shared_ptr<DisplayBufferCompositorFactory> const& db_compositor_factory, |
533 | 66 | std::shared_ptr<DisplayListener> const& display_listener, | 66 | std::shared_ptr<DisplayListener> const& display_listener, |
534 | 67 | std::shared_ptr<CompositorReport> const& compositor_report, | 67 | std::shared_ptr<CompositorReport> const& compositor_report, |
535 | 68 | std::chrono::milliseconds fixed_composite_delay, // -1 = automatic | ||
536 | 68 | bool compose_on_start); | 69 | bool compose_on_start); |
537 | 69 | ~MultiThreadedCompositor(); | 70 | ~MultiThreadedCompositor(); |
538 | 70 | 71 | ||
539 | @@ -86,6 +87,7 @@ | |||
540 | 86 | 87 | ||
541 | 87 | std::mutex state_guard; | 88 | std::mutex state_guard; |
542 | 88 | CompositorState state; | 89 | CompositorState state; |
543 | 90 | std::chrono::milliseconds fixed_composite_delay; | ||
544 | 89 | bool compose_on_start; | 91 | bool compose_on_start; |
545 | 90 | 92 | ||
546 | 91 | void schedule_compositing(int number_composites); | 93 | void schedule_compositing(int number_composites); |
547 | 92 | 94 | ||
548 | === modified file 'src/server/graphics/nested/display.cpp' | |||
549 | --- src/server/graphics/nested/display.cpp 2015-06-17 05:20:42 +0000 | |||
550 | +++ src/server/graphics/nested/display.cpp 2015-06-30 06:52:15 +0000 | |||
551 | @@ -137,6 +137,14 @@ | |||
552 | 137 | { | 137 | { |
553 | 138 | } | 138 | } |
554 | 139 | 139 | ||
555 | 140 | std::chrono::milliseconds | ||
556 | 141 | mgn::detail::DisplaySyncGroup::recommended_sleep() const | ||
557 | 142 | { | ||
558 | 143 | // TODO: Might make sense in future with nested bypass. We could save | ||
559 | 144 | // almost another frame of lag! | ||
560 | 145 | return std::chrono::milliseconds::zero(); | ||
561 | 146 | } | ||
562 | 147 | |||
563 | 140 | mgn::Display::Display( | 148 | mgn::Display::Display( |
564 | 141 | std::shared_ptr<mg::Platform> const& platform, | 149 | std::shared_ptr<mg::Platform> const& platform, |
565 | 142 | std::shared_ptr<HostConnection> const& connection, | 150 | std::shared_ptr<HostConnection> const& connection, |
566 | 143 | 151 | ||
567 | === modified file 'src/server/graphics/nested/display.h' | |||
568 | --- src/server/graphics/nested/display.h 2015-06-17 05:20:42 +0000 | |||
569 | +++ src/server/graphics/nested/display.h 2015-06-30 06:52:15 +0000 | |||
570 | @@ -97,6 +97,7 @@ | |||
571 | 97 | DisplaySyncGroup(std::shared_ptr<detail::DisplayBuffer> const& output); | 97 | DisplaySyncGroup(std::shared_ptr<detail::DisplayBuffer> const& output); |
572 | 98 | void for_each_display_buffer(std::function<void(graphics::DisplayBuffer&)> const&) override; | 98 | void for_each_display_buffer(std::function<void(graphics::DisplayBuffer&)> const&) override; |
573 | 99 | void post() override; | 99 | void post() override; |
574 | 100 | std::chrono::milliseconds recommended_sleep() const override; | ||
575 | 100 | private: | 101 | private: |
576 | 101 | std::shared_ptr<detail::DisplayBuffer> const output; | 102 | std::shared_ptr<detail::DisplayBuffer> const output; |
577 | 102 | }; | 103 | }; |
578 | 103 | 104 | ||
579 | === modified file 'src/server/graphics/offscreen/display.cpp' | |||
580 | --- src/server/graphics/offscreen/display.cpp 2015-06-17 05:20:42 +0000 | |||
581 | +++ src/server/graphics/offscreen/display.cpp 2015-06-30 06:52:15 +0000 | |||
582 | @@ -87,6 +87,12 @@ | |||
583 | 87 | { | 87 | { |
584 | 88 | } | 88 | } |
585 | 89 | 89 | ||
586 | 90 | std::chrono::milliseconds | ||
587 | 91 | mgo::detail::DisplaySyncGroup::recommended_sleep() const | ||
588 | 92 | { | ||
589 | 93 | return std::chrono::milliseconds::zero(); | ||
590 | 94 | } | ||
591 | 95 | |||
592 | 90 | mgo::Display::Display( | 96 | mgo::Display::Display( |
593 | 91 | EGLNativeDisplayType egl_native_display, | 97 | EGLNativeDisplayType egl_native_display, |
594 | 92 | std::shared_ptr<DisplayConfigurationPolicy> const& initial_conf_policy, | 98 | std::shared_ptr<DisplayConfigurationPolicy> const& initial_conf_policy, |
595 | 93 | 99 | ||
596 | === modified file 'src/server/graphics/offscreen/display.h' | |||
597 | --- src/server/graphics/offscreen/display.h 2015-06-17 05:20:42 +0000 | |||
598 | +++ src/server/graphics/offscreen/display.h 2015-06-30 06:52:15 +0000 | |||
599 | @@ -64,6 +64,7 @@ | |||
600 | 64 | DisplaySyncGroup(std::unique_ptr<DisplayBuffer> output); | 64 | DisplaySyncGroup(std::unique_ptr<DisplayBuffer> output); |
601 | 65 | void for_each_display_buffer(std::function<void(DisplayBuffer&)> const&) override; | 65 | void for_each_display_buffer(std::function<void(DisplayBuffer&)> const&) override; |
602 | 66 | void post() override; | 66 | void post() override; |
603 | 67 | std::chrono::milliseconds recommended_sleep() const override; | ||
604 | 67 | private: | 68 | private: |
605 | 68 | std::unique_ptr<DisplayBuffer> const output; | 69 | std::unique_ptr<DisplayBuffer> const output; |
606 | 69 | }; | 70 | }; |
607 | 70 | 71 | ||
608 | === modified file 'tests/include/mir/test/doubles/mock_display_device.h' | |||
609 | --- tests/include/mir/test/doubles/mock_display_device.h 2015-04-28 07:54:10 +0000 | |||
610 | +++ tests/include/mir/test/doubles/mock_display_device.h 2015-06-30 06:52:15 +0000 | |||
611 | @@ -40,6 +40,7 @@ | |||
612 | 40 | MOCK_METHOD1(commit, void(std::list<graphics::android::DisplayContents> const&)); | 40 | MOCK_METHOD1(commit, void(std::list<graphics::android::DisplayContents> const&)); |
613 | 41 | MOCK_METHOD1(compatible_renderlist, bool( | 41 | MOCK_METHOD1(compatible_renderlist, bool( |
614 | 42 | graphics::RenderableList const&)); | 42 | graphics::RenderableList const&)); |
615 | 43 | MOCK_CONST_METHOD0(recommended_sleep, std::chrono::milliseconds()); | ||
616 | 43 | }; | 44 | }; |
617 | 44 | } | 45 | } |
618 | 45 | } | 46 | } |
619 | 46 | 47 | ||
620 | === modified file 'tests/integration-tests/test_surface_stack_with_compositor.cpp' | |||
621 | --- tests/integration-tests/test_surface_stack_with_compositor.cpp 2015-06-25 03:00:08 +0000 | |||
622 | +++ tests/integration-tests/test_surface_stack_with_compositor.cpp 2015-06-30 06:52:15 +0000 | |||
623 | @@ -153,6 +153,9 @@ | |||
624 | 153 | mt::fake_shared(renderer_factory), | 153 | mt::fake_shared(renderer_factory), |
625 | 154 | null_comp_report}; | 154 | null_comp_report}; |
626 | 155 | }; | 155 | }; |
627 | 156 | |||
628 | 157 | std::chrono::milliseconds const default_delay{-1}; | ||
629 | 158 | |||
630 | 156 | } | 159 | } |
631 | 157 | 160 | ||
632 | 158 | TEST_F(SurfaceStackCompositor, composes_on_start_if_told_to_in_constructor) | 161 | TEST_F(SurfaceStackCompositor, composes_on_start_if_told_to_in_constructor) |
633 | @@ -162,7 +165,7 @@ | |||
634 | 162 | mt::fake_shared(stack), | 165 | mt::fake_shared(stack), |
635 | 163 | mt::fake_shared(dbc_factory), | 166 | mt::fake_shared(dbc_factory), |
636 | 164 | mt::fake_shared(stub_display_listener), | 167 | mt::fake_shared(stub_display_listener), |
638 | 165 | null_comp_report, true); | 168 | null_comp_report, default_delay, true); |
639 | 166 | mt_compositor.start(); | 169 | mt_compositor.start(); |
640 | 167 | 170 | ||
641 | 168 | EXPECT_TRUE(stub_primary_db.has_posted_at_least(1, timeout)); | 171 | EXPECT_TRUE(stub_primary_db.has_posted_at_least(1, timeout)); |
642 | @@ -176,7 +179,7 @@ | |||
643 | 176 | mt::fake_shared(stack), | 179 | mt::fake_shared(stack), |
644 | 177 | mt::fake_shared(dbc_factory), | 180 | mt::fake_shared(dbc_factory), |
645 | 178 | mt::fake_shared(stub_display_listener), | 181 | mt::fake_shared(stub_display_listener), |
647 | 179 | null_comp_report, false); | 182 | null_comp_report, default_delay, false); |
648 | 180 | mt_compositor.start(); | 183 | mt_compositor.start(); |
649 | 181 | 184 | ||
650 | 182 | EXPECT_TRUE(stub_primary_db.has_posted_at_least(0, timeout)); | 185 | EXPECT_TRUE(stub_primary_db.has_posted_at_least(0, timeout)); |
651 | @@ -190,7 +193,7 @@ | |||
652 | 190 | mt::fake_shared(stack), | 193 | mt::fake_shared(stack), |
653 | 191 | mt::fake_shared(dbc_factory), | 194 | mt::fake_shared(dbc_factory), |
654 | 192 | mt::fake_shared(stub_display_listener), | 195 | mt::fake_shared(stub_display_listener), |
656 | 193 | null_comp_report, false); | 196 | null_comp_report, default_delay, false); |
657 | 194 | mt_compositor.start(); | 197 | mt_compositor.start(); |
658 | 195 | 198 | ||
659 | 196 | stack.add_surface(stub_surface, default_params.depth, default_params.input_mode); | 199 | stack.add_surface(stub_surface, default_params.depth, default_params.input_mode); |
660 | @@ -212,7 +215,7 @@ | |||
661 | 212 | mt::fake_shared(stack), | 215 | mt::fake_shared(stack), |
662 | 213 | mt::fake_shared(dbc_factory), | 216 | mt::fake_shared(dbc_factory), |
663 | 214 | mt::fake_shared(stub_display_listener), | 217 | mt::fake_shared(stub_display_listener), |
665 | 215 | null_comp_report, false); | 218 | null_comp_report, default_delay, false); |
666 | 216 | mt_compositor.start(); | 219 | mt_compositor.start(); |
667 | 217 | 220 | ||
668 | 218 | stack.add_surface(stub_surface, default_params.depth, default_params.input_mode); | 221 | stack.add_surface(stub_surface, default_params.depth, default_params.input_mode); |
669 | @@ -235,7 +238,7 @@ | |||
670 | 235 | mt::fake_shared(stack), | 238 | mt::fake_shared(stack), |
671 | 236 | mt::fake_shared(dbc_factory), | 239 | mt::fake_shared(dbc_factory), |
672 | 237 | mt::fake_shared(stub_display_listener), | 240 | mt::fake_shared(stub_display_listener), |
674 | 238 | null_comp_report, false); | 241 | null_comp_report, default_delay, false); |
675 | 239 | mt_compositor.start(); | 242 | mt_compositor.start(); |
676 | 240 | 243 | ||
677 | 241 | stack.add_surface(stub_surface, default_params.depth, default_params.input_mode); | 244 | stack.add_surface(stub_surface, default_params.depth, default_params.input_mode); |
678 | @@ -256,7 +259,7 @@ | |||
679 | 256 | mt::fake_shared(stack), | 259 | mt::fake_shared(stack), |
680 | 257 | mt::fake_shared(dbc_factory), | 260 | mt::fake_shared(dbc_factory), |
681 | 258 | mt::fake_shared(stub_display_listener), | 261 | mt::fake_shared(stub_display_listener), |
683 | 259 | null_comp_report, false); | 262 | null_comp_report, default_delay, false); |
684 | 260 | mt_compositor.start(); | 263 | mt_compositor.start(); |
685 | 261 | 264 | ||
686 | 262 | stack.add_surface(stub_surface, default_params.depth, default_params.input_mode); | 265 | stack.add_surface(stub_surface, default_params.depth, default_params.input_mode); |
687 | @@ -281,7 +284,7 @@ | |||
688 | 281 | mt::fake_shared(stack), | 284 | mt::fake_shared(stack), |
689 | 282 | mt::fake_shared(dbc_factory), | 285 | mt::fake_shared(dbc_factory), |
690 | 283 | mt::fake_shared(stub_display_listener), | 286 | mt::fake_shared(stub_display_listener), |
692 | 284 | null_comp_report, false); | 287 | null_comp_report, default_delay, false); |
693 | 285 | 288 | ||
694 | 286 | mt_compositor.start(); | 289 | mt_compositor.start(); |
695 | 287 | stub_surface->move_to(geom::Point{1,1}); | 290 | stub_surface->move_to(geom::Point{1,1}); |
696 | @@ -300,7 +303,7 @@ | |||
697 | 300 | mt::fake_shared(stack), | 303 | mt::fake_shared(stack), |
698 | 301 | mt::fake_shared(dbc_factory), | 304 | mt::fake_shared(dbc_factory), |
699 | 302 | mt::fake_shared(stub_display_listener), | 305 | mt::fake_shared(stub_display_listener), |
701 | 303 | null_comp_report, false); | 306 | null_comp_report, default_delay, false); |
702 | 304 | 307 | ||
703 | 305 | mt_compositor.start(); | 308 | mt_compositor.start(); |
704 | 306 | stack.remove_surface(stub_surface); | 309 | stack.remove_surface(stub_surface); |
705 | @@ -322,7 +325,7 @@ | |||
706 | 322 | mt::fake_shared(stack), | 325 | mt::fake_shared(stack), |
707 | 323 | mt::fake_shared(dbc_factory), | 326 | mt::fake_shared(dbc_factory), |
708 | 324 | mt::fake_shared(stub_display_listener), | 327 | mt::fake_shared(stub_display_listener), |
710 | 325 | null_comp_report, false); | 328 | null_comp_report, default_delay, false); |
711 | 326 | 329 | ||
712 | 327 | mt_compositor.start(); | 330 | mt_compositor.start(); |
713 | 328 | stub_surface->primary_buffer_stream()->swap_buffers(&stubbuf, [](mg::Buffer*){}); | 331 | stub_surface->primary_buffer_stream()->swap_buffers(&stubbuf, [](mg::Buffer*){}); |
714 | 329 | 332 | ||
715 | === modified file 'tests/mir_test_doubles/mock_drm.cpp' | |||
716 | --- tests/mir_test_doubles/mock_drm.cpp 2015-06-25 03:00:08 +0000 | |||
717 | +++ tests/mir_test_doubles/mock_drm.cpp 2015-06-30 06:52:15 +0000 | |||
718 | @@ -216,6 +216,11 @@ | |||
719 | 216 | mode.clock = clock; | 216 | mode.clock = clock; |
720 | 217 | mode.htotal = htotal; | 217 | mode.htotal = htotal; |
721 | 218 | mode.vtotal = vtotal; | 218 | mode.vtotal = vtotal; |
722 | 219 | |||
723 | 220 | uint32_t total = htotal; | ||
724 | 221 | total *= vtotal; // extend to 32 bits | ||
725 | 222 | mode.vrefresh = clock * 1000UL / total; | ||
726 | 223 | |||
727 | 219 | if (preferred) | 224 | if (preferred) |
728 | 220 | mode.type |= DRM_MODE_TYPE_PREFERRED; | 225 | mode.type |= DRM_MODE_TYPE_PREFERRED; |
729 | 221 | 226 | ||
730 | 222 | 227 | ||
731 | === modified file 'tests/unit-tests/compositor/test_multi_threaded_compositor.cpp' | |||
732 | --- tests/unit-tests/compositor/test_multi_threaded_compositor.cpp 2015-06-25 03:00:08 +0000 | |||
733 | +++ tests/unit-tests/compositor/test_multi_threaded_compositor.cpp 2015-06-30 06:52:15 +0000 | |||
734 | @@ -83,6 +83,10 @@ | |||
735 | 83 | f(buffer); | 83 | f(buffer); |
736 | 84 | } | 84 | } |
737 | 85 | void post() override {} | 85 | void post() override {} |
738 | 86 | std::chrono::milliseconds recommended_sleep() const override | ||
739 | 87 | { | ||
740 | 88 | return std::chrono::milliseconds::zero(); | ||
741 | 89 | } | ||
742 | 86 | testing::NiceMock<mtd::MockDisplayBuffer> buffer; | 90 | testing::NiceMock<mtd::MockDisplayBuffer> buffer; |
743 | 87 | }; | 91 | }; |
744 | 88 | 92 | ||
745 | @@ -358,6 +362,7 @@ | |||
746 | 358 | auto const null_report = mr::null_compositor_report(); | 362 | auto const null_report = mr::null_compositor_report(); |
747 | 359 | unsigned int const composites_per_update{1}; | 363 | unsigned int const composites_per_update{1}; |
748 | 360 | auto const null_display_listener = std::make_shared<StubDisplayListener>(); | 364 | auto const null_display_listener = std::make_shared<StubDisplayListener>(); |
749 | 365 | std::chrono::milliseconds const default_delay{-1}; | ||
750 | 361 | 366 | ||
751 | 362 | } | 367 | } |
752 | 363 | 368 | ||
753 | @@ -370,7 +375,7 @@ | |||
754 | 370 | auto display = std::make_shared<mtd::StubDisplay>(nbuffers); | 375 | auto display = std::make_shared<mtd::StubDisplay>(nbuffers); |
755 | 371 | auto scene = std::make_shared<StubScene>(); | 376 | auto scene = std::make_shared<StubScene>(); |
756 | 372 | auto db_compositor_factory = std::make_shared<RecordingDisplayBufferCompositorFactory>(); | 377 | auto db_compositor_factory = std::make_shared<RecordingDisplayBufferCompositorFactory>(); |
758 | 373 | mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_display_listener, null_report, true}; | 378 | mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_display_listener, null_report, default_delay, true}; |
759 | 374 | 379 | ||
760 | 375 | compositor.start(); | 380 | compositor.start(); |
761 | 376 | 381 | ||
762 | @@ -396,6 +401,7 @@ | |||
763 | 396 | db_compositor_factory, | 401 | db_compositor_factory, |
764 | 397 | null_display_listener, | 402 | null_display_listener, |
765 | 398 | mock_report, | 403 | mock_report, |
766 | 404 | default_delay, | ||
767 | 399 | true}; | 405 | true}; |
768 | 400 | 406 | ||
769 | 401 | EXPECT_CALL(*mock_report, started()) | 407 | EXPECT_CALL(*mock_report, started()) |
770 | @@ -446,7 +452,7 @@ | |||
771 | 446 | auto display = std::make_shared<mtd::StubDisplay>(nbuffers); | 452 | auto display = std::make_shared<mtd::StubDisplay>(nbuffers); |
772 | 447 | auto scene = std::make_shared<StubScene>(); | 453 | auto scene = std::make_shared<StubScene>(); |
773 | 448 | auto db_compositor_factory = std::make_shared<RecordingDisplayBufferCompositorFactory>(); | 454 | auto db_compositor_factory = std::make_shared<RecordingDisplayBufferCompositorFactory>(); |
775 | 449 | mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_display_listener, null_report, true}; | 455 | mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_display_listener, null_report, default_delay, true}; |
776 | 450 | 456 | ||
777 | 451 | // Verify we're actually starting at zero frames | 457 | // Verify we're actually starting at zero frames |
778 | 452 | EXPECT_TRUE(db_compositor_factory->check_record_count_for_each_buffer(nbuffers, 0, 0)); | 458 | EXPECT_TRUE(db_compositor_factory->check_record_count_for_each_buffer(nbuffers, 0, 0)); |
779 | @@ -507,7 +513,7 @@ | |||
780 | 507 | auto scene = std::make_shared<StubScene>(); | 513 | auto scene = std::make_shared<StubScene>(); |
781 | 508 | auto factory = std::make_shared<RecordingDisplayBufferCompositorFactory>(); | 514 | auto factory = std::make_shared<RecordingDisplayBufferCompositorFactory>(); |
782 | 509 | mc::MultiThreadedCompositor compositor{display, scene, factory, | 515 | mc::MultiThreadedCompositor compositor{display, scene, factory, |
784 | 510 | null_display_listener, null_report, true}; | 516 | null_display_listener, null_report, default_delay, true}; |
785 | 511 | 517 | ||
786 | 512 | EXPECT_TRUE(factory->check_record_count_for_each_buffer(nbuffers, 0, 0)); | 518 | EXPECT_TRUE(factory->check_record_count_for_each_buffer(nbuffers, 0, 0)); |
787 | 513 | 519 | ||
788 | @@ -570,6 +576,58 @@ | |||
789 | 570 | compositor.stop(); | 576 | compositor.stop(); |
790 | 571 | } | 577 | } |
791 | 572 | 578 | ||
792 | 579 | TEST(MultiThreadedCompositor, recommended_sleep_throttles_compositor_loop) | ||
793 | 580 | { | ||
794 | 581 | using namespace testing; | ||
795 | 582 | using namespace std::chrono; | ||
796 | 583 | |||
797 | 584 | unsigned int const nbuffers = 3; | ||
798 | 585 | milliseconds const recommendation(10); | ||
799 | 586 | |||
800 | 587 | auto display = std::make_shared<mtd::StubDisplay>(nbuffers); | ||
801 | 588 | auto scene = std::make_shared<StubScene>(); | ||
802 | 589 | auto factory = std::make_shared<RecordingDisplayBufferCompositorFactory>(); | ||
803 | 590 | mc::MultiThreadedCompositor compositor{display, scene, factory, | ||
804 | 591 | null_display_listener, null_report, | ||
805 | 592 | recommendation, false}; | ||
806 | 593 | |||
807 | 594 | EXPECT_TRUE(factory->check_record_count_for_each_buffer(nbuffers, 0, 0)); | ||
808 | 595 | |||
809 | 596 | compositor.start(); | ||
810 | 597 | |||
811 | 598 | int const max_retries = 100; | ||
812 | 599 | int const nframes = 10; | ||
813 | 600 | auto start = system_clock::now(); | ||
814 | 601 | |||
815 | 602 | for (int frame = 1; frame <= nframes; ++frame) | ||
816 | 603 | { | ||
817 | 604 | scene->emit_change_event(); | ||
818 | 605 | |||
819 | 606 | int retry = 0; | ||
820 | 607 | while (retry < max_retries && | ||
821 | 608 | !factory->check_record_count_for_each_buffer(nbuffers, frame)) | ||
822 | 609 | { | ||
823 | 610 | std::this_thread::sleep_for(milliseconds(1)); | ||
824 | 611 | ++retry; | ||
825 | 612 | } | ||
826 | 613 | ASSERT_LT(retry, max_retries); | ||
827 | 614 | } | ||
828 | 615 | |||
829 | 616 | /* | ||
830 | 617 | * Detecting the throttling from outside the compositor thread is actually | ||
831 | 618 | * trickier than you think. Because the display buffer counter won't be | ||
832 | 619 | * delayed by the sleep; only the subsequent frame will be delayed. So | ||
833 | 620 | * that's why we measure overall duration here... | ||
834 | 621 | */ | ||
835 | 622 | auto duration = system_clock::now() - start; | ||
836 | 623 | // Minus 2 because the first won't be throttled, and the last not detected. | ||
837 | 624 | int minimum = recommendation.count() * (nframes - 2); | ||
838 | 625 | ASSERT_THAT(duration_cast<milliseconds>(duration).count(), | ||
839 | 626 | Ge(minimum)); | ||
840 | 627 | |||
841 | 628 | compositor.stop(); | ||
842 | 629 | } | ||
843 | 630 | |||
844 | 573 | TEST(MultiThreadedCompositor, when_no_initial_composite_is_needed_there_is_none) | 631 | TEST(MultiThreadedCompositor, when_no_initial_composite_is_needed_there_is_none) |
845 | 574 | { | 632 | { |
846 | 575 | using namespace testing; | 633 | using namespace testing; |
847 | @@ -579,7 +637,7 @@ | |||
848 | 579 | auto display = std::make_shared<mtd::StubDisplay>(nbuffers); | 637 | auto display = std::make_shared<mtd::StubDisplay>(nbuffers); |
849 | 580 | auto scene = std::make_shared<StubScene>(); | 638 | auto scene = std::make_shared<StubScene>(); |
850 | 581 | auto db_compositor_factory = std::make_shared<RecordingDisplayBufferCompositorFactory>(); | 639 | auto db_compositor_factory = std::make_shared<RecordingDisplayBufferCompositorFactory>(); |
852 | 582 | mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_display_listener, null_report, false}; | 640 | mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_display_listener, null_report, default_delay, false}; |
853 | 583 | 641 | ||
854 | 584 | // Verify we're actually starting at zero frames | 642 | // Verify we're actually starting at zero frames |
855 | 585 | ASSERT_TRUE(db_compositor_factory->check_record_count_for_each_buffer(nbuffers, 0, 0)); | 643 | ASSERT_TRUE(db_compositor_factory->check_record_count_for_each_buffer(nbuffers, 0, 0)); |
856 | @@ -602,7 +660,7 @@ | |||
857 | 602 | auto display = std::make_shared<mtd::StubDisplay>(nbuffers); | 660 | auto display = std::make_shared<mtd::StubDisplay>(nbuffers); |
858 | 603 | auto scene = std::make_shared<StubScene>(); | 661 | auto scene = std::make_shared<StubScene>(); |
859 | 604 | auto db_compositor_factory = std::make_shared<RecordingDisplayBufferCompositorFactory>(); | 662 | auto db_compositor_factory = std::make_shared<RecordingDisplayBufferCompositorFactory>(); |
861 | 605 | mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_display_listener, null_report, false}; | 663 | mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_display_listener, null_report, default_delay, false}; |
862 | 606 | 664 | ||
863 | 607 | compositor.start(); | 665 | compositor.start(); |
864 | 608 | 666 | ||
865 | @@ -637,7 +695,7 @@ | |||
866 | 637 | auto display = std::make_shared<mtd::StubDisplay>(nbuffers); | 695 | auto display = std::make_shared<mtd::StubDisplay>(nbuffers); |
867 | 638 | auto scene = std::make_shared<StubScene>(); | 696 | auto scene = std::make_shared<StubScene>(); |
868 | 639 | auto db_compositor_factory = std::make_shared<SurfaceUpdatingDisplayBufferCompositorFactory>(scene); | 697 | auto db_compositor_factory = std::make_shared<SurfaceUpdatingDisplayBufferCompositorFactory>(scene); |
870 | 640 | mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_display_listener, null_report, true}; | 698 | mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_display_listener, null_report, default_delay, true}; |
871 | 641 | 699 | ||
872 | 642 | compositor.start(); | 700 | compositor.start(); |
873 | 643 | 701 | ||
874 | @@ -656,7 +714,7 @@ | |||
875 | 656 | auto display = std::make_shared<StubDisplayWithMockBuffers>(nbuffers); | 714 | auto display = std::make_shared<StubDisplayWithMockBuffers>(nbuffers); |
876 | 657 | auto scene = std::make_shared<StubScene>(); | 715 | auto scene = std::make_shared<StubScene>(); |
877 | 658 | auto db_compositor_factory = std::make_shared<mtd::NullDisplayBufferCompositorFactory>(); | 716 | auto db_compositor_factory = std::make_shared<mtd::NullDisplayBufferCompositorFactory>(); |
879 | 659 | mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_display_listener, null_report, true}; | 717 | mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_display_listener, null_report, default_delay, true}; |
880 | 660 | 718 | ||
881 | 661 | display->for_each_mock_buffer([](mtd::MockDisplayBuffer& mock_buf) | 719 | display->for_each_mock_buffer([](mtd::MockDisplayBuffer& mock_buf) |
882 | 662 | { | 720 | { |
883 | @@ -692,7 +750,7 @@ | |||
884 | 692 | .Times(AtLeast(0)) | 750 | .Times(AtLeast(0)) |
885 | 693 | .WillRepeatedly(Return(mc::SceneElementSequence{})); | 751 | .WillRepeatedly(Return(mc::SceneElementSequence{})); |
886 | 694 | 752 | ||
888 | 695 | mc::MultiThreadedCompositor compositor{display, mock_scene, db_compositor_factory, null_display_listener, mock_report, true}; | 753 | mc::MultiThreadedCompositor compositor{display, mock_scene, db_compositor_factory, null_display_listener, mock_report, default_delay, true}; |
889 | 696 | 754 | ||
890 | 697 | compositor.start(); | 755 | compositor.start(); |
891 | 698 | compositor.start(); | 756 | compositor.start(); |
892 | @@ -707,7 +765,7 @@ | |||
893 | 707 | auto display = std::make_shared<StubDisplayWithMockBuffers>(nbuffers); | 765 | auto display = std::make_shared<StubDisplayWithMockBuffers>(nbuffers); |
894 | 708 | auto scene = std::make_shared<StubScene>(); | 766 | auto scene = std::make_shared<StubScene>(); |
895 | 709 | auto db_compositor_factory = std::make_shared<RecordingDisplayBufferCompositorFactory>(); | 767 | auto db_compositor_factory = std::make_shared<RecordingDisplayBufferCompositorFactory>(); |
897 | 710 | mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_display_listener, null_report, true}; | 768 | mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_display_listener, null_report, default_delay, true}; |
898 | 711 | 769 | ||
899 | 712 | scene->throw_on_add_observer(true); | 770 | scene->throw_on_add_observer(true); |
900 | 713 | 771 | ||
901 | @@ -757,7 +815,7 @@ | |||
902 | 757 | auto display = std::make_shared<StubDisplayWithMockBuffers>(nbuffers); | 815 | auto display = std::make_shared<StubDisplayWithMockBuffers>(nbuffers); |
903 | 758 | auto scene = std::make_shared<StubScene>(); | 816 | auto scene = std::make_shared<StubScene>(); |
904 | 759 | auto db_compositor_factory = std::make_shared<ThreadNameDisplayBufferCompositorFactory>(); | 817 | auto db_compositor_factory = std::make_shared<ThreadNameDisplayBufferCompositorFactory>(); |
906 | 760 | mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_display_listener, null_report, true}; | 818 | mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_display_listener, null_report, default_delay, true}; |
907 | 761 | 819 | ||
908 | 762 | compositor.start(); | 820 | compositor.start(); |
909 | 763 | 821 | ||
910 | @@ -786,7 +844,7 @@ | |||
911 | 786 | EXPECT_CALL(*mock_scene, register_compositor(_)) | 844 | EXPECT_CALL(*mock_scene, register_compositor(_)) |
912 | 787 | .Times(nbuffers); | 845 | .Times(nbuffers); |
913 | 788 | mc::MultiThreadedCompositor compositor{ | 846 | mc::MultiThreadedCompositor compositor{ |
915 | 789 | display, mock_scene, db_compositor_factory, null_display_listener, mock_report, true}; | 847 | display, mock_scene, db_compositor_factory, null_display_listener, mock_report, default_delay, true}; |
916 | 790 | 848 | ||
917 | 791 | compositor.start(); | 849 | compositor.start(); |
918 | 792 | 850 | ||
919 | @@ -809,7 +867,7 @@ | |||
920 | 809 | auto mock_report = std::make_shared<testing::NiceMock<mtd::MockCompositorReport>>(); | 867 | auto mock_report = std::make_shared<testing::NiceMock<mtd::MockCompositorReport>>(); |
921 | 810 | 868 | ||
922 | 811 | mc::MultiThreadedCompositor compositor{ | 869 | mc::MultiThreadedCompositor compositor{ |
924 | 812 | display, stub_scene, db_compositor_factory, mock_display_listener, mock_report, true}; | 870 | display, stub_scene, db_compositor_factory, mock_display_listener, mock_report, default_delay, true}; |
925 | 813 | 871 | ||
926 | 814 | EXPECT_CALL(*mock_display_listener, add_display(_)).Times(nbuffers); | 872 | EXPECT_CALL(*mock_display_listener, add_display(_)).Times(nbuffers); |
927 | 815 | 873 | ||
928 | @@ -840,7 +898,7 @@ | |||
929 | 840 | [&] { signal(SIGTERM, old_sigterm_handler); }); | 898 | [&] { signal(SIGTERM, old_sigterm_handler); }); |
930 | 841 | 899 | ||
931 | 842 | mc::MultiThreadedCompositor compositor{ | 900 | mc::MultiThreadedCompositor compositor{ |
933 | 843 | display, stub_scene, db_compositor_factory, mock_display_listener, mock_report, true}; | 901 | display, stub_scene, db_compositor_factory, mock_display_listener, mock_report, default_delay, true}; |
934 | 844 | 902 | ||
935 | 845 | EXPECT_CALL(*mock_display_listener, add_display(_)) | 903 | EXPECT_CALL(*mock_display_listener, add_display(_)) |
936 | 846 | .WillRepeatedly(Throw(std::runtime_error("Failed to add display"))); | 904 | .WillRepeatedly(Throw(std::runtime_error("Failed to add display"))); |
937 | 847 | 905 | ||
938 | === modified file 'tests/unit-tests/graphics/android/test_fb_device.cpp' | |||
939 | --- tests/unit-tests/graphics/android/test_fb_device.cpp 2015-06-25 03:00:08 +0000 | |||
940 | +++ tests/unit-tests/graphics/android/test_fb_device.cpp 2015-06-30 06:52:15 +0000 | |||
941 | @@ -100,6 +100,9 @@ | |||
942 | 100 | }, std::runtime_error); | 100 | }, std::runtime_error); |
943 | 101 | 101 | ||
944 | 102 | fbdev.commit({content}); | 102 | fbdev.commit({content}); |
945 | 103 | |||
946 | 104 | // Predictive bypass not enabled in FBDevice | ||
947 | 105 | EXPECT_EQ(0, fbdev.recommended_sleep().count()); | ||
948 | 103 | } | 106 | } |
949 | 104 | 107 | ||
950 | 105 | //not all fb devices provide a swap interval hook. make sure we don't explode if thats the case | 108 | //not all fb devices provide a swap interval hook. make sure we don't explode if thats the case |
951 | 106 | 109 | ||
952 | === modified file 'tests/unit-tests/graphics/android/test_hwc_device.cpp' | |||
953 | --- tests/unit-tests/graphics/android/test_hwc_device.cpp 2015-06-25 03:00:08 +0000 | |||
954 | +++ tests/unit-tests/graphics/android/test_hwc_device.cpp 2015-06-30 06:52:15 +0000 | |||
955 | @@ -439,6 +439,49 @@ | |||
956 | 439 | EXPECT_THAT(stub_buffer1.use_count(), Eq(use_count_before)); | 439 | EXPECT_THAT(stub_buffer1.use_count(), Eq(use_count_before)); |
957 | 440 | } | 440 | } |
958 | 441 | 441 | ||
959 | 442 | TEST_F(HwcDevice, overlays_are_throttled_per_predictive_bypass) | ||
960 | 443 | { | ||
961 | 444 | using namespace testing; | ||
962 | 445 | EXPECT_CALL(*mock_device, prepare(_)) | ||
963 | 446 | .WillRepeatedly(Invoke(set_all_layers_to_overlay)); | ||
964 | 447 | |||
965 | 448 | mga::HwcDevice device(mock_device); | ||
966 | 449 | |||
967 | 450 | mga::LayerList list(layer_adapter, {stub_renderable1}, {0,0}); | ||
968 | 451 | mga::DisplayContents content{primary, list, offset, stub_context, | ||
969 | 452 | stub_compositor}; | ||
970 | 453 | |||
971 | 454 | for (int frame = 0; frame < 5; ++frame) | ||
972 | 455 | { | ||
973 | 456 | device.commit({content}); | ||
974 | 457 | ASSERT_THAT(device.recommended_sleep().count(), Ge(8)); | ||
975 | 458 | } | ||
976 | 459 | } | ||
977 | 460 | |||
978 | 461 | TEST_F(HwcDevice, compositing_disables_predictive_bypass) | ||
979 | 462 | { | ||
980 | 463 | using namespace testing; | ||
981 | 464 | |||
982 | 465 | NiceMock<mtd::MockSwappingGLContext> mock_context; | ||
983 | 466 | ON_CALL(mock_context, last_rendered_buffer()) | ||
984 | 467 | .WillByDefault(Return(stub_fb_buffer)); | ||
985 | 468 | EXPECT_CALL(mock_context, swap_buffers()) | ||
986 | 469 | .Times(AtLeast(5)); | ||
987 | 470 | |||
988 | 471 | mga::LayerList list(layer_adapter, {}, geom::Displacement{}); | ||
989 | 472 | mtd::MockRenderableListCompositor mock_compositor; | ||
990 | 473 | mga::DisplayContents content{primary, list, offset, mock_context, | ||
991 | 474 | mock_compositor}; | ||
992 | 475 | |||
993 | 476 | mga::HwcDevice device(mock_device); | ||
994 | 477 | device.commit({content}); | ||
995 | 478 | for (int frame = 0; frame < 5; ++frame) | ||
996 | 479 | { | ||
997 | 480 | device.commit({content}); | ||
998 | 481 | ASSERT_EQ(0, device.recommended_sleep().count()); | ||
999 | 482 | } | ||
1000 | 483 | } | ||
1001 | 484 | |||
1002 | 442 | TEST_F(HwcDevice, does_not_set_acquirefences_when_it_has_set_them_previously_without_update) | 485 | TEST_F(HwcDevice, does_not_set_acquirefences_when_it_has_set_them_previously_without_update) |
1003 | 443 | { | 486 | { |
1004 | 444 | using namespace testing; | 487 | using namespace testing; |
1005 | 445 | 488 | ||
1006 | === modified file 'tests/unit-tests/graphics/android/test_hwc_fb_device.cpp' | |||
1007 | --- tests/unit-tests/graphics/android/test_hwc_fb_device.cpp 2015-06-25 03:00:08 +0000 | |||
1008 | +++ tests/unit-tests/graphics/android/test_hwc_fb_device.cpp 2015-06-30 06:52:15 +0000 | |||
1009 | @@ -174,4 +174,7 @@ | |||
1010 | 174 | 174 | ||
1011 | 175 | mga::DisplayContents content{primary, list, geom::Displacement{}, mock_context, stub_compositor}; | 175 | mga::DisplayContents content{primary, list, geom::Displacement{}, mock_context, stub_compositor}; |
1012 | 176 | device.commit({content}); | 176 | device.commit({content}); |
1013 | 177 | |||
1014 | 178 | // Predictive bypass not enabled in HwcFbDevice | ||
1015 | 179 | EXPECT_EQ(0, device.recommended_sleep().count()); | ||
1016 | 177 | } | 180 | } |
1017 | 178 | 181 | ||
1018 | === modified file 'tests/unit-tests/graphics/mesa/kms/mock_kms_output.h' | |||
1019 | --- tests/unit-tests/graphics/mesa/kms/mock_kms_output.h 2015-06-17 05:20:42 +0000 | |||
1020 | +++ tests/unit-tests/graphics/mesa/kms/mock_kms_output.h 2015-06-30 06:52:15 +0000 | |||
1021 | @@ -32,6 +32,7 @@ | |||
1022 | 32 | MOCK_METHOD0(reset, void()); | 32 | MOCK_METHOD0(reset, void()); |
1023 | 33 | MOCK_METHOD2(configure, void(geometry::Displacement, size_t)); | 33 | MOCK_METHOD2(configure, void(geometry::Displacement, size_t)); |
1024 | 34 | MOCK_CONST_METHOD0(size, geometry::Size()); | 34 | MOCK_CONST_METHOD0(size, geometry::Size()); |
1025 | 35 | MOCK_CONST_METHOD0(max_refresh_rate, int()); | ||
1026 | 35 | 36 | ||
1027 | 36 | MOCK_METHOD1(set_crtc, bool(uint32_t)); | 37 | MOCK_METHOD1(set_crtc, bool(uint32_t)); |
1028 | 37 | MOCK_METHOD0(clear_crtc, void()); | 38 | MOCK_METHOD0(clear_crtc, void()); |
1029 | 38 | 39 | ||
1030 | === modified file 'tests/unit-tests/graphics/mesa/kms/test_display_buffer.cpp' | |||
1031 | --- tests/unit-tests/graphics/mesa/kms/test_display_buffer.cpp 2015-06-25 03:00:08 +0000 | |||
1032 | +++ tests/unit-tests/graphics/mesa/kms/test_display_buffer.cpp 2015-06-30 06:52:15 +0000 | |||
1033 | @@ -46,6 +46,8 @@ | |||
1034 | 46 | class MesaDisplayBufferTest : public Test | 46 | class MesaDisplayBufferTest : public Test |
1035 | 47 | { | 47 | { |
1036 | 48 | public: | 48 | public: |
1037 | 49 | int const mock_refresh_rate = 60; | ||
1038 | 50 | |||
1039 | 49 | MesaDisplayBufferTest() | 51 | MesaDisplayBufferTest() |
1040 | 50 | : mock_bypassable_buffer{std::make_shared<NiceMock<MockBuffer>>()} | 52 | : mock_bypassable_buffer{std::make_shared<NiceMock<MockBuffer>>()} |
1041 | 51 | , fake_bypassable_renderable{ | 53 | , fake_bypassable_renderable{ |
1042 | @@ -78,6 +80,8 @@ | |||
1043 | 78 | .WillByDefault(Return(true)); | 80 | .WillByDefault(Return(true)); |
1044 | 79 | ON_CALL(*mock_kms_output, schedule_page_flip(_)) | 81 | ON_CALL(*mock_kms_output, schedule_page_flip(_)) |
1045 | 80 | .WillByDefault(Return(true)); | 82 | .WillByDefault(Return(true)); |
1046 | 83 | ON_CALL(*mock_kms_output, max_refresh_rate()) | ||
1047 | 84 | .WillByDefault(Return(mock_refresh_rate)); | ||
1048 | 81 | 85 | ||
1049 | 82 | ON_CALL(*mock_bypassable_buffer, size()) | 86 | ON_CALL(*mock_bypassable_buffer, size()) |
1050 | 83 | .WillByDefault(Return(display_area.size)); | 87 | .WillByDefault(Return(display_area.size)); |
1051 | @@ -154,6 +158,56 @@ | |||
1052 | 154 | EXPECT_EQ(original_count, mock_bypassable_buffer.use_count()); | 158 | EXPECT_EQ(original_count, mock_bypassable_buffer.use_count()); |
1053 | 155 | } | 159 | } |
1054 | 156 | 160 | ||
1055 | 161 | TEST_F(MesaDisplayBufferTest, predictive_bypass_is_throttled) | ||
1056 | 162 | { | ||
1057 | 163 | graphics::mesa::DisplayBuffer db( | ||
1058 | 164 | create_platform(), | ||
1059 | 165 | null_display_report(), | ||
1060 | 166 | {mock_kms_output}, | ||
1061 | 167 | nullptr, | ||
1062 | 168 | display_area, | ||
1063 | 169 | mir_orientation_normal, | ||
1064 | 170 | gl_config, | ||
1065 | 171 | mock_egl.fake_egl_context); | ||
1066 | 172 | |||
1067 | 173 | for (int frame = 0; frame < 5; ++frame) | ||
1068 | 174 | { | ||
1069 | 175 | ASSERT_TRUE(db.post_renderables_if_optimizable(bypassable_list)); | ||
1070 | 176 | db.post(); | ||
1071 | 177 | |||
1072 | 178 | // Cast to a simple int type so that test failures are readable | ||
1073 | 179 | int milliseconds_per_frame = 1000 / mock_refresh_rate; | ||
1074 | 180 | ASSERT_THAT(db.recommended_sleep().count(), | ||
1075 | 181 | Ge(milliseconds_per_frame/2)); | ||
1076 | 182 | } | ||
1077 | 183 | } | ||
1078 | 184 | |||
1079 | 185 | TEST_F(MesaDisplayBufferTest, frames_requiring_gl_are_not_throttled) | ||
1080 | 186 | { | ||
1081 | 187 | graphics::RenderableList non_bypassable_list{ | ||
1082 | 188 | std::make_shared<FakeRenderable>(geometry::Rectangle{{12, 34}, {1, 1}}) | ||
1083 | 189 | }; | ||
1084 | 190 | |||
1085 | 191 | graphics::mesa::DisplayBuffer db( | ||
1086 | 192 | create_platform(), | ||
1087 | 193 | null_display_report(), | ||
1088 | 194 | {mock_kms_output}, | ||
1089 | 195 | nullptr, | ||
1090 | 196 | display_area, | ||
1091 | 197 | mir_orientation_normal, | ||
1092 | 198 | gl_config, | ||
1093 | 199 | mock_egl.fake_egl_context); | ||
1094 | 200 | |||
1095 | 201 | for (int frame = 0; frame < 5; ++frame) | ||
1096 | 202 | { | ||
1097 | 203 | ASSERT_FALSE(db.post_renderables_if_optimizable(non_bypassable_list)); | ||
1098 | 204 | db.post(); | ||
1099 | 205 | |||
1100 | 206 | // Cast to a simple int type so that test failures are readable | ||
1101 | 207 | ASSERT_EQ(0, db.recommended_sleep().count()); | ||
1102 | 208 | } | ||
1103 | 209 | } | ||
1104 | 210 | |||
1105 | 157 | TEST_F(MesaDisplayBufferTest, bypass_buffer_only_referenced_once_by_db) | 211 | TEST_F(MesaDisplayBufferTest, bypass_buffer_only_referenced_once_by_db) |
1106 | 158 | { | 212 | { |
1107 | 159 | graphics::mesa::DisplayBuffer db( | 213 | graphics::mesa::DisplayBuffer db( |
1108 | 160 | 214 | ||
1109 | === modified file 'tests/unit-tests/graphics/nested/test_nested_display.cpp' | |||
1110 | --- tests/unit-tests/graphics/nested/test_nested_display.cpp 2015-06-25 13:43:43 +0000 | |||
1111 | +++ tests/unit-tests/graphics/nested/test_nested_display.cpp 2015-06-30 06:52:15 +0000 | |||
1112 | @@ -167,6 +167,23 @@ | |||
1113 | 167 | EXPECT_FALSE(weak_platform.expired()); | 167 | EXPECT_FALSE(weak_platform.expired()); |
1114 | 168 | } | 168 | } |
1115 | 169 | 169 | ||
1116 | 170 | TEST_F(NestedDisplay, never_enables_predictive_bypass) | ||
1117 | 171 | { // This test can be removed after it's implemented (after nested bypass) | ||
1118 | 172 | auto const nested_display = create_nested_display( | ||
1119 | 173 | null_platform, | ||
1120 | 174 | mt::fake_shared(stub_gl_config)); | ||
1121 | 175 | |||
1122 | 176 | int groups = 0; | ||
1123 | 177 | nested_display->for_each_display_sync_group( | ||
1124 | 178 | [&groups](mg::DisplaySyncGroup& g) | ||
1125 | 179 | { | ||
1126 | 180 | EXPECT_EQ(0, g.recommended_sleep().count()); | ||
1127 | 181 | ++groups; | ||
1128 | 182 | } | ||
1129 | 183 | ); | ||
1130 | 184 | |||
1131 | 185 | ASSERT_NE(0, groups); | ||
1132 | 186 | } | ||
1133 | 170 | 187 | ||
1134 | 171 | TEST_F(NestedDisplay, makes_context_current_on_creation_and_releases_on_destruction) | 188 | TEST_F(NestedDisplay, makes_context_current_on_creation_and_releases_on_destruction) |
1135 | 172 | { | 189 | { |
1136 | 173 | 190 | ||
1137 | === modified file 'tests/unit-tests/graphics/offscreen/test_offscreen_display.cpp' | |||
1138 | --- tests/unit-tests/graphics/offscreen/test_offscreen_display.cpp 2015-06-25 13:43:43 +0000 | |||
1139 | +++ tests/unit-tests/graphics/offscreen/test_offscreen_display.cpp 2015-06-30 06:52:15 +0000 | |||
1140 | @@ -78,6 +78,22 @@ | |||
1141 | 78 | EXPECT_TRUE(count); | 78 | EXPECT_TRUE(count); |
1142 | 79 | } | 79 | } |
1143 | 80 | 80 | ||
1144 | 81 | TEST_F(OffscreenDisplayTest, never_enables_predictive_bypass) | ||
1145 | 82 | { | ||
1146 | 83 | mgo::Display display{ | ||
1147 | 84 | native_display, | ||
1148 | 85 | std::make_shared<mg::CloneDisplayConfigurationPolicy>(), | ||
1149 | 86 | mr::null_display_report()}; | ||
1150 | 87 | |||
1151 | 88 | int groups = 0; | ||
1152 | 89 | display.for_each_display_sync_group([&](mg::DisplaySyncGroup& group){ | ||
1153 | 90 | ++groups; | ||
1154 | 91 | EXPECT_EQ(0, group.recommended_sleep().count()); | ||
1155 | 92 | }); | ||
1156 | 93 | |||
1157 | 94 | EXPECT_TRUE(groups); | ||
1158 | 95 | } | ||
1159 | 96 | |||
1160 | 81 | TEST_F(OffscreenDisplayTest, makes_fbo_current_rendering_target) | 97 | TEST_F(OffscreenDisplayTest, makes_fbo_current_rendering_target) |
1161 | 82 | { | 98 | { |
1162 | 83 | using namespace ::testing; | 99 | using namespace ::testing; |
PASSED: Continuous integration, rev:2688 jenkins. qa.ubuntu. com/job/ mir-ci/ 4224/ jenkins. qa.ubuntu. com/job/ mir-android- vivid-i386- build/3040 jenkins. qa.ubuntu. com/job/ mir-clang- wily-amd64- build/560 jenkins. qa.ubuntu. com/job/ mir-mediumtests -vivid- touch/2988 jenkins. qa.ubuntu. com/job/ mir-wily- amd64-ci/ 380 jenkins. qa.ubuntu. com/job/ mir-wily- amd64-ci/ 380/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ mir-mediumtests -builder- vivid-armhf/ 2988 jenkins. qa.ubuntu. com/job/ mir-mediumtests -builder- vivid-armhf/ 2988/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ mir-mediumtests -runner- mako/5801 s-jenkins. ubuntu- ci:8080/ job/touch- flash-device/ 21537
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/mir- ci/4224/ rebuild
http://