Skip to main content

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:
  1. Stores swap chain handle, device, and event handle
  2. Creates termination event for clean shutdown
  3. 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:
  1. Signals termination event to stop processing thread
  2. Waits for thread to complete (WaitForSingleObject)
  3. Handles wait results (WAIT_OBJECT_0, WAIT_ABANDONED, WAIT_TIMEOUT)
  4. 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
    }
}

Performance Considerations

Critical Performance Section

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

  1. Use GPU Operations: Avoid CPU access to frame buffers (requires staging surfaces and map/unmap)
  2. Asynchronous Processing: Queue GPU commands asynchronously
  3. Resource Pooling: Reuse D3D resources to avoid allocation overhead
  4. Minimize State Changes: Batch similar operations together
  5. 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

Build docs developers (and LLMs) love