feat(windows-drivers): STEP 7 — HDR/FP16 (validated on-glass: Mac connects WITH HDR)
apple / swift (push) Failing after 4s
apple / screenshots (push) Has been skipped
windows-drivers / probe-and-proto (push) Successful in 18s
ci / rust (push) Successful in 1m13s
windows-drivers / driver-build (push) Successful in 1m9s
ci / web (push) Successful in 42s
ci / docs-site (push) Successful in 59s
android / android (push) Successful in 3m16s
deb / build-publish (push) Successful in 2m37s
decky / build-publish (push) Successful in 10s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 6s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 5s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 3s
windows-host / package (push) Successful in 5m25s
ci / bench (push) Successful in 4m39s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m29s
docker / deploy-docs (push) Successful in 17s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m12s
apple / swift (push) Failing after 4s
apple / screenshots (push) Has been skipped
windows-drivers / probe-and-proto (push) Successful in 18s
ci / rust (push) Successful in 1m13s
windows-drivers / driver-build (push) Successful in 1m9s
ci / web (push) Successful in 42s
ci / docs-site (push) Successful in 59s
android / android (push) Successful in 3m16s
deb / build-publish (push) Successful in 2m37s
decky / build-publish (push) Successful in 10s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 6s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 5s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 3s
windows-host / package (push) Successful in 5m25s
ci / bench (push) Successful in 4m39s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m29s
docker / deploy-docs (push) Successful in 17s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m12s
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>
This commit is contained in:
@@ -88,10 +88,14 @@ pub fn init_adapter(device: WDFDEVICE) -> NTSTATUS {
|
||||
|
||||
let mut caps: iddcx::IDDCX_ADAPTER_CAPS = unsafe { core::mem::zeroed() };
|
||||
caps.Size = core::mem::size_of::<iddcx::IDDCX_ADAPTER_CAPS>() as u32;
|
||||
// Flags = NONE (SDR). The working upstream virtual-display-rs sets NO Flags. CAN_PROCESS_FP16 requires a
|
||||
// newer IddCx contract than the INF's UmdfExtensions=IddCx0102 grants, so the framework's adapter-caps
|
||||
// Validate (ddivalidation.cpp:797) rejects it with STATUS_INVALID_PARAMETER. HDR/FP16 is deferred to
|
||||
// STEP 7 (needs a higher UmdfExtensions binding + the obligated *2/HDR DDIs).
|
||||
// STEP 7 (HDR): declare we can process FP16 (scRGB) desktop surfaces — this is what marks the virtual
|
||||
// monitor advanced-color-capable (→ the host sees display_hdr=true → the "Use HDR" toggle appears). The
|
||||
// ONLY reason STEP 3 rejected this flag was setting it WITHOUT the obligated *2/HDR DDIs; those are now
|
||||
// registered in entry.rs (parse_monitor_description2/monitor_query_modes2/adapter_commit_modes2 +
|
||||
// query_target_info/set_default_hdr_metadata/set_gamma_ramp). The proven oracle sets exactly this flag
|
||||
// with the INF still at UmdfExtensions=IddCx0102. GammaSupport stays NONE (set above). Enum is bindgen
|
||||
// ModuleConsts — the variant is a plain-int const assignable straight to the `Flags` field.
|
||||
caps.Flags = iddcx::IDDCX_ADAPTER_FLAGS::IDDCX_ADAPTER_FLAGS_CAN_PROCESS_FP16;
|
||||
caps.MaxMonitorsSupported = 16;
|
||||
caps.EndPointDiagnostics = diag;
|
||||
|
||||
|
||||
@@ -81,11 +81,53 @@ pub unsafe extern "C" fn parse_monitor_description(
|
||||
STATUS_SUCCESS
|
||||
}
|
||||
|
||||
/// HDR (`*2`) mode list — writes `IDDCX_MONITOR_MODE2` (+BitsPerComponent). Mandatory under FP16.
|
||||
/// HDR (`*2`) mode list — writes `IDDCX_MONITOR_MODE2` (+BitsPerComponent). Mandatory under FP16. Mirrors
|
||||
/// the v1 `parse_monitor_description` exactly (EDID-serial lookup → count-then-fill, same
|
||||
/// BUFFER_TOO_SMALL/SUCCESS logic), but emits `IDDCX_MONITOR_MODE2` with the per-mode wire bit-depth so the
|
||||
/// OS offers HDR10 modes. `IDARG_OUT_PARSEMONITORDESCRIPTION` is the SAME out struct as v1 (shared by the C
|
||||
/// header); only the in-args / mode struct are the `*2` variants.
|
||||
pub unsafe extern "C" fn parse_monitor_description2(
|
||||
_p_in: *const iddcx::IDARG_IN_PARSEMONITORDESCRIPTION2,
|
||||
_p_out: *mut iddcx::IDARG_OUT_PARSEMONITORDESCRIPTION,
|
||||
p_in: *const iddcx::IDARG_IN_PARSEMONITORDESCRIPTION2,
|
||||
p_out: *mut iddcx::IDARG_OUT_PARSEMONITORDESCRIPTION,
|
||||
) -> NTSTATUS {
|
||||
// SAFETY: framework-provided in/out args, valid for the call.
|
||||
let in_args = unsafe { &*p_in };
|
||||
let out_args = unsafe { &mut *p_out };
|
||||
// SAFETY: the framework supplies a valid EDID buffer of `DataSize` bytes.
|
||||
let edid = unsafe {
|
||||
core::slice::from_raw_parts(
|
||||
in_args.MonitorDescription.pData.cast::<u8>(),
|
||||
in_args.MonitorDescription.DataSize as usize,
|
||||
)
|
||||
};
|
||||
let Ok(id) = crate::edid::Edid::get_serial(edid) else {
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
};
|
||||
let Some(modes) = crate::monitor::modes_for_id(id) else {
|
||||
return STATUS_NOT_FOUND;
|
||||
};
|
||||
let count = crate::monitor::flatten(&modes).count() as u32;
|
||||
out_args.MonitorModeBufferOutputCount = count;
|
||||
if in_args.MonitorModeBufferInputCount < count {
|
||||
// A zero input count is a count-only probe (success); a non-zero too-small buffer is an error.
|
||||
return if in_args.MonitorModeBufferInputCount > 0 {
|
||||
STATUS_BUFFER_TOO_SMALL
|
||||
} else {
|
||||
STATUS_SUCCESS
|
||||
};
|
||||
}
|
||||
// SAFETY: `pMonitorModes` points to >= `count` IDDCX_MONITOR_MODE2 entries (validated above).
|
||||
let out = unsafe { core::slice::from_raw_parts_mut(in_args.pMonitorModes, count as usize) };
|
||||
for (item, slot) in crate::monitor::flatten(&modes).zip(out.iter_mut()) {
|
||||
let mut mode: iddcx::IDDCX_MONITOR_MODE2 = unsafe { core::mem::zeroed() };
|
||||
mode.Size = core::mem::size_of::<iddcx::IDDCX_MONITOR_MODE2>() as u32;
|
||||
mode.Origin = iddcx::IDDCX_MONITOR_MODE_ORIGIN::IDDCX_MONITOR_MODE_ORIGIN_MONITORDESCRIPTOR;
|
||||
mode.MonitorVideoSignalInfo =
|
||||
crate::monitor::display_info(item.width, item.height, item.refresh_rate);
|
||||
mode.BitsPerComponent = crate::monitor::wire_bits();
|
||||
*slot = mode;
|
||||
}
|
||||
out_args.PreferredMonitorModeIdx = 0;
|
||||
STATUS_SUCCESS
|
||||
}
|
||||
|
||||
@@ -122,12 +164,30 @@ pub unsafe extern "C" fn monitor_query_modes(
|
||||
STATUS_SUCCESS
|
||||
}
|
||||
|
||||
/// HDR (`*2`) target modes — writes `IDDCX_TARGET_MODE2`. Mandatory under FP16.
|
||||
/// HDR (`*2`) target modes — writes `IDDCX_TARGET_MODE2`. Mandatory under FP16. Mirrors the v1
|
||||
/// `monitor_query_modes` exactly (pointer-match the monitor → count → fill), but emits `IDDCX_TARGET_MODE2`
|
||||
/// (with per-mode wire bit-depth) via `monitor::target_mode2`. `IDARG_OUT_QUERYTARGETMODES` is the SAME out
|
||||
/// struct as v1.
|
||||
pub unsafe extern "C" fn monitor_query_modes2(
|
||||
_monitor: iddcx::IDDCX_MONITOR,
|
||||
_p_in: *const iddcx::IDARG_IN_QUERYTARGETMODES2,
|
||||
_p_out: *mut iddcx::IDARG_OUT_QUERYTARGETMODES,
|
||||
monitor: iddcx::IDDCX_MONITOR,
|
||||
p_in: *const iddcx::IDARG_IN_QUERYTARGETMODES2,
|
||||
p_out: *mut iddcx::IDARG_OUT_QUERYTARGETMODES,
|
||||
) -> NTSTATUS {
|
||||
// SAFETY: framework-provided in/out args, valid for the call.
|
||||
let in_args = unsafe { &*p_in };
|
||||
let out_args = unsafe { &mut *p_out };
|
||||
let Some(modes) = crate::monitor::modes_for_object(monitor) else {
|
||||
return STATUS_NOT_FOUND;
|
||||
};
|
||||
let count = crate::monitor::flatten(&modes).count() as u32;
|
||||
out_args.TargetModeBufferOutputCount = count;
|
||||
if in_args.TargetModeBufferInputCount >= count {
|
||||
// SAFETY: `pTargetModes` points to >= `count` IDDCX_TARGET_MODE2 entries.
|
||||
let out = unsafe { core::slice::from_raw_parts_mut(in_args.pTargetModes, count as usize) };
|
||||
for (item, slot) in crate::monitor::flatten(&modes).zip(out.iter_mut()) {
|
||||
*slot = crate::monitor::target_mode2(item.width, item.height, item.refresh_rate);
|
||||
}
|
||||
}
|
||||
STATUS_SUCCESS
|
||||
}
|
||||
|
||||
|
||||
@@ -76,10 +76,18 @@ extern "C" fn driver_add(_driver: WDFDRIVER, mut init: PWDFDEVICE_INIT) -> NTSTA
|
||||
cfg.EvtIddCxMonitorGetDefaultDescriptionModes = Some(callbacks::monitor_get_default_modes);
|
||||
cfg.EvtIddCxMonitorQueryTargetModes = Some(callbacks::monitor_query_modes);
|
||||
cfg.EvtIddCxAdapterCommitModes = Some(callbacks::adapter_commit_modes);
|
||||
// SDR config — matches the working upstream virtual-display-rs. The *2 / gamma / HDR-metadata /
|
||||
// query-target-info callbacks are FP16-OBLIGATED: registering them while caps declare no FP16 makes the
|
||||
// framework's adapter Validate reject (ddivalidation.cpp:797 -> INVALID_PARAMETER). They return with
|
||||
// FP16 caps under a higher UmdfExtensions binding in STEP 7 (HDR).
|
||||
// STEP 7 (HDR): the *2 mode DDIs + the gamma/HDR-metadata/query-target-info callbacks. The adapter
|
||||
// caps now set CAN_PROCESS_FP16 (adapter.rs), which OBLIGATES this whole set — without them the OS
|
||||
// rejects the adapter at init ("Failed to get adapter"). The proven oracle (entry.rs) registers the *2
|
||||
// variants ALONGSIDE the v1 callbacks above (NOT instead of them) — the OS prefers the *2 on IddCx
|
||||
// 1.10 and falls back to v1 down-level — so we replicate exactly: keep both. The framework no longer
|
||||
// rejects the *2 set because the FP16 cap is now present (the only reason STEP 3 had to drop them).
|
||||
cfg.EvtIddCxParseMonitorDescription2 = Some(callbacks::parse_monitor_description2);
|
||||
cfg.EvtIddCxMonitorQueryTargetModes2 = Some(callbacks::monitor_query_modes2);
|
||||
cfg.EvtIddCxAdapterCommitModes2 = Some(callbacks::adapter_commit_modes2);
|
||||
cfg.EvtIddCxAdapterQueryTargetInfo = Some(callbacks::query_target_info);
|
||||
cfg.EvtIddCxMonitorSetDefaultHdrMetaData = Some(callbacks::set_default_hdr_metadata);
|
||||
cfg.EvtIddCxMonitorSetGammaRamp = Some(callbacks::set_gamma_ramp);
|
||||
cfg.EvtIddCxMonitorAssignSwapChain = Some(callbacks::assign_swap_chain);
|
||||
cfg.EvtIddCxMonitorUnassignSwapChain = Some(callbacks::unassign_swap_chain);
|
||||
cfg.EvtIddCxDeviceIoControl = Some(callbacks::device_io_control);
|
||||
|
||||
@@ -142,6 +142,35 @@ pub fn target_mode(width: u32, height: u32, refresh_rate: u32) -> iddcx::IDDCX_T
|
||||
tm
|
||||
}
|
||||
|
||||
/// Wire bit-depth advertised per mode in the `*2` (HDR) mode DDIs. STEP 7: advertise BOTH 8 and 10 bpc
|
||||
/// RGB (so the OS offers HDR10 modes), no YCbCr. The wdk-sys bindgen enum is `ModuleConsts`, so each
|
||||
/// `IDDCX_BITS_PER_COMPONENT_*` is a plain-int const and the `IDDCX_WIRE_BITS_PER_COMPONENT` fields are
|
||||
/// plain ints — OR the constants directly (NO newtype `.0` like the oracle's wdf-umdf-sys binding). Field
|
||||
/// names (Rgb/YCbCr444/YCbCr422/YCbCr420, IDDCX_BITS_PER_COMPONENT_8/_10/_NONE) are the verbatim C header
|
||||
/// names, identical across both bindings.
|
||||
pub fn wire_bits() -> iddcx::IDDCX_WIRE_BITS_PER_COMPONENT {
|
||||
let rgb = iddcx::IDDCX_BITS_PER_COMPONENT::IDDCX_BITS_PER_COMPONENT_8
|
||||
| iddcx::IDDCX_BITS_PER_COMPONENT::IDDCX_BITS_PER_COMPONENT_10;
|
||||
let mut w: iddcx::IDDCX_WIRE_BITS_PER_COMPONENT = unsafe { core::mem::zeroed() };
|
||||
w.Rgb = rgb;
|
||||
w.YCbCr444 = iddcx::IDDCX_BITS_PER_COMPONENT::IDDCX_BITS_PER_COMPONENT_NONE;
|
||||
w.YCbCr422 = iddcx::IDDCX_BITS_PER_COMPONENT::IDDCX_BITS_PER_COMPONENT_NONE;
|
||||
w.YCbCr420 = iddcx::IDDCX_BITS_PER_COMPONENT::IDDCX_BITS_PER_COMPONENT_NONE;
|
||||
w
|
||||
}
|
||||
|
||||
/// `IDDCX_TARGET_MODE2` for a scan-out mode (HDR `*2` path): builds the v1 [`target_mode`] and copies its
|
||||
/// `TargetVideoSignalInfo`, then stamps the `*2` Size + per-mode wire bit-depth ([`wire_bits`]). Rest
|
||||
/// zeroed.
|
||||
pub fn target_mode2(width: u32, height: u32, refresh_rate: u32) -> iddcx::IDDCX_TARGET_MODE2 {
|
||||
let m1 = target_mode(width, height, refresh_rate);
|
||||
let mut tm: iddcx::IDDCX_TARGET_MODE2 = unsafe { core::mem::zeroed() };
|
||||
tm.Size = core::mem::size_of::<iddcx::IDDCX_TARGET_MODE2>() as u32;
|
||||
tm.TargetVideoSignalInfo = m1.TargetVideoSignalInfo;
|
||||
tm.BitsPerComponent = wire_bits();
|
||||
tm
|
||||
}
|
||||
|
||||
/// A monitor's advertised modes (the looked-up entry returns a clone for lock-free mode-DDI fill).
|
||||
pub fn modes_for_id(id: u32) -> Option<Vec<Mode>> {
|
||||
MONITOR_MODES
|
||||
|
||||
Reference in New Issue
Block a user