Mir

Merge lp:~afrantzis/mir/non-blocking-swap-buffers into lp:mir

Proposed by Alexandros Frantzis
Status: Merged
Approved by: Alexandros Frantzis
Approved revision: no longer in the source branch.
Merged at revision: 1545
Proposed branch: lp:~afrantzis/mir/non-blocking-swap-buffers
Merge into: lp:mir
Diff against target: 662 lines (+426/-65)
8 files modified
include/test/mir_test/spin_wait.h (+38/-0)
include/test/mir_test_doubles/null_display_buffer_compositor_factory.h (+52/-0)
src/server/compositor/multi_threaded_compositor.cpp (+127/-49)
tests/acceptance-tests/CMakeLists.txt (+1/-0)
tests/acceptance-tests/test_client_surface_swap_buffers.cpp (+102/-0)
tests/mir_test/CMakeLists.txt (+1/-0)
tests/mir_test/spin_wait.cpp (+36/-0)
tests/unit-tests/compositor/test_multi_threaded_compositor.cpp (+69/-16)
To merge this branch: bzr merge lp:~afrantzis/mir/non-blocking-swap-buffers
Reviewer Review Type Date Requested Status
Alberto Aguirre (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Kevin DuBois (community) Abstain
Review via email: mp+214755@code.launchpad.net

Commit message

compositor: Consume buffers of surfaces that are not rendered on screen

This ensures that eglSwapBuffers() in clients doesn't block if a surface is not
rendered.

Description of the change

compositor: Consume buffers of surfaces that are not rendered on screen

This ensures that eglSwapBuffers() in clients doesn't block if a surface is not
rendered.

We introduce a compositing thread that consumes all buffers at a steady 60Hz rate, mimicking a real display. The consuming compositing thread is active even when we have real compositing threads, since there is no guarantee that all surfaces are rendered on screen even if we have multiple screens. Our buffer multi-monitor synchronization mechanism guarantees that this works without problems.

Other MPs in the non-blocking eglSwapBuffers() series:
https://code.launchpad.net/~afrantzis/mir/expose-display-buffer-only-for-power-mode-on/+merge/214758
https://code.launchpad.net/~afrantzis/unity-system-compositor/non-blocking-swap-buffers/+merge/214759

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
Alberto Aguirre (albaguirre) wrote :

~~~
231+ static int const vsync_period_microsec{16666};
232+
233+ auto now = duration_cast<microseconds>(
234+ steady_clock::now().time_since_epoch());
235+ microseconds next_vsync{
236+ (now.count() / vsync_period_microsec + 1) * vsync_period_microsec};
237+ std::this_thread::sleep_until(steady_clock::time_point{next_vsync});
~~~

You can let chrono do the conversions instead

typedef std::chrono::duration<int, std::ratio<1, 60>> vsync_periods;

//truncate to vsync periods
auto now = std::chrono::duration_cast<vsync_periods>(std::chrono::steady_clock::now().time_since_epoch());
//convert back to a timepoint
auto now_tp = std::chrono::time_point<std::chrono::steady_clock, vsync_periods>{now};
//Next vsync time point
auto next_vsync = now_tp + vsync_periods(1);
std::this_thread::sleep_until(next_vsync);

For test at line 353 - doesn't it need a timeout mechanism in case it does block?

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

comments
216 + if (r->visible())
I intend on removing visible and the saved resources vector soon anyways.

needs fixing:
220 + wait_until_next_vsync();
I'd rather this be "wait_a_reasonable_amount()" or something like that

discussion:
given mc::BufferStream::allow_framedropping(), which is set by the client, it seems strange drop frames via BufferConsumingFunctor if we decide not to consume the buffer. Shouldn't problems with swapping and ordering be solved more in the BufferStream/SwitchingBundle?

another thought:
could we do this in one loop/thread? (and only generate the renderlist once?)

188 + run_compositing_loop([&] { return display_buffer_compositor->composite();});
could we do:

run_compositing_loop([&] {
return display_buffer_compositor->composite(list);
//consume hidden surfaces?
});

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

> 216 + if (r->visible())
> I intend on removing visible and the saved resources vector soon anyways.

Does that mean that scene->generate_renderable_list() will return only renderables that have buffers for rendering?

@saved_resources

I guess the plan is to keep buffers "alive" using some other mechanism. Will we be able to use that mechanism from outside the DisplayBufferCompositor, or do we need to continue use the saved resources vector in the BufferConsumingThread?

220 + wait_until_next_vsync();

wait_until_next_fake_vsync() ?

> given mc::BufferStream::allow_framedropping(), which is set by the client, it seems strange drop frames via
> BufferConsumingFunctor if we decide not to consume the buffer. Shouldn't problems with swapping and ordering be
> solved more in the BufferStream/SwitchingBundle?

I thought a bit about this (but not much really), but couldn't find a satisfactory way to do it in BufferStream/SwitchingBundle. The main issue is that this is not unconditional framedropping, but "framedropping" only if not rendered, which is only known at the compositor level. Even if we find a way to do it in BufferStream/SwitchingBundle, we need to take into account the complexity we are going to add to an already complex component. In any case, I am open to ideas :)

> another thought:
> could we do this in one loop/thread? (and only generate the renderlist once?)

We need to support the cases of multiple active monitors and of no active monitors (e.g. screen off on android). For multiple active monitors we could do as you propose if we have a way for the display_buffer_compositor to return all the surface buffers it didn't consume. Note, however, that in terms of consumed buffers it's almost the same as (or worse than) having the extra thread proposed in this MP. Imagine for example two compositing threads T1,T2 for two outputs and two sets of surfaces A (visible on first output) and B (visible on second output). If we consume hidden surfaces in the compositing threads then T1 will consume AUB and and T2 will consume BUA. With an extra thread T1 consumes A, T2 consumes B and the extra thread consumes AUB. So both ways consume A twice and B twice. For more than two outputs however, consuming in the compositing thread themselves leads to more consumptions than with the extra thread.

For no active monitors and hence no compositing threads, we need the extra consuming thread anyway, so I decided to use it unconditionally, since it provides the behavior we want and it's conceptually simple (essentially just another (fake) output from the POV of our buffer handling).

Granted that for only one output what you describe is a win: we only use one thread and consume the hidden buffers only once. However, it does add some complexity that I wanted to avoid at this point, since this is a matter of optimization not correctness. I am fine with optimizing this use case when things have settled.

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

> You can let chrono do the conversions instead

Good idea. Fixed.

> For test at line 353 - doesn't it need a timeout mechanism in case it does block?

Fixed.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Alberto Aguirre (albaguirre) wrote :

Looks good.

review: Approve
Revision history for this message
Kevin DuBois (kdub) wrote :
Download full text (4.1 KiB)

> > 216 + if (r->visible())
> > I intend on removing visible and the saved resources vector soon anyways.
>
> Does that mean that scene->generate_renderable_list() will return only
> renderables that have buffers for rendering?

Right, the scene should be trimming out surfaces that are not visible().

>
> @saved_resources
>
> I guess the plan is to keep buffers "alive" using some other mechanism. Will
> we be able to use that mechanism from outside the DisplayBufferCompositor, or
> do we need to continue use the saved resources vector in the
> BufferConsumingThread?

We talked a bit about this during the standup with Daniel, I guess we'll have to see where it moves to. I was intending on moving it to the renderer or the RenderableList, although I quite liked moving it to the snapshots in the RenderableList on my first pass.

>
> 220 + wait_until_next_vsync();
>
> wait_until_next_fake_vsync() ?

This is fine, although I like
wait_until_next_client_unblock_event() a bit better. Either one.

>
> > given mc::BufferStream::allow_framedropping(), which is set by the client,
> it seems strange drop frames via
> > BufferConsumingFunctor if we decide not to consume the buffer. Shouldn't
> problems with swapping and ordering be
> > solved more in the BufferStream/SwitchingBundle?
>
> I thought a bit about this (but not much really), but couldn't find a
> satisfactory way to do it in BufferStream/SwitchingBundle. The main issue is
> that this is not unconditional framedropping, but "framedropping" only if not
> rendered, which is only known at the compositor level. Even if we find a way
> to do it in BufferStream/SwitchingBundle, we need to take into account the
> complexity we are going to add to an already complex component. In any case, I
> am open to ideas :)

