custom_video: add a GUD backend for USB indirect displays - #106
Open
alphanu1 wants to merge 2 commits into
Open
Conversation
On Windows the factory identifies the graphics vendor with
int vendor, device;
sscanf(device_id, "PCI\VEN_%x&DEV_%x", &vendor, &device);
if (vendor == 0x1002) // ATI/AMD
Not every display device has a PCI id. An indirect display reached over
USB reports something like
USB\VID_1D50&PID_614D&REV_0100
so the sscanf matches nothing, assigns nothing, and both locals are read
uninitialised on the line after. The comparison then depends on whatever
was on the stack, and can pick the ATI path on a machine with no AMD
hardware in it.
Initialise them. No functional change on a PCI display, where the sscanf
fills both in.
Adds modeline generation for a Generic USB Display -- a GUD device, as
carried by the in-tree Linux drm/gud driver -- driven on Windows by an
IddCx indirect display driver. The motivating case is a 15 kHz CRT on a
board that takes arbitrary modelines over USB, where the point is a
per-game timing rather than a mode picked from a fixed list.
Unlike the other Windows backends this one does not push timing into a
graphics driver through a vendor interface. There is no graphics driver
involved. The display's timings live in a file the display driver
watches, and it re-enumerates its monitor when that file changes:
C:\ProgramData\gud-windows\modelines.ini written here
C:\ProgramData\gud-windows\modes.active written by the driver
so process_modelist() writes the modelines and waits for the driver to
publish them. Setting one is then an ordinary ChangeDisplaySettingsEx,
because by that point Windows knows the mode.
get_timing() carries as much weight here as adding modes does. Windows
reports every mode this display has as progressive -- an IddCx target
mode set as interlaced comes back progressive, and there is no way to
express it -- so 648x480i60 arrives as 480p at 60 Hz and its line rate
computes as 31.5 kHz rather than the 15.75 it actually runs. Uncorrected
that puts a 15 kHz monitor's own modes out of range: asking for 640x480
on arcade_15 skips both 480-line modes and lands on 240 lines with half
of them thrown away. The driver knows the real timings and writes them
down; get_timing() reads them back, and the modes classify correctly.
The same asymmetry runs the other way when setting a mode.
set_desktop_mode() builds its DEVMODE with
lpDevMode.dmDisplayFlags = mode->interlace? DM_INTERLACED : 0;
and ChangeDisplaySettingsEx returns DISP_CHANGE_BADMODE for an
interlaced mode against this display, since Windows has no interlaced
mode to match. So the backend clears the flag once a mode becomes the
OS's to set. Nothing real is lost: interlace on a GUD display is a
property of the link between the driver and the board, not of anything
Windows does. It stays in the modeline, and the device interleaves on
read.
The backend claims a display only when the driver's published list names
that device -- modes.active carries a device line holding the hardware
id, and it is matched on VID/PID against EnumDisplayDevices' DeviceID.
Testing for a USB device id alone would claim any other USB-attached
display on the machine, a DisplayLink adapter or a second indirect
display among them, and write generated modelines into a driver with
nothing to do with them. Matching a fixed VID/PID instead would tie this
to one board, where the driver serves any GUD device. Every other
display reaches the existing paths unchanged, PCI ones included.
Entries the backend owns are prefixed sr_ and rewritten as a set;
anything else in the ini is somebody's hand-written modeline and is
copied through untouched, the driver treating that file as
authoritative.
Tested on a blitsCRT_Mister board against a 15 kHz CRT. Generated
384x224@59.185 (7.842 384 400 437 500 224 236 239 265) and 640x480i60
(13.038 640 666 727 831 480 483 489 523 interlace), both reaching the
CRT with their own porches. Checked that a PCI adapter is untouched,
that a missing modes.active falls back to system mode, and that a
modes.active naming a different device is not claimed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds modeline generation for a Generic USB Display — a GUD device, as carried by
the in-tree Linux
drm/guddriver — driven on Windows by an IddCx indirectdisplay driver. The motivating case is a 15 kHz CRT on a board that accepts
arbitrary modelines over USB, where the point is a per-game timing rather than a
mode chosen from a fixed list. That already works on Linux through
drmkms;this is the Windows half.
Two commits: a one-line correctness fix in the factory, then the backend.
How it differs from the other Windows backends
It does not push timing into a graphics driver through a vendor interface,
because there is no graphics driver involved. The display's timings live in a
file the display driver watches, and it re-enumerates its monitor when that file
changes:
process_modelist()writes the modelines and waits for the driver to publishthem. Setting one is then an ordinary
ChangeDisplaySettingsExthroughdisplay_windows, because by that point Windows knows the mode.caps()isADD | UPDATE.Why
get_timing()matters as much as adding modes hereWindows reports every mode this display has as progressive. An IddCx target mode
set as interlaced comes back progressive and there is no way to express it, so
648x480i60 arrives as 480p at 60 Hz and its line rate computes as 31.5 kHz
rather than the 15.75 it actually runs. Uncorrected, that puts a 15 kHz
monitor's own modes out of range:
A 480-line request lands on 240 lines with half of them discarded. The driver
knows the real timings and writes them to
modes.active;get_timing()readsthem back and the modes classify correctly:
The same asymmetry when setting a mode
set_desktop_mode()builds its DEVMODE withdmDisplayFlags = mode->interlace? DM_INTERLACED : 0, andChangeDisplaySettingsExreturnsDISP_CHANGE_BADMODEfor an interlaced modeagainst this display, since Windows has no interlaced mode to match it against.
The backend clears the flag once a mode becomes the OS's to set. Nothing real is
lost — interlace on a GUD display is a property of the link between the driver
and the board, not of anything Windows does. It stays in the modeline and the
device interleaves on read.
Does it affect anything else?
No, and it is gated deliberately tightly. The backend claims a display only when
the driver's published list names that device —
modes.activecarries adeviceline holding the hardware id, matched on VID/PID againstEnumDisplayDevices'DeviceID. Testing for a USB device id alone would claimany other USB-attached display on the machine, a DisplayLink adapter among them.
Matching a hardcoded VID/PID instead would tie this to one board, where the
driver serves any GUD device.
Verified on hardware:
Video chipset is not compatible→ existing path, untouchedmodes.activeabsentFound 0 custom→ system mode, identical to beforemodes.activenames another devicemodes.activenames this deviceFound 3 custom→ generated modesCUSTOM_VIDEO_TIMING_GUDis0x400, insideCUSTOM_VIDEO_TIMING_MASKandclear of the editable flags in the low bits and the
MODE_*flags in the highones. Linux is untouched: the include and the factory branch are inside
#if defined(_WIN32)and the new file is only in the WindowsSRClist.Two things you will reasonably object to
It depends on a driver that is not upstream, and you cannot test it without
the hardware. Both true. It is also the shape every backend here already has:
pstriplocates PowerStrip — third-party, commercial, not upstream — withFindWindowA("TPShidden", NULL)and logsPowerStrip found!;adlneeds AMD'sADL;
drmkmsneeds libdrm and a card under/dev/dri. Each is detected atruntime and inert when absent, and this is the same: it compiles unconditionally
on Windows, claims nothing when the file is not there, and costs a
fopenperdisplay at init. The driver is MIT and public, so the dependency is at least
inspectable. If you would still rather it were opt-in, a build flag or an ini
option is a small change and I am happy to make it.
The path is hardcoded.
C:\ProgramData\gud-windows\is the driver's ownfixed location — it reads and writes there and the backend does not get to
choose it — which is the same arrangement as
drmkmsand its device nodes, orpstripand its window class. It could be surfaced as aswitchres.inioptionif you want it configurable; I left it out rather than add an option with one
possible value.
The first commit
custom_video::make()doessscanf(device_id, "PCI\\VEN_%x&DEV_%x", &vendor, &device)on two uninitialisedlocals and compares
vendorto0x1002on the next line. Not every display hasa PCI id — a USB one reports
USB\VID_1D50&PID_614D&REV_0100— so the sscanfassigns nothing and the comparison reads whatever was on the stack. Separated
out because it stands on its own.
Testing
Built with MinGW g++ (
-Wall -Wextra, no new warnings) from a clean clone.Tested on a blitsCRT_Mister against a 15 kHz CRT. Generated and displayed:
384x224@59.185—7.842 384 400 437 500 224 236 239 265640x480i60—13.038 640 666 727 831 480 483 489 523 interlaceBoth reached the CRT with their own porches, neither is advertised by the
device. Linux is unbuilt and unchanged by this.
The Windows display driver is gud-windows;
docs/DESIGN.mdthere covers the interlace behaviour and themodes.activeformat.