//======================================================================== // Input lag test // Copyright (c) Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== // // This test renders a marker at the cursor position reported by GLFW to // check how much it lags behind the hardware mouse cursor // //======================================================================== #include #define GLFW_INCLUDE_NONE #include #define NK_IMPLEMENTATION #define NK_INCLUDE_FIXED_TYPES #define NK_INCLUDE_FONT_BAKING #define NK_INCLUDE_DEFAULT_FONT #define NK_INCLUDE_DEFAULT_ALLOCATOR #define NK_INCLUDE_VERTEX_BUFFER_OUTPUT #define NK_INCLUDE_STANDARD_VARARGS #include #define NK_GLFW_GL2_IMPLEMENTATION #include #include #include #include #include "getopt.h" void usage(void) { printf("Usage: inputlag [-h] [-f]\n"); printf("Options:\n"); printf(" -f create full screen window\n"); printf(" -h show this help\n"); } struct nk_vec2 cursor_new, cursor_pos, cursor_vel; enum { cursor_sync_query, cursor_input_message } cursor_method = cursor_sync_query; void sample_input(GLFWwindow* window) { float a = .25; // exponential smoothing factor if (cursor_method == cursor_sync_query) { double x, y; glfwGetCursorPos(window, &x, &y); cursor_new.x = (float) x; cursor_new.y = (float) y; } cursor_vel.x = (cursor_new.x - cursor_pos.x) * a + cursor_vel.x * (1 - a); cursor_vel.y = (cursor_new.y - cursor_pos.y) * a + cursor_vel.y * (1 - a); cursor_pos = cursor_new; } void cursor_pos_callback(GLFWwindow* window, double xpos, double ypos) { cursor_new.x = (float) xpos; cursor_new.y = (float) ypos; } int enable_vsync = nk_true; void update_vsync() { glfwSwapInterval(enable_vsync == nk_true ? 1 : 0); } int swap_clear = nk_false; int swap_finish = nk_true; int swap_occlusion_query = nk_false; int swap_read_pixels = nk_false; GLuint occlusion_query; void swap_buffers(GLFWwindow* window) { glfwSwapBuffers(window); if (swap_clear) glClear(GL_COLOR_BUFFER_BIT); if (swap_finish) glFinish(); if (swap_occlusion_query) { GLint occlusion_result; if (!occlusion_query) glGenQueries(1, &occlusion_query); glBeginQuery(GL_SAMPLES_PASSED, occlusion_query); glBegin(GL_POINTS); glVertex2f(0, 0); glEnd(); glEndQuery(GL_SAMPLES_PASSED); glGetQueryObjectiv(occlusion_query, GL_QUERY_RESULT, &occlusion_result); } if (swap_read_pixels) { unsigned char rgba[4]; glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, rgba); } } void error_callback(int error, const char* description) { fprintf(stderr, "Error: %s\n", description); } void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) { if (action != GLFW_PRESS) return; switch (key) { case GLFW_KEY_ESCAPE: glfwSetWindowShouldClose(window, 1); break; } } void draw_marker(struct nk_command_buffer* canvas, int lead, struct nk_vec2 pos) { 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) }; struct nk_rect rect = { -5 + pos.x, -5 + pos.y, 10, 10 }; nk_fill_circle(canvas, rect, colors[lead]); } int main(int argc, char** argv) { int ch, width, height; unsigned long frame_count = 0; double last_time, current_time; double frame_rate = 0; int fullscreen = GLFW_FALSE; GLFWmonitor* monitor = NULL; GLFWwindow* window; struct nk_context* nk; struct nk_font_atlas* atlas; int show_forecasts = nk_true; while ((ch = getopt(argc, argv, "fh")) != -1) { switch (ch) { case 'h': usage(); exit(EXIT_SUCCESS); case 'f': fullscreen = GLFW_TRUE; break; } } glfwSetErrorCallback(error_callback); if (!glfwInit()) exit(EXIT_FAILURE); if (fullscreen) { const GLFWvidmode* mode; monitor = glfwGetPrimaryMonitor(); mode = glfwGetVideoMode(monitor); width = mode->width; height = mode->height; } else { width = 640; height = 480; } glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); glfwWindowHint(GLFW_WIN32_KEYBOARD_MENU, GLFW_TRUE); window = glfwCreateWindow(width, height, "Input lag test", monitor, NULL); if (!window) { glfwTerminate(); exit(EXIT_FAILURE); } glfwMakeContextCurrent(window); gladLoadGL(glfwGetProcAddress); update_vsync(); last_time = glfwGetTime(); nk = nk_glfw3_init(window, NK_GLFW3_INSTALL_CALLBACKS); nk_glfw3_font_stash_begin(&atlas); nk_glfw3_font_stash_end(); glfwSetKeyCallback(window, key_callback); glfwSetCursorPosCallback(window, cursor_pos_callback); while (!glfwWindowShouldClose(window)) { int width, height; struct nk_rect area; glfwPollEvents(); sample_input(window); glfwGetWindowSize(window, &width, &height); area = nk_rect(0.f, 0.f, (float) width, (float) height); glClear(GL_COLOR_BUFFER_BIT); nk_glfw3_new_frame(); if (nk_begin(nk, "", area, 0)) { nk_flags align_left = NK_TEXT_ALIGN_LEFT | NK_TEXT_ALIGN_MIDDLE; struct nk_command_buffer *canvas = nk_window_get_canvas(nk); int lead; for (lead = show_forecasts ? 3 : 0; lead >= 0; lead--) draw_marker(canvas, lead, nk_vec2(cursor_pos.x + cursor_vel.x * lead, cursor_pos.y + cursor_vel.y * lead)); // print instructions nk_layout_row_dynamic(nk, 20, 1); nk_label(nk, "Move mouse uniformly and check marker under cursor:", align_left); for (lead = 0; lead <= 3; lead++) { nk_layout_row_begin(nk, NK_STATIC, 12, 2); nk_layout_row_push(nk, 25); draw_marker(canvas, lead, nk_layout_space_to_screen(nk, nk_vec2(20, 5))); nk_label(nk, "", 0); nk_layout_row_push(nk, 500); if (lead == 0) nk_label(nk, "- current cursor position (no input lag)", align_left); else nk_labelf(nk, align_left, "- %d-frame forecast (input lag is %d frame)", lead, lead); nk_layout_row_end(nk); } nk_layout_row_dynamic(nk, 20, 1); nk_checkbox_label(nk, "Show forecasts", &show_forecasts); nk_label(nk, "Input method:", align_left); if (nk_option_label(nk, "glfwGetCursorPos (sync query)", cursor_method == cursor_sync_query)) cursor_method = cursor_sync_query; if (nk_option_label(nk, "glfwSetCursorPosCallback (latest input message)", cursor_method == cursor_input_message)) cursor_method = cursor_input_message; nk_label(nk, "", 0); // separator nk_value_float(nk, "FPS", (float) frame_rate); if (nk_checkbox_label(nk, "Enable vsync", &enable_vsync)) update_vsync(); nk_label(nk, "", 0); // separator nk_label(nk, "After swap:", align_left); nk_checkbox_label(nk, "glClear", &swap_clear); nk_checkbox_label(nk, "glFinish", &swap_finish); nk_checkbox_label(nk, "draw with occlusion query", &swap_occlusion_query); nk_checkbox_label(nk, "glReadPixels", &swap_read_pixels); } nk_end(nk); nk_glfw3_render(NK_ANTI_ALIASING_ON); swap_buffers(window); frame_count++; current_time = glfwGetTime(); if (current_time - last_time > 1.0) { frame_rate = frame_count / (current_time - last_time); frame_count = 0; last_time = current_time; } } glfwTerminate(); exit(EXIT_SUCCESS); }