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, ¶meters);
// ...
}
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, ¶meters);
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)?