Merge lp:~raof/mir/1hz-rendering-always into lp:mir
- 1hz-rendering-always
- Merge into development-branch
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 |
Related bugs: |
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:
|
This proposal supersedes a proposal from 2014-04-17.
Description of the change
The alternate solution to eglSwapBuffers blocking; make SwitchingBuffer
Defaults to 1Hz swapping, but it should be obvious how we could wedge in arbitrarily interesting policy here.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Chris Halse Rogers (raof) wrote : Posted in a previous version of this proposal | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1553
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https:/
http://
Executed test runs:
FAILURE: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1554
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https:/
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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?
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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_
805 + }
806 + });
This could be private function.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1555
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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(
could be better and not depend on real time.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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/
buffer_
buffer_
default_
multi_
switching_
switching_bundle.h | 13 ++-
6 files changed, 113 insertions(+), 144 deletions(-)
It solves all the same problems solved by Alexandros' non-blocking-
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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:/
https:/
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...
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:1557
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:1560
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:1561
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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_
412 + std::unique_
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_
};
SwitchingBundle
Needs Fixing (Needs discussion about the abstraction part)
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1563
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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_
virtual void swap_unblocked() = 0;
virtual bool frame_drop_
};
The SwitchingBundle would then call swap_now_
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1564
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Gerry Boland (gerboland) wrote : | # |
I missed all the discussion about this, but could someone please summarize why this is needed?
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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_
virtual swap_unblocked(
? That way we've (a) hidden the underlying Alarm implementation away, which would otherwise leak timeness, and (b) can choose between single-
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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?
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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_
~~~~
54 + void wait(void);
In C++ we say "void wait();"
~~~~
113 + explicit BufferStreamFac
s/const std::shared_
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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(
~~~
Is this an implicit policy? oldest with respect to what? client-release?
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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/
Text conflict in tests/unit-
2 conflicts encountered.
and then after hacking around those, manual testing seems OK.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Daniel van Vugt (vanvugt) wrote : | # |
Now BufferQueue has landed, lots more conflicts:
Text conflict in src/server/
Text conflict in src/server/
Contents conflict in src/server/
Contents conflict in src/server/
Text conflict in tests/integrati
Text conflict in tests/integrati
Text conflict in tests/integrati
Text conflict in tests/unit-
Text conflict in tests/unit-
9 conflicts encountered.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Alan Griffiths (alan-griffiths) wrote : | # |
Needs rebasing on BufferQueue
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1580
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1581
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Alan Griffiths (alan-griffiths) wrote : | # |
CI says:
[ 63%] /mir/tests/
#include "mir_test_
~~~~
315 +#endif // MIR_TEST_
We're calling it Signal in this revision.
~~~~
2549 + MOCK_METHOD1(
Why create a mock method when we don't set expectations or behavior?
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1588
http://
Executed test runs:
FAILURE: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1589
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Alan Griffiths (alan-griffiths) wrote : | # |
No opinion until prerequisite is dealt with
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Daniel van Vugt (vanvugt) wrote : | # |
Oops, "Needs fixing" is well out of date now.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Alberto Aguirre (albaguirre) wrote : | # |
~~~
1179 +void TimeoutFrameDro
1180 +{
1181 + if (pending_swaps++ == 0)
1182 + alarm->
1183 +}
1184 +
1185 +void TimeoutFrameDro
1186 +{
1187 + if (alarm->state() != mir::time:
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_
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->
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_
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1591
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Alberto Aguirre (albaguirre) wrote : | # |
Conflicts with lp:~andreas-pokorny/mir/add-timer-to-main-loop
Text conflict in include/
Text conflict in include/
Text conflict in src/server/
Text conflict in tests/mir_
Text conflict in tests/unit-
~~~
2146 + clock->
2147 + {
2148 + if (state_copy->state == Pending)
2149 + {
2150 + if (now > state_copy-
2151 + {
2152 + state_copy->state = Triggered;
2153 + state_copy-
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_
2569 +
2570 + q.client_
2571 +
2572 + EXPECT_
2573 +
2574 + policy_
2575 + EXPECT_
~~~
This can be replaced with:
---
auto handle = client_
EXPECT_
policy_
EXPECT_
---
~~~
2598 + auto first_swap = std::make_
2599 + auto second_swap = std::make_
2600 +
2601 + /* Queue up two pending swaps */
2602 + mg::Buffer* client_buf;
2603 + q.client_
2604 + {
2605 + client_buf = buffer;
2606 + first_swap-
2607 + });
2608 + q.client_
2609 +
2610 + ASSERT_
2611 + ASSERT_
2612 +
2613 + q.compositor_
2614 +
2615 + EXPECT_
2616 + EXPECT_
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...
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Daniel van Vugt (vanvugt) wrote : | # |
Text conflict in include/
Text conflict in include/
Text conflict in src/server/
Text conflict in tests/mir_
Text conflict in tests/unit-
5 conflicts encountered.
:)
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Alan Griffiths (alan-griffiths) wrote : | # |
Merge conflicts
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1594
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1596
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Daniel van Vugt (vanvugt) : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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_
to avoid the new TODOs.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Alexandros Frantzis (afrantzis) wrote : | # |
114 +class FrameDroppingPo
115 +{
Delete CopyAssign (unless we want them for some reason?).
448 +class MockTimer : public mir::time::Timer
This is a FakeTimer not a MockTimer.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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 :)
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1600
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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 SwapBlockageObs
Also based on the comments why isn't "swap_now_
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Alexandros Frantzis (afrantzis) wrote : | # |
Didn't get a chance to review properly today. Abstaining for now to unblock.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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
> SwapBlockageObs
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_
> "swap_has_
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1603
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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?
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Andreas Pokorny (andreas-pokorny) : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Alan Griffiths (alan-griffiths) wrote : | # |
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 "FrameDroppingP
> > 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
> > SwapBlockageObs
>
> 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 TimeoutFrameDro
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_
and
724 + framedrop_
725 give_buffer_
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_
Actually, BufferQueue still has:
/* Last resort, drop oldest buffer from the ready queue */
if (frame_
{
auto const buffer = pop(ready_
So we have both framedrop_policy and frame_dropping_
> > Also based on the comments why isn't "swap_now_
> > "swap_has_
>
> 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_
...
724 + framedrop_
However, the more I think about it the more I think the interaction between FrameDroppingPo
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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 BufferBundleObs
class BufferBundleObs
{
public:
virtual client_
virtual client_
}
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_
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Alberto Aguirre (albaguirre) wrote : | # |
> class BufferBundleObs
> {
> public:
> virtual client_
> virtual client_
> }
virtual client_
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Chris Halse Rogers (raof) wrote : | # |
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
> "FrameDroppingP
> 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
>> > SwapBlockageObs
>>
>> 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 TimeoutFrameDro
> 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_
>
> and
>
> 724 + framedrop_
> 725 give_buffer_
>
> 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_
> clearer.)
It is a state transition, yes, with the wrinkle that it's recursive;
you can enter_swap_
If we go this way, enter_swap_
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_
> {
> auto const buffer = pop(ready_
> give_buffer_
>
> So we have both framedrop_policy and frame_dropping_
> BufferQueue. So we're not covering all the policy around
> framedropping in TimeoutFrameDro
Hm, this is true. How about if we added virtual void
client_
FrameDroppingPolicy *would* be responsible for all the framedropping
decisions?
> …
> class SwapBuffersPolicy
> {
> public:
> virtual void handle_...
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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
> BufferBundleObs
>
> class BufferBundleObs
> {
> public:
> virtual client_
> virtual client_
> }
>
> 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 BufferBundleObs
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_
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_
> externalization of buffer release is needed.
I don't think it can call force_requests_
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Alan Griffiths (alan-griffiths) wrote : | # |
Trying to summarize my understanding from the discussions going on.
1. the class FrameDroppingPolicy is really a SwapBuffersStat
2. FrameDroppingPolicy et alia fail to place all the frame dropping algorithm(s) into the policy object (and BitsOfFrameDrop
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)?
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Daniel van Vugt (vanvugt) wrote : | # |
Text conflict in tests/unit-
1 conflicts encountered.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Chris Halse Rogers (raof) wrote : | # |
1. While “SwapBuffersSta
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_
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 FrameDroppingPo
~~~~~~~
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:/
That said, seems a fairly obvious piece of abstraction to me - although this MP suggests it's not obvious to others :).
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Alan Griffiths (alan-griffiths) wrote : | # |
> 1. While “SwapBuffersSta
> 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_
> 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_
> 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 FrameDroppingPo
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:/
> always/
>
> That said, seems a fairly obvious piece of abstraction to me - although this
> MP suggests it's not obvious to others :).
Quite.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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?
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1604
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Daniel van Vugt (vanvugt) : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.)
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Alan Griffiths (alan-griffiths) wrote : | # |
Top approving as we're not going to get a better consensus by delaying.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) : | # |
Preview Diff
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) |
Remove BufferConsuming Functor from compositor
Now that SwitchingBuffer Bundle hands out buffers at a minimum, configurable, rate, we no
longer need a fake display consuming buffers at fake vsync.