Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,36 @@ Integration lessons (all host-side, none required driver changes):
Next: tranche 3b — ring-consuming capture backend in LuminalShine
(`display_vgd` platf::display_t), driver HDR10 caps, cursor/gamma DDIs,
WGC-RELIABILITY §7 alttab_stress port.

### Tranche 3b + HDR10 — COMPLETE (2026-07-20, Windows box)

**Milestones verified the same day:** (1) LuminalShine consumes the
frame ring directly (`display_vgd_vram_t`: claim → keyed-mutex key 1 →
GPU copy → release; no WGC helper, latency parity ~5 ms) and (2) **HDR10
end to end** — driver build 2 (caps 0x185) creates HDR monitors
(bit_depth=110 wire value), Windows engages advanced color off our
CTA-861.3 EDID block, the ring carries FP16 scRGB, and LuminalShine
encodes HEVC Main10 4:4:4 with HDR metadata. AV1 HDR 10-bit also works;
AV1 4:4:4 is an NVENC hardware gap on RTX 5080 (not a software item).

HDR bring-up lessons (one wasted signing round each — check first):
- **IDDCX_ADAPTER_FLAGS_CAN_PROCESS_FP16 is a contract, not a flag.**
AdapterInitAsync fails STATUS_INVALID_PARAMETER unless the driver also
registers EvtIddCxMonitorSetGammaRamp (HDR 3x4 matrix; GammaSupport
must not be NONE) and acquires via IddCxSwapChainReleaseAndAcquire-
Buffer2 (METADATA2). See "Updates for IddCx 1.10" doc for the full
obligation list. ETW breadcrumbs localize this in one traced
pnputil /restart-device — no re-sign needed to diagnose.
- **Proto bit_depth wire values are 8/10/110/112** (HDR carries a
leading "1"); hdr=1 with bit_depth=10 is BAD_BIT_DEPTH (-4), and the
host log only shows "result=-4" — check dispatch err codes first.
- Ring textures follow the acquired frame's DXGI format; an
advanced-color toggle (BGRA8 ⇄ FP16) is a generation bump like a size
change. Host reader re-latches format automatically.
- Host-side stall detection must key off `latest_sequence` vs the last
delivered sequence, never cumulative publish counters — an idle
desktop is indistinguishable from a stall by counters alone.

Next: cursor + gamma DDIs (hardware cursor ABI is in proto v0.3),
WGC-RELIABILITY §7 alttab_stress port, phase 7 packaging (installer,
strict control-device SDDL — release blocker, uninstaller).
17 changes: 17 additions & 0 deletions crates/luminal-vgd-driver/src/shell/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,23 @@ pub unsafe fn swapchain_release_and_acquire_buffer(
)
}

/// v1.10 variant — mandatory for CAN_PROCESS_FP16 adapters (per-frame
/// IDDCX_METADATA2: HDR metadata, surface color space, SDR white level).
/// Safe to call unconditionally: IddMinimumVersionRequired = 10 means the
/// OS only loads us with a ≥1.10 function table.
pub unsafe fn swapchain_release_and_acquire_buffer2(
swapchain: IDDCX_SWAPCHAIN,
in_args: *mut IDARG_IN_RELEASEANDACQUIREBUFFER2,
out_args: *mut IDARG_OUT_RELEASEANDACQUIREBUFFER2,
) -> NTSTATUS {
iddcx_call!(
_IDDFUNCENUM_IddCxSwapChainReleaseAndAcquireBuffer2TableIndex as PFN_IDDCXSWAPCHAINRELEASEANDACQUIREBUFFER2,
swapchain,
in_args,
out_args
)
}

