mirror of
https://github.com/djhackersdev/bemanitools.git
synced 2026-03-21 17:44:15 -05:00
feat: Extend monitor check tool, vsync and response time tests (#331)
Adding two more tests that have been proven very useful for debugging monitor issues that caused problems with IIDX. The vsync test flickers a text snippet between red and cyan on every even and uneven frame. If vsync works correctly, the text appears grey to the human eye. If there are issues like re-displaying a previous frame or a dropped frame, this appears as the text rendering red or cyan. The response time test scrolls two simple blocks/lines vertically to check if they creating ghosting/smearing on the display. The tool now supports an interactive and command line mode. Interactive mode is useful for running different tests on the setup while the command line mode can be used to ran these tests with fixed parameters and for a fixed amount of time, e.g. in a shell script, for automation purpose. The tool still has a few obvious imperfections like scaling issues on different resolutions. Considering the basic functionality is given, further improvements can follow in future iterations.
This commit is contained in:
parent
e0ff83f664
commit
d781905d83
|
|
@ -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
|
||||
|
|
|
|||
534
src/main/d3d9-monitor-check/cmdline.c
Normal file
534
src/main/d3d9-monitor-check/cmdline.c
Normal file
|
|
@ -0,0 +1,534 @@
|
|||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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 <command> <args>");
|
||||
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 <width> <height> <refresh_rate> [--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 <width> <height> <refresh_rate> [--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 <width> <height> <refresh_rate> [--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;
|
||||
}
|
||||
}
|
||||
8
src/main/d3d9-monitor-check/cmdline.h
Normal file
8
src/main/d3d9-monitor-check/cmdline.h
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#ifndef D3D9_MONITOR_CHECK_CMDLINE_H
|
||||
#define D3D9_MONITOR_CHECK_CMDLINE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
bool cmdline_main(int argc, char **argv);
|
||||
|
||||
#endif
|
||||
207
src/main/d3d9-monitor-check/font.c
Normal file
207
src/main/d3d9-monitor-check/font.c
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
#include <windows.h>
|
||||
|
||||
#include <d3dx9.h>
|
||||
#include <d3dx9core.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#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));
|
||||
}
|
||||
37
src/main/d3d9-monitor-check/font.h
Normal file
37
src/main/d3d9-monitor-check/font.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#ifndef D3D9_MONITOR_CHECK_FONT_H
|
||||
#define D3D9_MONITOR_CHECK_FONT_H
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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
|
||||
430
src/main/d3d9-monitor-check/gfx.c
Normal file
430
src/main/d3d9-monitor-check/gfx.c
Normal file
|
|
@ -0,0 +1,430 @@
|
|||
#include <windows.h>
|
||||
|
||||
#include <d3d9.h>
|
||||
#include <d3dx9core.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
59
src/main/d3d9-monitor-check/gfx.h
Normal file
59
src/main/d3d9-monitor-check/gfx.h
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
#ifndef D3D9_MONITOR_CHECK_GFX_H
|
||||
#define D3D9_MONITOR_CHECK_GFX_H
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <d3d9.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
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
|
||||
90
src/main/d3d9-monitor-check/input.c
Normal file
90
src/main/d3d9-monitor-check/input.c
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
#include <windows.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
26
src/main/d3d9-monitor-check/input.h
Normal file
26
src/main/d3d9-monitor-check/input.h
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#ifndef D3D9_MONITOR_CHECK_INPUT_H
|
||||
#define D3D9_MONITOR_CHECK_INPUT_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
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
|
||||
196
src/main/d3d9-monitor-check/interactive.c
Normal file
196
src/main/d3d9-monitor-check/interactive.c
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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 <width> <height> <refresh-rate> [--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);
|
||||
}
|
||||
8
src/main/d3d9-monitor-check/interactive.h
Normal file
8
src/main/d3d9-monitor-check/interactive.h
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#ifndef D3D9_MONITOR_CHECK_INTERACTIVE_H
|
||||
#define D3D9_MONITOR_CHECK_INTERACTIVE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
bool interactive_main(int argc, char **argv);
|
||||
|
||||
#endif
|
||||
|
|
@ -1,733 +1,24 @@
|
|||
#include <windows.h>
|
||||
|
||||
#include <d3d9.h>
|
||||
#include <d3dx9core.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#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 <command> <args>");
|
||||
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 <width> <height> <refresh_rate> [--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;
|
||||
|
|
|
|||
114
src/main/d3d9-monitor-check/menu.c
Normal file
114
src/main/d3d9-monitor-check/menu.c
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
#include <assert.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
32
src/main/d3d9-monitor-check/menu.h
Normal file
32
src/main/d3d9-monitor-check/menu.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#ifndef D3D9_MONITOR_CHECK_MENU_H
|
||||
#define D3D9_MONITOR_CHECK_MENU_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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
|
||||
22
src/main/d3d9-monitor-check/print-console.h
Normal file
22
src/main/d3d9-monitor-check/print-console.h
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#ifndef PRINT_CONSOLE_H
|
||||
#define PRINT_CONSOLE_H
|
||||
|
||||
#include <stdio.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);
|
||||
|
||||
#endif
|
||||
304
src/main/d3d9-monitor-check/refresh-rate-test.c
Normal file
304
src/main/d3d9-monitor-check/refresh-rate-test.c
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
32
src/main/d3d9-monitor-check/refresh-rate-test.h
Normal file
32
src/main/d3d9-monitor-check/refresh-rate-test.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#ifndef D3D9_MONITOR_CHECK_REFRESH_RATE_TEST_H
|
||||
#define D3D9_MONITOR_CHECK_REFRESH_RATE_TEST_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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
|
||||
123
src/main/d3d9-monitor-check/response-time-test.c
Normal file
123
src/main/d3d9-monitor-check/response-time-test.c
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
#include <windows.h>
|
||||
|
||||
#include <d3d9.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
19
src/main/d3d9-monitor-check/response-time-test.h
Normal file
19
src/main/d3d9-monitor-check/response-time-test.h
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#ifndef D3D9_MONITOR_CHECK_RESPONSE_TIME_TEST_H
|
||||
#define D3D9_MONITOR_CHECK_RESPONSE_TIME_TEST_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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
|
||||
69
src/main/d3d9-monitor-check/vsync-test.c
Normal file
69
src/main/d3d9-monitor-check/vsync-test.c
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
19
src/main/d3d9-monitor-check/vsync-test.h
Normal file
19
src/main/d3d9-monitor-check/vsync-test.h
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#ifndef D3D9_MONITOR_CHECK_VSYNC_TEST_H
|
||||
#define D3D9_MONITOR_CHECK_VSYNC_TEST_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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
|
||||
Loading…
Reference in New Issue
Block a user