Mir

Merge lp:~vanvugt/mir/judder into lp:mir

Proposed by Daniel van Vugt
Status: Work in progress
Proposed branch: lp:~vanvugt/mir/judder
Merge into: lp:mir
Diff against target: 297 lines (+166/-35)
3 files modified
src/server/compositor/multi_threaded_compositor.cpp (+29/-26)
tests/acceptance-tests/test_client_surface_swap_buffers.cpp (+3/-4)
tests/unit-tests/compositor/test_multi_threaded_compositor.cpp (+134/-5)
To merge this branch: bzr merge lp:~vanvugt/mir/judder
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Needs Fixing
Alexandros Frantzis Pending
Alan Griffiths Pending
Mir development team Pending
Review via email: mp+221276@code.launchpad.net

Commit message

Fix frame skipping/stealing/beating regressions (LP: #1308843) by ensuring
that fake frames don't occur if real frames are being composited.

This also modifies minimum buffer consumption to drop to 10Hz if compositors
block, ensuring clients always get their frames consumed in a definite time.
And it also happens to solve LP: #1308844.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~vanvugt/mir/judder updated
1586. By Daniel van Vugt

Merge latest development-branch

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~vanvugt/mir/judder updated
1587. By Daniel van Vugt

Merge latest development-branch

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)

Unmerged revisions

1587. By Daniel van Vugt

Merge latest development-branch

1586. By Daniel van Vugt

Merge latest development-branch

1585. By Daniel van Vugt

Adjust sleep to yield 10Hz client wakeups

1584. By Daniel van Vugt

Tidy up

1583. By Daniel van Vugt

Fix missing brace, FTBFS

1582. By Daniel van Vugt

Merge latest development-branch

1581. By Daniel van Vugt

Clean up

1580. By Daniel van Vugt

Remember to set the continuation flag correctly... even on frames when
we're not consuming frames.

1579. By Daniel van Vugt

Remember to keep fake compositing as required

1578. By Daniel van Vugt

