0

I'm new to graphics rendering, and I'm trying to write a win32 drawing app using D2D and D3D11. I use two overlapped offscreen D2D bitmaps to preserve the content of the canvas, the top-level bitmap is transparent.

Whenever a mouse message is received, a line from the last point to the current point will be rendered to the top-level bitmap. Then I will draw both bitmaps by Z-Order to the back buffer of the swap chain then call Present(0, 0).

As you may notice, the present call is event-driven in my design. If the mouse message is received every 1 ms, then in 1 second I will get 1000 polylines rendered on the top-level bitmap(which is good), then 1000 times to composite the two bitmaps, and 1000 times to call Present(which is really bad since I only need to present in, let's say, 60 fps). The redundant composition calls and present calls take the most resources of GPU, and finally Present(0, 0) blocks the UI thread, and the frequency of mouse messages reported is dramatically reduced.

int OnMouseMove(int x, int y)
{
    // ...

    // update top-level bitmap
    DrawLineTo(topLevelBitmap, x, y);

    // get back buffer
    CComPtr<IDXGISurface> dxgiBackBuffer;
    HRESULT hr = _dxgiSwapChain->GetBuffer(0, IID_PPV_ARGS(&dxgiBackBuffer));

    // Draw two bitmaps to the back buffer
    ClearBackBuffer(dxgiBackBuffer);
    DrawBimap(dxgiBackBuffer, backgroundBitmap);
    DrawBimap(dxgiBackBuffer, topLevelBitmap);

    // Present
    DXGI_PRESENT_PARAMETERS parameters = { 0 };
    _dxgiSwapChain->Present1(0, 0, &parameters);

    // ...
}

I tried to find a callback that could be used to trigger present calls, like CVDisplayLink/CADisplayLink on macOS/iOS, or a higher priority timer that could generate reliable callbacks, but failed. (WM_TIMER has a rather low priority so I didn't even try)

Another thought is to create a new thread and call present in a while loop and sleep for 16ms after each present is called. However, I'm not sure if this is a standard way, and I also worry about thread safety.

// in UI thread
int OnMouseMove(int x, int y)
{
    // update top-level bitmap
    DrawLineTo(topLevelBitmap, x, y);
}


// in new thread
while(1)
{
    // get back buffer
    CComPtr<IDXGISurface> dxgiBackBuffer;
    HRESULT hr = _dxgiSwapChain->GetBuffer(0, IID_PPV_ARGS(&dxgiBackBuffer));

    // Draw two bitmaps to the back buffer
    ClearBackBuffer(dxgiBackBuffer);
    DrawBimap(dxgiBackBuffer, backgroundBitmap);
    DrawBimap(dxgiBackBuffer, topLevelBitmap);

    // Present
    DXGI_PRESENT_PARAMETERS parameters = { 0 };
    _dxgiSwapChain->Present1(0, 0, &parameters);

    sleep(16);
}

So my question is what is the proper way to separate the off-screen rendering(draw line) and the on-screen rendering(draw bitmap & present)?

CookCookie
  • 35
  • 7
  • My suggestion would be to use DirectComposition https://learn.microsoft.com/en-us/windows/win32/directcomp/directcomposition-portal or better Windows.UI.Composition (which is kinda DComp 3.0 and works fine for desktop apps). This way you just "compose" your scene (supports multithreading, D2D, DWrite, etc.), and you don't event have to worry about swapchains, vsync, etc. Here is the seminal example https://gist.github.com/kennykerr/62923cdacaba28fedc4f3dab6e0c12ec#file-desktopcomposition-cpp – Simon Mourier Dec 31 '20 at 08:47

1 Answers1

2

What you should be doing here is to have a non-blocking event loop using PeekMessage, and then do your rendering on the same thread, after you've gotten all the window events. As for keeping your FPS locked to the monitor's refresh rate, Present's first argument is the sync interval, which should be set to 1, and it will block until the monitor is ready to show the next frame.

For example:

while (isOpen)
{
  // Message loop
  MSG message;
  while (PeekMessage(&message, m_WindowHandle, NULL, NULL, PM_REMOVE))
  {
      TranslateMessage(&message);
      DispatchMessage(&message);
  }

  // Render
  
  DXGI_PRESENT_PARAMETERS parameters = { 0 };
  _dxgiSwapChain->Present1(1, 0, &parameters);
}

Inside your window proc, you should just store the current position of the mouse so that you can then update it once in your render loop.

SparkyPotato
  • 448
  • 3
  • 8
  • There are a number of 'basic device & swapchain loops' implemented on [GitHub](https://github.com/walbourn/directx-vs-templates) you should look at. The answer here is correct, but there's some nuance when it comes to computing "elapsed time". See [this post](https://walbourn.github.io/understanding-game-time-revisited/). – Chuck Walbourn Jan 03 '21 at 01:28