Mir

Merge lp:~vanvugt/mir/manual-vsync-api-v2 into lp:mir

Proposed by Daniel van Vugt on 2017-04-27
Status: Work in progress
Proposed branch: lp:~vanvugt/mir/manual-vsync-api-v2
Merge into: lp:mir
Diff against target: 233 lines (+103/-2)
9 files modified
examples/eglapp.c (+15/-1)
include/client/mir_toolkit/mir_window.h (+16/-0)
src/client/frame_clock.cpp (+10/-0)
src/client/frame_clock.h (+8/-0)
src/client/mir_surface.cpp (+16/-0)
src/client/mir_surface.h (+2/-0)
src/client/mir_surface_api.cpp (+5/-0)
src/client/symbols.map (+6/-1)
tests/unit-tests/client/test_frame_clock.cpp (+25/-0)
To merge this branch: bzr merge lp:~vanvugt/mir/manual-vsync-api-v2
Reviewer Review Type Date Requested Status
Mir CI Bot continuous-integration Approve on 2017-04-27
Mir development team 2017-04-27 Pending
Review via email: mp+323313@code.launchpad.net

Commit message

Introducing a trivial API function that allows clients to throttle
themselves without ever blocking inside thirdparty code:
mir_window_get_microseconds_till_vblank

The intended methodology is for a client to set swap interval zero
and then use this new function to throttle its own swaps.

The intended use cases are:
 * Toolkits/apps that have an existing event loop (particularly
   single threaded ones) and don't want anything that complicates
   that architecture, but do want synchronized/throttled rendering.
 * Existing single-threaded users (Xmir only?) of the asynchronous
   mir_buffer_stream_swap_buffers(), can continue to have vsync and
   asynchronous buffer returns, and single-threaded simplicity,
   without putting a queuing/threading burden on libmirclient to
   throttle buffer returns at all. The existing client event loop can
   be used instead.

To post a comment you must log in.
Mir CI Bot (mir-ci-bot) wrote :

PASSED: Continuous integration, rev:4167
https://mir-jenkins.ubuntu.com/job/mir-ci/3380/
Executed test runs:
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-mir/4574
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/4700
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/4689
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=zesty/4689
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/4606
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/4606/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/4606
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/4606/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/4606
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/4606/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=zesty/4606
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=zesty/4606/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/4606
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/4606/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/3380/rebuild

review: Approve (continuous-integration)

Unmerged revisions

4167. By Daniel van Vugt on 2017-04-27

Rename the new unit test to match what it actually does

4166. By Daniel van Vugt on 2017-04-27

Use the new feature in examples and fix up C symbol exports.

4165. By Daniel van Vugt on 2017-04-27

And use the new function

4164. By Daniel van Vugt on 2017-04-27

Implement a new FrameClock method for the new API, which eases unit
testing and is crucially also right... so the test now passes.

4163. By Daniel van Vugt on 2017-04-27

Reintroduce and rewrite the unit test

4162. By Daniel van Vugt on 2017-04-27

Revert back to a simpler algorithm

4161. By Daniel van Vugt on 2017-04-27

A little progress

4160. By Daniel van Vugt on 2017-04-26

A new approach. Needs testing...

4159. By Daniel van Vugt on 2017-04-26

Drop the new unit test

4158. By Daniel van Vugt on 2017-04-26

