title: DirectX Screen Capture - Desktop Duplication API - limited frame rate of AcquireNextFrame
body: <p>I'm trying to use Windows <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/hh404487(v=vs.85).aspx" rel="nofollow noreferrer">Desktop Duplication API</a> to capture the screen and save the raw output to a video. I'm using <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/hh404615(v=vs.85).aspx" rel="nofollow noreferrer">AcquireNextFrame</a> with a very high timeout value (999ms). This way I should get every new frame from windows as soon as it at has one, which naturally should be at 60fps anyway. I end up getting sequences where everything looks good (frame 6-11), and then sequences where things look bad (frame 12-14). If I check <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/hh404512(v=vs.85).aspx" rel="nofollow noreferrer">AccumulatedFrames</a></p>
  
  <pre><code>lFrameInfo.AccumulatedFrames
  </code></pre>
  
  <p>the value is often 2 or higher. From my understanding, this means windows is saying "hey hold up, I don't have a frame for you yet", because calls to AcquireNextFrame take so long. But once windows does finally give me a frame, it is saying "hey you were actually too slow and ended up missing a frame". If i could somehow get these frames I think I would be getting 60hz.</p>
  
  <p>This can be further clarified with logging:</p>
  
  <pre><code>I0608 10:40:16.964375  4196 window_capturer_dd.cc:438] 206 - Frame 6 start acquire
  I0608 10:40:16.973867  4196 window_capturer_dd.cc:451] 216 - Frame 6 acquired
  I0608 10:40:16.981364  4196 window_capturer_dd.cc:438] 223 - Frame 7 start acquire
  I0608 10:40:16.990864  4196 window_capturer_dd.cc:451] 233 - Frame 7 acquired
  I0608 10:40:16.998364  4196 window_capturer_dd.cc:438] 240 - Frame 8 start acquire
  I0608 10:40:17.007876  4196 window_capturer_dd.cc:451] 250 - Frame 8 acquired
  I0608 10:40:17.015393  4196 window_capturer_dd.cc:438] 257 - Frame 9 start acquire
  I0608 10:40:17.023905  4196 window_capturer_dd.cc:451] 266 - Frame 9 acquired
  I0608 10:40:17.032411  4196 window_capturer_dd.cc:438] 274 - Frame 10 start acquire
  I0608 10:40:17.039912  4196 window_capturer_dd.cc:451] 282 - Frame 10 acquired
  I0608 10:40:17.048925  4196 window_capturer_dd.cc:438] 291 - Frame 11 start acquire
  I0608 10:40:17.058428  4196 window_capturer_dd.cc:451] 300 - Frame 11 acquired
  I0608 10:40:17.065943  4196 window_capturer_dd.cc:438] 308 - Frame 12 start acquire
  I0608 10:40:17.096945  4196 window_capturer_dd.cc:451] 336 - Frame 12 acquired
  I0608 10:40:17.098947  4196 window_capturer_dd.cc:464] 1 FRAMES MISSED on frame: 12
  I0608 10:40:17.101444  4196 window_capturer_dd.cc:438] 343 - Frame 13 start acquire
  I0608 10:40:17.128958  4196 window_capturer_dd.cc:451] 368 - Frame 13 acquired
  I0608 10:40:17.130957  4196 window_capturer_dd.cc:464] 1 FRAMES MISSED on frame: 13
  I0608 10:40:17.135459  4196 window_capturer_dd.cc:438] 377 - Frame 14 start acquire
  I0608 10:40:17.160959  4196 window_capturer_dd.cc:451] 399 - Frame 14 acquired
  I0608 10:40:17.162958  4196 window_capturer_dd.cc:464] 1 FRAMES MISSED on frame: 14
  </code></pre>
  
  <p>Frame 6-11 look good, the acquires are roughly 17ms apart. Frame 12 should be acquired at (300+17=317ms). Frame 12 starts waiting at 308, but doesn't get anything until 336ms. Windows didn't have anything for me until the frame after (300+17+17~=336ms). Okay sure maybe windows just missed a frame, but when I finally get it, I can check AccumulatedFrames and its value was 2 (meaning I missed a frame because I waited too long before calling AcquireNextFrame). In my understanding, it only makes sense for AccumulatedFrames to be larger than 1 if AcquireNextFrame returns immediately. </p>
  
  <p>Furthermore, I can use PresentMon while my capture software is running. The logs show MsBetweenDisplayChange for every frame, which is fairly steady at 16.666ms (with a couple outliers, but much less than my capture software is seeing).</p>
  
  <p>These people (<a href="https://stackoverflow.com/a/22219146">1</a>, <a href="https://steamcommunity.com/app/488040/discussions/0/343786195662386910/#c343786745996148440" rel="nofollow noreferrer">2</a>) seem to have been able to get 60fps, so I'm wondering what I am doing incorrectly. </p>
  
  <p>My code is based on <a href="https://www.codeproject.com/Tips/1116253/Desktop-screen-capture-on-Windows-via-Windows-Desk" rel="nofollow noreferrer">this</a>:</p>
  
  <pre><code>int main() {
      int FPS = 60;
      int video_length_sec = 5;
  
      int total_frames = FPS * video_length_sec;
      for (int i = 0; i &lt; total_frames; i++) {
          if(!CaptureSingleFrame()){
              i--;
          }
      }
  }
  
  ComPtr&lt;ID3D11Device&gt; lDevice;
  ComPtr&lt;ID3D11DeviceContext&gt; lImmediateContext;
  ComPtr&lt;IDXGIOutputDuplication&gt; lDeskDupl;
  ComPtr&lt;ID3D11Texture2D&gt; lAcquiredDesktopImage;
  ComPtr&lt;ID3D11Texture2D&gt; lGDIImage;
  ComPtr&lt;ID3D11Texture2D&gt; lDestImage;
  DXGI_OUTPUT_DESC lOutputDesc;
  DXGI_OUTDUPL_DESC lOutputDuplDesc;
  D3D11_TEXTURE2D_DESC desc;
  
  // Driver types supported
  D3D_DRIVER_TYPE gDriverTypes[] = {
      D3D_DRIVER_TYPE_HARDWARE
  };
  UINT gNumDriverTypes = ARRAYSIZE(gDriverTypes);
  
  // Feature levels supported
  D3D_FEATURE_LEVEL gFeatureLevels[] = {
      D3D_FEATURE_LEVEL_11_0,
      D3D_FEATURE_LEVEL_10_1,
      D3D_FEATURE_LEVEL_10_0,
      D3D_FEATURE_LEVEL_9_1
  };
  UINT gNumFeatureLevels = ARRAYSIZE(gFeatureLevels);
  
  
  bool Init() {
      int lresult(-1);
  
      D3D_FEATURE_LEVEL lFeatureLevel;
  
      HRESULT hr(E_FAIL);
  
      // Create device
      for (UINT DriverTypeIndex = 0; DriverTypeIndex &lt; gNumDriverTypes; ++DriverTypeIndex)
      {
          hr = D3D11CreateDevice(
              nullptr,
              gDriverTypes[DriverTypeIndex],
              nullptr,
              0,
              gFeatureLevels,
              gNumFeatureLevels,
              D3D11_SDK_VERSION,
              &amp;lDevice,
              &amp;lFeatureLevel,
              &amp;lImmediateContext);
  
          if (SUCCEEDED(hr))
          {
              // Device creation success, no need to loop anymore
              break;
          }
  
          lDevice.Reset();
  
          lImmediateContext.Reset();
      }
  
      if (FAILED(hr))
          return false;
  
      if (lDevice == nullptr)
          return false;
  
      // Get DXGI device
      ComPtr&lt;IDXGIDevice&gt; lDxgiDevice;
      hr = lDevice.As(&amp;lDxgiDevice);
  
      if (FAILED(hr))
          return false;
  
      // Get DXGI adapter
      ComPtr&lt;IDXGIAdapter&gt; lDxgiAdapter;
      hr = lDxgiDevice-&gt;GetParent(
          __uuidof(IDXGIAdapter), &amp;lDxgiAdapter);
  
      if (FAILED(hr))
          return false;
  
      lDxgiDevice.Reset();
  
      UINT Output = 0;
  
      // Get output
      ComPtr&lt;IDXGIOutput&gt; lDxgiOutput;
      hr = lDxgiAdapter-&gt;EnumOutputs(
          Output,
          &amp;lDxgiOutput);
  
      if (FAILED(hr))
          return false;
  
      lDxgiAdapter.Reset();
  
      hr = lDxgiOutput-&gt;GetDesc(
          &amp;lOutputDesc);
  
      if (FAILED(hr))
          return false;
  
      // QI for Output 1
      ComPtr&lt;IDXGIOutput1&gt; lDxgiOutput1;
      hr = lDxgiOutput.As(&amp;lDxgiOutput1);
  
      if (FAILED(hr))
          return false;
  
      lDxgiOutput.Reset();
  
      // Create desktop duplication
      hr = lDxgiOutput1-&gt;DuplicateOutput(
          lDevice.Get(), //TODO what im i doing here
          &amp;lDeskDupl);
  
      if (FAILED(hr))
          return false;
  
      lDxgiOutput1.Reset();
  
      // Create GUI drawing texture
      lDeskDupl-&gt;GetDesc(&amp;lOutputDuplDesc);
      desc.Width = lOutputDuplDesc.ModeDesc.Width;
      desc.Height = lOutputDuplDesc.ModeDesc.Height;
      desc.Format = lOutputDuplDesc.ModeDesc.Format;
      desc.ArraySize = 1;
      desc.BindFlags = D3D11_BIND_FLAG::D3D11_BIND_RENDER_TARGET;
      desc.MiscFlags = D3D11_RESOURCE_MISC_GDI_COMPATIBLE;
      desc.SampleDesc.Count = 1;
      desc.SampleDesc.Quality = 0;
      desc.MipLevels = 1;
      desc.CPUAccessFlags = 0;
      desc.Usage = D3D11_USAGE_DEFAULT;
  
  
      hr = lDevice-&gt;CreateTexture2D(&amp;desc, NULL, &amp;lGDIImage);
  
      if (FAILED(hr))
          return false;
  
      if (lGDIImage == nullptr)
          return false;
  
      // Create CPU access texture
      desc.Width = lOutputDuplDesc.ModeDesc.Width;
      desc.Height = lOutputDuplDesc.ModeDesc.Height;
      desc.Format = lOutputDuplDesc.ModeDesc.Format;
      std::cout &lt;&lt; desc.Width &lt;&lt; "x" &lt;&lt; desc.Height &lt;&lt; "\n\n\n";
      desc.ArraySize = 1;
      desc.BindFlags = 0;
      desc.MiscFlags = 0;
      desc.SampleDesc.Count = 1;
      desc.SampleDesc.Quality = 0;
      desc.MipLevels = 1;
      desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE;
      desc.Usage = D3D11_USAGE_STAGING;
  
      return true;
  }
  
  void WriteFrameToCaptureFile(ID3D11Texture2D* texture) {
  
      D3D11_MAPPED_SUBRESOURCE* pRes = new D3D11_MAPPED_SUBRESOURCE;
      UINT subresource = D3D11CalcSubresource(0, 0, 0);
  
      lImmediateContext-&gt;Map(texture, subresource, D3D11_MAP_READ_WRITE, 0, pRes);
  
      void* d = pRes-&gt;pData;
      char* data = reinterpret_cast&lt;char*&gt;(d);
  
      // writes data to file
      WriteFrameToCaptureFile(data, 0);
  }
  
  bool CaptureSingleFrame()
  {
      HRESULT hr(E_FAIL);
      ComPtr&lt;IDXGIResource&gt; lDesktopResource = nullptr;
      DXGI_OUTDUPL_FRAME_INFO lFrameInfo;
      ID3D11Texture2D* currTexture;
  
      hr = lDeskDupl-&gt;AcquireNextFrame(
          999,
          &amp;lFrameInfo,
          &amp;lDesktopResource);
  
      if (FAILED(hr)) {
          LOG(INFO) &lt;&lt; "Failed to acquire new frame";
          return false;
      }
  
      if (lFrameInfo.LastPresentTime.HighPart == 0) {
          // not interested in just mouse updates, which can happen much faster than 60fps if you really shake the mouse
          hr = lDeskDupl-&gt;ReleaseFrame();
          return false;
      }
  
      int accum_frames = lFrameInfo.AccumulatedFrames;
      if (accum_frames &gt; 1 &amp;&amp; current_frame != 1) {
          // TOO MANY OF THESE is the problem
          // especially after having to wait &gt;17ms in AcquireNextFrame()
      }
  
      // QI for ID3D11Texture2D
      hr = lDesktopResource.As(&amp;lAcquiredDesktopImage);
  
      // Copy image into a newly created CPU access texture
      hr = lDevice-&gt;CreateTexture2D(&amp;desc, NULL, &amp;currTexture);
      if (FAILED(hr))
          return false;
      if (currTexture == nullptr)
          return false;
  
      lImmediateContext-&gt;CopyResource(currTexture, lAcquiredDesktopImage.Get());
  
  
      writer_thread-&gt;Schedule(
          FROM_HERE, [this, currTexture]() {
          WriteFrameToCaptureFile(currTexture);
      });
      pending_write_counts_++;
  
      hr = lDeskDupl-&gt;ReleaseFrame();
  
      return true;
  }
  </code></pre>
  
  <p>**EDIT - According to <a href="https://docs.google.com/spreadsheets/d/1aHDWWHrPTJlDJGLzytkWtaO71f9ZgB537-wNNLAMO24/edit?usp=sharing" rel="nofollow noreferrer">my measurements</a>, you must call AcquireNextFrame() before the frame will actually appear by about ~10ms, or windows will fail to acquire it and get you the next one. Every time my recording program takes more than 7 ms to wrap around (after acquiring frame i until calling AcquireNextFrame() on i+1), frame i+1 is missed. </p>
  
  <p>***EDIT - <a href="https://drive.google.com/file/d/0B2UynDbrNloqM1E4cU9oaWhXdlk/view?usp=sharing" rel="nofollow noreferrer">Heres</a> a screenshot of GPU View showing what I'm talking about. The first 6 frames process in no time, then the 7th frame takes 119ms. The long rectangle beside "capture_to_argb.exe" corresponds to me being stuck inside AcquireNextFrame(). If you look up to the hardware queue, you can see it cleanly rendering at 60fps, even while I'm stuck in AcquireNextFrame(). At least this is my interpretation (I have no idea what I'm doing).</p>