Mir

Merge lp:~raof/mir/1hz-rendering-always into lp:mir

Proposed by Chris Halse Rogers
Status: Merged
Approved by: Alan Griffiths
Approved revision: no longer in the source branch.
Merged at revision: 1647
Proposed branch: lp:~raof/mir/1hz-rendering-always
Merge into: lp:mir
Prerequisite: lp:~andreas-pokorny/mir/add-timer-to-main-loop
Diff against target: 3043 lines (+1552/-378)
38 files modified
include/platform/mir/graphics/buffer_id.h (+1/-1)
include/server/mir/asio_main_loop.h (+2/-0)
include/server/mir/compositor/frame_dropping_policy.h (+69/-0)
include/server/mir/compositor/frame_dropping_policy_factory.h (+57/-0)
include/server/mir/default_server_configuration.h (+3/-0)
include/server/mir/time/timer.h (+8/-1)
include/test/mir_test/fake_clock.h (+72/-0)
include/test/mir_test/signal.h (+63/-0)
include/test/mir_test_doubles/mock_frame_dropping_policy_factory.h (+83/-0)
include/test/mir_test_doubles/mock_timer.h (+52/-0)
include/test/mir_test_doubles/stub_frame_dropping_policy_factory.h (+58/-0)
src/server/asio_main_loop.cpp (+23/-9)
src/server/compositor/CMakeLists.txt (+1/-0)
src/server/compositor/buffer_queue.cpp (+37/-1)
src/server/compositor/buffer_queue.h (+5/-1)
src/server/compositor/buffer_stream_factory.cpp (+6/-4)
src/server/compositor/buffer_stream_factory.h (+4/-4)
src/server/compositor/default_configuration.cpp (+15/-1)
src/server/compositor/multi_threaded_compositor.cpp (+53/-135)
src/server/compositor/timeout_frame_dropping_policy_factory.cpp (+92/-0)
src/server/compositor/timeout_frame_dropping_policy_factory.h (+56/-0)
tests/acceptance-tests/test_client_surface_swap_buffers.cpp (+8/-13)
tests/integration-tests/compositor/test_buffer_stream.cpp (+45/-15)
tests/integration-tests/compositor/test_swapping_swappers.cpp (+3/-1)
tests/integration-tests/graphics/android/test_buffer_integration.cpp (+4/-1)
tests/integration-tests/graphics/android/test_internal_client.cpp (+2/-1)
tests/mir_test/CMakeLists.txt (+2/-0)
tests/mir_test/fake_clock.cpp (+53/-0)
tests/mir_test/signal.cpp (+46/-0)
tests/mir_test_doubles/CMakeLists.txt (+2/-0)
tests/mir_test_doubles/mock_frame_dropping_policy_factory.cpp (+70/-0)
tests/mir_test_doubles/mock_timer.cpp (+149/-0)
tests/unit-tests/compositor/CMakeLists.txt (+1/-0)
tests/unit-tests/compositor/test_buffer_queue.cpp (+128/-46)
tests/unit-tests/compositor/test_multi_threaded_compositor.cpp (+15/-78)
tests/unit-tests/compositor/test_timeout_frame_dropping_policy.cpp (+186/-0)
tests/unit-tests/graphics/mesa/test_linux_virtual_terminal.cpp (+1/-0)
tests/unit-tests/test_asio_main_loop.cpp (+77/-66)
To merge this branch: bzr merge lp:~raof/mir/1hz-rendering-always
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Alan Griffiths Abstain
Daniel van Vugt Abstain
Andreas Pokorny (community) Approve
Alexandros Frantzis (community) Abstain
Alberto Aguirre (community) Approve
Review via email: mp+217377@code.launchpad.net

This proposal supersedes a proposal from 2014-04-17.

Commit message

SwitchingBundle: Allow clients to submit buffers at 1Hz, even when no compositing is taking place

Also resolves regressions - LP: #1308843 and LP: #1308844.

Description of the change

The alternate solution to eglSwapBuffers blocking; make SwitchingBufferBundle::client_acquire() block for no longer than a customisable timeout, or drop buffers.

Defaults to 1Hz swapping, but it should be obvious how we could wedge in arbitrarily interesting policy here.

To post a comment you must log in.
Revision history for this message
Chris Halse Rogers (raof) wrote : Posted in a previous version of this proposal

Remove BufferConsumingFunctor from compositor

Now that SwitchingBufferBundle hands out buffers at a minimum, configurable, rate, we no
longer need a fake display consuming buffers at fake vsync.

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: Needs Fixing (continuous-integration)
Revision history for this message
Robert Carr (robertcarr) wrote : Posted in a previous version of this proposal

Code looks fine at first pass...not sure I have entirely thought out all the implications though...maybe we can discuss them on IRC or you can provide some more context here?

Revision history for this message
Alberto Aguirre (albaguirre) wrote : Posted in a previous version of this proposal

796 + {
797 + if (nfree() <= 0)
798 + {
799 + while (nready == 0)
800 + cond.wait(lock);
801 +
802 + drop_frames(1);
803 + }
804 + complete_client_acquire(std::move(lock));
805 + }
806 + });

This could be private function.

review: Needs Fixing
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
Daniel van Vugt (vanvugt) wrote : Posted in a previous version of this proposal

(1) Interesting. Is 1Hz not too slow? Is that enough to solve the problem for music-app?

(2) Could you possibly avoid re-introducing timing to SwitchingBundle? It took a lot of effort to rid SwitchingBundle of all time-dependent logic, which I'm glad Alan made me do. And it's obviously more reliable not relying on timing. Or if you do then make sure it's mockable --> mir::time::Clock

(3) Does this solve other problems not explicitly mentioned? The timers plus larger code size makes me a bit nervous.

review: Needs Information
Revision history for this message
Robert Carr (robertcarr) wrote : Posted in a previous version of this proposal

Sorry still having trouble forming a total opinion as to what I think is best...will continue to ruminate.

I think Daniel is right about the time sources...if that were fixed maybe

+TEST_F(BufferStreamTest, client_swaps_at_at_least_1_Hz)

could be better and not depend on real time.

Revision history for this message
Chris Halse Rogers (raof) wrote : Posted in a previous version of this proposal

It's already mockable, via the mir::Timer interface, although I haven't used that mockability. I'll see how it looks without a mock timer.

@Daniel: (1) Gerry said 1Hz would be fast enough; the eventloop doesn't block immediately on renderloop blockage.

(2) I don't see how it's obviously more reliable not to depend on timing. This isn't changing the normal produce/consume case; this is adding a timeout on consume, which is inherently timing based. Alexandros' solution is *also* timing based, although in a different component.

(3) The actual code is not very big - if you remove the tests and test harness stuff, it's:
11 files changed, 307 insertions(+), 146 deletions(-)

If you only look at src/server/compositor:
 buffer_stream_factory.cpp | 8 +
 buffer_stream_factory.h | 6 -
 default_configuration.cpp | 4
 multi_threaded_compositor.cpp | 173 +++++++++++-------------------------------
 switching_bundle.cpp | 53 ++++++++++--
 switching_bundle.h | 13 ++-
 6 files changed, 113 insertions(+), 144 deletions(-)

It solves all the same problems solved by Alexandros' non-blocking-swapbuffers branches, and does so in a way that's cleaner (in my opinion, obviously) and more extensible (obvious to all right-thinking humans ☺).

Revision history for this message
Daniel van Vugt (vanvugt) wrote : Posted in a previous version of this proposal

Hmm. I disagree with adding new complexity and particularly the introduction of timing logic where previously there was none.

I think all the same problems are solved by the combination of two simpler branches:
https://code.launchpad.net/~vanvugt/mir/unocclude/+merge/216690
https://code.launchpad.net/~vanvugt/mir/judder/+merge/216694

Assuming the relevant bugs are indeed all fixable with such simpler solutions, I would prefer that. So this a soft disapproval. I may be out-voted...

review: Disapprove
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
Andreas Pokorny (andreas-pokorny) wrote : Posted in a previous version of this proposal

Nit: + 819 looks strangely indented - especially with the return statement directly afterwards

Verified with my local setup.

Overall the change does not seem to make SwitchingBundle itself more complex than it already is.

review: Approve
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
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
Alexandros Frantzis (afrantzis) wrote : Posted in a previous version of this proposal

A first pass:

88 +class Alarm
124 +class Timer

Delete CopyAssign.

401 + std::unique_lock<decltype(data->m)> lock(data->m);
412 + std::unique_lock<decltype(data->m)> lock(data->m);

We could use the slightly more efficient lock_guard<> since we don't use any of unique_lock's extra capabilities.

523 + the_main_loop());

Do we want to use the same MainLoop object for both the buffer drop timeouts and the display server main loop? Although I don't see a particular functional problem with doing so, it indicates an intention to serialize events from both sources, which is not really true.

On a related note, perhaps we need a better abstraction for this, one that hides all the timing details from our buffer mechanism, e.g.:

class FrameDroppingPolicy
{
    virtual std::unique_ptr<Alarm> schedule_frame_drop(std::function<void()> do_frame_drop) = 0;
};

SwitchingBundle(..., std::shared_ptr<FrameDroppingPolicy>, ...)

Needs Fixing (Needs discussion about the abstraction part)

review: Needs Fixing
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Chris Halse Rogers (raof) wrote :

1) I think we do want to use the same MainLoop object by default, although I could see having a separate the_timer() top-level and returning the mainloop from it might make the intent clearer.

2) I deliberately didn't use a more elaborate abstraction for the initial merge, but if we want to go the whole hog, sure.

In that case, I think the policy interface wants to be something like...

class FrameDroppingPolicy
{
    virtual void swap_now_blocking(std::function<void(void)> do_frame_drop) = 0;
    virtual void swap_unblocked() = 0;
    virtual bool frame_drop_pending() const = 0; /* Possibly don't need this */
};

The SwitchingBundle would then call swap_now_blocking(...) where we currently schedule the alarm, and swap_unblocked() from compositor_release(). If we decide that calling swap_unblocked() when there isn't a swap pending is an error, then we need frame_drop_pending() otherwise we can just unconditionally call swap_unblocked().

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alexandros Frantzis (afrantzis) wrote :

> In that case, I think the policy interface wants to be something like...

One decision we need to make (we can change it later of course), is whether we want a policy object per bundle or a single central policy object. The advantage of the later is that it allows us to change the drop timeout centrally, which could be useful if we want to set an optimal frame rate depending on the monitors present and power mode. For example we may want to set the drop rate as high as possible (but still below the smallest vsync rate for our timeout logic to work) when the device is plugged in, but drop to a lower rate for power saving modes.

Revision history for this message
Gerry Boland (gerboland) wrote :

I missed all the discussion about this, but could someone please summarize why this is needed?

Revision history for this message
Chris Halse Rogers (raof) wrote :

Gerry - it's the proper¹ fix for “swapbuffers shouldn't block indefinitely”, moving the responsibility for unblocking clients from the Compositor to the BufferBundle (where the actual blocking logic is).

¹: In my opinion, and it seems the general consensus.

Unless you mean the FrameDroppingPolicy discussion, which is thorourghly theoretical :).

Alexandros - can we split the difference with

virtual MirCookie swap_now_blocking(<>);
virtual swap_unblocked(MirCookie);

? That way we've (a) hidden the underlying Alarm implementation away, which would otherwise leak timeness, and (b) can choose between single-policy-object or policy-object-per-SwitchingBundle at DefaultConfiguration time.

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

45 +// Quite why C++11 doesn't have an existing std::semaphore is a mystery

I don't think it is so much a mystery as an artifact of the standards process. Essentially there wasn't an agreed proposal ready. IIRC there are things like barrier and semaphore in C++14.

In any case, doesn't the canonical semaphore take a count in initialization?

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

Looks sensible (apart from the following nits). But as it rips out some of Alexandros's stop-gap code I'd like to see his approval before it lands.

~~~~

36 +#ifndef MIR_SEMAPHORE_H_
37 +#define MIR_SEMAPHORE_H_

#ifndef MIR_TEST_FRAMEWORK_...

~~~~

54 + void wait(void);

In C++ we say "void wait();"

~~~~

113 + explicit BufferStreamFactory(const std::shared_ptr<graphics::GraphicBufferAllocator> &gralloc, const std::shared_ptr<time::Timer> timer);

s/const std::shared_ptr<time::Timer>/std::shared_ptr<time::Timer> const&/

review: Needs Fixing
Revision history for this message
Alberto Aguirre (albaguirre) wrote :

46 +class Semaphore

Perhaps BinarySemaphore?

The framedrop on timeout looks pretty straightforward to me. However I still maintain that the timeout should
only be enabled for the 2 conditions where we actually need it: when a surface is not visible on any display (due to a display off condition) and when a surface is occluded. But that's the FrameDroppingPolicy discussion, which I am okay coming in a separate MP.

~~~
TEST_F(SwitchingBundleTest, compositor_swapping_at_min_rate_gets_oldest_buffer)
~~~

Is this an implicit policy? oldest with respect to what? client-release?

review: Approve
Revision history for this message
Chris Halse Rogers (raof) wrote :

With respect to client-release, yes. The idea being that the compositor shouldn't skip frames - if it takes anything but the oldest frame it's going to have to *drop* all older frames, because it can't present frames out of order.

Should we add the ability to specify the presentation-time (such an API is entirely reasonable in EGL) this wouldn't be true, but then we'll need to rework the interfaces and implementation anyway.

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

I'm still uncomfortable with the introduction of timing, especially after I went to the trouble of removing all timing a while back. Solving timing problems without measuring time itself is always more robust and elegant, albeit more difficult to conceive. But without looking at the code...

(1) Needs fixing:
Text conflict in src/server/compositor/multi_threaded_compositor.cpp
Text conflict in tests/unit-tests/compositor/test_multi_threaded_compositor.cpp
2 conflicts encountered.

and then after hacking around those, manual testing seems OK.

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

Now BufferQueue has landed, lots more conflicts:

Text conflict in src/server/compositor/buffer_stream_factory.cpp
Text conflict in src/server/compositor/multi_threaded_compositor.cpp
Contents conflict in src/server/compositor/switching_bundle.cpp
Contents conflict in src/server/compositor/switching_bundle.h
Text conflict in tests/integration-tests/compositor/test_buffer_stream.cpp
Text conflict in tests/integration-tests/compositor/test_swapping_swappers.cpp
Text conflict in tests/integration-tests/graphics/android/test_buffer_integration.cpp
Text conflict in tests/unit-tests/compositor/test_buffer_queue.cpp
Text conflict in tests/unit-tests/compositor/test_multi_threaded_compositor.cpp
9 conflicts encountered.

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

Needs rebasing on BufferQueue

review: Needs Resubmitting
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: Needs Fixing (continuous-integration)
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

CI says:

[ 63%] /mir/tests/mir_test_doubles/mock_frame_dropping_policy_factory.cpp:19:65: fatal error: mir_test_doubles/mock_frame_dropping_policy_factory.h: No such file or directory
 #include "mir_test_doubles/mock_frame_dropping_policy_factory.h"

~~~~

315 +#endif // MIR_TEST_SEMAPHORE_H_

We're calling it Signal in this revision.

~~~~

2549 + MOCK_METHOD1(create_alarm, std::unique_ptr<mir::time::Alarm>(std::function<void(void)>));

Why create a mock method when we don't set expectations or behavior?

review: Needs Fixing
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel van Vugt (vanvugt) wrote :

The numbers make me worried: 3226 lines (+1567/-562) 41 files modified
Although it's a fairly serious bug so moving on...

(1) The "Policy" classes don't feel concrete enough to convince me that the design is quite right. I believe it's likely that needs rethinking, but don't yet understand the new architecture enough to make suggestions. Maybe next week.

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 :

No opinion until prerequisite is dealt with

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

Oops, "Needs fixing" is well out of date now.

review: Abstain
Revision history for this message
Alberto Aguirre (albaguirre) wrote :

~~~
1179 +void TimeoutFrameDroppingPolicy::swap_now_blocking()
1180 +{
1181 + if (pending_swaps++ == 0)
1182 + alarm->reschedule_in(timeout);
1183 +}
1184 +
1185 +void TimeoutFrameDroppingPolicy::swap_unblocked()
1186 +{
1187 + if (alarm->state() != mir::time::Alarm::Cancelled && alarm->cancel())
1188 + pending_swaps--;
1189 +}
1190 +}
~~~

Let's say the client calls client_acquire twice and there are no buffers available, so the two requests are delegated (pending_client_notifications.size == 2)

swap_now_blocking is called for the first delegated request (pending_swaps == 0) so an alarm is scheduled.
1181 + if (pending_swaps++ == 0)
1182 + alarm->reschedule_in(timeout);

The second call to swap_now_blocking does not create an alarm as pending_swaps == 1. That's ok.

Now let's say the first request is satisfied before the timeout expires- hence swap_unblocked is called and the alarm
is cancelled (pending_swaps == 1 and pending_client_notifications.size == 1)

Let's assume that after that point no requests will be satisfied (compositor blocked or stopped).

The client submits the buffer it got (client_release). The client has a pending request and there are no buffers available, but there is no alarm scheduled to drop the buffer so the client waits indefinitely.

In any case, I've started testing this MP with unity8 and the main case of having the music app respond to volume keys during screen power off works fine.

review: Needs Fixing
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 :
Download full text (4.0 KiB)

Conflicts with lp:~andreas-pokorny/mir/add-timer-to-main-loop

Text conflict in include/server/mir/asio_main_loop.h
Text conflict in include/server/mir/default_server_configuration.h
Text conflict in src/server/asio_main_loop.cpp
Text conflict in tests/mir_test/CMakeLists.txt
Text conflict in tests/unit-tests/test_asio_main_loop.cpp

~~~
2146 + clock->register_time_change_callback([state_copy](mt::FakeClock::time_point now)
2147 + {
2148 + if (state_copy->state == Pending)
2149 + {
2150 + if (now > state_copy->threshold)
2151 + {
2152 + state_copy->state = Triggered;
2153 + state_copy->callback();
2154 + return false;
2155 + }
2156 + return true;
2157 + }
2158 + else
2159 + {
2160 + return false;
2161 + }
2162 + });
~~~

This indentation looks awkward. Maybe a private method for that multi-line lambda?

~~~
2568 + auto buffers_swapped = std::make_shared<mt::Signal>();
2569 +
2570 + q.client_acquire([buffers_swapped](mg::Buffer*){ buffers_swapped->raise(); });
2571 +
2572 + EXPECT_FALSE(buffers_swapped->wait_for(std::chrono::milliseconds{100}));
2573 +
2574 + policy_factory.trigger_policies();
2575 + EXPECT_TRUE(buffers_swapped->wait_for(std::chrono::milliseconds{100}));
~~~

This can be replaced with:

---
auto handle = client_acquire_async(q);
EXPECT_THAT(handle->has_acquired_buffer(), Eq(false));
policy_factory.trigger_policies();
EXPECT_THAT(handle->has_acquired_buffer(), Eq(true));
---

~~~
2598 + auto first_swap = std::make_shared<mt::Signal>();
2599 + auto second_swap = std::make_shared<mt::Signal>();
2600 +
2601 + /* Queue up two pending swaps */
2602 + mg::Buffer* client_buf;
2603 + q.client_acquire([first_swap, &client_buf](mg::Buffer* buffer)
2604 + {
2605 + client_buf = buffer;
2606 + first_swap->raise();
2607 + });
2608 + q.client_acquire([second_swap](mg::Buffer*){ second_swap->raise(); });
2609 +
2610 + ASSERT_FALSE(first_swap->raised());
2611 + ASSERT_FALSE(second_swap->raised());
2612 +
2613 + q.compositor_acquire(nullptr);
2614 +
2615 + EXPECT_TRUE(first_swap->wait_for(std::chrono::milliseconds{100}));
2616 + EXPECT_FALSE(second_swap->raised());
2617 +
2618 + /* We have to release a client buffer here; framedropping or not,
2619 + * a client can't have 2 buffers outstanding in the nbuffers = 2 case.
2620 + */
2621 + q.cli...

Read more...

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

Text conflict in include/server/mir/asio_main_loop.h
Text conflict in include/server/mir/default_server_configuration.h
Text conflict in src/server/asio_main_loop.cpp
Text conflict in tests/mir_test/CMakeLists.txt
Text conflict in tests/unit-tests/test_asio_main_loop.cpp
5 conflicts encountered.

:)

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

