Mir

Merge lp:~vanvugt/mir/fix-compositor-lag into lp:~mir-team/mir/trunk

Proposed by Daniel van Vugt
Status: Merged
Approved by: kevin gunn
Approved revision: no longer in the source branch.
Merged at revision: 955
Proposed branch: lp:~vanvugt/mir/fix-compositor-lag
Merge into: lp:~mir-team/mir/trunk
Diff against target: 172 lines (+60/-27)
3 files modified
include/server/mir/compositor/multi_threaded_compositor.h (+2/-0)
src/server/compositor/multi_threaded_compositor.cpp (+15/-9)
tests/unit-tests/compositor/test_multi_threaded_compositor.cpp (+43/-18)
To merge this branch: bzr merge lp:~vanvugt/mir/fix-compositor-lag
Reviewer Review Type Date Requested Status
Kevin DuBois (community) Approve
Alexandros Frantzis (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+179117@code.launchpad.net

Commit message

Fix the compositor side of lag observed between input events and the screen.
This is half the fix for LP: #1199450.

The other half of the fix is to resolve client buffers arriving out of order,
which has not been fully diagnosed but is known to be resolved by the
"switch" branch.

Description of the change

I think this is still a bit of a kludge. It would be nice if we could work a more elegant solution into the architecture over time.

To post a comment you must log in.
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 :

It isn't entirely a new problem, but I don't like seeing tests that are dependent on timing intervals.

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

Alan,

I understand. Aside from longer execution times, it creates the risk of spurious failures. On the other hand, testing code in its "natural environment" without mocking (where you can) is arguably better practice.

Also, the test in question was already timing-dependent.

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

> Alan,
>
> I understand. Aside from longer execution times, it creates the risk of
> spurious failures. On the other hand, testing code in its "natural
> environment" without mocking (where you can) is arguably better practice.

At the risk of straying off the topic of this MP...

Unit tests are about testing code in isolation. That is, with test doubles to replace its "natural environment".

It is best practice to have tests at unit, integration and system level.

> Also, the test in question was already timing-dependent.

Already noted. ;)

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

I am not set up to test the lag issue, but otherwise looks good, doesn't seem to cause any unexpected effects.

review: Approve
Revision history for this message
Kevin DuBois (kdub) wrote :