pub unsafe fn swapchain_finished_processing_frame(swapchain: IDDCX_SWAPCHAIN) -> NTSTATUS {
iddcx_call!(
_IDDFUNCENUM_IddCxSwapChainFinishedProcessingFrameTableIndex as PFN_IDDCXSWAPCHAINFINISHEDPROCESSINGFRAME,
Expand Down
13 changes: 12 additions & 1 deletion crates/luminal-vgd-driver/src/shell/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ unsafe extern "C" fn evt_device_add(
idd.EvtIddCxAdapterCommitModes2 = Some(monitors::evt_commit_modes2);
idd.EvtIddCxMonitorSetDefaultHdrMetaData = Some(monitors::evt_set_default_hdr_metadata);
idd.EvtIddCxMonitorQueryTargetModes2 = Some(monitors::evt_query_target_modes2);
// CAN_PROCESS_FP16 obligates the gamma-ramp DDI (HDR 3x4 matrix);
// an FP16 adapter without it fails AdapterInitAsync validation.
idd.EvtIddCxMonitorSetGammaRamp = Some(monitors::evt_monitor_set_gamma_ramp);

let status = bindings::device_init_config(device_init, &idd);
if status != STATUS_SUCCESS {
Expand Down Expand Up @@ -183,6 +186,11 @@ unsafe extern "C" fn evt_d0_entry(
let mut caps: ffi::IDDCX_ADAPTER_CAPS = zeroed();
caps.Size = size_of::<ffi::IDDCX_ADAPTER_CAPS>() as u32;
caps.MaxMonitorsSupported = luminal_driver_proto::DEFAULT_MAX_MONITORS;
// HDR10: the OS only offers advanced color on an indirect display when
// the adapter declares it can process FP16 desktop content; the frame
// pipeline is format-agnostic (ring textures follow the acquired
// frame's desc), so this is safe to declare unconditionally.
caps.Flags = ffi::IDDCX_ADAPTER_FLAGS_IDDCX_ADAPTER_FLAGS_CAN_PROCESS_FP16;
// MaxDisplayPipelineRate stays 0 (IddSampleDriver convention);
// u64::MAX fails IddCxAdapterInitAsync parameter validation.
caps.EndPointDiagnostics.Size = size_of::<ffi::IDDCX_ENDPOINT_DIAGNOSTIC_INFO>() as u32;
Expand All @@ -193,7 +201,10 @@ unsafe extern "C" fn evt_d0_entry(
caps.EndPointDiagnostics.pEndPointManufacturerName = MANUFACTURER.as_ptr();
caps.EndPointDiagnostics.pHardwareVersion = addr_of_mut!(version);
caps.EndPointDiagnostics.pFirmwareVersion = addr_of_mut!(version);
caps.EndPointDiagnostics.GammaSupport = ffi::IDDCX_FEATURE_IMPLEMENTATION_IDDCX_FEATURE_IMPLEMENTATION_NONE;
// SOFTWARE, matching the registered EvtIddCxMonitorSetGammaRamp: the
// HDR 3x4 matrix is acknowledged by the driver (conversion happens in
// the host encoder). NONE alongside CAN_PROCESS_FP16 is contradictory.
caps.EndPointDiagnostics.GammaSupport = ffi::IDDCX_FEATURE_IMPLEMENTATION_IDDCX_FEATURE_IMPLEMENTATION_SOFTWARE;

let mut in_args: ffi::IDARG_IN_ADAPTER_INIT = zeroed();
in_args.WdfDevice = device;
Expand Down
15 changes: 9 additions & 6 deletions crates/luminal-vgd-driver/src/shell/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,18 @@ use std::time::Instant;
use crate::dispatch::{DeviceState, HandleCtx};
use luminal_vgd_core::modes::Mode;

/// Capabilities the phase-2 shell actually delivers. SDR only for now:
/// HDR, hardware cursor, and gamma caps are added when their DDIs land
/// (phases 2+/4 per docs/FEATURE-MATRIX.md).
pub(crate) const SHELL_CAPS: u32 =
luminal_driver_proto::caps::MULTI_MODE | luminal_driver_proto::caps::PERMANENT_POOL;
/// Capabilities the shell actually delivers. HDR10/SDR10 landed with the
/// HDR tranche (FP16-capable adapter, per-mode wire bit depth, format-
/// aware ring textures); hardware cursor and gamma caps are added when
/// their DDIs land (docs/FEATURE-MATRIX.md).
pub(crate) const SHELL_CAPS: u32 = luminal_driver_proto::caps::MULTI_MODE
| luminal_driver_proto::caps::PERMANENT_POOL
| luminal_driver_proto::caps::HDR10
| luminal_driver_proto::caps::SDR10_BIT;

/// Monotonic build stamp reported in HANDSHAKE/GET_STATUS (CI will stamp
/// this properly in phase 7; hand-bumped during bring-up).
pub(crate) const DRIVER_BUILD: u32 = 1;
pub(crate) const DRIVER_BUILD: u32 = 2;

/// NUL-terminated UTF-16 literal; size the array one past the text so the
/// terminator survives.
Expand Down
70 changes: 60 additions & 10 deletions crates/luminal-vgd-driver/src/shell/monitors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,10 +269,20 @@ pub unsafe extern "C" fn evt_commit_modes(
// HDR paths get real implementations alongside caps::HDR10 later.
// ---------------------------------------------------------------------

/// SDR-only wire format: 8-bit RGB, nothing else.
fn wire_bpc_sdr8() -> ffi::IDDCX_WIRE_BITS_PER_COMPONENT {
/// Wire format for one mode: RGB at the session's bit depth (8 for SDR-8,
/// 10 for SDR-10/HDR10, 12 for HDR12). The OS picks the highest depth the
/// whole path supports; HDR additionally needs the EDID's CTA-861.3 block
/// (core::edid) and the adapter's CAN_PROCESS_FP16 flag (shell::entry).
fn wire_bpc_for(mode: &Mode) -> ffi::IDDCX_WIRE_BITS_PER_COMPONENT {
use luminal_driver_proto::BitDepth;
let mut bpc: ffi::IDDCX_WIRE_BITS_PER_COMPONENT = unsafe { zeroed() };
bpc.Rgb = ffi::IDDCX_BITS_PER_COMPONENT_IDDCX_BITS_PER_COMPONENT_8;
bpc.Rgb = match mode.bit_depth {
BitDepth::Sdr8 => ffi::IDDCX_BITS_PER_COMPONENT_IDDCX_BITS_PER_COMPONENT_8,
BitDepth::Sdr10 | BitDepth::Hdr10 => {
ffi::IDDCX_BITS_PER_COMPONENT_IDDCX_BITS_PER_COMPONENT_10
}
BitDepth::Hdr12 => ffi::IDDCX_BITS_PER_COMPONENT_IDDCX_BITS_PER_COMPONENT_12,
};
bpc
}

Expand All @@ -282,7 +292,11 @@ pub unsafe extern "C" fn evt_adapter_query_target_info(
out_args: *mut ffi::IDARG_OUT_QUERYTARGET_INFO,
) -> NTSTATUS {
let out = &mut *out_args;
out.TargetCaps = ffi::IDDCX_TARGET_CAPS_IDDCX_TARGET_CAPS_NONE;
// HDR10/advanced color needs the target to advertise the wide/high
// color-space pipeline; harmless for SDR-only monitors (the OS still
// gates on the per-monitor EDID + wire bit depth).
out.TargetCaps = ffi::IDDCX_TARGET_CAPS_IDDCX_TARGET_CAPS_HIGH_COLOR_SPACE
| ffi::IDDCX_TARGET_CAPS_IDDCX_TARGET_CAPS_WIDE_COLOR_SPACE;
out.DitheringSupport = zeroed();
STATUS_SUCCESS
}
Expand Down Expand Up @@ -313,7 +327,7 @@ pub unsafe extern "C" fn evt_parse_monitor_description2(
slot.Size = size_of::<ffi::IDDCX_MONITOR_MODE2>() as u32;
slot.Origin = ffi::IDDCX_MONITOR_MODE_ORIGIN_IDDCX_MONITOR_MODE_ORIGIN_MONITORDESCRIPTOR;
slot.MonitorVideoSignalInfo = signal_info(mode, 0);
slot.BitsPerComponent = wire_bpc_sdr8();
slot.BitsPerComponent = wire_bpc_for(mode);
}
out.MonitorModeBufferOutputCount = fill as u32;
STATUS_SUCCESS
Expand Down Expand Up @@ -343,7 +357,7 @@ pub unsafe extern "C" fn evt_query_target_modes2(
// unused. A nonzero requirement against a zero adapter budget makes
// every mode unactivatable (Extend reverts, Scale/Res grayed).
slot.RequiredBandwidth = 0;
slot.BitsPerComponent = wire_bpc_sdr8();
slot.BitsPerComponent = wire_bpc_for(mode);
}
out.TargetModeBufferOutputCount = fill as u32;
STATUS_SUCCESS
Expand All @@ -356,11 +370,47 @@ pub unsafe extern "C" fn evt_commit_modes2(
STATUS_SUCCESS
}

/// CAN_PROCESS_FP16 contract (IddCx 1.10): the OS provides the HDR 3x4
/// color matrix here for adapters that advertise FP16 processing. Our
/// pipeline is pass-through — ring consumers receive the composed FP16
/// scRGB desktop verbatim and the host encoder performs the colorspace
/// conversion — so the matrix is acknowledged and traced, not applied to
/// pixels. GammaSupport is declared SOFTWARE to match.
pub unsafe extern "C" fn evt_monitor_set_gamma_ramp(
monitor: ffi::IDDCX_MONITOR,
in_args: *const ffi::IDARG_IN_SET_GAMMARAMP,
) -> NTSTATUS {
let inp = &*in_args;
let ramp_type = inp.Type as u32;
tracelogging::write_event!(
PROVIDER,
"SetGammaRamp",
level(Informational),
u64("monitor", &(monitor as u64)),
u32("type", &ramp_type),
u32("size", &inp.GammaRampSizeInBytes)
);
STATUS_SUCCESS
}

pub unsafe extern "C" fn evt_set_default_hdr_metadata(
_monitor: ffi::IDDCX_MONITOR,
_in_args: *const ffi::IDARG_IN_MONITOR_SET_DEFAULT_HDR_METADATA,
monitor: ffi::IDDCX_MONITOR,
in_args: *const ffi::IDARG_IN_MONITOR_SET_DEFAULT_HDR_METADATA,
) -> NTSTATUS {
// SDR-only shell: accept and ignore. Stored + used when caps::HDR10
// and the phase-4 frame pipeline land.
// The OS pushes the desktop's effective HDR10 static metadata here when
// advanced color engages. The wire pixels already carry the composed
// desktop (FP16 scRGB), so nothing needs reprocessing — trace it for
// diagnostics; surfacing it to the host via GET_STATUS can come later
// if the encoder wants monitor-derived mastering data.
let inp = &*in_args;
let meta_type = inp.Type as u32;
tracelogging::write_event!(
PROVIDER,
"SetDefaultHdrMetaData",
level(Informational),
u64("monitor", &(monitor as u64)),
u32("type", &meta_type),
u32("size", &inp.Size)
);
STATUS_SUCCESS
}
11 changes: 7 additions & 4 deletions crates/luminal-vgd-driver/src/shell/ring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use windows::Win32::Graphics::Direct3D11::{
D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX, D3D11_RESOURCE_MISC_SHARED_NTHANDLE,
D3D11_TEXTURE2D_DESC, D3D11_USAGE_DEFAULT,
};
use windows::Win32::Graphics::Dxgi::Common::{DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_SAMPLE_DESC};
use windows::Win32::Graphics::Dxgi::Common::{DXGI_FORMAT, DXGI_SAMPLE_DESC};
use windows::Win32::Graphics::Dxgi::{
IDXGIKeyedMutex, IDXGIResource1, DXGI_SHARED_RESOURCE_READ, DXGI_SHARED_RESOURCE_WRITE,
};
Expand Down Expand Up @@ -298,22 +298,25 @@ pub fn acquire_mutex(mutex: &IDXGIKeyedMutex, key: u64, timeout_ms: u32) -> Acqu
}

/// Create the `count` named shared textures for (`session_id`,
/// `generation`) at the given dimensions. BGRA8 (the IddCx frame surface
/// format); keyed mutex starts at key 0 (driver-writable).
/// `generation`) at the given dimensions and format. The format follows
/// the acquired swapchain frame's desc — BGRA8 for SDR desktops, FP16
/// (scRGB) or R10G10B10A2 once the OS composes the display in advanced
/// color; keyed mutex starts at key 0 (driver-writable).
pub fn create_shared_textures(
device: &ID3D11Device,
session_id: u64,
generation: u32,
count: u32,
width: u32,
height: u32,
format: DXGI_FORMAT,
) -> windows::core::Result<Vec<SharedTexture>> {
let desc = D3D11_TEXTURE2D_DESC {
Width: width,
Height: height,
MipLevels: 1,
ArraySize: 1,
Format: DXGI_FORMAT_B8G8R8A8_UNORM,
Format: format,
SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 },
Usage: D3D11_USAGE_DEFAULT,
BindFlags: (D3D11_BIND_RENDER_TARGET.0 | D3D11_BIND_SHADER_RESOURCE.0) as u32,
Expand Down
37 changes: 29 additions & 8 deletions crates/luminal-vgd-driver/src/shell/swapchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ pub(crate) struct FrameRing {
textures: Vec<SharedTexture>,
tex_width: u32,
tex_height: u32,
/// DXGI_FORMAT raw value of the current textures; the OS switches the
/// swapchain surface format when advanced color toggles (BGRA8 ⇄ FP16),
/// which retires textures exactly like a size change.
tex_format: u32,
/// Per-slot: has this slot ever been published this generation? A
/// never-published slot's keyed mutex is still at its creation key
/// (0, driver); after the first publish it lives at key 1 (host).
Expand Down Expand Up @@ -88,6 +92,7 @@ impl FrameRing {
textures: Vec::new(),
tex_width: 0,
tex_height: 0,
tex_format: 0,
ever_published: vec![false; slots],
assigned_before: false,
}
Expand All @@ -99,6 +104,7 @@ impl FrameRing {
self.textures.clear();
self.tex_width = 0;
self.tex_height = 0;
self.tex_format = 0;
self.ever_published.iter_mut().for_each(|b| *b = false);
let generation = self.policy.rebuild();
if let Some(s) = &self.section {
Expand Down Expand Up @@ -299,9 +305,16 @@ fn frame_loop(
last_heartbeat = Instant::now();
}

let mut out: ffi::IDARG_OUT_RELEASEANDACQUIREBUFFER = unsafe { zeroed() };
let status =
unsafe { bindings::swapchain_release_and_acquire_buffer(swapchain.0.cast(), &mut out) };
// Buffer2 is mandatory for CAN_PROCESS_FP16 adapters (IddCx 1.10):
// METADATA2 carries per-frame HDR metadata / surface color space /
// SDR white level alongside the same surface + QPC fields.
let mut in_args: ffi::IDARG_IN_RELEASEANDACQUIREBUFFER2 = unsafe { zeroed() };
in_args.Size = size_of::<ffi::IDARG_IN_RELEASEANDACQUIREBUFFER2>() as u32;
let mut out: ffi::IDARG_OUT_RELEASEANDACQUIREBUFFER2 = unsafe { zeroed() };
out.MetaData.Size = size_of::<ffi::IDDCX_METADATA2>() as u32;
let status = unsafe {
bindings::swapchain_release_and_acquire_buffer2(swapchain.0.cast(), &mut in_args, &mut out)
};
if status == STATUS_PENDING || status == E_PENDING {
unsafe {
let _ = WaitForSingleObject(HANDLE(frame_event.0), 100);
Expand Down Expand Up @@ -370,7 +383,7 @@ fn publish_frame(
ring: &mut FrameRing,
device: &ID3D11Device,
context: &ID3D11DeviceContext,
meta: &ffi::IDDCX_METADATA,
meta: &ffi::IDDCX_METADATA2,
) -> windows::core::Result<()> {
if ring.section.is_none() {
return Ok(()); // transport disabled; drain-only
Expand All @@ -387,9 +400,14 @@ fn publish_frame(
unsafe { frame_tex.GetDesc(&mut desc) };

// Lazy texture (re)creation: first frame, or the committed mode
// changed size. A size change is a full generation bump so the host
// re-opens textures by name.
if ring.textures.is_empty() || desc.Width != ring.tex_width || desc.Height != ring.tex_height {
// changed size, or the surface format changed (advanced-color toggle
// switches BGRA8 ⇄ FP16). Any change is a full generation bump so the
// host re-opens textures by name and re-latches the format.
if ring.textures.is_empty()
|| desc.Width != ring.tex_width
|| desc.Height != ring.tex_height
|| desc.Format.0 as u32 != ring.tex_format
{
if !ring.textures.is_empty() {
ring.retire_textures();
}
Expand All @@ -401,17 +419,20 @@ fn publish_frame(
slots,
desc.Width,
desc.Height,
desc.Format,
)?;
ring.tex_width = desc.Width;
ring.tex_height = desc.Height;
ring.tex_format = desc.Format.0 as u32;
tracelogging::write_event!(
PROVIDER,
"RingTexturesCreated",
level(Informational),
u64("session", &session_id),
u32("generation", &ring.policy.generation),
u32("width", &desc.Width),
u32("height", &desc.Height)
u32("height", &desc.Height),
u32("format", &(desc.Format.0 as u32))
);
}

Expand Down
9 changes: 9 additions & 0 deletions crates/luminal-vgd-ffi/include/luminal_vgd.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@
*/
#define VGD_ERR_IO -1000

/**
* Capability bits of `VgdCaps.caps` the backend gates on (literal
* mirrors of proto `caps::*`; the asserts below keep them honest —
* cbindgen cannot evaluate cross-crate constants).
*/
#define VGD_CAP_HDR10 1

#define VGD_CAP_SDR10_BIT 4

typedef struct VgdDeviceHandle VgdDeviceHandle;

typedef struct VgdRingHandle VgdRingHandle;
Expand Down
8 changes: 8 additions & 0 deletions crates/luminal-vgd-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ use luminal_vgd_host::device::{RingView, VgdDevice};
/// negative proto `err::*` code from the driver.
pub const VGD_ERR_IO: i32 = -1000;

/// Capability bits of `VgdCaps.caps` the backend gates on (literal
/// mirrors of proto `caps::*`; the asserts below keep them honest —
/// cbindgen cannot evaluate cross-crate constants).
pub const VGD_CAP_HDR10: u32 = 1;
pub const VGD_CAP_SDR10_BIT: u32 = 4;
const _: () = assert!(VGD_CAP_HDR10 == luminal_driver_proto::caps::HDR10);
const _: () = assert!(VGD_CAP_SDR10_BIT == luminal_driver_proto::caps::SDR10_BIT);

pub struct VgdDeviceHandle(VgdDevice);
pub struct VgdRingHandle(RingView);

Expand Down