Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose WindowId-based WinRT API to create CanvasSwapChain-s for HWNDs #924

Closed
Sergio0694 opened this issue Jun 5, 2023 · 2 comments
Closed
Labels
approved The proposal was approved in API review, it can be implemented feature

Comments

@Sergio0694
Copy link
Member

Sergio0694 commented Jun 5, 2023

Note: follow up to #915.

With the new HWND interop API, it is now much easier to create a CanvasSwapChain object from a given HWND owned by a developer. Specifically for C# developers, however, using this API means that there's still a good amount of extra work needed to perform the actual interop operations to access the API. The previously approved API included an additional extension method in the .NET projection assembly to expose the HWND API in a more convenient way. But we can do better 🙂

There is a new WinRT type, WindowId, which is basically a thin wrapper for HWND. On WinAppSDK, there are built-in APIs to easily convert to-from HWND-s, and most importantly, APIs like AppWindow directly expose a WindowId property. If we added built-in support for this in Win2D, we'd basically allow developers to create a new window + swapchain with 0 interop needed 🎉

API proposal

namespace Microsoft.Graphics.Canvas;

public sealed class CanvasSwapChain
{
    public static CanvasSwapChain CreateForWindowId(
        WindowId windowId,
        uint width,
        uint height);

    public static CanvasSwapChain CreateForWindowId(
        WindowId windowId,
        uint width,
        uint height,
        float dpi);

    public static CanvasSwapChain CreateForWindowId(
        WindowId windowId,
        uint width,
        uint height,
        float dpi,
        DirectXPixelFormat format,
        int bufferCount);
}

The shape mostly follows that already approved from the previous proposal, but adapted to use WindowId.
I've only included a new overload with also the explicit DPI value, but not the format or the buffer count.

Example use

This is all that will be needed to get an HWND swapchain running in a blank WinAppSDK app:

// Create, resize and show the window
AppWindow window = AppWindow.Create();
window.Resize(new SizeInt32(1280, 720));
window.Show();

// Create the swapchain
CanvasSwapChain swapchain = CanvasSwapChain.CreateForWindowId(window.Id, 1280, 720);

// Draw!
using (CanvasDrawingSession drawingSession = swapChain.CreateDrawingSession(default))
{
    drawingSession.DrawEllipse(155, 115, 80, 30, Color.FromArgb(a: 255, r: 0, g: 148, b: 255), 3);
    drawingSession.DrawText("Hello, world!", 100, 100, Color.FromArgb(a: 255, r: 255, g: 216, b: 0));
}

// Present the new frame
this.canvasSwapChain.Present(syncInterval: 1);

Additional notes

At least for now, these new APIs would only be for WinAppSDK, not UWP. The reason being that on WinAppSDK, AppWindow returns a WindowId value which can be used by Win2D to create an HWND swapchain directly (as there's documented helpers to get an HWND from a WindowId value). This is not the case on UWP (plus the UWP AppWindow is not just a thin HWND wrapper like on WinAppSDK). So until we find a way to expose this as nicely on UWP as well, we can keep these APIs just on the other branch.

To make these available on UWP, we'd need:

  • A documented API to convert HWND <-> WindowId (this would at least allow devs to use their own HWNDs)
  • (Optional) A documented API to get a WindowId value from UWP's AppWindow type, and direct DirectX content being allowed for such windows. Currently, the docs specifically say that AppWindow can only host XAML content (on UWP).

UPDATE: UWP now also has a documented way to convert HWND <-> WindowId, in Windows.UI.Interop.h 🎉

@Sergio0694
Copy link
Member Author

Notes from API review

  • Proposed APIs look good, but let's first only add the last two overloads with explicit parameters for size and DPIs. This will make it clearer for consumers they have to manage DPI changes and size manually. We can add more later if necessary.
public sealed class CanvasSwapChain
{
    public static CanvasSwapChain CreateForWindowId(
        WindowId windowId,
        uint width,
        uint height,
        float dpi);

    public static CanvasSwapChain CreateForWindowId(
        WindowId windowId,
        uint width,
        uint height,
        float dpi,
        DirectXPixelFormat format,
        int bufferCount);
}
  • We will be adding these to both WASDK and UWP to continue keeping the two branches in sync. The namespace of WindowId is different across the two targets, but this is already a common pattern with other APIs from WASDK as well.
  • We will investigate more how best to handle runtime checks or SDK checks in general, as the necessary interop APIs for WindowId are only available on recent Windows 11 SDKs for UWP. So for now, we'll start by only adding these two APIs to the WASDK branch, and add them to the UWP one too after we figure out a strategy for this we are comfortable with.

@Sergio0694 Sergio0694 added the approved The proposal was approved in API review, it can be implemented label Aug 8, 2023
@Sergio0694
Copy link
Member Author

Closing as completed. Implemented in the internal repo, will be available in the next preview for WinAppSDK (for now).

getrou pushed a commit that referenced this issue Aug 13, 2024
### Closes #924

### Overview

This PR introduces the following new APIs for `CanvasSwapChain`:
```cpp
[overload("CreateForWindowId")]
HRESULT CreateForWindowIdWithDpi(
    [in] ICanvasResourceCreator* resourceCreator,
    [in] Microsoft.UI.WindowId windowId,
    [in] float width,
    [in] float height,
    [in] float dpi,
    [out, retval] CanvasSwapChain** swapChain);

[overload("CreateForWindowId")]
HRESULT CreateForWindowIdWithAllOptions(
    [in] ICanvasResourceCreator* resourceCreator,
    [in] Microsoft.UI.WindowId windowId,
    [in] float width,
    [in] float height,
    [in] float dpi,
    [in] DIRECTX_PIXEL_FORMAT format,
    [in] INT32 bufferCount,
    [out, retval] CanvasSwapChain** swapChain);
```

> **NOTE:** we can add a simpler overload with no size/DPI in a follow up.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved The proposal was approved in API review, it can be implemented feature
Projects
None yet
Development

No branches or pull requests

1 participant