2
0

inputlag.c 8.9 KB

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