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: 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
Alan Griffiths Pending
PS Jenkins bot continuous-integration Pending
Daniel van Vugt Pending
Alexandros Frantzis Pending
Review via email: mp+221275@code.launchpad.net

This proposal supersedes a proposal from 2014-04-22.

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

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 : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

> 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 : Posted in a previous version of this proposal

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.

Revision history for this message
Chris Halse Rogers (raof) wrote : Posted in a previous version of this proposal

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*.

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

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 : Posted in a previous version of this proposal

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
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-05-28 07:43:51 +0000
3+++ src/server/compositor/multi_threaded_compositor.cpp 2014-05-28 16:49:25 +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-22 09:56:17 +0000
102+++ tests/acceptance-tests/test_client_surface_swap_buffers.cpp 2014-05-28 16:49:25 +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-28 07:43:51 +0000
128+++ tests/unit-tests/compositor/test_multi_threaded_compositor.cpp 2014-05-28 16:49:25 +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