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