New src/session_plan.rs: a Copy `SessionPlan { capture, topology, encoder, bit_depth, hdr }`
resolved ONCE from HostConfig (+ the negotiated bit_depth) at the top of `virtual_stream`,
logged, and threaded through build_pipeline_with_retry/build_pipeline. The three scattered
Windows dispatch points now read this one typed artifact instead of re-deriving from config
(plan §2.4, the "capture and encode disagree on the backend" hazard):
* capture: capture::capture_virtual_output takes a CaptureBackend IN (was re-reading
config().idd_push / capture_backend / no_wgc internally). CaptureBackend::resolve() is the
single resolver, shared with the GameStream + spike call sites.
* topology: virtual_stream reads plan.topology; should_use_helper is deleted (its body is
session_plan::resolve_topology, verbatim). The IDD-push reconnect-preempt guard reads
plan.capture too.
* encoder: recorded as EncoderBackend from encode::windows_resolved_backend (config-backed +
GPU-vendor cached since stage 2 -> already a single source). Threading encoder/input_format
into the encoder+capturer opens (which removes the capture->windows_resolved_backend()
back-reference recomputed in dxgi.rs) is stage 5.
Behavior-preserving by construction: each resolved decision is provably equivalent to the
pre-stage-3 reads (same config() + the same cached running_as_system()/GPU-vendor probes), so
old==new. SessionPlan is platform-neutral so it threads the shared virtual_stream/build_pipeline
signatures; on Linux it resolves to the single portal/single-process path.
Also fixes a pre-existing mod-ordering fmt drift in main.rs (mod config; / mod capture;).
Verified: Linux cargo check + clippy (-D warnings) + fmt clean on the touched files. Box build
(Windows compile) + on-glass (NVENC + IDD-push + mode switch) pending on the RTX box.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Migrate 31 genuinely-constant operator/dispatch env::var sites onto HostConfig, so the
capture/topology/encoder decision reads ONE owner instead of being recomputed at each call
site (the latent bug where capture and encode could disagree on the resolved backend, plan §2.4):
idd_push x7, no_wgc, capture_backend, render_adapter, encoder_pref (Linux open_video +
linux_zero_copy_is_vaapi), the Windows vdisplay-backend select, plus the plan-named
secure_dda/idd_depth/zerocopy/ten_bit and the multi-site perf x4 / compositor x5 /
video_source x3 / gamepad. Each HostConfig field's parser is byte-identical to the read it
replaced, so old==new by construction (the plan's "a flipped bool is a silent regression" guard).
Scope correction — the plan's "~64 sites / Linux XDG+compositor included / grep env::var -> 0"
was unsafe as written. Two classes are deliberately KEPT as live reads and documented in config.rs:
* Runtime-mutated session vars. vdisplay::apply_session_env REWRITES the process env on every
connect (the Bazzite Gaming<->Desktop follow): WAYLAND_DISPLAY, XDG_CURRENT_DESKTOP,
XDG_RUNTIME_DIR, DBUS_SESSION_BUS_ADDRESS, and the derived PUNKTFUNK_INPUT_BACKEND,
GAMESCOPE_SESSION/NODE, KWIN/MUTTER_VIRTUAL_PRIMARY, FORCE_SHM. Parsing these once would
freeze them at startup and silently break session-following — they are NOT constant.
* Single-use local tuning with no resolve-once benefit (and FEC_PCT even has two different
semantics): FEC_PCT, VIDEO_DROP, VBV_FRAMES, SPLIT_ENCODE, PACE_BURST_KB, the dxgi timing
knobs, the *_LIVE/test gates, plus path/dynamic reads (config-dir, PATH search,
env-forward-to-child). PUNKTFUNK_ZEROCOPY is split on purpose: Windows presence-semantics
moved to the field; Linux keeps its own truthy (1|true|yes|on) parser.
Verified: Linux cargo check + clippy (-D warnings) + fmt clean on the touched files. The
Windows-only edits are 1:1 substitutions; they get a real Windows compile on the box with Stage 3.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
config.rs: typed HostConfig parsed ONCE from env (idd_push/encoder_pref/no_helper/force_helper), replacing per-call env::var re-reads (PUNKTFUNK_ENCODER was re-read on EVERY windows_resolved_backend() call; PUNKTFUNK_IDD_PUSH is read 8x across the host — the recompute that lets capture + encode disagree on the backend, plan §2.4). Migrated the two highest-churn dispatch reads onto it (encode::windows_resolved_backend, punktfunk1::should_use_helper). Behavior-identical: the env is constant for the process lifetime (the service loads host.env before launch), so a lazily-parsed global == parsed-once-at-startup.
docs/windows-host-goal1-plan.md: the ORDERED, independently-shippable execution plan for Goal-1 (the plan's biggest unstarted goal — a from-scratch layered host architecture). Six behavior-preserving, box-verified stages (HostConfig -> SessionPlan -> SessionContext/SessionFactory -> seam-trait tightenings -> src/windows tree), because the host is live-validated and a monolithic rewrite would strand it broken. Stage 1 done here; stages 3-5 rewire the deployed path and require on-glass re-test.
Verified: Linux + box (--features nvenc) cargo check clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
EvtCleanupCallback on the WDFDEVICE (entry.rs + callbacks::device_cleanup): on device removal (PnP/unload) drop every monitor's swap-chain worker via monitor::cleanup_for_device_removal (joins threads, IddCx-free — the framework tears the monitors down with the device). Worker threads no longer linger into teardown.
Single identity per session (create_monitor): a re-ADD of a still-live session_id departs the stale monitor first, so one session maps to exactly one monitor (no duplicate EDID/target).
DeviceContext-owned state (audit §2.5): documented decision NOT to migrate the globals to a Box/AtomicPtr device-owned allocation. The IddCx monitor/mode DDIs receive only an IddCx handle (never the WDFDEVICE/context), so the state MUST be globally reachable (upstream virtual-display-rs is a process-static for the same reason); the globals are already module-encapsulated; and with one devnode + UmdfHostProcessSharing=ProcessSharingDisabled they die with the host process on removal anyway. A pointer variant would only add a host-gone-watchdog-race use-after-free for zero benefit.
Verified: driver workspace builds clean on the RTX box (.173).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The old all-Rust IddCx driver tree (packaging/windows/vdisplay-driver/ — the wdf-umdf-sys 'oracle', 7896 lines) is fully superseded by packaging/windows/drivers/ (wdk-sys / windows-drivers-rs + the owned pf-vdisplay-proto ABI), which is the source of the vendored + installed driver. It was in NO cargo workspace (never built) and NO CI workflow; only stale doc/script refs pointed at it (the confusion the audit + game-capture-bug doc both flagged).
Delete it + repoint the build-relevant refs (packaging/windows/README.md, stage-pf-vdisplay.ps1, pack-host-installer.ps1) at drivers/ + drivers/deploy-dev.ps1. The vendored driver (packaging/windows/pf-vdisplay/) is unaffected; docs/windows-virtual-display-rust-port.md keeps its historical mentions as narrative.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Build/sign/install script for the wdk-sys/windows-drivers-rs driver in packaging/windows/drivers/ (the new tree lacked one). Like the old vdisplay-driver/deploy-dev.ps1 but adds the FORCE_INTEGRITY clear (this tree links /INTEGRITYCHECK) and a 9.9.MMdd.HHmm DriverVer (the vendored build is 9.5.*). Verified: deployed the rebuilt driver to the RTX box (.173).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
publish() now guards width/height alongside format (CopyResource needs matching DIMS too, else garbage): drops a surface whose descriptor no longer matches the host ring (a fullscreen game mode-set the display) AND logs the actual descriptor once per mismatch episode, so a repro shows exactly what changed (GB1/Stage-0 diagnostic + the Stage-2 width/height guard).
log.rs: a process-lifetime, flushed, Mutex-shared append handle (opened ONCE) replaces the per-call open/append — so the swap-chain WORKER thread's lines land. They were hidden (per-call open raced the control thread / could fail under the worker's restricted token), which is exactly why a game-break repro showed no swap-chain-processor lines (bug doc S3). This is the observability foundation the bug doc gates Stage S (S1/S2 driver resilience) on.
Needs a driver rebuild + re-vendor to deploy (separate from the GB1 host-only fix). Stage 3 (trim default_modes) deprioritized: GB1 recovers from mode-sets, and trimming risks the live display-activation path.
Verified: driver workspace builds clean on the RTX box (.173).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The bug: a fullscreen game mode-sets the virtual display (format/size); the driver's publish() guard then drops every frame; the host's ring — fixed at the session-negotiated mode — never adapts -> frozen picture, then black on reconnect.
RECOVER (no DDA, per the chosen design): the ring now TRACKS the display's actual mode. At open it is sized to the display's actual resolution (new win_display::active_resolution, CCD/GDI) — so reconnecting while a game holds a different mode just works. Mid-session, the 250ms poll (was HDR-only) now also follows the active resolution; on any descriptor change (size or HDR) it recreates the ring at the new mode (recreate_ring generalized to a new size) -> the driver re-attaches -> frames resume at the game's mode. No freeze, no reconnect needed.
DROP if unrecoverable: a descriptor change starts a recovery clock (recovering_since); if no fresh frame resumes within 3s (e.g. an exclusive-flip the host can't follow), try_consume bails -> the session ends cleanly -> the client reconnects, instead of freezing forever. A pure idle desktop (no mode change) never triggers this.
Verified: host clippy (nvenc) clean on the RTX box. NEEDS ON-GLASS (Doom repro on .158): confirm the poll sees the mode-set, the ring recreates + recovers, the encoder+client adapt to the size change; tune the 3s window.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
wait_for_attach now requires the driver to publish a FIRST frame, not just attach (DRV_STATUS_OPENED). A fullscreen game can leave the virtual display in a format/size the driver's publish() guard rejects -> the driver ATTACHES but silently drops every frame; previously the host sailed past open() and only died on next_frame's 20s deadline (the 'reconnect = black + working audio' symptom). Now open() fails -> capture.rs falls back to DDA (reusing the C1 fallback) -> the game is captured + visible after a reconnect.
Safe at open: the OS composites the freshly-activated virtual display, so a frame arrives within ~1s — a normal/idle open isn't false-failed; only a genuinely-broken display (no frame in 4s) falls back (and DDA is a working path, so even a false-positive degrades gracefully).
GB1 Stage 1a (docs/windows-host-rewrite-game-capture-bug.md P3). The mid-session-without-reconnect live failover (composing capturer) is the next piece.
Verified: host clippy (nvenc) clean on the RTX box.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Moved the remaining 6 SudoVDA reach-in helpers + SavedConfig (resolve_gdi_name, set_advanced_color, advanced_color_enabled, set_active_mode, isolate/restore_displays_ccd) verbatim from vdisplay::sudovda into a backend-neutral crate::win_display module (the plan's windows/display_ccd.rs). The capturers (idd_push/dxgi/wgc), pf_vdisplay, and punktfunk1 now depend on these as PEERS via crate::win_display instead of reaching into the SudoVDA backend.
With win_adapter (F1 pt1), all 7 reach-in helpers are now neutral — the circular reach-in is broken, so SudoVDA can eventually be deleted (Goal 2) without losing the display utilities. sudovda re-exports the ones it still uses internally; its now-unused CCD/GDI imports were removed.
Verified: host clippy (nvenc) clean on the RTX box; Linux check clean (the new modules are #[cfg(windows)]).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The discrete-render-GPU LUID picker was display-utility living in the SudoVDA backend; moved it verbatim to a backend-neutral crate::win_adapter module (the plan's windows/adapter.rs). The IDD-push capturer + pf-vdisplay backend now depend on it as a PEER instead of reaching into vdisplay::sudovda — the first step in breaking the circular reach-in so SudoVDA can eventually be dropped (Goal 2). sudovda re-exports it for its own callers.
Remaining F1 increments: the CCD/HDR helpers (resolve_gdi_name, set_advanced_color, advanced_color_enabled, set_active_mode, isolate/restore_displays_ccd) → a neutral win_display module.
Verified: host clippy (nvenc) clean on the RTX box.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lock in the explicit-unsafe-block discipline so a fn-level 'unsafe' never silently blesses its whole body (the per-site // SAFETY: comments already landed in STEP 8). Builds clean on the RTX box — no fallout. The host-wide unsafe-lint sweep + clippy::undocumented_unsafe_blocks (hundreds of blocks across Linux+Windows) are a larger dedicated follow-up.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
§5.3 (C3): repeat_last() now copies the last frame into a FRESH rotated out-ring slot instead of re-handing last_present's slot, so a repeat (static desktop) never re-hands a slot still encoding under pipeline_depth>1. OUT_RING(3) > max depth(2) keeps the rotated slot free — the out-ring rotation contract now holds for repeats too, not just the synchronous-loop assumption.
§5.4 (C4): when enabling advanced color for a 10-bit client, trust set_advanced_color success and size the ring FP16 directly, instead of racing the advanced_color_enabled poll (which could size SDR while the driver composes FP16 -> format mismatch -> an immediate ring recreate + dropped first frames).
Verified: host clippy (nvenc) clean on the RTX box. On-glass to confirm: HDR-client first-frame + static-desktop pipelining.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
open() now hands the keepalive BACK on failure (the WGC attach_keepalive pattern) so the caller can fall back instead of tearing the virtual display down. Added a bounded wait_for_attach() that polls the driver's DRV_STATUS_OPENED — it checks ATTACH status, not frame arrival, so it never false-fails on an idle desktop that has composed no frame yet.
An attach failure (e.g. a hybrid-GPU render mismatch -> DRV_STATUS_TEX_FAIL, or the driver never opening the ring within 4s) now fails open() -> capture.rs falls back to DDA, instead of next_frame's 20s deadline leaving the session black. Pairs with the driver SET_RENDER_ADAPTER fix (cdccec6).
Verified: host clippy (nvenc) clean on the RTX box. Behavioral validation (fallback trigger + happy-path attach timing) needs an on-glass session.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
§4.2h (C2): the host already pins the discrete GPU via IOCTL_SET_RENDER_ADAPTER on the IDD-push path; now that the pf-vdisplay driver implements it (cdccec6), correct the stale 'driver returns STATUS_NOT_IMPLEMENTED / STEP-4 stub' comments. Hybrid iGPU+dGPU boxes now actually pin the NVENC GPU.
§6.1 (C5): switch the host gamepad SHM consumers (inject/{dualsense,gamepad}_windows.rs) to derive size/offsets/magic/name from pf_vdisplay_proto::gamepad::{PadShm,XusbShm} via offset_of!/size_of!/helpers, instead of hand-literal OFF_*/140 — proto is now the single source of truth (driver-side switch follows with the gamepad-driver unification). The DualShock4 backend reuses the same pub(super) consts unchanged.
Verified: host clippy (nvenc) clean on the RTX box (x86_64-pc-windows-msvc).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Audit §4.1: implement the host-gone watchdog — it was dead code (WATCHDOG_PINGS bumped but never sampled, no thread). Every IOCTL now bumps a liveness counter; a watchdog thread reap_orphaned()s monitors (created_at grace) if no IOCTL arrives within WATCHDOG_TIMEOUT_S, so a crashed/TerminateProcess'd host no longer leaves its virtual monitor + swap-chain worker + pooled D3D device wedged until the next CLEAR_ALL. Removes the false 'watchdog thread' comments.
Audit §4.2: implement SET_RENDER_ADAPTER (was STATUS_NOT_IMPLEMENTED) via IddCxAdapterSetRenderAdapter, so the host can pin the IDD render to the NVENC GPU on a hybrid iGPU+dGPU box (else the OS-picked iGPU makes the host ring textures un-openable -> DRV_STATUS_TEX_FAIL).
Audit §4.4: gate the world-writable C:\Users\Public\pfvd-driver.log behind debug builds / PFVD_DEBUG_LOG (a release build never writes it).
Audit §4.5: bounds-check the requested mode in IOCTL_ADD; compute display_info clock_rate in u64 + saturate (the old u32 refresh*(h+4)^2 overflowed/aborted the mode DDI for large modes).
Verified: driver workspace builds clean on the RTX box (WDK 26100 + LLVM 21.1.2, MSVC). On-glass functional validation of the watchdog/render-pin is a follow-up (needs a driver reinstall + session).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
§6.2: add offset_of! asserts to SharedHeader/AddReply/control structs so a same-size field reorder is a compile error, not silent corruption (size+Pod alone miss it).
§6.1: add XusbShm (64B) + PadShm (256B, incl device_type@140) layouts + Global\ name helpers + magics to the proto crate as the single source of truth, with offset asserts pinned to the shipped wire layout — kills the hand-duplicated literal-140 host/driver drift hazard. Enables bytemuck min_const_generics for the >32-byte reserved tails. Host + driver consumers switch in a follow-up.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The installer's vendored driver binary (packaging/windows/pf-vdisplay/) was STALE — built from the OLD
oracle tree (packaging/windows/vdisplay-driver/, wdf-umdf, SudoVDA-compat GUID), so it was
ABI-mismatched with the host (which opens the owned proto GUID 70667664). Re-vendor it from the NEW
drivers/ tree so the rewrite's ACTUAL driver is what the installer ships.
Built RELEASE on the RTX box from the new tree + the new .inx: cargo build --release -p pf-vdisplay ->
FORCE_INTEGRITY clear -> stampinf (DriverVer 06/25/2026,9.5.0625.1614, > the old 06/22) -> Inf2Cat
/os:10_X64 -> signtool sign the .cat with punktfunk-ds-test (.cat sig Valid). Replaces the stale
.dll/.inf/.cat; the .cer is unchanged (same cert).
ON-GLASS VALIDATED (install-test): pnputil /add-driver /install the release package -> clean WUDFHost
reload -> Status=OK, init_adapter -> IddCxAdapterInitAsync -> 0x0 (FP16 accepted),
IddCxMonitorCreate(id=1) -> 0x0. The shipping installer now installs + loads the real wdk-sys
proto-GUID driver, FP16/HDR-capable, monitor-create working.
Remaining STEP 8 (recorded in memory, deferred): re-point the stale "built from vdisplay-driver/"
comments in stage-pf-vdisplay.ps1 / pack-host-installer.ps1 / packaging README; selector default ->
pf-vdisplay unconditional; CI build-sign-or-stale-vendored drift guard; then DELETE the oracle tree.
KEEP sudovda.rs (runtime fallback + the backend-neutral CCD helpers pf_vdisplay.rs reuses) and the
WGC-relay/DDA secure path (the secure-desktop lock/UAC gate is not yet proven on glass for IDD-push).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The new wdk-sys driver tree (packaging/windows/drivers/pf-vdisplay/) had no INF — it borrowed the
oracle's (packaging/windows/vdisplay-driver/.../pf_vdisplay.inx), which blocked deleting the oracle.
Port it verbatim: the proto-vs-SudoVDA control GUID is registered in CODE
(WdfDeviceCreateDeviceInterface), so the INF is GUID-agnostic and identical — HWID Root\pf_vdisplay,
UmdfExtensions=IddCx0102, the control-device security DACL, UpperFilters=IndirectKmd,
UmdfHostProcessSharing=ProcessSharingDisabled. Prerequisite for the STEP-8 re-vendor (build ->
stampinf -> Inf2Cat -> sign the .dll/.cat from the NEW tree into packaging/windows/pf-vdisplay/,
replacing the stale oracle-built binary) and for deleting the oracle tree.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Audit pass over the new pf-vdisplay driver's unsafe surface: 92 per-site // SAFETY comments added
across adapter.rs / monitor.rs / entry.rs / callbacks.rs / swap_chain_processor.rs /
frame_transport.rs / direct_3d_device.rs (control.rs already had full coverage). COMMENTS ONLY — zero
logic, signature, or control-flow change (verified via git diff: every added line is a // SAFETY
comment or blank).
The dominant gap was the pervasive `core::mem::zeroed()` FFI-struct builds (IDDCX_*/WDF_*/
DISPLAYCONFIG_* C PODs whose all-zero bit pattern is a valid uninitialized/Invalid state, with the
required .Size/fields set immediately after) — each now carries a one-line // SAFETY. Plus explicit
notes on the two stack/local-pointer-into-FFI hazards (adapter.rs `version` ptr into
IddCxAdapterInitAsync; monitor.rs `edid` Vec ptr into IddCxMonitorCreate — both read synchronously
before the local drops) and the frame_transport.rs raw-HANDLE / mapped-header derefs + cleanup paths.
The already-justified Send/Sync wrappers (SendAdapter, CtxTypeInfo/DevCtxInfo, MonitorObject,
Sendable, FramePublisher) were audited — each already carried a // SAFETY. No site needed a code
change.
First slice of STEP 8 (the SudoVDA drop). Comments-only ⇒ build-neutral; windows-drivers.yml verifies
on the next runner build. Remaining STEP 8: re-vendor the installer's driver binary from the new
drivers/ tree (the shipping packaging/windows/pf-vdisplay/ binary is still built from the OLD oracle
tree with the SudoVDA-compat GUID — ABI-mismatched with the host's proto GUID), add an .inx to the
new tree, re-point scripts/README from vdisplay-driver/ to drivers/, flip the selector default to
pf-vdisplay, then delete the old oracle tree. Keep sudovda.rs (the runtime fallback + the
backend-neutral CCD helpers pf_vdisplay.rs reuses) and the WGC-relay/DDA secure path (the
secure-desktop gate is not yet passed on glass).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The pf-vdisplay driver now advertises HDR/FP16 and the full glass-to-glass HDR path works
end-to-end — validated LIVE: the Mac client connected to the .173 host WITH HDR (display_hdr=true,
FP16 ring -> NVENC P010). The STEP-3 assumption that FP16 needs a higher UmdfExtensions was WRONG:
IddCx0102 + CAN_PROCESS_FP16 + the *2 DDIs works (the oracle proved it; confirmed on-glass
IddCxAdapterInitAsync -> 0x0 WITH the FP16 cap set). Driver-only change — the host FP16-ring ->
NVENC-P010 path and the HDR EDID were already in place.
- adapter.rs: caps.Flags = IDDCX_ADAPTER_FLAGS_CAN_PROCESS_FP16.
- entry.rs: register the 6 *2/HDR callbacks (ParseMonitorDescription2, MonitorQueryTargetModes2,
AdapterCommitModes2, AdapterQueryTargetInfo, MonitorSetDefaultHdrMetaData, MonitorSetGammaRamp)
ALONGSIDE the v1 set (matching the oracle — CAN_PROCESS_FP16 OBLIGATES the *2 DDIs or the
framework rejects the adapter at init; STEP 3 rejected FP16 only because they weren't registered).
- callbacks.rs: parse_monitor_description2 + monitor_query_modes2 now fill IDDCX_MONITOR_MODE2 /
IDDCX_TARGET_MODE2 with BitsPerComponent (8|10 bpc RGB); query_target_info already reports
IDDCX_TARGET_CAPS_HIGH_COLOR_SPACE; set_default_hdr_metadata + set_gamma_ramp accept (the gamma
one is mandatory under FP16).
- monitor.rs: wire_bits() (Rgb 8|10, no YCbCr) + target_mode2().
- EDID + INF UNCHANGED (the EDID already carries the CTA-861.3 BT.2020 + ST.2084/PQ block; the INF
stays UmdfExtensions=IddCx0102).
Built via the ultracode flow (STEP-7 map workflow -> agent-implement -> box build [driver green] ->
deploy -> on-glass HDR). OPERATIONAL NOTE: do NOT Disable/Enable the IddCx devnode to reload it —
that leaves the adapter STOPPED in the persisted WUDFHost process (ADAPTER OnceLock survives), so
monitor-create then fails with 0xc00002b6 (INDIRECT_DISPLAY_DEVICE_STOPPED). Kill the pf_vdisplay
WUDFHost process (or reboot) for a clean adapter re-init.
This completes the pf-vdisplay rewrite STEP 0-7, all on-glass validated (loads, adapter inits,
monitor appears, swap-chain drain, IDD-push frames at ~235fps, and HDR). Remaining: STEP 8 (unsafe-
reduction + delete the old vdisplay-driver tree + the vendored SudoVDA driver + unbundle from the
installer = the SudoVDA drop).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The driver now publishes each acquired swap-chain surface into the host-created shared ring (the
IDD-push path) — the full glass-to-glass transport is code-complete. Both sides use the canonical
pf_vdisplay_proto::frame layout (lockstep by compile-error, not "must match" comments). Driver compiles
+ LOADS on-glass (adapter inits, Status=OK; no regression — the publisher is dormant until a frame is
acquired); host cargo check green; adversarially reviewed (no blockers — token layout, keyed-mutex key 0,
names by target_id, and the format guard all match the host consumer).
- new driver frame_transport.rs: FramePublisher OPENS the host ring by target_id (OpenFileMapping header
+ magic Acquire readiness gate + OpenEvent + OpenSharedResourceByName RING_LEN keyed-mutex textures),
writes its render LUID + DRV_STATUS back into the header; publish() is NON-BLOCKING (round-robin 0ms
try-acquire -> CopyResource -> ReleaseSync -> FrameToken::pack store Release -> SetEvent; drops the
frame if every slot is busy or the surface format != the ring format). Manual handle/view cleanup on
every try_open early return; RAII Drop (slots -> unmap -> CloseHandle). Layout/consts/names/token all
from pf_vdisplay_proto::frame.
- swap_chain_processor.rs run_core: lazy rate-limited attach (every ~30 frames) + is_stale re-attach
(mid-session HDR ring recreate); publishes buffer.MetaData.pSurface via IDXGIResource::from_raw_borrowed
(preserves IddCx's refcount) BEFORE IddCxSwapChainFinishedProcessingFrame. run/run_core gain the render
LUID; callbacks.rs assign_swap_chain passes it.
- host idd_push.rs migrated onto pf_vdisplay_proto::frame (deleted the hand-rolled SharedHeader / MAGIC /
VERSION / RING_LEN / DRV_STATUS_* / name fns / token packing) — pure refactor, byte-identical, no
behavior or gating change. DebugBlock + DXGI_SHARED_RESOURCE_RW kept local (not in the proto).
- driver windows crate gains Win32_System_Memory (MapViewOfFile/OpenFileMappingW/...); rustfmt'd the whole
driver workspace (incl. wdk-probe — fmt-only).
Built via the ultracode flow: STEP-6 map workflow -> agent-implement -> box build (driver + host both
green; caught nothing this time) -> adversarial-verify-agent (no blockers) -> FrameToken::pack hardening
-> deploy (loads). Glass-to-glass frame validation awaits a composited session (per the parity finding:
this headless box yields 0 frames for the proven SudoVDA path too). FOLLOW-UPs: port the optional
Global\pfvd-dbg DebugBlock triage channel to the new driver; STEP 7 HDR; STEP 8 drop SudoVDA.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
STEP 5 (b6486b3) added the windows + thiserror deps to pf-vdisplay/Cargo.toml but the
workspace lock was not updated (driver is windows-only, cant build on the Linux dev box).
Regenerated on the RTX box. Both crates were already resolved in the lock (pulled by
wdk-build), so this is purely the pf-vdisplay dependency edges.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The pf-vdisplay driver now consumes the OS swap-chain so a virtual monitor is a usable
display rather than a stalled one. Compiles + loads on-glass (no regression: adapter still
inits, Status=OK); adversarially reviewed — no blockers, the leak/deadlock invariants preserved.
- new swap_chain_processor.rs: a worker thread (MMCSS "Distribution") that binds the render D3D
device (IddCxSwapChainSetDevice, single-borrow 60x@50ms retry) then drains the swap-chain
(ReleaseAndAcquireBuffer2 -> FinishedProcessingFrame; E_PENDING waits 16ms on the surface
event). NO frame publisher yet (STEP 6). RAII terminate+join Drop; the load-bearing
top-of-loop terminate check (the oracle's reconnect-leak fix). Fixed a Rust-2021 disjoint-
capture bug: `.0` field access bypassed the Sendable Send wrapper -> rebind the whole wrappers.
- new direct_3d_device.rs: CreateDXGIFactory2 -> EnumAdapterByLuid(render LUID) -> D3D11CreateDevice;
a DEVICE_POOL of one Arc<Direct3DDevice> per render LUID (the NVIDIA-UMD-worker-thread leak fix).
- monitor.rs: MonitorObject gains swap_chain_processor; set/take helpers return it for the caller
to drop OUTSIDE the MONITOR_MODES lock (dropping joins the worker — must never happen under the
lock); remove_monitor/clear_all drop it before IddCxMonitorDeparture.
- callbacks.rs: assign_swap_chain spawns the processor (pooled device per RenderAdapterLuid;
WdfObjectDelete on D3D-init failure so the OS retries); unassign_swap_chain drops it. Fixed the
stale `panic = "abort"` doc (workspace is unwind; the extern "C" boundary aborts on unwind).
- Cargo.toml: windows 0.58 + thiserror (both already resolved in the driver lock). The 3 needed
swap-chain DDIs were already wrapped in wdk-iddcx; their HRESULT-shaped NTSTATUS is classified
by hand (hr>=0 success, 0x8000000A E_PENDING).
- Also rustfmt'd the whole driver workspace (it had never been driver-fmt'd).
Built via the ultracode flow: STEP-5 map workflow -> agent-implement -> box build (caught the
Send-capture bug) -> adversarial-verify-agent -> deploy (loads). Session-1 on-glass validation
(the drain loop servicing an ACTIVE monitor) is the next gate — assign_swap_chain only fires
under an interactive session. Note for STEP 6: target_id_for_object uses the MONITOR_MODES handle
lookup the oracle moved to a WDF context; revisit before target_id keys the shared frame ring.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1a86f16 shipped the agent-written pf_vdisplay.rs unformatted (cargo fmt --all --check
gate) and omitted the Cargo.lock edges for the new windows-only deps (pf-vdisplay-proto +
bytemuck). cargo fmt --all is now clean; Cargo.lock records the host dep edges.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The host can now drive the new pf-vdisplay IddCx driver instead of SudoVDA. Compiles
clean on BOTH Windows (cargo check -p punktfunk-host green) and Linux (cfg(windows)-gated,
main CI unaffected); adversarially reviewed (no blockers, lockstep with the driver).
- new vdisplay/pf_vdisplay.rs: cloned from the proven sudovda.rs, repointed to
pf_vdisplay_proto — interface GUID 70667664 (not e5bcc234), IOCTL 0x900-0x905 (not the
gappy 0x800/0x888/0x8FF), AddRequest/AddReply/RemoveRequest/SetRenderAdapterRequest
(bytemuck Pod, not the GUID-keyed AddParams), a u64 session_id monitor key (not a minted
GUID), and a single IOCTL_GET_INFO handshake that HARD-asserts protocol_version (vs
SudoVDA two-IOCTL best-effort). Full MGR/linger/refcount/teardown lifecycle preserved.
- reuses sudovda.rs backend-neutral CCD/DXGI helpers (set_active_mode, isolate/restore_
displays_ccd, resolve_gdi_name, resolve_render_adapter_luid, MON_GEN/CURRENT_MON_GEN,
SavedConfig) — widened to pub(crate), not duplicated.
- vdisplay::open()/probe() select the backend: PUNKTFUNK_VDISPLAY=pf|sudovda forces one;
default auto-detects (prefer pf-vdisplay if its interface enumerates, else SudoVDA stays
the shipping fallback).
Notes: SET_RENDER_ADAPTER is tolerated as the driver returns NOT_IMPLEMENTED today (STEP 4
tail); the cross-MGR wait_for_monitor_released only paces sudovda's MGR (benign until
IDD-push lands on pf-vdisplay, STEP 6 — documented in-code). On-glass "monitor appears at
WxH@Hz" gate is next.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The virtual-monitor lifecycle is now code-complete on the driver side (CI-green;
deployed — no load/adapter-init regression, Status=OK):
- new monitor.rs: the monitor/mode model (Mode/MonitorObject/MONITOR_MODES), ported from
upstream virtual-display-rs with guid:u128 -> session_id:u64. create_monitor builds an
EDID (serial=id) -> IddCxMonitorCreate -> IddCxMonitorArrival, stores the monitor, and
returns the OS target id + adapter LUID for AddReply. remove_monitor / clear_all depart
+ drop. display_info/target_mode build the DISPLAYCONFIG timing (the union videoStandard
u32 set directly — bindgen-API-agnostic, vs the oracle new_bitfield_1 transmute).
- callbacks.rs: parse_monitor_description (EDID-serial lookup -> count-then-fill
IDDCX_MONITOR_MODE) + monitor_query_modes (pointer-match -> IDDCX_TARGET_MODE) are real.
- control.rs: IOCTL_ADD -> create_monitor + AddReply, REMOVE -> remove_monitor, CLEAR_ALL
-> clear_all, via read_input/write_output_complete WDF buffer helpers. SET_RENDER_ADAPTER
still stubbed (hybrid-GPU pin, next) + the watchdog thread (next).
- DISPLAYCONFIG_* resolve at the wdk_sys root (pub use types::*), not iddcx.
Warnings are the STEP-7 *2/HDR stubs + created_at (read by the watchdog, next). The
on-glass "monitor appears at WxH@Hz" gate awaits the host switch to pf_vdisplay_proto.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
EvtIddCxDeviceIoControl now dispatches the pf-vdisplay-proto control plane (new
src/control.rs): IOCTL_GET_INFO writes InfoReply{protocol_version, watchdog_timeout_s}
(the host asserts the version + fails loudly on mismatch), IOCTL_PING bumps the watchdog
keepalive. ADD/REMOVE/SET_RENDER_ADAPTER/CLEAR_ALL are dispatched but stubbed
(STATUS_NOT_IMPLEMENTED) pending create_monitor + the real mode DDIs (next). Unknown
IOCTLs -> STATUS_NOT_FOUND. Builds CI-green; warnings are the *2/HDR stubs (STEP 7) +
the stored adapter handle (read by create_monitor, next).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Verified on-glass after cleanup: adapter still inits (IddCxAdapterInitAsync 0x0,
Status OK) and WdfDeviceCreateDeviceInterface 0x0.
- RESTORE WdfDeviceCreateDeviceInterface (regression from debugging): the proto control
plane sends IOCTLs via EvtIddCxDeviceIoControl, which needs the device interface for the
host to open. Upstream omits it only because it uses a socket; ours is IOCTL-based.
- Drop the framework_struct_size / version-table machinery + size.rs: size_of suffices
(these are IddCx 1.10 structs on a 1.10 framework, matching upstream). The version-table
reads were added chasing a size mismatch that was never the bug (GammaSupport was).
- Drop /OPT:NOICF (ICF folding was a non-issue) + fix the stale stub-pick comment (the
1.10 stub is needed for the dispatch table, not size.rs symbols).
- Debug-wait/PID-file/go-file gate already removed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The all-Rust wdk-sys IddCx driver now initializes its adapter on the RTX box:
IddCxAdapterInitAsync -> 0x0, EvtIddCxAdapterInitFinished fires, device Status=OK.
ROOT CAUSE (found via cdb wt-trace of iddcx!IddCxImplAdapterInitAsync + the upstream
virtual-display-rs source): IDDCX_ENDPOINT_DIAGNOSTIC_INFO.GammaSupport was left zeroed
= IDDCX_FEATURE_IMPLEMENTATION_UNINITIALIZED (0), which the framework adapter validator
(ddivalidation.cpp:797) rejects with STATUS_INVALID_PARAMETER. Must be NONE (1).
Also required (matched to the proven-working upstream virtual-display-rs, installed +
verified Status=OK on the same box):
- caps Flags = NONE (SDR). CAN_PROCESS_FP16 needs a newer contract than UmdfExtensions=
IddCx0102 grants; deferred to STEP 7 (HDR).
- SDR config: only the 7 required callbacks (+ DeviceIoControl for the proto control
plane). The *2/gamma/HDR-metadata/query-target-info callbacks are FP16-obligated and
rejected without FP16 caps; they return in STEP 7.
- device WDF context type on WdfDeviceCreate; adapter WDF context type on the init attrs.
Debugging note: cdb is reliable via live-attach (go-file gate to avoid the
IsDebuggerPresent race) but cdb -z static hangs on the VM; iddcx WPP needs the control
GUID (TMF GUIDs are not it). Diagnostics trimmed; log.rs dbglog kept for STEP 4+.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DECISIVE: installed the pre-built UPSTREAM virtual-display-rs (Rust wdf-umdf IddCx)
driver on the SAME box -> Status=OK. So a Rust IddCx driver inits an adapter here,
self-signed, right now. My wdk-sys driver still fails ONLY at IddCxAdapterInitAsync
(0xc000000d) despite matching virtual-display-rs on EVERY inspectable dimension:
- same iddcx 1.10 headers+stub
- IDDCX_ADAPTER_CAPS + IDD_CX_CLIENT_CONFIG byte-perfect (offsets match C header)
- runtime pointers all valid/non-null (names .rdata, version stack, dev handle)
- identical IddFunctions[idx]+IddDriverGlobals dispatch; indices 0/1/2
- matched the minimal link (tested vendored wdk-build WITHOUT OneCoreUAP/
NODEFAULTLIB/OPT/INTEGRITYCHECK -> still fails; export pollution ruled out)
- device context, no device interface (control via EvtIddCxDeviceIoControl), init order
The IddCx ClassExtension ETW provider emits no decodable reason (WPP/kernel-debugger
only). The remaining difference is the wdk-sys IddCx binding itself, invisible to
inspection. This commit keeps the upstream-matching structure (device context, no
interface) + the on-glass instrumentation; vendored wdk-build reverted to pristine.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On-glass diagnosis narrowed decisively. PROVEN it is the driver, NOT the box:
enabling the installed SudoVDA devnode -> Status=OK (the box inits a self-signed
IddCx adapter right now). SudoVDA uses the IDENTICAL UmdfExtensions=IddCx0102 and is
built against IddCx 1.10 (DriverVer 1.10.9.289) — exactly our config.
Matched SudoVDA/the oracle on every inspectable dimension, none fixed the
IddCxAdapterInitAsync INVALID_PARAMETER: caps byte-perfect (offsets+sizes vs C +
framework table), minimal SDR adapter fails identically, dispatch byte-identical to
the oracle (IddFunctions[idx] + IddDriverGlobals), IddMinimumVersionRequired=4 (same
as oracle), version pointers, ObjectAttributes, init order, and now an adapter WDF
context type (this commit). The remaining difference is the Rust binary itself vs
SudoVDA C++. Next: capture IddCx ETW/WPP rejection reason (or kernel debugger), or
build the oracle (wdf-umdf Rust) on-glass to isolate Rust-wide vs wdk-sys-specific.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
wstr! used `const W; W.as_ptr()` which points to a temporary dropped at the end of
the statement (dangling) — fixed to `static W` (stable address). On-glass it did NOT
change the IddCxAdapterInitAsync INVALID_PARAMETER, and a minimal SDR adapter
(Flags=NONE + required callbacks only) fails identically, so the caps content +
callbacks are NOT the blocker (offsets are byte-perfect vs C; sizes match the
framework table; dispatch + device are correct). Config restored to FP16 + full HDR
callbacks. Remaining suspects: IDARG_IN_ADAPTER_INIT layout, the missing DeviceContext
(oracle always sets one), or a box/framework regression.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
IddCxAdapterInitAsync still INVALID_PARAMETER. Logged offset_of! for every
IDDCX_ADAPTER_CAPS + IDDCX_ENDPOINT_DIAGNOSTIC_INFO field on the box: ALL match the
expected C x64 layout exactly (caps Flags=4 MaxRate=8 MaxMon=16 Diag=24 Static=80;
diag Trans=4 Friendly=8 Model=16 Manuf=24 HwVer=32 FwVer=40 Gamma=48). So the wdk-sys
bindgen lays the struct out correctly — NOT a layout bug. The caps are byte-identical
to C + match the framework size table + the oracle, yet rejected. Next: runtime
compare vs the oracle (does it init an adapter on this box now?) + WDK-docs deep-dive.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Major on-glass progress on the RTX box. The all-Rust wdk-sys IddCx driver now LOADS
under Secure Boot and runs the ENTIRE init chain: DriverEntry -> WdfDriverCreate ->
driver_add -> IddCxDeviceInitConfig(0x0) -> WdfDeviceCreate -> CreateDeviceInterface
-> IddCxDeviceInitialize -> D0Entry -> init_adapter. Findings:
- Signing was a RED HERRING (the driver loads); std works in WUDFHost (DualSense uses
it too).
- THE unblock: link the iddcx **1.10** IddCxStub (build.rs now picks the highest
version-aware), not 1.0 — the 1.0 stub lacks the version-table symbols AND its
dispatch table mismatched the 1.10 framework, which made IddCxDeviceInitConfig
return INVALID_PARAMETER. With 1.10 the whole chain runs.
- Added a file/OutputDebugString logger (log.rs, matches the DualSense driver) — the
driver was silent; this is how the chain was traced.
- size.rs: framework_struct_size() reads the frameworks authoritative struct sizes
from IddStructures[] (the config keeps size_of=208, validated working).
- adapter.rs: version ptrs + ObjectAttributes(InheritFromParent) + FP16 + framework
caps/diag/version sizes — matches the oracle.
KNOWN WIP: IddCxAdapterInitAsync still returns INVALID_PARAMETER though caps match
the framework size table (88/56/24) + the oracle exactly — likely a subtle wdk-sys
bindgen field-layout detail in IDDCX_ADAPTER_CAPS/IDDCX_ENDPOINT_DIAGNOSTIC_INFO.
CI gate (compile+link) stays green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
adapter.rs: init_adapter(device) builds IDDCX_ADAPTER_CAPS (CAN_PROCESS_FP16,
MaxMonitorsSupported=16, endpoint diagnostics with wstr! PCWSTR names) +
IDARG_IN_ADAPTER_INIT and calls IddCxAdapterInitAsync; EvtDeviceD0Entry triggers it
(idempotent), EvtIddCxAdapterInitFinished stashes the adapter in a OnceLock for
later DDIs. zeroed()+named-field construction dodges the Default-derive +
field-order questions. Compiles + links clean on the box (pf_vdisplay.dll 268KB).
CI gate = compile+link; the on-glass load/enumerate gate needs the box + an INF +
SwDeviceCreate (next).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The versioned IDD_STRUCTURE_SIZE path referenced IddClientVersionHigherThanFramework/
IddStructureCount/IddStructures — LNK2019 unresolved, because the WDK links the iddcx
1.0 IddCxStub which lacks those (they are >=1.4). We target 1.10 against a current
framework (higher==false) where size_of is exactly the versioned result, so use it
directly (the surface-assert refs linked only because they were DCE-eliminated).
pf-vdisplay now COMPILES + LINKS IddCxStub on the box (263,680B). Point
windows-drivers.yml at the whole workspace + clear FORCE_INTEGRITY on pf_vdisplay.dll;
drop the obsolete UINT diagnostic dump.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DriverEntry -> driver_add builds the full IDD_CX_CLIENT_CONFIG (14 IddCx callbacks +
PnP EvtDeviceD0Entry, all stubs with correct PFN signatures) sized via the ported
IDD_STRUCTURE_SIZE! (size.rs), runs IddCxDeviceInitConfig -> WdfDeviceCreate ->
WdfDeviceCreateDeviceInterface(the owned pf-vdisplay GUID, not SudoVDA) ->
IddCxDeviceInitialize. callbacks.rs has all 14 + device_d0_entry; query_target_info
implements HIGH_COLOR_SPACE. edid.rs salvaged verbatim from the oracle. proto gains
interface_guid_fields() (u128 -> Windows GUID fields). Links IddCxStub (the CI gate);
adapter/monitor/swapchain/IDD-push fill the stubs in STEP 3-6.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Graduate the proven iddcx_rt.rs dispatch into wdk-iddcx + add the full DDI set the
pf-vdisplay driver needs: DeviceInitConfig/Initialize, AdapterInitAsync,
MonitorCreate/Arrival/Departure, AdapterSetRenderAdapter (void-returning DDI — its
PFN returns ()), SwapChainSetDevice/ReleaseAndAcquireBuffer2/FinishedProcessingFrame.
One dispatch macro pins each (_IDDFUNCENUM index, PFN_* type) pair exactly once
(the only place table dispatch can be UB). Box-compiles green; IddCxStub link gets
validated when pf-vdisplay (cdylib) consumes it in STEP 2.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
M1 step 2 begins. Add the wdk-iddcx (lib, re-exports wdk_sys::iddcx) + pf-vdisplay
(cdylib) workspace members. pf-vdisplay STEP 0 = DriverEntry + WdfDeviceCreate
skeleton + a #[used] _std_link_gate forcing std::thread + OwnedHandle to link, so
the build proves the std surface resolves under the wdk-build UMDF link settings
(kernel32 is /NODEFAULTLIB - std must come via OneCoreUAP). If std fails to link
here, the SwapChainProcessor worker-thread design needs a CreateThread shim before
any callback work (port-plan critique gap #9).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Port-plan critique #1: convert "the (?i).*iddcx.* allowlist may miss a symbol the
full driver needs" from a box-only surprise into a CI compile gate. New
wdk-probe/src/iddcx_surface_assert.rs size_of-asserts every *2/HDR struct
(IDDCX_TARGET_MODE2/PATH2/METADATA2, IDARG_*RELEASEANDACQUIREBUFFER2 — these embed
DISPLAYCONFIG_*/LUID, which RESOLVE from crate::types: no allowlist gap),
None-asserts all 14 inbound PFN_IDD_CX_* callbacks, and confirms the .Size
machinery (IddStructures/IddStructureCount/IddClientVersionHigherThanFramework/
_IDDSTRUCTENUM::INDEX_*) + the FP16/HIGH_COLOR_SPACE flags. Box-built green; the
wdk-sys binding is proven complete for the ENTIRE driver, not just init. Also
silence the bindgen naming lints in the iddcx module.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First USE of the iddcx binding: a minimal table-dispatch (src/iddcx_rt.rs) over
wdk_sys::iddcx — IddFunctions[_IDDFUNCENUM::<Name>TableIndex] cast to PFN_*,
IddDriverGlobals as implicit arg 1 (the WDF model; ModuleConsts i32 index, not the
oracle NewType .0). The probe EvtDeviceAdd now calls IddCxDeviceInitConfig →
WdfDeviceCreate → IddCxDeviceInitialize → IddCxAdapterInitAsync, exports
IddMinimumVersionRequired=4, and build.rs links IddCxStub (globbed from the SDK
Lib dir that ships iddcx). CI gate = compile + link IddCxStub.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Last UINT errors were all `pub type Type = UINT;` inside bindgen enum modules
(pub mod _DXGI_X {..}) — the top-level UINT alias cannot reach nested modules. C++
parsing made bindgen keep the UINT typedef as the enum underlying repr (C mode
emits a primitive). translate_enum_integer_types(true) emits native u32 reprs, so
the enum modules are self-contained; struct-field UINT stays covered by the
src/iddcx.rs alias.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
UINT fails to resolve despite a top-level `pub type UINT` in the same scope as the
working `use crate::types::*` — error count byte-identical before/after the fix.
Add an if:always() step dumping the generated module structure + UINT-use context
to pinpoint the scope mismatch (RTX box rebooted to Proxmox, so CI is the only
validator).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Last iddcx type gaps: (1) DXGI enum newtypes are `pub use self::_DXGI_X::Type as
DXGI_X` — the `_DXGI_X` module needs allowlisting too (broaden DXGI_.* to
_?DXGI_.*, matching the OPM fix); (2) UINT bindgen raw_line landed in a scope the
bindings cannot see — define `pub type UINT` directly in src/iddcx.rs next to
`use crate::types::*` instead.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DXGI resolved. Remaining iddcx type gaps: OPM typedefs need their _OPM_* struct
tags too (recursively(false) drops them), D3DCOLORVALUE (an OPM field), and UINT
(unsigned int — absent from crate::types, and allowlist_type does not emit bare
primitive aliases). Broaden to _?OPM_.* + _?D3DCOLORVALUE and raw_line the UINT
alias.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The iddcx bindgen now SUCCEEDS (C++ fix). Generated module had 38 unresolved-type
errors — a bounded set wdk-sys does not bindgen: UINT, DXGI_FORMAT,
DXGI_COLOR_SPACE_TYPE, IDXGIDevice/Resource, 6 OPM_* types. No WDF type is
missing, so the crate::types sharing (type-identity) holds. Allowlist those
families so they emit locally in iddcx.rs (non-conflicting — absent from
crate::types), keeping allowlist_recursively(false).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Direct clang test on the box proved IddCx.h parses with 0 errors as C++ but fails
as C (wdk_default has no --language=c++) — the IDARG_* typedef names hit "must use
struct tag" in C mode. Fix generate_iddcx: --language=c++ + keep -DIDD_STUB +
allowlist_recursively(false) + full codegen, so it emits ONLY IddCx items
(structs, the IddFunctions table enums, DDI fn-ptr typedefs) and references
WDF/Win/DXGI types from wdk-sys via `use crate::types::*` (no re-emission, no
blocklist). Reverted the ENABLED_API_SUBSETS Iddcx entry (it wrongly pulled
IddCx into the C-mode constants/types passes).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The iddcx bindgen failed with IddCxFuncEnum.h "IDDCX_VERSION_MAJOR is not defined"
+ a cascade of "must use struct tag" on IDARG_* types — NOT the feared #515
header conflict (IddCx parsed fine alongside Base+Wdf). IddCx.h needs STUB mode
(function-table dispatch) for the version macros to resolve; add -DIDD_STUB to
generate_iddcx, matching the wdf-umdf oracle. Deliberately NOT WDF_STUB (wdk-sys
parses wdf non-stubbed; desyncing only here would break WDF type-identity).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Vendor the published, self-contained windows-drivers-rs 0.5.1 crates
(wdk-build, wdk-sys) under vendor/ and add a first-class ApiSubset::Iddcx that
bindgens iddcx/1.10/IddCx.h in an extra pass reusing bindgen::Builder::wdk_default
(allowlist_file (?i).*iddcx.* — emits only IddCx items; WDF/DXGI types resolve to
the shared base/wdf bindings, type-identity by construction). Mirrors the existing
gpio/hid/spb subsets exactly: wdk-build gets the enum variant + iddcx_headers()
(UMDF-only), wdk-sys gets generate_iddcx + the iddcx feature + pub mod iddcx.
[patch.crates-io] redirects all wdk-sys/wdk-build (incl. wdk 0.4.1 transitive) to
the patched copies. wdk-probe enables the iddcx feature.
MAKE-OR-BREAK: does IddCx.h bindgen in wdk-sys config without a header conflict
(issue #515) + does the generated module compile (type-identity)? CI answers it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>