feat: Extend monitor check tool, vsync and response time tests #331

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