协作头脑风暴
brainstorming
在实现前澄清需求、探索设计选择,并形成清晰的开发方向。
indirect-display【开发与工程】Windows Indirect Display Driver (IDD) framework. UMDF v2-based driver model for adding virtual monitors that the Desktop Window Manager treats as real displays, then capturing the rendered frames in user mode and routing them somewhere else (network, file, encoder) instead of a physical panel. Covers IddCx callbacks, monitor descriptors (EDID/MCCS), …
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
name: indirect-display description: | Windows Indirect Display Driver (IDD) framework. UMDF v2-based driver model for adding virtual monitors that the Desktop Window Manager treats as real displays, then capturing the rendered frames in user mode and routing them somewhere else (network, file, encoder) instead of a physical panel. Covers IddCx callbacks, monitor descriptors (EDID/MCCS), swap-chain processing, cursor handling, hot-plug, hardware cursor, multi-monitor, HDR, and integration with NVENC / Rivermax / RDMA NICs for low-latency network streaming.
USE WHEN: user mentions "IDD", "Indirect Display", "IddCx", "IddSampleDriver", "virtual monitor", "virtual display driver", "IDDCX_SWAPCHAIN", "AcquireBuffer", "FinishedProcessingFrame", "EDID for virtual display", "stream desktop", "network display"
Deep Knowledge: Use
mcp__documentation__fetch_docswith technology:indirect-display. Authoritative source:learn.microsoft.com/en-us/windows-hardware/drivers/display/indirect-display-driver-model-overview. Canonical sample:general/IddSampleDriverinmicrosoft/Windows-driver-samples.
IDD is a UMDF v2 driver that registers itself as a display adapter via IddCx. The OS treats the IDD's monitors like real displays for layout, DPI, multi-mon, cursor, etc. The driver receives swap-chains (a stream of GPU surfaces) for each connected monitor and is responsible for consuming the frames somehow.
| Goal | IDD fits? |
|---|---|
| Show desktop on a USB display, virtual monitor, or remote network endpoint | YES |
| Render a virtual screen and stream it over LAN/Wi-Fi/Rivermax NIC | YES |
| Add a "headless" extra monitor for window placement | YES |
| Drive a real PCIe GPU's display outputs | NO — that needs WDDM |
| Replace the primary display of an existing GPU | NO — additive only |
Apps (DWM composes the desktop into per-monitor swapchains)
|
────────────────────────────── Kernel mode (DXGK / GPU schedulers) ──────────
| (handled by IddCx + WDDM emulation)
────────────────────────────── User mode (WUDFHost.exe) ────────────────────
|
+----------------------------+
| Your IDD UMDF DLL |
| - EvtIddCxAdapterInitFinished
| - EvtIddCxParseMonitorDescription
| - EvtIddCxMonitorGetDefaultModes
| - EvtIddCxMonitorQueryTargetModes
| - EvtIddCxMonitorAssignSwapChain
| - <swap-chain processing thread>
| AcquireBuffer / FinishedProcessing
| -> encode (NVENC) -> send (TCP/UDP/Rivermax)
+----------------------------+
IddCxDriverInitialize registers IDDIddCxDeviceInitializeIddCxMonitorCreate + IddCxMonitorArrivalIddCxSwapChainAcquireBuffer → consume → IddCxSwapChainFinishedProcessingFrame → IddCxSwapChainReleaseAndAcquireBufferIddCxMonitorDeparture → IddCxMonitorReleaseAndDestroyProvide a synthetic EDID via EvtIddCxParseMonitorDescription. The IDD sample ships an EDID blob; for production, generate one with valid manufacturer ID, product code, supported timings (CEA blocks for HDR/HDMI traits), and checksum.
NTSTATUS MyEvtParseMonitorDescription(
const IDARG_IN_PARSEMONITORDESCRIPTION* in,
IDARG_OUT_PARSEMONITORDESCRIPTION* out)
{
if (in->MonitorDescription.DataSize != EDID_SIZE) return STATUS_INVALID_PARAMETER;
// Extract preferred timing from EDID block
out->MonitorModeBufferOutputCount = 1;
if (in->pMonitorModes != NULL) {
in->pMonitorModes[0] = MakeMode(1920, 1080, 60); // helper builds IDDCX_MONITOR_MODE
}
return STATUS_SUCCESS;
}
// Started from EvtIddCxMonitorAssignSwapChain
DWORD WINAPI SwapChainProcessor(LPVOID lp) {
auto* ctx = static_cast<MonitorContext*>(lp);
auto& dx = ctx->DxResources; // your D3D11/D3D12 device + context
for (;;) {
IDARG_OUT_RELEASEANDACQUIREBUFFER buf{};
HRESULT hr = IddCxSwapChainReleaseAndAcquireBuffer(ctx->SwapChain, &buf);
if (hr == E_PENDING) {
// Wait on `ctx->NewFrameEvent` with timeout, then continue
WaitForSingleObject(ctx->NewFrameEvent, 16); // ~60 fps poll
continue;
}
if (FAILED(hr)) break; // shutting down
// buf.MetaData.PresentationTime, .Buffer (ID3D11Texture2D / DXGI handle)
// 1. Get the staging texture (or use a shared NT handle)
// 2. Encode with NVENC / quick-sync / etc.
// 3. Send the encoded packet over the chosen transport (TCP / UDP / Rivermax)
ConsumeFrame(ctx, buf);
IDARG_IN_FINISHEDPROCESSINGFRAME fin{};
IddCxSwapChainFinishedProcessingFrame(ctx->SwapChain, &fin);
}
return 0;
}
The framework hands you GPU surfaces, not CPU bitmaps. Doing CPU-side Map per frame is fast enough for 1080p60 but won't reach 4K60 with low latency — keep the data on the GPU and feed an encoder.
DWM tells IDD whether the cursor should be drawn into the frame or composited separately. The driver gets cursor shape + position via:
IddCxMonitorSetupHardwareCursor — opt in to receive a separate cursor surfaceCursor event/handle: when signaled, call IddCxMonitorQueryHardwareCursor to get position + new shapeFor network streaming, sending the cursor as a separate small surface (vs. baking it into the frame) lets the receiver render it locally for sub-frame latency.
| Transport | Why pick it | Latency |
|---|---|---|
| NVIDIA Rivermax (ConnectX NICs) | Hardware-timed RTP, tight jitter, GPUDirect to DMA buffers | sub-ms |
| UDP + FEC (RIST / SRT) | Loss tolerance over WAN | 50–200 ms |
| WebRTC / SRTP | Browser-friendly receiver | 100–300 ms |
| Plain TCP | Simple, lossless; HOL blocking | 30 ms LAN, very bad WAN |
| RDMA (RoCE / iWARP) | Zero-copy LAN, NIC offloads | sub-ms |
For high resolution / framerate, encode first:
D3D11_TEXTURE2D directly with NV_ENC_INPUT_RESOURCE_TYPE_DIRECTX.Call IddCxMonitorArrival once per virtual monitor at startup; the DWM treats each independently. Each gets its own swap-chain assignment + processor thread. Keep per-monitor context small and avoid global state.
Declare HDR capability in the EDID (CEA HDR Static Metadata block) and in IDDCX_MONITOR_MODE.MonitorVideoSignalInfo.ColorInfo. DWM will then push HDR swap-chains (DXGI_FORMAT_R16G16B16A16_FLOAT / R10G10B10A2_UNORM). Your encoder pipeline must support 10-bit HDR (HEVC Main10 / AV1).
[Standard.NT$ARCH$.10.0...19041]
%Device.DeviceDesc% = MyIdd_Install, ROOT\MyIddDriver
[MyIdd_Install.NT.Wdf]
UmdfDispatcher = NativeIdd
UmdfServiceOrder = MyIddDriver
UmdfDirectHardwareAccess = AllowDirectHardwareAccess
UmdfFileObjectPolicy = AllowNullAndUnknownFileObjects
[MyIddDriver]
UmdfLibraryVersion = $UMDFVERSION$
ServiceBinary = %13%\MyIddDriver.dll
DriverCLSID = {GUID}
UmdfDispatcher = NativeIdd is the magic that wires IddCx in.
| Anti-Pattern | Why It's Bad | Correct Approach |
|---|---|---|
| Mapping every frame to CPU memory | Stalls GPU, kills throughput | Stay on GPU; encode with NVENC/QS/AMF directly |
| Allocating per frame | Heap churn at 60+ Hz | Pre-allocate ring of staging textures / encoder buffers |
Sleep(16) in the loop | Frame drops, jitter | Wait on the framework's "new frame" event with bounded timeout |
| Synchronous network I/O on the processing thread | Blocks the swap-chain | Encode + queue; separate sender thread / async I/O |
Forgetting FinishedProcessingFrame | Framework deadlocks | Pair every AcquireBuffer with a finish/release |
| Crashing on driver shutdown | Host recycle, brief desktop glitch | On EvtIddCxMonitorUnassignSwapChain, signal stop, join thread, release D3D resources |
| Bogus EDID | Monitor not recognized, weird modes | Generate a real EDID with valid checksum and CEA extension blocks |
| Hardcoded 1920×1080@60 | Users can't change the mode | Declare a sensible mode list in MonitorGetDefaultModes |