Mir

Merge lp:~vanvugt/mir/unfreeze-2 into lp:mir

Proposed by Daniel van Vugt
Status: Work in progress
Proposed branch: lp:~vanvugt/mir/unfreeze-2
Merge into: lp:mir
Diff against target: 256 lines (+150/-46)
3 files modified
src/server/compositor/buffer_queue.cpp (+66/-45)
src/server/compositor/buffer_queue.h (+2/-0)
tests/unit-tests/compositor/test_buffer_queue.cpp (+82/-1)
To merge this branch: bzr merge lp:~vanvugt/mir/unfreeze-2
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Needs Fixing
Mir development team Pending
Review via email: mp+243012@code.launchpad.net

Commit message

Ensure frame dropping never drops the newest frame. Dropping the
newest frame causes stuttering at best (LP: #1379685) or in the case
of an event-driven app would make it appear to freeze indefinitely
(LP: #1396006).

Description of the change

Unfortunately, despite the many options described in the code, occasionally not framedropping is the only option that actually works without any concession to visual quality.

In future I'd like to support a higher max nbuffers that will make the warning message disappear. This is required to properly support evil combinations of bypass/overlay/multimonitor/framedropping. However before that happens I want to land other work that ensures longer buffer queues don't also increase visible lag (work in progress: lp:~vanvugt/mir/ddouble).

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

failure logged as lp:1396978

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

Hmm, this compromise might not be necessary. I think I know how to work around the (other) Android issue that was blocking overallocation from working (and other work too).

lp:~vanvugt/mir/unfreeze-2 updated
2118. By Daniel van Vugt

Merge latest trunk

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

Unmerged revisions

2118. By Daniel van Vugt

Merge latest trunk

2117. By Daniel van Vugt

Update comments

2116. By Daniel van Vugt

And more comments

2115. By Daniel van Vugt

Update comments more

2114. By Daniel van Vugt

Better comments

2113. By Daniel van Vugt

Log a warning when frame dropping becomes unsafe

2112. By Daniel van Vugt

Fix test case that expected framedropping to work properly with only
two buffers.

2111. By Daniel van Vugt

Prototype "only drop when it's safe to do so"

2110. By Daniel van Vugt

Start again. And again start with the (failing) test cases for
frame dropping.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/server/compositor/buffer_queue.cpp'
2--- src/server/compositor/buffer_queue.cpp 2014-11-24 02:16:00 +0000
3+++ src/server/compositor/buffer_queue.cpp 2014-11-28 02:40:47 +0000
4@@ -19,6 +19,7 @@
5
6 #include "mir/graphics/graphic_buffer_allocator.h"
7 #include "mir/graphics/buffer_id.h"
8+#include "mir/logging/logger.h"
9
10 #include <boost/throw_exception.hpp>
11 #include <stdexcept>
12@@ -135,38 +136,8 @@
13 framedrop_policy = policy_provider.create_policy([this]
14 {
15 std::unique_lock<decltype(guard)> lock{guard};
16-
17- if (pending_client_notifications.empty())
18- {
19- /*
20- * This framedrop handler may be in the process of being dispatched
21- * when we try to cancel it by calling swap_unblocked() when we
22- * get a buffer to give back to the client. In this case we cannot
23- * cancel and this function may be called without any pending client
24- * notifications. This is a benign race that we can deal with by
25- * just ignoring the framedrop request.
26- */
27- return;
28- }
29-
30- if (ready_to_composite_queue.empty())
31- {
32- /*
33- * NOTE: This can only happen under two circumstances:
34- * 1) Client is single buffered. Don't do that, it's a bad idea.
35- * 2) Client already has a buffer, and is asking for a new one
36- * without submitting the old one.
37- *
38- * This shouldn't be exposed to the client as swap_buffers
39- * blocking, as the client already has something to render to.
40- *
41- * Our current implementation will never hit this case. If we
42- * later want clients to be able to own multiple buffers simultaneously
43- * then we might want to add an entry to the CompositorReport here.
44- */
45- return;
46- }
47- drop_frame(std::move(lock));
48+ if (!pending_client_notifications.empty())
49+ drop_frame(std::move(lock));
50 });
51 }
52
53@@ -198,7 +169,7 @@
54 }
55
56 /* Last resort, drop oldest buffer from the ready queue */
57- if (frame_dropping_enabled && !ready_to_composite_queue.empty())
58+ if (frame_dropping_enabled)
59 {
60 drop_frame(std::move(lock));
61 return;
62@@ -474,18 +445,68 @@
63
64 void mc::BufferQueue::drop_frame(std::unique_lock<std::mutex> lock)
65 {
66- auto buffer_to_give = pop(ready_to_composite_queue);
67- /* Advance compositor buffer so it always points to the most recent
68- * client content
69- */
70- if (!contains(current_compositor_buffer, buffers_sent_to_compositor))
71- {
72- current_buffer_users.clear();
73- void const* const impossible_user_id = this;
74- current_buffer_users.push_back(impossible_user_id);
75- std::swap(buffer_to_give, current_compositor_buffer);
76- }
77- give_buffer_to_client(buffer_to_give, std::move(lock));
78+ mg::Buffer* drop = nullptr;
79+
80+ if (!free_buffers.empty())
81+ { // We expect this to usually be empty, but always check free list first
82+ drop = free_buffers.back();
83+ free_buffers.pop_back();
84+ }
85+ else if (!ready_to_composite_queue.empty() &&
86+ !contains(current_compositor_buffer, buffers_sent_to_compositor))
87+ {
88+ // Remember current_compositor_buffer is always silently the front
89+ // of the ready queue.
90+ drop = current_compositor_buffer;
91+ current_compositor_buffer = pop(ready_to_composite_queue);
92+ current_buffer_users.clear();
93+ void const* const impossible_user_id = this;
94+ current_buffer_users.push_back(impossible_user_id);
95+ }
96+ else if (ready_to_composite_queue.size() > 1)
97+ {
98+ // You can take from the ready queue, so long as it's never the
99+ // tail of the queue (the newest frame).
100+ drop = pop(ready_to_composite_queue);
101+ }
102+
103+ if (drop)
104+ {
105+ give_buffer_to_client(drop, std::move(lock));
106+ }
107+ else
108+ {
109+ /*
110+ * Insufficient nbuffers for frame dropping? This means you're either
111+ * trying to use frame dropping with bypass/multimonitor or have
112+ * supplied nbuffers < 3. So consider the options...
113+ * 1. Crash. No, that's really unhelpful.
114+ * 2. Drop the visible frame. Probably not; it looks pretty awful.
115+ * Not just tearing but you'll see very ugly polygon rendering
116+ * artefacts.
117+ * 3. Drop the newest ready frame. Absolutely not; that will cause
118+ * indefinite freezes or at least stuttering.
119+ * 4. Overallocate; more buffers. Maybe in future but we don't yet
120+ * have a safe and reliable implementation. It seems to
121+ * kill Android rather quickly using four buffers (try glmark2).
122+ * 5. Just give a warning and carry on at regular frame rate
123+ * as if framedropping was disabled. Yes, see below.
124+ */
125+ std::call_once(warn_dropping_failed, [this]()
126+ {
127+ auto n = std::to_string(nbuffers);
128+ auto consuming = std::to_string(buffers_sent_to_compositor.size());
129+ auto ready = std::to_string(ready_to_composite_queue.size());
130+ auto producing = std::to_string(buffers_owned_by_client.size());
131+ mir::logging::log(mir::logging::Severity::warning,
132+ "Insufficient nbuffers("+n+") to support "
133+ "frame dropping ("+
134+ consuming+" compositing, "+
135+ ready+" ready, "+
136+ producing+" with client)",
137+ "BufferQueue");
138+ });
139+ }
140 }
141
142 void mc::BufferQueue::drop_old_buffers()
143
144=== modified file 'src/server/compositor/buffer_queue.h'
145--- src/server/compositor/buffer_queue.h 2014-11-24 02:16:00 +0000
146+++ src/server/compositor/buffer_queue.h 2014-11-28 02:40:47 +0000
147@@ -98,6 +98,8 @@
148 // Ensure framedrop_policy gets destroyed first so the callback installed
149 // does not access dead objects.
150 std::unique_ptr<FrameDroppingPolicy> framedrop_policy;
151+
152+ std::once_flag warn_dropping_failed;
153 };
154
155 }
156
157=== modified file 'tests/unit-tests/compositor/test_buffer_queue.cpp'
158--- tests/unit-tests/compositor/test_buffer_queue.cpp 2014-11-24 02:16:00 +0000
159+++ tests/unit-tests/compositor/test_buffer_queue.cpp 2014-11-28 02:40:47 +0000
160@@ -1050,6 +1050,87 @@
161 }
162 }
163
164+TEST_F(BufferQueueTest, framedropping_policy_never_drops_newest_frame)
165+{ // Regression test for LP: #1396006
166+ for (int nbuffers = 2; nbuffers <= max_nbuffers_to_test; ++nbuffers)
167+ {
168+ mtd::MockFrameDroppingPolicyFactory policy_factory;
169+ mc::BufferQueue q(nbuffers,
170+ allocator,
171+ basic_properties,
172+ policy_factory);
173+
174+ auto first = client_acquire_sync(q);
175+ q.client_release(first);
176+
177+ // Start rendering one (don't finish)
178+ auto d = q.compositor_acquire(nullptr);
179+ ASSERT_EQ(first, d.get());
180+
181+ auto second = client_acquire_sync(q);
182+ q.client_release(second);
183+
184+ // Client waits for a new frame
185+ auto end = client_acquire_async(q);
186+
187+ // Surface goes offscreen or occluded; trigger a timeout
188+ policy_factory.trigger_policies();
189+
190+ // If the queue is still willing to drop under these difficult
191+ // circumstances (and we don't mind if it doesn't), then ensure
192+ // it's never the newest frame that's been discarded.
193+ // That could be catastrophic as you never know if a client ever
194+ // will produce another frame.
195+ if (end->has_acquired_buffer())
196+ ASSERT_NE(second, end->buffer());
197+
198+ q.compositor_release(d);
199+ }
200+}
201+
202+TEST_F(BufferQueueTest, framedropping_surface_never_drops_newest_frame)
203+{ // Second regression test for LP: #1396006, LP: #1379685
204+ for (int nbuffers = 2; nbuffers <= max_nbuffers_to_test; ++nbuffers)
205+ {
206+ mc::BufferQueue q(nbuffers,
207+ allocator,
208+ basic_properties,
209+ policy_factory);
210+
211+ q.allow_framedropping(true);
212+
213+ // Fill 'er up
214+ std::vector<mg::Buffer*> order;
215+ for (int f = 0; f < nbuffers; ++f)
216+ {
217+ auto b = client_acquire_sync(q);
218+ order.push_back(b);
219+ q.client_release(b);
220+ }
221+
222+ // Composite all but one
223+ std::vector<std::shared_ptr<mg::Buffer>> compositing;
224+ for (int n = 0; n < nbuffers-1; ++n)
225+ {
226+ auto c = q.compositor_acquire(nullptr);
227+ compositing.push_back(c);
228+ ASSERT_EQ(order[n], c.get());
229+ }
230+
231+ // Ensure it's not the newest frame that gets dropped to satisfy the
232+ // client.
233+ auto end = client_acquire_async(q);
234+
235+ // The queue could solve this problem a few ways. It might choose to
236+ // defer framedropping till it's safe, or even allocate additional
237+ // buffers. We don't care which, just verify it's not losing the
238+ // latest frame. Because the screen could be indefinitely out of date
239+ // if that happens...
240+ ASSERT_TRUE(!end->has_acquired_buffer() ||
241+ end->buffer() != order.back());
242+ }
243+}
244+
245 TEST_F(BufferQueueTest, uncomposited_client_swaps_when_policy_triggered)
246 {
247 for (int nbuffers = 2;
248@@ -1079,7 +1160,7 @@
249
250 TEST_F(BufferQueueTest, partially_composited_client_swaps_when_policy_triggered)
251 {
252- for (int nbuffers = 2;
253+ for (int nbuffers = 3; // framedropping is only truly safe with 3
254 nbuffers <= max_nbuffers_to_test;
255 nbuffers++)
256 {

Subscribers

People subscribed via source and target branches