Add a unit test for the expected behaviour from the new MirWindow
function... presently failing.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'examples/eglapp.c'
2--- examples/eglapp.c 2017-04-05 03:11:28 +0000
3+++ examples/eglapp.c 2017-04-27 08:36:53 +0000
4@@ -22,6 +22,7 @@
5 #include <stdlib.h>
6 #include <signal.h>
7 #include <time.h>
8+#include <errno.h>
9 #include <string.h>
10 #include <EGL/egl.h>
11 #include <GLES2/gl2.h>
12@@ -38,6 +39,7 @@
13 static EGLSurface eglsurface;
14 static volatile sig_atomic_t running = 0;
15 static double refresh_rate = 0.0;
16+static mir_eglapp_bool alt_vsync = 0;
17
18 #pragma GCC diagnostic push
19 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
20@@ -84,6 +86,15 @@
21 return running;
22 }
23
24+/* Apparently usleep is deprecated so we write own own... */
25+static void micro_sleep(unsigned long usec)
26+{
27+ struct timespec ts = {0, usec * 1000};
28+ while (nanosleep(&ts, &ts) && errno == EINTR)
29+ {
30+ }
31+}
32+
33 void mir_eglapp_swap_buffers(void)
34 {
35 EGLint width, height;
36@@ -92,6 +103,8 @@
37 return;
38
39 eglSwapBuffers(egldisplay, eglsurface);
40+ if (alt_vsync)
41+ micro_sleep(mir_window_get_microseconds_till_vblank(window));
42
43 /*
44 * Querying the surface (actually the current buffer) dimensions here is
45@@ -347,6 +360,7 @@
46 {"-h", "!", &help, "Show this help text"},
47 {"-m <socket>", "=", &mir_socket, "Mir server socket"},
48 {"-n", "!", &no_vsync, "Don't sync to vblank"},
49+ {"-v", "!", &alt_vsync, "Sync to vblank using the alternate method (manual timing)"},
50 {"-o <id>", "%u", &output_id, "Force placement on output monitor ID"},
51 {"-q", "!", &quiet, "Quiet mode (no messages output)"},
52 {"-s <width>x<height>", "=", &dims, "Force window size"},
53@@ -372,7 +386,7 @@
54 return 0;
55 }
56
57- if (no_vsync)
58+ if (no_vsync || alt_vsync)
59 swapinterval = 0;
60
61 if (dims)
62
63=== modified file 'include/client/mir_toolkit/mir_window.h'
64--- include/client/mir_toolkit/mir_window.h 2017-04-13 07:00:27 +0000
65+++ include/client/mir_toolkit/mir_window.h 2017-04-27 08:36:53 +0000
66@@ -859,6 +859,22 @@
67 MirPersistentId* mir_window_request_persistent_id_sync(MirWindow* window)
68 __attribute__((deprecated("Use mir_window_request_window_id_sync")));
69 MirWindowId* mir_window_request_window_id_sync(MirWindow* window);
70+
71+/**
72+ * Query the approximate time interval in microseconds until the next vblank for
73+ * a given window (actually the next vblank for the monitor deemed most
74+ * relevant to the window's current placement). A relative time is returned so
75+ * as to avoid clock/API portability issues. And microsecond precision is used
76+ * because it's the largest unit of time that still provides ample precision
77+ * to be a few orders or magnitude more precise than human perception, or than
78+ * the refresh inverval of display hardware).
79+ *
80+ * \param [in] w The MirWindow
81+ * \return Time in microseconds to the next vblank for the given
82+ * window (may be as low as zero).
83+ */
84+unsigned long mir_window_get_microseconds_till_vblank(MirWindow const* w);
85+
86 #ifdef __cplusplus
87 }
88 /**@}*/
89
90=== modified file 'src/client/frame_clock.cpp'
91--- src/client/frame_clock.cpp 2016-11-29 08:00:45 +0000
92+++ src/client/frame_clock.cpp 2017-04-27 08:36:53 +0000
93@@ -154,3 +154,13 @@
94
95 return target;
96 }
97+
98+PosixTimestamp FrameClock::next_frame_after_now() const
99+{
100+ auto result = next_frame_after({});
101+ auto const now = get_current_time(result.clock_id);
102+ if (result <= now)
103+ result = next_frame_after(now);
104+ return result;
105+}
106+
107
108=== modified file 'src/client/frame_clock.h'
109--- src/client/frame_clock.h 2016-12-15 08:15:54 +0000
110+++ src/client/frame_clock.h 2017-04-27 08:36:53 +0000
111@@ -57,6 +57,14 @@
112 */
113 time::PosixTimestamp next_frame_after(time::PosixTimestamp prev) const;
114
115+ /**
116+ * Return the next frame that's in the future. This is a simplified
117+ * stateless version of next_frame_after() with the caveat that it lacks
118+ * the ability to detect and catch up missed frames (because there is no
119+ * state information indicating which frame deadline the caller last met).
120+ */
121+ time::PosixTimestamp next_frame_after_now() const;
122+
123 private:
124 time::PosixTimestamp fallback_resync_callback() const;
125
126
127=== modified file 'src/client/mir_surface.cpp'
128--- src/client/mir_surface.cpp 2017-04-13 07:00:27 +0000
129+++ src/client/mir_surface.cpp 2017-04-27 08:36:53 +0000
130@@ -238,6 +238,22 @@
131 */
132 }
133
134+std::chrono::microseconds MirSurface::microseconds_till_vblank() const
135+{
136+ std::chrono::microseconds ret(0);
137+
138+ auto const target = frame_clock->next_frame_after_now();
139+ auto const now = mir::time::PosixTimestamp::now(target.clock_id);
140+
141+ if (target > now)
142+ {
143+ auto const delta = target - now;
144+ ret = std::chrono::duration_cast<std::chrono::microseconds>(delta);
145+ }
146+
147+ return ret;
148+}
149+
150 MirWindowParameters MirSurface::get_parameters() const
151 {
152 std::lock_guard<decltype(mutex)> lock(mutex);
153
154=== modified file 'src/client/mir_surface.h'
155--- src/client/mir_surface.h 2017-04-13 07:00:27 +0000
156+++ src/client/mir_surface.h 2017-04-27 08:36:53 +0000
157@@ -224,6 +224,8 @@
158 std::shared_ptr<mir::client::FrameClock> get_frame_clock() const;
159 std::shared_ptr<mir::input::receiver::XKBMapper> get_keymapper() const;
160
161+ std::chrono::microseconds microseconds_till_vblank() const;
162+
163 private:
164 std::mutex mutable mutex; // Protects all members of *this
165
166
167=== modified file 'src/client/mir_surface_api.cpp'
168--- src/client/mir_surface_api.cpp 2017-04-13 07:00:27 +0000
169+++ src/client/mir_surface_api.cpp 2017-04-27 08:36:53 +0000
170@@ -1385,3 +1385,8 @@
171 wh->wait_for_all();
172 return result;
173 }
174+
175+unsigned long mir_window_get_microseconds_till_vblank(MirWindow const* w)
176+{
177+ return w->microseconds_till_vblank().count();
178+}
179
180=== modified file 'src/client/symbols.map'
181--- src/client/symbols.map 2017-04-13 07:00:27 +0000
182+++ src/client/symbols.map 2017-04-27 08:36:53 +0000
183@@ -579,7 +579,7 @@
184 };
185 } MIR_CLIENT_DETAIL_0.26.1;
186
187-MIR_CLIENT_0.27 { # New functions in Mir 0.27 or 1.0
188+MIR_CLIENT_0.27 { # New functions in Mir 0.27
189 global:
190 mir_connection_apply_session_input_config;
191 mir_connection_set_base_input_config;
192@@ -611,3 +611,8 @@
193 mir_touchscreen_config_set_mapping_mode;
194 mir_touchscreen_config_set_output_id;
195 } MIR_CLIENT_0.26.1;
196+
197+MIR_CLIENT_0.28 { # New functions in Mir 0.28 (or 1.0)
198+ global:
199+ mir_window_get_microseconds_till_vblank;
200+} MIR_CLIENT_0.27;
201
202=== modified file 'tests/unit-tests/client/test_frame_clock.cpp'
203--- tests/unit-tests/client/test_frame_clock.cpp 2017-03-13 08:12:52 +0000
204+++ tests/unit-tests/client/test_frame_clock.cpp 2017-04-27 08:36:53 +0000
205@@ -426,3 +426,28 @@
206 EXPECT_EQ(one_frame, in2 - in1);
207 EXPECT_EQ(one_frame, out2 - out1);
208 }
209+
210+TEST_F(FrameClockTest, next_frame_after_now_is_exactly_that)
211+{
212+ auto const& now = fake_time[CLOCK_MONOTONIC];
213+ FrameClock clock(with_fake_time);
214+ clock.set_period(one_frame);
215+
216+ auto a = now;
217+ auto b = clock.next_frame_after_now();
218+ EXPECT_GE(b, a);
219+ EXPECT_LE(b-now, one_frame);
220+
221+ for (int same = 0; same < 3; ++same)
222+ EXPECT_EQ(b, clock.next_frame_after_now()) << "i=" << same;
223+
224+ fake_sleep_until(b);
225+
226+ auto c = clock.next_frame_after_now();
227+ EXPECT_LT(b, c);
228+ EXPECT_EQ(b+one_frame, c);
229+ EXPECT_LE(c-now, one_frame);
230+
231+ for (int same = 0; same < 3; ++same)
232+ EXPECT_EQ(c, clock.next_frame_after_now()) << "i=" << same;
233+}

Subscribers

People subscribed via source and target branches