Compare commits

..

6 Commits

Author SHA1 Message Date
icex2
8b82b32894 feat: Integrate frame time performance graph into iidxhook 1-8
Integrate the previously created frame time graph component
into iidxhook 1 to 8. iidxhook9 uses d3d9ex which still requires
a separate hook implementation for imgui.

Guard the feature with a switch as it might have some performance
impact and it’s something that is only required to be turned on
when needed.
2025-02-07 15:19:02 +01:00
icex2
8efeb68c75 feat: Add frame time performance graph imgui overlay
A overlay window showing the a frame time graph with
the current frame time, ~10 seconds of history as well
as reference lines for the current avg. and a configurable
target frame time.

This is considered a debug tool to support in any efforts
that are related to understanding current frame times
of the games. More specifically this can be used to asses
impact of any bemanitools hooking to the game’s main
(render) loop.
2025-02-07 15:14:25 +01:00
icex2
d482acd533 feat: Add bt d3d9 hook specific imgui overlay
For now, we focus on internal overlays for bemanitools
that hook into an existing d3d9 context. The current
abstraction is fairly thin and should be fine as a start.

Implementations are called “components” and hook
up with a single frame_update function to execute
and logic and drawing updates with imgui
2025-02-07 15:12:21 +01:00
icex2
8d49bd8f40 fix: Add missing hook_table_revert function
The function signature was already defined in the header
but the implementation in the module missing
2025-02-07 15:10:31 +01:00
icex2
d977a0bf35 feat: Integrate imgui library, squash to initial integration
Version 1.91.7 of the imgui library with the cimgui wrapper.

imgui allows us to easily create minimal and powerful UI for
use-cases like in-game overlays or separate tooling.

The additional wrappers cimgui_impl_dx9 and cimgui_impl_win32
were added to provide a full C linkage integration for the rest
of the code base.

Tweaks to the makefile were kept to a minimum but enable
compilation of C++ since imgui is C++ based.

Remark: At this point bemanitools itself is still to be kept a pure
C codebase. Due to the lack of proper module/library management
with the current build system in bemanitools 5, proper separation
of concerns and clean integration with external libraries isn’t
possible with reasonable effort.


Summary:

Test Plan:
2025-02-07 15:10:31 +01:00
icex2
15a1762837 feat: nvgpu tool for tweaking nvidia gpu driver settings
An open source re-implementation of the “NvDisplayConfigLDJ"
tool with additional enhancements.

This can be used to tweak your nvidia GPU driver settings to
create custom display timings to address IIDX’s requirement
if expecting proper display timings. This can also be used for
any legacy IIDX versions that even expect very specific display
timings, e.g. 59.95 or 60.05 hz.

Furthermore, creating application profiles allows further tweaks
to important GPU settings such as the current performance mode
setting. This is crucial to ensure the GPU is not going into any
kind of power saving states which results in non-smooth
scrolling during gameplay and micro stuttering that cannot
be measured on application level.
2025-02-07 13:00:29 +01:00
145 changed files with 1776 additions and 5974 deletions

View File

@@ -30,7 +30,7 @@ jobs:
cd ..
- name: "Upload artifact"
uses: "actions/upload-artifact@v4"
uses: "actions/upload-artifact@v2"
with:
name: bemanitools-${{ github.sha }}
retention-days: 90

View File

@@ -35,7 +35,7 @@ jobs:
cd ..
- name: "Upload artifact"
uses: "actions/upload-artifact@v4"
uses: "actions/upload-artifact@v2"
with:
name: bemanitools-${{ github.sha }}
retention-days: 90
@@ -49,7 +49,7 @@ jobs:
steps:
# This already extracts the contents of the artifact
- name: "Download artifact"
uses: "actions/download-artifact@v4"
uses: "actions/download-artifact@v2"
with:
name: bemanitools-${{ github.sha }}

View File

@@ -7,27 +7,8 @@ given in tags. The pipeline will pick this up and cuts out the relevant section
### Features
- Major overhaul of [IIDX sync and performance settings](doc/iidxhook/iidx-syncbook.md)
documentation. Supports users in configuring their systems to run any IIDX version with proper
display timings, smooth frame rates and sync game-play
- Add `iidxio-async.dll` wrapper/shim library to drive another iidxio in a dedicated IO thread, see
[documentation](doc/iidxhook/iidxio-async.md) for details
- Add `nvgpu` tool to configure NVIDIA GPUs and create a custom timing profile, see
[documentation](doc/tools/nvgpu.md) for details
- Add `d3d9-frame-graph-hook.dll` that can be added as a hook to
either `launcher.exe` or `inject.exe` to display a graph of the frame times of the game's render
for debugging and performance analysis
- feat: Add `d3d9-monitor-check` tool to run different music game relevant monitor tests including
a re-implementation of the infamous IIDX “monitor check” as a separate tool for testing and
debugging. See [documentation](doc/tools/d3d9-monitor-check.md) for details
### Fixes
- iidxhook9: Work around reverb issue from Windows 26100.3476
- iidxhook9: fix camhook for new style camera detection
- `cam.port_layout` added to .conf files of iidx 27 to 30
- In general, a lot of internal cleanup
## 5.48
### Features

View File

