inputlag.c 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. //========================================================================
  2. // Input lag test
  3. // Copyright (c) Camilla Löwy <elmindreda@glfw.org>
  4. //
  5. // This software is provided 'as-is', without any express or implied
  6. // warranty. In no event will the authors be held liable for any damages
  7. // arising from the use of this software.
  8. //
  9. // Permission is granted to anyone to use this software for any purpose,
  10. // including commercial applications, and to alter it and redistribute it
  11. // freely, subject to the following restrictions:
  12. //
  13. // 1. The origin of this software must not be misrepresented; you must not
  14. // claim that you wrote the original software. If you use this software
  15. // in a product, an acknowledgment in the product documentation would
  16. // be appreciated but is not required.
  17. //
  18. // 2. Altered source versions must be plainly marked as such, and must not
  19. // be misrepresented as being the original software.
  20. //
  21. // 3. This notice may not be removed or altered from any source
  22. // distribution.
  23. //
  24. //========================================================================
  25. //
  26. // This test renders a marker at the cursor position reported by GLFW to
  27. // check how much it lags behind the hardware mouse cursor
  28. //
  29. //========================================================================
  30. #include <glad/gl.h>
  31. #define GLFW_INCLUDE_NONE
  32. #include <GLFW/glfw3.h>
  33. #define NK_IMPLEMENTATION
  34. #define NK_INCLUDE_FIXED_TYPES
  35. #define NK_INCLUDE_FONT_BAKING
  36. #define NK_INCLUDE_DEFAULT_FONT
  37. #define NK_INCLUDE_DEFAULT_ALLOCATOR
  38. #define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
  39. #define NK_INCLUDE_STANDARD_VARARGS
  40. #include <nuklear.h>
  41. #define NK_GLFW_GL2_IMPLEMENTATION
  42. #include <nuklear_glfw_gl2.h>
  43. #include <stdio.h>
  44. #include <stdlib.h>
  45. #include <string.h>
  46. #include "getopt.h"
  47. void usage(void)
  48. {
  49. printf("Usage: inputlag [-h] [-f]\n");
  50. printf("Options:\n");
  51. printf(" -f create full screen window\n");
  52. printf(" -h show this help\n");
  53. }
  54. struct nk_vec2 cursor_new, cursor_pos, cursor_vel;
  55. enum { cursor_sync_query, cursor_input_message } cursor_method = cursor_sync_query;
  56. void sample_input(GLFWwindow* window)
  57. {
  58. float a = .25; // exponential smoothing factor
  59. if (cursor_method == cursor_sync_query) {
  60. double x, y;
  61. glfwGetCursorPos(window, &x, &y);
  62. cursor_new.x = (float) x;
  63. cursor_new.y = (float) y;
  64. }
  65. cursor_vel.x = (cursor_new.x - cursor_pos.x) * a + cursor_vel.x * (1 - a);
  66. cursor_vel.y = (cursor_new.y - cursor_pos.y) * a + cursor_vel.y * (1 - a);
  67. cursor_pos = cursor_new;
  68. }
  69. void cursor_pos_callback(GLFWwindow* window, double xpos, double ypos)
  70. {
  71. cursor_new.x = (float) xpos;
  72. cursor_new.y = (float) ypos;
  73. }
  74. int enable_vsync = nk_true;
  75. void update_vsync()
  76. {
  77. glfwSwapInterval(enable_vsync == nk_true ? 1 : 0);
  78. }
  79. int swap_clear = nk_false;
  80. int swap_finish = nk_true;
  81. int swap_occlusion_query = nk_false;
  82. int swap_read_pixels = nk_false;
  83. GLuint occlusion_query;
  84. void swap_buffers(GLFWwindow* window)
  85. {
  86. glfwSwapBuffers(window);
  87. if (swap_clear)
  88. glClear(GL_COLOR_BUFFER_BIT);
  89. if (swap_finish)
  90. glFinish();
  91. if (swap_occlusion_query) {
  92. GLint occlusion_result;
  93. if (!occlusion_query)
  94. glGenQueries(1, &occlusion_query);
  95. glBeginQuery(GL_SAMPLES_PASSED, occlusion_query);
  96. glBegin(GL_POINTS);
  97. glVertex2f(0, 0);
  98. glEnd();
  99. glEndQuery(GL_SAMPLES_PASSED);
  100. glGetQueryObjectiv(occlusion_query, GL_QUERY_RESULT, &occlusion_result);
  101. }
  102. if (swap_read_pixels) {
  103. unsigned char rgba[4];
  104. glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, rgba);
  105. }
  106. }
  107. void error_callback(int error, const char* description)
  108. {
  109. fprintf(stderr, "Error: %s\n", description);
  110. }
  111. void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
  112. {
  113. if (action != GLFW_PRESS)
  114. return;
  115. switch (key)
  116. {
  117. case GLFW_KEY_ESCAPE:
  118. glfwSetWindowShouldClose(window, 1);
  119. break;
  120. }
  121. }
  122. void draw_marker(struct nk_command_buffer* canvas, int lead, struct nk_vec2 pos)
  123. {
  124. struct nk_color colors[4] = { nk_rgb(255,0,0), nk_rgb(255,255,0), nk_rgb(0,255,0), nk_rgb(0,96,255) };
  125. struct nk_rect rect = { -5 + pos.x, -5 + pos.y, 10, 10 };
  126. nk_fill_circle(canvas, rect, colors[lead]);
  127. }
  128. int main(int argc, char** argv)
  129. {
  130. int ch, width, height;
  131. unsigned long frame_count = 0;
  132. double last_time, current_time;
  133. double frame_rate = 0;
  134. int fullscreen = GLFW_FALSE;
  135. GLFWmonitor* monitor = NULL;
  136. GLFWwindow* window;
  137. struct nk_context* nk;
  138. struct nk_font_atlas* atlas;
  139. int show_forecasts = nk_true;
  140. while ((ch = getopt(argc, argv, "fh")) != -1)
  141. {
  142. switch (ch)
  143. {
  144. case 'h':
  145. usage();
  146. exit(EXIT_SUCCESS);
  147. case 'f':
  148. fullscreen = GLFW_TRUE;
  149. break;
  150. }
  151. }
  152. glfwSetErrorCallback(error_callback);
  153. if (!glfwInit())
  154. exit(EXIT_FAILURE);
  155. if (fullscreen)
  156. {
  157. const GLFWvidmode* mode;
  158. monitor = glfwGetPrimaryMonitor();
  159. mode = glfwGetVideoMode(monitor);
  160. width = mode->width;
  161. height = mode->height;
  162. }
  163. else
  164. {
  165. width = 640;
  166. height = 480;
  167. }
  168. glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
  169. glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
  170. glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE);
  171. window = glfwCreateWindow(width, height, "Input lag test", monitor, NULL);
  172. if (!window)
  173. {
  174. glfwTerminate();
  175. exit(EXIT_FAILURE);
  176. }
  177. glfwMakeContextCurrent(window);
  178. gladLoadGL(glfwGetProcAddress);
  179. update_vsync();
  180. last_time = glfwGetTime();
  181. nk = nk_glfw3_init(window, NK_GLFW3_INSTALL_CALLBACKS);
  182. nk_glfw3_font_stash_begin(&atlas);
  183. nk_glfw3_font_stash_end();
  184. glfwSetKeyCallback(window, key_callback);
  185. glfwSetCursorPosCallback(window, cursor_pos_callback);
  186. while (!glfwWindowShouldClose(window))
  187. {
  188. int width, height;
  189. struct nk_rect area;
  190. glfwPollEvents();
  191. sample_input(window);
  192. glfwGetWindowSize(window, &width, &height);
  193. area = nk_rect(0.f, 0.f, (float) width, (float) height);
  194. glClear(GL_COLOR_BUFFER_BIT);
  195. nk_glfw3_new_frame();
  196. if (nk_begin(nk, "", area, 0))
  197. {
  198. nk_flags align_left = NK_TEXT_ALIGN_LEFT | NK_TEXT_ALIGN_MIDDLE;
  199. struct nk_command_buffer *canvas = nk_window_get_canvas(nk);
  200. int lead;
  201. for (lead = show_forecasts ? 3 : 0; lead >= 0; lead--)
  202. draw_marker(canvas, lead, nk_vec2(cursor_pos.x + cursor_vel.x * lead,
  203. cursor_pos.y + cursor_vel.y * lead));
  204. // print instructions
  205. nk_layout_row_dynamic(nk, 20, 1);
  206. nk_label(nk, "Move mouse uniformly and check marker under cursor:", align_left);
  207. for (lead = 0; lead <= 3; lead++) {
  208. nk_layout_row_begin(nk, NK_STATIC, 12, 2);
  209. nk_layout_row_push(nk, 25);
  210. draw_marker(canvas, lead, nk_layout_space_to_screen(nk, nk_vec2(20, 5)));
  211. nk_label(nk, "", 0);
  212. nk_layout_row_push(nk, 500);
  213. if (lead == 0)
  214. nk_label(nk, "- current cursor position (no input lag)", align_left);
  215. else
  216. nk_labelf(nk, align_left, "- %d-frame forecast (input lag is %d frame)", lead, lead);
  217. nk_layout_row_end(nk);
  218. }
  219. nk_layout_row_dynamic(nk, 20, 1);
  220. nk_checkbox_label(nk, "Show forecasts", &show_forecasts);
  221. nk_label(nk, "Input method:", align_left);
  222. if (nk_option_label(nk, "glfwGetCursorPos (sync query)", cursor_method == cursor_sync_query))
  223. cursor_method = cursor_sync_query;
  224. if (nk_option_label(nk, "glfwSetCursorPosCallback (latest input message)", cursor_method == cursor_input_message))
  225. cursor_method = cursor_input_message;
  226. nk_label(nk, "", 0); // separator
  227. nk_value_float(nk, "FPS", (float) frame_rate);
  228. if (nk_checkbox_label(nk, "Enable vsync", &enable_vsync))
  229. update_vsync();
  230. nk_label(nk, "", 0); // separator
  231. nk_label(nk, "After swap:", align_left);
  232. nk_checkbox_label(nk, "glClear", &swap_clear);
  233. nk_checkbox_label(nk, "glFinish", &swap_finish);
  234. nk_checkbox_label(nk, "draw with occlusion query", &swap_occlusion_query);
  235. nk_checkbox_label(nk, "glReadPixels", &swap_read_pixels);
  236. }
  237. nk_end(nk);
  238. nk_glfw3_render(NK_ANTI_ALIASING_ON);
  239. swap_buffers(window);
  240. frame_count++;
  241. current_time = glfwGetTime();
  242. if (current_time - last_time > 1.0)
  243. {
  244. frame_rate = frame_count / (current_time - last_time);
  245. frame_count = 0;
  246. last_time = current_time;
  247. }
  248. }
  249. glfwTerminate();
  250. exit(EXIT_SUCCESS);
  251. }