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
=== modified file 'src/server/compositor/multi_threaded_compositor.cpp'
--- src/server/compositor/multi_threaded_compositor.cpp 2014-05-29 15:51:54 +0000
+++ src/server/compositor/multi_threaded_compositor.cpp 2014-05-30 08:03:29 +0000
@@ -32,6 +32,7 @@
32#include <condition_variable>32#include <condition_variable>
33#include <cstdint>33#include <cstdint>
34#include <chrono>34#include <chrono>
35#include <atomic>
3536
36namespace mc = mir::compositor;37namespace mc = mir::compositor;
37namespace mg = mir::graphics;38namespace mg = mir::graphics;
@@ -86,6 +87,8 @@
86 mg::DisplayBuffer& buffer;87 mg::DisplayBuffer& buffer;
87};88};
8889
90std::atomic<int> real_frames(0);
91
89class CompositingFunctor92class CompositingFunctor
90{93{
91public:94public:
@@ -180,7 +183,12 @@
180 r.top_left.x.as_int(), r.top_left.y.as_int(),183 r.top_left.x.as_int(), r.top_left.y.as_int(),
181 report_id);184 report_id);
182185
183 run_compositing_loop([&] { return display_buffer_compositor->composite();});186 run_compositing_loop([&]
187 {
188 bool ret = display_buffer_compositor->composite();
189 real_frames.fetch_add(1);
190 return ret;
191 });
184 }192 }
185 catch (...)193 catch (...)
186 {194 {
@@ -207,19 +215,31 @@
207 run_compositing_loop(215 run_compositing_loop(
208 [this]216 [this]
209 {217 {
210 std::vector<std::shared_ptr<mg::Buffer>> saved_resources;218 int old_real_frames = real_frames;
211219
220 // Emulate a moderately slow fake display.
221 // Note that due to feedback from consuming a buffer,
222 // this will trigger alternation between consuming a real
223 // and fake frame. So sleeping 50ms here actually results
224 // in a client frame rate of 10Hz.
225
226 std::this_thread::sleep_for(std::chrono::milliseconds(50));
227 bool consumed = real_frames != old_real_frames;
228
229 int more = 0;
212 auto const& renderables = scene->renderable_list_for(this);230 auto const& renderables = scene->renderable_list_for(this);
213
214 for (auto const& r : renderables)231 for (auto const& r : renderables)
215 {232 {
216 if (r->buffers_ready_for_compositor() > 0)233 int ready = r->buffers_ready_for_compositor();
217 saved_resources.push_back(r->buffer());234 more += ready;
235
236 // Now only composite a fake frame if no real display has
237 // composited during the sleep.
238 if (ready && !consumed)
239 (void)r->buffer();
218 }240 }
219241
220 wait_until_next_fake_vsync();242 return more > 0;
221
222 return false;
223 });243 });
224 }244 }
225 catch (...)245 catch (...)
@@ -228,23 +248,6 @@
228 }248 }
229249
230private:250private:
231 void wait_until_next_fake_vsync()
232 {
233 using namespace std::chrono;
234 typedef duration<int64_t, std::ratio<1, 60>> vsync_periods;
235
236 /* Truncate to vsync periods */
237 auto const previous_vsync =
238 duration_cast<vsync_periods>(steady_clock::now().time_since_epoch());
239 /* Convert back to a timepoint */
240 auto const previous_vsync_tp =
241 time_point<steady_clock, vsync_periods>{previous_vsync};
242 /* Next vsync time point */
243 auto const next_vsync = previous_vsync_tp + vsync_periods(1);
244
245 std::this_thread::sleep_until(next_vsync);
246 }
247
248 std::shared_ptr<mc::Scene> const scene;251 std::shared_ptr<mc::Scene> const scene;
249};252};
250253
251254
=== modified file 'tests/acceptance-tests/test_client_surface_swap_buffers.cpp'
--- tests/acceptance-tests/test_client_surface_swap_buffers.cpp 2014-05-29 15:51:54 +0000
+++ tests/acceptance-tests/test_client_surface_swap_buffers.cpp 2014-05-30 08:03:29 +0000
@@ -21,7 +21,7 @@
21#include "mir_test_framework/stubbed_server_configuration.h"21#include "mir_test_framework/stubbed_server_configuration.h"
22#include "mir_test_framework/in_process_server.h"22#include "mir_test_framework/in_process_server.h"
23#include "mir_test_framework/using_stub_client_platform.h"23#include "mir_test_framework/using_stub_client_platform.h"
24#include "mir_test_doubles/null_display_buffer_compositor_factory.h"24#include "mir_test_doubles/null_display.h"
25#include "mir_test/spin_wait.h"25#include "mir_test/spin_wait.h"
2626
27#include <gtest/gtest.h>27#include <gtest/gtest.h>
@@ -37,10 +37,9 @@
3737
38struct StubServerConfig : mtf::StubbedServerConfiguration38struct StubServerConfig : mtf::StubbedServerConfiguration
39{39{
40 auto the_display_buffer_compositor_factory()40 std::shared_ptr<mir::graphics::Display> the_display()
41 -> std::shared_ptr<mc::DisplayBufferCompositorFactory> override
42 {41 {
43 return std::make_shared<mtd::NullDisplayBufferCompositorFactory>();42 return std::make_shared<mtd::NullDisplay>();
44 }43 }
45};44};
4645
4746
=== modified file 'tests/unit-tests/compositor/test_multi_threaded_compositor.cpp'
--- tests/unit-tests/compositor/test_multi_threaded_compositor.cpp 2014-05-29 15:51:54 +0000
+++ tests/unit-tests/compositor/test_multi_threaded_compositor.cpp 2014-05-30 08:03:29 +0000
@@ -150,6 +150,55 @@
150 bool throw_on_add_observer_;150 bool throw_on_add_observer_;
151};151};
152152
153class StubDisplayBufferCompositor : public mc::DisplayBufferCompositor
154{
155public:
156 StubDisplayBufferCompositor(std::shared_ptr<mc::Scene> scene,
157 std::chrono::milliseconds frame_time)
158 : scene{scene}, frame_time{frame_time}
159 {
160 }
161
162 bool composite() override
163 {
164 auto const& all = scene->renderable_list_for(this);
165 for (auto const& r : all)
166 r->buffer(); // Consume a frame
167
168 std::this_thread::sleep_for(frame_time);
169 return false;
170 }
171
172private:
173 std::shared_ptr<mc::Scene> const scene;
174 std::chrono::milliseconds const frame_time;
175};
176
177
178class StubDisplayBufferCompositorFactory :
179 public mc::DisplayBufferCompositorFactory
180{
181public:
182 StubDisplayBufferCompositorFactory(std::shared_ptr<mc::Scene> scene,
183 int hz)
184 : scene{scene},
185 frame_time{std::chrono::milliseconds(1000 / hz)}
186 {
187 }
188
189 std::unique_ptr<mc::DisplayBufferCompositor>
190 create_compositor_for(mg::DisplayBuffer&) override
191 {
192 std::unique_ptr<mc::DisplayBufferCompositor> ret(
193 new StubDisplayBufferCompositor(scene, frame_time));
194 return ret;
195 }
196
197private:
198 std::shared_ptr<mc::Scene> const scene;
199 std::chrono::milliseconds const frame_time;
200};
201
153class RecordingDisplayBufferCompositor : public mc::DisplayBufferCompositor202class RecordingDisplayBufferCompositor : public mc::DisplayBufferCompositor
154{203{
155public:204public:
@@ -323,7 +372,7 @@
323class BufferCountingRenderable : public mtd::StubRenderable372class BufferCountingRenderable : public mtd::StubRenderable
324{373{
325public:374public:
326 BufferCountingRenderable(RenderableVisibility visibility)375 BufferCountingRenderable(RenderableVisibility visibility = RenderableVisibility::visible)
327 : buffers_requested_{0}, visibility{visibility}376 : buffers_requested_{0}, visibility{visibility}
328 {377 {
329 }378 }
@@ -627,12 +676,12 @@
627{676{
628 using namespace testing;677 using namespace testing;
629678
630 unsigned int const nbuffers{2};
631 auto renderable = std::make_shared<BufferCountingRenderable>(GetParam());679 auto renderable = std::make_shared<BufferCountingRenderable>(GetParam());
632 auto display = std::make_shared<StubDisplay>(nbuffers);680
681 // Defining zero outputs is the simplest way to emulate what would
682 // happen if all your displays were blocked in swapping...
683 auto display = std::make_shared<StubDisplay>(0);
633 auto stub_scene = std::make_shared<StubScene>(mg::RenderableList{renderable});684 auto stub_scene = std::make_shared<StubScene>(mg::RenderableList{renderable});
634 // We use NullDisplayBufferCompositors to simulate DisplayBufferCompositors
635 // not rendering a renderable.
636 auto db_compositor_factory = std::make_shared<mtd::NullDisplayBufferCompositorFactory>();685 auto db_compositor_factory = std::make_shared<mtd::NullDisplayBufferCompositorFactory>();
637686
638 mc::MultiThreadedCompositor compositor{687 mc::MultiThreadedCompositor compositor{
@@ -657,6 +706,86 @@
657 compositor.stop();706 compositor.stop();
658}707}
659708
709TEST(MultiThreadedCompositor, never_steals_frames_from_real_displays)
710{
711 /*
712 * Verify dummy frames are consumed slower than any physical display would
713 * consume them, so that the two types of consumers don't race and don't
714 * visibly skip (LP: #1308843)
715 */
716 using namespace testing;
717
718 unsigned int const nbuffers{2};
719 auto renderable = std::make_shared<BufferCountingRenderable>();
720 auto display = std::make_shared<StubDisplay>(nbuffers);
721 auto stub_scene = std::make_shared<StubScene>(mg::RenderableList{renderable});
722 // We use NullDisplayBufferCompositors to simulate DisplayBufferCompositors
723 // not rendering a renderable.
724 auto db_compositor_factory = std::make_shared<mtd::NullDisplayBufferCompositorFactory>();
725
726 mc::MultiThreadedCompositor compositor{
727 display, stub_scene, db_compositor_factory, null_report, true};
728
729 compositor.start();
730
731 // Realistically I've only ever seen LCDs go as low as 40Hz. But some TV
732 // outputs will go as low as 24Hz (traditional movie frame rate). So we
733 // need to ensure the dummy consumer is slower than that...
734 int const min_real_refresh_rate = 20;
735
736 int const secs = 5;
737 auto const duration = std::chrono::seconds{secs};
738 mir::test::spin_wait_for_condition_or_timeout(
739 [&] { return renderable->buffers_requested() <= 1; },
740 duration);
741
742 auto const end = std::chrono::steady_clock::now() + duration;
743 while (std::chrono::steady_clock::now() < end)
744 {
745 stub_scene->emit_change_event();
746 std::this_thread::yield();
747 }
748
749 int const max_dummy_frames = min_real_refresh_rate * secs;
750 ASSERT_GT(max_dummy_frames, renderable->buffers_requested());
751
752 compositor.stop();
753}
754
755TEST(MultiThreadedCompositor, only_real_displays_limit_consumption_rate)
756{
757 using namespace testing;
758
759 auto display = std::make_shared<StubDisplay>(1);
760 auto renderable = std::make_shared<BufferCountingRenderable>();
761 auto stub_scene = std::make_shared<StubScene>(mg::RenderableList{renderable});
762
763 int const framerate = 75; // Simulate a nice fast frame rate
764 auto db_compositor_factory =
765 std::make_shared<StubDisplayBufferCompositorFactory>(stub_scene,
766 framerate);
767
768 mc::MultiThreadedCompositor compositor{
769 display, stub_scene, db_compositor_factory, null_report, false};
770
771 compositor.start();
772
773 int const secs = 5;
774 auto const duration = std::chrono::seconds{secs};
775 auto const end = std::chrono::steady_clock::now() + duration;
776 while (std::chrono::steady_clock::now() < end)
777 {
778 stub_scene->emit_change_event();
779 std::this_thread::yield();
780 }
781
782 int const expected_frames = framerate * secs;
783 ASSERT_GT(expected_frames * 1.2f, renderable->buffers_requested());
784 ASSERT_LT(expected_frames * 0.8f, renderable->buffers_requested());
785
786 compositor.stop();
787}
788
660INSTANTIATE_TEST_CASE_P(789INSTANTIATE_TEST_CASE_P(
661 MultiThreadedCompositor, BufferConsumption,790 MultiThreadedCompositor, BufferConsumption,
662 ::testing::Values(RenderableVisibility::hidden, RenderableVisibility::visible));791 ::testing::Values(RenderableVisibility::hidden, RenderableVisibility::visible));

Subscribers

People subscribed via source and target branches