@@ -1,21 +0,0 @@
FROM --platform=amd64 debian:11.6-slim@sha256:f7d141c1ec6af549958a7a2543365a7829c2cdc4476308ec2e182f8a7c59b519
LABEL description="Development environment for bemanitools"
# mingw-w64-gcc has 32-bit and 64-bit toolchains
RUN apt-get update && apt-get install -y --no-install-recommends \
mingw-w64 \
mingw-w64-common \
make \
zip \
git \
clang-format \
python3-pip \
&& rm -rf /var/lib/apt/lists/*
RUN pip3 install mdformat
RUN mkdir /bemanitools
WORKDIR /bemanitools
ENV SHELL /bin/bash

View File

@@ -13,10 +13,8 @@ BUILDDIR ?= build
builddir_docker := $(BUILDDIR)/docker
docker_build_container_name := "bemanitools-build"
docker_build_image_name := "bemanitools-build:latest"
docker_dev_container_name := "bemanitools-dev"
docker_dev_image_name := "bemanitools-dev:latest"
docker_container_name := "bemanitools-build"
docker_image_name := "bemanitools-build:latest"
depdir := $(BUILDDIR)/dep
objdir := $(BUILDDIR)/obj
@@ -43,7 +41,6 @@ FORCE:
.PHONY: \
build-docker \
dev-docker \
clean \
code-format \
doc-format \
@@ -92,38 +89,21 @@ version:
$(V)echo "$(gitrev)" > version
build-docker:
$(V)docker rm -f $(docker_build_container_name) 2> /dev/null || true
$(V)docker rm -f $(docker_container_name) 2> /dev/null || true
$(V)docker \
build \
-t $(docker_build_image_name) \
-f Dockerfile.build \
-t $(docker_image_name) \
-f Dockerfile \
.
$(V)docker \
run \
--volume $(shell pwd):/bemanitools \
--name $(docker_build_container_name) \
$(docker_build_image_name)
dev-docker:
$(V)docker rm -f $(docker_dev_container_name) 2> /dev/null || true
$(V)docker \
build \
-t $(docker_dev_image_name) \
-f Dockerfile.dev \
.
$(V)docker \
run \
--interactive \
--tty \
--volume $(shell pwd):/bemanitools \
--name $(docker_dev_container_name) \
$(docker_dev_image_name)
--name $(docker_container_name) \
$(docker_image_name)
clean-docker:
$(V)docker rm -f $(docker_dev_container_name) || true
$(V)docker image rm -f $(docker_dev_image_name) || true
$(V)docker rm -f $(docker_build_container_name) || true
$(V)docker image rm -f $(docker_build_image_name) || true
$(V)docker rm -f $(docker_container_name) || true
$(V)docker image rm -f $(docker_image_name) || true
$(V)rm -rf $(BUILDDIR)
#

View File

@@ -100,8 +100,6 @@ include src/main/bstio/Module.mk
include src/main/camhook/Module.mk
include src/main/cconfig/Module.mk
include src/main/config/Module.mk
include src/main/d3d9-frame-graph-hook/Module.mk
include src/main/d3d9-monitor-check/Module.mk
include src/main/d3d9-util/Module.mk
include src/main/d3d9exhook/Module.mk
include src/main/ddrhook-util/Module.mk
@@ -156,7 +154,6 @@ include src/main/iidxhook6/Module.mk
include src/main/iidxhook7/Module.mk
include src/main/iidxhook8/Module.mk
include src/main/iidxhook9/Module.mk
include src/main/iidxio-async/Module.mk
include src/main/iidxio-bio2/Module.mk
include src/main/iidxio-ezusb/Module.mk
include src/main/iidxio-ezusb2/Module.mk
@@ -241,8 +238,6 @@ $(zipdir)/tools.zip: \
build/bin/indep-32/ezusb2-tool.exe \
build/bin/indep-32/ezusb-tool.exe \
build/bin/indep-32/nvgpu.exe \
build/bin/indep-32/d3d9-frame-graph-hook.dll \
build/bin/indep-32/d3d9-monitor-check.exe \
| $(zipdir)/
$(V)echo ... $@
$(V)zip -j $@ $^
@@ -258,8 +253,6 @@ $(zipdir)/tools-x64.zip: \
build/bin/indep-64/jbiotest.exe \
build/bin/indep-64/mempatch-hook.dll \
build/bin/indep-64/nvgpu.exe \
build/bin/indep-64/d3d9-frame-graph-hook.dll \
build/bin/indep-64/d3d9-monitor-check.exe \
| $(zipdir)/
$(V)echo ... $@
$(V)zip -j $@ $^
@@ -306,7 +299,7 @@ $(zipdir)/iidx-13.zip: \
$(V)zip -j $@ $^
$(zipdir)/iidx-14-to-17.zip: \
build/bin/avs2_803-32/iidxhook3.dll \
build/bin/avs2_0-32/iidxhook3.dll \
build/bin/indep-32/config.exe \
build/bin/indep-32/eamio.dll \
build/bin/indep-32/geninput.dll \
@@ -445,7 +438,7 @@ $(zipdir)/iidx-25-to-26.zip: \
$(V)echo ... $@
$(V)zip -j $@ $^
$(zipdir)/iidx-27-to-31.zip: \
$(zipdir)/iidx-27-to-30.zip: \
build/bin/avs2_1700-64/iidxhook9.dll \
build/bin/avs2_1700-64/launcher.exe \
build/bin/indep-64/config.exe \
@@ -458,12 +451,10 @@ $(zipdir)/iidx-27-to-31.zip: \
dist/iidx/gamestart-28.bat \
dist/iidx/gamestart-29.bat \
dist/iidx/gamestart-30.bat \
dist/iidx/gamestart-31.bat \
dist/iidx/iidxhook-27.conf \
dist/iidx/iidxhook-28.conf \
dist/iidx/iidxhook-29.conf \
dist/iidx/iidxhook-30.conf \
dist/iidx/iidxhook-31.conf \
dist/iidx/vefx.txt \
| $(zipdir)/
$(V)echo ... $@
@@ -472,7 +463,6 @@ $(zipdir)/iidx-27-to-31.zip: \
$(zipdir)/iidx-hwio-x86.zip: \
build/bin/indep-32/aciomgr.dll \
build/bin/indep-32/eamio-icca.dll \
build/bin/indep-32/iidxio-async.dll \
build/bin/indep-32/iidxio-bio2.dll \
build/bin/indep-32/iidxio-ezusb.dll \
build/bin/indep-32/iidxio-ezusb2.dll \
@@ -486,7 +476,6 @@ $(zipdir)/iidx-hwio-x86.zip: \
$(zipdir)/iidx-hwio-x64.zip: \
build/bin/indep-64/aciomgr.dll \
build/bin/indep-64/eamio-icca.dll \
build/bin/indep-64/iidxio-async.dll \
build/bin/indep-64/iidxio-bio2.dll \
build/bin/indep-64/iidxio-ezusb.dll \
build/bin/indep-64/iidxio-ezusb2.dll \
@@ -728,8 +717,6 @@ $(zipdir)/ddr-14-to-18.zip: \
build/bin/indep-32/eamio.dll \
build/bin/indep-32/geninput.dll \
dist/ddr/config.bat \
dist/ddr/gamestart-17.bat \
dist/ddr/gamestart-18.bat \
dist/ddr/gamestart-14.bat \
dist/ddr/gamestart-15.bat \
dist/ddr/gamestart-16.bat \
@@ -748,8 +735,6 @@ $(zipdir)/ddr-16-to-18-x64.zip: \
build/bin/indep-64/eamio.dll \
build/bin/indep-64/geninput.dll \
dist/ddr/config.bat \
dist/ddr/gamestart-17.bat \
dist/ddr/gamestart-18.bat \
dist/ddr/gamestart-16.bat \
dist/ddr/gamestart-17.bat \
dist/ddr/gamestart-18.bat \
@@ -869,7 +854,7 @@ $(BUILDDIR)/bemanitools.zip: \
$(zipdir)/iidx-20-cn.zip \
$(zipdir)/iidx-21-to-24.zip \
$(zipdir)/iidx-25-to-26.zip \
$(zipdir)/iidx-27-to-31.zip \
$(zipdir)/iidx-27-to-30.zip \
$(zipdir)/iidx-hwio-x86.zip \
$(zipdir)/iidx-hwio-x64.zip \
$(zipdir)/jb-01.zip \

View File

@@ -71,7 +71,6 @@ The following games are supported with their corresponding hook-libraries.
- Beatmania IIDX 28 BISTROVER (`iidx-27-to-30.zip`): [iidxhook9](doc/iidxhook/iidxhook9.md)
- Beatmania IIDX 29 CASTHOUR (`iidx-27-to-30.zip`): [iidxhook9](doc/iidxhook/iidxhook9.md)
- Beatmania IIDX 30 RESIDENT (`iidx-27-to-30.zip`): [iidxhook9](doc/iidxhook/iidxhook9.md)
- Beatmania IIDX 31 EPOLIS (`iidx-27-to-31.zip`): [iidxhook9](doc/iidxhook/iidxhook9.md)
- [jubeat](doc/jbhook/README.md)
- jubeat (`jb-01.zip`): [jbhook1](doc/jbhook/jbhook1.md)
- jubeat ripples (`jb-02.zip`): [jbhook1](doc/jbhook/jbhook1.md)
@@ -120,8 +119,6 @@ The following games are supported with their corresponding hook-libraries.
- [extiotest](doc/tools/extiotest.md)
- [aciotest](doc/tools/aciotest.md): Command line tool to quickly test ACIO devices
- config: UI input/output configuration tool when using the default bemanitools API (geninput)
- [d3d9-monitor-check](doc/tools/d3d9-monitor-check.md): Command line tool to check the monitor refresh rate of the
current GPU + monitor configuration
- ir-beat-patch-9/10: Patch the IR beat phase on IIDX 9 and 10
- [mempatch-hook](doc/tools/mempatch-hook.md): Patch raw memory locations in the target process
based on the provided configuration

View File

@@ -1,16 +0,0 @@
@echo off
cd /d %~dp0
if not exist dev mkdir dev
if not exist dev\e mkdir dev\e
if not exist dev\g mkdir dev\g
if not exist dev\nvram mkdir dev\nvram
if not exist dev\raw mkdir dev\raw
if not exist dev\raw\log mkdir dev\raw\log
if not exist dev\raw\fscache mkdir dev\raw\fscache
for /R prop\defaults %%D in (*.*) do (
if not exist dev\nvram\%%~nxD copy /y prop\defaults\%%~nxD dev\nvram
)
modules\launcher -H 134217728 -B iidxhook9.dll bm2dx.dll --config iidxhook-31.conf %*

View File

@@ -1,3 +1,6 @@
# Enable frame performance graph overlay
debug.enable_frame_perf_graph=false
# Magnetic card type, format XXX, 3 digit string (supports: C02, D01, E11, ECO)
eamuse.card_type=C02

View File

@@ -1,3 +1,6 @@
# Enable frame performance graph overlay
debug.enable_frame_perf_graph=false
# Magnetic card type, format XXX, 3 digit string (supports: C02, D01, E11, ECO)
eamuse.card_type=D01

View File

@@ -1,3 +1,6 @@
# Enable frame performance graph overlay
debug.enable_frame_perf_graph=false
# Magnetic card type, format XXX, 3 digit string (supports: C02, D01, E11, ECO)
eamuse.card_type=E11

View File

@@ -1,3 +1,6 @@
# Enable frame performance graph overlay
debug.enable_frame_perf_graph=false
# Magnetic card type, format XXX, 3 digit string (supports: C02, D01, E11, ECO)
eamuse.card_type=ECO

View File

@@ -1,3 +1,6 @@
# Enable frame performance graph overlay
debug.enable_frame_perf_graph=false
# Magnetic card type, format XXX, 3 digit string (supports: C02, D01, E11, ECO)
eamuse.card_type=C02

View File

@@ -1,3 +1,6 @@
# Enable frame performance graph overlay
debug.enable_frame_perf_graph=false
# URL (e.g. http://my.eamuse.server:80/whatever) or IPV4 (e.g. 127.0.0.1:80) of the target eamuse server. The port is optional but defaults to 80.
eamuse.server=localhost:80

View File

@@ -1,3 +1,6 @@
# Enable frame performance graph overlay
debug.enable_frame_perf_graph=false
# URL (e.g. http://my.eamuse.server:80/whatever) or IPV4 (e.g. 127.0.0.1:80) of the target eamuse server. The port is optional but defaults to 80.
eamuse.server=localhost:80

View File

@@ -1,3 +1,6 @@
# Enable frame performance graph overlay
debug.enable_frame_perf_graph=false
# URL (e.g. http://my.eamuse.server:80/whatever) or IPV4 (e.g. 127.0.0.1:80) of the target eamuse server. The port is optional but defaults to 80.
eamuse.server=localhost:80

View File

@@ -1,3 +1,6 @@
# Enable frame performance graph overlay
debug.enable_frame_perf_graph=false
# URL (e.g. http://my.eamuse.server:80/whatever) or IPV4 (e.g. 127.0.0.1:80) of the target eamuse server. The port is optional but defaults to 80.
eamuse.server=localhost:80

View File

@@ -1,3 +1,6 @@
# Enable frame performance graph overlay
debug.enable_frame_perf_graph=false
# PCBID
eamuse.pcbid=0101020304050607086F

View File

@@ -1,3 +1,6 @@
# Enable frame performance graph overlay
debug.enable_frame_perf_graph=false
# Run the game in a framed window (requires windowed option)
gfx.framed=false

View File

@@ -1,3 +1,6 @@
# Enable frame performance graph overlay
debug.enable_frame_perf_graph=false
# Run the game in a framed window (requires windowed option)
gfx.framed=false

View File

@@ -1,3 +1,6 @@
# Enable frame performance graph overlay
debug.enable_frame_perf_graph=false
# PCBID
eamuse.pcbid=0101020304050607086F

View File

@@ -1,3 +1,6 @@
# Enable frame performance graph overlay
debug.enable_frame_perf_graph=false
# Run the game in a framed window (requires windowed option)
gfx.framed=false

View File

@@ -1,3 +1,6 @@
# Enable frame performance graph overlay
debug.enable_frame_perf_graph=false
# Run the game in a framed window (requires windowed option)
gfx.framed=false

View File

@@ -1,3 +1,6 @@
# Enable frame performance graph overlay
debug.enable_frame_perf_graph=false
# Run the game in a framed window (requires windowed option)
gfx.framed=false

View File

@@ -1,3 +1,6 @@
# Enable frame performance graph overlay
debug.enable_frame_perf_graph=false
# Run the game in a framed window (requires windowed option)
gfx.framed=false

View File

@@ -1,3 +1,6 @@
# Enable frame performance graph overlay
debug.enable_frame_perf_graph=false
# Run the game in a framed window (requires windowed option)
gfx.framed=false

View File

@@ -1,3 +1,6 @@
# Enable frame performance graph overlay
debug.enable_frame_perf_graph=false
# Run the game in a framed window (requires windowed option)
gfx.framed=false

View File

@@ -1,3 +1,6 @@
# Enable frame performance graph overlay
debug.enable_frame_perf_graph=false
# Run the game in a framed window (requires windowed option)
gfx.framed=false

View File

@@ -22,9 +22,6 @@ io.tt_multiplier=1.0
# Disables the camera emulation
cam.disable_emu=true
# Camera port layout (0 = LDJ, 1 = CLDJ/TDJ-JA, 2 = TDJ-JB)
cam.port_layout=1
# Disable camera 1. Use, i.e., if you only have one camera and want it to be mapped to camera 2 ingame.
cam.disable_camera1=false

View File

@@ -22,9 +22,6 @@ io.tt_multiplier=1.0
# Disables the camera emulation
cam.disable_emu=true
# Camera port layout (0 = LDJ, 1 = CLDJ/TDJ-JA, 2 = TDJ-JB)
cam.port_layout=1
# Disable camera 1. Use, i.e., if you only have one camera and want it to be mapped to camera 2 ingame.
cam.disable_camera1=false

View File

@@ -22,9 +22,6 @@ io.tt_multiplier=1.0
# Disables the camera emulation
cam.disable_emu=true
# Camera port layout (0 = LDJ, 1 = CLDJ/TDJ-JA, 2 = TDJ-JB)
cam.port_layout=1
# Disable camera 1. Use, i.e., if you only have one camera and want it to be mapped to camera 2 ingame.
cam.disable_camera1=true

View File

@@ -22,9 +22,6 @@ io.tt_multiplier=1.0
# Disables the camera emulation
cam.disable_emu=true
# Camera port layout (0 = LDJ, 1 = CLDJ/TDJ-JA, 2 = TDJ-JB)
cam.port_layout=1
# Disable camera 1. Use, i.e., if you only have one camera and want it to be mapped to camera 2 ingame.
cam.disable_camera1=true

View File

@@ -1,84 +0,0 @@
# Disable card reader emulation and enable usage of real card reader hardware on COM1 (for games supporting slotted readers)
io.disable_card_reader_emu=false
# Disable BIO2 emulation and enable usage of real BIO2 hardware
io.disable_bio2_emu=false
# Disables the poll limiter, warning very high CPU usage may arise
io.disable_poll_limiter=false
# Lightning cab mode (requires additional IO emulation)
io.lightning_mode=false
# Disable camera connection
io.disable_cams=true
# Disables the built in file hooks, requiring manual file creation
io.disable_file_hooks=false
# Turntable sensitivity multiplier (1.0 is default)
io.tt_multiplier=1.0
# Disables the camera emulation
cam.disable_emu=true
# Disable camera 1. Use, i.e., if you only have one camera and want it to be mapped to camera 2 ingame.
cam.disable_camera1=true
# Disable camera 2. Use, i.e., if you have more than one camera but only want to map camera 1 ingame.
cam.disable_camera2=false
# Override camera device ID detection (copy from device manager, do not escape)
cam.device_id1=
# Override camera device ID detection (copy from device manager, do not escape)
cam.device_id2=
# Run the game in a framed window (requires windowed option)
gfx.framed=true
# Run the game windowed
gfx.windowed=false
# Confine mouse cursor to window
gfx.confined=false
# Windowed width, -1 for default size
gfx.window_width=-1
# Windowed height, -1 for default size
gfx.window_height=-1
# Windowed X, -1 for default X position
gfx.window_x=-1
# Windowed Y, -1 for default Y position
gfx.window_y=-1
# Forced refresh rate, -1 to not force any (try 59 or 60 if monitor check fails to lock on high refresh rate monitors)
gfx.forced_refresh_rate=-1
# D3D9ex device adapter (monitor), -1 to use default, 0, 1, 2 etc. to use specified adapter
gfx.device_adapter=-1
# Orientation to force monitor into, -1 to use default, 0, 1, 2, 3 to do 0, 90, 180, 270 degrees
gfx.force_orientation=-1
# Force a screen resolution (width), -1 to disable. Use this if the game does not auto detect your monitor's resolution properly, e.g. 1368x768 instead of 1280x720.
gfx.force_screen_res.width=-1
# Force a screen resolution (height), -1 to disable. Use this if the game does not auto detect your monitor's resolution properly, e.g. 1368x768 instead of 1280x720.
gfx.force_screen_res.height=-1
# IP of adapter to force override with
adapter.override_ip=
# Force ASIO audio mode/device (if applicable / TDJ default)
asio.force_asio=false
# Force WASAPI audio mode (if applicable / LDJ default)
asio.force_wasapi=true
# The ASIO device name to use
asio.device_name=XONAR SOUND CARD(64)

View File

@@ -1,319 +0,0 @@
# IIDX engine overview and how game-play is impacted by hardware and software
Date: 2025-02-09 Author: icex2
This document provides information and explains the following:
* A sort-of taxonomy of the different major IIDX game engine versions
* How the core game-play part of the engine works regarding
* Threading model
* Main render loop
* Timing and synchronization
The goal is to capture key knowledge about how the game's engine works to enable a better
understanding for developing bemanitools and how certain features can impact performance and
synchronization during game-play.
This document is not claiming to be complete or 100% accurate. It is based on my own personal
research and understanding of the IIDX game engine. It is very likely that some of the information
is wrong or misunderstood. Feel free to raise any questions or concerns in an issue as I want
this document to be as accurate as possible.
## Taxonomy of IIDX game engine versions
With every version, there have been numerous changes and improvements to the game engine. The
following is a rough taxonomy that is focused on cabinet and hardware changes. As these are likely
the main driver for key changes to the software and game engine, they create fairly distinct
stages throughout the different versions of the game. The key attributes to be considered here are:
* Main PCB
* IO board
* Monitor
All of these had significant impact on key software features appearing or changing.
### Twinkle hardware era: 1st Style to 8th Style
* [Twinkle PCB](https://github.com/shizmob/arcade-docs/blob/main/konami/boards.md#twinkle)
* [Original rear projection monitor](https://github.com/shizmob/arcade-docs/blob/main/konami/products.md#iidx-rear-projection-monitor)
* Homogenous hardware and software
### 1st gen PC-based upgrade only: 9th Style
* 9th Style came as an upgrade kit only to existing twinkle-based cabinet hardware
* [KNM-845G3-A02 PCB](https://github.com/shizmob/arcade-docs/blob/main/konami/boards.md#knm-845g3-a02)
* [C02 EZUSB FX USB IO board](https://github.com/shizmob/arcade-docs/blob/main/konami/io.md#gec02-pwbaa)
* Magnetic card readers connected to ezusb IO board
### 1st gen PC-based with dedicated cabinet: 10th Style to DistorteD (13)
* From here on, old cabinets that received the "Twinkle hardware" upgrade kit could always receive software only
upgrades
* [CRT monitor](https://github.com/shizmob/arcade-docs/blob/main/konami/products.md#iidx-crt-monitor) with new dedicated
cabinets
* [D01 EZUSB FX USB IO board](https://github.com/shizmob/arcade-docs/blob/main/konami/io.md#d01-io)
* Slotted card readers starting version 13
### 2nd gen PC: GOLD (14) to Lincle (19)
* [FAB-e945-KN205](https://github.com/shizmob/arcade-docs/blob/main/konami/boards.md#fab-e945-kn205)
* All prior cabinet configurations can be upgraded with a PCB and software upgrade
* New LCD monitor starting version 15
* Further variants of the LCD monitor appear starting version 18
* [IO2 EZUSB FX2 USB IO board](https://github.com/shizmob/arcade-docs/blob/main/konami/io.md#usbio2) introduced with
version 14
* Switch to wave pass readers on version 19
### 3rd gen PC: Tricoro (20) to Sinobuz (24)
* All prior cabinet configurations can be upgraded with a PCB and software upgrade
* [ADE-HM65](https://github.com/shizmob/arcade-docs/blob/main/konami/boards.md#hm65)
* Game engine supports HD mode (720p)
* Many new LCD monitor variants appear with every new version
### 4th gen PC: CANNON BALLERS (25) to RESIDENT (30)
* All prior cabinet configurations can be upgraded with a PCB and software upgrade
* [ADE-6291](https://github.com/shizmob/arcade-docs/blob/main/konami/boards.md#ade-6291)
* [BIO2 IO board](https://github.com/shizmob/arcade-docs/blob/main/konami/boards.md#hm65)
* Upgrade cabinets required the [BIO2 sub-IO board](https://github.com/shizmob/arcade-docs/blob/main/konami/io.md#bio2-ldj-sub-io)
* Drop of SD mode support starting version 27
* Supporting 120 hz monitors starting version 27
* Many new LCD monitor variants appear with every new version
* 42" 120 hz for lightning cabinets starting version 27
* 42" 60 hz for non-lightning cabinets starting version 25
### 5th gen PC: RESIDENT (30) to EPOLIS (31)
* All prior cabinet configurations can be upgraded with a PCB and software upgrade
* [C300-xonarae](https://github.com/shizmob/arcade-docs/blob/main/konami/boards.md#c300-xonarae)
* Game engine supports FHD mode (1080p)
## Threading model and core responsibilities in PC-based games
Since the early days of 9th Style to today (as of time of writing this: EPOLIS), the threading model
hasn't changed significantly:
* Main and rendering thread
* Runs your typical D3D9 render loop
* Using D3D9ex starting version 27
* Starting and managing other threads
* Any file and game asset loading
* Handles all the core game logic of the different screens starting from the initial boot screen
to the core game-play screen
* IO thread
* Polls the main IO hardware and synchronizes the in-memory input and output state with the
hardware
* Executes commands issued by the main thread to the IO board such as dongle or card reader
commands (ezusb generation of IO only)
* Card reader thread (not applicable to versions 9 to 12)
* Runs the protocol to read/write the hardware of the readers
* Async execution of higher level commands issued by the main thread to the card reader hardware,
e.g. card read, card eject etc.
* Audio thread
* Streaming of audio data to the sound API
* DirectSound for versions 9 to 26
* WASAPI for versions 27 to 30 for non-lighting cabinets with ADE-6291 hardware
* ASIO for versions 27 and newer for lighting cabinets
* ASIO for version 30/31 and newer for non-lighting cabinets with C300-xonarae hardware
* Async execution of higher level commands issued by the main thread to the audio backend, e.g.
play audio, stop audio, etc.
* Network thread
* Network plumbing with the xrpc protocol
* Async execution of request-response commands issued by the main thread
## Main and rendering thread
With the game having transitioning to different screens throughout it's lifecycle, the main render
loop might contain different logic such as asset loading. The following focuses on the main
game-play screen, only. Common rendering logic still applies to other screens.
This follows your typical D3D9 single threaded rendering loop which boils down to the following:
* Begin the scene (`BeginScene`)
* Set the render target (`SetRenderTarget`)
* Clear the back buffer (`Clear`)
* Run engine step. This includes and is mixed with
* IO input evaluation
* Game engine state and (re-) drawing the scene
* Commands to the audio playback, e.g. play key sounds
* Setting IO outputs
* End the scene (`EndScene`)
* Target a minimum frame time
* Introduced with version 11
* Sleep (`Sleep` or `SleepEx`) to fill up the frame up to either 13 ms or 14 ms (depending on the
version)
* This was likely introduced to not have the game run at hundreds of FPS if v-sync was disabled
(probably when running in window mode for development/testing purposes)
* Swapping the back buffers (`Present`)
* D3D9 is configured with v-sync enabled by default and targets 60 hz
* With this configuration, the call to `Present` fills up the remaining frame time to target the
configured 60 hz refresh rate
### Rendering resolutions
The game engine went through a couple of different rendering resolutions:
* 640x240: version 1 to 8 (all twinkle hardware)
* 640x480: version 9 to 19 (1st and 2nd gen PC-based)
* 1280x720 (HD mode) and 640x480 (SD mode): version 20 to 27 (3rd and 4th gen PC-based)
* 1280x720 (HD mode) only for version 28 to 29 (4th gen PC-based)
* 1280x720 (HD mode) and 1920x1080 (FHD mode) for version 30 (4th gen PC-based)
* 1920x1080 (FHD mode) only for version 30+ (5th gen PC-based)
## Core engine timing and synchronization
The following outlines and explains the core engine concepts related to timing and synchronization.
### The origins, twinkle hardware
* It appears that the game has been on the same codebase since it's inception.
* This can be reasoned by the following observations
* 9th style as the first PC-based game very buggy on initial release
* 9th style threading model very clunky with various thread settings showing they tried to
prioritize different processing on a single core CPU
* Coming from twinkle hardware that is a realtime system vs. a Windows XP system
* Fixed playstation 1 based hardware with a refresh rate of
[59.8260978565 hz](https://github.com/mamedev/mame/blob/bf9df04e005697788ae988ef5aea91a1878fd631/src/devices/video/psx.cpp#L435)
* No need to assume getting anything else if the resource budget per frame is not exceeded
* All sub-systems were driven synchronously to that fixed time step
* No need for the engine to synchronize audio during the song. When the song is started
correctly, it stayed sync throughout the song
* Observations and assumptions
* It appears the developers struggled a lot with the significant platform shift to PC as indicated
by various core engine improvements throughout the years still
* Switches of hardware generations further complicated the situation as these forced them to further
drift away from how the game engine was initially designed on the twinkle system
### Frame time/timing and engine step
* The game's engine step and timing is frame based
* The main render loop defines the minimum valid timing window for the game engine to be able to
evaluate
* 60 hz/fps = 16.667 ms (rounded to three decimal places)
* 120 hz/fps = 8.333 ms (rounded to three decimal places), only relevant for lightning cabinets with their 120 hz
screen/mode
* Depending on the GPU hardware and configuration, the GPU might drive the screen not exactly at
perfectly 60 hz/fps = 16.667 ms
* As outlined in the [main render loop](#main-and-rendering-thread), the `Present` call is the
key function call to determine the frame time for the game engine
* Just having v-sync enabled and telling D3D9 to **target** 60 hz, it still depends on how the GPU
driver is implemented and how the GPU driver's implementation of D3D9 executes on the `Present`
call
* This is primarily determined by how the GPU driver is operating and driving connected display
hardware
* The following factors can contribute to that either directly or indirectly
* Different monitor types analog vs. digital (CRT vs. LCD)
* Different monitor connections VGA vs. DVI/HDMI/DP
* Different GPU drivers
* Different GPU hardware and vendors
* Different GPU settings
### Monitor check screen
The monitor check screen was introduced with version 14. This was a solution to the problem of not
guaranteeing a single fixed refresh rate throughout different GPU and monitor combinations. See
a detailed explanation in the previous sub-subsection about
[what influences the frame time](#frame-timetiming-and-engine-step) of the game.
The goal of the monitor check is to measure the frame time of the main render loop and use the
results for [driving the core engine step](#engine-step-and-synchronization).
The monitor check evolved slightly as different GPU and monitor hardware, and GPU drivers yielded
varying refresh rates that determined the frame times of the main game loop.
Actual values are outlined further down in the [engine step time](#engine-step-time) section.
There are two main versions of the monitor check, all of them have always been part of the boot
process:
* 14
* Measures a total of 3000 frames
* If total time this took < 50004 ms -> so average of 16.668 ms per frame = 59.9952 hz
* S-Video mode -> 59.95 hz (also shows S-VIDEO when the monitor check completes)
* Otherwise VGA mode -> 60.05 hz
* If VGA is determined, the game patches the chart event data when loading
* `event_timestamp * 0.99817199 + 0.40000001`
* When S-Video no charts are patched and the timing data in the chart is used as is
* 15 to 18
* Identical to 14 except that chart patching now happens in milliseconds instead of frames
* VGA mode: `16.65279`
* S-Video mode: `16.680567`
* `event_timestamp * (1.0 / frame_time) + 0.40000001)`
* 19
* Identical to previous version
* Monitor check screen shows current FPS on the last revision of that version. However, it doesn't
use the value shown there by any means. Assuming this was added before the introduction of the
new monitor check on 20.
* 20 to 26
* Runs on boot or when switching monitor modes SD vs. HD
* Measures only 1200 frames
* Takes the average refresh rate of 1200 frames
* Applies the measured avg. frame time directly to the charts before song
* 27 to 30
* Frame rate measuring identical to prior versions
* Added additional logic after the frame time measurements for handling of 120 hz displays
* Includes checking the monitor and if the framerate matches as expected
* 30/31 LDJ-010 upgrade
* Added NvDisplayConfigLDJ tool which runs on system start and configures a custom timing in the
NVIDIA GPU driver to ensure a fixed monitor refresh rate
* 31
* Same code as before
* Consider the first 460 frames of the measurement as warm-up frames
* Discard these for the actual measurement to get more stable frame times
* Use a base avg. value of 119.982 for the 120 hz monitors and 59.9 for 60 hz monitors. Further
frames measured are counted towards the average with the given base value
### Engine core game-play with visual and audio synchronization
#### Chart data
* Chart data encodes note timing and timing window information
* Chart data consists of *note events* encoding a timestamp and note information such as type
and key-sound information
* Timing windows are defined per chart
* Most infamous example: GAMBOL (7Key/Another)
* Game engine timestamp evaluation and note event timestamp encoding
* 9 to 14: Refresh rate in hz
* 15 to 31: Milliseconds (equivalent to assuming 1000 fps)
* Fundamentally, that doesn't change game play experience as it's just a different way of
evaluating the timestamps since refresh rate in hz/frame rate in fps can be converted lossless to
frame time in ms
#### Engine step time
The engine uses the following source for frame rate/time to drive internal logic on the following
versions
* 9 to 13: Hardcoded 59.95 hz
* 14: Either 59.95 hz or 60.05 hz determined by which value is determined to be closer to the
measured one during the monitor check screen on startup
* 15 to 19: 59.95 hz or 60.05 hz
* Monitor check on startup measures the frame time and picks the value closest to what's been
measured
* Monitor check result can be overridden with the *Output Type* setting in the operator menu
* *S-Video* to force to 59.95 hz
* *VGA* to force to 60.05 hz
* 20 to 31: Monitor check on boot determines the frame time and uses the result throughout the
entire game session
#### Engine step and synchronization
Per frame engine step advancing and synchronization during game-play
* Game-play needs synchronization of the following aspects
* Drawn content on the screen
* Background audio playback track
* Key sound effects
* The game engine synchronizes these only at the start of a song
* It takes known/determined refresh rate/frame time as the fixed time for every frame cycle
throughout the entire song
* It starts playback of the background audio track together with playback of the chart data
* With every engine step/frame it advances internal calculations by that fixed amount of time.
Thus, the game engine expects this to be constant/stable throughout an entire song
* The chart data is advanced every frame by the engine
* Key sound events are turned into audio playback on command as the chart advances
* No re-synchronization or check-pointing between main rendering thread and audio is happening
throughout the song
* Remark: The above does not consider any kind of offsetting and timing adjustments for the sake
of keeping this simple and focus on the absolute core concepts

View File

@@ -48,8 +48,6 @@ Available implementations that can be swapped out depending on which kind of IO
use:
- `iidxio`: Default implementation supporting keyboard, mouse and USB game controllers
- [iidxio-async](iidxhook/iidxio-async.md): Shim implementation that runs another iidxio implementation in a dedicated
thread
- [iidxio-bio2](iidxhook/iidxio-bio2.md): Support BIO2 hardware
- [iidxio-ezusb](iidxhook/iidxio-ezusb.md): Support C02 ezusb FX hardware
- [iidxio-ezusb2](iidxhook/iidxio-ezusb2.md): Support IO2 ezusb FX2 hardware

View File

@@ -1,224 +0,0 @@
# IIDX "Syncbook": A guide to ensuring sync and performant gameplay
This is a guide applicable to all IIDX versions that are native Windows PC-based. The goal is to
enable proper configuration of those games to ensure the game engine "runs in-sync" with any of its
IO (visual GPU/display output, audio output, user input) resulting in high and stable performance
for smooth game-play.
This document won't go into the implementation details of the game engine. Please refer to the
[IIDX engine details journal](../dev/journal/2025-02-09-iidx-engine.md) for a more in-depth
information.
## Understanding the problems and options to address them
First, let's understand what the key problems are and how they contribute to different issues
that impact game-play.
### Song de-syncing
Symptom: Throughout playing a song, the audio is de-syncing even it was fine when the song started.
Cause: The game engine is fundamentally designed to run on a fixed and consistent frame
rate/time per engine step. A mismatch of expected frame time by the game engine and actually delivered
frame time by the GPU driver. This can come either as a stable frame time that just mismatches in
general or (highly) fluctuating frame times.
* G-sync on modern displays causes known fluctuations because it adjusts dynamically the monitor
refresh rate
* V-sync not correctly configured/enforced or even vsync-off override in driver is causing
inconsistent frame times
* Incorrect display timings causing refresh rate fluctuations resulting in inconsistent frame times
Stable frame times but not having the correct ones that the game engine expects is a different
problem that can be addressed by:
* Knowing the refresh rates expected by the game engine of the different game versions
* Configuring your GPU driver's display timings to match the expected frame times
* Or, patch the game's chart data with your custom display timings so the game engine gets uses
*your* "correct" frame times
* Note: Offline patching the chart data doesn't work for several game versions as the monitor
check does override this data, but not as flexible as required
* Use the chart patch feature of bemanitools instead
### Micro-stuttering and non-smooth note scrolling
This is a trickier problem as it can be caused by a few different things.
* GPU related
* Fluctuating refresh rates result in fluctuating frame times in the main game loop
* GPU power configuration in non-performance mode (e.g. "power saving") causes processing latency
due to sleep states
* Display related
* Display firmware buggy and/or internal scalers that expect to run at very specific refresh rates
and cannot adapt to any custom display timings enforced by the GPU
* A known example is the official
[PENDUAL LCD monitor](https://github.com/shizmob/arcade-docs/blob/6ac99975cdb2bf668362f65fa9fa3ffb2127308b/konami/product/GULDJ-JI.md#notes-and-known-issues) that does not v-sync correctly to any other refresh-rate than 60.000 hz
* CPU related
* CPU power configuration on Windows in non-performance mode (e.g. "power saving") causes
processing latency due to sleep states
* Correct CPU C-states configuration in BIOS/UEFI
* These cannot be configured in Windows and are separate from Windows's power configuration
* Other stuff running in the background of the system causing fluctuations
* (Synchronous) Disk I/O heavy applications stealing CPU time
* Or any other process creating a considerable amount of CPU or GPU load
## Configuration guide for different game versions
### Measuring monitor refresh rate
Let's start with configuring the system to meet the game engine's expectation regarding the
target monitor refresh rate and requiring vsync.
First, use the [d3d9-monitor-check tool](../tools/d3d9-monitor-check.md) to measure the currently
configured refresh rate of your monitor. Note that your monitor might yield different timings on
different resolutions and different game versions run on
[different rendering resolutions](../dev/journal/2025-02-09-iidx-engine.md#rendering-resolutions).
For example, the game version is 31 with the rendering resolution of 1920x1080 in FHD mode that you
want to target:
```bat
d3d9-monitor-check.exe cmdline refresh-rate-test 1920 1080 60
```
The test shows the current monitor refresh rate and the average refresh rate over the course of
the test. Check that the avg. refresh rate is stable during the measuring phase (during warm-up,
it's ok if it fluctuates).
This test allows you to determine/check:
* Refresh rate/frame time fluctuations: if you see (high) fluctuations on the first or second
decimal place
* What's your current target refresh rate the GPU is driving your monitor at
* If vsync isn't overridden by some other driver setting as you want to see a refresh rate around
the 60 hz mark
### Measuring vsync issues
Some displays or the GPU configuration might lead to vsync issues which can be detected with the
`d3d9-monitor-check.exe` tool as well.
```bat
d3d9-monitor-check.exe cmdline vsync-test 1920 1080 60
```
This runs a test with a VSYNC text that alternates red and cyan colors. If the text appears grey
all the time, everything's fine. If you spot brief red or cyan text either occasionally or
periodically, your current configuration has vsync issues resulting in skipped frames or old
frames displaying longer.
### Configuration
The following are recommended configurations for the different game versions. If any of these
don't work as expected, you have to troubleshoot and tweak these potentially. Please refer to the
[guidelines above](#configuration-guide-for-different-game-versions) to understand how to use
the tooling and test your changes.
#### Recommended configuration with NVIDIA GPU and driver
We want to achieve the following configuration on the GPU driver:
* Turn-off g-sync if you have a monitor that supports it
* Disable power saving mode on GPU driver
* V-sync is not forced off
* Run the display on a target refresh rate of `59.950 hz` which has been determined to be
[the most compatible across all PC-based versions](../dev/journal/2025-02-09-iidx-engine.md#engine-step-time).
##### Turn-off g-sync
Create a new GPU profile for IIDX, if it doesn't exist yet:
```bat
nvgpu.exe profile create iidx
```
Add the application names when the profile needs to be applied:
```bat
:: For iidx versions 9 to 17
nvgpu.exe profile application-add iidx bm2dx.exe
:: For iidx versions 18+
nvgpu.exe profile application-add iidx launcher.exe
```
Disable G-SYNC for the profile:
```bat
nvgpu.exe profile gsync-disable iidx
```
##### Disable power saving mode
Set GPU power state to maximum for the profile (assumes you have a
[GPU profile called `iidx` already created](#turn-off-g-sync)):
```bat
nvgpu.exe profile gpu-power-state-max iidx
```
##### Configure and verify target refresh rate of 59.950 hz and vsync
Get display ID of your primary display:
```bat
nvgpu.exe display primary-display-id
```
Copy the hex-value of the display ID and use it in the following commands.
Test the custom display timing first, e.g. for versions that run in 1920x1080
(Replace `<DISPLAY_ID> ` with the primary display ID you got):
```bat
nvgpu.exe display custom-resolution-test <DISPLAY_ID> 1920 1080 59.95 10
```
This will test it for 10 seconds and then revert. If the result looked good, i.e. the screen didn't
go blank or the display output was garbage, apply it:
```bat
nvgpu.exe display custom-resolution-set <DISPLAY_ID> 1920 1080 59.95
```
Test and verify the configuration is applied and works correctly:
```bat
d3d9-monitor-check.exe cmdline refresh-rate-test 1920 1080 60
```
Test if the enforced refresh rate is compatible with your display and vsync is working correctly:
```bat
d3d9-monitor-check.exe cmdline vsync-test 1920 1080 60
```
##### Alternative refresh rate configuration
If for some reason your display doesn't support 59.950 hz properly, i.e. display artifacts or vsync
issues, you can use any other 60 hz compatible refresh rate, e.g. anything between 59.900 hz and
60.100 hz that your display supports properly.
Follow the same steps [as above](#configure-and-verify-target-refresh-rate-of-59950-hz-and-vsync)
but use a different refresh rate, e.g. 60.000 hz.
Verify using the `d3d9-monitor-check.exe` with the outlined steps if you need to play around to find
a refresh rate that works for your setup.
For the game versions 9 to 19, you have to use the chart patch feature of bemanitools to patch
the charts to your custom refresh rate. Otherwise, the game engine will run on your configured
refresh rate, but it will take the non-matching refresh rate from the chart data which causes
song de-syncing.
For example, to configure a refresh rate of 60.000 hz in the iidxhook config file:
```text
gfx.monitor_check=60.000
```
##### ATI/AMD
Currently, there is no tooling provided by bemanitools to configure the AMD GPU driver. The basic
concepts of what needs to be configured still applies as outlined in the NVIDIA sections above
though, e.g. through AMD's GPU control panel.
Further steps and instructions TBD

View File

@@ -146,7 +146,16 @@ This can be related to various issues:
start "" /relatime "gamestart.bat"
```
Also refer to the [iidx-syncbook](iidx-syncbook.md).
- Enforce v-sync enabled in your GPU settings.
- Ensure that you have a constant refresh rate around the 60 hz (59.9xx or 60.0xx) that is not
jumping around. Use the timebase feature of one of the newer games to check that or enable
iidxhook's timebase and check the log output for the determined value. Run this a few times and
check if the results differ.
- Use iidxhook's frame rate limiter feature (see further below) to software lock the refresh rate.
This might be necessary on Windows 7 and newer for D3D8 games, e.g. iidx 9 to 12, which seem to
ignore GPU side v-sync.
- Use iidxhook's auto timebase feature (see further below) or set a pre-determined value to cut down
start-up times.
### The game still stutters (randomly) and drifts off-sync
@@ -164,7 +173,9 @@ This can be caused by:
## My songs are offsync
Refer to the [iidx-syncbook](iidx-syncbook.md).
- Make sure your machine's refresh rate is stable
- If you don't get a close to 59.94hz refresh rate, use the software monitor check/auto timebase
that's built into iidxhook (refer to help/config file)
## The game crashes instantly (10th, RED, HAPPY SKY)
@@ -222,8 +233,6 @@ file) or use a CLVSD.ax codec which has the debugger checks removed.
## I used the auto timebase option and/or limited my refresh rate but the songs are still going offsync
Refer to the [iidx-syncbook](iidx-syncbook.md) if you haven't already.
There aren't many options left. The old games were developed for specific hardware and are not
guaranteed to work well on (especially) newer hardware. Multiple monitor setups can also have a bad
impact on a stable refresh rate. Try a setup with just a single monitor you want to use for gameplay

View File

@@ -115,7 +115,16 @@ This can be related to various issues:
start "" /relatime "gamestart.bat"
```
Also refer to the [iidx-syncbook](iidx-syncbook.md).
- Enforce v-sync enabled in your GPU settings.
- Ensure that you have a constant refresh rate around the 60 hz (59.9xx or 60.0xx) that is not
jumping around. Use the timebase feature of one of the newer games to check that or enable
iidxhook's timebase and check the log output for the determined value. Run this a few times and
check if the results differ.
- Use iidxhook's frame rate limiter feature (see further below) to software lock the refresh rate.
This might be necessary on Windows 7 and newer for D3D8 games, e.g. iidx 9 to 12, which seem to
ignore GPU side v-sync.
- Use iidxhook's auto timebase feature (see further below) or set a pre-determined value to cut down
start-up times.
### The game still stutters (randomly) and drifts off-sync
@@ -133,7 +142,9 @@ This can be caused by:
## My songs are offsync
Refer to the [iidx-syncbook](iidx-syncbook.md).
- Make sure your machine's refresh rate is stable
- If you don't get a close to 59.94hz refresh rate, use the software monitor check/auto timebase
that's built into iidxhook (refer to help/config file)
## The game crashes instantly
@@ -181,8 +192,6 @@ CLVSD.ax codec which has the debugger checks removed.
## I used the auto timebase option and/or limited my refresh rate but the songs are still going offsync
Refer to the [iidx-syncbook](iidx-syncbook.md) if you haven't already.
There aren't many options left. The old games were developed for specific hardware and are not
guaranteed to work well on (especially) newer hardware. Multiple monitor setups can also have a bad
impact on a stable refresh rate. Try a setup with just a single monitor you want to use for gameplay

View File

@@ -132,7 +132,16 @@ This can be related to various issues:
start "" /relatime "gamestart.bat"
```
Also refer to the [iidx-syncbook](iidx-syncbook.md).
- Enforce v-sync enabled in your GPU settings.
- Ensure that you have a constant refresh rate around the 60 hz (59.9xx or 60.0xx) that is not
jumping around. Use the timebase feature of one of the newer games to check that or enable
iidxhook's timebase and check the log output for the determined value. Run this a few times and
check if the results differ.
- Use iidxhook's frame rate limiter feature (see further below) to software lock the refresh rate.
This might be necessary on Windows 7 and newer for D3D8 games, e.g. iidx 9 to 12, which seem to
ignore GPU side v-sync.
- Use iidxhook's auto timebase feature (see further below) or set a pre-determined value to cut down
start-up times.
## "NETWORK WARNING" instead of "NETWORK OK"
@@ -145,7 +154,15 @@ This can be caused by:
## My songs are offsync
Refer to the [iidx-syncbook](iidx-syncbook.md).
The built-in monitor check just determines if the game should sync to either 59.94 hz (S-Video
setting) or 60.04 hz (VGA setting). If you don't have a setup that runs on (as close as possible)
these values:
- Make sure your machine's refresh rate is stable, e.g. 60.00x hz.
- If you don't get a close to 59.94hz (S-Video setting) or 60.04 hz (VGA setting) refresh rate, go
an set the output mode in the operator menu to "VGA" to enforce the game to run chart syncing on
60.04 hz refresh rate (even if your setup does not have that value). Next, use the software
monitor check/auto timebase that's built into iidxhook (refer to cmd help/configfile).
## My game runs too fast
@@ -188,8 +205,6 @@ CLVSD.ax codec which has the debugger checks removed.
## I used the auto timebase option and/or limited my refresh rate but the songs are still going offsync
Refer to the [iidx-syncbook](iidx-syncbook.md) if you haven't already.
There aren't many options left. The old games were developed for specific hardware and are not
guaranteed to work well on (especially) newer hardware. Multiple monitor setups can also have a bad
impact on a stable refresh rate. Try a setup with just a single monitor you want to use for gameplay

View File

@@ -143,7 +143,16 @@ This can be related to various issues:
start "" /relatime "gamestart.bat"
```
Also refer to the [iidx-syncbook](iidx-syncbook.md).
- Enforce v-sync enabled in your GPU settings.
- Ensure that you have a constant refresh rate around the 60 hz (59.9xx or 60.0xx) that is not
jumping around. Use the timebase feature of one of the newer games to check that or enable
iidxhook's timebase and check the log output for the determined value. Run this a few times and
check if the results differ.
- Use iidxhook's frame rate limiter feature (see further below) to software lock the refresh rate.
This might be necessary on Windows 7 and newer for D3D8 games, e.g. iidx 9 to 12, which seem to
ignore GPU side v-sync.
- Use iidxhook's auto timebase feature (see further below) or set a pre-determined value to cut down
start-up times.
## "NETWORK WARNING" instead of "NETWORK OK"
@@ -156,7 +165,15 @@ This can be caused by:
## My songs are offsync
Refer to the [iidx-syncbook](iidx-syncbook.md).
The built-in monitor check just determines if the game should sync to either 59.94 hz (S-Video
setting) or 60.04 hz (VGA setting). If you don't have a setup that runs on (as close as possible)
these values:
- Make sure your machine's refresh rate is stable, e.g. 60.00x hz.
- If you don't get a close to 59.94hz (S-Video setting) or 60.04 hz (VGA setting) refresh rate, go
an set the output mode in the operator menu to "VGA" to enforce the game to run chart syncing on
60.04 hz refresh rate (even if your setup does not have that value). Next, use the software
monitor check/auto timebase that's built into iidxhook (refer to cmd help/configfile).
## My game runs too fast
@@ -194,8 +211,6 @@ CLVSD.ax codec which has the debugger checks removed.
## I used the auto timebase option and/or limited my refresh rate but the songs are still going offsync
Refer to the [iidx-syncbook](iidx-syncbook.md) if you haven't already.
There aren't many options left. The old games were developed for specific hardware and are not
guaranteed to work well on (especially) newer hardware. Multiple monitor setups can also have a bad
impact on a stable refresh rate. Try a setup with just a single monitor you want to use for gameplay

View File

@@ -143,7 +143,16 @@ This can be related to various issues:
start "" /relatime "gamestart.bat"
```
Also refer to the [iidx-syncbook](iidx-syncbook.md).
- Enforce v-sync enabled in your GPU settings.
- Ensure that you have a constant refresh rate around the 60 hz (59.9xx or 60.0xx) that is not
jumping around. Use the timebase feature of one of the newer games to check that or enable
iidxhook's timebase and check the log output for the determined value. Run this a few times and
check if the results differ.
- Use iidxhook's frame rate limiter feature (see further below) to software lock the refresh rate.
This might be necessary on Windows 7 and newer for D3D8 games, e.g. iidx 9 to 12, which seem to
ignore GPU side v-sync.
- Use iidxhook's auto timebase feature (see further below) or set a pre-determined value to cut down
start-up times.
## "NETWORK WARNING" instead of "NETWORK OK"
@@ -156,7 +165,11 @@ This can be caused by:
## My songs are offsync
Refer to the [iidx-syncbook](iidx-syncbook.md).
From this version onwards (if you use the very final data of Lincle), the game comes with a built-in
auto timebase option ("monitor check" on startup) which dynamically, detects the refresh rate of
your current setup. Thus, BT5's timebase option is not included from this hook version onwards,
anymore. Ensure that refresh rate displayed is very stable, e.g. 60.00x hz, and the game should be
able to provide you with a smooth and sync game experience.
## My game runs too fast

View File

@@ -155,7 +155,11 @@ This can be related to various issues:
start "" /relatime "gamestart.bat"
```
Also refer to the [iidx-syncbook](iidx-syncbook.md).
- Enforce v-sync enabled in your GPU settings.
- Ensure that you have a constant refresh rate around the 60 hz (59.9xx or 60.0xx) that is not
jumping around. Use the timebase feature of one of the newer games to check that or enable
iidxhook's timebase and check the log output for the determined value. Run this a few times and
check if the results differ.
## "NETWORK WARNING" instead of "NETWORK OK"
@@ -168,7 +172,11 @@ This can be caused by:
## My songs are offsync
Refer to the [iidx-syncbook](iidx-syncbook.md).
From this version onwards (or Lincle very final revision), the game comes with a built-in auto
timebase option ("monitor check" on startup) which dynamically, detects the refresh rate of your
current setup. Thus, BT5's timebase option is not included from this hook version onwards, anymore.
Ensure that refresh rate displayed is very stable, e.g. 60.00x hz, and the game should be able to
provide you with a smooth and sync game experience.
## My game runs too fast

View File

@@ -159,7 +159,11 @@ This can be related to various issues:
start "" /relatime "gamestart.bat"
```
Also refer to the [iidx-syncbook](iidx-syncbook.md).
- Enforce v-sync enabled in your GPU settings.
- Ensure that you have a constant refresh rate around the 60 hz (59.9xx or 60.0xx) that is not
jumping around. Use the timebase feature of one of the newer games to check that or enable
iidxhook's timebase and check the log output for the determined value. Run this a few times and
check if the results differ.
## "NETWORK WARNING" instead of "NETWORK OK"
@@ -172,7 +176,11 @@ This can be caused by:
## My songs are offsync
Refer to the [iidx-syncbook](iidx-syncbook.md).
From IIDX 20 (or Lincle very final revision) onwards, the game comes with a built-in auto timebase
option ("monitor check" on startup) which dynamically, detects the refresh rate of your current
setup. Thus, BT5's timebase option is not included from this hook version onwards, anymore. Ensure
that refresh rate displayed is very stable, e.g. 60.00x hz, and the game should be able to provide
you with a smooth and sync game experience.
## My game runs too fast

View File

@@ -158,7 +158,11 @@ This can be related to various issues:
start "" /relatime "gamestart.bat"
```
Also refer to the [iidx-syncbook](iidx-syncbook.md).
- Enforce v-sync enabled in your GPU settings.
- Ensure that you have a constant refresh rate around the 60 hz (59.9xx or 60.0xx) that is not
jumping around. Use the timebase feature of one of the newer games to check that or enable
iidxhook's timebase and check the log output for the determined value. Run this a few times and
check if the results differ.
## "NETWORK WARNING" instead of "NETWORK OK"
@@ -171,7 +175,11 @@ This can be caused by:
## My songs are offsync
Refer to the [iidx-syncbook](iidx-syncbook.md).
From IIDX 20 (or Lincle very final revision) onwards, the game comes with a built-in auto timebase
option ("monitor check" on startup) which dynamically, detects the refresh rate of your current
setup. Thus, BT5's timebase option is not included from this hook version onwards, anymore. Ensure
that refresh rate displayed is very stable, e.g. 60.00x hz, and the game should be able to provide
you with a smooth and sync game experience.
## My game runs too fast

View File

@@ -6,7 +6,6 @@ The following games are compatible with this version of iidxhook:
- BISTROVER
- CASTHOUR
- RESIDENT
- EPOLIS
The games must be bootstrapped using [launcher](../launcher.md).
@@ -58,7 +57,7 @@ unpacked data looks like this:
- Unpack the package containing iidxhook9 into the root folder so iidxhook9.dll and all other
files are located in the same folder as *data*, *prop*, *bm2dx.dll*, etc.
- For version 30-31
- For version 30
- Unpack the package containing iidxhook9 into the `modules` folder so iidxhook9.dll and all other
files are located in the same folder as the game's DLLs, e.g. *bm2dx.dll*, etc.
@@ -94,12 +93,6 @@ The syntax for the "key=value" is the same as in the config file. Make sure to h
However, if a parameter is specifed in the configuration file and as a command line argument, the
command line argument overrides the config file's value.
# Note on camhook
The cammera hook (camhook) is used to allow other class compliant cameras to be used. Depending on the variant of the game, you will need to select the correct camera port layout.
For original LDJ, this is always 0, for conversion kit LDJ and original TDJ-JA, this is 1, and for TDJ-JB this will be 2 (this case is rare / you will know if this is needed on your setup).
# Eamuse network setup
If you want to run the games online, you need a valid PCBID and the service URL. Open
@@ -212,7 +205,11 @@ This can be related to various issues:
start "" /relatime "gamestart.bat"
```
Also refer to the [iidx-syncbook](iidx-syncbook.md).
- Enforce v-sync enabled in your GPU settings.
- Ensure that you have a constant refresh rate around the 60 hz (59.9xx or 60.0xx) that is not
jumping around. Use the timebase feature of one of the newer games to check that or enable
iidxhook's timebase and check the log output for the determined value. Run this a few times and
check if the results differ.
## "NETWORK WARNING" instead of "NETWORK OK"
@@ -225,7 +222,11 @@ This can be caused by:
## My songs are offsync
Refer to the [iidx-syncbook](iidx-syncbook.md).
From IIDX 20 (or Lincle very final revision) onwards, the game comes with a built-in auto timebase
option ("monitor check" on startup) which dynamically, detects the refresh rate of your current
setup. Thus, BT5's timebase option is not included from this hook version onwards, anymore. Ensure
that refresh rate displayed is very stable, e.g. 60.00x hz, and the game should be able to provide
you with a smooth and sync game experience.
## My game runs too fast

View File

@@ -1,22 +0,0 @@
# IIDXIO async API implementation
This implementation of the iidxio API is a shim library that takes another iidxio library and
runs the functions `iidx_io_ep1_send`, `iidx_io_ep2_recv` and `iidx_io_ep3_write_16seg` in a
dedicated thread. State synchronization to the getter and setter functions is also handled
transparently.
Usage of this **may** improve performance of certain iidxio implementations or when using them
in certain integrations, e.g. the send and receive functions of a iidxio implementation for some
target IO hardware calls are synchronous and expensive.
This is not a fix/solution to a badly implemented iidxio library with poor performance as it cannot
make it go faster and address potential latency issues, for example.
Use with caution and know why and when you need to use it.
## Setup
* Add `iidxio-async.dll` in the same folder as your `iidxhookX.dll`
* Rename your `iidxio.dll` to `iidxio-async-child.dll`
* Rename `iidxio-async.dll` to `iidxio.dll`
* Run the game

View File

@@ -1,28 +0,0 @@
# D3D9 Monitor Check
A separate application providing various music game relevant tests, e.g. the infamous IIDX “monitor
check” without having to run the actual game. The tool can be used to debug and test your monitor
regarding refresh rate calibration (custom monitor timings), v-sync issues or ghosting issues.
Simply run the tool without any arguments to get a full synopsis with usage instructions.
## "Accuracy" remarks refresh rate test/monitor check
The tool has been tested on an actual cabinet with `nvgpu` setting different custom timings. The
accuracy seems to be even higher than what IIDXs monitor check is actually showing. For example,
with a custom timing of 59.900 hz, this tool yields fairly accurate and stable avg. 59.902 hz.
The monitor check of IIDX 29 showed results of 59.8981 hz to 59.8997 hz on screen. As these are the
only visible values to the user, determining a specific (avg.) value that can be used as input for
other tooling or settings (e.g. patching charts to a different refresh rate on older games with
bemanitools) is difficult. This doesn't mean that the game's monitor checks are actually
inaccurate or wrong. Once the check is finished it uses the
[avg. value it determined](../dev/journal/2025-02-09-iidx-engine.md) to patch the chart data
correctly. The value displayed to the user is just the floating value.
Thus, modern games with a built-in monitor check (starting IIDX 20) are syncing up
fine and don't need any further patching or modifications.
For older games, picking a value that is not as close as possible to an accurate avg. value can
easily lead to issues with sync. So it's recommended to use the d3d9-monitor-check tool to get the
most accurate value.

View File

@@ -18,8 +18,63 @@ cannot be measured on application level.
Simply run the tool without any arguments to get a full synopsis of available commands.
## Example usage for IIDX
## Example usage for modern IIDX
Refer to the dedicated [iidx-syncbook](../iidxhook/iidx-syncbook.md) documentation for details on
how to use `nvgpu` to configure your system to run IIDX versions with proper display timings, smooth
frame rates and sync game-play.
### Custom GPU profile
The following creates a custom profile to address potential performance concerns such as not 100% smooth scrolling and
micro stuttering.
* Create a custom profile: `nvgpu profile create launcher`
* Add the launcher application to the profile: `nvgpu profile application-add launcher launcher.exe`
* This will apply to any (IIDX) game running with `launcher.exe`
* Disable G-SYNC for the profile: `nvgpu profile gsync-disable launcher`
* Set GPU power state to maximum for the profile: `nvgpu profile gpu-power-state-max launcher`
### Custom timing
* Run the game and observe the monitor check screen (requires IIDX 20+)
* Take note of the refresh rate of the monitor that is determined by the game
* Exit the game
* Run `nvgpu display list` to get the display ID of the monitor you want to change use that ID in the following commands
* Run `nvgpu display custom-resolution-test` with your display ID and monitor settings to test the custom configuration
first
* For example, for IIDX 31 which runs in native 1920x1080 with a monitor also having that as its native resolution,
having the monitor id `0x12345678` and the monitor check yielding a value of ~`59.9345`, run
`nvgpu display custom-resolution-test 1920 1080 59.9345 10`
* Observe if the test is successful and the display doesn't turn blank or displays a glitched image for ~10 seconds
* Run `nvgpu display custom-resolution-set` with the previously tested settings to apply the custom display mode
* For example, `nvgpu display custom-resolution-set 0x12345678 1920 1080 59.9345`
## Example usage for legacy IIDX
Legacy IIDX concerns any game prior to IIDX 20 that introduced the monitor check screen. The game engine was expecting
a the display/GPU/driver to perform at specific refresh rate timings in order to provide correct timing and audio
playback for the game. To this date (i.e. IIDX 31), the game engine never re-syncs audio playback during gameplay.
Therefore any flaky and incorrect timing will result in audio desynchronization either early on or throughout a song.
### Custom GPU profile
The following creates a custom profile to address potential performance concerns such as not 100% smooth scrolling and
micro stuttering.
* Create a custom profile: `nvgpu profile create bm2dx`
* Add the launcher application to the profile: `nvgpu profile application-add bm2dx bm2dx.exe`
* This will apply to any (IIDX) game running with an executable named `bm2fx.exe`
* Disable G-SYNC for the profile: `nvgpu profile gsync-disable bm2dx`
* Set GPU power state to maximum for the profile: `nvgpu profile gpu-power-state-max bm2dx`
### Custom timing
* Use a modern game and it to observe the monitor check screen (requires IIDX 20+)
* Take note of the refresh rate of the monitor that is determined by the game
* Exit the game
* Run `nvgpu display list` to get the display ID of the monitor you want to change use that ID in the following commands
* Run `nvgpu display custom-resolution-test` with your display ID and monitor settings to test the custom configuration
first
* For example, for IIDX 31 which runs in native 1920x1080 with a monitor also having that as its native resolution,
having the monitor id `0x12345678` and the monitor check yielding a value of ~`59.9345`, run
`nvgpu display custom-resolution-test 1920 1080 59.9345 10`
* Observe if the test is successful and the display doesn't turn blank or displays a glitched image for ~10 seconds
* Run `nvgpu display custom-resolution-set` with the previously tested settings to apply the custom display mode
* For example, `nvgpu display custom-resolution-set 0x12345678 1920 1080 59.9345`

View File

@@ -220,9 +220,6 @@ void property_file_write(struct property *prop, const char *path);
int property_set_flag(struct property *prop, int flags, int mask);
void property_destroy(struct property *prop);
avs_error property_get_error(struct property *prop);
void property_clear_error(struct property *prop);
int property_psmap_import(
struct property *prop,
struct property_node *root,

View File

@@ -25,8 +25,6 @@ EXPORTS
property_destroy
property_file_write
property_insert_read
property_clear_error
property_get_error
property_mem_write
property_read_query_memsize
property_search

View File

@@ -28,8 +28,6 @@ EXPORTS
property_destroy
property_file_write
property_insert_read
property_clear_error
property_get_error
property_mem_write
property_read_query_memsize
property_search

View File

@@ -26,8 +26,6 @@ EXPORTS
property_desc_to_buffer @246 NONAME
property_destroy @247 NONAME
property_insert_read @255 NONAME
property_clear_error @573 NONAME
property_get_error @573 NONAME
property_node_create @266 NONAME
property_node_datasize @267 NONAME
property_node_name @274 NONAME

View File

@@ -25,8 +25,6 @@ EXPORTS
property_desc_to_buffer @201 NONAME
property_destroy @264 NONAME
property_insert_read @23 NONAME
property_clear_error @573 NONAME
property_get_error @573 NONAME
property_node_create @316 NONAME
property_node_datasize @249 NONAME
property_node_name @255 NONAME

View File

@@ -25,8 +25,6 @@ EXPORTS
property_desc_to_buffer @201 NONAME == XC058ba50000cd
property_destroy @264 NONAME == XC058ba500010f
property_insert_read @23 NONAME == XC058ba5000016
property_clear_error @573 NONAME
property_get_error @573 NONAME
property_node_create @316 NONAME == XC058ba5000143
property_node_datasize @249 NONAME == XC058ba5000100
property_node_name @255 NONAME == XC058ba5000106

View File

@@ -24,8 +24,6 @@ EXPORTS
property_desc_to_buffer @131 NONAME
property_destroy @130 NONAME
property_insert_read @133 NONAME
property_clear_error @573 NONAME
property_get_error @573 NONAME
property_node_name @573 NONAME ==
property_node_read @573 NONAME ==
property_node_remove @148 NONAME

View File

@@ -26,8 +26,6 @@ EXPORTS
property_desc_to_buffer @129 NONAME
property_destroy @128 NONAME
property_insert_read @131 NONAME
property_clear_error @573 NONAME
property_get_error @573 NONAME
property_node_create @145 NONAME
property_node_name @150 NONAME
property_node_read @154 NONAME == XCd229cc0000f3

View File

@@ -19,8 +19,6 @@ EXPORTS
property_destroy @125 NONAME
property_desc_to_buffer @126 NONAME
property_insert_read @128 NONAME
property_clear_error @573 NONAME
property_get_error @573 NONAME
property_search @141 NONAME
property_node_create @142 NONAME
property_node_name @147 NONAME == XCnbrep7000092

View File

@@ -19,8 +19,6 @@ EXPORTS
property_destroy @146 NONAME
property_desc_to_buffer @147 NONAME
property_insert_read @149 NONAME
property_clear_error @158 NONAME == XCnbrep700009d
property_get_error @159 NONAME == XCnbrep700009e
property_search @162 NONAME
property_node_create @163 NONAME
property_node_name @168 NONAME == XCnbrep70000a7

View File

@@ -21,8 +21,6 @@ EXPORTS
property_destroy @146 NONAME
property_desc_to_buffer @147 NONAME
property_insert_read @149 NONAME
property_clear_error @158 NONAME == XCgsqzn000009d
property_get_error @159 NONAME == XCgsqzn000009e
property_search @162 NONAME
property_node_create @163 NONAME
property_node_name @168 NONAME == XCgsqzn00000a7

View File

@@ -25,8 +25,6 @@ EXPORTS
property_destroy
property_file_write
property_insert_read
property_clear_error
property_get_error
property_mem_write
property_read_query_memsize
property_search

View File

@@ -26,8 +26,6 @@ EXPORTS
property_desc_to_buffer @129 NONAME
property_destroy @128 NONAME
property_insert_read @131 NONAME
property_clear_error @573 NONAME
property_get_error @573 NONAME
property_node_create @145 NONAME
property_node_name @150 NONAME
property_node_read @154 NONAME == XCd229cc0000f3

View File

@@ -26,8 +26,6 @@ EXPORTS
property_desc_to_buffer @129 NONAME
property_destroy @128 NONAME
property_insert_read @131 NONAME
property_clear_error @573 NONAME
property_get_error @573 NONAME
property_node_create @145 NONAME
property_node_name @573 NONAME ==
property_node_read @573 NONAME ==

View File

@@ -19,8 +19,6 @@ EXPORTS
property_destroy @125 NONAME
property_desc_to_buffer @126 NONAME
property_insert_read @128 NONAME
property_clear_error @573 NONAME
property_get_error @573 NONAME
property_search @141 NONAME
property_node_create @142 NONAME
property_node_name @147 NONAME == XCnbrep7000092

View File

@@ -19,14 +19,12 @@ EXPORTS
property_destroy @146 NONAME
property_desc_to_buffer @147 NONAME
property_insert_read @149 NONAME
property_clear_error @158 NONAME == XCnbrep700009d
property_get_error @159 NONAME == XCnbrep700009e
property_search @162 NONAME
property_node_create @163 NONAME
property_node_name @168 NONAME == XCnbrep70000a7
property_node_remove @164 NONAME
property_node_type @169 NONAME == XCnbrep70000a8
property_node_clone @165 NONAME == XCnbrep70000a4
property_node_clone @165 NONAME
property_node_traversal @167 NONAME
property_node_refdata @166 NONAME == XCnbrep70000a5
property_node_datasize @171 NONAME == XCnbrep70000aa

View File

@@ -21,8 +21,6 @@ EXPORTS
property_destroy @146 NONAME
property_desc_to_buffer @147 NONAME
property_insert_read @149 NONAME
property_clear_error @158 NONAME == XCgsqzn000009d
property_get_error @159 NONAME == XCgsqzn000009e
property_search @162 NONAME
property_node_create @163 NONAME
property_node_name @168 NONAME == XCgsqzn00000a7

View File

@@ -96,14 +96,4 @@ const char *avs_util_error_str(avs_error error)
}
return avs_util_error_unknown;
}
const char *avs_util_property_error_get_and_clear(struct property *prop)
{
avs_error error;
error = property_get_error(prop);
property_clear_error(prop);
return avs_util_error_str(error);
}

View File

@@ -5,6 +5,4 @@
const char *avs_util_error_str(avs_error error);
const char *avs_util_property_error_get_and_clear(struct property *prop);
#endif
#endif

View File

@@ -8,5 +8,4 @@ libs_camhook := \
src_camhook := \
cam.c \
cam-detect.c \
config-cam.c \

View File

@@ -1,402 +0,0 @@
#define LOG_MODULE "cam-hook"
// clang-format off
// Don't format because the order is important here
#include <windows.h>
#include <initguid.h>
// clang-format on
#include <mfapi.h>
#include <mfidl.h>
#include <mfobjects.h>
#include <cfgmgr32.h>
#include <setupapi.h>
#include <stdio.h>
#include "camhook/cam-detect.h"
#include "util/defs.h"
#include "util/log.h"
#include "util/str.h"
EXTERN_GUID(
MY_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
0xc60ac5fe,
0x252a,
0x478f,
0xa0,
0xef,
0xbc,
0x8f,
0xa5,
0xf7,
0xca,
0xd3);
EXTERN_GUID(
MY_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID,
0x8ac3587a,
0x4ae7,
0x42d8,
0x99,
0xe0,
0x0a,
0x60,
0x13,
0xee,
0xf9,
0x0f);
EXTERN_GUID(
MY_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
0x58f0aad8,
0x22bf,
0x4f8a,
0xbb,
0x3d,
0xd2,
0xc4,
0x97,
0x8c,
0x6e,
0x2f);
// define ourselves cause mingw has these wrong
bool check_four(const char inA[4], const char inB[4])
{
return (*(uint32_t *) inA == *(uint32_t *) inB);
}
char *grab_next_camera_id(char *buffer, size_t bsz)
{
static size_t gotten = 0;
IMFAttributes *pAttributes = NULL;
IMFActivate **ppDevices = NULL;
buffer[0] = '\0';
HRESULT hr = MFCreateAttributes(&pAttributes, 1);
if (FAILED(hr)) {
log_info("MFCreateAttributes failed: %ld", hr);
goto done;
}
hr = pAttributes->lpVtbl->SetGUID(
pAttributes,
&MY_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
&MY_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
if (FAILED(hr)) {
log_info("SetGUID failed: %ld", hr);
goto done;
}
UINT32 count;
hr = MFEnumDeviceSources(pAttributes, &ppDevices, &count);
if (FAILED(hr)) {
log_info("MFEnumDeviceSources failed: %ld", hr);
goto done;
}
if (count <= gotten) {
log_info("gotten failed: %d < %d", count, (int) gotten);
// not enough remaining
goto done;
}
wchar_t wSymLink[CAMERA_DATA_STRING_SIZE];
UINT32 sz;
hr = ppDevices[gotten]->lpVtbl->GetString(
ppDevices[gotten],
&MY_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
wSymLink,
CAMERA_DATA_STRING_SIZE,
&sz);
if (FAILED(hr)) {
log_info("GetString failed: %ld", hr);
goto done;
}
wcstombs(buffer, wSymLink, bsz);
log_info("Detected webcam: %s\n", buffer);
++gotten;
done:
if (pAttributes) {
pAttributes->lpVtbl->Release(pAttributes);
}
for (DWORD i = 0; i < count; i++) {
if (ppDevices != NULL && ppDevices[i]) {
ppDevices[i]->lpVtbl->Release(ppDevices[i]);
}
}
CoTaskMemFree(ppDevices);
return buffer;
}
bool convert_sym_to_path(const char *sym, char *path)
{
HDEVINFO DeviceInfoSet = SetupDiCreateDeviceInfoList(NULL, NULL);
if (DeviceInfoSet == INVALID_HANDLE_VALUE) {
log_info("Could not open SetupDiCreateDeviceInfoList\n");
return 0;
}
SP_DEVICE_INTERFACE_DATA DeviceInterfaceData = {0};
DeviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
if (!SetupDiOpenDeviceInterfaceA(
DeviceInfoSet, sym, 0, &DeviceInterfaceData)) {
log_info("Could not SetupDiOpenDeviceInterfaceA\n");
return 0;
}
SP_DEVINFO_DATA DeviceInfoData = {0};
DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
if (!SetupDiGetDeviceInterfaceDetailA(
DeviceInfoSet,
&DeviceInterfaceData,
NULL,
0,
NULL,
&DeviceInfoData)) {
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
log_info("Could not SetupDiGetDeviceInterfaceDetailA\n");
return 0;
}
}
DWORD sz;
if (!SetupDiGetDeviceInstanceIdA(
DeviceInfoSet,
&DeviceInfoData,
path,
CAMERA_DATA_STRING_SIZE,
&sz)) {
log_info("Could not SetupDiGetDeviceInstanceIdA\n");
return 0;
}
if (DeviceInfoSet != INVALID_HANDLE_VALUE) {
if (!SetupDiDeleteDeviceInterfaceData(
DeviceInfoSet, &DeviceInterfaceData)) {
log_info("Could not SetupDiDeleteDeviceInterfaceData\n");
return 0;
}
SetupDiDestroyDeviceInfoList(DeviceInfoSet);
}
return 1;
}
void strtolower(char *str)
{
for (size_t i = 0; str[i]; i++) {
str[i] = tolower(str[i]);
}
}
bool convert_path_to_fakesym(const char *path, wchar_t *sym, char *extra_o)
{
char root[16] = {0};
char vidstr[16] = {0};
char pidstr[16] = {0};
char mistr[16] = {0};
char extra[64] = {0};
sscanf(
path,
"%[^\\]\\%[^&]&%[^&]&%[^\\]\\%s",
root,
vidstr,
pidstr,
mistr,
extra);
strcpy(extra_o, extra);
strtolower(root);
strtolower(vidstr);
strtolower(pidstr);
strtolower(mistr);
strtolower(extra);
char buffer[CAMERA_DATA_STRING_SIZE];
snprintf(
buffer,
CAMERA_DATA_STRING_SIZE,
"\\\\?\\%s#%s&%s&%s#%s",
root,
vidstr,
pidstr,
mistr,
extra);
mbstowcs(sym, buffer, CAMERA_DATA_STRING_SIZE);
return true;
}
void fill_cam_struct(struct camera_data *data, const char *devid)
{
char buffer[CAMERA_DATA_STRING_SIZE];
data->setup = false;
if (!devid || strlen(devid) == 0) {
devid = grab_next_camera_id(buffer, CAMERA_DATA_STRING_SIZE);
}
if (!devid || strlen(devid) == 0) {
// no more cameras remain?
return;
}
if (strlen(devid) >= CAMERA_DATA_STRING_SIZE) {
// error probably log something?
return;
}
// detect input type
if (check_four(devid, "\\\\?\\")) {
// SYMBOLIC_LINK
if (!convert_sym_to_path(devid, data->deviceInstancePath)) {
log_info("Could not convert %s to path", devid);
return;
}
} else if (check_four(devid, "USB\\")) {
// Device instance path
strcpy(data->deviceInstancePath, devid);
// continue
} else {
// UNKNOWN ENTRY
log_info("UNK: %s", devid);
log_info("Please enter the device instance path");
return;
}
if (!convert_path_to_fakesym(
data->deviceInstancePath,
data->deviceSymbolicLink,
data->extra_upper)) {
log_info("Could not convert %s to sym", data->deviceInstancePath);
return;
}
log_info("dev path: %s", data->deviceInstancePath);
char* vid_str = strstr(data->deviceInstancePath, "VID_");
char* pid_str = strstr(data->deviceInstancePath, "PID_");
if (!vid_str || !pid_str) {
log_info("Could not parse VID/PID in %s", data->deviceInstancePath);
return;
}
data->vid = strtol(vid_str + 4, NULL, 16);
data->pid = strtol(pid_str + 4, NULL, 16);
log_info("vid: %04x", data->vid);
log_info("pid: %04x", data->pid);
// locate device nodes
DEVINST dnDevInst;
DEVINST parentDev;
CONFIGRET cmret;
cmret = CM_Locate_DevNodeA(
&dnDevInst, data->deviceInstancePath, CM_LOCATE_DEVNODE_NORMAL);
if (cmret != CR_SUCCESS) {
log_info("CM_Locate_DevNodeA fail: %s", data->deviceInstancePath);
return;
}
cmret = CM_Get_Parent(&parentDev, dnDevInst, 0);
if (cmret != CR_SUCCESS) {
log_info("CM_Get_Parent fail: %s", data->deviceInstancePath);
return;
}
cmret = CM_Get_Device_IDA(
parentDev, data->parent_deviceInstancePath, CAMERA_DATA_STRING_SIZE, 0);
if (cmret != CR_SUCCESS) {
log_info("CM_Get_Device_IDA parent fail: %s", data->deviceInstancePath);
return;
}
ULONG szAddr;
ULONG szDesc;
ULONG szDriverKey;
szAddr = 4;
cmret = CM_Get_DevNode_Registry_PropertyA(
dnDevInst, CM_DRP_ADDRESS, NULL, &data->address, &szAddr, 0);
if (cmret != CR_SUCCESS) {
log_info(
"CM_Get_DevNode_Registry_PropertyA CM_DRP_ADDRESS fail: %s",
data->deviceInstancePath);
return;
}
szDesc = CAMERA_DATA_STRING_SIZE;
cmret = CM_Get_DevNode_Registry_PropertyA(
dnDevInst, CM_DRP_DEVICEDESC, NULL, &data->name, &szDesc, 0);
if (cmret != CR_SUCCESS) {
log_info(
"CM_Get_DevNode_Registry_PropertyA CM_DRP_DEVICEDESC fail: %s",
data->deviceInstancePath);
return;
}
szAddr = 4;
cmret = CM_Get_DevNode_Registry_PropertyA(
parentDev, CM_DRP_ADDRESS, NULL, &data->parent_address, &szAddr, 0);
if (cmret != CR_SUCCESS) {
log_info(
"CM_Get_DevNode_Registry_PropertyA CM_DRP_ADDRESS parent fail: %s",
data->deviceInstancePath);
return;
}
szDesc = CAMERA_DATA_STRING_SIZE;
cmret = CM_Get_DevNode_Registry_PropertyA(
parentDev, CM_DRP_DEVICEDESC, NULL, &data->parent_name, &szDesc, 0);
if (cmret != CR_SUCCESS) {
log_info(
"CM_Get_DevNode_Registry_PropertyA CM_DRP_DEVICEDESC parent fail: %s",
data->deviceInstancePath);
return;
}
szDriverKey = CAMERA_DATA_STRING_SIZE;
cmret = CM_Get_DevNode_Registry_PropertyA(
parentDev, CM_DRP_DRIVER, NULL, &data->parent_driverKey, &szDriverKey, 0);
if (cmret != CR_SUCCESS) {
log_info(
"CM_Get_DevNode_Registry_PropertyA CM_DRP_DRIVER parent fail: %s",
data->deviceInstancePath);
return;
}
log_info("Found %s @ %d", data->name, data->address);
log_info("Parent %s @ %d %s", data->parent_name, data->parent_address, data->parent_driverKey);
data->setup = true;
}

View File

@@ -1,34 +0,0 @@
#ifndef CAMHOOK_CAM_DETECT_H
#define CAMHOOK_CAM_DETECT_H
#include <stdbool.h>
#include <stdint.h>
#include <wchar.h>
#define CAMERA_DATA_STRING_SIZE 0x100
struct camera_data {
bool setup;
char name[CAMERA_DATA_STRING_SIZE];
char deviceInstancePath[CAMERA_DATA_STRING_SIZE];
wchar_t deviceSymbolicLink[CAMERA_DATA_STRING_SIZE];
char extra_upper[CAMERA_DATA_STRING_SIZE];
int address;
char parent_name[CAMERA_DATA_STRING_SIZE];
char parent_driverKey[CAMERA_DATA_STRING_SIZE];
char parent_deviceInstancePath[CAMERA_DATA_STRING_SIZE];
int parent_address;
int16_t vid;
int16_t pid;
bool fake_addressed;
int fake_address;
bool fake_located;
size_t fake_located_node;
};
void fill_cam_struct(struct camera_data *data, const char *devid);
#endif

View File

@@ -4,8 +4,6 @@
// Don't format because the order is important here
#include <windows.h>
#include <initguid.h>
#include <winioctl.h>
#include <usbioctl.h>
// clang-format on
#include <mfapi.h>
@@ -21,25 +19,80 @@
#include "hook/table.h"
#include "camhook/cam.h"
#include "camhook/cam-detect.h"
#include "util/defs.h"
#include "util/log.h"
#include "util/str.h"
#include "util/time.h"
#define CAMHOOK_NUM_LAYOUTS 3
#define CAMERA_DATA_STRING_SIZE 0x100
static struct camera_data camData[CAMHOOK_CONFIG_CAM_MAX];
int camAddresses[CAMHOOK_NUM_LAYOUTS][CAMHOOK_CONFIG_CAM_MAX] = {
{1, 7},
{4, 9},
{4, 3},
EXTERN_GUID(
MY_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
0xc60ac5fe,
0x252a,
0x478f,
0xa0,
0xef,
0xbc,
0x8f,
0xa5,
0xf7,
0xca,
0xd3);
EXTERN_GUID(
MY_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID,
0x8ac3587a,
0x4ae7,
0x42d8,
0x99,
0xe0,
0x0a,
0x60,
0x13,
0xee,
0xf9,
0x0f);
EXTERN_GUID(
MY_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
0x58f0aad8,
0x22bf,
0x4f8a,
0xbb,
0x3d,
0xd2,
0xc4,
0x97,
0x8c,
0x6e,
0x2f);
// define ourselves cause mingw has these wrong
struct CameraData {
bool setup;
char name[CAMERA_DATA_STRING_SIZE];
char deviceInstancePath[CAMERA_DATA_STRING_SIZE];
wchar_t deviceSymbolicLink[CAMERA_DATA_STRING_SIZE];
char extra_upper[CAMERA_DATA_STRING_SIZE];
int address;
char parent_name[CAMERA_DATA_STRING_SIZE];
char parent_deviceInstancePath[CAMERA_DATA_STRING_SIZE];
int parent_address;
bool fake_addressed;
int fake_address;
bool fake_located;
size_t fake_located_node;
};
static struct CameraData camData[CAMHOOK_CONFIG_CAM_MAX];
int camAddresses[CAMHOOK_CONFIG_CAM_MAX] = {
1,
7,
};
static size_t num_addressed_cams = 0;
static size_t num_located_cams = 0;
static int camhook_port_layout = 0;
static enum camhook_version camhook_version = CAMHOOK_VERSION_OLD;
static CONFIGRET my_CM_Locate_DevNodeA(
PDEVINST pdnDevInst, DEVINSTID_A pDeviceID, ULONG ulFlags);
@@ -103,26 +156,6 @@ static HDEVINFO my_SetupDiGetClassDevsA(
static HDEVINFO (*real_SetupDiGetClassDevsA)(
CONST GUID *ClassGuid, PCSTR Enumerator, HWND hwndParent, DWORD Flags);
static BOOL STDCALL my_DeviceIoControl(
HANDLE hFile,
uint32_t dwIoControlCode,
void *lpInBuffer,
uint32_t nInBufferSize,
void *lpOutBuffer,
uint32_t nOutBufferSize,
uint32_t *lpBytesReturned,
OVERLAPPED *lpOverlapped);
static BOOL(STDCALL *real_DeviceIoControl)(
HANDLE fd,
uint32_t code,
void *in_bytes,
uint32_t in_nbytes,
void *out_bytes,
uint32_t out_nbytes,
uint32_t *out_returned,
OVERLAPPED *ovl);
static const struct hook_symbol camhook_cfgmgr32_syms[] = {
{.name = "CM_Locate_DevNodeA",
.patch = my_CM_Locate_DevNodeA,
@@ -147,24 +180,12 @@ static const struct hook_symbol camhook_cfgmgr32_syms[] = {
.link = (void **) &real_SetupDiGetClassDevsA},
};
static const struct hook_symbol camhook_cfgmgr32_syms_new[] = {
{.name = "CM_Locate_DevNodeA",
.patch = my_CM_Locate_DevNodeA,
.link = (void **) &real_CM_Locate_DevNodeA},
};
static const struct hook_symbol camhook_mf_syms[] = {
{.name = "MFEnumDeviceSources",
.patch = my_MFEnumDeviceSources,
.link = (void **) &real_MFEnumDeviceSources},
};
static struct hook_symbol camhook_ioctl_syms[] = {
{.name = "DeviceIoControl",
.patch = my_DeviceIoControl,
.link = (void *) &real_DeviceIoControl},
};
DEVINST camhook_custom_nodes[CAMHOOK_CONFIG_CAM_MAX] = {
0x04040004,
0x04040008,
@@ -188,42 +209,25 @@ my_CM_Locate_DevNodeA(PDEVINST pdnDevInst, DEVINSTID_A pDeviceID, ULONG ulFlags)
log_info("seeking: %s", pDeviceID);
for (size_t i = 0; i < CAMHOOK_CONFIG_CAM_MAX; ++i) {
if (camData[i].setup) {
if (camhook_version == CAMHOOK_VERSION_OLD) {
snprintf(
builtString,
CAMERA_DATA_STRING_SIZE,
"USB\\VID_288C&PID_0002&MI_00\\%s",
camData[i].extra_upper);
} else if (camhook_version == CAMHOOK_VERSION_NEW) {
snprintf(
builtString,
CAMERA_DATA_STRING_SIZE,
"USB\\VID_05A3&PID_9230&MI_00\\%s",
camData[i].extra_upper);
}
snprintf(
builtString,
CAMERA_DATA_STRING_SIZE,
"USB\\VID_288C&PID_0002&MI_00\\%s",
camData[i].extra_upper);
log_info("built: %s", builtString);
if (strcmp(pDeviceID, builtString) == 0) {
if (camhook_version == CAMHOOK_VERSION_OLD) {
if (!camData[i].fake_located) {
camData[i].fake_located_node = num_located_cams;
camData[i].fake_located = true;
++num_located_cams;
}
log_info(
"Injecting custom device %d to node %x",
(int) i,
(int) camData[i].fake_located_node);
*pdnDevInst =
camhook_custom_nodes[camData[i].fake_located_node];
return CR_SUCCESS;
} else if (camhook_version == CAMHOOK_VERSION_NEW) {
// inject original device
log_info(
"Injecting original device %d %s -> %s",
(int) i, pDeviceID, camData[i].deviceInstancePath);
pDeviceID = camData[i].deviceInstancePath;
if (!camData[i].fake_located) {
camData[i].fake_located_node = num_located_cams;
camData[i].fake_located = true;
++num_located_cams;
}
log_info(
"Injecting custom device %d to node %x",
(int) i,
(int) camData[i].fake_located_node);
*pdnDevInst =
camhook_custom_nodes[camData[i].fake_located_node];
return CR_SUCCESS;
}
}
}
@@ -274,9 +278,9 @@ HRESULT my_GetAllocatedString(
IMFActivate *self, REFGUID guidKey, LPWSTR *ppwszValue, UINT32 *pcchLength)
{
HRESULT ret;
// log_info("Inside: %s", __FUNCTION__);
log_info("Inside: %s", __FUNCTION__);
// should probably check GUID == MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, oh well
// should probably check GUID, oh well
ret = real_GetAllocatedString(self, guidKey, ppwszValue, pcchLength);
char *pMBBuffer = (char *) malloc(0x100);
wcstombs(pMBBuffer, *ppwszValue, 0x100);
@@ -295,35 +299,19 @@ HRESULT my_GetAllocatedString(
// if matches, replace with target device ID
if (pwc) {
if (camhook_version == CAMHOOK_VERSION_OLD) {
// \\?\usb#vid_288c&pid_0002&mi_00
pwc[12] = L'2';
pwc[13] = L'8';
pwc[14] = L'8';
pwc[15] = L'c';
// \\?\usb#vid_288c&pid_0002&mi_00
pwc[12] = L'2';
pwc[13] = L'8';
pwc[14] = L'8';
pwc[15] = L'c';
pwc[21] = L'0';
pwc[22] = L'0';
pwc[23] = L'0';
pwc[24] = L'2';
pwc[21] = L'0';
pwc[22] = L'0';
pwc[23] = L'0';
pwc[24] = L'2';
pwc[29] = L'0';
pwc[30] = L'0';
} else if (camhook_version == CAMHOOK_VERSION_NEW) {
// \\?\usb#vid_05a3&pid_9230&mi_00
pwc[12] = L'0';
pwc[13] = L'5';
pwc[14] = L'a';
pwc[15] = L'3';
pwc[21] = L'9';
pwc[22] = L'2';
pwc[23] = L'3';
pwc[24] = L'0';
pwc[29] = L'0';
pwc[30] = L'0';
}
pwc[29] = L'0';
pwc[30] = L'0';
wcstombs(pMBBuffer, *ppwszValue, 0x100);
log_info("Replaced: %s", pMBBuffer);
@@ -346,7 +334,7 @@ static HRESULT my_MFEnumDeviceSources(
HRESULT ret;
// log_info("Inside: %s", __FUNCTION__);
log_info("Inside: %s", __FUNCTION__);
ret = real_MFEnumDeviceSources(
pAttributes, pppSourceActivate, pcSourceActivate);
nsrcs = *pcSourceActivate;
@@ -449,9 +437,8 @@ static BOOL my_SetupDiGetDeviceRegistryPropertyA(
if (camData[i].setup) {
if (addr == camData[i].parent_address) {
if (!camData[i].fake_addressed) {
// old style always uses set 0
camData[i].fake_address =
camAddresses[0][num_addressed_cams];
camAddresses[num_addressed_cams];
camData[i].fake_addressed = true;
++num_addressed_cams;
}
@@ -504,180 +491,315 @@ static HDEVINFO my_SetupDiGetClassDevsA(
return real_SetupDiGetClassDevsA(ClassGuid, Enumerator, hwndParent, Flags);
}
ULONG get_matching_device_replacement_id(
HANDLE hFile,
ULONG ConnectionIndex,
USB_DEVICE_DESCRIPTOR* desc)
bool check_four(const char inA[4], const char inB[4])
{
ULONG replacement_id = 0;
for (size_t i = 0; i < CAMHOOK_CONFIG_CAM_MAX; ++i) {
if (camData[i].setup) {
// log_info("Checking %lu %04x %04x vs %lu %04x %04x",
// ConnectionIndex, desc->idVendor, desc->idProduct,
// camData[i].address, camData[i].vid, camData[i].pid);
if (
ConnectionIndex == camData[i].parent_address &&
desc->idVendor == camData[i].vid &&
desc->idProduct == camData[i].pid
) {
// do secondary check for driver key using
// IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME
USB_NODE_CONNECTION_DRIVERKEY_NAME req;
req.ActualLength = 0;
req.ConnectionIndex = ConnectionIndex;
req.DriverKeyName[0] = '\0';
DWORD nBytes;
if (!DeviceIoControl(
hFile,
IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME,
&req,
sizeof(req),
&req,
sizeof(req),
&nBytes,
0LL
)) {
log_warning(
"Failed to get driver key name size for device %04x %04x",
desc->idVendor,
desc->idProduct);
continue;
}
if (req.ActualLength <= sizeof(req)) {
log_warning(
"Driver key name size too small for device %04x %04x",
desc->idVendor,
desc->idProduct);
continue;
}
nBytes = req.ActualLength;
USB_NODE_CONNECTION_DRIVERKEY_NAME *driverKeyNameW = malloc(nBytes);
if (!driverKeyNameW) {
log_warning("Failed to allocate driver key name buffer for device %04x %04x",
desc->idVendor,
desc->idProduct);
continue;
}
driverKeyNameW->ActualLength = 0;
driverKeyNameW->ConnectionIndex = ConnectionIndex;
driverKeyNameW->DriverKeyName[0] = '\0';
if (!DeviceIoControl(
hFile,
IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME,
driverKeyNameW,
nBytes,
driverKeyNameW,
nBytes,
&nBytes,
0LL
)) {
log_warning(
"Failed to get driver key name for device %04x %04x",
desc->idVendor,
desc->idProduct);
continue;
}
char *driverKeyNameA = NULL;
wstr_narrow(driverKeyNameW->DriverKeyName, &driverKeyNameA);
free(driverKeyNameW);
if (strcmp(driverKeyNameA, camData[i].parent_driverKey) != 0) {
// log just in case?
log_info(
"Driver key name mismatch for device %04x %04x %s != %s",
desc->idVendor,
desc->idProduct,
driverKeyNameA,
camData[i].parent_driverKey);
if (driverKeyNameA) {
free(driverKeyNameA);
}
continue;
}
log_info(
"Replacing device @ %lu with %d for %04x %04x %s",
ConnectionIndex,
camAddresses[camhook_port_layout][i],
desc->idVendor,
desc->idProduct,
driverKeyNameA);
if (driverKeyNameA) {
free(driverKeyNameA);
}
replacement_id = camAddresses[camhook_port_layout][i];
break;
}
}
}
return replacement_id;
return (*(uint32_t *) inA == *(uint32_t *) inB);
}
static BOOL STDCALL my_DeviceIoControl(
HANDLE hFile,
uint32_t dwIoControlCode,
void *lpInBuffer,
uint32_t nInBufferSize,
void *lpOutBuffer,
uint32_t nOutBufferSize,
uint32_t *lpBytesReturned,
OVERLAPPED *lpOverlapped)
char *grab_next_camera_id(char *buffer, size_t bsz)
{
BOOL res;
static size_t gotten = 0;
res = real_DeviceIoControl(
hFile,
dwIoControlCode,
lpInBuffer,
nInBufferSize,
lpOutBuffer,
nOutBufferSize,
lpBytesReturned,
lpOverlapped);
IMFAttributes *pAttributes = NULL;
IMFActivate **ppDevices = NULL;
// if error just return
if (!res) {
return res;
buffer[0] = '\0';
HRESULT hr = MFCreateAttributes(&pAttributes, 1);
if (FAILED(hr)) {
log_info("MFCreateAttributes failed: %ld", hr);
goto done;
}
// detect IOCTL_USB_GET_NODE_CONNECTION_INFORMATION (_EX)
// we don't bother faking the rest as it's never read
if (dwIoControlCode == IOCTL_USB_GET_NODE_CONNECTION_INFORMATION) {
USB_NODE_CONNECTION_INFORMATION *connectionInfo = lpOutBuffer;
hr = pAttributes->lpVtbl->SetGUID(
pAttributes,
&MY_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
&MY_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
if (FAILED(hr)) {
log_info("SetGUID failed: %ld", hr);
goto done;
}
ULONG replacement_id = get_matching_device_replacement_id(
hFile, connectionInfo->ConnectionIndex, &connectionInfo->DeviceDescriptor);
if (replacement_id > 0) {
connectionInfo->ConnectionIndex = replacement_id;
}
} else if (dwIoControlCode == IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX) {
USB_NODE_CONNECTION_INFORMATION_EX *connectionInfoEx = lpOutBuffer;
UINT32 count;
hr = MFEnumDeviceSources(pAttributes, &ppDevices, &count);
ULONG replacement_id = get_matching_device_replacement_id(
hFile, connectionInfoEx->ConnectionIndex, &connectionInfoEx->DeviceDescriptor);
if (replacement_id > 0) {
connectionInfoEx->ConnectionIndex = replacement_id;
if (FAILED(hr)) {
log_info("MFEnumDeviceSources failed: %ld", hr);
goto done;
}
if (count <= gotten) {
log_info("gotten failed: %d < %d", count, (int) gotten);
// not enough remaining
goto done;
}
wchar_t wSymLink[CAMERA_DATA_STRING_SIZE];
UINT32 sz;
hr = ppDevices[gotten]->lpVtbl->GetString(
ppDevices[gotten],
&MY_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
wSymLink,
CAMERA_DATA_STRING_SIZE,
&sz);
if (FAILED(hr)) {
log_info("GetString failed: %ld", hr);
goto done;
}
wcstombs(buffer, wSymLink, bsz);
log_info("Detected webcam: %s\n", buffer);
++gotten;
done:
if (pAttributes) {
pAttributes->lpVtbl->Release(pAttributes);
}
for (DWORD i = 0; i < count; i++) {
if (ppDevices != NULL && ppDevices[i]) {
ppDevices[i]->lpVtbl->Release(ppDevices[i]);
}
}
return res;
CoTaskMemFree(ppDevices);
return buffer;
}
void camhook_set_version(enum camhook_version version) {
camhook_version = version;
bool convert_sym_to_path(const char *sym, char *path)
{
HDEVINFO DeviceInfoSet = SetupDiCreateDeviceInfoList(NULL, NULL);
if (DeviceInfoSet == INVALID_HANDLE_VALUE) {
log_info("Could not open SetupDiCreateDeviceInfoList\n");
return 0;
}
SP_DEVICE_INTERFACE_DATA DeviceInterfaceData = {0};
DeviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
if (!SetupDiOpenDeviceInterfaceA(
DeviceInfoSet, sym, 0, &DeviceInterfaceData)) {
log_info("Could not SetupDiOpenDeviceInterfaceA\n");
return 0;
}
SP_DEVINFO_DATA DeviceInfoData = {0};
DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
if (!SetupDiGetDeviceInterfaceDetailA(
DeviceInfoSet,
&DeviceInterfaceData,
NULL,
0,
NULL,
&DeviceInfoData)) {
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
log_info("Could not SetupDiGetDeviceInterfaceDetailA\n");
return 0;
}
}
DWORD sz;
if (!SetupDiGetDeviceInstanceIdA(
DeviceInfoSet,
&DeviceInfoData,
path,
CAMERA_DATA_STRING_SIZE,
&sz)) {
log_info("Could not SetupDiGetDeviceInstanceIdA\n");
return 0;
}
if (DeviceInfoSet != INVALID_HANDLE_VALUE) {
if (!SetupDiDeleteDeviceInterfaceData(
DeviceInfoSet, &DeviceInterfaceData)) {
log_info("Could not SetupDiDeleteDeviceInterfaceData\n");
return 0;
}
SetupDiDestroyDeviceInfoList(DeviceInfoSet);
}
return 1;
}
void strtolower(char *str)
{
for (size_t i = 0; str[i]; i++) {
str[i] = tolower(str[i]);
}
}
bool convert_path_to_fakesym(const char *path, wchar_t *sym, char *extra_o)
{
char root[16] = {0};
char vidstr[16] = {0};
char pidstr[16] = {0};
char mistr[16] = {0};
char extra[64] = {0};
sscanf(
path,
"%[^\\]\\%[^&]&%[^&]&%[^\\]\\%s",
root,
vidstr,
pidstr,
mistr,
extra);
strcpy(extra_o, extra);
strtolower(root);
strtolower(vidstr);
strtolower(pidstr);
strtolower(mistr);
strtolower(extra);
char buffer[CAMERA_DATA_STRING_SIZE];
snprintf(
buffer,
CAMERA_DATA_STRING_SIZE,
"\\\\?\\%s#%s&%s&%s#%s",
root,
vidstr,
pidstr,
mistr,
extra);
mbstowcs(sym, buffer, CAMERA_DATA_STRING_SIZE);
return true;
}
void fill_cam_struct(struct CameraData *data, const char *devid)
{
char buffer[CAMERA_DATA_STRING_SIZE];
data->setup = false;
if (!devid || strlen(devid) == 0) {
devid = grab_next_camera_id(buffer, CAMERA_DATA_STRING_SIZE);
}
if (!devid || strlen(devid) == 0) {
// no more cameras remain?
return;
}
if (strlen(devid) >= CAMERA_DATA_STRING_SIZE) {
// error probably log something?
return;
}
// detect input type
if (check_four(devid, "\\\\?\\")) {
// SYMBOLIC_LINK
if (!convert_sym_to_path(devid, data->deviceInstancePath)) {
log_info("Could not convert %s to path", devid);
return;
}
} else if (check_four(devid, "USB\\")) {
// Device instance path
strcpy(data->deviceInstancePath, devid);
// continue
} else {
// UNKNOWN ENTRY
log_info("UNK: %s", devid);
log_info("Please enter the device instance path");
return;
}
if (!convert_path_to_fakesym(
data->deviceInstancePath,
data->deviceSymbolicLink,
data->extra_upper)) {
log_info("Could not convert %s to sym", data->deviceInstancePath);
return;
}
log_info("dev path: %s", data->deviceInstancePath);
// locate device nodes
DEVINST dnDevInst;
DEVINST parentDev;
CONFIGRET cmret;
cmret = CM_Locate_DevNodeA(
&dnDevInst, data->deviceInstancePath, CM_LOCATE_DEVNODE_NORMAL);
if (cmret != CR_SUCCESS) {
log_info("CM_Locate_DevNodeA fail: %s", data->deviceInstancePath);
return;
}
cmret = CM_Get_Parent(&parentDev, dnDevInst, 0);
if (cmret != CR_SUCCESS) {
log_info("CM_Get_Parent fail: %s", data->deviceInstancePath);
return;
}
cmret = CM_Get_Device_IDA(
parentDev, data->parent_deviceInstancePath, CAMERA_DATA_STRING_SIZE, 0);
if (cmret != CR_SUCCESS) {
log_info("CM_Get_Device_IDA parent fail: %s", data->deviceInstancePath);
return;
}
ULONG szAddr;
ULONG szDesc;
szAddr = 4;
szDesc = CAMERA_DATA_STRING_SIZE;
cmret = CM_Get_DevNode_Registry_PropertyA(
dnDevInst, CM_DRP_ADDRESS, NULL, &data->address, &szAddr, 0);
if (cmret != CR_SUCCESS) {
log_info(
"CM_Get_DevNode_Registry_PropertyA fail: %s",
data->deviceInstancePath);
return;
}
cmret = CM_Get_DevNode_Registry_PropertyA(
dnDevInst, CM_DRP_DEVICEDESC, NULL, &data->name, &szDesc, 0);
if (cmret != CR_SUCCESS) {
log_info(
"CM_Get_DevNode_Registry_PropertyA fail: %s",
data->deviceInstancePath);
return;
}
szAddr = 4;
szDesc = CAMERA_DATA_STRING_SIZE;
cmret = CM_Get_DevNode_Registry_PropertyA(
parentDev, CM_DRP_ADDRESS, NULL, &data->parent_address, &szAddr, 0);
if (cmret != CR_SUCCESS) {
log_info(
"CM_Get_DevNode_Registry_PropertyA parent fail: %s",
data->deviceInstancePath);
return;
}
cmret = CM_Get_DevNode_Registry_PropertyA(
parentDev, CM_DRP_DEVICEDESC, NULL, &data->parent_name, &szDesc, 0);
if (cmret != CR_SUCCESS) {
log_info(
"CM_Get_DevNode_Registry_PropertyA parent fail: %s",
data->deviceInstancePath);
return;
}
log_info("Found %s @ %d", data->name, data->address);
log_info("Parent %s @ %d", data->parent_name, data->parent_address);
data->setup = true;
}
void camhook_init(struct camhook_config_cam *config_cam)
@@ -705,42 +827,15 @@ void camhook_init(struct camhook_config_cam *config_cam)
}
}
camhook_port_layout = config_cam->port_layout;
if (camhook_port_layout < 0 || camhook_port_layout >= CAMHOOK_NUM_LAYOUTS) {
camhook_port_layout = 0;
}
if (num_setup > 0) {
// always
hook_table_apply(
NULL,
"setupapi.dll",
camhook_cfgmgr32_syms,
lengthof(camhook_cfgmgr32_syms));
hook_table_apply(
NULL, "Mf.dll", camhook_mf_syms, lengthof(camhook_mf_syms));
if (camhook_version == CAMHOOK_VERSION_OLD) {
hook_table_apply(
NULL,
"cfgmgr32.dll",
camhook_cfgmgr32_syms,
lengthof(camhook_cfgmgr32_syms));
} else if (camhook_version == CAMHOOK_VERSION_NEW) {
// for CAMHOOK_VERSION_NEW we restore original VID/PID
// so the parent lookup succeeds later
// yes the DLL moved???
hook_table_apply(
NULL,
"setupapi.dll",
camhook_cfgmgr32_syms_new,
lengthof(camhook_cfgmgr32_syms_new));
// they copied / forked usb/usbview/enum.c
// however they don't use most of it
// so we only need DeviceIoControl to inject port #
hook_table_apply(
NULL,
"kernel32.dll",
camhook_ioctl_syms,
lengthof(camhook_ioctl_syms));
}
log_info("Inserted cam hooks for %d cams", (int) num_setup);
// If the user has manually disabled all cams, don't print this in the
// log

View File

@@ -3,15 +3,6 @@
#include "camhook/config-cam.h"
// unused to control which camhook version is being used
// defaults to CAMHOOK_VERSION_OLD
enum camhook_version {
CAMHOOK_VERSION_OLD,
CAMHOOK_VERSION_NEW,
};
void camhook_set_version(enum camhook_version version);
void camhook_init(struct camhook_config_cam *config_cam);
void camhook_fini(void);

View File

@@ -7,9 +7,6 @@
#define CAMHOOK_CONFIG_CAM_DISABLE_EMU_KEY "cam.disable_emu"
#define CAMHOOK_CONFIG_CAM_DEFAULT_DISABLE_EMU_VALUE false
#define CAMHOOK_CONFIG_CAM_PORT_LAYOUT_KEY "cam.port_layout"
#define CAMHOOK_CONFIG_CAM_DEFAULT_PORT_LAYOUT_VALUE 0
// the following four arrays are based on CAMHOOK_CONFIG_CAM_MAX
// please insert more elements if more cams are added
const char *camhook_config_disable_camera[CAMHOOK_CONFIG_CAM_MAX] = {
@@ -33,7 +30,7 @@ const char *camhook_config_device_default_values[CAMHOOK_CONFIG_CAM_MAX] = {
"",
};
void camhook_config_cam_init(struct cconfig *config, size_t num_cams, bool use_port_layout)
void camhook_config_cam_init(struct cconfig *config, size_t num_cams)
{
cconfig_util_set_bool(
config,
@@ -41,14 +38,6 @@ void camhook_config_cam_init(struct cconfig *config, size_t num_cams, bool use_p
CAMHOOK_CONFIG_CAM_DEFAULT_DISABLE_EMU_VALUE,
"Disables the camera emulation");
if (use_port_layout) {
cconfig_util_set_int(
config,
CAMHOOK_CONFIG_CAM_PORT_LAYOUT_KEY,
CAMHOOK_CONFIG_CAM_DEFAULT_PORT_LAYOUT_VALUE,
"Camera port layout (0 = LDJ, 1 = CLDJ/TDJ-JA, 2 = TDJ-JB)");
}
for (size_t i = 0; i < num_cams; ++i) {
cconfig_util_set_bool(
config,
@@ -67,8 +56,7 @@ void camhook_config_cam_init(struct cconfig *config, size_t num_cams, bool use_p
void camhook_config_cam_get(
struct camhook_config_cam *config_cam,
struct cconfig *config,
size_t num_cams,
bool use_port_layout)
size_t num_cams)
{
config_cam->num_devices = num_cams;
@@ -83,21 +71,6 @@ void camhook_config_cam_get(
CAMHOOK_CONFIG_CAM_DISABLE_EMU_KEY,
CAMHOOK_CONFIG_CAM_DEFAULT_DISABLE_EMU_VALUE);
}
if (use_port_layout) {
if (!cconfig_util_get_int(
config,
CAMHOOK_CONFIG_CAM_PORT_LAYOUT_KEY,
&config_cam->port_layout,
CAMHOOK_CONFIG_CAM_DEFAULT_PORT_LAYOUT_VALUE)) {
log_warning(
"Invalid value for key '%s' specified, fallback "
"to default '%d'",
CAMHOOK_CONFIG_CAM_PORT_LAYOUT_KEY,
CAMHOOK_CONFIG_CAM_DEFAULT_PORT_LAYOUT_VALUE);
}
}
for (size_t i = 0; i < num_cams; ++i) {
if (!cconfig_util_get_bool(
config,

View File

@@ -10,20 +10,15 @@
struct camhook_config_cam {
bool disable_emu;
size_t num_devices;
int port_layout;
char device_id[CAMHOOK_CONFIG_CAM_MAX][MAX_PATH];
bool disable_camera[CAMHOOK_CONFIG_CAM_MAX];
};
void camhook_config_cam_init(
struct cconfig *config,
size_t num_cams,
bool use_port_layout);
void camhook_config_cam_init(struct cconfig *config, size_t num_cams);
void camhook_config_cam_get(
struct camhook_config_cam *config_cam,
struct cconfig *config,
size_t num_cams,
bool use_port_layout);
size_t num_cams);
#endif

View File

@@ -1,16 +0,0 @@
dlls += d3d9-frame-graph-hook
ldflags_d3d9-frame-graph-hook := \
-ld3d9 \
-ldwmapi\
-lgdi32 \
libs_d3d9-frame-graph-hook := \
hook \
imgui-bt \
imgui-debug \
imgui \
util \
src_d3d9-frame-graph-hook := \
main.c \

View File

@@ -1,4 +0,0 @@
LIBRARY d3d9-frame-graph-hook
EXPORTS
DllMain@12 @1 NONAME

View File

@@ -1,32 +0,0 @@
#include <windows.h>
#include "hook/d3d9.h"
#include "hook/table.h"
#include "imgui-bt/imgui-d3d9-hook.h"
#include "imgui-debug/frame-perf-graph.h"
#include "util/defs.h"
#include "util/log.h"
static const hook_d3d9_irp_handler_t d3d9_frame_graph_hook_handlers[] = {
imgui_hook_d3d9_irp_handler
};
BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx)
{
imgui_bt_component_t debug_frame_graph_component;
if (reason == DLL_PROCESS_ATTACH) {
log_to_writer(log_writer_null, NULL);
imgui_debug_frame_perf_graph_init(60.0f, &debug_frame_graph_component);
imgui_d3d9_hook_init(&debug_frame_graph_component, 1);
hook_d3d9_init(d3d9_frame_graph_hook_handlers, lengthof(d3d9_frame_graph_hook_handlers));
}
return TRUE;
}

View File

@@ -1,22 +0,0 @@
exes += d3d9-monitor-check \
ldflags_d3d9-monitor-check := \
-ld3d9 \
-ldwmapi \
-lgdi32 \
-ld3dx9 \
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

View File

@@ -1,534 +0,0 @@
#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;
}
}

View File

@@ -1,8 +0,0 @@
#ifndef D3D9_MONITOR_CHECK_CMDLINE_H
#define D3D9_MONITOR_CHECK_CMDLINE_H
#include <stdbool.h>
bool cmdline_main(int argc, char **argv);
#endif

View File

@@ -1,207 +0,0 @@
#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));
}

View File

@@ -1,37 +0,0 @@
#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

View File

@@ -1,430 +0,0 @@
#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);
}

View File

@@ -1,59 +0,0 @@
#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

View File

@@ -1,90 +0,0 @@
#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);
}

View File

@@ -1,26 +0,0 @@
#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

View File

@@ -1,196 +0,0 @@
#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);
}

View File

@@ -1,8 +0,0 @@
#ifndef D3D9_MONITOR_CHECK_INTERACTIVE_H
#define D3D9_MONITOR_CHECK_INTERACTIVE_H
#include <stdbool.h>
bool interactive_main(int argc, char **argv);
#endif

View File

@@ -1,41 +0,0 @@
#include <string.h>
#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 tool");
printfln_err("");
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(" 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();
printfln_err("ERROR: Insufficient arguments");
return 1;
}
command = argv[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();
success = false;
}
return success ? 0 : 1;
}

View File

@@ -1,114 +0,0 @@
#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);
}

View File

@@ -1,32 +0,0 @@
#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

View File

@@ -1,22 +0,0 @@
#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

View File

@@ -1,304 +0,0 @@
#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);
}

View File

@@ -1,32 +0,0 @@
#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

View File

@@ -1,123 +0,0 @@
#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);
}

View File

@@ -1,19 +0,0 @@
#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

View File

@@ -1,69 +0,0 @@
#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);
}

View File

@@ -1,19 +0,0 @@
#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

View File

@@ -174,10 +174,11 @@ static void hook_table_revert_to_iid(
sym = &syms[j];
if (hook_table_match_proc(&iate, sym)) {
// Only revert-able if the original pointer was stored previously
// Only revert-able if the original pointer was stored
// previously
if (sym->link != NULL && *sym->link != NULL) {
pe_patch(iate.ppointer, sym->link, sizeof(*sym->link));
}
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More