Merge conflicts

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
Daniel van Vugt (vanvugt) :
review: Abstain
Revision history for this message
Andreas Pokorny (andreas-pokorny) wrote :

Made a first run through the MP:

There already is WaitObject and WaitCondition and now Signal. Two of them provide a boolean to indicate that the wait did not time out, while the first one throws. WaitCondition has Mock Actions, and provides seconds as duration format. Signal provides both a duration and a time point to wait for. Unifying that would be nice. From the three names Signal sounds best. WaitCondition is a bit off as it indicates that there is a condition somewhere.

What about:

inline int max_ownable_buffers(int number_of_available_buffers) { return number_of_available_buffers - 1; }

to avoid the new TODOs.

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

Verified both bugs are fixed.

I'm not a big fan of the new design. Particularly:

(1) framedrop_policy is an object that you modify? Making policy an object is one thing I don't like but can kind of understand. However, modifying a policy object on the fly doesn't sound like the design is quite right.

On the other hand, I am a big fan of fixing bug 1308843 and bug 1308844. And the minimal intrusion to the BufferQueue class is also nice.

review: Abstain (manual testing)
Revision history for this message
Alexandros Frantzis (afrantzis) wrote :

114 +class FrameDroppingPolicyFactory
115 +{

Delete CopyAssign (unless we want them for some reason?).

448 +class MockTimer : public mir::time::Timer

This is a FakeTimer not a MockTimer.

review: Needs Fixing
Revision history for this message
Alberto Aguirre (albaguirre) wrote :

LGTM

review: Approve
Revision history for this message
Chris Halse Rogers (raof) wrote :

@Alexandros: I don't think it _is_ a FakeTimer; it's a Timer that you set the behaviour of (although admittedly via an external parameter).

Updated anyway.

Revision history for this message
Chris Halse Rogers (raof) wrote :

I'll propose the merge of WaitObject, WaitCondition, and Signal in a follow-up branch. This is probably big enough :)

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 :

661 + //TODO: We have to framedrop, but we can't.
662 + //assert(), throw, or ignore?
663 + return;

Is this a request for discussion?

~~~~

61 +class FrameDroppingPolicy
...
70 + /**
71 + * \brief Notify that a swap has blocked
72 + */
73 + virtual void swap_now_blocking() = 0;
74 + /**
75 + * \brief Notify that previous swap is no longer blocking
76 + */
77 + virtual void swap_unblocked() = 0;

I don't immediately understand the role of a "policy" class that doesn't seem to be responsible for doing anything (it only has these two member functions). Based on the functions it has is a SwapBlockageObserver not a FrameDroppingPolicy.

Also based on the comments why isn't "swap_now_blocking()" called "swap_has_blocked()"? And "swap_unblocked()" "swap_has_unblocked()"?

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

Didn't get a chance to review properly today. Abstaining for now to unblock.

review: Abstain
Revision history for this message
Chris Halse Rogers (raof) wrote :

On Fri, May 16, 2014 at 9:57 PM, Alan Griffiths <email address hidden>
wrote:
> Review: Needs Information
>
> 661 + //TODO: We have to framedrop, but we can't.
> 662 + //assert(), throw, or ignore?
> 663 + return;
>
> Is this a request for discussion?

Yes, albeit a low-priority one; this case can't happen the way we
currently use BufferQueue, as it can only occur when a client is
requesting a buffer without first having submitted a buffer. Since we
only allow clients to have a single buffer outstanding, we don't
currently hit this.

Having said this, I think that means we should assert() so that we'll
pick this
up if and when we allow clients to claim multiple buffers.

>
> ~~~~
>
> 61 +class FrameDroppingPolicy
> ...
> 70 + /**
> 71 + * \brief Notify that a swap has blocked
> 72 + */
> 73 + virtual void swap_now_blocking() = 0;
> 74 + /**
> 75 + * \brief Notify that previous swap is no longer blocking
> 76 + */
> 77 + virtual void swap_unblocked() = 0;
>
> I don't immediately understand the role of a "policy" class that
> doesn't seem to be responsible for doing anything (it only has these
> two member functions). Based on the functions it has is a
> SwapBlockageObserver not a FrameDroppingPolicy.

It makes frame dropping decisions based upon notifications from the
buffer queue.

The mechanism it uses for dropping frames is set at creation time,
which is why it doesn't appear in the interface.

>
> Also based on the comments why isn't "swap_now_blocking()" called
> "swap_has_blocked()"? And "swap_unblocked()" "swap_has_unblocked()"?

Hm. I clearly need to reword the comment; it's swap_now_blocking()
because the blocking is ongoing, and will continue to be until the
policy decides to framedrop or the swap_unblocked() is called.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Andreas Pokorny (andreas-pokorny) wrote :

> [...]
> Having said this, I think that means we should assert() so that we'll
> pick this
> up if and when we allow clients to claim multiple buffers.

This not something a client can trigger in any way - so assert seems right. But It is not asserting right now?

Revision history for this message
Chris Halse Rogers (raof) wrote :

On Mon, May 19, 2014 at 5:54 PM, Andreas Pokorny
<email address hidden> wrote:
>> [...]
>> Having said this, I think that means we should assert() so that
>> we'll
>> pick this
>> up if and when we allow clients to claim multiple buffers.
>
> This not something a client can trigger in any way - so assert seems
> right. But It is not asserting right now?

I ended up deciding that it's not an actual problem. The only way to
trigger it is for the client to hold multiple buffers and then request
another one without submitting a previous buffer (or to use
single-buffering, but I continue to maintain that's inherently broken).

If the client already has a buffer but hasn't submitted it then it's
got something to render to, and so swap_buffers *shouldn't* block, so
we don't need to framedrop.

Revision history for this message
Andreas Pokorny (andreas-pokorny) :
review: Approve
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :
Download full text (4.3 KiB)

Apologies for meandering thoughts. But this is continuing the *Needs discussion*...

Short version: while I'm in agreement with the approach this branch proposes I've hung up on a lack of design clarity around "FrameDroppingPolicy" which isn't really a policy and doesn't encapsulates the framedropping logic.

> > 61 +class FrameDroppingPolicy
> > ...
> > 70 + /**
> > 71 + * \brief Notify that a swap has blocked
> > 72 + */
> > 73 + virtual void swap_now_blocking() = 0;
> > 74 + /**
> > 75 + * \brief Notify that previous swap is no longer blocking
> > 76 + */
> > 77 + virtual void swap_unblocked() = 0;
> >
> > I don't immediately understand the role of a "policy" class that
> > doesn't seem to be responsible for doing anything (it only has these
> > two member functions). Based on the functions it has is a
> > SwapBlockageObserver not a FrameDroppingPolicy.
>
> It makes frame dropping decisions based upon notifications from the
> buffer queue.
>
> The mechanism it uses for dropping frames is set at creation time,
> which is why it doesn't appear in the interface.

I don't think you've quite understood this concern. It isn't the role of TimeoutFrameDroppingPolicy I'm saying is unclear it is FrameDroppingPolicy that I'm doubtful about. The code that uses it looks like this:

711 + /* Can't give the client a buffer yet; they'll just have to wait
712 + * until the compositor is done with an old frame, or the policy
713 + * says they've waited long enough.
714 + */
715 + framedrop_policy->swap_now_blocking();

and

724 + framedrop_policy->swap_unblocked();
725 give_buffer_to_client(buffer, std::move(lock));

