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