diff --git a/src/main/d3d9-monitor-check/Module.mk b/src/main/d3d9-monitor-check/Module.mk index 0f66079..3c342e0 100644 --- a/src/main/d3d9-monitor-check/Module.mk +++ b/src/main/d3d9-monitor-check/Module.mk @@ -10,4 +10,13 @@ libs_d3d9-monitor-check := \ util \ src_d3d9-monitor-check := \ + cmdline.c \ + font.c \ + gfx.c \ + input.c \ + interactive.c \ main.c \ + menu.c \ + refresh-rate-test.c \ + response-time-test.c \ + vsync-test.c diff --git a/src/main/d3d9-monitor-check/cmdline.c b/src/main/d3d9-monitor-check/cmdline.c new file mode 100644 index 0000000..628b306 --- /dev/null +++ b/src/main/d3d9-monitor-check/cmdline.c @@ -0,0 +1,534 @@ +#include +#include + +#include "d3d9-monitor-check/gfx.h" +#include "d3d9-monitor-check/input.h" +#include "d3d9-monitor-check/print-console.h" +#include "d3d9-monitor-check/refresh-rate-test.h" +#include "d3d9-monitor-check/response-time-test.h" +#include "d3d9-monitor-check/vsync-test.h" + +static void _print_synopsis() +{ + printfln_err("D3D9 monitor check tool command line mode"); + printfln_err(""); + printfln_err("Usage:"); + printfln_err(" d3d9-monitor-check cmdline "); + printfln_err(""); + printfln_err("Available commands:"); + printfln_err(" adapter: Query adapter information"); + printfln_err(" modes: Query adapter modes. Use this to get supported mandatory parameters for width, height and refresh rate for the tests."); + printfln_err(""); + printfln_err(" refresh-rate-test [--warm-up-secs n] [--total-secs n] [--results-timeout-secs n] [--windowed] [--vsync-off]: Run a display refresh rate test (i.e. IIDX monitor check)."); + printfln_err(" width: Width of the rendering resolution to run the test at"); + printfln_err(" height: Height of the rendering resolution to run the test at"); + printfln_err(" refresh_rate: Target refresh rate to run the test at"); + printfln_err(" warm-up-secs: Optional. Number of seconds to warm-up before executing the main run that counts towards the measurement results"); + printfln_err(" total-secs: Optional. Total number of seconds to run the test for that count towards the measurement results"); + printfln_err(" results-timeout-secs: Optional. Number of seconds to display final result after the test before exiting"); + printfln_err(" windowed: Optional. Run the test in windowed mode (not recommended)"); + printfln_err(" vsync-off: Optional. Run the test with vsync off (not recommended)"); + printfln_err(""); + printfln_err(" response-time-test [--total-secs n] [--windowed] [--vsync-off]: Run a test to visually inspect the display's response times."); + printfln_err(" width: Width of the rendering resolution to run the test at"); + printfln_err(" height: Height of the rendering resolution to run the test at"); + printfln_err(" refresh_rate: Target refresh rate to run the test at"); + printfln_err(" total-secs: Optional. Total number of seconds to run the test for that count towards the measurement results"); + printfln_err(" windowed: Optional. Run the test in windowed mode (not recommended)"); + printfln_err(" vsync-off: Optional. Run the test with vsync off (not recommended)"); + printfln_err(""); + printfln_err(" vsync-test [--total-secs n] [--windowed]: Run a test to visually inspect the display's vsync behavior."); + printfln_err(" width: Width of the rendering resolution to run the test at"); + printfln_err(" height: Height of the rendering resolution to run the test at"); + printfln_err(" refresh_rate: Target refresh rate to run the test at"); + printfln_err(" total-secs: Optional. Total number of seconds to run the test for that count towards the measurement results"); + printfln_err(" windowed: Optional. Run the test in windowed mode (not recommended)"); +} + +static bool _adapter() +{ + D3DADAPTER_IDENTIFIER9 identifier; + + if (!gfx_adapter_info_get(&identifier)) { + return false; + } + + printfln_out("Driver: %s", identifier.Driver); + printfln_out("Description: %s", identifier.Description); + printfln_out("DeviceName: %s", identifier.DeviceName); +#ifdef _WIN32 + printfln_out("DriverVersion: %lld", identifier.DriverVersion.QuadPart); +#else + printfln_out("DriverVersion: %lu.%lu", identifier.DriverVersionHighPart, identifier.DriverVersionLowPart); +#endif + printfln_out("VendorId: %lu", identifier.VendorId); + printfln_out("DeviceId: %lu", identifier.DeviceId); + printfln_out("SubSysId: %lu", identifier.SubSysId); + printfln_out("Revision: %lu", identifier.Revision); + printfln_out("DeviceIdentifier: {%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", + identifier.DeviceIdentifier.Data1, + identifier.DeviceIdentifier.Data2, + identifier.DeviceIdentifier.Data3, + identifier.DeviceIdentifier.Data4[0], + identifier.DeviceIdentifier.Data4[1], + identifier.DeviceIdentifier.Data4[2], + identifier.DeviceIdentifier.Data4[3], + identifier.DeviceIdentifier.Data4[4], + identifier.DeviceIdentifier.Data4[5], + identifier.DeviceIdentifier.Data4[6], + identifier.DeviceIdentifier.Data4[7]); + printfln_out("WHQLLevel: %lu", identifier.WHQLLevel); + + return true; +} + +static bool _modes() +{ + gfx_adapter_modes_t modes; + + if (!gfx_adapter_modes_get(&modes)) { + return false; + } + + for (uint32_t i = 0; i < modes.count; i++) { + printfln_out("%d: %d x %d @ %d hz", i, modes.modes[i].Width, modes.modes[i].Height, modes.modes[i].RefreshRate); + } + + return true; +} + +static bool _refresh_rate_test( + uint32_t width, + uint32_t height, + uint32_t refresh_rate, + uint32_t warm_up_frame_count, + uint32_t sample_frame_count, + uint32_t result_timeout_frame_count, + bool windowed, + bool vsync) +{ + gfx_t *gfx; + input_t *input; + refresh_rate_test_t *test; + gfx_info_t gfx_info; + refresh_rate_test_results_t results; + uint32_t results_timeout_seconds; + + input_init(&input); + + if (!gfx_init(width, height, refresh_rate, windowed, vsync, &gfx)) { + input_fini(input); + return false; + } + + if (!refresh_rate_test_init(gfx, warm_up_frame_count, sample_frame_count, &test)) { + input_fini(input); + gfx_fini(gfx); + return false; + } + + do { + input_update(input); + + if (input_key_esc_pushed(input)) { + break; + } + } while (refresh_rate_test_frame_update(test) && + gfx_last_frame_count_get(gfx) < warm_up_frame_count + sample_frame_count); + + results_timeout_seconds = result_timeout_frame_count / refresh_rate; + + // Display final results + for (uint32_t i = 0; i < result_timeout_frame_count; i++) { + input_update(input); + + if (input_key_esc_pushed(input)) { + break; + } + + if (!refresh_rate_test_results_frame_update(test, results_timeout_seconds)) { + break; + } + } + + gfx_info_get(gfx, &gfx_info); + refresh_rate_test_results_get(test, &results); + + printfln_err("Final results"); + printfln_out("GPU: %s", gfx_info.adapter_identifier); + printfln_out("Spec: %d x %d @ %d hz, %s, vsync %s", gfx_info.width, gfx_info.height, gfx_info.refresh_rate, + gfx_info.windowed ? "windowed" : "fullscreen", gfx_info.vsync ? "on" : "off"); + printfln_out("Total warm-up frame count: %d", results.total_warm_up_frame_count); + printfln_out("Total sample frame count: %d", results.total_sample_frame_count); + printfln_out("Avg frame time (ms): %.3f", results.avg_frame_time_ms); + printfln_out("Avg refresh rate (hz): %.3f", results.avg_refresh_rate_hz); + + refresh_rate_test_fini(test); + input_fini(input); + gfx_fini(gfx); + + return true; +} + +static bool _response_time_test( + uint32_t width, + uint32_t height, + uint32_t refresh_rate, + uint32_t total_frame_count, + bool windowed, + bool vsync) +{ + gfx_t *gfx; + input_t *input; + response_time_test_t *test; + + input_init(&input); + + // Force vsync on, off option doesn't make sense for this test + if (!gfx_init(width, height, refresh_rate, windowed, vsync, &gfx)) { + input_fini(input); + return false; + } + + if (!response_time_test_init(gfx, &test)) { + input_fini(input); + gfx_fini(gfx); + return false; + } + + do { + input_update(input); + + if (input_key_esc_pushed(input)) { + break; + } + } while (response_time_test_frame_update(test) && + gfx_last_frame_count_get(gfx) < total_frame_count); + + response_time_test_fini(test); + input_fini(input); + gfx_fini(gfx); + + return true; +} + +static bool _vsync_test( + uint32_t width, + uint32_t height, + uint32_t refresh_rate, + uint32_t total_frame_count, + bool windowed) +{ + gfx_t *gfx; + input_t *input; + vsync_test_t *test; + + input_init(&input); + + // Force vsync on, off option doesn't make sense for this test + if (!gfx_init(width, height, refresh_rate, windowed, true, &gfx)) { + input_fini(input); + return false; + } + + if (!vsync_test_init(gfx, &test)) { + input_fini(input); + gfx_fini(gfx); + return false; + } + + do { + input_update(input); + + if (input_key_esc_pushed(input)) { + break; + } + } while (vsync_test_frame_update(test) && + gfx_last_frame_count_get(gfx) < total_frame_count); + + vsync_test_fini(test); + input_fini(input); + gfx_fini(gfx); + + return true; +} + +static bool _cmd_refresh_rate_test(int argc, char **argv) +{ + uint32_t width; + uint32_t height; + uint32_t refresh_rate; + uint32_t warm_up_seconds; + uint32_t sample_seconds; + uint32_t results_timeout_seconds; + bool windowed; + bool vsync; + + uint32_t total_warm_up_frame_count; + uint32_t total_sample_frame_count; + uint32_t result_timeout_frame_count; + + if (argc < 3) { + _print_synopsis(); + printfln_err("ERROR: Insufficient arguments"); + return false; + } + + width = atoi(argv[0]); + + if (width == 0 || width > 16384) { + _print_synopsis(); + printfln_err("ERROR: Invalid width: %d", width); + return false; + } + + height = atoi(argv[1]); + + if (height == 0 || height > 16384) { + _print_synopsis(); + printfln_err("ERROR: Invalid height: %d", height); + return false; + } + + refresh_rate = atoi(argv[2]); + + if (refresh_rate == 0 || refresh_rate > 1000) { + _print_synopsis(); + printfln_err("ERROR: Invalid refresh rate: %d", refresh_rate); + return false; + } + + // Sane defaults + warm_up_seconds = 10; + sample_seconds = 20; + results_timeout_seconds = 5; + windowed = false; + vsync = true; + + for (int i = 3; i < argc; i++) { + if (!strcmp(argv[i], "--warm-up-secs")) { + if (i + 1 < argc) { + warm_up_seconds = atoi(argv[++i]); + + if (warm_up_seconds == 0) { + _print_synopsis(); + printfln_err("ERROR: Invalid warm-up seconds: %d", warm_up_seconds); + return false; + } + } else { + _print_synopsis(); + printfln_err("ERROR: Missing argument for --warm-up-secs"); + return false; + } + } else if (!strcmp(argv[i], "--sample-secs")) { + if (i + 1 < argc) { + sample_seconds = atoi(argv[++i]); + + if (sample_seconds == 0) { + _print_synopsis(); + printfln_err("ERROR: Invalid sample seconds: %d", sample_seconds); + return false; + } + } else { + _print_synopsis(); + printfln_err("ERROR: Missing argument for --total-secs"); + return false; + } + } else if (!strcmp(argv[i], "--results-timeout-secs")) { + if (i + 1 < argc) { + results_timeout_seconds = atoi(argv[++i]); + } else { + _print_synopsis(); + printfln_err("ERROR: Missing argument for --results-timeout-secs"); + } + } else if (!strcmp(argv[i], "--windowed")) { + windowed = true; + } else if (!strcmp(argv[i], "--vsync-off")) { + vsync = false; + } + } + + total_warm_up_frame_count = warm_up_seconds * refresh_rate; + total_sample_frame_count = sample_seconds * refresh_rate; + result_timeout_frame_count = results_timeout_seconds * refresh_rate; + + return _refresh_rate_test( + width, + height, + refresh_rate, + total_warm_up_frame_count, + total_sample_frame_count, + result_timeout_frame_count, + windowed, + vsync); +} + +static bool _cmd_response_time_test(int argc, char **argv) +{ + uint32_t width; + uint32_t height; + uint32_t refresh_rate; + uint32_t total_seconds; + bool windowed; + bool vsync; + + uint32_t total_frame_count; + + if (argc < 3) { + _print_synopsis(); + printfln_err("ERROR: Insufficient arguments"); + return false; + } + + width = atoi(argv[0]); + + if (width == 0 || width > 16384) { + _print_synopsis(); + printfln_err("ERROR: Invalid width: %d", width); + return false; + } + + height = atoi(argv[1]); + + if (height == 0 || height > 16384) { + _print_synopsis(); + printfln_err("ERROR: Invalid height: %d", height); + return false; + } + + refresh_rate = atoi(argv[2]); + + if (refresh_rate == 0 || refresh_rate > 1000) { + _print_synopsis(); + printfln_err("ERROR: Invalid refresh rate: %d", refresh_rate); + return false; + } + + // Sane defaults + total_seconds = 30; + windowed = false; + vsync = true; + for (int i = 3; i < argc; i++) { + if (!strcmp(argv[i], "--total-secs")) { + if (i + 1 < argc) { + total_seconds = atoi(argv[++i]); + + if (total_seconds == 0) { + _print_synopsis(); + printfln_err("ERROR: Invalid total seconds: %d", total_seconds); + return false; + } + } else { + _print_synopsis(); + printfln_err("ERROR: Missing argument for --total-secs"); + return false; + } + } else if (!strcmp(argv[i], "--windowed")) { + windowed = true; + } else if (!strcmp(argv[i], "--vsync-off")) { + vsync = false; + } + } + + total_frame_count = total_seconds * refresh_rate; + + return _response_time_test(width, height, refresh_rate, total_frame_count, windowed, vsync); +} + +static bool _cmd_vsync_test(int argc, char **argv) +{ + uint32_t width; + uint32_t height; + uint32_t refresh_rate; + uint32_t total_seconds; + bool windowed; + + uint32_t total_frame_count; + + if (argc < 3) { + _print_synopsis(); + printfln_err("ERROR: Insufficient arguments"); + return false; + } + + width = atoi(argv[0]); + + if (width == 0 || width > 16384) { + _print_synopsis(); + printfln_err("ERROR: Invalid width: %d", width); + return false; + } + + height = atoi(argv[1]); + + if (height == 0 || height > 16384) { + _print_synopsis(); + printfln_err("ERROR: Invalid height: %d", height); + return false; + } + + refresh_rate = atoi(argv[2]); + + if (refresh_rate == 0 || refresh_rate > 1000) { + _print_synopsis(); + printfln_err("ERROR: Invalid refresh rate: %d", refresh_rate); + return false; + } + + // Sane defaults + total_seconds = 30; + windowed = false; + + for (int i = 3; i < argc; i++) { + if (!strcmp(argv[i], "--total-secs")) { + if (i + 1 < argc) { + total_seconds = atoi(argv[++i]); + + if (total_seconds == 0) { + _print_synopsis(); + printfln_err("ERROR: Invalid total seconds: %d", total_seconds); + return false; + } + } else { + _print_synopsis(); + printfln_err("ERROR: Missing argument for --total-secs"); + return false; + } + } else if (!strcmp(argv[i], "--windowed")) { + windowed = true; + } + } + + total_frame_count = total_seconds * refresh_rate; + + return _vsync_test(width, height, refresh_rate, total_frame_count, windowed); +} + +bool cmdline_main(int argc, char **argv) +{ + const char *command; + + if (argc < 1) { + _print_synopsis(); + printfln_err("ERROR: Insufficient arguments"); + return false; + } + + command = argv[0]; + + if (!strcmp(command, "adapter")) { + return _adapter(); + } else if (!strcmp(command, "modes")) { + return _modes(); + } else if (!strcmp(command, "refresh-rate-test")) { + return _cmd_refresh_rate_test(argc - 1, argv + 1); + } else if (!strcmp(command, "response-time-test")) { + return _cmd_response_time_test(argc - 1, argv + 1); + } else if (!strcmp(command, "vsync-test")) { + return _cmd_vsync_test(argc - 1, argv + 1); + } else { + _print_synopsis(argv[0]); + printfln_err("ERROR: Unknown command: %s", command); + return false; + } +} \ No newline at end of file diff --git a/src/main/d3d9-monitor-check/cmdline.h b/src/main/d3d9-monitor-check/cmdline.h new file mode 100644 index 0000000..43303b8 --- /dev/null +++ b/src/main/d3d9-monitor-check/cmdline.h @@ -0,0 +1,8 @@ +#ifndef D3D9_MONITOR_CHECK_CMDLINE_H +#define D3D9_MONITOR_CHECK_CMDLINE_H + +#include + +bool cmdline_main(int argc, char **argv); + +#endif diff --git a/src/main/d3d9-monitor-check/font.c b/src/main/d3d9-monitor-check/font.c new file mode 100644 index 0000000..a281799 --- /dev/null +++ b/src/main/d3d9-monitor-check/font.c @@ -0,0 +1,207 @@ +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "d3d9-monitor-check/font.h" +#include "d3d9-monitor-check/gfx.h" + +#include "util/mem.h" + +#define BASE_RESOLUTION_WIDTH 640.0f +#define BASE_RESOLUTION_HEIGHT 480.0f +#define BASE_FONT_SIZE 20.0f +#define BASE_MARGIN 1.0f +// Standard Windows DPI +#define BASE_DPI 96.0f + +typedef struct font { + gfx_t *gfx; + ID3DXFont *font; + float scale_x; + float scale_y; + float dpi_scale; + uint32_t font_height; + uint32_t line_height; + uint32_t base_offset_x; + uint32_t base_offset_y; +} font_t; + +static void _font_text_draw(font_t *font, uint32_t x, uint32_t y, uint8_t r, uint8_t g, uint8_t b, const char *text) +{ + uint32_t scaled_x; + uint32_t scaled_y; + RECT rect; + + assert(font); + assert(text); + + // Scale position based on both resolution and DPI + scaled_x = (uint32_t)(x * font->scale_x * font->dpi_scale) + font->base_offset_x; + scaled_y = (uint32_t)(y * font->scale_y * font->dpi_scale) + font->base_offset_y; + + rect.left = scaled_x; + rect.top = scaled_y; + // Scale text box width based on both resolution and DPI + rect.right = scaled_x + (uint32_t)(BASE_RESOLUTION_WIDTH * font->scale_x * font->dpi_scale); + rect.bottom = scaled_y + font->line_height; + + ID3DXFont_DrawText( + font->font, + NULL, + text, + -1, + &rect, + DT_LEFT | DT_TOP | DT_NOCLIP, + D3DCOLOR_XRGB(r, g, b)); +} + +bool font_init(gfx_t *gfx, uint32_t size, font_t **font) +{ + IDirect3DDevice9 *device; + HRESULT hr; + HDC hdc; + int dpi; + + assert(gfx); + assert(font); + + *font = xmalloc(sizeof(font_t)); + memset(*font, 0, sizeof(font_t)); + + (*font)->gfx = gfx; + + // Get the system DPI + hdc = GetDC(NULL); + dpi = GetDeviceCaps(hdc, LOGPIXELSY); + ReleaseDC(NULL, hdc); + + // Calculate DPI scale relative to base DPI + (*font)->dpi_scale = dpi / BASE_DPI; + + // Calculate resolution scaling factors + (*font)->scale_x = gfx_width_get(gfx) / BASE_RESOLUTION_WIDTH; + (*font)->scale_y = gfx_height_get(gfx) / BASE_RESOLUTION_HEIGHT; + + // Scale font height based on both DPI and resolution + // This ensures text maintains physical size across different resolutions + (*font)->font_height = (uint32_t)(size * (*font)->dpi_scale); + + // Line height matches font height + (*font)->line_height = (*font)->font_height; + + // Scale margins based on DPI and resolution + (*font)->base_offset_x = (uint32_t)(BASE_MARGIN * (*font)->scale_x * (*font)->dpi_scale); + (*font)->base_offset_y = (uint32_t)(BASE_MARGIN * (*font)->scale_y * (*font)->dpi_scale); + + device = gfx_device_get(gfx); + + hr = D3DXCreateFont( + device, + (*font)->font_height, + 0, + FW_BOLD, + 1, + FALSE, + DEFAULT_CHARSET, + OUT_DEFAULT_PRECIS, + ANTIALIASED_QUALITY, // Changed to antialiased for better readability + DEFAULT_PITCH | FF_DONTCARE, + "Arial", + &(*font)->font); + + if (hr != D3D_OK) { + free(*font); + return false; + } + + return true; +} + +void font_fini(font_t *font) +{ + assert(font); + + ID3DXFont_Release(font->font); + + free(font); +} + +void font_text_begin(font_t *font, uint32_t line_spacing, uint32_t origin_x, uint32_t origin_y, font_text_t *text) +{ + assert(font); + assert(text); + + text->font = font; + text->line_spacing = line_spacing; + text->origin_x = origin_x; + text->origin_y = origin_y; + text->current_x = origin_x; + text->current_y = origin_y; +} + +void font_text_white_draw(font_text_t *text, const char *fmt, ...) +{ + va_list args; + char buffer[4096]; + + assert(text); + assert(fmt); + + va_start(args, fmt); + vsprintf(buffer, fmt, args); + va_end(args); + + _font_text_draw(text->font, text->current_x, text->current_y, 255, 255, 255, buffer); +} + +void font_text_red_draw(font_text_t *text, const char *fmt, ...) +{ + va_list args; + char buffer[4096]; + + assert(text); + assert(fmt); + + va_start(args, fmt); + vsprintf(buffer, fmt, args); + va_end(args); + + _font_text_draw(text->font, text->current_x, text->current_y, 255, 0, 0, buffer); +} + +void font_text_cyan_draw(font_text_t *text, const char *fmt, ...) +{ + va_list args; + char buffer[4096]; + + assert(text); + assert(fmt); + + va_start(args, fmt); + vsprintf(buffer, fmt, args); + va_end(args); + + _font_text_draw(text->font, text->current_x, text->current_y, 0, 255, 255, buffer); +} + +void font_text_newline(font_text_t *text) +{ + assert(text); + + text->current_x = text->origin_x; + text->current_y += text->font->line_height + text->line_spacing; +} + +void font_text_end(font_text_t *text) +{ + assert(text); + + memset(text, 0, sizeof(font_text_t)); +} \ No newline at end of file diff --git a/src/main/d3d9-monitor-check/font.h b/src/main/d3d9-monitor-check/font.h new file mode 100644 index 0000000..a3be599 --- /dev/null +++ b/src/main/d3d9-monitor-check/font.h @@ -0,0 +1,37 @@ +#ifndef D3D9_MONITOR_CHECK_FONT_H +#define D3D9_MONITOR_CHECK_FONT_H + +#include +#include +#include + +#include "d3d9-monitor-check/gfx.h" + +typedef struct font font_t; + +typedef struct font_text { + font_t *font; + uint32_t line_spacing; + uint32_t origin_x; + uint32_t origin_y; + uint32_t current_x; + uint32_t current_y; +} font_text_t; + +bool font_init(gfx_t *gfx, uint32_t size, font_t **font); + +void font_fini(font_t *font); + +void font_text_begin(font_t *font, uint32_t line_spacing, uint32_t origin_x, uint32_t origin_y, font_text_t *text); + +void font_text_white_draw(font_text_t *text, const char *fmt, ...); + +void font_text_red_draw(font_text_t *text, const char *fmt, ...); + +void font_text_cyan_draw(font_text_t *text, const char *fmt, ...); + +void font_text_newline(font_text_t *text); + +void font_text_end(font_text_t *text); + +#endif diff --git a/src/main/d3d9-monitor-check/gfx.c b/src/main/d3d9-monitor-check/gfx.c new file mode 100644 index 0000000..081301d --- /dev/null +++ b/src/main/d3d9-monitor-check/gfx.c @@ -0,0 +1,430 @@ +#include + +#include +#include + +#include +#include +#include + +#include "d3d9-monitor-check/gfx.h" +#include "d3d9-monitor-check/print-console.h" + +#include "util/mem.h" +#include "util/time.h" + +typedef struct gfx_ctx_t { + HWND hwnd; + IDirect3D9 *d3d; + IDirect3DDevice9 *device; + uint32_t width; + uint32_t height; + uint32_t refresh_rate; + bool windowed; + bool vsync; +} gfx_ctx_t; + +typedef struct gfx_render_state_t { + uint64_t frame_current; + uint64_t frame_start_time; + uint64_t last_frame_time_us; +} gfx_render_state_t; + +typedef struct gfx { + gfx_ctx_t ctx; + gfx_render_state_t render_state; +} gfx_t; + +static const D3DFORMAT _gfx_d3dformat = D3DFMT_X8R8G8B8; + +static bool _gfx_d3d_context_create(IDirect3D9 **d3d) +{ + // Initialize D3D + *d3d = Direct3DCreate9(D3D_SDK_VERSION); + + if (!*d3d) { + printfln_winerr("Creating d3d context failed"); + return false; + } + + return true; +} + +static bool _gfx_adapter_identifier_query(IDirect3D9 *d3d, D3DADAPTER_IDENTIFIER9 *identifier) +{ + HRESULT hr; + + hr = IDirect3D9_GetAdapterIdentifier(d3d, D3DADAPTER_DEFAULT, 0, identifier); + + if (hr != D3D_OK) { + printfln_winerr("GetAdapterIdentifier failed"); + return false; + } + + return true; +} + +static bool _gfx_window_create(uint32_t width, uint32_t height, HWND *hwnd) +{ + WNDCLASSEX wc; + + memset(&wc, 0, sizeof(wc)); + + wc.cbSize = sizeof(wc); + wc.lpfnWndProc = DefWindowProc; + wc.hInstance = GetModuleHandle(NULL); + wc.lpszClassName = "D3D9MonitorCheck"; + + RegisterClassExA(&wc); + + // Create window + *hwnd = CreateWindowA( + wc.lpszClassName, + "D3D9 Monitor Check", + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, + CW_USEDEFAULT, + width, + height, + NULL, + NULL, + wc.hInstance, + NULL); + + if (!*hwnd) { + printfln_winerr("Failed to create window"); + return false; + } + + return true; +} + +static bool _gfx_d3d_device_create( + HWND hwnd, + IDirect3D9 *d3d, + uint32_t width, + uint32_t height, + uint32_t refresh_rate, + bool windowed, + bool vsync, + IDirect3DDevice9 **device) +{ + D3DPRESENT_PARAMETERS pp; + HRESULT hr; + + memset(&pp, 0, sizeof(pp)); + + if (windowed) { + ShowWindow(hwnd, SW_SHOW); + + pp.Windowed = TRUE; + pp.FullScreen_RefreshRateInHz = 0; + } else { + ShowCursor(FALSE); + + pp.Windowed = FALSE; + pp.FullScreen_RefreshRateInHz = refresh_rate; + } + + if (vsync) { + pp.PresentationInterval = D3DPRESENT_INTERVAL_ONE; + } else { + pp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; + } + + pp.BackBufferWidth = width; + pp.BackBufferHeight = height; + pp.BackBufferFormat = _gfx_d3dformat; + pp.BackBufferCount = 2; + pp.MultiSampleType = D3DMULTISAMPLE_NONE; + pp.MultiSampleQuality = 0; + pp.SwapEffect = D3DSWAPEFFECT_DISCARD; + pp.hDeviceWindow = hwnd; + pp.EnableAutoDepthStencil = TRUE; + pp.AutoDepthStencilFormat = D3DFMT_D16; + pp.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER; + + // Create D3D device + hr = IDirect3D9_CreateDevice( + d3d, + D3DADAPTER_DEFAULT, + D3DDEVTYPE_HAL, + hwnd, + D3DCREATE_HARDWARE_VERTEXPROCESSING, + &pp, + device); + + if (hr != D3D_OK) { + printfln_winerr("Creating d3d device failed"); + return false; + } + + return true; +} + +bool gfx_adapter_info_get(D3DADAPTER_IDENTIFIER9 *adapter) +{ + IDirect3D9 *d3d; + + assert(adapter); + + memset(adapter, 0, sizeof(D3DADAPTER_IDENTIFIER9)); + + if (!_gfx_d3d_context_create(&d3d)) { + return false; + } + + if (!_gfx_adapter_identifier_query(d3d, adapter)) { + IDirect3D9_Release(d3d); + return false; + } + + IDirect3D9_Release(d3d); + + return true; +} + +bool gfx_adapter_modes_get(gfx_adapter_modes_t *modes) +{ + IDirect3D9 *d3d; + HRESULT hr; + UINT mode_count; + + assert(modes); + + memset(modes, 0, sizeof(gfx_adapter_modes_t)); + + if (!_gfx_d3d_context_create(&d3d)) { + return false; + } + + mode_count = IDirect3D9_GetAdapterModeCount(d3d, D3DADAPTER_DEFAULT, _gfx_d3dformat); + + if (mode_count > sizeof(modes->modes)) { + mode_count = sizeof(modes->modes); + printfln_err("WARNING: Available adapter modes (total %d) is greater than the maximum supported modes in structure (%zu)", mode_count, sizeof(modes->modes)); + } + + for (UINT i = 0; i < mode_count; i++) { + hr = IDirect3D9_EnumAdapterModes(d3d, D3DADAPTER_DEFAULT, _gfx_d3dformat, i, &modes->modes[i]); + + if (hr != D3D_OK) { + printfln_winerr("EnumAdapterMode index %d failed", i); + IDirect3D9_Release(d3d); + return false; + } + } + + modes->count = mode_count; + + IDirect3D9_Release(d3d); + + return true; +} + +bool gfx_init( + uint32_t width, + uint32_t height, + uint32_t refresh_rate, + bool windowed, + bool vsync, + gfx_t **gfx) +{ + HWND hwnd; + IDirect3D9 *d3d; + D3DADAPTER_IDENTIFIER9 identifier; + IDirect3DDevice9 *device; + + assert(gfx); + + printfln_err("Creating d3d context ..."); + + if (!_gfx_d3d_context_create(&d3d)) { + return false; + } + + printfln_err("Querying adapter identifier ..."); + + if (!_gfx_adapter_identifier_query(d3d, &identifier)) { + IDirect3D9_Release(d3d); + return false; + } + + printfln_err("Adapter:"); + printfln_err("Driver: %s", identifier.Driver); + printfln_err("Description: %s", identifier.Description); + printfln_err("DeviceName: %s", identifier.DeviceName); +#ifdef _WIN32 + printfln_err("DriverVersion: %lld", identifier.DriverVersion.QuadPart); +#else + printfln_err("DriverVersion: %lu.%lu", identifier.DriverVersionHighPart, identifier.DriverVersionLowPart); +#endif + + printfln_err("Creating window with %dx%d ...", width, height); + + if (!_gfx_window_create(width, height, &hwnd)) { + IDirect3D9_Release(d3d); + return false; + } + + printfln_err("Creating d3d device %d x %d @ %d hz %s vsync %s ...", + width, + height, + refresh_rate, + windowed ? "windowed" : "fullscreen", + vsync ? "on" : "off"); + + if (!_gfx_d3d_device_create( + hwnd, + d3d, + width, + height, + refresh_rate, + windowed, + vsync, + &device)) { + IDirect3D9_Release(d3d); + DestroyWindow(hwnd); + return false; + } + + *gfx = xmalloc(sizeof(gfx_t)); + memset(*gfx, 0, sizeof(gfx_t)); + + (*gfx)->ctx.hwnd = hwnd; + (*gfx)->ctx.d3d = d3d; + (*gfx)->ctx.device = device; + (*gfx)->ctx.width = width; + (*gfx)->ctx.height = height; + (*gfx)->ctx.refresh_rate = refresh_rate; + (*gfx)->ctx.windowed = windowed; + (*gfx)->ctx.vsync = vsync; + + (*gfx)->render_state.frame_current = 0; + (*gfx)->render_state.frame_start_time = 0; + (*gfx)->render_state.last_frame_time_us = 0; + + return true; +} + +uint32_t gfx_width_get(gfx_t *gfx) +{ + assert(gfx); + + return gfx->ctx.width; +} + +uint32_t gfx_height_get(gfx_t *gfx) +{ + assert(gfx); + + return gfx->ctx.height; +} + +bool gfx_info_get(gfx_t *gfx, gfx_info_t *info) +{ + D3DADAPTER_IDENTIFIER9 identifier; + + assert(gfx); + assert(info); + + if (!_gfx_adapter_identifier_query(gfx->ctx.d3d, &identifier)) { + return false; + } + + strncpy(info->adapter_identifier, identifier.Description, sizeof(info->adapter_identifier)); + + info->width = gfx->ctx.width; + info->height = gfx->ctx.height; + info->refresh_rate = gfx->ctx.refresh_rate; + info->windowed = gfx->ctx.windowed; + info->vsync = gfx->ctx.vsync; + + return true; +} + +IDirect3DDevice9 *gfx_device_get(gfx_t *gfx) +{ + assert(gfx); + + return gfx->ctx.device; +} + +bool gfx_frame_begin(gfx_t *gfx) +{ + MSG msg; + + assert(gfx); + + // Required to not make windows think we are stuck and not responding + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + if (msg.message == WM_QUIT) { + return false; + } + + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + IDirect3DDevice9_Clear( + gfx->ctx.device, + 0, + NULL, + D3DCLEAR_TARGET, + D3DCOLOR_XRGB(0, 0, 0), + 1.0f, + 0); + IDirect3DDevice9_BeginScene(gfx->ctx.device); + + return true; +} + +void gfx_frame_end(gfx_t *gfx) +{ + uint64_t end_time; + + assert(gfx); + + IDirect3DDevice9_EndScene(gfx->ctx.device); + IDirect3DDevice9_Present(gfx->ctx.device, NULL, NULL, NULL, NULL); + + // End previous frame time measurement after the frame has been presented (which might include waiting for vsync) + // and start a new frame time measurement immediately + // This ensures that anything that comes after ending the frame and still before starting a new frame, e.g. IO + // processing, is included in the total frame time measurement + // However, this can not include the very first frame + if (gfx->render_state.frame_current > 0) { + end_time = time_get_counter(); + gfx->render_state.last_frame_time_us = time_get_elapsed_us(end_time - gfx->render_state.frame_start_time); + gfx->render_state.frame_start_time = end_time; + } + + gfx->render_state.frame_start_time = time_get_counter(); + + gfx->render_state.frame_current++; +} + +uint64_t gfx_last_frame_count_get(gfx_t *gfx) +{ + assert(gfx); + + return gfx->render_state.frame_current; +} + +uint64_t gfx_last_frame_time_us_get(gfx_t *gfx) +{ + assert(gfx); + + return gfx->render_state.last_frame_time_us; +} + +void gfx_fini(gfx_t *gfx) +{ + assert(gfx); + + IDirect3DDevice9_Release(gfx->ctx.device); + IDirect3D9_Release(gfx->ctx.d3d); + DestroyWindow(gfx->ctx.hwnd); + + free(gfx); +} \ No newline at end of file diff --git a/src/main/d3d9-monitor-check/gfx.h b/src/main/d3d9-monitor-check/gfx.h new file mode 100644 index 0000000..790d862 --- /dev/null +++ b/src/main/d3d9-monitor-check/gfx.h @@ -0,0 +1,59 @@ +#ifndef D3D9_MONITOR_CHECK_GFX_H +#define D3D9_MONITOR_CHECK_GFX_H + +#include + +#include + +#include +#include + +typedef struct gfx gfx_t; +struct IDirect3DDevice9; + +typedef struct gfx_adapter_modes { + D3DDISPLAYMODE modes[1024]; + uint32_t count; +} gfx_adapter_modes_t; + +typedef struct gfx_info { + char adapter_identifier[1024]; + uint32_t width; + uint32_t height; + uint32_t refresh_rate; + bool windowed; + bool vsync; +} gfx_info_t; + +bool gfx_adapter_info_get(D3DADAPTER_IDENTIFIER9 *adapter); + +bool gfx_adapter_modes_get(gfx_adapter_modes_t *modes); + +bool gfx_init(uint32_t width, + uint32_t height, + uint32_t refresh_rate, + bool windowed, + bool vsync, + gfx_t **gfx); + +uint32_t gfx_width_get(gfx_t *gfx); + +uint32_t gfx_height_get(gfx_t *gfx); + +bool gfx_info_get(gfx_t *gfx, gfx_info_t *info); + +IDirect3DDevice9 *gfx_device_get(gfx_t *gfx); + +bool gfx_frame_begin(gfx_t *gfx); + +void gfx_frame_end(gfx_t *gfx); + +uint64_t gfx_last_frame_count_get(gfx_t *gfx); + +uint64_t gfx_last_frame_time_us_get(gfx_t *gfx); + +bool gfx_adpater_info_get(gfx_t *gfx); + +void gfx_fini(gfx_t *gfx); + +#endif diff --git a/src/main/d3d9-monitor-check/input.c b/src/main/d3d9-monitor-check/input.c new file mode 100644 index 0000000..bb862db --- /dev/null +++ b/src/main/d3d9-monitor-check/input.c @@ -0,0 +1,90 @@ +#include + +#include +#include +#include + +#include "util/mem.h" + +typedef struct input_key_state { + bool previous_update_state; + bool current_update_state; +} input_key_state_t; + +typedef struct input { + input_key_state_t esc; + input_key_state_t up; + input_key_state_t down; + input_key_state_t left; + input_key_state_t right; + input_key_state_t enter; +} input_t; + +void input_init(input_t **input) +{ + assert(input); + + *input = xmalloc(sizeof(input_t)); + memset(*input, 0, sizeof(input_t)); +} + +void input_update(input_t *input) +{ + assert(input); + + input->esc.previous_update_state = input->esc.current_update_state; + input->esc.current_update_state = GetAsyncKeyState(VK_ESCAPE) & 0x8000; + + input->up.previous_update_state = input->up.current_update_state; + input->up.current_update_state = GetAsyncKeyState(VK_UP) & 0x8000; + + input->down.previous_update_state = input->down.current_update_state; + input->down.current_update_state = GetAsyncKeyState(VK_DOWN) & 0x8000; + + input->left.previous_update_state = input->left.current_update_state; + input->left.current_update_state = GetAsyncKeyState(VK_LEFT) & 0x8000; + + input->right.previous_update_state = input->right.current_update_state; + input->right.current_update_state = GetAsyncKeyState(VK_RIGHT) & 0x8000; + + input->enter.previous_update_state = input->enter.current_update_state; + input->enter.current_update_state = GetAsyncKeyState(VK_RETURN) & 0x8000; + +} + +bool input_key_esc_pushed(input_t *input) +{ + return input->esc.previous_update_state == false && input->esc.current_update_state == true; +} + +bool input_key_up_pushed(input_t *input) +{ + return input->up.previous_update_state == false && input->up.current_update_state == true; +} + +bool input_key_down_pushed(input_t *input) +{ + return input->down.previous_update_state == false && input->down.current_update_state == true; +} + +bool input_key_left_pushed(input_t *input) +{ + return input->left.previous_update_state == false && input->left.current_update_state == true; +} + +bool input_key_right_pushed(input_t *input) +{ + return input->right.previous_update_state == false && input->right.current_update_state == true; +} + +bool input_key_enter_pushed(input_t *input) +{ + return input->enter.previous_update_state == false && input->enter.current_update_state == true; +} + +void input_fini(input_t *input) +{ + assert(input); + + free(input); +} \ No newline at end of file diff --git a/src/main/d3d9-monitor-check/input.h b/src/main/d3d9-monitor-check/input.h new file mode 100644 index 0000000..e0428ce --- /dev/null +++ b/src/main/d3d9-monitor-check/input.h @@ -0,0 +1,26 @@ +#ifndef D3D9_MONITOR_CHECK_INPUT_H +#define D3D9_MONITOR_CHECK_INPUT_H + +#include + +typedef struct input input_t; + +void input_init(input_t **input); + +void input_update(input_t *input); + +bool input_key_esc_pushed(input_t *input); + +bool input_key_up_pushed(input_t *input); + +bool input_key_down_pushed(input_t *input); + +bool input_key_left_pushed(input_t *input); + +bool input_key_right_pushed(input_t *input); + +bool input_key_enter_pushed(input_t *input); + +void input_fini(input_t *input); + +#endif diff --git a/src/main/d3d9-monitor-check/interactive.c b/src/main/d3d9-monitor-check/interactive.c new file mode 100644 index 0000000..6f74431 --- /dev/null +++ b/src/main/d3d9-monitor-check/interactive.c @@ -0,0 +1,196 @@ +#include +#include +#include + +#include "d3d9-monitor-check/gfx.h" +#include "d3d9-monitor-check/input.h" +#include "d3d9-monitor-check/menu.h" +#include "d3d9-monitor-check/print-console.h" +#include "d3d9-monitor-check/refresh-rate-test.h" +#include "d3d9-monitor-check/response-time-test.h" +#include "d3d9-monitor-check/vsync-test.h" + +typedef enum INTERACTIVE_SCREEN { + INTERACTIVE_SCREEN_MENU = 0, + INTERACTIVE_SCREEN_REFRESH_RATE_TEST = 1, + INTERACTIVE_SCREEN_RESPONSE_TIME_TEST = 2, + INTERACTIVE_SCREEN_VSYNC_TEST = 3, +} interactive_screen_t; + +static void _print_synopsis() +{ + printfln_err("D3D9 monitor check tool interactive mode"); + printfln_err(""); + printfln_err("Usage:"); + printfln_err(" d3d9-monitor-check interactive [--windowed] [--vsync-off]"); + printfln_err(""); + printfln_err(" width: Width of the rendering resolution to run the test at"); + printfln_err(" height: Height of the rendering resolution to run the test at"); + printfln_err(" refresh-rate: Target refresh rate to run the test at"); + printfln_err(" windowed: Optional. Run the test in windowed mode (not recommended)"); + printfln_err(" vsync-off: Optional. Run the test with vsync off (not recommended)"); +} + +static bool _interactive( + uint32_t width, + uint32_t height, + uint32_t refresh_rate, + bool windowed, + bool vsync) +{ + gfx_t *gfx; + input_t *input; + bool loop_running; + interactive_screen_t current_screen; + menu_item_t selected_menu_item; + + menu_t *menu; + refresh_rate_test_t *refresh_rate_test; + response_time_test_t *response_time_test; + vsync_test_t *vsync_test; + + input_init(&input); + + if (!gfx_init(width, height, refresh_rate, windowed, vsync, &gfx)) { + input_fini(input); + return false; + } + + if (!menu_init(gfx, &menu)) { + gfx_fini(gfx); + input_fini(input); + return false; + } + + loop_running = true; + current_screen = INTERACTIVE_SCREEN_MENU; + + while (loop_running) { + input_update(input); + + switch (current_screen) { + case INTERACTIVE_SCREEN_MENU: + if (input_key_esc_pushed(input)) { + loop_running = false; + break; + } else if (input_key_up_pushed(input)) { + menu_select_cursor_move_up(menu); + } else if (input_key_down_pushed(input)) { + menu_select_cursor_move_down(menu); + } else if (input_key_enter_pushed(input)) { + selected_menu_item = menu_item_selected_get(menu); + + switch (selected_menu_item) { + case MENU_ITEM_REFRESH_RATE_TEST: + if (!refresh_rate_test_init(gfx, 600, 1200 ,&refresh_rate_test)) { + loop_running = false; + break; + } + + current_screen = INTERACTIVE_SCREEN_REFRESH_RATE_TEST; + break; + + case MENU_ITEM_RESPONSE_TIME_TEST: + if (!response_time_test_init(gfx, &response_time_test)) { + loop_running = false; + break; + } + + current_screen = INTERACTIVE_SCREEN_RESPONSE_TIME_TEST; + break; + + case MENU_ITEM_VSYNC_TEST: + if (!vsync_test_init(gfx, &vsync_test)) { + loop_running = false; + break; + } + + current_screen = INTERACTIVE_SCREEN_VSYNC_TEST; + break; + + case MENU_ITEM_EXIT: + loop_running = false; + break; + + default: + assert(0); + break; + } + } + + if (!menu_frame_update(menu)) { + loop_running = false; + break; + } + + break; + + case INTERACTIVE_SCREEN_REFRESH_RATE_TEST: + if ( input_key_esc_pushed(input) || + !refresh_rate_test_frame_update(refresh_rate_test)) { + current_screen = INTERACTIVE_SCREEN_MENU; + refresh_rate_test_fini(refresh_rate_test); + } + + break; + + case INTERACTIVE_SCREEN_RESPONSE_TIME_TEST: + if ( input_key_esc_pushed(input) || + !response_time_test_frame_update(response_time_test)) { + current_screen = INTERACTIVE_SCREEN_MENU; + response_time_test_fini(response_time_test); + } + + break; + + case INTERACTIVE_SCREEN_VSYNC_TEST: + if ( input_key_esc_pushed(input) || + !vsync_test_frame_update(vsync_test)) { + current_screen = INTERACTIVE_SCREEN_MENU; + vsync_test_fini(vsync_test); + } + + break; + } + } + + menu_fini(menu); + + gfx_fini(gfx); + input_fini(input); + + return true; +} + +bool interactive_main(int argc, char **argv) +{ + uint32_t width; + uint32_t height; + uint32_t refresh_rate; + bool windowed; + bool vsync; + + assert(argv); + + if (argc < 3) { + _print_synopsis(); + printfln_err("ERROR: Insufficient arguments"); + return false; + } + + width = atoi(argv[0]); + height = atoi(argv[1]); + refresh_rate = atoi(argv[2]); + windowed = false; + vsync = true; + + for (int i = 3; i < argc; i++) { + if (!strcmp(argv[i], "--windowed")) { + windowed = true; + } else if (!strcmp(argv[i], "--vsync-off")) { + vsync = false; + } + } + + return _interactive(width, height, refresh_rate, windowed, vsync); +} \ No newline at end of file diff --git a/src/main/d3d9-monitor-check/interactive.h b/src/main/d3d9-monitor-check/interactive.h new file mode 100644 index 0000000..48032b9 --- /dev/null +++ b/src/main/d3d9-monitor-check/interactive.h @@ -0,0 +1,8 @@ +#ifndef D3D9_MONITOR_CHECK_INTERACTIVE_H +#define D3D9_MONITOR_CHECK_INTERACTIVE_H + +#include + +bool interactive_main(int argc, char **argv); + +#endif diff --git a/src/main/d3d9-monitor-check/main.c b/src/main/d3d9-monitor-check/main.c index 2b07038..9cf3cdb 100644 --- a/src/main/d3d9-monitor-check/main.c +++ b/src/main/d3d9-monitor-check/main.c @@ -1,733 +1,24 @@ -#include - -#include -#include - -#include -#include -#include #include -#include "util/time.h" -#include "util/winerr.h" - -#define printf_out(fmt, ...) \ - fprintf(stdout, fmt, ##__VA_ARGS__) -#define printf_err(fmt, ...) \ - fprintf(stderr, fmt, ##__VA_ARGS__) -#define printfln_out(fmt, ...) \ - fprintf(stdout, fmt "\n", ##__VA_ARGS__) -#define printfln_err(fmt, ...) \ - fprintf(stderr, fmt "\n", ##__VA_ARGS__) - -#define printfln_winerr(fmt, ...) \ - char *winerr = util_winerr_format_last_error_code(); \ - fprintf(stderr, fmt ": %s\n", ##__VA_ARGS__, winerr); \ - free(winerr); - -static const D3DFORMAT _d3dformat = D3DFMT_X8R8G8B8; +#include "d3d9-monitor-check/cmdline.h" +#include "d3d9-monitor-check/interactive.h" +#include "d3d9-monitor-check/print-console.h" static void _print_synopsis() { - printfln_err("D3D9 monitor check"); + printfln_err("D3D9 monitor check tool"); printfln_err(""); - printfln_err("Improved open source re-implementation of IIDX's infamous \"monitor check\" screen"); - printfln_err("Run a bare D3D9 render loop to measure the refresh rate of the current GPU + monitor configuration"); - printfln_err(""); - printfln_err("Usage:"); - printfln_err(" d3d9-monitor-check "); + printfln_err("A versatile tool for running various (music game relevant) monitor tests using the D3D9 rendering API"); printfln_err(""); printfln_err("Available commands:"); - printfln_err(" adapter: Query adapter information"); - printfln_err(" modes: Query adapter modes"); - printfln_err(" run [--warm-up-secs n] [--total-secs n] [--results-timeout-secs n] [--windowed] [--vsync-off]: Run the monitor check. Ensure that the mandatory parameters for width, height and refresh rate are values that are supported by the adapter's mode. Use the \"modes\" subcommand to get a list of supported modes."); - printfln_err(" width: Width of the rendering resolution to run the test at"); - printfln_err(" height: Height of the rendering resolution to run the test at"); - printfln_err(" refresh_rate: Target refresh rate to run the test at"); - printfln_err(" warm-up-secs: Optional. Number of seconds to warm-up before executing the main run that counts towards the measurement results"); - printfln_err(" total-secs: Optional. Total number of seconds to run the test for that count towards the measurement results"); - printfln_err(" results-timeout-secs: Optional. Number of seconds to display final result after the test before exiting"); - printfln_err(" windowed: Optional. Run the test in windowed mode (not recommended)"); - printfln_err(" vsync-off: Optional. Run the test with vsync off (not recommended)"); -} - -static bool _create_d3d_context(IDirect3D9 **d3d) -{ - // Initialize D3D - *d3d = Direct3DCreate9(D3D_SDK_VERSION); - - if (!*d3d) { - printfln_winerr("Creating d3d context failed"); - return false; - } - - return true; -} - -static bool _query_adapter_identifier(IDirect3D9 *d3d, D3DADAPTER_IDENTIFIER9 *identifier) -{ - HRESULT hr; - - hr = IDirect3D9_GetAdapterIdentifier(d3d, D3DADAPTER_DEFAULT, 0, identifier); - - if (hr != D3D_OK) { - printfln_winerr("GetAdapterIdentifier failed"); - return false; - } - - return true; -} - -static bool _create_window(uint32_t width, uint32_t height, HWND *hwnd) -{ - WNDCLASSEX wc; - - memset(&wc, 0, sizeof(wc)); - - wc.cbSize = sizeof(wc); - wc.lpfnWndProc = DefWindowProc; - wc.hInstance = GetModuleHandle(NULL); - wc.lpszClassName = "D3D9MonitorCheck"; - - RegisterClassExA(&wc); - - // Create window - *hwnd = CreateWindowA( - wc.lpszClassName, - "D3D9 Monitor Check", - WS_OVERLAPPEDWINDOW, - CW_USEDEFAULT, - CW_USEDEFAULT, - width, - height, - NULL, - NULL, - wc.hInstance, - NULL); - - if (!*hwnd) { - printfln_winerr("Failed to create window"); - return false; - } - - return true; -} - -static bool _create_d3d_device( - HWND hwnd, - IDirect3D9 *d3d, - uint32_t width, - uint32_t height, - uint32_t refresh_rate, - bool windowed, - bool vsync_off, - IDirect3DDevice9 **device) -{ - D3DPRESENT_PARAMETERS pp; - HRESULT hr; - - memset(&pp, 0, sizeof(pp)); - - if (windowed) { - ShowWindow(hwnd, SW_SHOW); - - pp.Windowed = TRUE; - pp.FullScreen_RefreshRateInHz = 0; - } else { - ShowCursor(FALSE); - - pp.Windowed = FALSE; - pp.FullScreen_RefreshRateInHz = refresh_rate; - } - - if (vsync_off) { - pp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; - } else { - pp.PresentationInterval = D3DPRESENT_INTERVAL_ONE; - } - - pp.BackBufferWidth = width; - pp.BackBufferHeight = height; - pp.BackBufferFormat = _d3dformat; - pp.BackBufferCount = 2; - pp.MultiSampleType = D3DMULTISAMPLE_NONE; - pp.MultiSampleQuality = 0; - pp.SwapEffect = D3DSWAPEFFECT_DISCARD; - pp.hDeviceWindow = hwnd; - pp.EnableAutoDepthStencil = TRUE; - pp.AutoDepthStencilFormat = D3DFMT_D16; - pp.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER; - - // Create D3D device - hr = IDirect3D9_CreateDevice( - d3d, - D3DADAPTER_DEFAULT, - D3DDEVTYPE_HAL, - hwnd, - D3DCREATE_HARDWARE_VERTEXPROCESSING, - &pp, - device); - - if (hr != D3D_OK) { - printfln_winerr("Creating d3d device failed"); - return false; - } - - return true; -} - -static uint32_t _get_font_height(uint32_t resolution_height) -{ - // Default size for 480p - return (uint32_t) (20.0f * resolution_height / 480.0f); -} - -static uint32_t _get_text_offset_x(uint32_t resolution_width) -{ - // Default offset for 480p - return (uint32_t) (20.0f * resolution_width / 480.0f); -} - -static uint32_t _get_text_offset_y(uint32_t resolution_height, uint32_t font_height) -{ - // Default offset for 480p - return (uint32_t) (font_height + 10 * (resolution_height / 640.0f)); -} - -static bool _create_font(IDirect3DDevice9 *device, uint32_t font_height, ID3DXFont **font) -{ - HRESULT hr; - - hr = D3DXCreateFont(device, font_height, 0, FW_BOLD, 1, FALSE, DEFAULT_CHARSET, - OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, - "Arial", font); - - if (hr != D3D_OK) { - printfln_winerr("Creating font failed"); - return false; - } - - return true; -} - -static void _draw_text(IDirect3DDevice9 *device, ID3DXFont *font, uint32_t font_height, int x, int y, const char *fmt, ...) -{ - va_list args; - char text[1024]; - RECT rect; - - va_start(args, fmt); - vsprintf(text, fmt, args); - va_end(args); - - rect.left = x; - rect.top = y; - // Base width of 300 is based on 480p - rect.right = x + (480 * (font_height / 20.0f)); - rect.bottom = y + font_height; - - ID3DXFont_DrawText(font, NULL, text, -1, &rect, DT_LEFT | DT_TOP, D3DCOLOR_XRGB(255, 255, 255)); -} - -static bool _is_esc_key_pressed() -{ - return GetAsyncKeyState(VK_ESCAPE) & 0x8000; -} - -static bool _is_esc_key_released() -{ - return !(GetAsyncKeyState(VK_ESCAPE) & 0x8000); -} - -static bool _adapter() -{ - IDirect3D9 *d3d; - D3DADAPTER_IDENTIFIER9 identifier; - - if (!_create_d3d_context(&d3d)) { - return false; - } - - if (!_query_adapter_identifier(d3d, &identifier)) { - IDirect3D9_Release(d3d); - return false; - } - - printfln_out("Driver: %s", identifier.Driver); - printfln_out("Description: %s", identifier.Description); - printfln_out("DeviceName: %s", identifier.DeviceName); -#ifdef _WIN32 - printfln_out("DriverVersion: %lld", identifier.DriverVersion.QuadPart); -#else - printfln_out("DriverVersion: %lu.%lu", identifier.DriverVersionHighPart, identifier.DriverVersionLowPart); -#endif - printfln_out("VendorId: %lu", identifier.VendorId); - printfln_out("DeviceId: %lu", identifier.DeviceId); - printfln_out("SubSysId: %lu", identifier.SubSysId); - printfln_out("Revision: %lu", identifier.Revision); - printfln_out("DeviceIdentifier: {%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", - identifier.DeviceIdentifier.Data1, - identifier.DeviceIdentifier.Data2, - identifier.DeviceIdentifier.Data3, - identifier.DeviceIdentifier.Data4[0], - identifier.DeviceIdentifier.Data4[1], - identifier.DeviceIdentifier.Data4[2], - identifier.DeviceIdentifier.Data4[3], - identifier.DeviceIdentifier.Data4[4], - identifier.DeviceIdentifier.Data4[5], - identifier.DeviceIdentifier.Data4[6], - identifier.DeviceIdentifier.Data4[7]); - printfln_out("WHQLLevel: %lu", identifier.WHQLLevel); - - IDirect3D9_Release(d3d); - - return true; -} - -static bool _modes() -{ - IDirect3D9 *d3d; - HRESULT hr; - UINT mode_count; - D3DDISPLAYMODE mode; - - memset(&mode, 0, sizeof(D3DDISPLAYMODE)); - - if (!_create_d3d_context(&d3d)) { - return false; - } - - mode_count = IDirect3D9_GetAdapterModeCount(d3d, D3DADAPTER_DEFAULT, _d3dformat); - - printfln_err("Available adapter modes (total %d)", mode_count); - printfln_err("Mode index: width x height @ refresh rate"); - - for (UINT i = 0; i < mode_count; i++) { - hr = IDirect3D9_EnumAdapterModes(d3d, D3DADAPTER_DEFAULT, _d3dformat, i, &mode); - - if (hr != D3D_OK) { - printfln_winerr("EnumAdapterMode index %d failed", i); - IDirect3D9_Release(d3d); - return false; - } - - printfln_out("%d: %d x %d @ %d hz", i, mode.Width, mode.Height, mode.RefreshRate); - } - - IDirect3D9_Release(d3d); - - return true; -} - -static bool _run(uint32_t width, uint32_t height, uint32_t refresh_rate, uint32_t total_warm_up_frame_count, - uint32_t total_frame_count, uint32_t results_timeout_seconds, bool windowed, bool vsync_off) -{ - HWND hwnd; - IDirect3D9 *d3d; - D3DADAPTER_IDENTIFIER9 identifier; - IDirect3DDevice9 *device; - uint32_t font_height; - ID3DXFont *font; - uint32_t text_offset_x; - uint32_t text_offset_y; - - MSG msg; - bool exit_loop; - bool warm_up_done; - uint32_t warm_up_frame_count; - uint32_t frame_count; - uint64_t start_time; - uint64_t end_time; - uint64_t elapsed_us; - uint64_t total_elapsed_us; - - printfln_err("Creating d3d context ..."); - - if (!_create_d3d_context(&d3d)) { - return false; - } - - printfln_err("Querying adapter identifier ..."); - - if (!_query_adapter_identifier(d3d, &identifier)) { - IDirect3D9_Release(d3d); - return false; - } - - printfln_err("Adapter:"); - printfln_err("Driver: %s", identifier.Driver); - printfln_err("Description: %s", identifier.Description); - printfln_err("DeviceName: %s", identifier.DeviceName); -#ifdef _WIN32 - printfln_err("DriverVersion: %lld", identifier.DriverVersion.QuadPart); -#else - printfln_err("DriverVersion: %lu.%lu", identifier.DriverVersionHighPart, identifier.DriverVersionLowPart); -#endif - - printfln_err("Creating window with %dx%d ...", width, height); - - if (!_create_window(width, height, &hwnd)) { - IDirect3D9_Release(d3d); - return false; - } - - printfln_err("Creating d3d device %d x %d @ %d hz %s vsync %s ...", - width, - height, - refresh_rate, - windowed ? "windowed" : "fullscreen", - vsync_off ? "off" : "on"); - - if (!_create_d3d_device( - hwnd, - d3d, - width, - height, - refresh_rate, - windowed, - vsync_off, - &device)) { - IDirect3D9_Release(d3d); - DestroyWindow(hwnd); - return false; - } - - printfln_err("Creating font ..."); - - font_height = _get_font_height(height); - - if (!_create_font(device, font_height, &font)) { - IDirect3DDevice9_Release(device); - IDirect3D9_Release(d3d); - DestroyWindow(hwnd); - return false; - } - - text_offset_x = _get_text_offset_x(width); - text_offset_y = _get_text_offset_y(height, font_height); - - // --------------------------------------------------------------------------------------------- - - exit_loop = false; - warm_up_done = false; - - warm_up_frame_count = 0; - frame_count = 0; - - elapsed_us = 0; - total_elapsed_us = 0; - - printfln_err("Warm-up for %d frames ...", total_warm_up_frame_count); - - start_time = time_get_counter(); - - while (warm_up_frame_count + frame_count < total_warm_up_frame_count + total_frame_count) { - // reset when warm-up is done - if (warm_up_frame_count >= total_warm_up_frame_count && !warm_up_done) { - warm_up_done = true; - total_elapsed_us = 0; - printfln_err("Warm-up finished"); - printfln_err("Running test for %d frames ...", total_frame_count); - } - - // Required to not make windows think we are stuck and not responding - while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { - if (msg.message == WM_QUIT) { - exit_loop = true; - break; - } - - TranslateMessage(&msg); - DispatchMessage(&msg); - } - - if (exit_loop) { - break; - } - - if (_is_esc_key_pressed()) { - // Avoid multi triggering with further key evaluations - while (!_is_esc_key_released()) { - Sleep(10); - } - - exit_loop = true; - break; - } - - IDirect3DDevice9_Clear( - device, - 0, - NULL, - D3DCLEAR_TARGET, - D3DCOLOR_XRGB(0, 0, 0), - 1.0f, - 0); - IDirect3DDevice9_BeginScene(device); - - _draw_text(device, font, font_height, text_offset_x, text_offset_y, "D3D9 Monitor Check"); - _draw_text(device, font, font_height, text_offset_x, text_offset_y * 3, - "GPU: %s", identifier.Description); - _draw_text(device, font, font_height, text_offset_x, text_offset_y * 4, - "Spec: %d x %d @ %d hz, %s, vsync %s", width, height, refresh_rate, - windowed ? "windowed" : "fullscreen", vsync_off ? "off" : "on"); - - if (warm_up_frame_count < total_warm_up_frame_count) { - // First frame won't have any data available causing division by zero in the stats - if (warm_up_frame_count != 0) { - _draw_text(device, font, font_height, text_offset_x, text_offset_y * 6, "Status: Warm-up in progress ..."); - - _draw_text(device, font, font_height, text_offset_x, text_offset_y * 7, - "Frame: %d / %d", warm_up_frame_count, total_warm_up_frame_count); - _draw_text(device, font, font_height, text_offset_x, text_offset_y * 8, - "Last frame time: %.3f ms", elapsed_us / 1000.0f); - _draw_text(device, font, font_height, text_offset_x, text_offset_y * 9, - "Avg frame time: %.3f ms", total_elapsed_us / warm_up_frame_count / 1000.0f); - _draw_text(device, font, font_height, text_offset_x, text_offset_y * 10, - "Last refresh rate: %.3f Hz", 1000.0f / (elapsed_us / 1000.0f)); - _draw_text(device, font, font_height, text_offset_x, text_offset_y * 11, - "Avg refresh rate: %.3f Hz", 1000.0f / (total_elapsed_us / warm_up_frame_count / 1000.0f)); - } - } else { - // First frame won't have any data available causing division by zero in the stats - if (frame_count != 0) { - _draw_text(device, font, font_height, text_offset_x, text_offset_y * 6, "Status: Measuring in progress ..."); - - _draw_text(device, font, font_height, text_offset_x, text_offset_y * 7, - "Frame: %d / %d", frame_count, total_frame_count); - _draw_text(device, font, font_height, text_offset_x, text_offset_y * 8, - "Last frame time: %.3f ms", elapsed_us / 1000.0f); - _draw_text(device, font, font_height, text_offset_x, text_offset_y * 9, - "Avg frame time: %.3f ms", total_elapsed_us / frame_count / 1000.0f); - _draw_text(device, font, font_height, text_offset_x, text_offset_y * 10, - "Last refresh rate: %.3f Hz", 1000.0f / (elapsed_us / 1000.0f)); - _draw_text(device, font, font_height, text_offset_x, text_offset_y * 11, - "Avg refresh rate: %.3f Hz", 1000.0f / (total_elapsed_us / frame_count / 1000.0f)); - } - } - - _draw_text(device, font, font_height, text_offset_x, text_offset_y * 13, "Press ESC to exit early"); - - IDirect3DDevice9_EndScene(device); - IDirect3DDevice9_Present(device, NULL, NULL, NULL, NULL); - - end_time = time_get_counter(); - elapsed_us = time_get_elapsed_us(end_time - start_time); - start_time = end_time; - total_elapsed_us += elapsed_us; - - if (warm_up_frame_count < total_warm_up_frame_count) { - warm_up_frame_count++; - } else { - frame_count++; - } - } - - // --------------------------------------------------------------------------------------------- - - printfln_err("Running test finished"); - - IDirect3DDevice9_Clear( - device, - 0, - NULL, - D3DCLEAR_TARGET, - D3DCOLOR_XRGB(0, 0, 0), - 1.0f, - 0); - IDirect3DDevice9_BeginScene(device); - - _draw_text(device, font, font_height, text_offset_x, text_offset_y, "D3D9 Monitor Check"); - _draw_text(device, font, font_height, text_offset_x, text_offset_y * 3, - "GPU: %s", identifier.Description); - _draw_text(device, font, font_height, text_offset_x, text_offset_y * 4, - "Spec: %d x %d @ %d hz, %s, vsync %s", width, height, refresh_rate, - windowed ? "windowed" : "fullscreen", vsync_off ? "off" : "on"); - - if (exit_loop) { - _draw_text(device, font, font_height, text_offset_x, text_offset_y * 6, "Status: Exited early"); - } else { - _draw_text(device, font, font_height, text_offset_x, text_offset_y * 6, "Status: Finished"); - } - - _draw_text(device, font, font_height, text_offset_x, text_offset_y * 7, - "Total warm-up frame count: %d", warm_up_frame_count); - _draw_text(device, font, font_height, text_offset_x, text_offset_y * 8, - "Total sample frame count: %d", frame_count); - _draw_text(device, font, font_height, text_offset_x, text_offset_y * 9, - "Avg frame time: %.3f ms", total_elapsed_us > 0 && frame_count > 0 ? total_elapsed_us / frame_count / 1000.0f : 0); - _draw_text(device, font, font_height, text_offset_x, text_offset_y * 10, - "Avg refresh rate: %.3f Hz", total_elapsed_us > 0 && frame_count > 0 ? 1000.0f / (total_elapsed_us / frame_count / 1000.0f) : 0); - - _draw_text(device, font, font_height, text_offset_x, text_offset_y * 12, "Exiting in %d seconds ...", - results_timeout_seconds); - _draw_text(device, font, font_height, text_offset_x, text_offset_y * 13, "Press ESC to exit immediately"); - - IDirect3DDevice9_EndScene(device); - IDirect3DDevice9_Present(device, NULL, NULL, NULL, NULL); - - exit_loop = false; - - for (uint32_t i = 0; i < results_timeout_seconds * 1000 / 10; i++) { - // Required to not make windows think we are stuck and not responding - while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { - if (msg.message == WM_QUIT) { - exit_loop = true; - break; - } - - TranslateMessage(&msg); - DispatchMessage(&msg); - } - - if (exit_loop) { - break; - } - - // Allow quick exit - if (_is_esc_key_pressed()) { - // Avoid multi triggering with further key evaluations - while (!_is_esc_key_released()) { - Sleep(10); - } - - exit_loop = true; - break; - } - - Sleep(10); - } - - // --------------------------------------------------------------------------------------------- - - printfln_err("Final results"); - printfln_out("GPU: %s", identifier.Description); - printfln_out("Spec: %d x %d @ %d hz, %s, vsync %s", width, height, refresh_rate, - windowed ? "windowed" : "fullscreen", vsync_off ? "off" : "on"); - printfln_out("Avg frame time (ms): %.3f", total_elapsed_us > 0 && frame_count > 0 ? total_elapsed_us / frame_count / 1000.0f : 0); - printfln_out("Avg refresh rate (hz): %.3f", total_elapsed_us > 0 && frame_count > 0 ? 1000.0f / (total_elapsed_us / frame_count / 1000.0f) : 0); - - ID3DXFont_Release(font); - IDirect3DDevice9_Release(device); - IDirect3D9_Release(d3d); - DestroyWindow(hwnd); - - return true; -} - -static bool _cmd_adapter() -{ - return _adapter(); -} - -static bool _cmd_modes() -{ - return _modes(); -} - -static bool _cmd_run(int argc, char **argv) -{ - uint32_t width; - uint32_t height; - uint32_t refresh_rate; - uint32_t warm_up_seconds; - uint32_t total_seconds; - uint32_t results_timeout_seconds; - bool windowed; - bool vsync_off; - - uint32_t total_warm_up_frame_count; - uint32_t total_frame_count; - - if (argc < 3) { - _print_synopsis(); - printfln_err("ERROR: Insufficient arguments"); - return false; - } - - width = atoi(argv[0]); - - if (width == 0 || width > 16384) { - _print_synopsis(); - printfln_err("ERROR: Invalid width: %d", width); - return false; - } - - height = atoi(argv[1]); - - if (height == 0 || height > 16384) { - _print_synopsis(); - printfln_err("ERROR: Invalid height: %d", height); - return false; - } - - refresh_rate = atoi(argv[2]); - - if (refresh_rate == 0 || refresh_rate > 1000) { - _print_synopsis(); - printfln_err("ERROR: Invalid refresh rate: %d", refresh_rate); - return false; - } - - // Sane defaults - warm_up_seconds = 10; - total_seconds = 20; - results_timeout_seconds = 5; - windowed = false; - vsync_off = false; - - for (int i = 3; i < argc; i++) { - if (!strcmp(argv[i], "--warm-up-secs")) { - if (i + 1 < argc) { - warm_up_seconds = atoi(argv[++i]); - - if (warm_up_seconds == 0) { - _print_synopsis(); - printfln_err("ERROR: Invalid warm-up seconds: %d", warm_up_seconds); - return false; - } - } else { - _print_synopsis(); - printfln_err("ERROR: Missing argument for --warm-up-secs"); - return false; - } - } else if (!strcmp(argv[i], "--total-secs")) { - if (i + 1 < argc) { - total_seconds = atoi(argv[++i]); - - if (total_seconds == 0) { - _print_synopsis(); - printfln_err("ERROR: Invalid total seconds: %d", total_seconds); - return false; - } - } else { - _print_synopsis(); - printfln_err("ERROR: Missing argument for --total-secs"); - return false; - } - } else if (!strcmp(argv[i], "--results-timeout-secs")) { - if (i + 1 < argc) { - results_timeout_seconds = atoi(argv[++i]); - } else { - _print_synopsis(); - printfln_err("ERROR: Missing argument for --results-timeout-secs"); - } - } else if (!strcmp(argv[i], "--windowed")) { - windowed = true; - } else if (!strcmp(argv[i], "--vsync-off")) { - vsync_off = true; - } - } - - total_warm_up_frame_count = warm_up_seconds * refresh_rate; - total_frame_count = total_seconds * refresh_rate; - - return _run(width, height, refresh_rate, total_warm_up_frame_count, total_frame_count, results_timeout_seconds, windowed, vsync_off); + printfln_err(" cmdline: Run the tool in command line mode"); + printfln_err(" interactive: Run the tool in interactive mode"); } int main(int argc, char **argv) { const char *command; + bool success; if (argc < 2) { _print_synopsis(); @@ -737,23 +28,13 @@ int main(int argc, char **argv) command = argv[1]; - if (!strcmp(command, "adapter")) { - if (!_cmd_adapter(argc - 2, argv + 2)) { - return 1; - } - } else if (!strcmp(command, "modes")) { - if (!_cmd_modes(argc - 2, argv + 2)) { - return 1; - } - } else if (!strcmp(command, "run")) { - if (!_cmd_run(argc - 2, argv + 2)) { - return 1; - } + if (!strcmp(command, "cmdline")) { + success = cmdline_main(argc - 2, argv + 2); + } else if (!strcmp(command, "interactive")) { + success = interactive_main(argc - 2, argv + 2); } else { - _print_synopsis(argv[0]); - printfln_err("ERROR: Unknown command: %s", command); - return 1; + _print_synopsis(); + success = false; } - return 0; -} + return success ? 0 : 1; diff --git a/src/main/d3d9-monitor-check/menu.c b/src/main/d3d9-monitor-check/menu.c new file mode 100644 index 0000000..0c940af --- /dev/null +++ b/src/main/d3d9-monitor-check/menu.c @@ -0,0 +1,114 @@ +#include + +#include "d3d9-monitor-check/gfx.h" +#include "d3d9-monitor-check/font.h" +#include "d3d9-monitor-check/menu.h" + +#include "util/mem.h" + +typedef struct menu { + gfx_t *gfx; + font_t *font; + menu_item_t current_selected_item; +} menu_t; + +bool menu_init( + gfx_t *gfx, + menu_t **menu) +{ + assert(gfx); + assert(menu); + + *menu = xmalloc(sizeof(menu_t)); + (*menu)->gfx = gfx; + (*menu)->current_selected_item = MENU_ITEM_REFRESH_RATE_TEST; + + if (!font_init(gfx, 20, &(*menu)->font)) { + free(*menu); + return false; + } + + return true; +} + +void menu_select_cursor_move_up(menu_t *menu) +{ + assert(menu); + + if (menu->current_selected_item == MENU_ITEM_REFRESH_RATE_TEST) { + menu->current_selected_item = MENU_ITEM_EXIT; + } else { + menu->current_selected_item--; + } +} + +void menu_select_cursor_move_down(menu_t *menu) +{ + assert(menu); + + if (menu->current_selected_item == MENU_ITEM_EXIT) { + menu->current_selected_item = MENU_ITEM_REFRESH_RATE_TEST; + } else { + menu->current_selected_item++; + } +} + +menu_item_t menu_item_selected_get(menu_t *menu) +{ + assert(menu); + + return menu->current_selected_item; +} + +bool menu_frame_update(menu_t *menu) +{ + font_text_t text; + + assert(menu); + + if (!gfx_frame_begin(menu->gfx)) { + return false; + } + + font_text_begin(menu->font, 10, 10, 10, &text); + font_text_white_draw(&text, "D3D9 Monitor Check - Main Menu"); + font_text_newline(&text); + font_text_newline(&text); + if (menu->current_selected_item == MENU_ITEM_REFRESH_RATE_TEST) { + font_text_white_draw(&text, "-> 1. Refresh Rate Test"); + } else { + font_text_white_draw(&text, " 1. Refresh Rate Test"); + } + font_text_newline(&text); + if (menu->current_selected_item == MENU_ITEM_RESPONSE_TIME_TEST) { + font_text_white_draw(&text, "-> 2. Response Time Test"); + } else { + font_text_white_draw(&text, " 2. Response Time Test"); + } + font_text_newline(&text); + if (menu->current_selected_item == MENU_ITEM_VSYNC_TEST) { + font_text_white_draw(&text, "-> 3. VSync Test"); + } else { + font_text_white_draw(&text, " 3. VSync Test"); + } + font_text_newline(&text); + if (menu->current_selected_item == MENU_ITEM_EXIT) { + font_text_white_draw(&text, "-> 4. Exit"); + } else { + font_text_white_draw(&text, " 4. Exit"); + } + font_text_end(&text); + + gfx_frame_end(menu->gfx); + + return true; +} + +void menu_fini(menu_t *menu) +{ + assert(menu); + + font_fini(menu->font); + + free(menu); +} \ No newline at end of file diff --git a/src/main/d3d9-monitor-check/menu.h b/src/main/d3d9-monitor-check/menu.h new file mode 100644 index 0000000..e8abddb --- /dev/null +++ b/src/main/d3d9-monitor-check/menu.h @@ -0,0 +1,32 @@ +#ifndef D3D9_MONITOR_CHECK_MENU_H +#define D3D9_MONITOR_CHECK_MENU_H + +#include +#include + +#include "d3d9-monitor-check/gfx.h" + +typedef enum MENU_ITEM { + MENU_ITEM_REFRESH_RATE_TEST = 0, + MENU_ITEM_RESPONSE_TIME_TEST = 1, + MENU_ITEM_VSYNC_TEST = 2, + MENU_ITEM_EXIT = 3, +} menu_item_t; + +typedef struct menu menu_t; + +bool menu_init( + gfx_t *gfx, + menu_t **menu); + +void menu_select_cursor_move_up(menu_t *menu); + +void menu_select_cursor_move_down(menu_t *menu); + +menu_item_t menu_item_selected_get(menu_t *menu); + +bool menu_frame_update(menu_t *menu); + +void menu_fini(menu_t *menu); + +#endif \ No newline at end of file diff --git a/src/main/d3d9-monitor-check/print-console.h b/src/main/d3d9-monitor-check/print-console.h new file mode 100644 index 0000000..b0e38b8 --- /dev/null +++ b/src/main/d3d9-monitor-check/print-console.h @@ -0,0 +1,22 @@ +#ifndef PRINT_CONSOLE_H +#define PRINT_CONSOLE_H + +#include + +#include "util/winerr.h" + +#define printf_out(fmt, ...) \ + fprintf(stdout, fmt, ##__VA_ARGS__) +#define printf_err(fmt, ...) \ + fprintf(stderr, fmt, ##__VA_ARGS__) +#define printfln_out(fmt, ...) \ + fprintf(stdout, fmt "\n", ##__VA_ARGS__) +#define printfln_err(fmt, ...) \ + fprintf(stderr, fmt "\n", ##__VA_ARGS__) + +#define printfln_winerr(fmt, ...) \ + char *winerr = util_winerr_format_last_error_code(); \ + fprintf(stderr, fmt ": %s\n", ##__VA_ARGS__, winerr); \ + free(winerr); + +#endif diff --git a/src/main/d3d9-monitor-check/refresh-rate-test.c b/src/main/d3d9-monitor-check/refresh-rate-test.c new file mode 100644 index 0000000..d872514 --- /dev/null +++ b/src/main/d3d9-monitor-check/refresh-rate-test.c @@ -0,0 +1,304 @@ +#include +#include +#include +#include + +#include "d3d9-monitor-check/gfx.h" +#include "d3d9-monitor-check/font.h" +#include "d3d9-monitor-check/refresh-rate-test.h" + +#include "util/mem.h" + +typedef enum refresh_rate_test_phase { + REFRESH_RATE_TEST_PHASE_WARM_UP = 0, + REFRESH_RATE_TEST_PHASE_MEASURE = 1, + REFRESH_RATE_TEST_PHASE_DONE = 2 +} refresh_rate_test_phase_t; + +typedef struct refresh_rate_test_ctx { + gfx_t *gfx; + font_t *font; + uint32_t total_warm_up_frame_count; + uint32_t total_sample_frame_count; +} refresh_rate_test_ctx_t; + +typedef struct refresh_rate_test_state { + gfx_info_t gfx_info; + refresh_rate_test_phase_t phase; + uint64_t last_frame_time_us; + uint32_t warm_up_frame_count; + uint32_t sample_frame_count; + uint64_t warm_up_elapsed_us; + uint64_t sample_elapsed_us; +} refresh_rate_test_state_t; + +typedef struct refresh_rate_test { + refresh_rate_test_ctx_t ctx; + refresh_rate_test_state_t state; +} refresh_rate_test_t; + +static refresh_rate_test_phase_t _refresh_rate_test_phase_evaluate(const refresh_rate_test_t *test) +{ + switch (test->state.phase) { + case REFRESH_RATE_TEST_PHASE_WARM_UP: + if (test->state.warm_up_frame_count < test->ctx.total_warm_up_frame_count) { + return REFRESH_RATE_TEST_PHASE_WARM_UP; + } else { + return REFRESH_RATE_TEST_PHASE_MEASURE; + } + + case REFRESH_RATE_TEST_PHASE_MEASURE: + if (test->state.sample_frame_count < test->ctx.total_sample_frame_count) { + return REFRESH_RATE_TEST_PHASE_MEASURE; + } else { + return REFRESH_RATE_TEST_PHASE_DONE; + } + + case REFRESH_RATE_TEST_PHASE_DONE: + return REFRESH_RATE_TEST_PHASE_DONE; + + default: + assert(0); + return REFRESH_RATE_TEST_PHASE_DONE; + } +} + +bool refresh_rate_test_init( + gfx_t *gfx, + uint32_t total_warm_up_frame_count, + uint32_t total_sample_frame_count, + refresh_rate_test_t **test) +{ + assert(gfx); + assert(test); + + *test = xmalloc(sizeof(refresh_rate_test_t)); + memset(*test, 0, sizeof(refresh_rate_test_t)); + + if (!font_init(gfx, 20, &(*test)->ctx.font)) { + free(*test); + return false; + } + + font_init(gfx, 20, &(*test)->ctx.font); + + (*test)->ctx.gfx = gfx; + (*test)->ctx.total_warm_up_frame_count = total_warm_up_frame_count; + (*test)->ctx.total_sample_frame_count = total_sample_frame_count; + + (*test)->state.phase = REFRESH_RATE_TEST_PHASE_WARM_UP; + (*test)->state.last_frame_time_us = 0; + (*test)->state.warm_up_frame_count = 0; + (*test)->state.sample_frame_count = 0; + (*test)->state.warm_up_elapsed_us = 0; + (*test)->state.sample_elapsed_us = 0; + + gfx_info_get(gfx, &(*test)->state.gfx_info); + + return true; +} + +static bool _refresh_rate_test_phase_warm_up_frame_update(refresh_rate_test_t *test) +{ + font_text_t text; + + assert(test); + + if (!gfx_frame_begin(test->ctx.gfx)) { + return false; + } + + font_text_begin(test->ctx.font, 0, 10, 10, &text); + + font_text_white_draw(&text, "Refresh Rate Test"); + font_text_newline(&text); + font_text_white_draw(&text, "GPU: %s", test->state.gfx_info.adapter_identifier); + font_text_newline(&text); + font_text_white_draw(&text, "Spec: %d x %d @ %d hz, %s, vsync %s", test->state.gfx_info.width, test->state.gfx_info.height, test->state.gfx_info.refresh_rate, + test->state.gfx_info.windowed ? "windowed" : "fullscreen", test->state.gfx_info.vsync ? "on" : "off"); + font_text_newline(&text); + font_text_newline(&text); + + // First frame won't have any data available causing division by zero in the stats + if (test->state.warm_up_frame_count != 0) { + font_text_white_draw(&text, "Status: Warm-up in progress ..."); + font_text_newline(&text); + + font_text_white_draw(&text, "Frame: %d / %d", test->state.warm_up_frame_count, test->ctx.total_warm_up_frame_count); + font_text_newline(&text); + font_text_white_draw(&text, "Last frame time: %.3f ms", test->state.last_frame_time_us / 1000.0f); + font_text_newline(&text); + font_text_white_draw(&text, "Avg frame time: %.3f ms", test->state.warm_up_elapsed_us / test->state.warm_up_frame_count / 1000.0f); + font_text_newline(&text); + font_text_white_draw(&text, "Last refresh rate: %.3f Hz", 1000.0f / (test->state.last_frame_time_us / 1000.0f)); + font_text_newline(&text); + font_text_white_draw(&text, "Avg refresh rate: %.3f Hz", 1000.0f / (test->state.warm_up_elapsed_us / test->state.warm_up_frame_count / 1000.0f)); + font_text_newline(&text); + } + + font_text_newline(&text); + font_text_white_draw(&text, "Press ESC to exit early"); + + font_text_end(&text); + + gfx_frame_end(test->ctx.gfx); + + test->state.last_frame_time_us = gfx_last_frame_time_us_get(test->ctx.gfx); + test->state.warm_up_elapsed_us += test->state.last_frame_time_us; + test->state.warm_up_frame_count++; + + return true; +} + +static bool _refresh_rate_test_phase_measure_frame_update(refresh_rate_test_t *test) +{ + font_text_t text; + + assert(test); + + if (!gfx_frame_begin(test->ctx.gfx)) { + return false; + } + + font_text_begin(test->ctx.font, 0, 10, 10, &text); + + font_text_white_draw(&text, "Refresh Rate Test"); + font_text_newline(&text); + font_text_white_draw(&text, "GPU: %s", test->state.gfx_info.adapter_identifier); + font_text_newline(&text); + font_text_white_draw(&text, "Spec: %d x %d @ %d hz, %s, vsync %s", test->state.gfx_info.width, test->state.gfx_info.height, test->state.gfx_info.refresh_rate, + test->state.gfx_info.windowed ? "windowed" : "fullscreen", test->state.gfx_info.vsync ? "on" : "off"); + font_text_newline(&text); + font_text_newline(&text); + + // First frame won't have any data available causing division by zero in the stats + if (test->state.sample_frame_count != 0) { + font_text_white_draw(&text, "Status: Measuring in progress ..."); + font_text_newline(&text); + + font_text_white_draw(&text, "Frame: %d / %d", test->state.sample_frame_count, test->ctx.total_sample_frame_count); + font_text_newline(&text); + font_text_white_draw(&text, "Last frame time: %.3f ms", test->state.last_frame_time_us / 1000.0f); + font_text_newline(&text); + font_text_white_draw(&text, "Avg frame time: %.3f ms", test->state.sample_elapsed_us / test->state.sample_frame_count / 1000.0f); + font_text_newline(&text); + font_text_white_draw(&text, "Last refresh rate: %.3f Hz", 1000.0f / (test->state.last_frame_time_us / 1000.0f)); + font_text_newline(&text); + font_text_white_draw(&text, "Avg refresh rate: %.3f Hz", 1000.0f / (test->state.sample_elapsed_us / test->state.sample_frame_count / 1000.0f)); + font_text_newline(&text); + } + + font_text_newline(&text); + font_text_white_draw(&text, "Press ESC to exit early"); + + font_text_end(&text); + + gfx_frame_end(test->ctx.gfx); + + test->state.last_frame_time_us = gfx_last_frame_time_us_get(test->ctx.gfx); + test->state.sample_elapsed_us += test->state.last_frame_time_us; + test->state.sample_frame_count++; + + return true; +} + +bool refresh_rate_test_frame_update(refresh_rate_test_t *test) +{ + assert(test); + + test->state.phase = _refresh_rate_test_phase_evaluate(test); + + switch (test->state.phase) { + case REFRESH_RATE_TEST_PHASE_WARM_UP: + return _refresh_rate_test_phase_warm_up_frame_update(test); + + case REFRESH_RATE_TEST_PHASE_MEASURE: + return _refresh_rate_test_phase_measure_frame_update(test); + + case REFRESH_RATE_TEST_PHASE_DONE: + return false; + + default: + assert(0); + return false; + } +} + +void refresh_rate_test_results_get(const refresh_rate_test_t *test, refresh_rate_test_results_t *results) +{ + assert(test); + assert(results); + + results->total_warm_up_frame_count = test->state.warm_up_frame_count; + results->total_sample_frame_count = test->state.sample_frame_count; + + if (test->state.sample_elapsed_us > 0 && test->state.sample_frame_count > 0) { + results->avg_frame_time_ms = test->state.sample_elapsed_us / test->state.sample_frame_count / 1000.0f; + results->avg_refresh_rate_hz = 1000.0f / results->avg_frame_time_ms; + } else { + results->avg_frame_time_ms = 0; + results->avg_refresh_rate_hz = 0; + } +} + +bool refresh_rate_test_results_frame_update(refresh_rate_test_t *test, uint32_t results_timeout_seconds) +{ + refresh_rate_test_results_t results; + font_text_t text; + + assert(test); + + if (!gfx_frame_begin(test->ctx.gfx)) { + return false; + } + + refresh_rate_test_results_get(test, &results); + + font_text_begin(test->ctx.font, 0, 10, 10, &text); + + font_text_white_draw(&text, "Refresh Rate Test"); + font_text_newline(&text); + font_text_white_draw(&text, "GPU: %s", test->state.gfx_info.adapter_identifier); + font_text_newline(&text); + font_text_white_draw(&text, "Spec: %d x %d @ %d hz, %s, vsync %s", test->state.gfx_info.width, test->state.gfx_info.height, test->state.gfx_info.refresh_rate, + test->state.gfx_info.windowed ? "windowed" : "fullscreen", test->state.gfx_info.vsync ? "on" : "off"); + font_text_newline(&text); + font_text_newline(&text); + + if (test->ctx.total_warm_up_frame_count != test->state.warm_up_frame_count || + test->ctx.total_sample_frame_count != test->state.sample_frame_count) { + font_text_white_draw(&text, "Status: Warning, exited early"); + } else { + font_text_white_draw(&text, "Status: Completed"); + } + + font_text_newline(&text); + font_text_white_draw(&text, "Total warm-up frame count: %d", results.total_warm_up_frame_count); + font_text_newline(&text); + font_text_white_draw(&text, "Total sample frame count: %d", results.total_sample_frame_count); + font_text_newline(&text); + font_text_white_draw(&text, "Avg frame time: %.3f ms", results.avg_frame_time_ms); + font_text_newline(&text); + font_text_white_draw(&text, "Avg refresh rate: %.3f Hz", results.avg_refresh_rate_hz); + font_text_newline(&text); + font_text_newline(&text); + + font_text_white_draw(&text, "Exiting in %d seconds ...", results_timeout_seconds); + font_text_newline(&text); + font_text_white_draw(&text, "Press ESC to exit immediately"); + + font_text_end(&text); + + gfx_frame_end(test->ctx.gfx); + + return true; +} + +void refresh_rate_test_fini(refresh_rate_test_t *test) +{ + assert(test); + + font_fini(test->ctx.font); + + free(test); +} \ No newline at end of file diff --git a/src/main/d3d9-monitor-check/refresh-rate-test.h b/src/main/d3d9-monitor-check/refresh-rate-test.h new file mode 100644 index 0000000..31969b8 --- /dev/null +++ b/src/main/d3d9-monitor-check/refresh-rate-test.h @@ -0,0 +1,32 @@ +#ifndef D3D9_MONITOR_CHECK_REFRESH_RATE_TEST_H +#define D3D9_MONITOR_CHECK_REFRESH_RATE_TEST_H + +#include +#include + +#include "d3d9-monitor-check/gfx.h" + +typedef struct refresh_rate_test_results_t { + uint32_t total_warm_up_frame_count; + uint32_t total_sample_frame_count; + double avg_frame_time_ms; + double avg_refresh_rate_hz; +} refresh_rate_test_results_t; + +typedef struct refresh_rate_test refresh_rate_test_t; + +bool refresh_rate_test_init( + gfx_t *gfx, + uint32_t total_warm_up_frame_count, + uint32_t total_sample_frame_count, + refresh_rate_test_t **test); + +bool refresh_rate_test_frame_update(refresh_rate_test_t *test); + +void refresh_rate_test_results_get(const refresh_rate_test_t *test, refresh_rate_test_results_t *results); + +bool refresh_rate_test_results_frame_update(refresh_rate_test_t *test, uint32_t results_timeout_seconds); + +void refresh_rate_test_fini(refresh_rate_test_t *test); + +#endif diff --git a/src/main/d3d9-monitor-check/response-time-test.c b/src/main/d3d9-monitor-check/response-time-test.c new file mode 100644 index 0000000..71333c7 --- /dev/null +++ b/src/main/d3d9-monitor-check/response-time-test.c @@ -0,0 +1,123 @@ +#include + +#include + +#include +#include +#include + +#include "d3d9-monitor-check/gfx.h" + +#include "util/mem.h" +#include "util/time.h" + +#define DEFAULT_SCROLL_SPEED_PX_PER_SEC 200.0f +#define DEFAULT_BLOCK_DISTANCE_PX 50 +#define RECT_WIDTH_PX 80 +#define RECT_HEIGHT_PX 20 + +typedef struct response_time_test { + gfx_t *gfx; + float scroll_speed_px_per_sec; + uint32_t block_distance_px; + float current_y_pos; +} response_time_test_t; + +bool response_time_test_init( + gfx_t *gfx, + response_time_test_t **test) +{ + assert(gfx); + assert(test); + + *test = xmalloc(sizeof(response_time_test_t)); + + (*test)->gfx = gfx; + (*test)->scroll_speed_px_per_sec = DEFAULT_SCROLL_SPEED_PX_PER_SEC; + (*test)->block_distance_px = DEFAULT_BLOCK_DISTANCE_PX; + (*test)->current_y_pos = 0; + + return true; +} + +bool response_time_test_frame_update(response_time_test_t *test) +{ + IDirect3DDevice9 *device; + uint32_t screen_height; + uint32_t screen_width; + float delta_time; + D3DRECT rect_white; + D3DRECT rect_blue; + + assert(test); + + device = gfx_device_get(test->gfx); + screen_width = gfx_width_get(test->gfx); + screen_height = gfx_height_get(test->gfx); + + delta_time = gfx_last_frame_time_us_get(test->gfx) / 1000000.0f; + + // Update position + test->current_y_pos += test->scroll_speed_px_per_sec * delta_time; + + // Loop position when rectangles are off screen + if (test->current_y_pos > screen_height + RECT_HEIGHT_PX) { + test->current_y_pos = -RECT_HEIGHT_PX; + } + + // Calculate rectangle positions + rect_white.x1 = (screen_width / 2) - RECT_WIDTH_PX - 10; + rect_white.x2 = rect_white.x1 + RECT_WIDTH_PX; + rect_white.y1 = (int) test->current_y_pos; + rect_white.y2 = rect_white.y1 + RECT_HEIGHT_PX; + + rect_blue.x1 = (screen_width / 2) + 10; + rect_blue.x2 = rect_blue.x1 + RECT_WIDTH_PX; + rect_blue.y1 = (int) test->current_y_pos + test->block_distance_px; + rect_blue.y2 = rect_blue.y1 + RECT_HEIGHT_PX; + + if (!gfx_frame_begin(test->gfx)) { + return false; + } + + // Background + IDirect3DDevice9_Clear( + device, + 0, + NULL, + D3DCLEAR_TARGET, + D3DCOLOR_XRGB(128, 128, 128), + 1.0f, + 0); + + // White rectangle + IDirect3DDevice9_Clear( + device, + 1, + &rect_white, + D3DCLEAR_TARGET, + D3DCOLOR_XRGB(255, 255, 255), + 1.0f, + 0); + + // Blue rectangle + IDirect3DDevice9_Clear( + device, + 1, + &rect_blue, + D3DCLEAR_TARGET, + D3DCOLOR_XRGB(0, 0, 255), + 1.0f, + 0); + + gfx_frame_end(test->gfx); + + return true; +} + +void response_time_test_fini(response_time_test_t *test) +{ + assert(test); + + free(test); +} diff --git a/src/main/d3d9-monitor-check/response-time-test.h b/src/main/d3d9-monitor-check/response-time-test.h new file mode 100644 index 0000000..e428b8c --- /dev/null +++ b/src/main/d3d9-monitor-check/response-time-test.h @@ -0,0 +1,19 @@ +#ifndef D3D9_MONITOR_CHECK_RESPONSE_TIME_TEST_H +#define D3D9_MONITOR_CHECK_RESPONSE_TIME_TEST_H + +#include +#include + +#include "d3d9-monitor-check/gfx.h" + +typedef struct response_time_test response_time_test_t; + +bool response_time_test_init( + gfx_t *gfx, + response_time_test_t **test); + +bool response_time_test_frame_update(response_time_test_t *test); + +void response_time_test_fini(response_time_test_t *test); + +#endif // D3D9_MONITOR_CHECK_RESPONSE_TIME_TEST_H diff --git a/src/main/d3d9-monitor-check/vsync-test.c b/src/main/d3d9-monitor-check/vsync-test.c new file mode 100644 index 0000000..2fdc8f0 --- /dev/null +++ b/src/main/d3d9-monitor-check/vsync-test.c @@ -0,0 +1,69 @@ +#include +#include +#include + +#include "d3d9-monitor-check/gfx.h" +#include "d3d9-monitor-check/font.h" + +#include "util/mem.h" + +typedef struct vsync_test { + gfx_t *gfx; + font_t *font; +} vsync_test_t; + +bool vsync_test_init( + gfx_t *gfx, + vsync_test_t **test) +{ + assert(gfx); + assert(test); + + *test = xmalloc(sizeof(vsync_test_t)); + memset(*test, 0, sizeof(vsync_test_t)); + + if (!font_init(gfx, 160, &(*test)->font)) { + free(*test); + return false; + } + + (*test)->gfx = gfx; + + return true; +} + +bool vsync_test_frame_update(vsync_test_t *test) +{ + uint64_t frame_count; + font_text_t text; + + assert(test); + + frame_count = gfx_last_frame_count_get(test->gfx); + + if (!gfx_frame_begin(test->gfx)) { + return false; + } + + font_text_begin(test->font, 0, 80, 80, &text); + + if (frame_count % 2 == 0) { + font_text_red_draw(&text, "VSYNC"); + } else { + font_text_cyan_draw(&text, "VSYNC"); + } + + font_text_end(&text); + + gfx_frame_end(test->gfx); + + return true; +} + +void vsync_test_fini(vsync_test_t *test) +{ + assert(test); + + font_fini(test->font); + free(test); +} \ No newline at end of file diff --git a/src/main/d3d9-monitor-check/vsync-test.h b/src/main/d3d9-monitor-check/vsync-test.h new file mode 100644 index 0000000..ad6fd97 --- /dev/null +++ b/src/main/d3d9-monitor-check/vsync-test.h @@ -0,0 +1,19 @@ +#ifndef D3D9_MONITOR_CHECK_VSYNC_TEST_H +#define D3D9_MONITOR_CHECK_VSYNC_TEST_H + +#include +#include + +#include "d3d9-monitor-check/gfx.h" + +typedef struct vsync_test vsync_test_t; + +bool vsync_test_init( + gfx_t *gfx, + vsync_test_t **test); + +bool vsync_test_frame_update(vsync_test_t *test); + +void vsync_test_fini(vsync_test_t *test); + +#endif // D3D9_MONITOR_CHECK_VSYNC_TEST_H