Neither fragment looks to me like a call to be handled by a "policy". (They look a bit more like state transitions - but if that that model is correct "enter_swap_blocked()/exit_swap_blocked() is probably clearer.)

Actually, BufferQueue still has:

    /* Last resort, drop oldest buffer from the ready queue */
    if (frame_dropping_enabled && !ready_to_composite_queue.empty())
    {
        auto const buffer = pop(ready_to_composite_queue);
        give_buffer_to_client(buffer, std::move(lock));

So we have both framedrop_policy and frame_dropping_enabled in BufferQueue. So we're not covering all the policy around framedropping in TimeoutFrameDroppingPolicy.

> > Also based on the comments why isn't "swap_now_blocking()" called
> > "swap_has_blocked()"? And "swap_unblocked()" "swap_has_unblocked()"?
>
> Hm. I clearly need to reword the comment; it's swap_now_blocking()
> because the blocking is ongoing, and will continue to be until the
> policy decides to framedrop or the swap_unblocked() is called.

I think the need for the comment (and that on 711..714) makes it clear that the current name is unsatifactory. C.f.

711 + /* Can't give the client a buffer yet; they'll just have to wait
712 + * until the compositor is done with an old frame, or the policy
713 + * says they've waited long enough.
714 + */
715 + framedrop_policy->handle_swap_buffer_unavailable();
...
724 + framedrop_policy->handle_swap_buffer_available();

However, the more I think about it the more I think the interaction between FrameDroppingPoli...

Read more...

review: Needs Information
Revision history for this message
Alberto Aguirre (albaguirre) wrote :

The encapsulation should be the handling of "delegated client_acquire requests" - i.e. requests that could not be fullfilled before client_acquire returned.

So perhaps a bit verbose but FrameDroppingPolicy interface should be BufferBundleObserver:

class BufferBundleObserver
{
public:
    virtual client_acquire_request_has_blocked() = 0;
    virtual client_acquire_request_has_been_fullfilled() = 0;
}

Then an external policy object can use that for it's state machine (and in the future combined with other external signals - such as display off) to decide to call force_requests_to_complete if need be - then no externalization of buffer release is needed.

Revision history for this message
Alberto Aguirre (albaguirre) wrote :

> class BufferBundleObserver
> {
> public:
> virtual client_acquire_request_has_blocked() = 0;
> virtual client_acquire_request_has_been_fullfilled() = 0;
> }

virtual client_acquire_request_fulfilled() = 0;

Revision history for this message
Chris Halse Rogers (raof) wrote :
Download full text (4.2 KiB)

On Mon, May 19, 2014 at 9:24 PM, Alan Griffiths <email address hidden>
wrote:
> Review: Needs Information
>
> Apologies for meandering thoughts. But this is continuing the *Needs
> discussion*...
>
> Short version: while I'm in agreement with the approach this branch
> proposes I've hung up on a lack of design clarity around
> "FrameDroppingPolicy" which isn't really a policy and doesn't
> encapsulates the framedropping logic.
>
>> > 61 +class FrameDroppingPolicy
>> > ...
>> > 70 + /**
>> > 71 + * \brief Notify that a swap has blocked
>> > 72 + */
>> > 73 + virtual void swap_now_blocking() = 0;
>> > 74 + /**
>> > 75 + * \brief Notify that previous swap is no longer blocking
>> > 76 + */
>> > 77 + virtual void swap_unblocked() = 0;
>> >
>> > I don't immediately understand the role of a "policy" class that
>> > doesn't seem to be responsible for doing anything (it only has
>> these
>> > two member functions). Based on the functions it has is a
>> > SwapBlockageObserver not a FrameDroppingPolicy.
>>
>> It makes frame dropping decisions based upon notifications from the
>> buffer queue.
>>
>> The mechanism it uses for dropping frames is set at creation time,
>> which is why it doesn't appear in the interface.
>
> I don't think you've quite understood this concern. It isn't the role
> of TimeoutFrameDroppingPolicy I'm saying is unclear it is
> FrameDroppingPolicy that I'm doubtful about. The code that uses it
> looks like this:
>
> 711 + /* Can't give the client a buffer yet; they'll just have to wait
> 712 + * until the compositor is done with an old frame, or the policy
> 713 + * says they've waited long enough.
> 714 + */
> 715 + framedrop_policy->swap_now_blocking();
>
> and
>
> 724 + framedrop_policy->swap_unblocked();
> 725 give_buffer_to_client(buffer, std::move(lock));
>
> Neither fragment looks to me like a call to be handled by a "policy".
> (They look a bit more like state transitions - but if that that model
> is correct "enter_swap_blocked()/exit_swap_blocked() is probably
> clearer.)

It is a state transition, yes, with the wrinkle that it's recursive;
you can enter_swap_blocked() while currently in a swap_blocked state.
If we go this way, enter_swap_blocked()/exit_swap_blocked() would be a
reasonable rename.

The policy needs to know the state, and when the state changes, so it
does indeed look like a state observer.

>
>
> Actually, BufferQueue still has:
>
> /* Last resort, drop oldest buffer from the ready queue */
> if (frame_dropping_enabled && !ready_to_composite_queue.empty())
> {
> auto const buffer = pop(ready_to_composite_queue);
> give_buffer_to_client(buffer, std::move(lock));
>
> So we have both framedrop_policy and frame_dropping_enabled in
> BufferQueue. So we're not covering all the policy around
> framedropping in TimeoutFrameDroppingPolicy.

Hm, this is true. How about if we added virtual void
client_requests_framedropping(bool); to FrameDroppingPolicy. Then
FrameDroppingPolicy *would* be responsible for all the framedropping
decisions?

> …
> class SwapBuffersPolicy
> {
> public:
> virtual void handle_...

Read more...

Revision history for this message
Chris Halse Rogers (raof) wrote :

On Tue, May 20, 2014 at 5:31 AM, Alberto Aguirre
<email address hidden> wrote:
> The encapsulation should be the handling of "delegated client_acquire
> requests" - i.e. requests that could not be fullfilled before
> client_acquire returned.
>
> So perhaps a bit verbose but FrameDroppingPolicy interface should be
> BufferBundleObserver:
>
> class BufferBundleObserver
> {
> public:
> virtual client_acquire_request_has_blocked() = 0;
> virtual client_acquire_request_has_been_fullfilled() = 0;
> }
>
> Then an external policy object can use that for it's state machine
> (and in the future combined with other external signals - such as
> display off)

The external policy object would need to *implement* this interface
(or, alternatively and redundantly, use a BufferBundleObserver that
forwards these calls) because the policy's state depends not only on
the state of the buffer but the timing of the state changes - we want
to drop a frame 1s after client_acquire_request_has_blocked().

If the FrameDroppingPolicy interface acquires sufficiently disparate
notifications like this we can split out observer interfaces for it to
implement. At the moment, I think that's premature abstraction.

> to decide to call force_requests_to_complete if need be - then no
> externalization of buffer release is needed.

I don't think it can call force_requests_to_complete - that's for
different use case, and throws away *all* client rendering, rather than
just the oldest frame.

We could add an explicit drop_frame interface to BufferQueue, though.

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

Trying to summarize my understanding from the discussions going on.

1. the class FrameDroppingPolicy is really a SwapBuffersStateObserver with odd method names.

2. FrameDroppingPolicy et alia fail to place all the frame dropping algorithm(s) into the policy object (and BitsOfFrameDroppingPolicy isn't a nice name)

3. Is the policy really about "frame dropping"? Or is it really about how buffers are swapped (with frame dropping an "implementation detail" that has leaked).

~~~~

Given the amount of discussion it is causing I have to ask - why do we need a frame dropping policy? And a factory for the policy?

This uncertainty has the feel of premature generalization: Is "plugging" this particular subset of the logic actually useful for something we need to implement now (or a prerequisite for a near term goal)?

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

Text conflict in tests/unit-tests/test_asio_main_loop.cpp
1 conflicts encountered.

review: Needs Fixing
Revision history for this message
Chris Halse Rogers (raof) wrote :

1. While “SwapBuffersStateObserver” is not an incorrect name, I don't think it's a *useful* name; it tells you information you can easily deduce from the names of the methods in the interface, but doesn't tell you what the class is expected to do.

2. Yes, although it'll be easy enough to fold the last piece of FrameDroppingPolicy into the class.

3. Yes, the policy is about framedropping. To expand:

The top-level problem this solves is ‘we don't want mir_surface_swap_buffers to block indefinitely’. Given that frames may remain undisplayed indefinitely - such as when the surface is entirely obscured or the screen is off - this means that either we allocate and return new buffers indefinitely or we drop frames.

Unbounded memory use is obviously not acceptable, so we've chosen to drop frames. When to drop frames (currently only in this case, but easily implemented in general) is the purview of FrameDroppingPolicy.

~~~~~~~

No, this particular piece of abstraction is not necessary for anything we need to implement now, nor is a prerequisite for a near-term goal. This would have been just as effectively implemented by the MP circa one month ago, where I deliberately implemented the simplest solution. (https://code.launchpad.net/~raof/mir/1hz-rendering-always/+merge/217377/comments/516869)

That said, seems a fairly obvious piece of abstraction to me - although this MP suggests it's not obvious to others :).

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

> 1. While “SwapBuffersStateObserver” is not an incorrect name, I don't think
> it's a *useful* name; it tells you information you can easily deduce from the
> names of the methods in the interface, but doesn't tell you what the class is
> expected to do.

I guess we disagree about what detail can be abstracted away and what is essential. The code calling "swap_now_blocking()/enter_swap_blocked()" needs to know about the "observer" - it doesn't need to know about framedropping.

> 2. Yes, although it'll be easy enough to fold the last piece of
> FrameDroppingPolicy into the class.

There are two pieces, but agreed.

> 3. Yes, the policy is about framedropping. To expand:
>
> The top-level problem this solves is ‘we don't want mir_surface_swap_buffers
> to block indefinitely’. Given that frames may remain undisplayed indefinitely
> - such as when the surface is entirely obscured or the screen is off - this
> means that either we allocate and return new buffers indefinitely or we drop
> frames.
>
> Unbounded memory use is obviously not acceptable, so we've chosen to drop
> frames. When to drop frames (currently only in this case, but easily
> implemented in general) is the purview of FrameDroppingPolicy.

I understand the problem and accept the solution. But I'm not convinced it makes sense splitting out just the framedropping logic from the rest of the swap buffers algorithm.

> No, this particular piece of abstraction is not necessary for anything we need
> to implement now, nor is a prerequisite for a near-term goal. This would have
> been just as effectively implemented by the MP circa one month ago, where I
> deliberately implemented the simplest solution.
> (https://code.launchpad.net/~raof/mir/1hz-rendering-
> always/+merge/217377/comments/516869)
>
> That said, seems a fairly obvious piece of abstraction to me - although this
> MP suggests it's not obvious to others :).

Quite.

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

I would be OK with merging the policy into the BufferQueue if it would unblock us, as long as it is well abstracted in there too. That is, it would ideally be in a state where it would be trivial to pull the functionality out, not so much because I want to pull it out at the moment, but mostly to ensure that all the details are properly hidden away.

BTW, one reason we may need to have an external policy class, is, as mentioned before, to be able to dynamically change the drop rate depending on the vsync rates of the connected outputs.

Revision history for this message
Chris Halse Rogers (raof) wrote :

We don't actually need to change the drop rate depending on the vsync rates, though? In the unlikely event of encountering a display with vsync > 1s, the rendering will remain correct; you just won't see every frame.

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

> We don't actually need to change the drop rate depending on the vsync rates,
> though? In the unlikely event of encountering a display with vsync > 1s, the
> rendering will remain correct; you just won't see every frame.

Note: I am referring to desktop use cases, where we (probably) won't be pausing apps when they are unfocused.

We may want to drop with the highest rate that keeps the algorithm working (i.e. less than the vsync rates) to ensure smooth rendering and reduce stale frame issues (imagine a window being occluded and unoccluded while other windows are being moved over it). We also may want to reduce the drop rate when we are on battery in order to reduce power consumption.

Having an external policy allows the shell to make such decisions instead of hardcoding them in Mir.

As I said, I would be OK with merging the policy in, but I am also OK with the current approach.

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

> Note: I am referring to desktop use cases, where we (probably) won't be
> pausing apps when they are unfocused.

s/unfocused/occluded/

Revision history for this message
Chris Halse Rogers (raof) wrote :

I think there's broad support for something looking much like this; shall we land this now and I'll do extra cleanup in a follow-on branch?

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Daniel van Vugt (vanvugt) :
review: Abstain
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

I won't reiterate my concerns with the naming of FrameDroppingPolicy and its member functions or about the abstraction it represents. (Oh! I guess I just did.)

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

Top approving as we're not going to get a better consensus by delaying.

Revision history for this message
PS Jenkins bot (ps-jenkins) :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'include/platform/mir/graphics/buffer_id.h'
2--- include/platform/mir/graphics/buffer_id.h 2013-08-28 03:41:48 +0000
3+++ include/platform/mir/graphics/buffer_id.h 2014-05-21 00:42:11 +0000
4@@ -27,7 +27,7 @@
5 class BufferID
6 {
7 public:
8- BufferID() : value(id_invalid){}
9+ BufferID() noexcept: value(id_invalid){}
10 explicit BufferID(uint32_t val) : value(val) {}
11 bool is_valid() const { return (id_invalid != value); }
12 uint32_t as_uint32_t() const { return value; };
13
14=== modified file 'include/server/mir/asio_main_loop.h'
15--- include/server/mir/asio_main_loop.h 2014-05-19 02:55:29 +0000
16+++ include/server/mir/asio_main_loop.h 2014-05-21 00:42:11 +0000
17@@ -53,6 +53,8 @@
18 std::function<void()> callback) override;
19 std::unique_ptr<time::Alarm> notify_at(mir::time::Timestamp time_point,
20 std::function<void()> callback) override;
21+ std::unique_ptr<time::Alarm> create_alarm(std::function<void()> callback) override;
22+
23 void enqueue(void const* owner, ServerAction const& action);
24 void pause_processing_for(void const* owner);
25 void resume_processing_for(void const* owner);
26
27=== added file 'include/server/mir/compositor/frame_dropping_policy.h'
28--- include/server/mir/compositor/frame_dropping_policy.h 1970-01-01 00:00:00 +0000
29+++ include/server/mir/compositor/frame_dropping_policy.h 2014-05-21 00:42:11 +0000
30@@ -0,0 +1,69 @@
31+/*
32+ * Copyright © 2014 Canonical Ltd.
33+ *
34+ * This program is free software: you can redistribute it and/or modify it
35+ * under the terms of the GNU General Public License version 3,
36+ * as published by the Free Software Foundation.
37+ *
38+ * This program is distributed in the hope that it will be useful,
39+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
40+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
41+ * GNU General Public License for more details.
42+ *
43+ * You should have received a copy of the GNU General Public License
44+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
45+ *
46+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
47+ */
48+
49+#ifndef MIR_COMPOSITOR_FRAME_DROPPING_POLICY_H_
50+#define MIR_COMPOSITOR_FRAME_DROPPING_POLICY_H_
51+
52+#include <functional>
53+
54+namespace mir
55+{
56+namespace compositor
57+{
58+/**
59+ * \brief Policy to determine when to drop a frame from a client
60+ *
61+ * The FrameDroppingPolicy objects are constructed from a
62+ * \ref FrameDroppingPolicyFactory
63+ *
64+ * The frame dropping mechanism is provided as the
65+ * \a drop_frames argument of \ref FrameDroppingPolicyFactory::create_policy
66+ *
67+ * The policy may decide to drop a frame any time that there is an outstanding
68+ * swap - namely, when there have been more calls to \ref swap_now_blocking
69+ * than to \ref swap_unblocked
70+ */
71+class FrameDroppingPolicy
72+{
73+public:
74+ virtual ~FrameDroppingPolicy() = default;
75+
76+ FrameDroppingPolicy(FrameDroppingPolicy const&) = delete;
77+ FrameDroppingPolicy& operator=(FrameDroppingPolicy const&) = delete;
78+
79+ /**
80+ * \brief Notify that a swap is now blocking
81+ */
82+ virtual void swap_now_blocking() = 0;
83+ /**
84+ * \brief Notify that previous swap is no longer blocking
85+ */
86+ virtual void swap_unblocked() = 0;
87+
88+protected:
89+ /**
90+ * \note FrameDroppingPolicies should not be constructed directly;
91+ * use a \ref FrameDroppingPolicyFactory
92+ */
93+ FrameDroppingPolicy() = default;
94+};
95+
96+}
97+}
98+
99+#endif // MIR_COMPOSITOR_FRAME_DROPPING_POLICY_H_
100
101=== added file 'include/server/mir/compositor/frame_dropping_policy_factory.h'
102--- include/server/mir/compositor/frame_dropping_policy_factory.h 1970-01-01 00:00:00 +0000
103+++ include/server/mir/compositor/frame_dropping_policy_factory.h 2014-05-21 00:42:11 +0000
104@@ -0,0 +1,57 @@
105+/*
106+ * Copyright © 2014 Canonical Ltd.
107+ *
108+ * This program is free software: you can redistribute it and/or modify it
109+ * under the terms of the GNU General Public License version 3,
110+ * as published by the Free Software Foundation.
111+ *
112+ * This program is distributed in the hope that it will be useful,
113+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
114+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
115+ * GNU General Public License for more details.
116+ *
117+ * You should have received a copy of the GNU General Public License
118+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
119+ *
120+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
121+ */
122+
123+#ifndef MIR_COMPOSITOR_FRAME_DROPPING_POLICY_FACTORY_H_
124+#define MIR_COMPOSITOR_FRAME_DROPPING_POLICY_FACTORY_H_
125+
126+#include <memory>
127+
128+namespace mir
129+{
130+namespace compositor
131+{
132+class FrameDroppingPolicy;
133+
134+/**
135+ * \brief Creator of FrameDroppingPolicies
136+ *
137+ * The FrameDroppingPolicyFactory is how you go from a means of dropping frames -
138+ * the \a drop_frames parameter of \ref create_policy -
139+ * to a \ref FrameDroppingPolicy
140+ */
141+class FrameDroppingPolicyFactory
142+{
143+public:
144+ FrameDroppingPolicyFactory() = default;
145+ virtual ~FrameDroppingPolicyFactory() = default;
146+
147+ FrameDroppingPolicyFactory(FrameDroppingPolicyFactory const&) = delete;
148+ FrameDroppingPolicyFactory& operator=(FrameDroppingPolicyFactory const&) = delete;
149+
150+ /**
151+ * \brief Create a FrameDroppingPolicy that will call \a drop_frame when it decides to drop a frame
152+ * \param drop_frame Function to call when a frame needs to be dropped
153+ * \return The policy object.
154+ */
155+ virtual std::unique_ptr<FrameDroppingPolicy> create_policy(std::function<void(void)> drop_frame) const = 0;
156+};
157+
158+}
159+}
160+
161+#endif // MIR_COMPOSITOR_FRAME_DROPPING_POLICY_FACTORY_H_
162
163=== modified file 'include/server/mir/default_server_configuration.h'
164--- include/server/mir/default_server_configuration.h 2014-05-20 08:38:05 +0000
165+++ include/server/mir/default_server_configuration.h 2014-05-21 00:42:11 +0000
166@@ -46,6 +46,7 @@
167 class Compositor;
168 class RendererFactory;
169 class CompositorReport;
170+class FrameDroppingPolicyFactory;
171 }
172 namespace frontend
173 {
174@@ -195,6 +196,7 @@
175 * @{ */
176 virtual std::shared_ptr<graphics::GraphicBufferAllocator> the_buffer_allocator();
177 virtual std::shared_ptr<compositor::Scene> the_scene();
178+ virtual std::shared_ptr<compositor::FrameDroppingPolicyFactory> the_frame_dropping_policy_factory();
179 /** @} */
180
181 /** @name frontend configuration - dependencies
182@@ -321,6 +323,7 @@
183 CachedPtr<frontend::Screencast> screencast;
184 CachedPtr<compositor::RendererFactory> renderer_factory;
185 CachedPtr<compositor::BufferStreamFactory> buffer_stream_factory;
186+ CachedPtr<compositor::FrameDroppingPolicyFactory> frame_dropping_policy_factory;
187 CachedPtr<scene::SurfaceStack> surface_stack;
188 CachedPtr<scene::SceneReport> scene_report;
189
190
191=== modified file 'include/server/mir/time/timer.h'
192--- include/server/mir/time/timer.h 2014-04-24 13:58:18 +0000
193+++ include/server/mir/time/timer.h 2014-05-21 00:42:11 +0000
194@@ -58,7 +58,14 @@
195 */
196 virtual std::unique_ptr<Alarm> notify_at(Timestamp time_point,
197 std::function<void()> callback) = 0;
198-
199+ /**
200+ * \brief Create an Alarm that will not fire until scheduled
201+ *
202+ * \param callback Function to call when the Alarm signals
203+ *
204+ * \return A handle to an Alarm that can later be scheduled
205+ */
206+ virtual std::unique_ptr<Alarm> create_alarm(std::function<void()> callback) = 0;
207
208 Timer(Timer const&) = delete;
209 Timer& operator=(Timer const&) = delete;
210
211=== added file 'include/test/mir_test/fake_clock.h'
212--- include/test/mir_test/fake_clock.h 1970-01-01 00:00:00 +0000
213+++ include/test/mir_test/fake_clock.h 2014-05-21 00:42:11 +0000
214@@ -0,0 +1,72 @@
215+/*
216+ * Copyright © 2014 Canonical Ltd.
217+ *
218+ * This program is free software: you can redistribute it and/or modify it
219+ * under the terms of the GNU General Public License version 3,
220+ * as published by the Free Software Foundation.
221+ *
222+ * This program is distributed in the hope that it will be useful,
223+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
224+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
225+ * GNU General Public License for more details.
226+ *
227+ * You should have received a copy of the GNU General Public License
228+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
229+ *
230+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
231+ */
232+
233+#ifndef MIR_TEST_FAKE_CLOCK_H_
234+#define MIR_TEST_FAKE_CLOCK_H_
235+
236+#include <chrono>
237+#include <functional>
238+#include <list>
239+
240+namespace mir
241+{
242+namespace test
243+{
244+/**
245+ * @brief An invasive time source for Mocks/Stubs/Fakes that depend on timing
246+ */
247+class FakeClock
248+{
249+public:
250+ typedef std::chrono::nanoseconds duration;
251+ typedef duration::rep rep;
252+ typedef duration::period period;
253+ typedef std::chrono::time_point<FakeClock, duration> time_point;
254+
255+ static constexpr bool is_steady = false;
256+ time_point now() const;
257+
258+ FakeClock();
259+ /**
260+ * \brief Advance the fake clock
261+ * \note Advancing by a negative duration will move the clock backwards
262+ */
263+ template<typename rep, typename period>
264+ void advance_time(std::chrono::duration<rep, period> by)
265+ {
266+ advance_time_ns(std::chrono::duration_cast<std::chrono::nanoseconds>(by));
267+ }
268+
269+ /**
270+ * \brief Register an event callback when the time is changed
271+ * \param cb Function to call when the time is changed.
272+ * This function is called with the new time.
273+ * If the function returns false, it will no longer be called
274+ * on subsequent time changes.
275+ */
276+ void register_time_change_callback(std::function<bool(time_point)> cb);
277+private:
278+ void advance_time_ns(std::chrono::nanoseconds by);
279+ std::chrono::nanoseconds current_time;
280+ std::list<std::function<bool(time_point)>> callbacks;
281+};
282+
283+}
284+}
285+
286+#endif // MIR_TEST_FAKE_CLOCK_H_
287
288=== added file 'include/test/mir_test/signal.h'
289--- include/test/mir_test/signal.h 1970-01-01 00:00:00 +0000
290+++ include/test/mir_test/signal.h 2014-05-21 00:42:11 +0000
291@@ -0,0 +1,63 @@
292+/*
293+ * Copyright © 2014 Canonical Ltd.
294+ *
295+ * This program is free software: you can redistribute it and/or modify
296+ * it under the terms of the GNU General Public License version 3 as
297+ * published by the Free Software Foundation.
298+ *
299+ * This program is distributed in the hope that it will be useful,
300+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
301+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
302+ * GNU General Public License for more details.
303+ *
304+ * You should have received a copy of the GNU General Public License
305+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
306+ *
307+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
308+ */
309+
310+#ifndef MIR_TEST_SIGNAL_H_
311+#define MIR_TEST_SIGNAL_H_
312+
313+#include <condition_variable>
314+#include <chrono>
315+#include <mutex>
316+
317+namespace mir
318+{
319+namespace test
320+{
321+/**
322+ * @brief A threadsafe, waitable signal
323+ */
324+class Signal
325+{
326+public:
327+ Signal();
328+
329+ void raise();
330+ bool raised();
331+
332+ void wait();
333+ template<typename rep, typename period>
334+ bool wait_for(std::chrono::duration<rep, period> delay)
335+ {
336+ std::unique_lock<decltype(mutex)> lock(mutex);
337+ return cv.wait_for(lock, delay, [this]() { return signalled; });
338+ }
339+ template<class Clock, class Duration>
340+ bool wait_until(std::chrono::time_point<Clock, Duration> const& time)
341+ {
342+ std::unique_lock<decltype(mutex)> lock(mutex);
343+ return cv.wait_until(lock, time, [this]() { return signalled; });
344+ }
345+
346+private:
347+ std::mutex mutex;
348+ std::condition_variable cv;
349+ bool signalled;
350+};
351+}
352+}
353+
354+#endif // MIR_TEST_SIGNAL_H_
355
356=== added file 'include/test/mir_test_doubles/mock_frame_dropping_policy_factory.h'
357--- include/test/mir_test_doubles/mock_frame_dropping_policy_factory.h 1970-01-01 00:00:00 +0000
358+++ include/test/mir_test_doubles/mock_frame_dropping_policy_factory.h 2014-05-21 00:42:11 +0000
359@@ -0,0 +1,83 @@
360+/*
361+ * Copyright © 2014 Canonical Ltd.
362+ *
363+ * This program is free software: you can redistribute it and/or modify it
364+ * under the terms of the GNU General Public License version 3,
365+ * as published by the Free Software Foundation.
366+ *
367+ * This program is distributed in the hope that it will be useful,
368+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
369+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
370+ * GNU General Public License for more details.
371+ *
372+ * You should have received a copy of the GNU General Public License
373+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
374+ *
375+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
376+ */
377+
378+
379+#ifndef MIR_TEST_DOUBLES_MOCK_FRAME_DROPPING_POLICY_FACTORY_H_
380+#define MIR_TEST_DOUBLES_MOCK_FRAME_DROPPING_POLICY_FACTORY_H_
381+
382+#include "mir/compositor/frame_dropping_policy_factory.h"
383+#include "mir/compositor/frame_dropping_policy.h"
384+
385+#include <unordered_set>
386+#include <functional>
387+
388+#include <gmock/gmock.h>
389+
390+namespace mc = mir::compositor;
391+
392+namespace mir
393+{
394+namespace test
395+{
396+namespace doubles
397+{
398+
399+class MockFrameDroppingPolicyFactory;
400+
401+class MockFrameDroppingPolicy : public mc::FrameDroppingPolicy
402+{
403+public:
404+ MockFrameDroppingPolicy(std::function<void(void)> callback,
405+ MockFrameDroppingPolicyFactory const* parent);
406+ ~MockFrameDroppingPolicy();
407+
408+ MOCK_METHOD0(swap_now_blocking, void(void));
409+ MOCK_METHOD0(swap_unblocked, void(void));
410+
411+ void trigger();
412+
413+private:
414+ friend class MockFrameDroppingPolicyFactory;
415+ void parent_destroyed();
416+
417+ std::function<void(void)> callback;
418+ MockFrameDroppingPolicyFactory const* parent;
419+};
420+
421+class MockFrameDroppingPolicyFactory : public mc::FrameDroppingPolicyFactory
422+{
423+public:
424+ std::unique_ptr<mc::FrameDroppingPolicy> create_policy(std::function<void(void)> drop_frame) const override;
425+
426+ ~MockFrameDroppingPolicyFactory();
427+
428+ void trigger_policies() const;
429+
430+private:
431+ friend class MockFrameDroppingPolicy;
432+
433+ void policy_destroyed(MockFrameDroppingPolicy* policy) const;
434+ mutable std::unordered_set<MockFrameDroppingPolicy*> policies;
435+};
436+
437+}
438+}
439+}
440+
441+
442+#endif // MIR_TEST_DOUBLES_MOCK_FRAME_DROPPING_POLICY_FACTORY_H_
443
444=== added file 'include/test/mir_test_doubles/mock_timer.h'
445--- include/test/mir_test_doubles/mock_timer.h 1970-01-01 00:00:00 +0000
446+++ include/test/mir_test_doubles/mock_timer.h 2014-05-21 00:42:11 +0000
447@@ -0,0 +1,52 @@
448+/*
449+ * Copyright © 2014 Canonical Ltd.
450+ *
451+ * This program is free software: you can redistribute it and/or modify it
452+ * under the terms of the GNU General Public License version 3,
453+ * as published by the Free Software Foundation.
454+ *
455+ * This program is distributed in the hope that it will be useful,
456+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
457+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
458+ * GNU General Public License for more details.
459+ *
460+ * You should have received a copy of the GNU General Public License
461+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
462+ *
463+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
464+ */
465+
466+#ifndef MIR_TEST_DOUBLES_MOCK_TIMER_H_
467+#define MIR_TEST_DOUBLES_MOCK_TIMER_H_
468+
469+#include "mir/time/timer.h"
470+#include "mir_test/fake_clock.h"
471+#include <memory>
472+
473+namespace mir
474+{
475+namespace test
476+{
477+namespace doubles
478+{
479+
480+class FakeTimer : public mir::time::Timer
481+{
482+public:
483+ FakeTimer(std::shared_ptr<FakeClock> const& clock);
484+
485+ std::unique_ptr<time::Alarm> notify_in(std::chrono::milliseconds delay,
486+ std::function<void(void)> callback) override;
487+ std::unique_ptr<time::Alarm> notify_at(time::Timestamp time_point,
488+ std::function<void(void)> callback) override;
489+ std::unique_ptr<time::Alarm> create_alarm(std::function<void ()> callback) override;
490+
491+private:
492+ std::shared_ptr<FakeClock> const clock;
493+};
494+
495+}
496+}
497+}
498+
499+#endif // MIR_TEST_DOUBLES_MOCK_TIMER_H_
500
501=== added file 'include/test/mir_test_doubles/stub_frame_dropping_policy_factory.h'
502--- include/test/mir_test_doubles/stub_frame_dropping_policy_factory.h 1970-01-01 00:00:00 +0000
503+++ include/test/mir_test_doubles/stub_frame_dropping_policy_factory.h 2014-05-21 00:42:11 +0000
504@@ -0,0 +1,58 @@
505+/*
506+ * Copyright © 2014 Canonical Ltd.
507+ *
508+ * This program is free software: you can redistribute it and/or modify it
509+ * under the terms of the GNU General Public License version 3,
510+ * as published by the Free Software Foundation.
511+ *
512+ * This program is distributed in the hope that it will be useful,
513+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
514+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
515+ * GNU General Public License for more details.
516+ *
517+ * You should have received a copy of the GNU General Public License
518+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
519+ *
520+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
521+ */
522+
523+
524+#ifndef MIR_TEST_DOUBLES_STUB_FRAME_DROPPING_POLICY_FACTORY_H_
525+#define MIR_TEST_DOUBLES_STUB_FRAME_DROPPING_POLICY_FACTORY_H_
526+
527+#include "mir/compositor/frame_dropping_policy_factory.h"
528+#include "mir/compositor/frame_dropping_policy.h"
529+
530+namespace mc = mir::compositor;
531+
532+namespace mir
533+{
534+namespace test
535+{
536+namespace doubles
537+{
538+
539+class StubFrameDroppingPolicy : public mc::FrameDroppingPolicy
540+{
541+public:
542+ void swap_now_blocking()
543+ {
544+ }
545+ void swap_unblocked()
546+ {
547+ }
548+};
549+
550+class StubFrameDroppingPolicyFactory : public mc::FrameDroppingPolicyFactory
551+{
552+public:
553+ std::unique_ptr<mc::FrameDroppingPolicy> create_policy(std::function<void(void)>) const override
554+ {
555+ return std::unique_ptr<mc::FrameDroppingPolicy>{new StubFrameDroppingPolicy};
556+ }
557+};
558+
559+}
560+}
561+}
562+#endif // TEST_DOUBLES_STUB_FRAME_DROPPING_POLICY_FACTORY_H_
563
564=== modified file 'src/server/asio_main_loop.cpp'
565--- src/server/asio_main_loop.cpp 2014-05-19 02:55:29 +0000
566+++ src/server/asio_main_loop.cpp 2014-05-21 00:42:11 +0000
567@@ -172,6 +172,9 @@
568 mir::time::Timestamp time_point,
569 std::function<void(void)> callback);
570
571+ AlarmImpl(boost::asio::io_service& io,
572+ std::function<void(void)> callback);
573+
574 ~AlarmImpl() noexcept override;
575
576 bool cancel() override;
577@@ -200,8 +203,7 @@
578 AlarmImpl::AlarmImpl(boost::asio::io_service& io,
579 std::chrono::milliseconds delay,
580 std::function<void ()> callback)
581- : timer{io},
582- data{std::make_shared<InternalState>(callback)}
583+ : AlarmImpl(io, callback)
584 {
585 reschedule_in(delay);
586 }
587@@ -209,12 +211,19 @@
588 AlarmImpl::AlarmImpl(boost::asio::io_service& io,
589 mir::time::Timestamp time_point,
590 std::function<void ()> callback)
591- : timer{io},
592- data{std::make_shared<InternalState>(callback)}
593+ : AlarmImpl(io, callback)
594 {
595 reschedule_for(time_point);
596 }
597
598+AlarmImpl::AlarmImpl(boost::asio::io_service& io,
599+ std::function<void(void)> callback)
600+ : timer{io},
601+ data{std::make_shared<InternalState>(callback)}
602+{
603+ data->state = triggered;
604+}
605+
606 AlarmImpl::~AlarmImpl() noexcept
607 {
608 AlarmImpl::cancel();
609@@ -247,10 +256,10 @@
610
611 bool AlarmImpl::reschedule_for(mir::time::Timestamp time_point)
612 {
613+ auto boost_epoch = boost::posix_time::from_time_t(0);
614+ auto microseconds_since_epoch = std::chrono::duration_cast<std::chrono::microseconds>(time_point.time_since_epoch()).count();
615 bool cancelling =
616- timer.expires_at(boost::posix_time::from_time_t(
617- std::chrono::high_resolution_clock::to_time_t(time_point)
618- ));
619+ timer.expires_at(boost_epoch + boost::posix_time::microseconds{microseconds_since_epoch});
620 update_timer();
621 return cancelling;
622 }
623@@ -290,8 +299,13 @@
624 std::function<void()> callback)
625 {
626 return std::unique_ptr<mir::time::Alarm>{new AlarmImpl{io, time_point, callback}};
627-
628-}
629+}
630+
631+std::unique_ptr<mir::time::Alarm> mir::AsioMainLoop::create_alarm(std::function<void()> callback)
632+{
633+ return std::unique_ptr<mir::time::Alarm>{new AlarmImpl{io, callback}};
634+}
635+
636 void mir::AsioMainLoop::enqueue(void const* owner, ServerAction const& action)
637 {
638 {
639
640=== modified file 'src/server/compositor/CMakeLists.txt'
641--- src/server/compositor/CMakeLists.txt 2014-05-06 03:17:55 +0000
642+++ src/server/compositor/CMakeLists.txt 2014-05-21 00:42:11 +0000
643@@ -14,6 +14,7 @@
644 default_configuration.cpp
645 screencast_display_buffer.cpp
646 compositing_screencast.cpp
647+ timeout_frame_dropping_policy_factory.cpp
648 buffer_queue.cpp
649 )
650
651
652=== modified file 'src/server/compositor/buffer_queue.cpp'
653--- src/server/compositor/buffer_queue.cpp 2014-05-19 04:01:20 +0000
654+++ src/server/compositor/buffer_queue.cpp 2014-05-21 00:42:11 +0000
655@@ -23,6 +23,7 @@
656 #include <boost/throw_exception.hpp>
657 #include <stdexcept>
658 #include <algorithm>
659+#include <cassert>
660
661 namespace mc = mir::compositor;
662 namespace mg = mir::graphics;
663@@ -93,7 +94,8 @@
664 mc::BufferQueue::BufferQueue(
665 int nbuffers,
666 std::shared_ptr<graphics::GraphicBufferAllocator> const& gralloc,
667- graphics::BufferProperties const& props)
668+ graphics::BufferProperties const& props,
669+ mc::FrameDroppingPolicyFactory const& policy_provider)
670 : nbuffers{nbuffers},
671 frame_dropping_enabled{false},
672 the_properties{props},
673@@ -127,6 +129,30 @@
674 */
675 if (nbuffers == 1)
676 free_buffers.push_back(current_compositor_buffer);
677+
678+ framedrop_policy = policy_provider.create_policy([this]
679+ {
680+ std::unique_lock<decltype(guard)> lock{guard};
681+ assert(!pending_client_notifications.empty());
682+ if (ready_to_composite_queue.empty())
683+ {
684+ /*
685+ * NOTE: This can only happen under two circumstances:
686+ * 1) Client is single buffered. Don't do that, it's a bad idea.
687+ * 2) Client already has a buffer, and is asking for a new one
688+ * without submitting the old one.
689+ *
690+ * This shouldn't be exposed to the client as swap_buffers
691+ * blocking, as the client already has something to render to.
692+ *
693+ * Our current implementation will never hit this case. If we
694+ * later want clients to be able to own multiple buffers simultaneously
695+ * then we might want to add an entry to the CompositorReport here.
696+ */
697+ return;
698+ }
699+ give_buffer_to_client(pop(ready_to_composite_queue), std::move(lock));
700+ });
701 }
702
703 void mc::BufferQueue::client_acquire(mc::BufferQueue::Callback complete)
704@@ -161,7 +187,14 @@
705 {
706 auto const buffer = pop(ready_to_composite_queue);
707 give_buffer_to_client(buffer, std::move(lock));
708+ return;
709 }
710+
711+ /* Can't give the client a buffer yet; they'll just have to wait
712+ * until the compositor is done with an old frame, or the policy
713+ * says they've waited long enough.
714+ */
715+ framedrop_policy->swap_now_blocking();
716 }
717
718 void mc::BufferQueue::client_release(graphics::Buffer* released_buffer)
719@@ -388,7 +421,10 @@
720 std::unique_lock<std::mutex> lock)
721 {
722 if (!pending_client_notifications.empty())
723+ {
724+ framedrop_policy->swap_unblocked();
725 give_buffer_to_client(buffer, std::move(lock));
726+ }
727 else
728 free_buffers.push_back(buffer);
729 }
730
731=== modified file 'src/server/compositor/buffer_queue.h'
732--- src/server/compositor/buffer_queue.h 2014-05-19 02:55:29 +0000
733+++ src/server/compositor/buffer_queue.h 2014-05-21 00:42:11 +0000
734@@ -18,6 +18,8 @@
735 #ifndef MIR_BUFFER_QUEUE_H_
736 #define MIR_BUFFER_QUEUE_H_
737
738+#include "mir/compositor/frame_dropping_policy_factory.h"
739+#include "mir/compositor/frame_dropping_policy.h"
740 #include "buffer_bundle.h"
741
742 #include <mutex>
743@@ -42,7 +44,8 @@
744
745 BufferQueue(int nbuffers,
746 std::shared_ptr<graphics::GraphicBufferAllocator> const& alloc,
747- graphics::BufferProperties const& props);
748+ graphics::BufferProperties const& props,
749+ FrameDroppingPolicyFactory const& policy_provider);
750
751 void client_acquire(Callback complete) override;
752 void client_release(graphics::Buffer* buffer) override;
753@@ -81,6 +84,7 @@
754
755 int nbuffers;
756 bool frame_dropping_enabled;
757+ std::unique_ptr<FrameDroppingPolicy> framedrop_policy;
758 graphics::BufferProperties the_properties;
759
760 std::condition_variable snapshot_released;
761
762=== modified file 'src/server/compositor/buffer_stream_factory.cpp'
763--- src/server/compositor/buffer_stream_factory.cpp 2014-04-21 15:56:41 +0000
764+++ src/server/compositor/buffer_stream_factory.cpp 2014-05-21 00:42:11 +0000
765@@ -34,11 +34,13 @@
766 namespace mg = mir::graphics;
767 namespace ms = mir::scene;
768
769-mc::BufferStreamFactory::BufferStreamFactory(
770- const std::shared_ptr<mg::GraphicBufferAllocator> &gralloc)
771- : gralloc(gralloc)
772+mc::BufferStreamFactory::BufferStreamFactory(std::shared_ptr<mg::GraphicBufferAllocator> const& gralloc,
773+ std::shared_ptr<mc::FrameDroppingPolicyFactory> const& policy_factory)
774+ : gralloc(gralloc),
775+ policy_factory{policy_factory}
776 {
777 assert(gralloc);
778+ assert(policy_factory);
779 }
780
781
782@@ -46,6 +48,6 @@
783 mg::BufferProperties const& buffer_properties)
784 {
785 // Note: Framedropping and bypass both require a minimum 3 buffers
786- auto switching_bundle = std::make_shared<mc::BufferQueue>(3, gralloc, buffer_properties);
787+ auto switching_bundle = std::make_shared<mc::BufferQueue>(3, gralloc, buffer_properties, *policy_factory);
788 return std::make_shared<mc::BufferStreamSurfaces>(switching_bundle);
789 }
790
791=== modified file 'src/server/compositor/buffer_stream_factory.h'
792--- src/server/compositor/buffer_stream_factory.h 2014-03-06 06:05:17 +0000
793+++ src/server/compositor/buffer_stream_factory.h 2014-05-21 00:42:11 +0000
794@@ -22,6 +22,7 @@
795 #define MIR_COMPOSITOR_BUFFER_STREAM_FACTORY_H_
796
797 #include "mir/scene/buffer_stream_factory.h"
798+#include "mir/compositor/frame_dropping_policy_factory.h"
799
800 #include <memory>
801
802@@ -37,9 +38,8 @@
803 class BufferStreamFactory : public scene::BufferStreamFactory
804 {
805 public:
806-
807- explicit BufferStreamFactory(
808- const std::shared_ptr<graphics::GraphicBufferAllocator> &gralloc);
809+ BufferStreamFactory(std::shared_ptr<graphics::GraphicBufferAllocator> const& gralloc,
810+ std::shared_ptr<FrameDroppingPolicyFactory> const& policy_factory);
811
812 virtual ~BufferStreamFactory() {}
813
814@@ -48,7 +48,7 @@
815
816 private:
817 std::shared_ptr<graphics::GraphicBufferAllocator> gralloc;
818-
819+ std::shared_ptr<FrameDroppingPolicyFactory> const policy_factory;
820 };
821
822 }
823
824=== modified file 'src/server/compositor/default_configuration.cpp'
825--- src/server/compositor/default_configuration.cpp 2014-04-24 08:42:12 +0000
826+++ src/server/compositor/default_configuration.cpp 2014-05-21 00:42:11 +0000
827@@ -23,6 +23,8 @@
828 #include "multi_threaded_compositor.h"
829 #include "gl_renderer_factory.h"
830 #include "compositing_screencast.h"
831+#include "timeout_frame_dropping_policy_factory.h"
832+#include "mir/main_loop.h"
833
834 #include "mir/frontend/screencast.h"
835 #include "mir/options/configuration.h"
836@@ -39,7 +41,19 @@
837 return buffer_stream_factory(
838 [this]()
839 {
840- return std::make_shared<mc::BufferStreamFactory>(the_buffer_allocator());
841+ return std::make_shared<mc::BufferStreamFactory>(the_buffer_allocator(),
842+ the_frame_dropping_policy_factory());
843+ });
844+}
845+
846+std::shared_ptr<mc::FrameDroppingPolicyFactory>
847+mir::DefaultServerConfiguration::the_frame_dropping_policy_factory()
848+{
849+ return frame_dropping_policy_factory(
850+ [this]()
851+ {
852+ return std::make_shared<mc::TimeoutFrameDroppingPolicyFactory>(the_main_loop(),
853+ std::chrono::milliseconds{1000});
854 });
855 }
856
857
858=== modified file 'src/server/compositor/multi_threaded_compositor.cpp'
859--- src/server/compositor/multi_threaded_compositor.cpp 2014-05-19 02:55:29 +0000
860+++ src/server/compositor/multi_threaded_compositor.cpp 2014-05-21 00:42:11 +0000
861@@ -30,8 +30,6 @@
862
863 #include <thread>
864 #include <condition_variable>
865-#include <cstdint>
866-#include <chrono>
867
868 namespace mc = mir::compositor;
869 namespace mg = mir::graphics;
870@@ -89,29 +87,38 @@
871 class CompositingFunctor
872 {
873 public:
874- CompositingFunctor() : running{true}, frames_scheduled{false} {}
875- virtual ~CompositingFunctor() = default;
876-
877- void schedule_compositing()
878- {
879- std::lock_guard<std::mutex> lock{run_mutex};
880-
881- frames_scheduled = true;
882- run_cv.notify_one();
883- }
884-
885- void stop()
886- {
887- std::lock_guard<std::mutex> lock{run_mutex};
888- running = false;
889- run_cv.notify_one();
890- }
891-
892-protected:
893- void run_compositing_loop(std::function<bool()> const& composite)
894+ CompositingFunctor(std::shared_ptr<mc::DisplayBufferCompositorFactory> const& db_compositor_factory,
895+ mg::DisplayBuffer& buffer,
896+ std::shared_ptr<CompositorReport> const& report)
897+ : display_buffer_compositor_factory{db_compositor_factory},
898+ buffer(buffer),
899+ running{true},
900+ frames_scheduled{0},
901+ report{report}
902+ {
903+ }
904+
905+ void operator()() noexcept // noexcept is important! (LP: #1237332)
906+ try
907 {
908 std::unique_lock<std::mutex> lock{run_mutex};
909
910+ /*
911+ * Make the buffer the current rendering target, and release
912+ * it when the thread is finished.
913+ */
914+ CurrentRenderingTarget target{buffer};
915+
916+ auto display_buffer_compositor = display_buffer_compositor_factory->create_compositor_for(buffer);
917+
918+ CompositorReport::SubCompositorId report_id =
919+ display_buffer_compositor.get();
920+
921+ const auto& r = buffer.view_area();
922+ report->added_display(r.size.width.as_int(), r.size.height.as_int(),
923+ r.top_left.x.as_int(), r.top_left.y.as_int(),
924+ report_id);
925+
926 while (running)
927 {
928 /* Wait until compositing has been scheduled or we are stopped */
929@@ -125,8 +132,7 @@
930 {
931 frames_scheduled = false;
932 lock.unlock();
933-
934- auto more_frames_pending = composite();
935+ auto more_frames_pending = display_buffer_compositor->composite();
936
937 /*
938 * Each surface could have a number of frames ready in its buffer
939@@ -140,114 +146,36 @@
940 }
941 }
942 }
943+ catch(...)
944+ {
945+ mir::terminate_with_current_exception();
946+ }
947+
948+ void schedule_compositing()
949+ {
950+ std::lock_guard<std::mutex> lock{run_mutex};
951+
952+ frames_scheduled = true;
953+ run_cv.notify_one();
954+ }
955+
956+ void stop()
957+ {
958+ std::lock_guard<std::mutex> lock{run_mutex};
959+ running = false;
960+ run_cv.notify_one();
961+ }
962
963 private:
964+ std::shared_ptr<mc::DisplayBufferCompositorFactory> const display_buffer_compositor_factory;
965+ mg::DisplayBuffer& buffer;
966 bool running;
967 bool frames_scheduled;
968 std::mutex run_mutex;
969 std::condition_variable run_cv;
970-};
971-
972-class DisplayBufferCompositingFunctor : public CompositingFunctor
973-{
974-public:
975- DisplayBufferCompositingFunctor(
976- std::shared_ptr<mc::DisplayBufferCompositorFactory> const& db_compositor_factory,
977- mg::DisplayBuffer& buffer,
978- std::shared_ptr<mc::CompositorReport> const& report)
979- : display_buffer_compositor_factory{db_compositor_factory},
980- buffer(buffer),
981- report{report}
982- {
983- }
984-
985- void operator()() noexcept // noexcept is important! (LP: #1237332)
986- try
987- {
988- /*
989- * Make the buffer the current rendering target, and release
990- * it when the thread is finished.
991- */
992- CurrentRenderingTarget target{buffer};
993-
994- auto display_buffer_compositor = display_buffer_compositor_factory->create_compositor_for(buffer);
995-
996- CompositorReport::SubCompositorId report_id =
997- display_buffer_compositor.get();
998-
999- const auto& r = buffer.view_area();
1000- report->added_display(r.size.width.as_int(), r.size.height.as_int(),
1001- r.top_left.x.as_int(), r.top_left.y.as_int(),
1002- report_id);
1003-
1004- run_compositing_loop([&] { return display_buffer_compositor->composite();});
1005- }
1006- catch (...)
1007- {
1008- mir::terminate_with_current_exception();
1009- }
1010-
1011-private:
1012- std::shared_ptr<mc::DisplayBufferCompositorFactory> const display_buffer_compositor_factory;
1013- mg::DisplayBuffer& buffer;
1014 std::shared_ptr<CompositorReport> const report;
1015 };
1016
1017-class BufferConsumingFunctor : public CompositingFunctor
1018-{
1019-public:
1020- BufferConsumingFunctor(std::shared_ptr<mc::Scene> const& scene)
1021- : scene{scene}
1022- {
1023- }
1024-
1025- void operator()() noexcept // noexcept is important! (LP: #1237332)
1026- try
1027- {
1028- run_compositing_loop(
1029- [this]
1030- {
1031- std::vector<std::shared_ptr<mg::Buffer>> saved_resources;
1032-
1033- auto const& renderables = scene->renderable_list_for(this);
1034-
1035- for (auto const& r : renderables)
1036- {
1037- if (r->buffers_ready_for_compositor() > 0)
1038- saved_resources.push_back(r->buffer());
1039- }
1040-
1041- wait_until_next_fake_vsync();
1042-
1043- return false;
1044- });
1045- }
1046- catch (...)
1047- {
1048- mir::terminate_with_current_exception();
1049- }
1050-
1051-private:
1052- void wait_until_next_fake_vsync()
1053- {
1054- using namespace std::chrono;
1055- typedef duration<int64_t, std::ratio<1, 60>> vsync_periods;
1056-
1057- /* Truncate to vsync periods */
1058- auto const previous_vsync =
1059- duration_cast<vsync_periods>(steady_clock::now().time_since_epoch());
1060- /* Convert back to a timepoint */
1061- auto const previous_vsync_tp =
1062- time_point<steady_clock, vsync_periods>{previous_vsync};
1063- /* Next vsync time point */
1064- auto const next_vsync = previous_vsync_tp + vsync_periods(1);
1065-
1066- std::this_thread::sleep_until(next_vsync);
1067- }
1068-
1069- std::shared_ptr<mc::Scene> const scene;
1070-};
1071-
1072 }
1073 }
1074
1075@@ -349,23 +277,13 @@
1076 /* Start the display buffer compositing threads */
1077 display->for_each_display_buffer([this](mg::DisplayBuffer& buffer)
1078 {
1079- auto thread_functor_raw =
1080- new mc::DisplayBufferCompositingFunctor{
1081- display_buffer_compositor_factory, buffer, report};
1082- auto thread_functor =
1083- std::unique_ptr<mc::DisplayBufferCompositingFunctor>(thread_functor_raw);
1084+ auto thread_functor_raw = new mc::CompositingFunctor{display_buffer_compositor_factory, buffer, report};
1085+ auto thread_functor = std::unique_ptr<mc::CompositingFunctor>(thread_functor_raw);
1086
1087 threads.push_back(std::thread{std::ref(*thread_functor)});
1088 thread_functors.push_back(std::move(thread_functor));
1089 });
1090
1091- /* Start the buffer consuming thread */
1092- auto thread_functor_raw = new mc::BufferConsumingFunctor{scene};
1093- auto thread_functor = std::unique_ptr<mc::BufferConsumingFunctor>(thread_functor_raw);
1094-
1095- threads.push_back(std::thread{std::ref(*thread_functor)});
1096- thread_functors.push_back(std::move(thread_functor));
1097-
1098 state = CompositorState::started;
1099 }
1100
1101
1102=== added file 'src/server/compositor/timeout_frame_dropping_policy_factory.cpp'
1103--- src/server/compositor/timeout_frame_dropping_policy_factory.cpp 1970-01-01 00:00:00 +0000
1104+++ src/server/compositor/timeout_frame_dropping_policy_factory.cpp 2014-05-21 00:42:11 +0000
1105@@ -0,0 +1,92 @@
1106+/*
1107+ * Copyright © 2014 Canonical Ltd.
1108+ *
1109+ * This program is free software: you can redistribute it and/or modify it
1110+ * under the terms of the GNU General Public License version 3,
1111+ * as published by the Free Software Foundation.
1112+ *
1113+ * This program is distributed in the hope that it will be useful,
1114+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1115+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1116+ * GNU General Public License for more details.
1117+ *
1118+ * You should have received a copy of the GNU General Public License
1119+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1120+ *
1121+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
1122+ */
1123+
1124+#include "mir/compositor/frame_dropping_policy.h"
1125+
1126+#include "timeout_frame_dropping_policy_factory.h"
1127+
1128+#include <cassert>
1129+#include <atomic>
1130+#include <chrono>
1131+#include <boost/throw_exception.hpp>
1132+
1133+namespace mc = mir::compositor;
1134+
1135+namespace
1136+{
1137+class TimeoutFrameDroppingPolicy : public mc::FrameDroppingPolicy
1138+{
1139+public:
1140+ TimeoutFrameDroppingPolicy(std::shared_ptr<mir::time::Timer> const& timer,
1141+ std::chrono::milliseconds timeout,
1142+ std::function<void(void)> drop_frame);
1143+
1144+ void swap_now_blocking() override;
1145+ void swap_unblocked() override;
1146+
1147+private:
1148+ std::chrono::milliseconds const timeout;
1149+ std::unique_ptr<mir::time::Alarm> alarm;
1150+ std::atomic<unsigned int> pending_swaps;
1151+};
1152+
1153+TimeoutFrameDroppingPolicy::TimeoutFrameDroppingPolicy(std::shared_ptr<mir::time::Timer> const& timer,
1154+ std::chrono::milliseconds timeout,
1155+ std::function<void(void)> drop_frame)
1156+ : timeout{timeout},
1157+ pending_swaps{0}
1158+{
1159+ alarm = timer->create_alarm([this, drop_frame]
1160+ {
1161+ assert(pending_swaps.load() > 0);
1162+ drop_frame();
1163+ if (--pending_swaps > 0)
1164+ alarm->reschedule_in(this->timeout);
1165+ });
1166+}
1167+
1168+void TimeoutFrameDroppingPolicy::swap_now_blocking()
1169+{
1170+ if (pending_swaps++ == 0)
1171+ alarm->reschedule_in(timeout);
1172+}
1173+
1174+void TimeoutFrameDroppingPolicy::swap_unblocked()
1175+{
1176+ if (alarm->state() != mir::time::Alarm::cancelled && alarm->cancel())
1177+ {
1178+ if (--pending_swaps > 0)
1179+ {
1180+ alarm->reschedule_in(timeout);
1181+ }
1182+ }
1183+}
1184+}
1185+
1186+mc::TimeoutFrameDroppingPolicyFactory::TimeoutFrameDroppingPolicyFactory(std::shared_ptr<mir::time::Timer> const& timer,
1187+ std::chrono::milliseconds timeout)
1188+ : timer{timer},
1189+ timeout{timeout}
1190+{
1191+}
1192+
1193+
1194+std::unique_ptr<mc::FrameDroppingPolicy> mc::TimeoutFrameDroppingPolicyFactory::create_policy(std::function<void ()> drop_frame) const
1195+{
1196+ return std::unique_ptr<mc::FrameDroppingPolicy>{new TimeoutFrameDroppingPolicy{timer, timeout, drop_frame}};
1197+}
1198
1199=== added file 'src/server/compositor/timeout_frame_dropping_policy_factory.h'
1200--- src/server/compositor/timeout_frame_dropping_policy_factory.h 1970-01-01 00:00:00 +0000
1201+++ src/server/compositor/timeout_frame_dropping_policy_factory.h 2014-05-21 00:42:11 +0000
1202@@ -0,0 +1,56 @@
1203+/*
1204+ * Copyright © 2014 Canonical Ltd.
1205+ *
1206+ * This program is free software: you can redistribute it and/or modify it
1207+ * under the terms of the GNU General Public License version 3,
1208+ * as published by the Free Software Foundation.
1209+ *
1210+ * This program is distributed in the hope that it will be useful,
1211+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1212+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1213+ * GNU General Public License for more details.
1214+ *
1215+ * You should have received a copy of the GNU General Public License
1216+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1217+ *
1218+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
1219+ */
1220+
1221+
1222+#ifndef MIR_COMPOSITOR_TIMEOUT_FRAME_DROPPING_POLICY_FACTORY_H_
1223+#define MIR_COMPOSITOR_TIMEOUT_FRAME_DROPPING_POLICY_FACTORY_H_
1224+
1225+#include "mir/compositor/frame_dropping_policy_factory.h"
1226+#include "mir/time/timer.h"
1227+
1228+#include <memory>
1229+#include <chrono>
1230+
1231+namespace mir
1232+{
1233+namespace compositor
1234+{
1235+
1236+/**
1237+ * \brief Creator of timeout-based FrameDroppingPolicies
1238+ */
1239+class TimeoutFrameDroppingPolicyFactory : public FrameDroppingPolicyFactory
1240+{
1241+public:
1242+ /**
1243+ * \param timer Timer that the policies constructed will schedule alarms on
1244+ * \param timeout Milliseconds that the policies will wait before dropping a frame
1245+ */
1246+ TimeoutFrameDroppingPolicyFactory(std::shared_ptr<mir::time::Timer> const& timer,
1247+ std::chrono::milliseconds timeout);
1248+
1249+ std::unique_ptr<FrameDroppingPolicy> create_policy(std::function<void(void)> drop_frame) const override;
1250+private:
1251+ std::shared_ptr<mir::time::Timer> const timer;
1252+ std::chrono::milliseconds timeout;
1253+};
1254+
1255+}
1256+}
1257+
1258+#endif // MIR_COMPOSITOR_TIMEOUT_FRAME_DROPPING_POLICY_FACTORY_FACTORY_H_
1259
1260=== modified file 'tests/acceptance-tests/test_client_surface_swap_buffers.cpp'
1261--- tests/acceptance-tests/test_client_surface_swap_buffers.cpp 2014-04-10 07:44:47 +0000
1262+++ tests/acceptance-tests/test_client_surface_swap_buffers.cpp 2014-05-21 00:42:11 +0000
1263@@ -22,13 +22,12 @@
1264 #include "mir_test_framework/in_process_server.h"
1265 #include "mir_test_framework/using_stub_client_platform.h"
1266 #include "mir_test_doubles/null_display_buffer_compositor_factory.h"
1267-#include "mir_test/spin_wait.h"
1268+#include "mir_test/signal.h"
1269
1270 #include <gtest/gtest.h>
1271
1272-#include <atomic>
1273-
1274 namespace mtf = mir_test_framework;
1275+namespace mt = mir::test;
1276 namespace mtd = mir::test::doubles;
1277 namespace mc = mir::compositor;
1278
1279@@ -55,8 +54,8 @@
1280
1281 void swap_buffers_callback(MirSurface*, void* ctx)
1282 {
1283- auto buffers_swapped = static_cast<std::atomic<bool>*>(ctx);
1284- *buffers_swapped = true;
1285+ auto buffers_swapped = static_cast<mt::Signal*>(ctx);
1286+ buffers_swapped->raise();
1287 }
1288
1289 }
1290@@ -78,23 +77,19 @@
1291 };
1292
1293 auto const surface = mir_connection_create_surface_sync(connection, &request_params);
1294- ASSERT_NE(nullptr, surface);
1295+ ASSERT_TRUE(mir_surface_is_valid(surface));
1296
1297 for (int i = 0; i < 10; ++i)
1298 {
1299- std::atomic<bool> buffers_swapped{false};
1300+ mt::Signal buffers_swapped;
1301
1302 mir_surface_swap_buffers(surface, swap_buffers_callback, &buffers_swapped);
1303
1304- mir::test::spin_wait_for_condition_or_timeout(
1305- [&] { return buffers_swapped.load(); },
1306- std::chrono::seconds{5});
1307-
1308- /*
1309+ /*
1310 * ASSERT instead of EXPECT, since if we continue we will block in future
1311 * mir client calls (e.g mir_connection_release).
1312 */
1313- ASSERT_TRUE(buffers_swapped.load());
1314+ ASSERT_TRUE(buffers_swapped.wait_for(std::chrono::seconds{5}));
1315 }
1316
1317 mir_surface_release_sync(surface);
1318
1319=== modified file 'tests/integration-tests/compositor/test_buffer_stream.cpp'
1320--- tests/integration-tests/compositor/test_buffer_stream.cpp 2014-04-26 04:42:07 +0000
1321+++ tests/integration-tests/compositor/test_buffer_stream.cpp 2014-05-21 00:42:11 +0000
1322@@ -18,11 +18,14 @@
1323
1324 #include "src/server/compositor/buffer_stream_surfaces.h"
1325 #include "mir/graphics/graphic_buffer_allocator.h"
1326+#include "mir/time/timer.h"
1327 #include "src/server/compositor/buffer_queue.h"
1328+#include "src/server/compositor/timeout_frame_dropping_policy_factory.h"
1329
1330 #include "mir_test_doubles/stub_buffer.h"
1331 #include "mir_test_doubles/stub_buffer_allocator.h"
1332-#include "multithread_harness.h"
1333+#include "mir_test_doubles/mock_timer.h"
1334+#include "mir_test/signal.h"
1335
1336 #include <gmock/gmock.h>
1337
1338@@ -33,12 +36,13 @@
1339
1340 namespace mc = mir::compositor;
1341 namespace mg = mir::graphics;
1342-namespace mt = mir::testing;
1343 namespace mtd = mir::test::doubles;
1344+namespace mt =mir::test;
1345 namespace geom = mir::geometry;
1346
1347 namespace
1348 {
1349+
1350 struct BufferStreamSurfaces : mc::BufferStreamSurfaces
1351 {
1352 using mc::BufferStreamSurfaces::BufferStreamSurfaces;
1353@@ -46,29 +50,29 @@
1354 // Convenient function to allow tests to be written in linear style
1355 void swap_client_buffers_blocking(mg::Buffer*& buffer)
1356 {
1357- std::mutex mutex;
1358- std::condition_variable cv;
1359- bool done = false;
1360+ swap_client_buffers_cancellable(buffer, std::make_shared<mt::Signal>());
1361+ }
1362
1363+ void swap_client_buffers_cancellable(mg::Buffer*& buffer, std::shared_ptr<mt::Signal> const& signal)
1364+ {
1365 swap_client_buffers(buffer,
1366- [&](mg::Buffer* new_buffer)
1367+ [signal, &buffer](mg::Buffer* new_buffer)
1368 {
1369- std::unique_lock<decltype(mutex)> lock(mutex);
1370 buffer = new_buffer;
1371- done = true;
1372- cv.notify_one();
1373+ signal->raise();
1374 });
1375
1376- std::unique_lock<decltype(mutex)> lock(mutex);
1377-
1378- cv.wait(lock, [&]{ return done; });
1379+ signal->wait();
1380 }
1381 };
1382
1383 struct BufferStreamTest : public ::testing::Test
1384 {
1385 BufferStreamTest()
1386- : nbuffers{3},
1387+ : clock{std::make_shared<mt::FakeClock>()},
1388+ timer{std::make_shared<mtd::FakeTimer>(clock)},
1389+ frame_drop_timeout{1000},
1390+ nbuffers{3},
1391 buffer_stream{create_bundle()}
1392 {
1393 }
1394@@ -79,12 +83,18 @@
1395 mg::BufferProperties properties{geom::Size{380, 210},
1396 mir_pixel_format_abgr_8888,
1397 mg::BufferUsage::hardware};
1398+ mc::TimeoutFrameDroppingPolicyFactory policy_factory{timer,
1399+ frame_drop_timeout};
1400
1401 return std::make_shared<mc::BufferQueue>(nbuffers,
1402- allocator,
1403- properties);
1404+ allocator,
1405+ properties,
1406+ policy_factory);
1407 }
1408
1409+ std::shared_ptr<mt::FakeClock> clock;
1410+ std::shared_ptr<mtd::FakeTimer> timer;
1411+ std::chrono::milliseconds const frame_drop_timeout;
1412 const int nbuffers;
1413 BufferStreamSurfaces buffer_stream;
1414 };
1415@@ -338,3 +348,23 @@
1416 for (auto &s : snapshotters)
1417 s->join();
1418 }
1419+
1420+TEST_F(BufferStreamTest, blocked_client_is_released_on_timeout)
1421+{
1422+ using namespace testing;
1423+
1424+ mg::Buffer* placeholder{nullptr};
1425+
1426+ // Grab all the buffers...
1427+ // TODO: the magic “nbuffers - 1” number should be removed
1428+ for (int i = 0; i < nbuffers - 1; ++i)
1429+ buffer_stream.swap_client_buffers_blocking(placeholder);
1430+
1431+ auto swap_completed = std::make_shared<mt::Signal>();
1432+ buffer_stream.swap_client_buffers(placeholder, [swap_completed](mg::Buffer*) {swap_completed->raise();});
1433+
1434+ EXPECT_FALSE(swap_completed->raised());
1435+ clock->advance_time(frame_drop_timeout + std::chrono::milliseconds{1});
1436+
1437+ EXPECT_TRUE(swap_completed->wait_for(std::chrono::milliseconds{100}));
1438+}
1439
1440=== modified file 'tests/integration-tests/compositor/test_swapping_swappers.cpp'
1441--- tests/integration-tests/compositor/test_swapping_swappers.cpp 2014-04-21 15:56:41 +0000
1442+++ tests/integration-tests/compositor/test_swapping_swappers.cpp 2014-05-21 00:42:11 +0000
1443@@ -17,6 +17,7 @@
1444 */
1445
1446 #include "mir_test_doubles/stub_buffer_allocator.h"
1447+#include "mir_test_doubles/stub_frame_dropping_policy_factory.h"
1448 #include "multithread_harness.h"
1449
1450 #include "src/server/compositor/buffer_queue.h"
1451@@ -47,7 +48,8 @@
1452 auto properties = mg::BufferProperties{geom::Size{380, 210},
1453 mir_pixel_format_abgr_8888,
1454 mg::BufferUsage::hardware};
1455- switching_bundle = std::make_shared<mc::BufferQueue>(3, allocator, properties);
1456+ mtd::StubFrameDroppingPolicyFactory policy_factory;
1457+ switching_bundle = std::make_shared<mc::BufferQueue>(3, allocator, properties, policy_factory);
1458 }
1459
1460 std::shared_ptr<mc::BufferQueue> switching_bundle;
1461
1462=== modified file 'tests/integration-tests/graphics/android/test_buffer_integration.cpp'
1463--- tests/integration-tests/graphics/android/test_buffer_integration.cpp 2014-04-21 15:56:41 +0000
1464+++ tests/integration-tests/graphics/android/test_buffer_integration.cpp 2014-05-21 00:42:11 +0000
1465@@ -26,6 +26,8 @@
1466 #include "testdraw/graphics_region_factory.h"
1467 #include "testdraw/patterns.h"
1468
1469+#include "mir_test_doubles/stub_frame_dropping_policy_factory.h"
1470+
1471 #include <gtest/gtest.h>
1472
1473 namespace mc=mir::compositor;
1474@@ -54,6 +56,7 @@
1475 MirPixelFormat pf;
1476 mg::BufferProperties buffer_properties;
1477 std::shared_ptr<mtd::GraphicsRegionFactory> graphics_region_factory;
1478+ mir::test::doubles::StubFrameDroppingPolicyFactory policy_factory;
1479 };
1480
1481 auto client_acquire_blocking(mc::BufferQueue& switching_bundle)
1482@@ -116,7 +119,7 @@
1483
1484 auto allocator = std::make_shared<mga::AndroidGraphicBufferAllocator>(null_buffer_initializer);
1485
1486- mc::BufferQueue swapper(2, allocator, buffer_properties);
1487+ mc::BufferQueue swapper(2, allocator, buffer_properties, policy_factory);
1488
1489 auto returned_buffer = client_acquire_blocking(swapper);
1490
1491
1492=== modified file 'tests/integration-tests/graphics/android/test_internal_client.cpp'
1493--- tests/integration-tests/graphics/android/test_internal_client.cpp 2014-05-19 02:55:29 +0000
1494+++ tests/integration-tests/graphics/android/test_internal_client.cpp 2014-05-21 00:42:11 +0000
1495@@ -40,6 +40,7 @@
1496
1497 #include "mir_test_doubles/stub_input_channel.h"
1498 #include "mir_test_doubles/null_surface_configurator.h"
1499+#include "mir_test_doubles/stub_frame_dropping_policy_factory.h"
1500
1501 #include <EGL/egl.h>
1502 #include <gtest/gtest.h>
1503@@ -99,7 +100,7 @@
1504 auto stub_input_factory = std::make_shared<StubInputFactory>();
1505 auto null_buffer_initializer = std::make_shared<mg::NullBufferInitializer>();
1506 auto allocator = std::make_shared<mga::AndroidGraphicBufferAllocator>(null_buffer_initializer);
1507- auto buffer_stream_factory = std::make_shared<mc::BufferStreamFactory>(allocator);
1508+ auto buffer_stream_factory = std::make_shared<mc::BufferStreamFactory>(allocator, std::make_shared<mtd::StubFrameDroppingPolicyFactory>());
1509 auto scene_report = mr::null_scene_report();
1510 auto const surface_configurator = std::make_shared<mtd::NullSurfaceConfigurator>();
1511 auto surface_allocator = std::make_shared<ms::SurfaceAllocator>(buffer_stream_factory, stub_input_factory, surface_configurator, scene_report);
1512
1513=== modified file 'tests/mir_test/CMakeLists.txt'
1514--- tests/mir_test/CMakeLists.txt 2014-05-19 02:55:29 +0000
1515+++ tests/mir_test/CMakeLists.txt 2014-05-21 00:42:11 +0000
1516@@ -5,6 +5,8 @@
1517 display_config_matchers.cpp
1518 cross_process_action.cpp
1519 spin_wait.cpp
1520+ signal.cpp
1521+ fake_clock.cpp
1522 popen.cpp
1523 wait_object.cpp
1524 )
1525
1526=== added file 'tests/mir_test/fake_clock.cpp'
1527--- tests/mir_test/fake_clock.cpp 1970-01-01 00:00:00 +0000
1528+++ tests/mir_test/fake_clock.cpp 2014-05-21 00:42:11 +0000
1529@@ -0,0 +1,53 @@
1530+/*
1531+ * Copyright © 2014 Canonical Ltd.
1532+ *
1533+ * This program is free software: you can redistribute it and/or modify it
1534+ * under the terms of the GNU General Public License version 3,
1535+ * as published by the Free Software Foundation.
1536+ *
1537+ * This program is distributed in the hope that it will be useful,
1538+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1539+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1540+ * GNU General Public License for more details.
1541+ *
1542+ * You should have received a copy of the GNU General Public License
1543+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1544+ *
1545+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
1546+ */
1547+
1548+#include "mir_test/fake_clock.h"
1549+
1550+namespace mt = mir::test;
1551+
1552+mt::FakeClock::FakeClock()
1553+ : current_time{std::chrono::nanoseconds::zero()}
1554+{
1555+}
1556+
1557+mt::FakeClock::time_point mt::FakeClock::now() const
1558+{
1559+ return time_point{current_time};
1560+}
1561+
1562+void mt::FakeClock::register_time_change_callback(std::function<bool(mt::FakeClock::time_point)> cb)
1563+{
1564+ callbacks.push_back(cb);
1565+}
1566+
1567+void mt::FakeClock::advance_time_ns(std::chrono::nanoseconds by)
1568+{
1569+ current_time += by;
1570+ auto next = callbacks.begin();
1571+ while (next != callbacks.end())
1572+ {
1573+ if (!(*next)(now()))
1574+ {
1575+ next = callbacks.erase(next);
1576+ }
1577+ else
1578+ {
1579+ next++;
1580+ }
1581+ }
1582+}
1583
1584=== added file 'tests/mir_test/signal.cpp'
1585--- tests/mir_test/signal.cpp 1970-01-01 00:00:00 +0000
1586+++ tests/mir_test/signal.cpp 2014-05-21 00:42:11 +0000
1587@@ -0,0 +1,46 @@
1588+/*
1589+ * Copyright © 2014 Canonical Ltd.
1590+ *
1591+ * This program is free software: you can redistribute it and/or modify
1592+ * it under the terms of the GNU General Public License version 3 as
1593+ * published by the Free Software Foundation.
1594+ *
1595+ * This program is distributed in the hope that it will be useful,
1596+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1597+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1598+ * GNU General Public License for more details.
1599+ *
1600+ * You should have received a copy of the GNU General Public License
1601+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1602+ *
1603+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
1604+ */
1605+
1606+#include "mir_test/signal.h"
1607+
1608+namespace mt = mir::test;
1609+
1610+mt::Signal::Signal()
1611+ : signalled{false}
1612+{
1613+}
1614+
1615+void mt::Signal::raise()
1616+{
1617+ std::unique_lock<decltype(mutex)> lock(mutex);
1618+ signalled = true;
1619+ lock.unlock();
1620+ cv.notify_all();
1621+}
1622+
1623+bool mt::Signal::raised()
1624+{
1625+ std::lock_guard<decltype(mutex)> lock(mutex);
1626+ return signalled;
1627+}
1628+
1629+void mt::Signal::wait()
1630+{
1631+ std::unique_lock<decltype(mutex)> lock(mutex);
1632+ cv.wait(lock, [this]() { return signalled; });
1633+}
1634
1635=== modified file 'tests/mir_test_doubles/CMakeLists.txt'
1636--- tests/mir_test_doubles/CMakeLists.txt 2014-03-06 06:05:17 +0000
1637+++ tests/mir_test_doubles/CMakeLists.txt 2014-05-21 00:42:11 +0000
1638@@ -11,6 +11,8 @@
1639 fake_event_hub.cpp
1640 fake_event_hub_input_configuration.cpp
1641 test_protobuf_socket_server.cpp
1642+ mock_timer.cpp
1643+ mock_frame_dropping_policy_factory.cpp
1644 )
1645
1646 set(
1647
1648=== added file 'tests/mir_test_doubles/mock_frame_dropping_policy_factory.cpp'
1649--- tests/mir_test_doubles/mock_frame_dropping_policy_factory.cpp 1970-01-01 00:00:00 +0000
1650+++ tests/mir_test_doubles/mock_frame_dropping_policy_factory.cpp 2014-05-21 00:42:11 +0000
1651@@ -0,0 +1,70 @@
1652+/*
1653+ * Copyright © 2014 Canonical Ltd.
1654+ *
1655+ * This program is free software: you can redistribute it and/or modify it
1656+ * under the terms of the GNU General Public License version 3,
1657+ * as published by the Free Software Foundation.
1658+ *
1659+ * This program is distributed in the hope that it will be useful,
1660+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1661+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1662+ * GNU General Public License for more details.
1663+ *
1664+ * You should have received a copy of the GNU General Public License
1665+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1666+ *
1667+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
1668+ */
1669+
1670+#include "mir_test_doubles/mock_frame_dropping_policy_factory.h"
1671+
1672+namespace mc = mir::compositor;
1673+namespace mtd = mir::test::doubles;
1674+
1675+mtd::MockFrameDroppingPolicy::MockFrameDroppingPolicy(std::function<void(void)> callback,
1676+ MockFrameDroppingPolicyFactory const* parent)
1677+ : callback{callback},
1678+ parent{parent}
1679+{
1680+}
1681+
1682+mtd::MockFrameDroppingPolicy::~MockFrameDroppingPolicy()
1683+{
1684+ if (parent)
1685+ parent->policy_destroyed(this);
1686+}
1687+
1688+void mtd::MockFrameDroppingPolicy::trigger()
1689+{
1690+ callback();
1691+}
1692+
1693+void mtd::MockFrameDroppingPolicy::parent_destroyed()
1694+{
1695+ parent = nullptr;
1696+}
1697+
1698+std::unique_ptr<mc::FrameDroppingPolicy>
1699+mtd::MockFrameDroppingPolicyFactory::create_policy(std::function<void(void)> drop_frame) const
1700+{
1701+ auto policy = new ::testing::NiceMock<MockFrameDroppingPolicy>{drop_frame, this};
1702+ policies.insert(policy);
1703+ return std::unique_ptr<mc::FrameDroppingPolicy>{policy};
1704+}
1705+
1706+mtd::MockFrameDroppingPolicyFactory::~MockFrameDroppingPolicyFactory()
1707+{
1708+ for (auto policy : policies)
1709+ policy->parent_destroyed();
1710+}
1711+
1712+void mtd::MockFrameDroppingPolicyFactory::trigger_policies() const
1713+{
1714+ for (auto policy : policies)
1715+ policy->trigger();
1716+}
1717+
1718+void mtd::MockFrameDroppingPolicyFactory::policy_destroyed(MockFrameDroppingPolicy* policy) const
1719+{
1720+ policies.erase(policy);
1721+}
1722
1723=== added file 'tests/mir_test_doubles/mock_timer.cpp'
1724--- tests/mir_test_doubles/mock_timer.cpp 1970-01-01 00:00:00 +0000
1725+++ tests/mir_test_doubles/mock_timer.cpp 2014-05-21 00:42:11 +0000
1726@@ -0,0 +1,149 @@
1727+/*
1728+ * Copyright © 2014 Canonical Ltd.
1729+ *
1730+ * This program is free software: you can redistribute it and/or modify it
1731+ * under the terms of the GNU General Public License version 3,
1732+ * as published by the Free Software Foundation.
1733+ *
1734+ * This program is distributed in the hope that it will be useful,
1735+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1736+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1737+ * GNU General Public License for more details.
1738+ *
1739+ * You should have received a copy of the GNU General Public License
1740+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1741+ *
1742+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
1743+ */
1744+
1745+#include "mir_test_doubles/mock_timer.h"
1746+
1747+namespace mt = mir::test;
1748+namespace mtd = mir::test::doubles;
1749+
1750+namespace
1751+{
1752+
1753+class FakeAlarm : public mir::time::Alarm
1754+{
1755+public:
1756+ FakeAlarm(std::function<void(void)> callback, std::shared_ptr<mt::FakeClock> const& clock);
1757+ ~FakeAlarm() override;
1758+
1759+
1760+ bool cancel() override;
1761+ State state() const override;
1762+ bool reschedule_in(std::chrono::milliseconds delay) override;
1763+ bool reschedule_for(mir::time::Timestamp timeout) override;
1764+
1765+private:
1766+ struct InternalState
1767+ {
1768+ explicit InternalState(std::function<void(void)> callback);
1769+ State state;
1770+ std::function<void(void)> const callback;
1771+ mt::FakeClock::time_point threshold;
1772+ };
1773+
1774+ bool handle_time_change(InternalState& state, mt::FakeClock::time_point now);
1775+
1776+ std::shared_ptr<InternalState> const internal_state;
1777+ std::shared_ptr<mt::FakeClock> const clock;
1778+};
1779+
1780+FakeAlarm::InternalState::InternalState(std::function<void(void)> callback)
1781+ : state{mir::time::Alarm::pending}, callback{callback}
1782+{
1783+}
1784+
1785+FakeAlarm::FakeAlarm(std::function<void(void)> callback, std::shared_ptr<mt::FakeClock> const& clock)
1786+ : internal_state{std::make_shared<InternalState>(callback)}, clock{clock}
1787+{
1788+}
1789+
1790+FakeAlarm::~FakeAlarm()
1791+{
1792+ cancel();
1793+}
1794+
1795+bool FakeAlarm::cancel()
1796+{
1797+ if (internal_state->state == triggered)
1798+ return false;
1799+
1800+ internal_state->state = cancelled;
1801+ return true;
1802+}
1803+
1804+FakeAlarm::State FakeAlarm::state() const
1805+{
1806+ return internal_state->state;
1807+}
1808+
1809+bool FakeAlarm::handle_time_change(InternalState& state,
1810+ mir::test::FakeClock::time_point now)
1811+{
1812+ if (state.state == pending)
1813+ {
1814+ if (now > state.threshold)
1815+ {
1816+ state.state = triggered;
1817+ state.callback();
1818+ return false;
1819+ }
1820+ return true;
1821+ }
1822+ else
1823+ {
1824+ return false;
1825+ }
1826+}
1827+
1828+bool FakeAlarm::reschedule_in(std::chrono::milliseconds delay)
1829+{
1830+ bool rescheduled = internal_state->state == pending;
1831+
1832+ internal_state->state = pending;
1833+ internal_state->threshold = clock->now() + delay;
1834+
1835+ std::shared_ptr<InternalState> state_copy{internal_state};
1836+ clock->register_time_change_callback([this, state_copy](mt::FakeClock::time_point now)
1837+ {
1838+ return handle_time_change(*state_copy, now);
1839+ });
1840+ return rescheduled;
1841+}
1842+
1843+bool FakeAlarm::reschedule_for(mir::time::Timestamp timeout)
1844+{
1845+ // time::Timestamp is on a different clock, so not directly comparable
1846+ auto delay = std::chrono::duration_cast<std::chrono::milliseconds>(timeout.time_since_epoch() -
1847+ clock->now().time_since_epoch());
1848+ return reschedule_in(delay);
1849+}
1850+}
1851+
1852+mtd::FakeTimer::FakeTimer(std::shared_ptr<FakeClock> const& clock) : clock{clock}
1853+{
1854+}
1855+
1856+std::unique_ptr<mir::time::Alarm> mtd::FakeTimer::notify_in(std::chrono::milliseconds delay,
1857+ std::function<void(void)> callback)
1858+{
1859+ auto alarm = std::unique_ptr<mir::time::Alarm>{new FakeAlarm{callback, clock}};
1860+ alarm->reschedule_in(delay);
1861+ return std::move(alarm);
1862+}
1863+
1864+std::unique_ptr<mir::time::Alarm> mtd::FakeTimer::notify_at(time::Timestamp time_point,
1865+ std::function<void(void)> callback)
1866+{
1867+ auto alarm = std::unique_ptr<mir::time::Alarm>{new FakeAlarm{callback, clock}};
1868+ alarm->reschedule_for(time_point);
1869+ return std::move(alarm);
1870+}
1871+
1872+std::unique_ptr<mir::time::Alarm> mir::test::doubles::FakeTimer::create_alarm(std::function<void(void)> callback)
1873+{
1874+ return std::unique_ptr<mir::time::Alarm>{new FakeAlarm{callback, clock}};
1875+}
1876
1877=== modified file 'tests/unit-tests/compositor/CMakeLists.txt'
1878--- tests/unit-tests/compositor/CMakeLists.txt 2014-04-21 15:56:41 +0000
1879+++ tests/unit-tests/compositor/CMakeLists.txt 2014-05-21 00:42:11 +0000
1880@@ -9,6 +9,7 @@
1881 ${CMAKE_CURRENT_SOURCE_DIR}/test_occlusion.cpp
1882 ${CMAKE_CURRENT_SOURCE_DIR}/test_screencast_display_buffer.cpp
1883 ${CMAKE_CURRENT_SOURCE_DIR}/test_compositing_screencast.cpp
1884+ ${CMAKE_CURRENT_SOURCE_DIR}/test_timeout_frame_dropping_policy.cpp
1885 )
1886
1887 set(UNIT_TEST_SOURCES ${UNIT_TEST_SOURCES} PARENT_SCOPE)
1888
1889=== modified file 'tests/unit-tests/compositor/test_buffer_queue.cpp'
1890--- tests/unit-tests/compositor/test_buffer_queue.cpp 2014-05-19 07:44:29 +0000
1891+++ tests/unit-tests/compositor/test_buffer_queue.cpp 2014-05-21 00:42:11 +0000
1892@@ -20,6 +20,9 @@
1893 #include "src/server/compositor/buffer_queue.h"
1894 #include "mir_test_doubles/stub_buffer_allocator.h"
1895 #include "mir_test_doubles/stub_buffer.h"
1896+#include "mir_test_doubles/stub_frame_dropping_policy_factory.h"
1897+#include "mir_test_doubles/mock_frame_dropping_policy_factory.h"
1898+#include "mir_test/signal.h"
1899 #include "mir_test/auto_unblock_thread.h"
1900
1901 #include <gtest/gtest.h>
1902@@ -28,6 +31,8 @@
1903 #include <atomic>
1904 #include <mutex>
1905 #include <chrono>
1906+#include <algorithm>
1907+#include <map>
1908 #include <deque>
1909 #include <unordered_set>
1910
1911@@ -36,11 +41,13 @@
1912 namespace mt = mir::test;
1913 namespace mc=mir::compositor;
1914 namespace mg = mir::graphics;
1915+namespace mt=mir::test;
1916
1917 using namespace testing;
1918
1919 namespace
1920 {
1921+
1922 class BufferQueueTest : public ::testing::Test
1923 {
1924 public:
1925@@ -62,6 +69,7 @@
1926 int max_nbuffers_to_test;
1927 std::shared_ptr<mtd::StubBufferAllocator> allocator;
1928 mg::BufferProperties basic_properties;
1929+ mtd::StubFrameDroppingPolicyFactory policy_factory;
1930 };
1931
1932 class AcquireWaitHandle
1933@@ -191,7 +199,7 @@
1934 std::unique_ptr<mc::BufferQueue> q;
1935 ASSERT_NO_THROW(q = std::move(
1936 std::unique_ptr<mc::BufferQueue>(
1937- new mc::BufferQueue(1, allocator, basic_properties))));
1938+ new mc::BufferQueue(1, allocator, basic_properties, policy_factory))));
1939 ASSERT_THAT(q, Ne(nullptr));
1940
1941 auto handle = client_acquire_async(*q);
1942@@ -226,7 +234,7 @@
1943
1944 TEST_F(BufferQueueTest, buffer_queue_of_one_supports_resizing)
1945 {
1946- mc::BufferQueue q(1, allocator, basic_properties);
1947+ mc::BufferQueue q(1, allocator, basic_properties, policy_factory);
1948
1949 const geom::Size expect_size{10, 20};
1950 q.resize(expect_size);
1951@@ -251,22 +259,22 @@
1952
1953 TEST_F(BufferQueueTest, framedropping_is_disabled_by_default)
1954 {
1955- mc::BufferQueue bundle(2, allocator, basic_properties);
1956+ mc::BufferQueue bundle(2, allocator, basic_properties, policy_factory);
1957 EXPECT_THAT(bundle.framedropping_allowed(), Eq(false));
1958 }
1959
1960 TEST_F(BufferQueueTest, throws_when_creating_with_invalid_num_buffers)
1961 {
1962- EXPECT_THROW(mc::BufferQueue a(0, allocator, basic_properties), std::logic_error);
1963- EXPECT_THROW(mc::BufferQueue a(-1, allocator, basic_properties), std::logic_error);
1964- EXPECT_THROW(mc::BufferQueue a(-10, allocator, basic_properties), std::logic_error);
1965+ EXPECT_THROW(mc::BufferQueue a(0, allocator, basic_properties, policy_factory), std::logic_error);
1966+ EXPECT_THROW(mc::BufferQueue a(-1, allocator, basic_properties, policy_factory), std::logic_error);
1967+ EXPECT_THROW(mc::BufferQueue a(-10, allocator, basic_properties, policy_factory), std::logic_error);
1968 }
1969
1970 TEST_F(BufferQueueTest, client_can_acquire_and_release_buffer)
1971 {
1972 for (int nbuffers = 1; nbuffers <= max_nbuffers_to_test; ++nbuffers)
1973 {
1974- mc::BufferQueue q(nbuffers, allocator, basic_properties);
1975+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
1976
1977 auto handle = client_acquire_async(q);
1978 ASSERT_THAT(handle->has_acquired_buffer(), Eq(true));
1979@@ -278,7 +286,7 @@
1980 {
1981 for (int nbuffers = 2; nbuffers <= max_nbuffers_to_test; ++nbuffers)
1982 {
1983- mc::BufferQueue q(nbuffers, allocator, basic_properties);
1984+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
1985 int const max_ownable_buffers = nbuffers - 1;
1986 for (int acquires = 0; acquires < max_ownable_buffers; ++acquires)
1987 {
1988@@ -292,7 +300,7 @@
1989 TEST_F(BufferQueueTest, clients_can_have_multiple_pending_completions)
1990 {
1991 int const nbuffers = 3;
1992- mc::BufferQueue q(nbuffers, allocator, basic_properties);
1993+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
1994
1995 for (int i = 0; i < nbuffers - 1; ++i)
1996 {
1997@@ -322,7 +330,7 @@
1998 {
1999 for (int nbuffers = 2; nbuffers <= max_nbuffers_to_test; ++nbuffers)
2000 {
2001- mc::BufferQueue q(nbuffers, allocator, basic_properties);
2002+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
2003 ASSERT_THAT(q.framedropping_allowed(), Eq(false));
2004
2005 void const* main_compositor = reinterpret_cast<void const*>(0);
2006@@ -351,7 +359,7 @@
2007 {
2008 for (int nbuffers = 2; nbuffers <= max_nbuffers_to_test; ++nbuffers)
2009 {
2010- mc::BufferQueue q(nbuffers, allocator, basic_properties);
2011+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
2012 q.allow_framedropping(true);
2013
2014 for (int i = 0; i < 1000; i++)
2015@@ -366,7 +374,7 @@
2016 /* Regression test for LP: #1210042 */
2017 TEST_F(BufferQueueTest, clients_dont_recycle_startup_buffer)
2018 {
2019- mc::BufferQueue q(3, allocator, basic_properties);
2020+ mc::BufferQueue q(3, allocator, basic_properties, policy_factory);
2021
2022 auto handle = client_acquire_async(q);
2023 ASSERT_THAT(handle->has_acquired_buffer(), Eq(true));
2024@@ -386,7 +394,7 @@
2025 {
2026 for (int nbuffers = 3; nbuffers <= max_nbuffers_to_test; ++nbuffers)
2027 {
2028- mc::BufferQueue q(nbuffers, allocator, basic_properties);
2029+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
2030
2031 auto handle1 = client_acquire_async(q);
2032 ASSERT_THAT(handle1->has_acquired_buffer(), Eq(true));
2033@@ -406,7 +414,7 @@
2034 {
2035 for (int nbuffers = 2; nbuffers <= max_nbuffers_to_test; ++nbuffers)
2036 {
2037- mc::BufferQueue q(nbuffers, allocator, basic_properties);
2038+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
2039
2040 std::atomic<bool> done(false);
2041 auto unblock = [&done] { done = true; };
2042@@ -441,7 +449,7 @@
2043 {
2044 for (int nbuffers = 1; nbuffers <= max_nbuffers_to_test; ++nbuffers)
2045 {
2046- mc::BufferQueue q(nbuffers, allocator, basic_properties);
2047+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
2048
2049 auto handle = client_acquire_async(q);
2050 ASSERT_THAT(handle->has_acquired_buffer(), Eq(true));
2051@@ -459,7 +467,7 @@
2052 {
2053 for (int nbuffers = 1; nbuffers <= max_nbuffers_to_test; ++nbuffers)
2054 {
2055- mc::BufferQueue q(nbuffers, allocator, basic_properties);
2056+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
2057
2058 auto handle = client_acquire_async(q);
2059 ASSERT_THAT(handle->has_acquired_buffer(), Eq(true));
2060@@ -481,7 +489,7 @@
2061 {
2062 for (int nbuffers = 2; nbuffers <= max_nbuffers_to_test; ++nbuffers)
2063 {
2064- mc::BufferQueue q(nbuffers, allocator, basic_properties);
2065+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
2066
2067 for (int i = 0; i < 10; ++i)
2068 {
2069@@ -515,7 +523,7 @@
2070 {
2071 for (int nbuffers = 1; nbuffers <= max_nbuffers_to_test; ++nbuffers)
2072 {
2073- mc::BufferQueue q(nbuffers, allocator, basic_properties);
2074+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
2075
2076 for (int i = 0; i < 100; i++)
2077 {
2078@@ -529,7 +537,7 @@
2079 {
2080 for (int nbuffers = 2; nbuffers <= max_nbuffers_to_test; ++nbuffers)
2081 {
2082- mc::BufferQueue q(nbuffers, allocator, basic_properties);
2083+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
2084 q.allow_framedropping(false);
2085
2086 std::atomic<bool> done(false);
2087@@ -567,7 +575,7 @@
2088 {
2089 for (int nbuffers = 1; nbuffers <= max_nbuffers_to_test; ++nbuffers)
2090 {
2091- mc::BufferQueue q(nbuffers, allocator, basic_properties);
2092+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
2093
2094 mg::BufferID client_id;
2095
2096@@ -596,7 +604,7 @@
2097 {
2098 for (int nbuffers = 1; nbuffers <= max_nbuffers_to_test; ++nbuffers)
2099 {
2100- mc::BufferQueue q(nbuffers, allocator, basic_properties);
2101+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
2102
2103 auto handle = client_acquire_async(q);
2104 ASSERT_THAT(handle->has_acquired_buffer(), Eq(true));
2105@@ -611,7 +619,7 @@
2106 /* Regression test for LP#1270964 */
2107 TEST_F(BufferQueueTest, compositor_client_interleaved)
2108 {
2109- mc::BufferQueue q(3, allocator, basic_properties);
2110+ mc::BufferQueue q(3, allocator, basic_properties, policy_factory);
2111
2112 auto handle = client_acquire_async(q);
2113 ASSERT_THAT(handle->has_acquired_buffer(), Eq(true));
2114@@ -636,7 +644,7 @@
2115 // This test simulates bypass behaviour
2116 for (int nbuffers = 2; nbuffers <= max_nbuffers_to_test; ++nbuffers)
2117 {
2118- mc::BufferQueue q(nbuffers, allocator, basic_properties);
2119+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
2120
2121 std::shared_ptr<mg::Buffer> compositor[2];
2122
2123@@ -673,7 +681,7 @@
2124 {
2125 for (int nbuffers = 1; nbuffers <= max_nbuffers_to_test; ++nbuffers)
2126 {
2127- mc::BufferQueue q(nbuffers, allocator, basic_properties);
2128+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
2129
2130 auto comp_buffer = q.compositor_acquire(this);
2131 auto snapshot = q.snapshot_acquire();
2132@@ -687,7 +695,7 @@
2133 {
2134 for (int nbuffers = 1; nbuffers <= max_nbuffers_to_test; ++nbuffers)
2135 {
2136- mc::BufferQueue q(nbuffers, allocator, basic_properties);
2137+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
2138 int const num_snapshots = 100;
2139
2140 std::shared_ptr<mg::Buffer> buf[num_snapshots];
2141@@ -703,7 +711,7 @@
2142 {
2143 for (int nbuffers = 2; nbuffers <= max_nbuffers_to_test; ++nbuffers)
2144 {
2145- mc::BufferQueue q(nbuffers, allocator, basic_properties);
2146+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
2147
2148 auto handle = client_acquire_async(q);
2149 ASSERT_THAT(handle->has_acquired_buffer(), Eq(true));
2150@@ -727,7 +735,7 @@
2151 {
2152 for (int nbuffers = 2; nbuffers <= max_nbuffers_to_test; ++nbuffers)
2153 {
2154- mc::BufferQueue q(nbuffers, allocator, basic_properties);
2155+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
2156
2157 std::atomic<bool> done(false);
2158
2159@@ -760,7 +768,7 @@
2160 {
2161 for (int nbuffers = 3; nbuffers <= max_nbuffers_to_test; ++nbuffers)
2162 {
2163- mc::BufferQueue q(nbuffers, allocator, basic_properties);
2164+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
2165
2166 std::shared_ptr<mg::Buffer> compositor[2];
2167
2168@@ -801,7 +809,7 @@
2169 {
2170 for (int nbuffers = 2; nbuffers <= max_nbuffers_to_test; ++nbuffers)
2171 {
2172- mc::BufferQueue q(nbuffers, allocator, basic_properties);
2173+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
2174 q.allow_framedropping(true);
2175
2176 int const nframes = 100;
2177@@ -823,7 +831,7 @@
2178 {
2179 for (int nbuffers = 2; nbuffers <= max_nbuffers_to_test; ++nbuffers)
2180 {
2181- mc::BufferQueue q(nbuffers, allocator, basic_properties);
2182+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
2183 q.allow_framedropping(false);
2184
2185 int const max_ownable_buffers = nbuffers - 1;
2186@@ -848,7 +856,7 @@
2187 {
2188 for (int nbuffers = 2; nbuffers <= 3; nbuffers++)
2189 {
2190- mc::BufferQueue q(nbuffers, allocator, basic_properties);
2191+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
2192 unsigned long client_frames = 0;
2193 const unsigned long compose_frames = 20;
2194
2195@@ -904,7 +912,7 @@
2196 */
2197 for (int nbuffers = 3; nbuffers <= 3; nbuffers++)
2198 {
2199- mc::BufferQueue q(nbuffers, allocator, basic_properties);
2200+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
2201 unsigned long client_frames = 0;
2202 unsigned long const compose_frames = 100;
2203 auto const frame_time = std::chrono::milliseconds(16);
2204@@ -962,7 +970,7 @@
2205 {
2206 for (int nbuffers = 1; nbuffers <= max_nbuffers_to_test; ++nbuffers)
2207 {
2208- mc::BufferQueue q(nbuffers, allocator, basic_properties);
2209+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
2210
2211 for (int width = 1; width < 100; ++width)
2212 {
2213@@ -985,11 +993,19 @@
2214 }
2215 }
2216
2217+namespace
2218+{
2219+int max_ownable_buffers(int nbuffers)
2220+{
2221+ return (nbuffers == 1) ? 1 : nbuffers - 1;
2222+}
2223+}
2224+
2225 TEST_F(BufferQueueTest, compositor_acquires_resized_frames)
2226 {
2227 for (int nbuffers = 1; nbuffers <= max_nbuffers_to_test; ++nbuffers)
2228 {
2229- mc::BufferQueue q(nbuffers, allocator, basic_properties);
2230+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
2231 mg::BufferID history[5];
2232
2233 const int width0 = 123;
2234@@ -998,9 +1014,8 @@
2235 const int dy = -3;
2236 int width = width0;
2237 int height = height0;
2238- int const nbuffers_to_use = nbuffers == 1 ? 1 : nbuffers - 1;
2239
2240- for (int produce = 0; produce < nbuffers_to_use; ++produce)
2241+ for (int produce = 0; produce < max_ownable_buffers(nbuffers); ++produce)
2242 {
2243 geom::Size new_size{width, height};
2244 width += dx;
2245@@ -1018,7 +1033,7 @@
2246 width = width0;
2247 height = height0;
2248
2249- for (int consume = 0; consume < nbuffers_to_use; ++consume)
2250+ for (int consume = 0; consume < max_ownable_buffers(nbuffers); ++consume)
2251 {
2252 geom::Size expect_size{width, height};
2253 width += dx;
2254@@ -1047,12 +1062,79 @@
2255 }
2256 }
2257
2258+TEST_F(BufferQueueTest, uncomposited_client_swaps_when_policy_triggered)
2259+{
2260+ for (int nbuffers = 2;
2261+ nbuffers < max_nbuffers_to_test;
2262+ nbuffers++)
2263+ {
2264+ mtd::MockFrameDroppingPolicyFactory policy_factory;
2265+ mc::BufferQueue q(nbuffers,
2266+ allocator,
2267+ basic_properties,
2268+ policy_factory);
2269+
2270+ for (int i = 0; i < max_ownable_buffers(nbuffers); i++)
2271+ {
2272+ auto client = client_acquire_sync(q);
2273+ q.client_release(client);
2274+ }
2275+
2276+ auto handle = client_acquire_async(q);
2277+
2278+ EXPECT_FALSE(handle->has_acquired_buffer());
2279+
2280+ policy_factory.trigger_policies();
2281+ EXPECT_TRUE(handle->has_acquired_buffer());
2282+ }
2283+}
2284+
2285+TEST_F(BufferQueueTest, partially_composited_client_swaps_when_policy_triggered)
2286+{
2287+ for (int nbuffers = 2;
2288+ nbuffers < max_nbuffers_to_test;
2289+ nbuffers++)
2290+ {
2291+ mtd::MockFrameDroppingPolicyFactory policy_factory;
2292+ mc::BufferQueue q(nbuffers,
2293+ allocator,
2294+ basic_properties,
2295+ policy_factory);
2296+
2297+ for (int i = 0; i < max_ownable_buffers(nbuffers); i++)
2298+ {
2299+ auto client = client_acquire_sync(q);
2300+ q.client_release(client);
2301+ }
2302+
2303+ /* Queue up two pending swaps */
2304+ auto first_swap = client_acquire_async(q);
2305+ auto second_swap = client_acquire_async(q);
2306+
2307+ ASSERT_FALSE(first_swap->has_acquired_buffer());
2308+ ASSERT_FALSE(second_swap->has_acquired_buffer());
2309+
2310+ q.compositor_acquire(nullptr);
2311+
2312+ EXPECT_TRUE(first_swap->has_acquired_buffer());
2313+ EXPECT_FALSE(second_swap->has_acquired_buffer());
2314+
2315+ /* We have to release a client buffer here; framedropping or not,
2316+ * a client can't have 2 buffers outstanding in the nbuffers = 2 case.
2317+ */
2318+ first_swap->release_buffer();
2319+
2320+ policy_factory.trigger_policies();
2321+ EXPECT_TRUE(second_swap->has_acquired_buffer());
2322+ }
2323+}
2324+
2325 TEST_F(BufferQueueTest, with_single_buffer_compositor_acquires_resized_frames_eventually)
2326 {
2327 int const nbuffers{1};
2328 geom::Size const new_size{123,456};
2329
2330- mc::BufferQueue q(nbuffers, allocator, basic_properties);
2331+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
2332
2333 q.client_release(client_acquire_sync(q));
2334 q.resize(new_size);
2335@@ -1072,7 +1154,7 @@
2336 { // Regression test for LP: #1319765
2337 using namespace testing;
2338
2339- mc::BufferQueue q{2, allocator, basic_properties};
2340+ mc::BufferQueue q{2, allocator, basic_properties, policy_factory};
2341
2342 q.client_release(client_acquire_sync(q));
2343 auto a = q.compositor_acquire(this);
2344@@ -1095,7 +1177,7 @@
2345 { // Extended regression test for LP: #1319765
2346 using namespace testing;
2347
2348- mc::BufferQueue q{2, allocator, basic_properties};
2349+ mc::BufferQueue q{2, allocator, basic_properties, policy_factory};
2350
2351 for (int i = 0; i < 100; ++i)
2352 {
2353@@ -1137,7 +1219,7 @@
2354
2355 int const nbuffers{3};
2356
2357- mc::BufferQueue q{nbuffers, allocator, basic_properties};
2358+ mc::BufferQueue q{nbuffers, allocator, basic_properties, policy_factory};
2359 q.allow_framedropping(true);
2360
2361 std::vector<std::shared_ptr<mg::Buffer>> buffers;
2362@@ -1183,7 +1265,7 @@
2363 {
2364 for (int nbuffers = 2; nbuffers <= max_nbuffers_to_test; ++nbuffers)
2365 {
2366- mc::BufferQueue q(nbuffers, allocator, basic_properties);
2367+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
2368
2369 std::mutex client_buffer_guard;
2370 mg::Buffer* client_buffer = nullptr;
2371@@ -1231,7 +1313,7 @@
2372 {
2373 for (int nbuffers = 2; nbuffers <= max_nbuffers_to_test; ++nbuffers)
2374 {
2375- mc::BufferQueue q(nbuffers, allocator, basic_properties);
2376+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
2377 for (int i = 0; i < 100; ++i)
2378 {
2379 auto handle = client_acquire_async(q);
2380@@ -1267,7 +1349,7 @@
2381 {
2382 for (int nbuffers = 3; nbuffers <= max_nbuffers_to_test; ++nbuffers)
2383 {
2384- mc::BufferQueue q(nbuffers, allocator, basic_properties);
2385+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
2386
2387 void const* main_compositor = reinterpret_cast<void const*>(0);
2388 void const* second_compositor = reinterpret_cast<void const*>(1);
2389@@ -1327,7 +1409,7 @@
2390 {
2391 for (int nbuffers = 2; nbuffers <= max_nbuffers_to_test; ++nbuffers)
2392 {
2393- mc::BufferQueue q(nbuffers, allocator, basic_properties);
2394+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
2395 q.allow_framedropping(false);
2396
2397 std::atomic<bool> done(false);
2398@@ -1368,7 +1450,7 @@
2399 TEST_F(BufferQueueTest, DISABLED_lp_1317801_regression_test)
2400 {
2401 int const nbuffers = 3;
2402- mc::BufferQueue q(nbuffers, allocator, basic_properties);
2403+ mc::BufferQueue q(nbuffers, allocator, basic_properties, policy_factory);
2404
2405 q.client_release(client_acquire_sync(q));
2406
2407
2408=== modified file 'tests/unit-tests/compositor/test_multi_threaded_compositor.cpp'
2409--- tests/unit-tests/compositor/test_multi_threaded_compositor.cpp 2014-05-05 03:36:45 +0000
2410+++ tests/unit-tests/compositor/test_multi_threaded_compositor.cpp 2014-05-21 00:42:11 +0000
2411@@ -29,9 +29,6 @@
2412 #include "mir_test_doubles/mock_display_buffer.h"
2413 #include "mir_test_doubles/mock_compositor_report.h"
2414 #include "mir_test_doubles/mock_scene.h"
2415-#include "mir_test_doubles/stub_renderable.h"
2416-#include "mir_test_doubles/null_display_buffer_compositor_factory.h"
2417-#include "mir_test/spin_wait.h"
2418
2419 #include <boost/throw_exception.hpp>
2420
2421@@ -40,7 +37,6 @@
2422 #include <thread>
2423 #include <mutex>
2424 #include <chrono>
2425-#include <atomic>
2426
2427 #include <gmock/gmock.h>
2428 #include <gtest/gtest.h>
2429@@ -103,7 +99,7 @@
2430
2431 mg::RenderableList renderable_list_for(void const*) const
2432 {
2433- return renderable_list;
2434+ return mg::RenderableList{};
2435 }
2436
2437 void add_observer(std::shared_ptr<ms::Observer> const& observer_)
2438@@ -318,35 +314,19 @@
2439 unsigned int render_count;
2440 };
2441
2442-enum class RenderableVisibility { hidden, visible };
2443-
2444-class BufferCountingRenderable : public mtd::StubRenderable
2445+class NullDisplayBufferCompositorFactory : public mc::DisplayBufferCompositorFactory
2446 {
2447 public:
2448- BufferCountingRenderable(RenderableVisibility visibility)
2449- : buffers_requested_{0}, visibility{visibility}
2450- {
2451- }
2452-
2453- std::shared_ptr<mg::Buffer> buffer() const override
2454- {
2455- ++buffers_requested_;
2456- return std::make_shared<mtd::StubBuffer>();
2457- }
2458-
2459- bool visible() const override
2460- {
2461- return visibility == RenderableVisibility::visible;
2462- }
2463-
2464- int buffers_requested() const
2465- {
2466- return buffers_requested_;
2467- }
2468-
2469-private:
2470- mutable std::atomic<int> buffers_requested_;
2471- RenderableVisibility const visibility;
2472+ std::unique_ptr<mc::DisplayBufferCompositor> create_compositor_for(mg::DisplayBuffer&)
2473+ {
2474+ struct NullDisplayBufferCompositor : mc::DisplayBufferCompositor
2475+ {
2476+ bool composite() { return false; }
2477+ };
2478+
2479+ auto raw = new NullDisplayBufferCompositor{};
2480+ return std::unique_ptr<NullDisplayBufferCompositor>(raw);
2481+ }
2482 };
2483
2484 auto const null_report = mr::null_compositor_report();
2485@@ -573,7 +553,7 @@
2486
2487 auto display = std::make_shared<StubDisplayWithMockBuffers>(nbuffers);
2488 auto scene = std::make_shared<StubScene>();
2489- auto db_compositor_factory = std::make_shared<mtd::NullDisplayBufferCompositorFactory>();
2490+ auto db_compositor_factory = std::make_shared<NullDisplayBufferCompositorFactory>();
2491 mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_report, true};
2492
2493 display->for_each_mock_buffer([](mtd::MockDisplayBuffer& mock_buf)
2494@@ -590,12 +570,12 @@
2495
2496 TEST(MultiThreadedCompositor, double_start_or_stop_ignored)
2497 {
2498- using namespace ::testing;
2499+ using namespace testing;
2500
2501 unsigned int const nbuffers{3};
2502 auto display = std::make_shared<StubDisplayWithMockBuffers>(nbuffers);
2503 auto mock_scene = std::make_shared<mtd::MockScene>();
2504- auto db_compositor_factory = std::make_shared<mtd::NullDisplayBufferCompositorFactory>();
2505+ auto db_compositor_factory = std::make_shared<NullDisplayBufferCompositorFactory>();
2506 auto mock_report = std::make_shared<testing::NiceMock<mtd::MockCompositorReport>>();
2507
2508 EXPECT_CALL(*mock_report, started())
2509@@ -618,49 +598,6 @@
2510 compositor.stop();
2511 }
2512
2513-namespace
2514-{
2515-struct BufferConsumption : ::testing::TestWithParam<RenderableVisibility> {};
2516-}
2517-
2518-TEST_P(BufferConsumption, consumes_buffers_for_renderables_that_are_not_rendered)
2519-{
2520- using namespace testing;
2521-
2522- unsigned int const nbuffers{2};
2523- auto renderable = std::make_shared<BufferCountingRenderable>(GetParam());
2524- auto display = std::make_shared<StubDisplay>(nbuffers);
2525- auto stub_scene = std::make_shared<StubScene>(mg::RenderableList{renderable});
2526- // We use NullDisplayBufferCompositors to simulate DisplayBufferCompositors
2527- // not rendering a renderable.
2528- auto db_compositor_factory = std::make_shared<mtd::NullDisplayBufferCompositorFactory>();
2529-
2530- mc::MultiThreadedCompositor compositor{
2531- display, stub_scene, db_compositor_factory, null_report, true};
2532-
2533- compositor.start();
2534-
2535- mir::test::spin_wait_for_condition_or_timeout(
2536- [&] { return renderable->buffers_requested() == 1; },
2537- std::chrono::seconds{5});
2538-
2539- EXPECT_THAT(renderable->buffers_requested(), Eq(1));
2540-
2541- stub_scene->emit_change_event();
2542-
2543- mir::test::spin_wait_for_condition_or_timeout(
2544- [&] { return renderable->buffers_requested() == 2; },
2545- std::chrono::seconds{5});
2546-
2547- EXPECT_THAT(renderable->buffers_requested(), Eq(2));
2548-
2549- compositor.stop();
2550-}
2551-
2552-INSTANTIATE_TEST_CASE_P(
2553- MultiThreadedCompositor, BufferConsumption,
2554- ::testing::Values(RenderableVisibility::hidden, RenderableVisibility::visible));
2555-
2556 TEST(MultiThreadedCompositor, cleans_up_after_throw_in_start)
2557 {
2558 unsigned int const nbuffers{3};
2559
2560=== added file 'tests/unit-tests/compositor/test_timeout_frame_dropping_policy.cpp'
2561--- tests/unit-tests/compositor/test_timeout_frame_dropping_policy.cpp 1970-01-01 00:00:00 +0000
2562+++ tests/unit-tests/compositor/test_timeout_frame_dropping_policy.cpp 2014-05-21 00:42:11 +0000
2563@@ -0,0 +1,186 @@
2564+/*
2565+ * Copyright © 2014 Canonical Ltd.
2566+ *
2567+ * This program is free software: you can redistribute it and/or modify it
2568+ * under the terms of the GNU General Public License version 3,
2569+ * as published by the Free Software Foundation.
2570+ *
2571+ * This program is distributed in the hope that it will be useful,
2572+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2573+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2574+ * GNU General Public License for more details.
2575+ *
2576+ * You should have received a copy of the GNU General Public License
2577+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2578+ *
2579+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
2580+ */
2581+
2582+#include "src/server/compositor/timeout_frame_dropping_policy_factory.h"
2583+#include "mir/compositor/frame_dropping_policy.h"
2584+
2585+#include "mir_test_doubles/mock_timer.h"
2586+
2587+#include "mir_test/gmock_fixes.h"
2588+
2589+#include <stdexcept>
2590+
2591+#include <gtest/gtest.h>
2592+#include <gmock/gmock.h>
2593+
2594+namespace mc = mir::compositor;
2595+namespace mt = mir::test;
2596+namespace mtd = mir::test::doubles;
2597+
2598+TEST(TimeoutFrameDroppingPolicy, does_not_fire_before_notified_of_block)
2599+{
2600+ auto clock = std::make_shared<mt::FakeClock>();
2601+ auto timer = std::make_shared<mtd::FakeTimer>(clock);
2602+ std::chrono::milliseconds const timeout{1000};
2603+ bool frame_dropped{false};
2604+
2605+ mc::TimeoutFrameDroppingPolicyFactory factory{timer, timeout};
2606+ auto policy = factory.create_policy([&frame_dropped]{ frame_dropped = true; });
2607+
2608+ clock->advance_time(timeout + std::chrono::milliseconds{1});
2609+ EXPECT_FALSE(frame_dropped);
2610+}
2611+
2612+TEST(TimeoutFrameDroppingPolicy, schedules_alarm_for_correct_timeout)
2613+{
2614+ auto clock = std::make_shared<mt::FakeClock>();
2615+ auto timer = std::make_shared<mtd::FakeTimer>(clock);
2616+ std::chrono::milliseconds const timeout{1000};
2617+ bool frame_dropped{false};
2618+
2619+ mc::TimeoutFrameDroppingPolicyFactory factory{timer, timeout};
2620+ auto policy = factory.create_policy([&frame_dropped]{ frame_dropped = true; });
2621+ policy->swap_now_blocking();
2622+
2623+ clock->advance_time(timeout - std::chrono::milliseconds{1});
2624+ EXPECT_FALSE(frame_dropped);
2625+ clock->advance_time(std::chrono::milliseconds{2});
2626+ EXPECT_TRUE(frame_dropped);
2627+}
2628+
2629+TEST(TimeoutFrameDroppingPolicy, framedrop_callback_cancelled_by_unblock)
2630+{
2631+ auto clock = std::make_shared<mt::FakeClock>();
2632+ auto timer = std::make_shared<mtd::FakeTimer>(clock);
2633+ std::chrono::milliseconds const timeout{1000};
2634+
2635+ bool frame_dropped{false};
2636+ mc::TimeoutFrameDroppingPolicyFactory factory{timer, timeout};
2637+ auto policy = factory.create_policy([&frame_dropped]{ frame_dropped = true; });
2638+
2639+ policy->swap_now_blocking();
2640+ policy->swap_unblocked();
2641+
2642+ clock->advance_time(timeout * 10);
2643+
2644+ EXPECT_FALSE(frame_dropped);
2645+}
2646+
2647+TEST(TimeoutFrameDroppingPolicy, policy_drops_one_frame_per_blocking_swap)
2648+{
2649+ auto clock = std::make_shared<mt::FakeClock>();
2650+ auto timer = std::make_shared<mtd::FakeTimer>(clock);
2651+ std::chrono::milliseconds const timeout{1000};
2652+
2653+ bool frame_dropped{false};
2654+ mc::TimeoutFrameDroppingPolicyFactory factory{timer, timeout};
2655+ auto policy = factory.create_policy([&frame_dropped]{ frame_dropped = true; });
2656+
2657+ policy->swap_now_blocking();
2658+ policy->swap_now_blocking();
2659+ policy->swap_now_blocking();
2660+
2661+ clock->advance_time(timeout + std::chrono::milliseconds{1});
2662+ EXPECT_TRUE(frame_dropped);
2663+
2664+ frame_dropped = false;
2665+ clock->advance_time(timeout + std::chrono::milliseconds{2});
2666+ EXPECT_TRUE(frame_dropped);
2667+
2668+ frame_dropped = false;
2669+ clock->advance_time(timeout + std::chrono::milliseconds{1});
2670+ EXPECT_TRUE(frame_dropped);
2671+
2672+ frame_dropped = false;
2673+ clock->advance_time(timeout + std::chrono::milliseconds{1});
2674+ EXPECT_FALSE(frame_dropped);
2675+}
2676+
2677+TEST(TimeoutFrameDroppingPolicy, policy_drops_frames_no_more_frequently_than_timeout)
2678+{
2679+ auto clock = std::make_shared<mt::FakeClock>();
2680+ auto timer = std::make_shared<mtd::FakeTimer>(clock);
2681+ std::chrono::milliseconds const timeout{1000};
2682+
2683+ bool frame_dropped{false};
2684+ mc::TimeoutFrameDroppingPolicyFactory factory{timer, timeout};
2685+ auto policy = factory.create_policy([&frame_dropped]{ frame_dropped = true; });
2686+
2687+ policy->swap_now_blocking();
2688+ policy->swap_now_blocking();
2689+
2690+ clock->advance_time(timeout + std::chrono::milliseconds{1});
2691+ EXPECT_TRUE(frame_dropped);
2692+
2693+ frame_dropped = false;
2694+ clock->advance_time(timeout - std::chrono::milliseconds{1});
2695+ EXPECT_FALSE(frame_dropped);
2696+ clock->advance_time(std::chrono::milliseconds{2});
2697+ EXPECT_TRUE(frame_dropped);
2698+}
2699+
2700+TEST(TimeoutFrameDroppingPolicy, newly_blocking_frame_doesnt_reset_timeout)
2701+{
2702+ auto clock = std::make_shared<mt::FakeClock>();
2703+ auto timer = std::make_shared<mtd::FakeTimer>(clock);
2704+ std::chrono::milliseconds const timeout{1000};
2705+
2706+ bool frame_dropped{false};
2707+ mc::TimeoutFrameDroppingPolicyFactory factory{timer, timeout};
2708+ auto policy = factory.create_policy([&frame_dropped]{ frame_dropped = true; });
2709+
2710+ policy->swap_now_blocking();
2711+ clock->advance_time(timeout - std::chrono::milliseconds{1});
2712+
2713+ policy->swap_now_blocking();
2714+ clock->advance_time(std::chrono::milliseconds{2});
2715+ EXPECT_TRUE(frame_dropped);
2716+}
2717+
2718+TEST(TimeoutFrameDroppingPolicy, interspersed_timeouts_and_unblocks)
2719+{
2720+ auto clock = std::make_shared<mt::FakeClock>();
2721+ auto timer = std::make_shared<mtd::FakeTimer>(clock);
2722+ std::chrono::milliseconds const timeout{1000};
2723+
2724+ bool frame_dropped{false};
2725+ mc::TimeoutFrameDroppingPolicyFactory factory{timer, timeout};
2726+ auto policy = factory.create_policy([&frame_dropped]{ frame_dropped = true; });
2727+
2728+ policy->swap_now_blocking();
2729+ policy->swap_now_blocking();
2730+ policy->swap_now_blocking();
2731+
2732+ /* First frame gets dropped... */
2733+ clock->advance_time(timeout + std::chrono::milliseconds{1});
2734+ EXPECT_TRUE(frame_dropped);
2735+
2736+ /* ...Compositor gets its act in order and consumes a frame... */
2737+ frame_dropped = false;
2738+ policy->swap_unblocked();
2739+ clock->advance_time(timeout - std::chrono::milliseconds{1});
2740+ EXPECT_FALSE(frame_dropped);
2741+
2742+ /* ...but not a second frame, so third swap should trigger a timeout */
2743+ clock->advance_time(std::chrono::milliseconds{2});
2744+ EXPECT_TRUE(frame_dropped);
2745+
2746+ frame_dropped = false;
2747+ clock->advance_time(timeout + std::chrono::milliseconds{1});
2748+ EXPECT_FALSE(frame_dropped);
2749+}
2750
2751=== modified file 'tests/unit-tests/graphics/mesa/test_linux_virtual_terminal.cpp'
2752--- tests/unit-tests/graphics/mesa/test_linux_virtual_terminal.cpp 2014-05-19 02:55:29 +0000
2753+++ tests/unit-tests/graphics/mesa/test_linux_virtual_terminal.cpp 2014-05-21 00:42:11 +0000
2754@@ -26,6 +26,7 @@
2755
2756 #include <gtest/gtest.h>
2757 #include <gmock/gmock.h>
2758+#include "mir_test/gmock_fixes.h"
2759
2760 #include <stdexcept>
2761
2762
2763=== modified file 'tests/unit-tests/test_asio_main_loop.cpp'
2764--- tests/unit-tests/test_asio_main_loop.cpp 2014-05-20 08:38:05 +0000
2765+++ tests/unit-tests/test_asio_main_loop.cpp 2014-05-21 00:42:11 +0000
2766@@ -20,6 +20,7 @@
2767 #include "mir/time/high_resolution_clock.h"
2768 #include "mir_test/pipe.h"
2769 #include "mir_test/auto_unblock_thread.h"
2770+#include "mir_test/signal.h"
2771 #include "mir_test/wait_object.h"
2772
2773 #include <gtest/gtest.h>
2774@@ -325,58 +326,54 @@
2775
2776 TEST_F(AsioMainLoopAlarmTest, main_loop_runs_until_stop_called)
2777 {
2778- std::mutex checkpoint_mutex;
2779- std::condition_variable checkpoint;
2780- bool hit_checkpoint{false};
2781+ auto mainloop_started = std::make_shared<mt::Signal>();
2782
2783 auto fire_on_mainloop_start = ml.notify_in(std::chrono::milliseconds{0},
2784- [&checkpoint_mutex, &checkpoint, &hit_checkpoint]()
2785+ [mainloop_started]()
2786 {
2787- std::unique_lock<decltype(checkpoint_mutex)> lock(checkpoint_mutex);
2788- hit_checkpoint = true;
2789- checkpoint.notify_all();
2790+ mainloop_started->raise();
2791 });
2792
2793 UnblockMainLoop unblocker(ml);
2794
2795- {
2796- std::unique_lock<decltype(checkpoint_mutex)> lock(checkpoint_mutex);
2797- ASSERT_TRUE(checkpoint.wait_for(lock, std::chrono::milliseconds{100}, [&hit_checkpoint]() { return hit_checkpoint; }));
2798- }
2799+ ASSERT_TRUE(mainloop_started->wait_for(std::chrono::milliseconds{100}));
2800
2801- auto alarm = ml.notify_in(std::chrono::milliseconds{10}, [this]
2802+ auto timer_fired = std::make_shared<mt::Signal>();
2803+ auto alarm = ml.notify_in(std::chrono::milliseconds{10}, [timer_fired]
2804 {
2805- wait.notify_ready();
2806+ timer_fired->raise();
2807 });
2808
2809- EXPECT_NO_THROW(wait.wait_until_ready(std::chrono::milliseconds{500}));
2810+ EXPECT_TRUE(timer_fired->wait_for(std::chrono::milliseconds{500}));
2811
2812 ml.stop();
2813 // Main loop should be stopped now
2814
2815- hit_checkpoint = false;
2816+ timer_fired = std::make_shared<mt::Signal>();
2817 auto should_not_fire = ml.notify_in(std::chrono::milliseconds{0},
2818- [&checkpoint_mutex, &checkpoint, &hit_checkpoint]()
2819+ [timer_fired]()
2820 {
2821- std::unique_lock<decltype(checkpoint_mutex)> lock(checkpoint_mutex);
2822- hit_checkpoint = true;
2823- checkpoint.notify_all();
2824+ timer_fired->raise();
2825 });
2826
2827- std::unique_lock<decltype(checkpoint_mutex)> lock(checkpoint_mutex);
2828- EXPECT_FALSE(checkpoint.wait_for(lock, std::chrono::milliseconds{50}, [&hit_checkpoint]() { return hit_checkpoint; }));
2829+ EXPECT_FALSE(timer_fired->wait_for(std::chrono::milliseconds{100}));
2830 }
2831
2832 TEST_F(AsioMainLoopAlarmTest, alarm_fires_with_correct_delay)
2833 {
2834- auto alarm = ml.notify_in(std::chrono::milliseconds{50}, [this]()
2835+ UnblockMainLoop unblocker(ml);
2836+
2837+ auto timer_fired = std::make_shared<mt::Signal>();
2838+ auto alarm = ml.notify_in(std::chrono::milliseconds{100}, [timer_fired]()
2839 {
2840- wait.notify_ready();
2841+ timer_fired->raise();
2842 });
2843
2844- UnblockMainLoop unblocker(ml);
2845+ // Shouldn't fire before timeout
2846+ EXPECT_FALSE(timer_fired->wait_for(std::chrono::milliseconds{50}));
2847
2848- EXPECT_NO_THROW(wait.wait_until_ready(std::chrono::milliseconds{100}));
2849+ // Give a nice, long wait for our slow ARM valgrind friends
2850+ EXPECT_TRUE(timer_fired->wait_for(std::chrono::milliseconds{200}));
2851 }
2852
2853 TEST_F(AsioMainLoopAlarmTest, multiple_alarms_fire)
2854@@ -385,120 +382,129 @@
2855 std::atomic<int> call_count{0};
2856 std::array<std::unique_ptr<mir::time::Alarm>, alarm_count> alarms;
2857
2858+ auto alarms_fired = std::make_shared<mt::Signal>();
2859+
2860 for (auto& alarm : alarms)
2861 {
2862- alarm = ml.notify_in(std::chrono::milliseconds{50}, [this, &call_count]()
2863+ alarm = ml.notify_in(std::chrono::milliseconds{5}, [alarms_fired, &call_count]()
2864 {
2865- call_count.fetch_add(1);
2866+ call_count++;
2867 if (call_count == alarm_count)
2868- wait.notify_ready();
2869+ alarms_fired->raise();
2870 });
2871 }
2872
2873 UnblockMainLoop unblocker(ml);
2874
2875- EXPECT_NO_THROW(wait.wait_until_ready(std::chrono::milliseconds{100}));
2876+ EXPECT_TRUE(alarms_fired->wait_for(std::chrono::milliseconds{100}));
2877+
2878 for (auto& alarm : alarms)
2879 EXPECT_EQ(mir::time::Alarm::triggered, alarm->state());
2880 }
2881
2882-
2883 TEST_F(AsioMainLoopAlarmTest, alarm_changes_to_triggered_state)
2884 {
2885- auto alarm = ml.notify_in(std::chrono::milliseconds{50}, [this]()
2886+ auto alarm_fired = std::make_shared<mt::Signal>();
2887+ auto alarm = ml.notify_in(std::chrono::milliseconds{5}, [alarm_fired]()
2888 {
2889- wait.notify_ready();
2890+ alarm_fired->raise();
2891 });
2892
2893 UnblockMainLoop unblocker(ml);
2894
2895- EXPECT_NO_THROW(wait.wait_until_ready(std::chrono::milliseconds{100}));
2896+ ASSERT_TRUE(alarm_fired->wait_for(std::chrono::milliseconds{100}));
2897
2898 EXPECT_EQ(mir::time::Alarm::triggered, alarm->state());
2899 }
2900
2901 TEST_F(AsioMainLoopAlarmTest, alarm_starts_in_pending_state)
2902 {
2903- auto alarm = ml.notify_in(std::chrono::milliseconds{50}, [this]() {});
2904-
2905 UnblockMainLoop unblocker(ml);
2906
2907+ auto alarm = ml.notify_in(std::chrono::milliseconds{5000}, [](){});
2908+
2909 EXPECT_EQ(mir::time::Alarm::pending, alarm->state());
2910 }
2911
2912 TEST_F(AsioMainLoopAlarmTest, cancelled_alarm_doesnt_fire)
2913 {
2914- auto alarm = ml.notify_in(std::chrono::milliseconds{200}, [this]()
2915+ UnblockMainLoop unblocker(ml);
2916+ auto alarm_fired = std::make_shared<mt::Signal>();
2917+
2918+ auto alarm = ml.notify_in(std::chrono::milliseconds{100}, [alarm_fired]()
2919 {
2920- wait.notify_ready();
2921+ alarm_fired->raise();
2922 });
2923
2924- UnblockMainLoop unblocker(ml);
2925-
2926 EXPECT_TRUE(alarm->cancel());
2927- EXPECT_THROW(wait.wait_until_ready(std::chrono::milliseconds{300}), std::runtime_error);
2928+
2929+ EXPECT_FALSE(alarm_fired->wait_for(std::chrono::milliseconds{300}));
2930 EXPECT_EQ(mir::time::Alarm::cancelled, alarm->state());
2931 }
2932
2933 TEST_F(AsioMainLoopAlarmTest, destroyed_alarm_doesnt_fire)
2934 {
2935- auto alarm = ml.notify_in(std::chrono::milliseconds{200}, [this]()
2936+ auto alarm_fired = std::make_shared<mt::Signal>();
2937+
2938+ auto alarm = ml.notify_in(std::chrono::milliseconds{200}, [alarm_fired]()
2939 {
2940- wait.notify_ready();
2941+ alarm_fired->raise();
2942 });
2943
2944 UnblockMainLoop unblocker(ml);
2945
2946 alarm.reset(nullptr);
2947
2948- EXPECT_THROW(wait.wait_until_ready(std::chrono::milliseconds{300}), std::runtime_error);
2949+ EXPECT_FALSE(alarm_fired->wait_for(std::chrono::milliseconds{300}));
2950 }
2951
2952 TEST_F(AsioMainLoopAlarmTest, rescheduled_alarm_fires_again)
2953 {
2954- std::mutex m;
2955- std::condition_variable called;
2956+ auto first_trigger = std::make_shared<mt::Signal>();
2957+ auto second_trigger = std::make_shared<mt::Signal>();
2958+ std::atomic<int> call_count{0};
2959
2960- auto alarm = ml.notify_in(std::chrono::milliseconds{0}, [this, &m, &called]()
2961+ auto alarm = ml.notify_in(std::chrono::milliseconds{0}, [first_trigger, second_trigger, &call_count]()
2962 {
2963- std::unique_lock<decltype(m)> lock(m);
2964- call_count++;
2965- if (call_count == 2)
2966- wait.notify_ready();
2967- called.notify_all();
2968+ auto prev_call_count = call_count++;
2969+ if (prev_call_count == 0)
2970+ first_trigger->raise();
2971+ if (prev_call_count == 1)
2972+ second_trigger->raise();
2973+ if (prev_call_count > 1)
2974+ FAIL() << "Alarm called too many times";
2975 });
2976
2977 UnblockMainLoop unblocker(ml);
2978
2979- {
2980- std::unique_lock<decltype(m)> lock(m);
2981- ASSERT_TRUE(called.wait_for(lock,
2982- std::chrono::milliseconds{50},
2983- [this](){ return call_count == 1; }));
2984- }
2985-
2986+ ASSERT_TRUE(first_trigger->wait_for(std::chrono::milliseconds{50}));
2987 ASSERT_EQ(mir::time::Alarm::triggered, alarm->state());
2988+
2989+
2990 alarm->reschedule_in(std::chrono::milliseconds{100});
2991+
2992 EXPECT_EQ(mir::time::Alarm::pending, alarm->state());
2993
2994- EXPECT_NO_THROW(wait.wait_until_ready(std::chrono::milliseconds{500}));
2995+ EXPECT_TRUE(second_trigger->wait_for(std::chrono::milliseconds{500}));
2996 EXPECT_EQ(mir::time::Alarm::triggered, alarm->state());
2997 }
2998
2999 TEST_F(AsioMainLoopAlarmTest, rescheduled_alarm_cancels_previous_scheduling)
3000 {
3001- auto alarm = ml.notify_in(std::chrono::milliseconds{100}, [this]()
3002+ std::atomic<int> call_count{0};
3003+
3004+ auto alarm = ml.notify_in(std::chrono::milliseconds{50}, [&call_count]()
3005 {
3006 call_count++;
3007- wait.notify_ready();
3008 });
3009
3010 UnblockMainLoop unblocker(ml);
3011
3012- EXPECT_TRUE(alarm->reschedule_in(std::chrono::milliseconds{150}));
3013+ EXPECT_TRUE(alarm->reschedule_in(std::chrono::milliseconds{10}));
3014 EXPECT_EQ(mir::time::Alarm::pending, alarm->state());
3015
3016- EXPECT_NO_THROW(wait.wait_until_ready(std::chrono::milliseconds{500}));
3017+ std::this_thread::sleep_for(std::chrono::milliseconds{200});
3018+
3019 EXPECT_EQ(mir::time::Alarm::triggered, alarm->state());
3020 EXPECT_EQ(1, call_count);
3021 }
3022@@ -507,13 +513,18 @@
3023 {
3024 mir::time::HighResolutionClock clock;
3025
3026- mir::time::Timestamp real_soon = clock.sample() + std::chrono::microseconds{120};
3027+ mir::time::Timestamp real_soon = clock.sample() + std::chrono::milliseconds{120};
3028+ auto alarm_fired = std::make_shared<mt::Signal>();
3029
3030- auto alarm = ml.notify_at(real_soon, [this]() { wait.notify_ready(); });
3031+ auto alarm = ml.notify_at(real_soon, [alarm_fired]()
3032+ {
3033+ alarm_fired->raise();
3034+ });
3035
3036 UnblockMainLoop unblocker(ml);
3037
3038- EXPECT_NO_THROW(wait.wait_until_ready(std::chrono::milliseconds{200}));
3039+ EXPECT_FALSE(alarm_fired->wait_until(real_soon - std::chrono::milliseconds{50}));
3040+ EXPECT_TRUE(alarm_fired->wait_until(real_soon + std::chrono::milliseconds{50}));
3041 }
3042
3043 TEST(AsioMainLoopTest, dispatches_action)

Subscribers

People subscribed via source and target branches