5

In a traditional Windows program that uses GDI for graphics, you would have to worry about only drawing the area of the window that needs to be redrawn; this is the "update rect" and is accessed either by PAINTSTRUCT.rcPaint or by a call to GetUpdateRect(). (This is also available as an HRGN through other means.)

Do I need to do the same thing with Direct2D? All the examples on MSDN just draw the entire client area indiscriminately and searching online hasn't turned up anything else.

Or in other words, would anything bad happen to parts outside the update rect if I only draw within the update rect, for instance either manually or with PushAxisAlignedClip() or PushLayer()?

Furthermore, the documentation for ID2D1HwndRenderTarget::Resize() says

After this method is called, the contents of the render target's back-buffer are not defined, even if the D2D1_PRESENT_OPTIONS_RETAIN_CONTENTS option was specified when the render target was created.

Does this mean that whatever update region would be caused by resizing (such as shown by this picture from this page) is invalid and I should redraw the whole window (for instance, by calling InvalidateRect(NULL)) on a resize?

Thanks.

andlabs
  • 11,290
  • 1
  • 31
  • 52

2 Answers2

3

Yes. Use PushAxisAlignedClip with D2D1_ANTIALIAS_MODE_ALIASED.

Call ID2D1HwndRenderTarget::Resize when the window is resized. Do pay attention to the HRESULT it returns. It can return D2DERR_RECREATE_TARGET, but you may not know that it can also return D2DERR_DISPLAY_STATE_INVALID (which can also be returned by EndDraw, btw). And yes, call InvalidateRect(NULL) after that.

I also recommend using D2D1_PRESENT_OPTIONS_RETAIN_CONTENTS because otherwise you'll run into nasty bugs on some stupid driver/hardware configurations and in other events. Don't ask me why -- it just doesn't behave well without this. You'll get bug reports that your whole rendering area just becomes filled with black. It took me months to figure out that all I had to do was to use that flag. I was never able to repro the problem locally.

Rick Brewster
  • 3,374
  • 1
  • 17
  • 21
  • (Sorry for the delay in responding.) All right, thanks. And I assume I must also convert the `RECT` to use device-independent pixels, correct? And should I be treating `D2DERR_DISPLAY_STATE_INVALID` as equivalent to `D2DERR_RECREATE_TARGET`, or should I respond differently to that HRESULT? Thanks for the other tips as well; I've implemented the `RECREATE_TARGET` one now. – andlabs Dec 05 '15 at 01:41
  • 1
    In my code I'm handling `D2DERR_DISPLAY_STATE_INVALID` in the same way as `D2DERR_RECREATE_TARGET` and haven't had anybody report any issues. (this is paint.net I'm talking about, which has millions of users) I just looked at my code and apparently `ERROR_INVALID_HANDLE` will pop out of `EndDraw` as well, and I handle it in the same way too. If you want to look closer, install the latest Paint.NET and use ILSpy or Reflector on PaintDotNet.Framework.dll and search for a class called `Direct2DControlHandler`. My `WM_PAINT` handler is implemented in the `RelayGdiPaintImpl` method there. – Rick Brewster Dec 05 '15 at 20:49
  • All right, thanks. That still leaves the question about whether to adjust the clipping rect to be in DIPs instead of logical units; should I? I'm not sure if you do (going by ILSpy)... – andlabs Dec 18 '15 at 18:51
  • 1
    Paint.NET doesn't configure Direct2D for any sort of DIP-ness: it's always told 96 DPI, and adjustments are made at a higher level in the UI controls themselves. So anything you see in that disassembly is working with pixels. So if you told Direct2D anything but 96 DPI then you will have to do conversions yourself. – Rick Brewster Dec 22 '15 at 19:42
1

No. You have to do near the same thing as normal with gdi. Instead of using a HBITMAP backbuffer, you have to use d2d bitmap. In wm_size, you resize and redraw your d2d bitmap, and in wm_paint instead of bitblt a hbitmap you have to use the render drawbitmap method. and render just the part from the paintstruct rect member (hope this help you) :

Global or class members :

ID2D1Factory* g_pD2DFactory = NULL; 
ID2D1HwndRenderTarget* g_pRenderTarget = NULL;
ID2D1SolidColorBrush* g_pBlackBrush = NULL;
ID2D1SolidColorBrush* g_pWhiteBrush = NULL; 
ID2D1BitmapRenderTarget* g_bitmapRenderTarget = NULL; //for d2d bitmap

The global or class static wndproc:

case WM_CREATE: {
     if (FAILED(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &g_pD2DFactory))) {
        throw;
    }

    LPCREATESTRUCT lpcs = (LPCREATESTRUCT)lParam;

    HRESULT hr = g_pD2DFactory->CreateHwndRenderTarget(
         D2D1::RenderTargetProperties(), 
         D2D1::HwndRenderTargetProperties(hWnd, D2D1::SizeU(lpcs->cx, lpcs->cy)), 
         &g_pRenderTarget
    );

    if (FAILED(hr)) {
        throw;
    }

    if (FAILED(g_pRenderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black), &g_pBlackBrush))) {
        throw;
    }

    if (FAILED(g_pRenderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White), &g_pWhiteBrush))) {
        throw;
    }
    break;
}
case WM_SIZE: {
    if(FAILED(g_pRenderTarget->Resize(D2D1::SizeU(LOWORD(lParam), HIWORD(lParam))))) {
        throw;
    }

    D2D_SAFE_RELEASE(g_bitmapRenderTarget)

    g_pRenderTarget->CreateCompatibleRenderTarget(&g_bitmapRenderTarget);

    g_bitmapRenderTarget->BeginDraw();
    g_bitmapRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::AliceBlue));
    g_bitmapRenderTarget->DrawEllipse(D2D1::Ellipse(D2D1::Point2F(100,100), 50,50), g_pBlackBrush);
    g_bitmapRenderTarget->EndDraw();
    break;
}
case WM_PAINT: {
    HDC hDc;
    PAINTSTRUCT ps;
    LPCRECT lpRect;
    ID2D1Bitmap* bitmap;
    D2D1_RECT_F d2d1Rect;
    hDc = BeginPaint(hWnd, &ps);
    lpRect = &ps.rcPaint;
    d2d1Rect = D2D1::RectF(lpRect->left, lpRect->top, lpRect->right, lpRect->bottom);
    g_bitmapRenderTarget->GetBitmap(&bitmap);

    g_pRenderTarget->BeginDraw();
    g_pRenderTarget->DrawBitmap(
        bitmap, d2d1Rect, 1.0f, D2D1_BITMAP_INTERPOLATION_MODE::D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, 
        d2d1Rect
    );
    g_pRenderTarget->EndDraw();

    EndPaint(hWnd, &ps);
    return 0;
 }
zep
  • 11
  • 2