1

Edits. I'm switching for HwndRenderTargets to DeviceContexts and SwapChains for Hwnd in a Direct2d + Win32 application and the render target is not rendering completely on the swap chain.

    class direct2dWindow : public direct2dContext
    {
    private:
        HWND hwnd;

        // for main window
        ID2D1DeviceContext* renderTarget;
        IDXGISwapChain1* swapChain;
        IDXGISurface* surface;
        ID3D11Texture2D* texture;
        ID2D1Bitmap1* bitmap;

        // for children
        bool childWindow;
        direct2dBitmapCore* childBitmap;

        void applySwapChain();

    public:

        direct2dWindow(HWND hwnd, adapterSet* _adapter, bool _childWindow);
        virtual ~direct2dWindow();

        void resize(UINT x, UINT y);
        void moveWindow(UINT x, UINT y, UINT h, UINT w);

        virtual ID2D1DeviceContext* getRenderTarget()
        {
            return childBitmap ? childBitmap->getRenderTarget() : renderTarget;
        }

        ID2D1Bitmap1* getBitmap() { return childBitmap ? childBitmap->getBitmap() : bitmap; }

        virtual void beginDraw(bool& _adapter_blown_away);
        virtual void endDraw(bool& _adapter_blown_away);

        bool isChild() { return childWindow; }
        HWND getWindow() { return hwnd; }
    };


    void direct2dWindow::resize(UINT x, UINT y)
    {
        HRESULT hr;

        if (childWindow)
        {
            if (childBitmap)
                delete childBitmap;
            D2D1_SIZE_F size;
            size.width = x;
            size.height = y;
            childBitmap = new direct2dBitmapCore(size, factory);
        }
        else 
        {

            if (renderTarget)
            {
                renderTarget->SetTarget(nullptr);
            }
            if (bitmap)
            {
                bitmap->Release();
                bitmap = nullptr;
            }
            if (surface)
            {
                surface->Release();
                surface = nullptr;
            }

            hr = swapChain->ResizeBuffers(2, x, y, DXGI_FORMAT_B8G8R8A8_UNORM, 0);
            throwOnFail(hr, "Couldn't resize swapchain");

            applySwapChain();
        }
    }


    direct2dWindow::direct2dWindow(HWND _hwnd, adapterSet* _adapterSet, bool _childWindow) : direct2dContext(_adapterSet)
    {

        HRESULT hr;

        hwnd = _hwnd;
        bitmap = nullptr;
        surface = nullptr;
        swapChain = nullptr;
        renderTarget = nullptr;
        childBitmap = nullptr;
        childWindow = _childWindow;

        D2D1_DEVICE_CONTEXT_OPTIONS options;

        options = D2D1_DEVICE_CONTEXT_OPTIONS::D2D1_DEVICE_CONTEXT_OPTIONS_NONE;

        hr = _adapterSet->getD2DDevice()->CreateDeviceContext(options, &renderTarget);
        throwOnFail(hr, "Could not create device context");

        RECT rect;
        ::GetClientRect(hwnd, &rect);
        int width = abs(rect.right - rect.left);
        int height = abs(rect.bottom - rect.top);

        if (!childWindow) {

            DXGI_SWAP_CHAIN_DESC1 swapChainDesc = { 0 };
            swapChainDesc.Width = width;
            swapChainDesc.Height = height;
            swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // this is the most common swapchain format
            swapChainDesc.Stereo = false;
            swapChainDesc.SampleDesc.Count = 1;
            swapChainDesc.SampleDesc.Quality = 0;
            swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_BACK_BUFFER;
            swapChainDesc.BufferCount = 2;                     // use double buffering to enable flip
            swapChainDesc.Scaling = DXGI_SCALING_NONE;
            swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
            swapChainDesc.Flags = 0;

            hr = _adapterSet->getDxFactory()->CreateSwapChainForHwnd(_adapterSet->getD3DDevice(), hwnd, &swapChainDesc, nullptr, nullptr, &swapChain);
            throwOnFail(hr, "Could not create swap chain");

            DXGI_RGBA color;
            color.a = 1.0;
            color.r = 0.0;
            color.g = .80;
            color.b = 1.0;

            swapChain->SetBackgroundColor(&color);

            applySwapChain();
        }
        else 
        {
            D2D1_SIZE_F size;
            size.width = width;
            size.height = height;
            childBitmap = new direct2dBitmapCore(size, _adapterSet);
        }
    }

    void direct2dWindow::applySwapChain()
    {

        HRESULT hr;

        float dpix, dpiy;
        int dpiWindow;

        dpiWindow = ::GetDpiForWindow(hwnd);
        renderTarget->GetDpi(&dpix, &dpiy);

        // Now we set up the Direct2D render target bitmap linked to the swapchain. 
        // Whenever we render to this bitmap, it is directly rendered to the 
        // swap chain associated with the window.
        D2D1_BITMAP_PROPERTIES1 bitmapProperties =
            D2D1::BitmapProperties1(
                D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
                D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE),
                dpix,
                dpiy
            );

        // Direct2D needs the dxgi version of the backbuffer surface pointer.
        hr = swapChain->GetBuffer(0, IID_IDXGISurface, (void**)&surface);
        throwOnFail(hr, "Could not get swap chain surface");

        DXGI_SURFACE_DESC sdec;
        surface->GetDesc(&sdec);

        // Get a D2D surface from the DXGI back buffer to use as the D2D render target.
        hr = renderTarget->CreateBitmapFromDxgiSurface(
            surface,
            &bitmapProperties,
            &bitmap
        );

        auto pxsx = bitmap->GetPixelSize();
        auto sizex = bitmap->GetSize();

        throwOnFail(hr, "Could create bitmap from surface");

        // Now we can set the Direct2D render target.
        renderTarget->SetTarget(bitmap);
        auto dipssz = renderTarget->GetSize();

    }

