smourier / DirectN

Direct interop Code for .NET Framework, .NET Core and .NET 5+ : DXGI, WIC, DirectX 9 to 12, Direct2D, Direct Write, Direct Composition, Media Foundation, WASAPI, CodecAPI, GDI, Spatial Audio, DVD, Windows Media Player, UWP DXInterop, WinUI3, etc.
MIT License
311 stars 28 forks source link

When running DirectN.WinUI3.MinimalD3D11 with dpi scaling enabled, the cube is not centered. #36

Closed dmitry-egorov closed 1 year ago

dmitry-egorov commented 1 year ago

When running DirectN.WinUI3.MinimalD3D11 with dpi scaling enabled, the cube is not centered.

I have a fix for this. Lines 186, 187:

_framebufferVP.Width = framebufferDepthDesc.Width;
_framebufferVP.Height = framebufferDepthDesc.Height;

can be replaced with:

_framebufferVP.Width = framebufferDepthDesc.Width / panel.CompositionScaleX;
_framebufferVP.Height = framebufferDepthDesc.Height / panel.CompositionScaleY;

It seems that Direct3D uses screen coordinates in DIPs, not in pixels and so the viewport must be scaled accordingly.

smourier commented 1 year ago

The code is a 1 to 1 port from https://gist.github.com/d7samurai/abab8a580d0298cb2f34a44eec41d39d so I've not tested it extensively, but you're right, and I think it makes sense here.

I've updated the code https://github.com/smourier/DirectN/commit/2282e571f311fba0a98bafa8d0d759b102e10166

Thanks.

dmitry-egorov commented 1 year ago

This example was a great help for me in integrating a DirectX component into a WinUI app. There are other examples on the web, but yours is the only one not convoluted by inheritance hierarchies. I just thought I'd contribute a little, so that it works better for others out of the box.

Thank you.

dmitry-egorov commented 1 year ago

Upon further investigating the issue, I realized that the solution is wrong. The correct solution is to not modify the viewport size, but set the matrix transform on the SwapChain, like this:

var dxgiMatrix3X2F = new DXGI_MATRIX_3X2_F();
dxgiMatrix3X2F._11 = 1.0f / panel.CompositionScaleX;
dxgiMatrix3X2F._22 = 1.0f / panel.CompositionScaleY;
_swapChain.As<IDXGISwapChain2>(true).SetMatrixTransform(dxgiMatrix3X2F);

As I understand, the transform is applied to the swapchain during composition, setting it to the correct size.

By modifying the viewport instead, we are setting up the pipline to render into a smaller part of the SwapChain. The output turns out to be the right size, but it's not in full resolution and the image is blurry.

I'm sorry for providing the wrong solution before.

smourier commented 1 year ago

Hi,

I didn't noticed the image was blurry, but indeed, it's best to work on the swap chain directly I guess.

I've just commited a slight different code: https://github.com/smourier/DirectN/commit/0b14851df0bd5e19c99d63c99eced43c52de04e7 to allow the app to redraw properly when dpi changes (so you can move the app from one monitor to another and support different dpis):

        if (Content is FrameworkElement fe)
        {
            // this whole convoluted thing is to detect dpi has changed...
            fe.Loaded += (s, e) =>
            {
                var scale = fe.XamlRoot.RasterizationScale;
                fe.XamlRoot.Changed += (s2, e2) =>
                {
                    if (fe.XamlRoot.RasterizationScale != scale)
                    {
                        Dispose();
                        Init();
                    }
                };
            };
        }

and I use Content.XamlRoot.RasterizationScale instead as it's updated with the new DPI value immediately.

        var panelScaleTransform = new DXGI_MATRIX_3X2_F();
        panelScaleTransform._11 = (float)(1 / Content.XamlRoot.RasterizationScale);
        panelScaleTransform._22 = (float)(1 / Content.XamlRoot.RasterizationScale);
        _swapChain.As<IDXGISwapChain2>()?.SetMatrixTransform(panelScaleTransform);