lgtm

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'include/server/mir/compositor/multi_threaded_compositor.h'
2--- include/server/mir/compositor/multi_threaded_compositor.h 2013-07-26 05:12:14 +0000
3+++ include/server/mir/compositor/multi_threaded_compositor.h 2013-08-08 08:53:04 +0000
4@@ -34,6 +34,8 @@
5 namespace compositor
6 {
7
8+enum {max_client_buffers = 3};
9+
10 class DisplayBufferCompositorFactory;
11 class CompositingFunctor;
12 class Scene;
13
14=== modified file 'src/server/compositor/multi_threaded_compositor.cpp'
15--- src/server/compositor/multi_threaded_compositor.cpp 2013-07-26 05:12:14 +0000
16+++ src/server/compositor/multi_threaded_compositor.cpp 2013-08-08 08:53:04 +0000
17@@ -60,7 +60,7 @@
18 : display_buffer_compositor_factory{db_compositor_factory},
19 buffer(buffer),
20 running{true},
21- compositing_scheduled{false}
22+ frames_scheduled{0}
23 {
24 }
25
26@@ -79,10 +79,10 @@
27 while (running)
28 {
29 /* Wait until compositing has been scheduled or we are stopped */
30- while (!compositing_scheduled && running)
31+ while (!frames_scheduled && running)
32 run_cv.wait(lock);
33
34- compositing_scheduled = false;
35+ frames_scheduled--;
36
37 /*
38 * Check if we are running before compositing, since we may have
39@@ -100,11 +100,17 @@
40 void schedule_compositing()
41 {
42 std::lock_guard<std::mutex> lock{run_mutex};
43- if (!compositing_scheduled)
44- {
45- compositing_scheduled = true;
46- run_cv.notify_one();
47- }
48+
49+ /*
50+ * Each surface could have a number of frames ready in its buffer
51+ * queue. And we need to ensure that we render all of them so that
52+ * none linger in the queue indefinitely (seen as input lag). So while
53+ * there's no API support for finding out queue lengths, assume the
54+ * worst and schedule enough frames to ensure all surfaces' queues
55+ * are fully drained.
56+ */
57+ frames_scheduled = max_client_buffers;
58+ run_cv.notify_one();
59 }
60
61 void stop()
62@@ -118,7 +124,7 @@
63 std::shared_ptr<mc::DisplayBufferCompositorFactory> const display_buffer_compositor_factory;
64 mg::DisplayBuffer& buffer;
65 bool running;
66- bool compositing_scheduled;
67+ int frames_scheduled;
68 std::mutex run_mutex;
69 std::condition_variable run_cv;
70 };
71
72=== modified file 'tests/unit-tests/compositor/test_multi_threaded_compositor.cpp'
73--- tests/unit-tests/compositor/test_multi_threaded_compositor.cpp 2013-07-26 05:12:14 +0000
74+++ tests/unit-tests/compositor/test_multi_threaded_compositor.cpp 2013-08-08 08:53:04 +0000
75@@ -202,17 +202,18 @@
76
77 bool check_record_count_for_each_buffer(
78 unsigned int nbuffers,
79- std::function<bool(unsigned int)> const& check)
80+ unsigned int min,
81+ unsigned int max = ~0u)
82 {
83 std::lock_guard<std::mutex> lk{m};
84
85 if (records.size() < nbuffers)
86- return false;
87+ return (min == 0 && max == 0);
88
89 for (auto const& e : records)
90 {
91 Record const& r = e.second;
92- if (!check(r.first))
93+ if (r.first < min || r.first > max)
94 return false;
95 }
96
97@@ -327,36 +328,60 @@
98 {
99 using namespace testing;
100
101- unsigned int const nbuffers{3};
102+ unsigned int const nbuffers{mc::max_client_buffers};
103
104 auto display = std::make_shared<StubDisplay>(nbuffers);
105 auto scene = std::make_shared<StubScene>();
106 auto db_compositor_factory = std::make_shared<RecordingDisplayBufferCompositorFactory>();
107 mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory};
108
109- auto at_least_one = [](unsigned int n){return n >= 1;};
110- auto at_least_two = [](unsigned int n){return n >= 2;};
111- auto exactly_two = [](unsigned int n){return n == 2;};
112+ // Verify we're actually starting at zero frames
113+ EXPECT_TRUE(db_compositor_factory->check_record_count_for_each_buffer(nbuffers, 0, 0));
114
115 compositor.start();
116
117- /* Wait until buffers have been rendered to once (initial render) */
118- while (!db_compositor_factory->check_record_count_for_each_buffer(nbuffers, at_least_one))
119+ // Initial render: 3 frames should be composited at least
120+ while (!db_compositor_factory->check_record_count_for_each_buffer(nbuffers, 3))
121 std::this_thread::sleep_for(std::chrono::milliseconds(10));
122
123+ // Now we have 3 redraws, pause for a while
124+ std::this_thread::sleep_for(std::chrono::milliseconds(1000));
125+
126+ // ... and make sure the number is still only 3
127+ EXPECT_TRUE(db_compositor_factory->check_record_count_for_each_buffer(nbuffers, nbuffers, nbuffers));
128+
129+ // Trigger more surface changes
130 scene->emit_change_event();
131
132- /* Wait until buffers have been rendered to twice (initial render + change) */
133- while (!db_compositor_factory->check_record_count_for_each_buffer(nbuffers, at_least_two))
134- std::this_thread::sleep_for(std::chrono::milliseconds(10));
135-
136- /* Give some more time for other renders (if any) to happen */
137- std::this_thread::sleep_for(std::chrono::milliseconds(10));
138+ // Display buffers should be forced to render another 3, so that's 6
139+ while (!db_compositor_factory->check_record_count_for_each_buffer(nbuffers, 2*nbuffers))
140+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
141+
142+ // Now pause without any further surface changes
143+ std::this_thread::sleep_for(std::chrono::milliseconds(1000));
144+
145+ // Verify we never triggered more than 6 compositions
146+ EXPECT_TRUE(db_compositor_factory->check_record_count_for_each_buffer(nbuffers, 2*nbuffers, 2*nbuffers));
147+
148+ compositor.stop(); // Pause the compositor so we don't race
149+
150+ // Now trigger many surfaces changes close together
151+ for (int i = 0; i < 10; i++)
152+ scene->emit_change_event();
153+
154+ compositor.start();
155+
156+ // Display buffers should be forced to render another 3, so that's 9
157+ while (!db_compositor_factory->check_record_count_for_each_buffer(nbuffers, 3*nbuffers))
158+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
159+
160+ // Now pause without any further surface changes
161+ std::this_thread::sleep_for(std::chrono::milliseconds(1000));
162+
163+ // Verify we never triggered more than 9 compositions
164+ EXPECT_TRUE(db_compositor_factory->check_record_count_for_each_buffer(nbuffers, 3*nbuffers, 3*nbuffers));
165
166 compositor.stop();
167-
168- /* Only two renders should have happened */
169- EXPECT_TRUE(db_compositor_factory->check_record_count_for_each_buffer(nbuffers, exactly_two));
170 }
171
172 TEST(MultiThreadedCompositor, surface_update_from_render_doesnt_deadlock)

Subscribers

People subscribed via source and target branches