Merge lp:~vanvugt/mir/target into lp:mir
- target
- Merge into development-branch
Status: | Merged |
---|---|
Approved by: | Daniel van Vugt |
Approved revision: | no longer in the source branch. |
Merged at revision: | 2464 |
Proposed branch: | lp:~vanvugt/mir/target |
Merge into: | lp:mir |
Prerequisite: | lp:~mir-team/mir/fix-1438160 |
Diff against target: |
363 lines (+348/-0) 2 files modified
examples/CMakeLists.txt (+6/-0) examples/target.c (+342/-0) |
To merge this branch: | bzr merge lp:~vanvugt/mir/target |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot (community) | continuous-integration | Approve | |
Robert Carr (community) | Approve | ||
Alan Griffiths | Approve | ||
Review via email:
|
Commit message
Introducing mir_demo_
This provides a visual scale by which to grade input latency, particularly
when watching the mouse pointer. Such a small scale is required if you are
to reliably see any improvement (or regression) of only a few milliseconds
latency.
mir_demo_
Mir's default resampling, so achieves about half a frame lower latency on
average. Even with the default swap interval of one. Although forcing swap
interval to zero (-n option) reduces visible latency even more. Doing its
own sampling also allows mir_demo_
native frame rate, rather than being limited by the Mir input resampling
rate.
Tip: If you find it hard to see the cursor against the target then using
the zoom feature (Super+mousewheel) of mir_proving_server helps. Because
that will visually amplify any offset.
Description of the change
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:2485
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
SUCCESS: 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) wrote : | # |
The failure seems to be unrelated...
28: C++ exception with description "stop_server() failed to stop server" thrown in TearDown().
Build timed out (after 120 minutes). Marking the build as failed.
Build was aborted
Build was marked for publishing on https:/
Finished: FAILURE
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:2487
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Daniel van Vugt (vanvugt) wrote : | # |
Definitely not related to this proposal...
10: [ RUN ] ClientLibrary.
10: [1427959486.308220] Server: Starting
10: [1427959486.308335] Platform Loader: Selected driver: dummy (version 0.13.0)
10: [1427959486.309135] mirserver: Using software cursor
10: [1427959486.311136] DisplayServer: Mir version 0.13.0
10: /mir/tests/
10: Value of: native_
10: Expected: is equal to 800
10: Actual: 1 (of type int)
10: /mir/tests/
10: Value of: native_
10: Expected: is equal to 600
10: Actual: 1 (of type int)
10: [1427959486.318845] Server: Stopping
10: [ FAILED ] ClientLibrary.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Robert Carr (robertcarr) wrote : | # |
I see no problem with this landing but I'm not sure about the analysis/goal.
>> mir_demo_
It seems mir_demo_
>> in place of
>> Mir's default resampling, so achieves about half a frame lower latency on
>> average.
From my understanding this would be the intentional re sampling latency...we can remove it at the cost of varying the sample rate per frame (which in the case of uniform speed scrolls results in jerkiness).
There are some useful visual depictions of what is going on here at:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Daniel van Vugt (vanvugt) wrote : | # |
You're right, this is just "sampling" input in the most efficient way. Not "re-sampling". I'll adjust the wording...
Indeed, not all apps can work this way. Those that have not been written to handle input efficiently will need the built-in input resampling. Also those that benefit from touch motion prediction will want that. But in theory any app/toolkit could be given optimal input handling by:
1. Only wake up on input events.
2. When woken, schedule a frame if none scheduled already.
3. At the start of each frame, /get/ the latest input state (i.e. current cursor position).
4. Use the latest input state to render your frame.
This is all stuff I remember being taught in 1998. It's kind of embarrassing we forget it so often.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:2488
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
SUCCESS: 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 : | # |
Looks like the 60's are calling but OK
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Robert Carr (robertcarr) wrote : | # |
>> But in theory any app/toolkit could be given optimal input handling by
>> 1. Only wake up on input events.
>> 2. When woken, schedule a frame if none scheduled already.
>> 3. At the start of each frame, /get/ the latest input state (i.e. current cursor position).
>> 4. Use the latest input state to render your frame.
This seems like it would be true but I don't think it is. What this example fails to take in to account is that the "latest input state" is updated at a varying rate from the frame.
This article:
http://
Has a good visual depiction of unresampled input with a sample rate mismatch (the "current code" version of firefox logo).
Example is ok though
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Daniel van Vugt (vanvugt) wrote : | # |
Absolutely in touch scrolling uniformity needs to be considered. That's why we still have a fixed input resampling interval and it still looks better than our attempts to adjust it dynamically did.
But this demo is mainly for tracking the cursor as quickly as possible. Not animation.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) : | # |
Preview Diff
1 | === modified file 'examples/CMakeLists.txt' |
2 | --- examples/CMakeLists.txt 2015-03-31 12:48:35 +0000 |
3 | +++ examples/CMakeLists.txt 2015-04-07 07:01:52 +0000 |
4 | @@ -50,6 +50,12 @@ |
5 | target_link_libraries(mir_demo_client_egltriangle |
6 | eglapp |
7 | ) |
8 | +mir_add_wrapped_executable(mir_demo_client_target |
9 | + target.c |
10 | +) |
11 | +target_link_libraries(mir_demo_client_target |
12 | + eglapp |
13 | +) |
14 | mir_add_wrapped_executable(mir_demo_client_eglcounter |
15 | eglcounter.cpp |
16 | ) |
17 | |
18 | === added file 'examples/target.c' |
19 | --- examples/target.c 1970-01-01 00:00:00 +0000 |
20 | +++ examples/target.c 2015-04-07 07:01:52 +0000 |
21 | @@ -0,0 +1,342 @@ |
22 | +/* |
23 | + * Copyright © 2015 Canonical Ltd. |
24 | + * |
25 | + * This program is free software: you can redistribute it and/or modify |
26 | + * it under the terms of the GNU General Public License version 3 as |
27 | + * published by the Free Software Foundation. |
28 | + * |
29 | + * This program is distributed in the hope that it will be useful, |
30 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
31 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
32 | + * GNU General Public License for more details. |
33 | + * |
34 | + * You should have received a copy of the GNU General Public License |
35 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
36 | + * |
37 | + * Author: Daniel van Vugt <daniel.van.vugt@canonical.com> |
38 | + */ |
39 | + |
40 | +#define _POSIX_C_SOURCE 200112L // for setenv() from stdlib.h |
41 | +#include "eglapp.h" |
42 | +#include <assert.h> |
43 | +#include <stdio.h> |
44 | +#include <math.h> |
45 | +#include <GLES2/gl2.h> |
46 | +#include <mir_toolkit/mir_surface.h> |
47 | +#include <pthread.h> |
48 | +#include <stdlib.h> |
49 | +#include <signal.h> |
50 | + |
51 | +enum |
52 | +{ |
53 | + max_touches = 10 |
54 | +}; |
55 | + |
56 | +typedef struct |
57 | +{ |
58 | + float x, y; |
59 | +} Vec2; |
60 | + |
61 | +typedef struct |
62 | +{ |
63 | + int points; |
64 | + Vec2 point[max_touches]; |
65 | +} TouchState; |
66 | + |
67 | +typedef struct |
68 | +{ |
69 | + pthread_mutex_t mutex; |
70 | + pthread_cond_t change; |
71 | + bool changed; |
72 | + |
73 | + bool resized; |
74 | + TouchState touch; |
75 | +} State; |
76 | + |
77 | +static GLuint load_shader(const char *src, GLenum type) |
78 | +{ |
79 | + GLuint shader = glCreateShader(type); |
80 | + if (shader) |
81 | + { |
82 | + GLint compiled; |
83 | + glShaderSource(shader, 1, &src, NULL); |
84 | + glCompileShader(shader); |
85 | + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); |
86 | + if (!compiled) |
87 | + { |
88 | + GLchar log[1024]; |
89 | + glGetShaderInfoLog(shader, sizeof log - 1, NULL, log); |
90 | + log[sizeof log - 1] = '\0'; |
91 | + printf("load_shader compile failed: %s\n", log); |
92 | + glDeleteShader(shader); |
93 | + shader = 0; |
94 | + } |
95 | + } |
96 | + return shader; |
97 | +} |
98 | + |
99 | +GLuint generate_target_texture() |
100 | +{ |
101 | + const int width = 512, height = width; |
102 | + typedef struct { GLubyte r, b, g, a; } Texel; |
103 | + Texel image[height][width]; |
104 | + // Note the 0.5f to convert from pixel corner (GL) to middle (image) |
105 | + const float centrex = width/2 - 0.5f, centrey = height/2 - 0.5f; |
106 | + const Texel blank = {0, 0, 0, 0}; |
107 | + const int radius = centrex - 1; |
108 | + const Texel ring[] = |
109 | + { |
110 | + { 0, 0, 0, 255}, |
111 | + { 0, 0, 255, 255}, |
112 | + { 0, 255, 0, 255}, |
113 | + {255, 255, 0, 255}, |
114 | + {255, 128, 0, 255}, |
115 | + {255, 0, 0, 255}, |
116 | + }; |
117 | + const int rings = sizeof(ring) / sizeof(ring[0]); |
118 | + |
119 | + for (int y = 0; y < height; ++y) |
120 | + { |
121 | + for (int x = 0; x < width; ++x) |
122 | + { |
123 | + float dx = x - centrex, dy = y - centrey; |
124 | + int layer = rings * sqrtf(dx * dx + dy * dy) / radius; |
125 | + image[y][x] = layer < rings ? ring[layer] : blank; |
126 | + } |
127 | + } |
128 | + |
129 | + GLuint tex; |
130 | + glGenTextures(1, &tex); |
131 | + glBindTexture(GL_TEXTURE_2D, tex); |
132 | + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); |
133 | + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); |
134 | + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, |
135 | + GL_LINEAR_MIPMAP_LINEAR); |
136 | + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
137 | + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, |
138 | + GL_UNSIGNED_BYTE, image); |
139 | + glGenerateMipmap(GL_TEXTURE_2D); |
140 | + |
141 | + return tex; |
142 | +} |
143 | + |
144 | +static void get_all_touch_points(const MirInputEvent *ievent, TouchState *touch) |
145 | +{ |
146 | + if (mir_input_event_get_type(ievent) == mir_input_event_type_pointer) |
147 | + { |
148 | + touch->points = 0; |
149 | + const MirPointerEvent *pevent = |
150 | + mir_input_event_get_pointer_event(ievent); |
151 | + if (mir_pointer_event_action(pevent) != mir_pointer_action_leave) |
152 | + { |
153 | + touch->points = 1; |
154 | + touch->point[0] = (Vec2) |
155 | + { |
156 | + mir_pointer_event_axis_value(pevent, mir_pointer_axis_x), |
157 | + mir_pointer_event_axis_value(pevent, mir_pointer_axis_y) |
158 | + }; |
159 | + } |
160 | + } |
161 | + else if (mir_input_event_get_type(ievent) == mir_input_event_type_touch) |
162 | + { |
163 | + const MirTouchEvent *tevent = mir_input_event_get_touch_event(ievent); |
164 | + int n = mir_touch_event_point_count(tevent); |
165 | + if (n > max_touches) |
166 | + n = max_touches; |
167 | + bool all_up = true; |
168 | + for (int p = 0; p < n; ++p) |
169 | + { |
170 | + if (mir_touch_event_action(tevent, p) != mir_touch_action_up) |
171 | + all_up = false; |
172 | + touch->point[p] = (Vec2) |
173 | + { |
174 | + mir_touch_event_axis_value(tevent, p, mir_touch_axis_x), |
175 | + mir_touch_event_axis_value(tevent, p, mir_touch_axis_y) |
176 | + }; |
177 | + } |
178 | + touch->points = all_up ? 0 : n; |
179 | + } |
180 | +} |
181 | + |
182 | +static void on_event(MirSurface *surface, const MirEvent *event, void *context) |
183 | +{ |
184 | + (void)surface; |
185 | + State *state = (State*)context; |
186 | + |
187 | + // FIXME: We presently need to know that events come in on a different |
188 | + // thread to main (LP: #1194384). When that's resolved, simple |
189 | + // single-threaded apps like this won't need pthread. |
190 | + pthread_mutex_lock(&state->mutex); |
191 | + |
192 | + switch (mir_event_get_type(event)) |
193 | + { |
194 | + case mir_event_type_input: |
195 | + get_all_touch_points(mir_event_get_input_event(event), &state->touch); |
196 | + break; |
197 | + case mir_event_type_resize: |
198 | + state->resized = true; |
199 | + break; |
200 | + case mir_event_type_close_surface: |
201 | + // TODO: eglapp.h needs a quit() function or different behaviour of |
202 | + // mir_eglapp_shutdown(). |
203 | + raise(SIGTERM); // handled by eglapp |
204 | + break; |
205 | + default: |
206 | + break; |
207 | + } |
208 | + |
209 | + state->changed = true; |
210 | + pthread_cond_signal(&state->change); |
211 | + pthread_mutex_unlock(&state->mutex); |
212 | +} |
213 | + |
214 | +int main(int argc, char *argv[]) |
215 | +{ |
216 | + const char vshadersrc[] = |
217 | + "attribute vec2 position;\n" |
218 | + "attribute vec2 texcoord;\n" |
219 | + "uniform float scale;\n" |
220 | + "uniform vec2 translate;\n" |
221 | + "uniform mat4 projection;\n" |
222 | + "varying vec2 v_texcoord;\n" |
223 | + "\n" |
224 | + "void main()\n" |
225 | + "{\n" |
226 | + " gl_Position = projection *\n" |
227 | + " vec4(position * scale + translate, 0.0, 1.0);\n" |
228 | + " v_texcoord = texcoord;\n" |
229 | + "}\n"; |
230 | + |
231 | + const char fshadersrc[] = |
232 | + "precision mediump float;\n" |
233 | + "varying vec2 v_texcoord;\n" |
234 | + "uniform sampler2D texture;\n" |
235 | + "\n" |
236 | + "void main()\n" |
237 | + "{\n" |
238 | + " gl_FragColor = texture2D(texture, v_texcoord);\n" |
239 | + "}\n"; |
240 | + |
241 | + // Disable Mir's input resampling. We do our own here, in a way that |
242 | + // has even lower latency than Mir's default algorithm. |
243 | + // TODO: Make a proper client API function for this: |
244 | + setenv("MIR_CLIENT_INPUT_RATE", "0", 0); |
245 | + |
246 | + static unsigned int width = 0, height = 0; |
247 | + if (!mir_eglapp_init(argc, argv, &width, &height)) |
248 | + return 1; |
249 | + |
250 | + GLuint vshader = load_shader(vshadersrc, GL_VERTEX_SHADER); |
251 | + assert(vshader); |
252 | + GLuint fshader = load_shader(fshadersrc, GL_FRAGMENT_SHADER); |
253 | + assert(fshader); |
254 | + GLuint prog = glCreateProgram(); |
255 | + assert(prog); |
256 | + glAttachShader(prog, vshader); |
257 | + glAttachShader(prog, fshader); |
258 | + glLinkProgram(prog); |
259 | + |
260 | + GLint linked; |
261 | + glGetProgramiv(prog, GL_LINK_STATUS, &linked); |
262 | + if (!linked) |
263 | + { |
264 | + GLchar log[1024]; |
265 | + glGetProgramInfoLog(prog, sizeof log - 1, NULL, log); |
266 | + log[sizeof log - 1] = '\0'; |
267 | + printf("Link failed: %s\n", log); |
268 | + return 2; |
269 | + } |
270 | + |
271 | + glUseProgram(prog); |
272 | + |
273 | + const GLfloat square[] = |
274 | + { // position texcoord |
275 | + -0.5f, +0.5f, 0.0f, 1.0f, |
276 | + +0.5f, +0.5f, 1.0f, 1.0f, |
277 | + +0.5f, -0.5f, 1.0f, 0.0f, |
278 | + -0.5f, -0.5f, 0.0f, 0.0f, |
279 | + }; |
280 | + GLint position = glGetAttribLocation(prog, "position"); |
281 | + GLint texcoord = glGetAttribLocation(prog, "texcoord"); |
282 | + glVertexAttribPointer(position, 2, GL_FLOAT, GL_FALSE, 4*sizeof(GLfloat), |
283 | + square); |
284 | + glVertexAttribPointer(texcoord, 2, GL_FLOAT, GL_FALSE, 4*sizeof(GLfloat), |
285 | + square+2); |
286 | + glEnableVertexAttribArray(position); |
287 | + glEnableVertexAttribArray(texcoord); |
288 | + |
289 | + GLint scale = glGetUniformLocation(prog, "scale"); |
290 | + glUniform1f(scale, 128.0f); |
291 | + |
292 | + GLint translate = glGetUniformLocation(prog, "translate"); |
293 | + glUniform2f(translate, 0.0f, 0.0f); |
294 | + |
295 | + GLint projection = glGetUniformLocation(prog, "projection"); |
296 | + |
297 | + GLuint tex = generate_target_texture(); |
298 | + glBindTexture(GL_TEXTURE_2D, tex); |
299 | + |
300 | + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); |
301 | + glViewport(0, 0, width, height); |
302 | + |
303 | + glEnable(GL_BLEND); |
304 | + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |
305 | + |
306 | + State state = |
307 | + { |
308 | + PTHREAD_MUTEX_INITIALIZER, |
309 | + PTHREAD_COND_INITIALIZER, |
310 | + true, |
311 | + true, |
312 | + {0, {{0,0}}} |
313 | + }; |
314 | + MirSurface *surface = mir_eglapp_native_surface(); |
315 | + mir_surface_set_event_handler(surface, on_event, &state); |
316 | + |
317 | + while (mir_eglapp_running()) |
318 | + { |
319 | + pthread_mutex_lock(&state.mutex); |
320 | + |
321 | + while (mir_eglapp_running() && !state.changed) |
322 | + pthread_cond_wait(&state.change, &state.mutex); |
323 | + |
324 | + if (state.resized) |
325 | + { |
326 | + // mir_eglapp_swap_buffers updates the viewport for us... |
327 | + GLint viewport[4]; |
328 | + glGetIntegerv(GL_VIEWPORT, viewport); |
329 | + int w = viewport[2], h = viewport[3]; |
330 | + |
331 | + // TRANSPOSED projection matrix to convert from the Mir input |
332 | + // rectangle {{0,0},{w,h}} to GL screen rectangle {{-1,1},{2,2}}. |
333 | + GLfloat matrix[16] = {2.0f/w, 0.0f, 0.0f, 0.0f, |
334 | + 0.0f, -2.0f/h, 0.0f, 0.0f, |
335 | + 0.0f, 0.0f, 1.0f, 0.0f, |
336 | + -1.0f, 1.0f, 0.0f, 1.0f}; |
337 | + // Note GL_FALSE: GLES does not support the transpose option |
338 | + glUniformMatrix4fv(projection, 1, GL_FALSE, matrix); |
339 | + state.resized = false; |
340 | + } |
341 | + |
342 | + glClear(GL_COLOR_BUFFER_BIT); |
343 | + |
344 | + for (int p = 0; p < state.touch.points; ++p) |
345 | + { |
346 | + // Note the 0.5f to convert from pixel middle to corner (GL) |
347 | + glUniform2f(translate, state.touch.point[p].x + 0.5f, |
348 | + state.touch.point[p].y + 0.5f); |
349 | + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); |
350 | + } |
351 | + |
352 | + // Put the event loop back to sleep: |
353 | + state.changed = false; |
354 | + pthread_mutex_unlock(&state.mutex); |
355 | + |
356 | + mir_eglapp_swap_buffers(); |
357 | + } |
358 | + |
359 | + mir_surface_set_event_handler(surface, NULL, NULL); |
360 | + mir_eglapp_shutdown(); |
361 | + |
362 | + return 0; |
363 | +} |
FAILED: Continuous integration, rev:2483 jenkins. qa.ubuntu. com/job/ mir-ci/ 3423/ jenkins. qa.ubuntu. com/job/ mir-android- vivid-i386- build/1912 jenkins. qa.ubuntu. com/job/ mir-clang- vivid-amd64- build/1911/ console jenkins. qa.ubuntu. com/job/ mir-mediumtests -vivid- touch/1862 jenkins. qa.ubuntu. com/job/ mir-vivid- amd64-ci/ 1420/console jenkins. qa.ubuntu. com/job/ mir-mediumtests -builder- vivid-armhf/ 1862 jenkins. qa.ubuntu. com/job/ mir-mediumtests -builder- vivid-armhf/ 1862/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ mir-mediumtests -runner- mako/4813 s-jenkins. ubuntu- ci:8080/ job/touch- flash-device/ 19345
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/mir- ci/3423/ rebuild
http://