Migrating from ID2D1HwndRenderTarget/Direct2D 1.0 to ID2D1DeviceContext/Direct2D 1.1

thebluetropics 1,046 Reputation points
2022-09-11T10:44:49.29+00:00

Originally titled "Migrating from ID2D1HwndRenderTarget/Direct2D 1.0 to ID2D1DeviceContext/Direct2D 1.1"

Introduction

Hi. I am a developer from Indonesia with codename "thebluetropics".

I have using Direct2D 1.0 since some weeks ago. I started to code windows application in the same time as I learn C++. And today, I have experience on Direct2D 1.0 for building a basic window and drawing text on it using DirectWrite.

The Microsoft Learn lead me to build a Direct2D application using ID2D1HwndRenderTarget in the first tutorial. But yes, ID2D1HwndRenderTarget is not prefered way anymore, as stated in this here.

This is may be considered as repost, as I post many things about Direct2D before. But I decided to rewrite all of the post before to this post, with more details.

The simple application of Direct2D using ID2D1HwndRenderTarget

Here is the link to the single-file manner Direct2D 1.0 project I made specially for this post, for comparison purpose. The preset is: Visual Studio 2022 / Windows Desktop Wizard / Desktop Applications.

I've been comfortable of using Direct2D at this point. But the newer Direct2D version makes everything much more complex and hard to learn (at least, for me). Here is the workflow from most of my Direct2D applications:

  1. Creating a MainWindow class that holds all window specific states like Factories, Render target, Brush, Text format. Holds both device-independent and device-dependent resources.
  2. Initializing factories and device-independent resources inside the WM_CREATE message.
  3. Inside the WM_PAINT message:
    • Checking for device-dependent resources, initialize when the pointer is nullptr.
    • Drawing (BeginPaint(), Clear(), DrawTextW(), EndPaint(), etc).
    • If EndPaint() returns an D2DERR_RECREATE_TARGET, release all device-dependent resources and returns 1 to the WM_PAINT message, telling failed to draw the client area.
  4. Inside the WM_SIZE message:
  5. If render target is nullptr, return 1 to the WM_SIZE, telling it's not processing it.
  6. Resizing the render target (ID2D1HwndRenderTarget::Resize()).

That's all for the Direct2D 1.0, using ID2D1HwndRenderTarget.

Questions for the Direct2D 1.1

Direct2D 1.1 gives a new feature called device context, i.e ID2D1DeviceContext.

I have created a Direct2D using device context some days ago, but it's still fail to work, with a bunch of undesirable behavior:

  • Failed to handle WM_SIZE, the text is successfully centered, but the background fail to fill up the remaining of client area.
  • GPU is heating up. I fixed this problem at the moment, but still the implementation is still questionable.
  • Other problems, which I mostly forgot.

All of that happens from this tutorial. Here is the most of my questions for Direct2D 1.1:

  1. Which are device-dependent resources and which are not.
  2. Inside the WM_PAINT, what should I do?
  3. Should I check on every WM_PAINT for ID2D1DeviceContext? If exists, do not create, if doesn't exists create. But what else I should check? And what variables are temporary during the initialization of Device Dependent Resources?
  4. Inside the WM_SIZE, what should I do? I know I have to IDXGISwapChain1::ResizeBuffer().

The rest should be identical (or even same) with Direct2D 1.0, right? (e.g initializing factories inside WM_CREATE message)

How do I implement this example in Direct2D 1.1?

If possible, please give a working example of Direct2D 1.1 app using Device contexts.

I have zero idea why my application that uses Device context didnt work well.

Note: I have started C++ programming some week ago, and I started Direct2D programming the same time as I learn C++. I have Web Development background from Node.js, that's it, I have poor knowledge of C++ as Node.js is an Asynchronous.

Windows development | Windows API - Win32
Developer technologies | C++
0 comments No comments
{count} votes

