Mir

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

Proposed by Daniel van Vugt
Status: Superseded
Proposed branch: lp:~vanvugt/mir/judder
Merge into: lp:mir
Diff against target: 276 lines (+156/-35)
3 files modified
src/server/compositor/multi_threaded_compositor.cpp (+20/-27)
tests/acceptance-tests/test_client_surface_swap_buffers.cpp (+3/-4)
tests/unit-tests/compositor/test_multi_threaded_compositor.cpp (+133/-4)
To merge this branch: bzr merge lp:~vanvugt/mir/judder
Reviewer Review Type Date Requested Status
Alan Griffiths Disapprove
PS Jenkins bot (community) continuous-integration Approve
Daniel van Vugt Abstain
Alexandros Frantzis (community) Needs Information
Review via email: mp+216694@code.launchpad.net

This proposal has been superseded by a proposal from 2014-05-28.

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 happens to solve LP: #1308844.

To post a comment you must log in.
Revision history for this message
Daniel van Vugt (vanvugt) wrote :

There's an intermittent failure in MirSurfaceSwapBuffersTest.swap_buffers_does_not_block_when_surface_is_not_composited due to stub class changes in r1567. That needs fixing.

review: Needs Fixing
Revision history for this message
Alexandros Frantzis (afrantzis) wrote :

This reintroduces eglSwapBuffers() blocking for buffers that are not rendered by the real compositors (i.e. for surfaces that are either occluded or off screen). That is, it only keeps eglSwapBuffers() running when all monitors are off. This can't be a long term solution if we want eglSwapBuffers() to be non-blocking in general, although perhaps it can serve as a workaround for our Qt 5.2 issues.

For a proper solution we need a more fine-grained approach, making decisions per renderable. Chris' approach https://code.launchpad.net/~raof/mir/1hz-rendering-always/+merge/216246 does this an the SwitchingBundle level, but we can achieve the same results at the MultiThreadedCompositor level. I will work on a proposal for that and we can decided on the best approach.

review: Needs Information
Revision history for this message
Alexandros Frantzis (afrantzis) wrote :

> Chris' approach https://code.launchpad.net/~raof/mir/1hz-rendering-always/+merge/216246
> does this an the SwitchingBundle level, but we can achieve the same results at the
> MultiThreadedCompositor level

You can find it here: https://code.launchpad.net/~afrantzis/mir/consume-only-not-rendered-buffers/+merge/216725

Revision history for this message
Daniel van Vugt (vanvugt) wrote :

Alexandros:

I know. Hence https://code.launchpad.net/~vanvugt/mir/unocclude/+merge/216690

I think these two proposals are both safer and more elegant than the 1Hz branch.

lp:~vanvugt/mir/judder updated
1573. By Daniel van Vugt

Ensure load/increment is atomic

Revision history for this message
Chris Halse Rogers (raof) wrote :

I disagree that adding a fake display is more elegant than dropping frames after a timeout; the former is a hack, the latter is a direct implementation of the conceptual behaviour we desire.

I suspect that the 1Hz branch will be safer over time as code changes; it implements the client behaviour we care about in the code that implements client behaviour, rather than as a side-effect of the default compositor.

Hm. How does this interact with the Qt scenegraph thingy? Isn't that replacing the compositor?

That said, it might well be that this is safer than the 1Hz branch *right now*.

lp:~vanvugt/mir/judder updated
1574. By Daniel van Vugt

Fix failing test:
TEST_F(MirSurfaceSwapBuffersTest, swap_buffers_does_not_block_when_surface_is_not_composited)

due to it employing a fake display with a single fake output, yielding a
real compositing functor thread that was racing the fake compositor.

Revision history for this message
Daniel van Vugt (vanvugt) :
review: Abstain
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

While this seems to have the desired effect it isn't an elegant solution. (I don't have an elegant solution [yet], but there are a lot of MPs around this area pulling in different directions. Maybe there's a nice solution trying to get out.)

review: Abstain
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

After today's standup the consensus is that when SwitchingBundle cannot supply a buffer (before a timeout) we should to be explicitly dropping frames to provide one and not faking it by consuming the frames in the compositor. In broad terms that's Chris's approach - but we need to review the detail.

So, while I think the tests included here are valid, the changes are not the way we want to go forward.

review: Disapprove
lp:~vanvugt/mir/judder updated
1575. By Daniel van Vugt

Merge latest development-branch and fix conflicts.

1576. By Daniel van Vugt

Fix tabs -> spaces

1577. By Daniel van Vugt

More debug output

1578. By Daniel van Vugt

Remove more tabs

1579. By Daniel van Vugt

Remember to keep fake compositing as required

1580. By Daniel van Vugt

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

1581. By Daniel van Vugt

Clean up

1582. By Daniel van Vugt

Merge latest development-branch

1583. By Daniel van Vugt

Fix missing brace, FTBFS

1584. By Daniel van Vugt

Tidy up

1585. By Daniel van Vugt

Adjust sleep to yield 10Hz client wakeups

1586. By Daniel van Vugt

Merge latest development-branch

1587. By Daniel van Vugt

Merge latest development-branch

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

Subscribers

People subscribed via source and target branches