1 /**************************************************************************
3 * Copyright 2013-2014 RAD Game Tools and Valve Software
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 **************************************************************************/
26 // File: voglbench.cpp
27 #include "vogl_common.h"
28 #include "vogl_gl_replayer.h"
29 #include "vogl_texture_format.h"
30 #include "vogl_trace_file_writer.h"
32 #include "vogl_colorized_console.h"
33 #include "vogl_command_line_params.h"
34 #include "vogl_cfile_stream.h"
35 #include "vogl_value.h"
36 #include "vogl_dynamic_stream.h"
37 #include "vogl_file_utils.h"
38 #include "vogl_mergesort.h"
39 #include "vogl_unique_ptr.h"
40 #include "vogl_find_files.h"
41 #include "vogl_bigint128.h"
42 #include "vogl_regex.h"
44 #include <sys/types.h>
47 #include "vogl_json.h"
48 #include "vogl_blob_manager.h"
50 #include "libtelemetry.h"
53 #include <X11/Xutil.h>
56 //$ TODO: investigate using SDL for windows and any keyboard controls.
57 //$ Run clang-format on everything.
58 //$ Use Telemetry to speed this bugger up.
60 //----------------------------------------------------------------------------------------------------------------------
62 //----------------------------------------------------------------------------------------------------------------------
63 static void *g_actual_libgl_module_handle;
64 static cfile_stream *g_vogl_pLog_stream;
66 //----------------------------------------------------------------------------------------------------------------------
67 // command line params
68 //----------------------------------------------------------------------------------------------------------------------
69 static command_line_param_desc g_command_line_param_descs[] =
71 { "width", 1, false, "Replay: Set initial window width (default is 1024)" },
72 { "height", 1, false, "Replay: Set initial window height (default is 768)" },
73 { "msaa", 1, false, "Replay: Set initial window multisamples (default is 0)" },
74 { "lock_window_dimensions", 0, false, "Replay: Don't automatically change window's dimensions during replay" },
75 { "endless", 0, false, "Replay: Loop replay endlessly instead of exiting" },
76 { "force_debug_context", 0, false, "Replay: Force GL debug contexts" },
78 { "telemetry_level", 1, false, "Set Telemetry level." },
80 { "loop_frame", 1, false, "Replay: loop mode's start frame" },
81 { "loop_len", 1, false, "Replay: loop mode's loop length" },
82 { "loop_count", 1, false, "Replay: loop mode's loop count" },
83 { "logfile", 1, false, "Create logfile" },
84 { "logfile_append", 1, false, "Append output to logfile" },
85 { "help", 0, false, "Display this help" },
86 { "?", 0, false, "Display this help" },
87 { "pause", 0, false, "Wait for a key at startup (so a debugger can be attached)" },
88 { "verbose", 0, false, "Verbose debug output" },
89 { "quiet", 0, false, "Disable all console output" },
92 //----------------------------------------------------------------------------------------------------------------------
94 //----------------------------------------------------------------------------------------------------------------------
95 static bool init_logfile()
99 dynamic_string log_file(g_command_line_params.get_value_as_string_or_empty("logfile"));
100 dynamic_string log_file_append(g_command_line_params.get_value_as_string_or_empty("logfile_append"));
101 if (log_file.is_empty() && log_file_append.is_empty())
104 dynamic_string filename(log_file_append.is_empty() ? log_file : log_file_append);
106 // This purposely leaks, don't care
107 g_vogl_pLog_stream = vogl_new(cfile_stream);
109 if (!g_vogl_pLog_stream->open(filename.get_ptr(), cDataStreamWritable, !log_file_append.is_empty()))
111 vogl_error_printf("%s: Failed opening log file \"%s\"\n", VOGL_FUNCTION_NAME, filename.get_ptr());
116 vogl_message_printf("Opened log file \"%s\"\n", filename.get_ptr());
117 console::set_log_stream(g_vogl_pLog_stream);
123 //----------------------------------------------------------------------------------------------------------------------
125 //----------------------------------------------------------------------------------------------------------------------
126 static void tool_print_title()
130 vogl_printf("voglbench ");
131 if (sizeof(void *) > 4)
132 vogl_printf("64-bit ");
134 vogl_printf("32-bit ");
135 #ifdef VOGL_BUILD_DEBUG
136 vogl_printf("Debug ");
138 vogl_printf("Release ");
140 vogl_printf("Built %s %s\n", __DATE__, __TIME__);
143 //----------------------------------------------------------------------------------------------------------------------
145 //----------------------------------------------------------------------------------------------------------------------
146 static void tool_print_help()
150 vogl_printf("Usage: voglbench [ -option ... ] input_file optional_output_file [ -option ... ]\n");
151 vogl_printf("Command line options may begin with single minus \"-\" or double minus \"--\"\n");
153 vogl_printf("\nCommand line options:\n");
155 dump_command_line_info(VOGL_ARRAY_SIZE(g_command_line_param_descs), g_command_line_param_descs, "--");
158 //----------------------------------------------------------------------------------------------------------------------
159 // init_command_line_params
160 //----------------------------------------------------------------------------------------------------------------------
161 static bool init_command_line_params(int argc, char *argv[])
165 command_line_params::parse_config parse_cfg;
166 parse_cfg.m_single_minus_params = true;
167 parse_cfg.m_double_minus_params = true;
169 if (!g_command_line_params.parse(get_command_line_params(argc, argv), VOGL_ARRAY_SIZE(g_command_line_param_descs), g_command_line_param_descs, parse_cfg))
171 vogl_error_printf("%s: Failed parsing command line parameters!\n", VOGL_FUNCTION_NAME);
178 if (g_command_line_params.get_value_as_bool("help") || g_command_line_params.get_value_as_bool("?"))
187 //----------------------------------------------------------------------------------------------------------------------
189 //----------------------------------------------------------------------------------------------------------------------
190 static bool load_gl()
194 g_actual_libgl_module_handle = dlopen("libGL.so.1", RTLD_LAZY);
195 if (!g_actual_libgl_module_handle)
197 vogl_error_printf("%s: Failed loading libGL.so.1!\n", VOGL_FUNCTION_NAME);
201 GL_ENTRYPOINT(glXGetProcAddress) = reinterpret_cast<glXGetProcAddress_func_ptr_t>(dlsym(g_actual_libgl_module_handle, "glXGetProcAddress"));
202 if (!GL_ENTRYPOINT(glXGetProcAddress))
204 vogl_error_printf("%s: Failed getting address of glXGetProcAddress() from libGL.so.1!\n", VOGL_FUNCTION_NAME);
211 //----------------------------------------------------------------------------------------------------------------------
212 // vogl_get_proc_address_helper
213 //----------------------------------------------------------------------------------------------------------------------
214 static vogl_void_func_ptr_t vogl_get_proc_address_helper(const char *pName)
218 vogl_void_func_ptr_t pFunc = g_actual_libgl_module_handle ? reinterpret_cast<vogl_void_func_ptr_t>(dlsym(g_actual_libgl_module_handle, pName)) : NULL;
220 if ((!pFunc) && (GL_ENTRYPOINT(glXGetProcAddress)))
221 pFunc = reinterpret_cast<vogl_void_func_ptr_t>(GL_ENTRYPOINT(glXGetProcAddress)(reinterpret_cast<const GLubyte *>(pName)));
226 //----------------------------------------------------------------------------------------------------------------------
227 // vogl_direct_gl_func_prolog - This function is called before EVERY single GL/GLX function call we make.
228 //----------------------------------------------------------------------------------------------------------------------
229 static void vogl_direct_gl_func_prolog(gl_entrypoint_id_t entrypoint_id, void *pUser_data, void **ppStack_data)
231 VOGL_NOTE_UNUSED(entrypoint_id);
232 VOGL_NOTE_UNUSED(pUser_data);
233 VOGL_NOTE_UNUSED(ppStack_data);
235 vogl_printf("* GLPROLOG %s\n", g_vogl_entrypoint_descs[entrypoint_id].m_pName);
238 //----------------------------------------------------------------------------------------------------------------------
239 // vogl_direct_gl_func_epilog - This function is called immediately after EVERY single GL/GLX function call we make.
240 //----------------------------------------------------------------------------------------------------------------------
241 static void vogl_direct_gl_func_epilog(gl_entrypoint_id_t entrypoint_id, void *pUser_data, void **ppStack_data)
243 VOGL_NOTE_UNUSED(entrypoint_id);
244 VOGL_NOTE_UNUSED(pUser_data);
245 VOGL_NOTE_UNUSED(ppStack_data);
247 vogl_printf("* GLEPILOG %s\n", g_vogl_entrypoint_descs[entrypoint_id].m_pName);
250 //----------------------------------------------------------------------------------------------------------------------
252 //----------------------------------------------------------------------------------------------------------------------
253 static bool voglbench_init(int argc, char *argv[])
257 g_thread_safe_random.seed_from_urandom();
259 colorized_console::init();
260 colorized_console::set_exception_callback();
261 //console::set_tool_prefix("(voglbench) ");
265 if (!init_command_line_params(argc, argv))
269 int telemetry_level = g_command_line_params.get_value_as_int("telemetry_level", 0,
270 TELEMETRY_LEVEL_MIN + 1, TELEMETRY_LEVEL_MIN, TELEMETRY_LEVEL_MAX);
271 telemetry_set_level(telemetry_level);
275 vogl_common_lib_early_init();
276 vogl_common_lib_global_init();
278 if (g_command_line_params.get_value_as_bool("quiet"))
279 console::disable_output();
281 if (g_command_line_params.get_value_as_bool("gl_debug_log"))
283 vogl_set_direct_gl_func_prolog(vogl_direct_gl_func_prolog, NULL);
284 vogl_set_direct_gl_func_epilog(vogl_direct_gl_func_epilog, NULL);
290 bool wrap_all_gl_calls = false;
291 vogl_init_actual_gl_entrypoints(vogl_get_proc_address_helper, wrap_all_gl_calls);
295 //----------------------------------------------------------------------------------------------------------------------
297 //----------------------------------------------------------------------------------------------------------------------
298 static void voglbench_deinit()
302 colorized_console::deinit();
305 //----------------------------------------------------------------------------------------------------------------------
306 // X11_Pending - from SDL
307 //----------------------------------------------------------------------------------------------------------------------
308 static int X11_Pending(Display *display)
312 /* Flush the display connection and look to see if events are queued */
314 if (XEventsQueued(display, QueuedAlready))
319 /* More drastic measures are required -- see if X is ready to talk */
321 static struct timeval zero_time; /* static == 0 */
325 x11_fd = ConnectionNumber(display);
327 FD_SET(x11_fd, &fdset);
328 if (select(x11_fd + 1, &fdset, NULL, NULL, &zero_time) == 1)
330 return (XPending(display));
334 /* Oh well, nothing is ready .. */
338 //----------------------------------------------------------------------------------------------------------------------
339 // get_replayer_flags_from_command_line_params
340 //----------------------------------------------------------------------------------------------------------------------
341 static uint get_replayer_flags_from_command_line_params()
343 uint replayer_flags = cGLReplayerBenchmarkMode;
347 const char *m_pCommand;
349 } s_replayer_command_line_params[] =
351 { "verbose", cGLReplayerVerboseMode },
352 { "force_debug_context", cGLReplayerForceDebugContexts },
353 { "debug", cGLReplayerDebugMode },
354 { "lock_window_dimensions", cGLReplayerLockWindowDimensions },
357 for (uint i = 0; i < sizeof(s_replayer_command_line_params) / sizeof(s_replayer_command_line_params[0]); i++)
359 if (g_command_line_params.get_value_as_bool(s_replayer_command_line_params[i].m_pCommand))
360 replayer_flags |= s_replayer_command_line_params[i].m_flag;
363 return replayer_flags;
366 //----------------------------------------------------------------------------------------------------------------------
368 //----------------------------------------------------------------------------------------------------------------------
369 static bool tool_replay_mode()
373 dynamic_string trace_filename(g_command_line_params.get_value_as_string_or_empty("", 1));
374 if (trace_filename.is_empty())
376 vogl_error_printf("%s: No trace file specified!\n", VOGL_FUNCTION_NAME);
380 dynamic_string actual_trace_filename;
381 vogl_unique_ptr<vogl_trace_file_reader> pTrace_reader(vogl_open_trace_file(
383 actual_trace_filename,
384 g_command_line_params.get_value_as_string_or_empty("loose_file_path").get_ptr()));
385 if (!pTrace_reader.get())
387 vogl_error_printf("%s: File not found, or unable to determine file type of trace file \"%s\"\n", VOGL_FUNCTION_NAME, trace_filename.get_ptr());
391 vogl_printf("Reading trace file %s\n", actual_trace_filename.get_ptr());
393 vogl_gl_replayer replayer;
394 vogl_replay_window window;
396 uint replayer_flags = get_replayer_flags_from_command_line_params();
398 // TODO: This will create a window with default attributes, which seems fine for the majority of traces.
399 // Unfortunately, some GL call streams *don't* want an alpha channel, or depth, or stencil etc. in the default framebuffer so this may become a problem.
400 // Also, this design only supports a single window, which is going to be a problem with multiple window traces.
401 if (!window.open(g_command_line_params.get_value_as_int("width", 0, 1024, 1, 65535), g_command_line_params.get_value_as_int("height", 0, 768, 1, 65535), g_command_line_params.get_value_as_int("msaa", 0, 0, 0, 65535)))
403 vogl_error_printf("%s: Failed initializing replay window\n", VOGL_FUNCTION_NAME);
407 if (!replayer.init(replayer_flags, &window, pTrace_reader->get_sof_packet(), pTrace_reader->get_multi_blob_manager()))
409 vogl_error_printf("%s: Failed initializing GL replayer\n", VOGL_FUNCTION_NAME);
413 // Disable all glGetError() calls in vogl_utils.cpp.
414 vogl_disable_gl_get_error();
416 XSelectInput(window.get_display(), window.get_xwindow(),
417 EnterWindowMask | LeaveWindowMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | ExposureMask | FocusChangeMask | KeyPressMask | KeyReleaseMask | PropertyChangeMask | StructureNotifyMask | KeymapStateMask);
419 Atom wmDeleteMessage = XInternAtom(window.get_display(), "WM_DELETE_WINDOW", False);
420 XSetWMProtocols(window.get_display(), window.get_xwindow(), &wmDeleteMessage, 1);
422 Bool win_mapped = false;
424 vogl_gl_state_snapshot *pSnapshot = NULL;
425 int64_t snapshot_loop_start_frame = -1;
426 int64_t snapshot_loop_end_frame = -1;
428 vogl::hash_map<uint64_t> keys_pressed, keys_down;
430 int loop_frame = g_command_line_params.get_value_as_int("loop_frame", 0, -1);
431 int loop_len = math::maximum<int>(g_command_line_params.get_value_as_int("loop_len", 0, 1), 1);
432 int loop_count = math::maximum<int>(g_command_line_params.get_value_as_int("loop_count", 0, cINT32_MAX), 1);
433 bool endless_mode = g_command_line_params.get_value_as_bool("endless");
440 tmZone(TELEMETRY_LEVEL0, TMZF_NONE, "Main Loop");
442 while (X11_Pending(window.get_display()))
446 // Watch for new X eventsn
447 XNextEvent(window.get_display(), &newEvent);
449 switch (newEvent.type)
453 KeySym xsym = XLookupKeysym(&newEvent.xkey, 0);
455 //printf("KeyPress 0%04llX %" PRIu64 "\n", (uint64_t)xsym, (uint64_t)xsym);
457 keys_down.insert(xsym);
458 keys_pressed.insert(xsym);
464 KeySym xsym = XLookupKeysym(&newEvent.xkey, 0);
466 //printf("KeyRelease 0x%04llX %" PRIu64 "\n", (uint64_t)xsym, (uint64_t)xsym);
468 keys_down.erase(xsym);
475 //printf("FocusIn/FocusOut\n");
483 //XRefreshKeyboardMapping(&newEvent);
488 //printf("UnmapNotify\n");
498 //printf("MapNotify\n");
504 if (!replayer.update_window_dimensions())
509 case ConfigureNotify:
511 if (!replayer.update_window_dimensions())
518 vogl_message_printf("Exiting\n");
523 if (newEvent.xclient.data.l[0] == (int)wmDeleteMessage)
525 vogl_message_printf("Exiting\n");
536 if (replayer.get_at_frame_boundary())
538 if ((!pSnapshot) && (loop_frame != -1) && (static_cast<int64_t>(replayer.get_frame_index()) == loop_frame))
540 vogl_debug_printf("%s: Capturing replayer state at start of frame %u\n", VOGL_FUNCTION_NAME, replayer.get_frame_index());
542 pSnapshot = replayer.snapshot_state();
546 vogl_printf("Snapshot succeeded\n");
548 snapshot_loop_start_frame = pTrace_reader->get_cur_frame();
549 snapshot_loop_end_frame = pTrace_reader->get_cur_frame() + loop_len;
551 vogl_debug_printf("%s: Loop start: %" PRIi64 " Loop end: %" PRIi64 "\n", VOGL_FUNCTION_NAME, snapshot_loop_start_frame, snapshot_loop_end_frame);
555 vogl_error_printf("Snapshot failed!\n");
561 vogl_gl_replayer::status_t status = replayer.process_pending_window_resize();
562 if (status == vogl_gl_replayer::cStatusOK)
566 status = replayer.process_next_packet(*pTrace_reader);
568 if ((status == vogl_gl_replayer::cStatusNextFrame) ||
569 (status == vogl_gl_replayer::cStatusResizeWindow) ||
570 (status == vogl_gl_replayer::cStatusAtEOF) ||
571 (status == vogl_gl_replayer::cStatusHardFailure))
578 if (status == vogl_gl_replayer::cStatusHardFailure)
581 if (status == vogl_gl_replayer::cStatusAtEOF)
583 vogl_message_printf("%s: At trace EOF, frame index %u\n", VOGL_FUNCTION_NAME, replayer.get_frame_index());
586 if (replayer.get_at_frame_boundary() &&
589 ((pTrace_reader->get_cur_frame() == snapshot_loop_end_frame) || (status == vogl_gl_replayer::cStatusAtEOF)))
591 status = replayer.begin_applying_snapshot(pSnapshot, false);
592 if ((status != vogl_gl_replayer::cStatusOK) && (status != vogl_gl_replayer::cStatusResizeWindow))
595 pTrace_reader->seek_to_frame(static_cast<uint>(snapshot_loop_start_frame));
597 vogl_debug_printf("%s: Applying snapshot and seeking back to frame %" PRIi64 "\n", VOGL_FUNCTION_NAME, snapshot_loop_start_frame);
602 bool print_progress = (status == vogl_gl_replayer::cStatusAtEOF) ||
603 ((replayer.get_at_frame_boundary()) && ((replayer.get_frame_index() % 100) == 0));
606 if (pTrace_reader->get_type() == cBINARY_TRACE_FILE_READER)
608 vogl_binary_trace_file_reader &binary_trace_reader = *static_cast<vogl_binary_trace_file_reader *>(pTrace_reader.get());
610 vogl_printf("Replay now at frame index %u, trace file offet %" PRIu64 ", GL call counter %" PRIu64 ", %3.2f%% percent complete\n",
611 replayer.get_frame_index(),
612 binary_trace_reader.get_cur_file_ofs(),
613 replayer.get_last_parsed_call_counter(),
614 binary_trace_reader.get_trace_file_size() ? (binary_trace_reader.get_cur_file_ofs() * 100.0f) / binary_trace_reader.get_trace_file_size() : 0);
618 if (status == vogl_gl_replayer::cStatusAtEOF)
622 double time_since_start = tm.get_elapsed_secs();
624 vogl_printf("%u total swaps, %.3f secs, %3.3f avg fps\n", replayer.get_total_swaps(), time_since_start, replayer.get_frame_index() / time_since_start);
628 vogl_printf("Resetting state and rewinding back to frame 0\n");
630 replayer.reset_state();
632 if (!pTrace_reader->seek_to_frame(0))
634 vogl_error_printf("%s: Failed rewinding trace reader!\n", VOGL_FUNCTION_NAME);
650 //----------------------------------------------------------------------------------------------------------------------
652 //----------------------------------------------------------------------------------------------------------------------
653 static int xerror_handler(Display *dsp, XErrorEvent *error)
655 char error_string[256];
656 XGetErrorText(dsp, error->error_code, error_string, sizeof(error_string));
658 fprintf(stderr, "voglbench: Fatal X Windows Error: %s\n", error_string);
662 //----------------------------------------------------------------------------------------------------------------------
664 //----------------------------------------------------------------------------------------------------------------------
665 int main(int argc, char *argv[])
667 #if VOGL_FUNCTION_TRACING
670 setvbuf(stdout, NULL, _IONBF, 0);
671 setvbuf(stderr, NULL, _IONBF, 0);
676 XSetErrorHandler(xerror_handler);
678 if (!voglbench_init(argc, argv))
684 if (g_command_line_params.get_count("") < 2)
686 vogl_error_printf("No trace file specified!\n");
694 if (g_command_line_params.get_value_as_bool("pause"))
696 vogl_message_printf("Press key to continue\n");
701 bool success = tool_replay_mode();
703 vogl_printf("%u warning(s), %u error(s)\n",
704 console::get_total_messages(cWarningConsoleMessage),
705 console::get_total_messages(cErrorConsoleMessage));
709 return success ? EXIT_SUCCESS : EXIT_FAILURE;