I've read this over and over again...and I've played with the code above, the other bits about handling the sizing. HR is always S_OK. Everything works, it seems like DXGI or the Context doesn't understand the D2D context on top of the swap chain doesn't cover the whole client area. Spy++ shows the window positioned correctly ok.

Image showing output

In the image, the blue is the swap chain background color, so I know that it is actually presenting the entire screen (which, in an earlier edit, I was not sure of). The green consists of two rectangles drawn on the render target. As you can see, they are clipped, even though the target should be full size itself. However, I noticed that the size of the render target, given by the snippet, is the same size in dips as it should be in pixels, which would explain why the output is clipped, I think.

https://learn.microsoft.com/en-us/windows/win32/api/dxgi1_2/nf-dxgi1_2-idxgifactory2-createswapchainforhwnd

TJ Bandrowsky
  • 842
  • 8
  • 12
  • 2
    Could you please show a minimal, reproducible sample without private information? – YangXiaoPo-MSFT Jul 13 '22 at 02:15
  • I pulled out the snippets of code that are relevant, if that helps. I also had done some additional investigation and clarified the problem. – TJ Bandrowsky Jul 13 '22 at 04:32
  • See above. The "minimum" is a Windows DirectX app... and that's well, not minimal. But I did pull more code from the above. The problem is symptomized by the last line "auto dipssz = renderTarget->GetSize();" in the applyswapchain method. I would have expected the rendertarget size in dips to be rather smaller than the size of the pixels of the window. But its' the same, meaning, the target is the wrong size and that has something to do with the clipping. – TJ Bandrowsky Jul 13 '22 at 04:38
  • 2
    Yep, that's the issue. Seems like SetTarget on a Direct2DContext picks up the wrong size off of the bitmap. – TJ Bandrowsky Jul 13 '22 at 05:01
  • And that seems to work better if I do SetBuffers before hand... GetClientRect(hwnd, &r); int x = r.right - r.left; int y = r.bottom - r.top; hr = swapChain->ResizeBuffers(2, x, y, DXGI_FORMAT_B8G8R8A8_UNORM, 0); throwOnFail(hr, "Couldn't resize swapchain"); – TJ Bandrowsky Jul 13 '22 at 05:12
  • If you're happy and found the issue, you should answer yourself and close that question – Simon Mourier Jul 13 '22 at 05:17
  • Thanks Simon. I will here shortly. I'm just testing now. – TJ Bandrowsky Jul 13 '22 at 07:08

1 Answers1

0

In a nutshell:

I think if you keep everything aligned to the window dpi, you are generally better off. Direct2d converts your drawing through the context from dips to whatever you tell the combination of the context, bitmap, surface, swapchain, whatever. Performance wise, it feels like a better result as there's no flickering because its too slow (or blitting when it should be flipping).

In any case, it is extremely important for both performance and display issues that the Swap Chain, Bitmaps, Direct2DContexts and RenderTargets are aligned with respect to the dpi and sizes. Direct2d is going to convert whatever you tell it into Dips, but you have to help it. This is absolutely critical for high dpi scenarios, or scenarios where there is some other dpi needed across the board. But you will, if using something besides the dpi returned by GetDpiForWindow, will force a conversion.

This means, that, going by the example we all stole, the direct2d bitmap used for the swap chain must be set to the dpi of the WINDOW. So, this is missing most likely because the original author never anticipated a high dpi scenario or simply didn't have a high dpi monitor (come on MS, get your tech writers some kit!)

        dpiWindow = GetDpiForWindow(hwnd);

        D2D1_BITMAP_PROPERTIES1 bitmapProperties = {};
        
        bitmapProperties.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW;
        bitmapProperties.pixelFormat = D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE);
        bitmapProperties.colorContext = nullptr;
        bitmapProperties.dpiX = dpiWindow;
        bitmapProperties.dpiY = dpiWindow;
            
        // Direct2D needs the dxgi version of the backbuffer surface pointer.
        hr = swapChain->GetBuffer(0, IID_IDXGISurface, (void**)&surface);
        throwOnFail(hr, "Could not get swap chain surface");

        DXGI_SURFACE_DESC sdec;
        surface->GetDesc(&sdec);

        // Get a D2D surface from the DXGI back buffer to use as the D2D render target.
        hr = renderTarget->CreateBitmapFromDxgiSurface(
            surface,
            &bitmapProperties,
            &bitmap
        );

        auto pxsx = bitmap->GetPixelSize();
        auto sizex = bitmap->GetSize();

Pay attention, to how at the end, you can check to see the pixel and dip size of the resultant bitmap. Generally, for high dpi displays, the pixel size will be much larger, than the dip size.

Now, here's the way I ruined my life for a few days to learn. The target or context has its own DPI, will draw converting to that DPI, from Dips. So, you also have to tell the context to use the same dpi as the bitmap you are settargeting with it...

        throwOnFail(hr, "Could create bitmap from surface");

        // Now we can set the Direct2D render target.
        auto dipssz = renderTarget->GetSize();
        auto pixssz = renderTarget->GetPixelSize();
        renderTarget->SetDpi(dpiWindow, dpiWindow);
        renderTarget->SetTarget(bitmap);
        dipssz = renderTarget->GetSize();

        std::cout << "render target pixel size " << pixssz.width << " " << pixssz.height << " " << std::endl;
        std::cout << "render target dip size " << dipssz.width << " " << dipssz.height << " " << std::endl;
        return;

Again, you make this aligned to the window, don't worry as Direct2d is drawing in dips and converting to whatever you put in the target and the bitmap, and they will convert around as needed.

TJ Bandrowsky
  • 842
  • 8
  • 12