Documentation Index
Fetch the complete documentation index at: https://mintlify.com/VirtualDrivers/Virtual-Display-Driver/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The SwapChainProcessor class manages a dedicated thread that continuously consumes and processes frame buffers from an IddCx swap chain. This is the core rendering pipeline component of the virtual display driver.
Header: Driver.h:71-89
Implementation: Driver.cpp:3082-3400+
Class Declaration
class SwapChainProcessor
{
public:
SwapChainProcessor(IDDCX_SWAPCHAIN hSwapChain,
std::shared_ptr<Direct3DDevice> Device,
HANDLE NewFrameEvent);
~SwapChainProcessor();
private:
static DWORD CALLBACK RunThread(LPVOID Argument);
void Run();
void RunCore();
public:
IDDCX_SWAPCHAIN m_hSwapChain;
std::shared_ptr<Direct3DDevice> m_Device;
HANDLE m_hAvailableBufferEvent;
Microsoft::WRL::Wrappers::Thread m_hThread;
Microsoft::WRL::Wrappers::Event m_hTerminateEvent;
};
Constructor
SwapChainProcessor::SwapChainProcessor
Signature:
SwapChainProcessor::SwapChainProcessor(
IDDCX_SWAPCHAIN hSwapChain,
std::shared_ptr<Direct3DDevice> Device,
HANDLE NewFrameEvent
);
Parameters:
hSwapChain: Handle to the IddCx swap chain object
Device: Shared pointer to the Direct3D device for rendering
NewFrameEvent: Event handle signaled when new frame is available
Source Location: Driver.cpp:3082
Initialization:
- Stores swap chain handle, device, and event handle
- Creates termination event for clean shutdown
- Spawns processing thread immediately
Example Usage:
auto processor = std::make_unique<SwapChainProcessor>(
swapChainHandle,
devicePtr,
newFrameEventHandle
);
Thread Creation:
m_hTerminateEvent.Attach(CreateEvent(nullptr, FALSE, FALSE, nullptr));
m_hThread.Attach(CreateThread(nullptr, 0, RunThread, this, 0, nullptr));
Destructor
SwapChainProcessor::~SwapChainProcessor
Signature:
SwapChainProcessor::~SwapChainProcessor();
Source Location: Driver.cpp:3123
Cleanup Process:
- Signals termination event to stop processing thread
- Waits for thread to complete (
WaitForSingleObject)
- Handles wait results (WAIT_OBJECT_0, WAIT_ABANDONED, WAIT_TIMEOUT)
- Logs cleanup status
Thread Synchronization:
SetEvent(m_hTerminateEvent.Get());
if (m_hThread.Get()) {
DWORD waitResult = WaitForSingleObject(m_hThread.Get(), INFINITE);
// Handle waitResult
}
Thread Management
RunThread (Static)
Signature:
static DWORD CALLBACK SwapChainProcessor::RunThread(LPVOID Argument);
Source Location: Driver.cpp:3182
Purpose: Thread entry point that invokes the instance Run() method.
Implementation:
DWORD CALLBACK SwapChainProcessor::RunThread(LPVOID Argument) {
reinterpret_cast<SwapChainProcessor*>(Argument)->Run();
return 0;
}
Run
Signature:
void SwapChainProcessor::Run();
Source Location: Driver.cpp:3193
Purpose: Sets up multimedia thread characteristics and invokes core processing loop.
Multimedia Thread Configuration:
DWORD AvTask = 0;
HANDLE AvTaskHandle = AvSetMmThreadCharacteristicsW(L"Distribution", &AvTask);
Benefits:
- Intelligent thread prioritization by Windows
- Improved throughput in high CPU-load scenarios
- Better frame timing consistency
Cleanup:
RunCore(); // Process frames
// Delete swap chain when processing completes
if (m_hSwapChain) {
WdfObjectDelete((WDFOBJECT)m_hSwapChain);
m_hSwapChain = nullptr;
}
// Revert thread characteristics
AvRevertMmThreadCharacteristics(AvTaskHandle);
RunCore
Signature:
void SwapChainProcessor::RunCore();
Source Location: Driver.cpp:3261
Purpose: Main frame acquisition and processing loop.
Frame Processing Pipeline
Step 1: Set Device to SwapChain
// Get DXGI device interface
ComPtr<IDXGIDevice> DxgiDevice;
HRESULT hr = m_Device->Device.As(&DxgiDevice);
// Validate device is still valid
if (!m_Device || !m_Device->Device) {
vddlog("e", "Direct3DDevice became invalid during SwapChain processing");
return;
}
// Assign device to swap chain
IDAARG_IN_SWAPCHAINSETDEVICE SetDevice = {};
SetDevice.pDevice = DxgiDevice.Get();
hr = IddCxSwapChainSetDevice(m_hSwapChain, &SetDevice);
Step 2: Buffer Acquisition Loop
for (;;) {
ComPtr<IDXGIResource> AcquiredBuffer;
// Request next buffer from producer
IDARG_IN_RELEASEANDACQUIREBUFFER2 BufferInArgs = {};
BufferInArgs.Size = sizeof(BufferInArgs);
IDXGIResource* pSurface;
// Use version-appropriate API
if (IDD_IS_FUNCTION_AVAILABLE(IddCxSwapChainReleaseAndAcquireBuffer2)) {
IDARG_OUT_RELEASEANDACQUIREBUFFER2 Buffer = {};
hr = IddCxSwapChainReleaseAndAcquireBuffer2(m_hSwapChain, &BufferInArgs, &Buffer);
pSurface = Buffer.MetaData.pSurface;
} else {
IDARG_OUT_RELEASEANDACQUIREBUFFER Buffer = {};
hr = IddCxSwapChainReleaseAndAcquireBuffer(m_hSwapChain, &Buffer);
pSurface = Buffer.MetaData.pSurface;
}
// Handle result...
}
Step 3: Handle Pending Buffers
if (hr == E_PENDING) {
// No buffer available yet, wait for signal
HANDLE WaitHandles[] = {
m_hAvailableBufferEvent, // New frame available
m_hTerminateEvent.Get() // Shutdown requested
};
DWORD WaitResult = WaitForMultipleObjects(
ARRAYSIZE(WaitHandles),
WaitHandles,
FALSE, // Wait for any handle
100 // 100ms timeout
);
if (WaitResult == WAIT_OBJECT_0 || WaitResult == WAIT_TIMEOUT) {
continue; // Retry buffer acquisition
} else if (WaitResult == WAIT_OBJECT_0 + 1) {
break; // Terminate requested
} else {
// Unexpected error
hr = HRESULT_FROM_WIN32(WaitResult);
break;
}
}
Step 4: Process Acquired Buffer
else if (SUCCEEDED(hr)) {
AcquiredBuffer.Attach(pSurface);
// ==============================
// TODO: Process the frame here
//
// This is the most performance-critical section.
// Possible operations:
// * GPU copy to staging surface for CPU mapping
// * GPU encode operation (H.264, H.265, etc.)
// * GPU VPBlt to another surface
// * Custom compute shader processing
// ==============================
// Release the buffer
AcquiredBuffer.Reset();
// Notify IddCx that frame processing is complete
hr = IddCxSwapChainFinishedProcessingFrame(m_hSwapChain);
if (FAILED(hr)) {
break; // Fatal error
}
}
The frame processing section (marked with TODO in the source) is the most performance-critical part of the driver:
// Acquired buffer is available here
AcquiredBuffer.Attach(pSurface);
// **CRITICAL PERFORMANCE SECTION**
// Must complete as quickly as possible to maintain frame rate
// Typical operations:
// - GPU copy: ~0.5-2ms for 1080p
// - GPU encode: ~5-15ms for 1080p H.264
// - Compute shader: ~1-5ms depending on complexity
AcquiredBuffer.Reset();
Optimization Guidelines
- Use GPU Operations: Avoid CPU access to frame buffers (requires staging surfaces and map/unmap)
- Asynchronous Processing: Queue GPU commands asynchronously
- Resource Pooling: Reuse D3D resources to avoid allocation overhead
- Minimize State Changes: Batch similar operations together
- Profile Regularly: Use GPU profilers to identify bottlenecks
Retry Logic
The implementation includes retry logic for transient errors:
DWORD retryDelay = 1;
const DWORD maxRetryDelay = 100;
int retryCount = 0;
const int maxRetries = 5;
// On successful buffer acquisition:
retryDelay = 1;
retryCount = 0;
// On error:
if (retryCount < maxRetries) {
Sleep(retryDelay);
retryDelay = min(retryDelay * 2, maxRetryDelay); // Exponential backoff
retryCount++;
continue;
}
Error Handling
Common Error Scenarios
E_PENDING: No buffer currently available
- Action: Wait for
m_hAvailableBufferEvent signal
- Timeout: 100ms
- Recovery: Retry acquisition
DXGI_ERROR_DEVICE_REMOVED: GPU device lost
- Action: Log error and exit processing loop
- Recovery: OS will recreate swap chain with new device
DXGI_ERROR_ACCESS_LOST: Swap chain access lost
- Action: Exit processing loop
- Recovery: Wait for new swap chain assignment
Logging
All major operations are logged for diagnostics:
vddlog("d", "SwapChain processing thread started");
vddlog("i", "Device set to swap chain successfully");
vddlog("e", "Failed to acquire buffer");
vddlog("w", "Buffer acquisition retry");
Thread Lifecycle
Integration Example
Creating SwapChainProcessor
void IndirectDeviceContext::AssignSwapChain(
IDDCX_MONITOR& Monitor,
IDDCX_SWAPCHAIN SwapChain,
LUID RenderAdapter,
HANDLE NewFrameEvent
) {
// Get or create D3D device for this adapter
auto device = GetOrCreateDevice(RenderAdapter);
// Create processor
m_ProcessingThread = std::make_unique<SwapChainProcessor>(
SwapChain,
device,
NewFrameEvent
);
m_Monitor = Monitor;
}
Destroying SwapChainProcessor
void IndirectDeviceContext::UnassignSwapChain() {
// Destructor automatically handles cleanup
m_ProcessingThread.reset();
}
Member Variables
m_hSwapChain
Type: IDDCX_SWAPCHAIN
Purpose: Handle to the IddCx swap chain object.
Usage: Passed to all IddCx swap chain APIs.
m_Device
Type: std::shared_ptr<Direct3DDevice>
Purpose: Shared pointer to D3D device used for rendering.
Lifecycle: Reference counted, automatically cleaned up when last reference released.
m_hAvailableBufferEvent
Type: HANDLE
Purpose: Event signaled by IddCx when new frame buffer is available.
Ownership: Created and managed by IddCx, not closed by driver.
m_hThread
Type: Microsoft::WRL::Wrappers::Thread
Purpose: RAII wrapper for processing thread handle.
Cleanup: Automatically closed when wrapper destroyed.
m_hTerminateEvent
Type: Microsoft::WRL::Wrappers::Event
Purpose: Event signaled to request thread termination.
Lifecycle: Created in constructor, signaled in destructor.
References
- Source Files:
~/workspace/source/Virtual Display Driver (HDR)/MttVDD/Driver.h (lines 71-89)
~/workspace/source/Virtual Display Driver (HDR)/MttVDD/Driver.cpp (lines 3082-3400+)
- Related Classes: Direct3DDevice
- IddCx SwapChain Reference: Microsoft Docs