chromium/third_party/win_virtual_display/driver/SwapChainProcessor.cpp

// Copyright (c) Microsoft Corporation

#include "SwapChainProcessor.h"

namespace display::test {

SwapChainProcessor::SwapChainProcessor(IDDCX_SWAPCHAIN hSwapChain,
                                       std::unique_ptr<Direct3DDevice> Device,
                                       HANDLE NewFrameEvent)
    : m_hSwapChain(hSwapChain),
      m_Device(std::move(Device)),
      m_hAvailableBufferEvent(NewFrameEvent) {
  m_hTerminateEvent.Attach(CreateEvent(nullptr, FALSE, FALSE, nullptr));

  // Immediately create and run the swap-chain processing thread, passing 'this'
  // as the thread parameter
  m_hThread.Attach(CreateThread(nullptr, 0, RunThread, this, 0, nullptr));
}

SwapChainProcessor::~SwapChainProcessor() {
  // Alert the swap-chain processing thread to terminate
  SetEvent(m_hTerminateEvent.Get());

  if (m_hThread.Get()) {
    // Wait for the thread to terminate
    WaitForSingleObject(m_hThread.Get(), INFINITE);
  }
}

DWORD CALLBACK SwapChainProcessor::RunThread(LPVOID Argument) {
  reinterpret_cast<SwapChainProcessor*>(Argument)->Run();
  return 0;
}

void SwapChainProcessor::Run() {
  // For improved performance, make use of the Multimedia Class Scheduler
  // Service, which will intelligently prioritize this thread for improved
  // throughput in high CPU-load scenarios.
  DWORD AvTask = 0;
  HANDLE AvTaskHandle = AvSetMmThreadCharacteristicsW(L"Distribution", &AvTask);

  RunCore();

  // Always delete the swap-chain object when swap-chain processing loop
  // terminates in order to kick the system to provide a new swap-chain if
  // necessary.
  WdfObjectDelete((WDFOBJECT)m_hSwapChain);
  m_hSwapChain = nullptr;

  AvRevertMmThreadCharacteristics(AvTaskHandle);
}

void SwapChainProcessor::RunCore() {
  // Get the DXGI device interface
  Microsoft::WRL::ComPtr<IDXGIDevice> DxgiDevice;
  HRESULT hr = m_Device->Device.As(&DxgiDevice);
  if (FAILED(hr)) {
    return;
  }

  IDARG_IN_SWAPCHAINSETDEVICE SetDevice = {};
  SetDevice.pDevice = DxgiDevice.Get();

  hr = IddCxSwapChainSetDevice(m_hSwapChain, &SetDevice);
  if (FAILED(hr)) {
    return;
  }

  // Acquire and release buffers in a loop
  for (;;) {
    Microsoft::WRL::ComPtr<IDXGIResource> AcquiredBuffer;

    // Ask for the next buffer from the producer
    IDARG_OUT_RELEASEANDACQUIREBUFFER Buffer = {};
    hr = IddCxSwapChainReleaseAndAcquireBuffer(m_hSwapChain, &Buffer);

    // AcquireBuffer immediately returns STATUS_PENDING if no buffer is yet
    // available
    if (hr == E_PENDING) {
      // We must wait for a new buffer
      HANDLE WaitHandles[] = {m_hAvailableBufferEvent, m_hTerminateEvent.Get()};
      DWORD WaitResult = WaitForMultipleObjects(ARRAYSIZE(WaitHandles),
                                                WaitHandles, FALSE, 16);
      if (WaitResult == WAIT_OBJECT_0 || WaitResult == WAIT_TIMEOUT) {
        // We have a new buffer, so try the AcquireBuffer again
        continue;
      } else if (WaitResult == WAIT_OBJECT_0 + 1) {
        // We need to terminate
        break;
      } else {
        // The wait was cancelled or something unexpected happened
        hr = HRESULT_FROM_WIN32(WaitResult);
        break;
      }
    } else if (SUCCEEDED(hr)) {
      // We have new frame to process, the surface has a reference on it that
      // the driver has to release
      AcquiredBuffer.Attach(Buffer.MetaData.pSurface);

      // ==============================
      // TODO: Process the frame here
      //
      // This is the most performance-critical section of code in an IddCx
      // driver. It's important that whatever is done with the acquired surface
      // be finished as quickly as possible. This operation could be:
      //  * a GPU copy to another buffer surface for later processing (such as a
      //  staging surface for mapping to CPU memory)
      //  * a GPU encode operation
      //  * a GPU VPBlt to another surface
      //  * a GPU custom compute shader encode operation
      // ==============================

      // We have finished processing this frame hence we release the reference
      // on it. If the driver forgets to release the reference to the surface,
      // it will be leaked which results in the surfaces being left around after
      // swapchain is destroyed. NOTE: Although we release reference to the
      // surface here; the driver still owns the Buffer.MetaData.pSurface
      // surface until IddCxSwapChainReleaseAndAcquireBuffer returns S_OK and
      // gives us a new frame, a driver may want to use the surface in future
      // to re-encode the desktop for better quality if there is no new frame
      // for a while
      AcquiredBuffer.Reset();

      // Indicate to OS that we have finished inital processing of the frame, it
      // is a hint that OS could start preparing another frame
      hr = IddCxSwapChainFinishedProcessingFrame(m_hSwapChain);
      if (FAILED(hr)) {
        break;
      }

      // ==============================
      // TODO: Report frame statistics once the asynchronous encode/send work is
      // completed
      //
      // Drivers should report information about sub-frame timings, like encode
      // time, send time, etc.
      // ==============================
      // IddCxSwapChainReportFrameStatistics(m_hSwapChain, ...);
    } else {
      // The swap-chain was likely abandoned (e.g. DXGI_ERROR_ACCESS_LOST), so
      // exit the processing loop
      break;
    }
  }
}
}  // namespace display::test