Remove more tabs

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/server/compositor/multi_threaded_compositor.cpp'
2--- src/server/compositor/multi_threaded_compositor.cpp 2014-05-29 15:51:54 +0000
3+++ src/server/compositor/multi_threaded_compositor.cpp 2014-05-30 08:03:29 +0000
4@@ -32,6 +32,7 @@
5 #include <condition_variable>
6 #include <cstdint>
7 #include <chrono>
8+#include <atomic>
9
10 namespace mc = mir::compositor;
11 namespace mg = mir::graphics;
12@@ -86,6 +87,8 @@
13 mg::DisplayBuffer& buffer;
14 };
15
16+std::atomic<int> real_frames(0);
17+
18 class CompositingFunctor
19 {
20 public:
21@@ -180,7 +183,12 @@
22 r.top_left.x.as_int(), r.top_left.y.as_int(),
23 report_id);
24
25- run_compositing_loop([&] { return display_buffer_compositor->composite();});
26+ run_compositing_loop([&]
27+ {
28+ bool ret = display_buffer_compositor->composite();
29+ real_frames.fetch_add(1);
30+ return ret;
31+ });
32 }
33 catch (...)
34 {
35@@ -207,19 +215,31 @@
36 run_compositing_loop(
37 [this]
38 {
39- std::vector<std::shared_ptr<mg::Buffer>> saved_resources;
40-
41+ int old_real_frames = real_frames;
42+
43+ // Emulate a moderately slow fake display.
44+ // Note that due to feedback from consuming a buffer,
45+ // this will trigger alternation between consuming a real
46+ // and fake frame. So sleeping 50ms here actually results
47+ // in a client frame rate of 10Hz.
48+
49+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
50+ bool consumed = real_frames != old_real_frames;
51+
52+ int more = 0;
53 auto const& renderables = scene->renderable_list_for(this);
54-
55 for (auto const& r : renderables)
56 {
57- if (r->buffers_ready_for_compositor() > 0)
58- saved_resources.push_back(r->buffer());
59+ int ready = r->buffers_ready_for_compositor();
60+ more += ready;
61+
62+ // Now only composite a fake frame if no real display has
63+ // composited during the sleep.
64+ if (ready && !consumed)
65+ (void)r->buffer();
66 }
67
68- wait_until_next_fake_vsync();
69-
70- return false;
71+ return more > 0;
72 });
73 }
74 catch (...)
75@@ -228,23 +248,6 @@
76 }
77
78 private:
79- void wait_until_next_fake_vsync()
80- {
81- using namespace std::chrono;
82- typedef duration<int64_t, std::ratio<1, 60>> vsync_periods;
83-
84- /* Truncate to vsync periods */
85- auto const previous_vsync =
86- duration_cast<vsync_periods>(steady_clock::now().time_since_epoch());
87- /* Convert back to a timepoint */
88- auto const previous_vsync_tp =
89- time_point<steady_clock, vsync_periods>{previous_vsync};
90- /* Next vsync time point */
91- auto const next_vsync = previous_vsync_tp + vsync_periods(1);
92-
93- std::this_thread::sleep_until(next_vsync);
94- }
95-
96 std::shared_ptr<mc::Scene> const scene;
97 };
98
99
100=== modified file 'tests/acceptance-tests/test_client_surface_swap_buffers.cpp'
101--- tests/acceptance-tests/test_client_surface_swap_buffers.cpp 2014-05-29 15:51:54 +0000
102+++ tests/acceptance-tests/test_client_surface_swap_buffers.cpp 2014-05-30 08:03:29 +0000
103@@ -21,7 +21,7 @@
104 #include "mir_test_framework/stubbed_server_configuration.h"
105 #include "mir_test_framework/in_process_server.h"
106 #include "mir_test_framework/using_stub_client_platform.h"
107-#include "mir_test_doubles/null_display_buffer_compositor_factory.h"
108+#include "mir_test_doubles/null_display.h"
109 #include "mir_test/spin_wait.h"
110
111 #include <gtest/gtest.h>
112@@ -37,10 +37,9 @@
113
114 struct StubServerConfig : mtf::StubbedServerConfiguration
115 {
116- auto the_display_buffer_compositor_factory()
117- -> std::shared_ptr<mc::DisplayBufferCompositorFactory> override
118+ std::shared_ptr<mir::graphics::Display> the_display()
119 {
120- return std::make_shared<mtd::NullDisplayBufferCompositorFactory>();
121+ return std::make_shared<mtd::NullDisplay>();
122 }
123 };
124
125
126=== modified file 'tests/unit-tests/compositor/test_multi_threaded_compositor.cpp'
127--- tests/unit-tests/compositor/test_multi_threaded_compositor.cpp 2014-05-29 15:51:54 +0000
128+++ tests/unit-tests/compositor/test_multi_threaded_compositor.cpp 2014-05-30 08:03:29 +0000
129@@ -150,6 +150,55 @@
130 bool throw_on_add_observer_;
131 };
132
133+class StubDisplayBufferCompositor : public mc::DisplayBufferCompositor
134+{
135+public:
136+ StubDisplayBufferCompositor(std::shared_ptr<mc::Scene> scene,
137+ std::chrono::milliseconds frame_time)
138+ : scene{scene}, frame_time{frame_time}
139+ {
140+ }
141+
142+ bool composite() override
143+ {
144+ auto const& all = scene->renderable_list_for(this);
145+ for (auto const& r : all)
146+ r->buffer(); // Consume a frame
147+
148+ std::this_thread::sleep_for(frame_time);
149+ return false;
150+ }
151+
152+private:
153+ std::shared_ptr<mc::Scene> const scene;
154+ std::chrono::milliseconds const frame_time;
155+};
156+
157+
158+class StubDisplayBufferCompositorFactory :
159+ public mc::DisplayBufferCompositorFactory
160+{
161+public:
162+ StubDisplayBufferCompositorFactory(std::shared_ptr<mc::Scene> scene,
163+ int hz)
164+ : scene{scene},
165+ frame_time{std::chrono::milliseconds(1000 / hz)}
166+ {
167+ }
168+
169+ std::unique_ptr<mc::DisplayBufferCompositor>
170+ create_compositor_for(mg::DisplayBuffer&) override
171+ {
172+ std::unique_ptr<mc::DisplayBufferCompositor> ret(
173+ new StubDisplayBufferCompositor(scene, frame_time));
174+ return ret;
175+ }
176+
177+private:
178+ std::shared_ptr<mc::Scene> const scene;
179+ std::chrono::milliseconds const frame_time;
180+};
181+
182 class RecordingDisplayBufferCompositor : public mc::DisplayBufferCompositor
183 {
184 public:
185@@ -323,7 +372,7 @@
186 class BufferCountingRenderable : public mtd::StubRenderable
187 {
188 public:
189- BufferCountingRenderable(RenderableVisibility visibility)
190+ BufferCountingRenderable(RenderableVisibility visibility = RenderableVisibility::visible)
191 : buffers_requested_{0}, visibility{visibility}
192 {
193 }
194@@ -627,12 +676,12 @@
195 {
196 using namespace testing;
197
198- unsigned int const nbuffers{2};
199 auto renderable = std::make_shared<BufferCountingRenderable>(GetParam());
200- auto display = std::make_shared<StubDisplay>(nbuffers);
201+
202+ // Defining zero outputs is the simplest way to emulate what would
203+ // happen if all your displays were blocked in swapping...
204+ auto display = std::make_shared<StubDisplay>(0);
205 auto stub_scene = std::make_shared<StubScene>(mg::RenderableList{renderable});
206- // We use NullDisplayBufferCompositors to simulate DisplayBufferCompositors
207- // not rendering a renderable.
208 auto db_compositor_factory = std::make_shared<mtd::NullDisplayBufferCompositorFactory>();
209
210 mc::MultiThreadedCompositor compositor{
211@@ -657,6 +706,86 @@
212 compositor.stop();
213 }
214
215+TEST(MultiThreadedCompositor, never_steals_frames_from_real_displays)
216+{
217+ /*
218+ * Verify dummy frames are consumed slower than any physical display would
219+ * consume them, so that the two types of consumers don't race and don't
220+ * visibly skip (LP: #1308843)
221+ */
222+ using namespace testing;
223+
224+ unsigned int const nbuffers{2};
225+ auto renderable = std::make_shared<BufferCountingRenderable>();
226+ auto display = std::make_shared<StubDisplay>(nbuffers);
227+ auto stub_scene = std::make_shared<StubScene>(mg::RenderableList{renderable});
228+ // We use NullDisplayBufferCompositors to simulate DisplayBufferCompositors
229+ // not rendering a renderable.
230+ auto db_compositor_factory = std::make_shared<mtd::NullDisplayBufferCompositorFactory>();
231+
232+ mc::MultiThreadedCompositor compositor{
233+ display, stub_scene, db_compositor_factory, null_report, true};
234+
235+ compositor.start();
236+
237+ // Realistically I've only ever seen LCDs go as low as 40Hz. But some TV
238+ // outputs will go as low as 24Hz (traditional movie frame rate). So we
239+ // need to ensure the dummy consumer is slower than that...
240+ int const min_real_refresh_rate = 20;
241+
242+ int const secs = 5;
243+ auto const duration = std::chrono::seconds{secs};
244+ mir::test::spin_wait_for_condition_or_timeout(
245+ [&] { return renderable->buffers_requested() <= 1; },
246+ duration);
247+
248+ auto const end = std::chrono::steady_clock::now() + duration;
249+ while (std::chrono::steady_clock::now() < end)
250+ {
251+ stub_scene->emit_change_event();
252+ std::this_thread::yield();
253+ }
254+
255+ int const max_dummy_frames = min_real_refresh_rate * secs;
256+ ASSERT_GT(max_dummy_frames, renderable->buffers_requested());
257+
258+ compositor.stop();
259+}
260+
261+TEST(MultiThreadedCompositor, only_real_displays_limit_consumption_rate)
262+{
263+ using namespace testing;
264+
265+ auto display = std::make_shared<StubDisplay>(1);
266+ auto renderable = std::make_shared<BufferCountingRenderable>();
267+ auto stub_scene = std::make_shared<StubScene>(mg::RenderableList{renderable});
268+
269+ int const framerate = 75; // Simulate a nice fast frame rate
270+ auto db_compositor_factory =
271+ std::make_shared<StubDisplayBufferCompositorFactory>(stub_scene,
272+ framerate);
273+
274+ mc::MultiThreadedCompositor compositor{
275+ display, stub_scene, db_compositor_factory, null_report, false};
276+
277+ compositor.start();
278+
279+ int const secs = 5;
280+ auto const duration = std::chrono::seconds{secs};
281+ auto const end = std::chrono::steady_clock::now() + duration;
282+ while (std::chrono::steady_clock::now() < end)
283+ {
284+ stub_scene->emit_change_event();
285+ std::this_thread::yield();
286+ }
287+
288+ int const expected_frames = framerate * secs;
289+ ASSERT_GT(expected_frames * 1.2f, renderable->buffers_requested());
290+ ASSERT_LT(expected_frames * 0.8f, renderable->buffers_requested());
291+
292+ compositor.stop();
293+}
294+
295 INSTANTIATE_TEST_CASE_P(
296 MultiThreadedCompositor, BufferConsumption,
297 ::testing::Values(RenderableVisibility::hidden, RenderableVisibility::visible));

Subscribers

People subscribed via source and target branches