Accepted answer
  1. Castorix31 90,686 Reputation points
    2022-09-11T15:26:22.59+00:00

    I rewrote it with ID2D1DeviceContext from a Win32 template I use for tests (there is just a warning not really important :
    DXGI WARNING: IDXGIFactory::CreateSwapChain/IDXGISwapChain::ResizeBuffers: The buffer height inferred from the output window is zero. Taking 8 as a reasonable default instead [ MISCELLANEOUS WARNING #2) :

    #include <windows.h>  
    #include <tchar.h>  
      
    //#include <commctrl.h>  
    //#pragma comment (lib, "comctl32")  
      
    #pragma comment(linker,"\"/manifestdependency:type='win32' \  
    name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \  
    processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")  
      
    #include <d3d11.h>  
    #pragma comment (lib, "D3D11")  
    #include <d2d1.h>  
    #pragma comment (lib, "D2d1")  
    #include <d2d1_1.h> // ID2D1Factory1  
      
    #include <dxgi1_2.h>  
      
    #include <dwrite.h>  
    #pragma comment(lib,"Dwrite")  
      
    template <class T> void SafeRelease(T** ppT)  
    {  
     if (*ppT)  
     {  
     (*ppT)->Release();  
     *ppT = NULL;  
     }  
    }  
      
    HINSTANCE hInst;  
    LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);  
    int nWidth = 600, nHeight = 400;  
      
    ID2D1Factory* m_pD2DFactory = NULL;  
    ID2D1Factory1* m_pD2DFactory1 = NULL;  
    IDWriteFactory* m_pDWriteFactory = NULL;  
      
    ID3D11Device* m_pD3D11Device = NULL;  
    ID3D11DeviceContext* m_pD3D11DeviceContext = NULL;  
    IDXGIDevice1* m_pDXGIDevice = NULL;  
      
    ID2D1Device* m_pD2DDevice = NULL;  
    ID2D1DeviceContext* m_pD2DDeviceContext = NULL;  
      
    ID2D1SolidColorBrush* m_pD2DMainBrush = NULL;  
    IDWriteTextFormat* m_pTextFormat = NULL;  
    IDWriteTextLayout* m_pTextLayout = NULL;  
      
    IDXGISwapChain1* m_pDXGISwapChain1 = NULL;  
    ID2D1Bitmap1* m_pD2DTargetBitmap = NULL;  
      
    HRESULT CreateD2D1Factory();  
    HRESULT CreateDWriteFactory();  
    HRESULT CreateD3D11Device();  
    HRESULT CreateDeviceResources();  
    HRESULT CreateSwapChain(HWND hWnd);  
    HRESULT ConfigureSwapChain(HWND hWnd);  
    void Clean();  
      
    WCHAR m_wsFont[] = L"Arial";  
    FLOAT m_nFontSize = 15.0F;  
    WCHAR m_wsText[] = L"Test";  
      
    HRESULT OnPaint(HWND hWnd);  
    void OnResize(HWND hWnd, UINT nWidth, UINT nHeight);  
      
    int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)  
    {  
     hInst = hInstance;  
     WNDCLASSEX wcex =  
     {  
     sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW, WndProc, 0, 0, hInst, LoadIcon(NULL, IDI_APPLICATION),  
     LoadCursor(NULL, IDC_ARROW), (HBRUSH)(COLOR_WINDOW + 1)/*(HBRUSH)GetStockObject(BLACK_BRUSH)*/, NULL, TEXT("WindowClass"), NULL,  
     };  
     if (!RegisterClassEx(&wcex))  
     return MessageBox(NULL, TEXT("Cannot register class !"), TEXT("Error"), MB_ICONERROR | MB_OK);  
     int nX = (GetSystemMetrics(SM_CXSCREEN) - nWidth) / 2, nY = (GetSystemMetrics(SM_CYSCREEN) - nHeight) / 2;  
     HWND hWnd = CreateWindowEx(0, wcex.lpszClassName, TEXT("Direct2D Test DrawTextLayout"), WS_OVERLAPPEDWINDOW, nX, nY, nWidth, nHeight, NULL, NULL, hInst, NULL);  
     if (!hWnd)  
     return MessageBox(NULL, TEXT("Cannot create window !"), TEXT("Error"), MB_ICONERROR | MB_OK);  
     ShowWindow(hWnd, SW_SHOWNORMAL);  
     UpdateWindow(hWnd);  
     MSG msg;  
     while (GetMessage(&msg, NULL, 0, 0))  
     {  
     TranslateMessage(&msg);  
     DispatchMessage(&msg);  
     }  
     return (int)msg.wParam;  
    }  
      
    LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)  
    {  
     switch (message)  
     {  
     case WM_CREATE:  
     {  
     HRESULT hr = CoInitialize(NULL);  
     if (SUCCEEDED(hr))  
     {  
     hr = CreateD2D1Factory();  
     if (SUCCEEDED(hr))  
     {  
     hr = CreateDWriteFactory();  
     if (SUCCEEDED(hr))  
     {  
     hr = CreateD3D11Device();  
     hr = CreateDeviceResources();  
     hr = CreateSwapChain(hWnd);  
     if (SUCCEEDED(hr))  
     hr = ConfigureSwapChain(hWnd);  
     }  
     }  
     }  
     return 0;  
     }  
     break;  
     case WM_PAINT:  
     {  
     return OnPaint(hWnd);  
     }  
     break;  
     case WM_SIZE:  
     {  
     UINT nWidth = LOWORD(lParam);  
     UINT nHeight = HIWORD(lParam);  
     OnResize(hWnd, nWidth, nHeight);  
     return 0;  
     }  
     break;  
     case WM_DESTROY:  
     {  
     Clean();  
     CoUninitialize();  
     PostQuitMessage(0);  
     return 0;  
     }  
     break;  
     default:  
     return DefWindowProc(hWnd, message, wParam, lParam);  
     }  
     return 0;  
    }  
      
    HRESULT CreateD2D1Factory()  
    {  
     HRESULT hr = S_OK;  
     D2D1_FACTORY_OPTIONS options;  
     ZeroMemory(&options, sizeof(D2D1_FACTORY_OPTIONS));  
     options.debugLevel = D2D1_DEBUG_LEVEL::D2D1_DEBUG_LEVEL_INFORMATION;  
     hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, options, &m_pD2DFactory);  
     if (SUCCEEDED(hr))  
     hr = m_pD2DFactory->QueryInterface((ID2D1Factory1**)&m_pD2DFactory1);  
     return hr;  
    }  
      
    HRESULT CreateDWriteFactory()  
    {  
     HRESULT hr = S_OK;  
     hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE::DWRITE_FACTORY_TYPE_SHARED, __uuidof(m_pDWriteFactory), reinterpret_cast<IUnknown**>(&m_pDWriteFactory));  
     return hr;  
    }  
      
    // https://learn.microsoft.com/en-us/windows/win32/direct2d/devices-and-device-contexts  
    HRESULT CreateD3D11Device()  
    {  
     HRESULT hr = S_OK;  
     UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;  
     creationFlags |= D3D11_CREATE_DEVICE_DEBUG;  
      
     // This array defines the set of DirectX hardware feature levels this app  supports.  
     // The ordering is important and you should  preserve it.  
     // Don't forget to declare your app's minimum required feature level in its  
     // description.  All apps are assumed to support 9.1 unless otherwise stated.  
     D3D_FEATURE_LEVEL featureLevels[] =  
     {  
     D3D_FEATURE_LEVEL_11_1,  
     D3D_FEATURE_LEVEL_11_0,  
     D3D_FEATURE_LEVEL_10_1,  
     D3D_FEATURE_LEVEL_10_0,  
     D3D_FEATURE_LEVEL_9_3,  
     D3D_FEATURE_LEVEL_9_2,  
     D3D_FEATURE_LEVEL_9_1  
     };  
      
     // Create the DX11 API device object, and get a corresponding context.  
      
     D3D_FEATURE_LEVEL featureLevel;  
     hr = D3D11CreateDevice(  
     NULL,                    // specify null to use the default adapter  
     D3D_DRIVER_TYPE_HARDWARE,  
     0,  
     creationFlags,              // optionally set debug and Direct2D compatibility flags  
     featureLevels,              // list of feature levels this app can support  
     ARRAYSIZE(featureLevels),   // number of possible feature levels  
     D3D11_SDK_VERSION,  
     &m_pD3D11Device,                    // returns the Direct3D device created  
     &featureLevel,            // returns feature level of device created  
     &m_pD3D11DeviceContext                    // returns the device immediate context  
     );  
     if (SUCCEEDED(hr))  
     {  
     // Obtain the underlying DXGI device of the Direct3D11 device.  
     hr = m_pD3D11Device->QueryInterface((IDXGIDevice1**)&m_pDXGIDevice);  
     if (SUCCEEDED(hr))  
     {  
     // Obtain the Direct2D device for 2-D rendering.  
     hr = m_pD2DFactory1->CreateDevice(m_pDXGIDevice, &m_pD2DDevice);  
     if (SUCCEEDED(hr))  
     {  
     // Get Direct2D device's corresponding device context object.  
     hr = m_pD2DDevice->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &m_pD2DDeviceContext);  
     if (SUCCEEDED(hr))  
     {  
     m_pD2DDeviceContext->SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);  
     m_pD2DDeviceContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_DEFAULT);  
     }  
     }  
     }  
     }  
     return hr;  
    }  
      
    HRESULT CreateFormatAndLayout()  
    {  
     HRESULT hr = S_OK;  
     if (m_pTextFormat == nullptr)  
     {  
     DWRITE_FONT_WEIGHT weight = DWRITE_FONT_WEIGHT_NORMAL;  
     DWRITE_FONT_STYLE style = DWRITE_FONT_STYLE_NORMAL;  
      
     hr = m_pDWriteFactory->CreateTextFormat(m_wsFont, NULL, weight, style, DWRITE_FONT_STRETCH_NORMAL, m_nFontSize, L"", &m_pTextFormat);  
     if (SUCCEEDED(hr))  
     {  
     hr = m_pTextFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER);  
     hr = m_pTextFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER);  
     hr = m_pDWriteFactory->CreateTextLayout(m_wsText, ARRAYSIZE(m_wsText), m_pTextFormat, 500, 100, &m_pTextLayout);  
     if (SUCCEEDED(hr))  
     {  
     IDWriteTypography* pTypography = nullptr;  
     hr = m_pDWriteFactory->CreateTypography(&pTypography);  
     if (SUCCEEDED(hr))  
     {  
     DWRITE_FONT_FEATURE ff =  
     {  
     DWRITE_FONT_FEATURE_TAG_STYLISTIC_SET_7,  
     1  
     };  
     hr = pTypography->AddFontFeature(ff);  
     if (SUCCEEDED(hr))  
     {  
     DWRITE_TEXT_RANGE tr = { 0, ARRAYSIZE(m_wsText) };  
     m_pTextLayout->SetTypography(pTypography, tr);  
     }  
     SafeRelease(&pTypography);  
     }  
     }  
     }  
     }  
     return hr;  
    }  
      
    HRESULT CreateDeviceResources()  
    {  
     HRESULT hr = S_OK;  
     if (m_pD2DMainBrush == NULL)  
     {  
     hr = m_pD2DDeviceContext->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black), &m_pD2DMainBrush);  
     }  
     hr = CreateFormatAndLayout();  
     return hr;  
    }  
      
    HRESULT CreateSwapChain(HWND hWnd)  
    {  
     HRESULT hr = S_OK;  
     DXGI_SWAP_CHAIN_DESC1 swapChainDesc = { 0 };  
     swapChainDesc.Width = 1;  
     swapChainDesc.Height = 1;  
     /*RECT rc;  
     GetClientRect(m_hStatic, &rc);  
     swapChainDesc.Width = rc.right - rc.left;  
     swapChainDesc.Height = rc.bottom - rc.top;*/  
     swapChainDesc.Format = DXGI_FORMAT::DXGI_FORMAT_B8G8R8A8_UNORM;  
     swapChainDesc.Stereo = false;  
     swapChainDesc.SampleDesc.Count = 1; // don't use multi-sampling  
     swapChainDesc.SampleDesc.Quality = 0;  
     swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;  
     swapChainDesc.BufferCount = 2; // use double buffering to enable flip  
     swapChainDesc.Scaling = (hWnd != NULL) ? DXGI_SCALING::DXGI_SCALING_NONE : DXGI_SCALING::DXGI_SCALING_STRETCH;  
     swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT::DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;  
     swapChainDesc.Flags = 0;  
      
     IDXGIAdapter* pDXGIAdapter = nullptr;  
     hr = m_pDXGIDevice->GetAdapter(&pDXGIAdapter);  
     if (SUCCEEDED(hr))  
     {  
     IDXGIFactory2* pDXGIFactory2 = nullptr;  
     hr = pDXGIAdapter->GetParent(IID_PPV_ARGS(&pDXGIFactory2));  
     if (SUCCEEDED(hr))  
     {  
     if (hWnd != NULL)  
     {  
     hr = pDXGIFactory2->CreateSwapChainForHwnd(m_pD3D11Device, hWnd, &swapChainDesc, nullptr, nullptr, &m_pDXGISwapChain1);  
     }  
     else  
     {  
     hr = pDXGIFactory2->CreateSwapChainForComposition(m_pD3D11Device, &swapChainDesc, nullptr, &m_pDXGISwapChain1);  
     }  
     if (SUCCEEDED(hr))  
     hr = m_pDXGIDevice->SetMaximumFrameLatency(1);  
     SafeRelease(&pDXGIFactory2);  
     }  
     SafeRelease(&pDXGIAdapter);  
     }  
     return hr;  
    }  
      
    HRESULT ConfigureSwapChain(HWND hWnd)  
    {  
     HRESULT hr = S_OK;  
      
     D2D1_BITMAP_PROPERTIES1 bitmapProperties = D2D1::BitmapProperties1(  
     D2D1_BITMAP_OPTIONS::D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS::D2D1_BITMAP_OPTIONS_CANNOT_DRAW,  
     D2D1::PixelFormat(DXGI_FORMAT::DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE::D2D1_ALPHA_MODE_IGNORE),  
     0,  
     0,  
     NULL  
     );  
     unsigned int nDPI = GetDpiForWindow(hWnd);  
     bitmapProperties.dpiX = nDPI;  
     bitmapProperties.dpiY = nDPI;  
      
     IDXGISurface* pDXGISurface;  
     if (m_pDXGISwapChain1)  
     {  
     hr = m_pDXGISwapChain1->GetBuffer(0, IID_PPV_ARGS(&pDXGISurface));  
     if (SUCCEEDED(hr))  
     {  
     hr = m_pD2DDeviceContext->CreateBitmapFromDxgiSurface(pDXGISurface, bitmapProperties, &m_pD2DTargetBitmap);  
     if (SUCCEEDED(hr))  
     {  
     m_pD2DDeviceContext->SetTarget(m_pD2DTargetBitmap);  
     }  
     SafeRelease(&pDXGISurface);  
     }  
     }  
     return hr;  
    }  
      
    HRESULT OnPaint(HWND hWnd)  
    {  
     HRESULT hr = S_OK;  
      
     PAINTSTRUCT ps;  
     HDC hDC = BeginPaint(hWnd, &ps);  
      
     if (m_pD2DDeviceContext && m_pDXGISwapChain1)  
     {  
     m_pD2DDeviceContext->BeginDraw();  
      
     D2D1_SIZE_F size = m_pD2DDeviceContext->GetSize();  
     m_pD2DDeviceContext->Clear(D2D1::ColorF(D2D1::ColorF::White));  
     //m_pD2DDeviceContext->Clear(D2D1::ColorF(D2D1::ColorF::LightCoral));  
      
     if (m_pTextLayout)  
     {  
     D2D1_POINT_2F pt = D2D1::Point2F(0, 0);  
     m_pTextLayout->SetMaxWidth(size.width);  
     m_pTextLayout->SetMaxHeight(size.height);  
     m_pD2DDeviceContext->DrawTextLayout(pt, m_pTextLayout, m_pD2DMainBrush, D2D1_DRAW_TEXT_OPTIONS_NO_SNAP | D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT);  
     }  
     hr = m_pD2DDeviceContext->EndDraw();  
     if (hr == D2DERR_RECREATE_TARGET)  
     {  
     m_pD2DDeviceContext->SetTarget(NULL);  
     SafeRelease(&m_pD2DDeviceContext);  
     hr = CreateD3D11Device();  
     hr = CreateDeviceResources();  
     hr = CreateSwapChain(hWnd);  
     if (SUCCEEDED(hr))  
     hr = ConfigureSwapChain(hWnd);  
     }  
     hr = m_pDXGISwapChain1->Present(1, 0);  
     }  
      
     EndPaint(hWnd, &ps);  
     InvalidateRect(hWnd, NULL, FALSE);  
     return hr;  
    }  
      
    void OnResize(HWND hWnd, UINT nWidth, UINT nHeight)  
    {  
     if (m_pDXGISwapChain1)  
     {  
     HRESULT hr = S_OK;  
      
     m_pD2DDeviceContext->SetTarget(nullptr);  
     SafeRelease(&m_pD2DTargetBitmap);  
     hr = m_pDXGISwapChain1->ResizeBuffers(  
     2, // Double-buffered swap chain.  
     nWidth,  
     nHeight,  
     DXGI_FORMAT_B8G8R8A8_UNORM,  
     0  
     );  
     if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)  
     {  
     CreateD3D11Device();  
     CreateSwapChain(hWnd);  
     }  
     else  
     {  
     //DX::ThrowIfFailed(hr);  
     }  
     ConfigureSwapChain(hWnd);  
     }  
    }  
      
    void CleanDeviceResources()  
    {  
     //SafeRelease(&m_pD2DBitmap);  
     SafeRelease(&m_pD2DMainBrush);  
     SafeRelease(&m_pTextFormat);  
     SafeRelease(&m_pTextLayout);  
    }  
      
    void Clean()  
    {  
     SafeRelease(&m_pD2DDevice);  
     SafeRelease(&m_pD2DDeviceContext);  
     SafeRelease(&m_pD2DTargetBitmap);  
      
     CleanDeviceResources();  
      
     SafeRelease(&m_pDXGISwapChain1);  
      
     SafeRelease(&m_pDXGIDevice);  
     SafeRelease(&m_pD3D11Device);  
     SafeRelease(&m_pD3D11DeviceContext);  
      
     SafeRelease(&m_pDWriteFactory);  
     SafeRelease(&m_pD2DFactory1);  
     SafeRelease(&m_pD2DFactory);  
    }  
      
      
    
    1 person found this answer helpful.

0 additional answers

Sort by: Most helpful

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.