Yeah, I don't have any ideas that don't involve complicating SwitchingBundle, so I'm happy to go with this solution... and hopefully follow up with an approach that doesn't involve a new thread once the dust has settled.

>
> > another thought:
> > could we do this in one loop/thread? (and only generate the renderlist
> once?)
>
> We need to support the cases of multiple active monitors and of no active
> monitors (e.g. screen off on android). For multiple active monitors we could
> do as you propose if we have a way for the display_buffer_compositor to return
> all the surface buffers it didn't consume. Note, however, that in terms of
> consumed buffers it's almost the same as (or worse than) having the extra
> thread proposed in this MP. Imagine for example two compositing threads T1,T2
> for two outputs and two sets of surfaces A (visible on first output) and B
> (visible on second output). If we consume hidden surfaces in the compositing
> threads then T1 will consume AUB and and T2 will consume BUA. With an extra
> thread T1 consumes A, T2 consumes B and the extra thread consumes AUB. So both
> ways consume A twice and B twice. For more than two outputs however, consuming
> in the compositing thread themselves leads to more consumptions than with the
> extra thread.
>
> For no active monitors and hence no compositing threads, we need the extra
> consuming thread anyway, so I d...

Read more...

review: Abstain
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Alberto Aguirre (albaguirre) wrote :

