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
=== modified file 'include/server/mir/compositor/multi_threaded_compositor.h'
--- include/server/mir/compositor/multi_threaded_compositor.h 2013-07-26 05:12:14 +0000
+++ include/server/mir/compositor/multi_threaded_compositor.h 2013-08-08 08:53:04 +0000
@@ -34,6 +34,8 @@
34namespace compositor34namespace compositor
35{35{
3636
37enum {max_client_buffers = 3};
38
37class DisplayBufferCompositorFactory;39class DisplayBufferCompositorFactory;
38class CompositingFunctor;40class CompositingFunctor;
39class Scene;41class Scene;
4042
=== modified file 'src/server/compositor/multi_threaded_compositor.cpp'
--- src/server/compositor/multi_threaded_compositor.cpp 2013-07-26 05:12:14 +0000
+++ src/server/compositor/multi_threaded_compositor.cpp 2013-08-08 08:53:04 +0000
@@ -60,7 +60,7 @@
60 : display_buffer_compositor_factory{db_compositor_factory},60 : display_buffer_compositor_factory{db_compositor_factory},
61 buffer(buffer),61 buffer(buffer),
62 running{true},62 running{true},
63 compositing_scheduled{false}63 frames_scheduled{0}
64 {64 {
65 }65 }
6666
@@ -79,10 +79,10 @@
79 while (running)79 while (running)
80 {80 {
81 /* Wait until compositing has been scheduled or we are stopped */81 /* Wait until compositing has been scheduled or we are stopped */
82 while (!compositing_scheduled && running)82 while (!frames_scheduled && running)
83 run_cv.wait(lock);83 run_cv.wait(lock);
8484
85 compositing_scheduled = false;85 frames_scheduled--;
8686
87 /*87 /*
88 * Check if we are running before compositing, since we may have88 * Check if we are running before compositing, since we may have
@@ -100,11 +100,17 @@
100 void schedule_compositing()100 void schedule_compositing()
101 {101 {
102 std::lock_guard<std::mutex> lock{run_mutex};102 std::lock_guard<std::mutex> lock{run_mutex};
103 if (!compositing_scheduled)103
104 {104 /*
105 compositing_scheduled = true;105 * Each surface could have a number of frames ready in its buffer
106 run_cv.notify_one();106 * queue. And we need to ensure that we render all of them so that
107 }107 * none linger in the queue indefinitely (seen as input lag). So while
108 * there's no API support for finding out queue lengths, assume the
109 * worst and schedule enough frames to ensure all surfaces' queues
110 * are fully drained.
111 */
112 frames_scheduled = max_client_buffers;
113 run_cv.notify_one();
108 }114 }
109115
110 void stop()116 void stop()
@@ -118,7 +124,7 @@
118 std::shared_ptr<mc::DisplayBufferCompositorFactory> const display_buffer_compositor_factory;124 std::shared_ptr<mc::DisplayBufferCompositorFactory> const display_buffer_compositor_factory;
119 mg::DisplayBuffer& buffer;125 mg::DisplayBuffer& buffer;
120 bool running;126 bool running;
121 bool compositing_scheduled;127 int frames_scheduled;
122 std::mutex run_mutex;128 std::mutex run_mutex;
123 std::condition_variable run_cv;129 std::condition_variable run_cv;
124};130};
125131
=== modified file 'tests/unit-tests/compositor/test_multi_threaded_compositor.cpp'
--- tests/unit-tests/compositor/test_multi_threaded_compositor.cpp 2013-07-26 05:12:14 +0000
+++ tests/unit-tests/compositor/test_multi_threaded_compositor.cpp 2013-08-08 08:53:04 +0000
@@ -202,17 +202,18 @@
202202
203 bool check_record_count_for_each_buffer(203 bool check_record_count_for_each_buffer(
204 unsigned int nbuffers,204 unsigned int nbuffers,
205 std::function<bool(unsigned int)> const& check)205 unsigned int min,
206 unsigned int max = ~0u)
206 {207 {
207 std::lock_guard<std::mutex> lk{m};208 std::lock_guard<std::mutex> lk{m};
208209
209 if (records.size() < nbuffers)210 if (records.size() < nbuffers)
210 return false;211 return (min == 0 && max == 0);
211212
212 for (auto const& e : records)213 for (auto const& e : records)
213 {214 {
214 Record const& r = e.second;215 Record const& r = e.second;
215 if (!check(r.first))216 if (r.first < min || r.first > max)
216 return false;217 return false;
217 }218 }
218219
@@ -327,36 +328,60 @@
327{328{
328 using namespace testing;329 using namespace testing;
329330
330 unsigned int const nbuffers{3};331 unsigned int const nbuffers{mc::max_client_buffers};
331332
332 auto display = std::make_shared<StubDisplay>(nbuffers);333 auto display = std::make_shared<StubDisplay>(nbuffers);
333 auto scene = std::make_shared<StubScene>();334 auto scene = std::make_shared<StubScene>();
334 auto db_compositor_factory = std::make_shared<RecordingDisplayBufferCompositorFactory>();335 auto db_compositor_factory = std::make_shared<RecordingDisplayBufferCompositorFactory>();
335 mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory};336 mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory};
336337
337 auto at_least_one = [](unsigned int n){return n >= 1;};338 // Verify we're actually starting at zero frames
338 auto at_least_two = [](unsigned int n){return n >= 2;};339 EXPECT_TRUE(db_compositor_factory->check_record_count_for_each_buffer(nbuffers, 0, 0));
339 auto exactly_two = [](unsigned int n){return n == 2;};
340340
341 compositor.start();341 compositor.start();
342342
343 /* Wait until buffers have been rendered to once (initial render) */343 // Initial render: 3 frames should be composited at least
344 while (!db_compositor_factory->check_record_count_for_each_buffer(nbuffers, at_least_one))344 while (!db_compositor_factory->check_record_count_for_each_buffer(nbuffers, 3))
345 std::this_thread::sleep_for(std::chrono::milliseconds(10));345 std::this_thread::sleep_for(std::chrono::milliseconds(10));
346346
347 // Now we have 3 redraws, pause for a while
348 std::this_thread::sleep_for(std::chrono::milliseconds(1000));
349
350 // ... and make sure the number is still only 3
351 EXPECT_TRUE(db_compositor_factory->check_record_count_for_each_buffer(nbuffers, nbuffers, nbuffers));
352
353 // Trigger more surface changes
347 scene->emit_change_event();354 scene->emit_change_event();
348355
349 /* Wait until buffers have been rendered to twice (initial render + change) */356 // Display buffers should be forced to render another 3, so that's 6
350 while (!db_compositor_factory->check_record_count_for_each_buffer(nbuffers, at_least_two))357 while (!db_compositor_factory->check_record_count_for_each_buffer(nbuffers, 2*nbuffers))
351 std::this_thread::sleep_for(std::chrono::milliseconds(10));358 std::this_thread::sleep_for(std::chrono::milliseconds(10));
352359
353 /* Give some more time for other renders (if any) to happen */360 // Now pause without any further surface changes
354 std::this_thread::sleep_for(std::chrono::milliseconds(10));361 std::this_thread::sleep_for(std::chrono::milliseconds(1000));
362
363 // Verify we never triggered more than 6 compositions
364 EXPECT_TRUE(db_compositor_factory->check_record_count_for_each_buffer(nbuffers, 2*nbuffers, 2*nbuffers));
365
366 compositor.stop(); // Pause the compositor so we don't race
367
368 // Now trigger many surfaces changes close together
369 for (int i = 0; i < 10; i++)
370 scene->emit_change_event();
371
372 compositor.start();
373
374 // Display buffers should be forced to render another 3, so that's 9
375 while (!db_compositor_factory->check_record_count_for_each_buffer(nbuffers, 3*nbuffers))
376 std::this_thread::sleep_for(std::chrono::milliseconds(10));
377
378 // Now pause without any further surface changes
379 std::this_thread::sleep_for(std::chrono::milliseconds(1000));
380
381 // Verify we never triggered more than 9 compositions
382 EXPECT_TRUE(db_compositor_factory->check_record_count_for_each_buffer(nbuffers, 3*nbuffers, 3*nbuffers));
355383
356 compositor.stop();384 compositor.stop();
357
358 /* Only two renders should have happened */
359 EXPECT_TRUE(db_compositor_factory->check_record_count_for_each_buffer(nbuffers, exactly_two));
360}385}
361386
362TEST(MultiThreadedCompositor, surface_update_from_render_doesnt_deadlock)387TEST(MultiThreadedCompositor, surface_update_from_render_doesnt_deadlock)

Subscribers

People subscribed via source and target branches