feat: Extend monitor check tool, vsync and response time tests #331
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user