434+ auto const surface = mir_connection_create_surface_sync(connection, &request_params);

Missing corresponding mir_surface_release_sync(surface);

review: Needs Fixing
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
Alberto Aguirre (albaguirre) :
review: Approve
Revision history for this message
Alexandros Frantzis (afrantzis) wrote :

A prototype of an alternative solution to provide non-blocking SwapBuffers involving only SwitchingBundle changes is:

https://code.launchpad.net/~raof/mir/1hz-rendering-always/

We can change the rate to be whatever we like, as long it's less than the smallest vsync rate of any of the attached displays (plus some safety margin).

!!!Note: the functionality is currently broken in r1545 of that branch

For now we will go with the current branch to unblock our stack with Qt 5.2, and we can discuss more about the alternative as it matures.

My 2 cents so I don't forget:

Pros of the alternative: completely transparent to compositor, only switching bundle needs to be changed, consumes only buffers that are not otherwise consumed

Cons of the alternative: not obviously correct, makes an already complex component more complex, can't stop all buffer consumption by just stopping the compositor, can't reach up to vsync rate

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'include/test/mir_test/spin_wait.h'
2--- include/test/mir_test/spin_wait.h 1970-01-01 00:00:00 +0000
3+++ include/test/mir_test/spin_wait.h 2014-04-10 07:45:21 +0000
4@@ -0,0 +1,38 @@
5+/*
6+ * Copyright © 2014 Canonical Ltd.
7+ *
8+ * This program is free software: you can redistribute it and/or modify it
9+ * under the terms of the GNU General Public License version 3,
10+ * as published by the Free Software Foundation.
11+ *
12+ * This program is distributed in the hope that it will be useful,
13+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+ * GNU General Public License for more details.
16+ *
17+ * You should have received a copy of the GNU General Public License
18+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
19+ *
20+ * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
21+ */
22+
23+#ifndef MIR_TEST_SPIN_WAIT_H_
24+#define MIR_TEST_SPIN_WAIT_H_
25+
26+#include <functional>
27+#include <chrono>
28+
29+namespace mir
30+{
31+namespace test
32+{
33+
34+bool spin_wait_for_condition_or_timeout(
35+ std::function<bool()> const& condition,
36+ std::chrono::milliseconds timeout,
37+ std::chrono::milliseconds spin_period = std::chrono::milliseconds{10});
38+
39+}
40+}
41+
42+#endif /* MIR_TEST_SPIN_WAIT_H_ */
43
44=== added file 'include/test/mir_test_doubles/null_display_buffer_compositor_factory.h'
45--- include/test/mir_test_doubles/null_display_buffer_compositor_factory.h 1970-01-01 00:00:00 +0000
46+++ include/test/mir_test_doubles/null_display_buffer_compositor_factory.h 2014-04-10 07:45:21 +0000
47@@ -0,0 +1,52 @@
48+/*
49+ * Copyright © 2014 Canonical Ltd.
50+ *
51+ * This program is free software: you can redistribute it and/or modify it
52+ * under the terms of the GNU General Public License version 3,
53+ * as published by the Free Software Foundation.
54+ *
55+ * This program is distributed in the hope that it will be useful,
56+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
57+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
58+ * GNU General Public License for more details.
59+ *
60+ * You should have received a copy of the GNU General Public License
61+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
62+ *
63+ * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
64+ */
65+
66+#ifndef MIR_TEST_DOUBLES_NULL_DISPLAY_BUFFER_COMPOSITOR_FACTORY_H_
67+#define MIR_TEST_DOUBLES_NULL_DISPLAY_BUFFER_COMPOSITOR_FACTORY_H_
68+
69+#include "mir/compositor/display_buffer_compositor_factory.h"
70+#include "mir/compositor/display_buffer_compositor.h"
71+
72+namespace mir
73+{
74+namespace test
75+{
76+namespace doubles
77+{
78+
79+class NullDisplayBufferCompositorFactory : public compositor::DisplayBufferCompositorFactory
80+{
81+public:
82+ auto create_compositor_for(graphics::DisplayBuffer&)
83+ -> std::unique_ptr<compositor::DisplayBufferCompositor> override
84+ {
85+ struct NullDisplayBufferCompositor : compositor::DisplayBufferCompositor
86+ {
87+ bool composite() { return false; }
88+ };
89+
90+ auto raw = new NullDisplayBufferCompositor{};
91+ return std::unique_ptr<NullDisplayBufferCompositor>(raw);
92+ }
93+};
94+
95+}
96+}
97+}
98+
99+#endif /* MIR_TEST_DOUBLES_NULL_DISPLAY_BUFFER_COMPOSITOR_FACTORY_H_ */
100
101=== modified file 'src/server/compositor/multi_threaded_compositor.cpp'
102--- src/server/compositor/multi_threaded_compositor.cpp 2014-03-07 03:15:55 +0000
103+++ src/server/compositor/multi_threaded_compositor.cpp 2014-04-10 07:45:21 +0000
104@@ -26,6 +26,8 @@
105
106 #include <thread>
107 #include <condition_variable>
108+#include <cstdint>
109+#include <chrono>
110
111 namespace mc = mir::compositor;
112 namespace mg = mir::graphics;
113@@ -56,37 +58,29 @@
114 class CompositingFunctor
115 {
116 public:
117- CompositingFunctor(std::shared_ptr<mc::DisplayBufferCompositorFactory> const& db_compositor_factory,
118- mg::DisplayBuffer& buffer,
119- std::shared_ptr<CompositorReport> const& report)
120- : display_buffer_compositor_factory{db_compositor_factory},
121- buffer(buffer),
122- running{true},
123- frames_scheduled{0},
124- report{report}
125- {
126- }
127-
128- void operator()() noexcept // noexcept is important! (LP: #1237332)
129+ CompositingFunctor() : running{true}, frames_scheduled{false} {}
130+ virtual ~CompositingFunctor() = default;
131+
132+ void schedule_compositing()
133+ {
134+ std::lock_guard<std::mutex> lock{run_mutex};
135+
136+ frames_scheduled = true;
137+ run_cv.notify_one();
138+ }
139+
140+ void stop()
141+ {
142+ std::lock_guard<std::mutex> lock{run_mutex};
143+ running = false;
144+ run_cv.notify_one();
145+ }
146+
147+protected:
148+ void run_compositing_loop(std::function<bool()> const& composite)
149 {
150 std::unique_lock<std::mutex> lock{run_mutex};
151
152- /*
153- * Make the buffer the current rendering target, and release
154- * it when the thread is finished.
155- */
156- CurrentRenderingTarget target{buffer};
157-
158- auto display_buffer_compositor = display_buffer_compositor_factory->create_compositor_for(buffer);
159-
160- CompositorReport::SubCompositorId report_id =
161- display_buffer_compositor.get();
162-
163- const auto& r = buffer.view_area();
164- report->added_display(r.size.width.as_int(), r.size.height.as_int(),
165- r.top_left.x.as_int(), r.top_left.y.as_int(),
166- report_id);
167-
168 while (running)
169 {
170 /* Wait until compositing has been scheduled or we are stopped */
171@@ -100,7 +94,8 @@
172 {
173 frames_scheduled = false;
174 lock.unlock();
175- auto more_frames_pending = display_buffer_compositor->composite();
176+
177+ auto more_frames_pending = composite();
178
179 /*
180 * Each surface could have a number of frames ready in its buffer
181@@ -115,31 +110,103 @@
182 }
183 }
184
185- void schedule_compositing()
186- {
187- std::lock_guard<std::mutex> lock{run_mutex};
188-
189- frames_scheduled = true;
190- run_cv.notify_one();
191- }
192-
193- void stop()
194- {
195- std::lock_guard<std::mutex> lock{run_mutex};
196- running = false;
197- run_cv.notify_one();
198- }
199-
200 private:
201- std::shared_ptr<mc::DisplayBufferCompositorFactory> const display_buffer_compositor_factory;
202- mg::DisplayBuffer& buffer;
203 bool running;
204 bool frames_scheduled;
205 std::mutex run_mutex;
206 std::condition_variable run_cv;
207+};
208+
209+class DisplayBufferCompositingFunctor : public CompositingFunctor
210+{
211+public:
212+ DisplayBufferCompositingFunctor(
213+ std::shared_ptr<mc::DisplayBufferCompositorFactory> const& db_compositor_factory,
214+ mg::DisplayBuffer& buffer,
215+ std::shared_ptr<mc::CompositorReport> const& report)
216+ : display_buffer_compositor_factory{db_compositor_factory},
217+ buffer(buffer),
218+ report{report}
219+ {
220+ }
221+
222+ void operator()() noexcept // noexcept is important! (LP: #1237332)
223+ {
224+ /*
225+ * Make the buffer the current rendering target, and release
226+ * it when the thread is finished.
227+ */
228+ CurrentRenderingTarget target{buffer};
229+
230+ auto display_buffer_compositor = display_buffer_compositor_factory->create_compositor_for(buffer);
231+
232+ CompositorReport::SubCompositorId report_id =
233+ display_buffer_compositor.get();
234+
235+ const auto& r = buffer.view_area();
236+ report->added_display(r.size.width.as_int(), r.size.height.as_int(),
237+ r.top_left.x.as_int(), r.top_left.y.as_int(),
238+ report_id);
239+
240+ run_compositing_loop([&] { return display_buffer_compositor->composite();});
241+ }
242+
243+private:
244+ std::shared_ptr<mc::DisplayBufferCompositorFactory> const display_buffer_compositor_factory;
245+ mg::DisplayBuffer& buffer;
246 std::shared_ptr<CompositorReport> const report;
247 };
248
249+class BufferConsumingFunctor : public CompositingFunctor
250+{
251+public:
252+ BufferConsumingFunctor(std::shared_ptr<mc::Scene> const& scene)
253+ : scene{scene}
254+ {
255+ }
256+
257+ void operator()() noexcept // noexcept is important! (LP: #1237332)
258+ {
259+ run_compositing_loop(
260+ [this]
261+ {
262+ std::vector<std::shared_ptr<mg::Buffer>> saved_resources;
263+
264+ auto const& renderables = scene->generate_renderable_list();
265+
266+ for (auto const& r : renderables)
267+ {
268+ if (r->visible())
269+ saved_resources.push_back(r->buffer(this));
270+ }
271+
272+ wait_until_next_fake_vsync();
273+
274+ return false;
275+ });
276+ }
277+
278+private:
279+ void wait_until_next_fake_vsync()
280+ {
281+ using namespace std::chrono;
282+ typedef duration<int64_t, std::ratio<1, 60>> vsync_periods;
283+
284+ /* Truncate to vsync periods */
285+ auto const previous_vsync =
286+ duration_cast<vsync_periods>(steady_clock::now().time_since_epoch());
287+ /* Convert back to a timepoint */
288+ auto const previous_vsync_tp =
289+ time_point<steady_clock, vsync_periods>{previous_vsync};
290+ /* Next vsync time point */
291+ auto const next_vsync = previous_vsync_tp + vsync_periods(1);
292+
293+ std::this_thread::sleep_until(next_vsync);
294+ }
295+
296+ std::shared_ptr<mc::Scene> const scene;
297+};
298+
299 }
300 }
301
302@@ -184,13 +251,25 @@
303 /* Start the compositing threads */
304 display->for_each_display_buffer([this](mg::DisplayBuffer& buffer)
305 {
306- auto thread_functor_raw = new mc::CompositingFunctor{display_buffer_compositor_factory, buffer, report};
307- auto thread_functor = std::unique_ptr<mc::CompositingFunctor>(thread_functor_raw);
308+ auto thread_functor_raw =
309+ new mc::DisplayBufferCompositingFunctor{
310+ display_buffer_compositor_factory, buffer, report};
311+ auto thread_functor =
312+ std::unique_ptr<mc::DisplayBufferCompositingFunctor>(thread_functor_raw);
313
314 threads.push_back(std::thread{std::ref(*thread_functor)});
315 thread_functors.push_back(std::move(thread_functor));
316 });
317
318+ /* Start the buffer consuming thread */
319+ {
320+ auto thread_functor_raw = new mc::BufferConsumingFunctor{scene};
321+ auto thread_functor = std::unique_ptr<mc::BufferConsumingFunctor>(thread_functor_raw);
322+
323+ threads.push_back(std::thread{std::ref(*thread_functor)});
324+ thread_functors.push_back(std::move(thread_functor));
325+ }
326+
327 /* Recomposite whenever the scene changes */
328 scene->set_change_callback([this]()
329 {
330@@ -205,7 +284,6 @@
331 lk.unlock();
332 schedule_compositing();
333 }
334-
335 }
336
337 void mc::MultiThreadedCompositor::stop()
338
339=== modified file 'tests/acceptance-tests/CMakeLists.txt'
340--- tests/acceptance-tests/CMakeLists.txt 2014-03-26 05:48:59 +0000
341+++ tests/acceptance-tests/CMakeLists.txt 2014-04-10 07:45:21 +0000
342@@ -28,6 +28,7 @@
343 test_client_library_drm.cpp
344 test_protobuf.cpp
345 test_client_screencast.cpp
346+ test_client_surface_swap_buffers.cpp
347 ${GENERATED_PROTOBUF_SRCS}
348 ${GENERATED_PROTOBUF_HDRS}
349 )
350
351=== added file 'tests/acceptance-tests/test_client_surface_swap_buffers.cpp'
352--- tests/acceptance-tests/test_client_surface_swap_buffers.cpp 1970-01-01 00:00:00 +0000
353+++ tests/acceptance-tests/test_client_surface_swap_buffers.cpp 2014-04-10 07:45:21 +0000
354@@ -0,0 +1,102 @@
355+/*
356+ * Copyright © 2014 Canonical Ltd.
357+ *
358+ * This program is free software: you can redistribute it and/or modify it
359+ * under the terms of the GNU General Public License version 3,
360+ * as published by the Free Software Foundation.
361+ *
362+ * This program is distributed in the hope that it will be useful,
363+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
364+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
365+ * GNU General Public License for more details.
366+ *
367+ * You should have received a copy of the GNU General Public License
368+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
369+ *
370+ * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
371+ */
372+
373+#include "mir_toolkit/mir_client_library.h"
374+
375+#include "mir_test_framework/stubbed_server_configuration.h"
376+#include "mir_test_framework/in_process_server.h"
377+#include "mir_test_framework/using_stub_client_platform.h"
378+#include "mir_test_doubles/null_display_buffer_compositor_factory.h"
379+#include "mir_test/spin_wait.h"
380+
381+#include <gtest/gtest.h>
382+
383+#include <atomic>
384+
385+namespace mtf = mir_test_framework;
386+namespace mtd = mir::test::doubles;
387+namespace mc = mir::compositor;
388+
389+namespace
390+{
391+
392+struct StubServerConfig : mtf::StubbedServerConfiguration
393+{
394+ auto the_display_buffer_compositor_factory()
395+ -> std::shared_ptr<mc::DisplayBufferCompositorFactory> override
396+ {
397+ return std::make_shared<mtd::NullDisplayBufferCompositorFactory>();
398+ }
399+};
400+
401+class MirSurfaceSwapBuffersTest : public mir_test_framework::InProcessServer
402+{
403+public:
404+ mir::DefaultServerConfiguration& server_config() override { return server_config_; }
405+
406+ StubServerConfig server_config_;
407+ mtf::UsingStubClientPlatform using_stub_client_platform;
408+};
409+
410+void swap_buffers_callback(MirSurface*, void* ctx)
411+{
412+ auto buffers_swapped = static_cast<std::atomic<bool>*>(ctx);
413+ *buffers_swapped = true;
414+}
415+
416+}
417+
418+TEST_F(MirSurfaceSwapBuffersTest, swap_buffers_does_not_block_when_surface_is_not_composited)
419+{
420+ using namespace testing;
421+
422+ auto const connection = mir_connect_sync(new_connection().c_str(), __PRETTY_FUNCTION__);
423+ ASSERT_TRUE(mir_connection_is_valid(connection));
424+
425+ MirSurfaceParameters request_params =
426+ {
427+ __PRETTY_FUNCTION__,
428+ 640, 480,
429+ mir_pixel_format_abgr_8888,
430+ mir_buffer_usage_hardware,
431+ mir_display_output_id_invalid
432+ };
433+
434+ auto const surface = mir_connection_create_surface_sync(connection, &request_params);
435+ ASSERT_NE(nullptr, surface);
436+
437+ for (int i = 0; i < 10; ++i)
438+ {
439+ std::atomic<bool> buffers_swapped{false};
440+
441+ mir_surface_swap_buffers(surface, swap_buffers_callback, &buffers_swapped);
442+
443+ mir::test::spin_wait_for_condition_or_timeout(
444+ [&] { return buffers_swapped.load(); },
445+ std::chrono::seconds{5});
446+
447+ /*
448+ * ASSERT instead of EXPECT, since if we continue we will block in future
449+ * mir client calls (e.g mir_connection_release).
450+ */
451+ ASSERT_TRUE(buffers_swapped.load());
452+ }
453+
454+ mir_surface_release_sync(surface);
455+ mir_connection_release(connection);
456+}
457
458=== modified file 'tests/mir_test/CMakeLists.txt'
459--- tests/mir_test/CMakeLists.txt 2013-08-28 03:41:48 +0000
460+++ tests/mir_test/CMakeLists.txt 2014-04-10 07:45:21 +0000
461@@ -4,4 +4,5 @@
462 pipe.cpp
463 display_config_matchers.cpp
464 cross_process_action.cpp
465+ spin_wait.cpp
466 )
467
468=== added file 'tests/mir_test/spin_wait.cpp'
469--- tests/mir_test/spin_wait.cpp 1970-01-01 00:00:00 +0000
470+++ tests/mir_test/spin_wait.cpp 2014-04-10 07:45:21 +0000
471@@ -0,0 +1,36 @@
472+/*
473+ * Copyright © 2014 Canonical Ltd.
474+ *
475+ * This program is free software: you can redistribute it and/or modify it
476+ * under the terms of the GNU General Public License version 3,
477+ * as published by the Free Software Foundation.
478+ *
479+ * This program is distributed in the hope that it will be useful,
480+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
481+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
482+ * GNU General Public License for more details.
483+ *
484+ * You should have received a copy of the GNU General Public License
485+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
486+ *
487+ * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
488+ */
489+
490+#include "mir_test/spin_wait.h"
491+
492+#include <thread>
493+
494+bool mir::test::spin_wait_for_condition_or_timeout(
495+ std::function<bool()> const& condition,
496+ std::chrono::milliseconds timeout,
497+ std::chrono::milliseconds spin_period)
498+{
499+ auto const end = std::chrono::steady_clock::now() + timeout;
500+
501+ while (std::chrono::steady_clock::now() < end && !condition())
502+ {
503+ std::this_thread::sleep_for(spin_period);
504+ }
505+
506+ return condition();
507+}
508
509=== modified file 'tests/unit-tests/compositor/test_multi_threaded_compositor.cpp'
510--- tests/unit-tests/compositor/test_multi_threaded_compositor.cpp 2014-04-03 00:14:45 +0000
511+++ tests/unit-tests/compositor/test_multi_threaded_compositor.cpp 2014-04-10 07:45:21 +0000
512@@ -26,12 +26,16 @@
513 #include "mir_test_doubles/mock_display_buffer.h"
514 #include "mir_test_doubles/mock_compositor_report.h"
515 #include "mir_test_doubles/mock_scene.h"
516+#include "mir_test_doubles/stub_renderable.h"
517+#include "mir_test_doubles/null_display_buffer_compositor_factory.h"
518+#include "mir_test/spin_wait.h"
519
520 #include <unordered_map>
521 #include <unordered_set>
522 #include <thread>
523 #include <mutex>
524 #include <chrono>
525+#include <atomic>
526
527 #include <gmock/gmock.h>
528 #include <gtest/gtest.h>
529@@ -84,11 +88,15 @@
530 class StubScene : public mc::Scene
531 {
532 public:
533- StubScene() : callback{[]{}} {}
534+ StubScene(mg::RenderableList const& list)
535+ : callback{[]{}}, renderable_list{list}
536+ {
537+ }
538+ StubScene() : StubScene(mg::RenderableList{}) {}
539
540 mg::RenderableList generate_renderable_list() const
541 {
542- return mg::RenderableList{};
543+ return renderable_list;
544 }
545
546 void set_change_callback(std::function<void()> const& f)
547@@ -114,6 +122,7 @@
548 private:
549 std::function<void()> callback;
550 std::mutex callback_mutex;
551+ mg::RenderableList renderable_list;
552 };
553
554 class RecordingDisplayBufferCompositor : public mc::DisplayBufferCompositor
555@@ -286,19 +295,24 @@
556 unsigned int render_count;
557 };
558
559-class NullDisplayBufferCompositorFactory : public mc::DisplayBufferCompositorFactory
560+class BufferCountingRenderable : public mtd::StubRenderable
561 {
562 public:
563- std::unique_ptr<mc::DisplayBufferCompositor> create_compositor_for(mg::DisplayBuffer&)
564- {
565- struct NullDisplayBufferCompositor : mc::DisplayBufferCompositor
566- {
567- bool composite() { return false; }
568- };
569-
570- auto raw = new NullDisplayBufferCompositor{};
571- return std::unique_ptr<NullDisplayBufferCompositor>(raw);
572- }
573+ BufferCountingRenderable() : buffers_requested_{0} {}
574+
575+ std::shared_ptr<mg::Buffer> buffer(void const*) const override
576+ {
577+ ++buffers_requested_;
578+ return std::make_shared<mtd::StubBuffer>();
579+ }
580+
581+ int buffers_requested() const
582+ {
583+ return buffers_requested_;
584+ }
585+
586+private:
587+ mutable std::atomic<int> buffers_requested_;
588 };
589
590 auto const null_report = mr::null_compositor_report();
591@@ -525,7 +539,7 @@
592
593 auto display = std::make_shared<StubDisplayWithMockBuffers>(nbuffers);
594 auto scene = std::make_shared<StubScene>();
595- auto db_compositor_factory = std::make_shared<NullDisplayBufferCompositorFactory>();
596+ auto db_compositor_factory = std::make_shared<mtd::NullDisplayBufferCompositorFactory>();
597 mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_report, true};
598
599 display->for_each_mock_buffer([](mtd::MockDisplayBuffer& mock_buf)
600@@ -542,17 +556,22 @@
601
602 TEST(MultiThreadedCompositor, double_start_or_stop_ignored)
603 {
604+ using namespace testing;
605+
606 unsigned int const nbuffers{3};
607 auto display = std::make_shared<StubDisplayWithMockBuffers>(nbuffers);
608 auto mock_scene = std::make_shared<mtd::MockScene>();
609- auto db_compositor_factory = std::make_shared<NullDisplayBufferCompositorFactory>();
610+ auto db_compositor_factory = std::make_shared<mtd::NullDisplayBufferCompositorFactory>();
611 auto mock_report = std::make_shared<testing::NiceMock<mtd::MockCompositorReport>>();
612 EXPECT_CALL(*mock_report, started())
613 .Times(1);
614 EXPECT_CALL(*mock_report, stopped())
615 .Times(1);
616- EXPECT_CALL(*mock_scene, set_change_callback(testing::_))
617+ EXPECT_CALL(*mock_scene, set_change_callback(_))
618 .Times(2);
619+ EXPECT_CALL(*mock_scene, generate_renderable_list())
620+ .Times(AtLeast(0))
621+ .WillRepeatedly(Return(mg::RenderableList{}));
622
623 mc::MultiThreadedCompositor compositor{display, mock_scene, db_compositor_factory, mock_report, true};
624
625@@ -561,3 +580,37 @@
626 compositor.stop();
627 compositor.stop();
628 }
629+
630+TEST(MultiThreadedCompositor, consumes_buffers_for_renderables_that_are_not_rendered)
631+{
632+ using namespace testing;
633+
634+ unsigned int const nbuffers{2};
635+ auto renderable = std::make_shared<BufferCountingRenderable>();
636+ auto display = std::make_shared<StubDisplay>(nbuffers);
637+ auto stub_scene = std::make_shared<StubScene>(mg::RenderableList{renderable});
638+ // We use NullDisplayBufferCompositors to simulate DisplayBufferCompositors
639+ // not rendering a renderable.
640+ auto db_compositor_factory = std::make_shared<mtd::NullDisplayBufferCompositorFactory>();
641+
642+ mc::MultiThreadedCompositor compositor{
643+ display, stub_scene, db_compositor_factory, null_report, true};
644+
645+ compositor.start();
646+
647+ mir::test::spin_wait_for_condition_or_timeout(
648+ [&] { return renderable->buffers_requested() == 1; },
649+ std::chrono::seconds{5});
650+
651+ EXPECT_THAT(renderable->buffers_requested(), Eq(1));
652+
653+ stub_scene->emit_change_event();
654+
655+ mir::test::spin_wait_for_condition_or_timeout(
656+ [&] { return renderable->buffers_requested() == 2; },
657+ std::chrono::seconds{5});
658+
659+ EXPECT_THAT(renderable->buffers_requested(), Eq(2));
660+
661+ compositor.stop();
662+}

Subscribers

People subscribed via source and target branches