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 bindings to support efficiently authoring ICanvasEffect objects #894

Closed
Sergio0694 opened this issue Dec 12, 2022 · 1 comment
Closed

Comments

@Sergio0694
Copy link
Member

Sergio0694 commented Dec 12, 2022

Note: follow up to #888.

Background and motivation

The changes in #888 allow developers to implement their own custom ICanvasImage objects that can then be used with Win2D to eg. be drawn onto a drawing session. For an example one one such custom effect, see Sergio0694/ComputeSharp#451. Win2D supports all that's needed to make this possible once that PR is merged, but we can take things one step further to also allow developers to efficiently implement ICanvasEffect. This can also lay the foundation to potentially allow these effects to be used in a composition brush in the future, if WinUI 3 allowed externally implemented effects in a graph passed to Compositor.CreateEffectFactory.

The issue

Right now, developers already have all the tools they need to implement this interface. But they have no way of efficiently doing so. The crux of the problem is that in order to implement the ICanvasEffect interface, effect authors need access to an ID2D1DeviceContext object. From within any of the ICanvasEffect methods, they have the following options:

  • Get the CanvasDevice from the input ICanvasResourceCreatorWithDpi object, then get the underlying ID2D1Device (it can be done by jumping through the ICanvasResourceWrapperNative interface), and then from there calling CreateDeviceContext. This works, but it's fairly inefficient as creating a device is expensive.
  • They could implement their own per-device pool of device contexts, but this means (1) extra complexity and also (2) having a whole separate pool of such objects, given that Win2D already internally implements a pool of devices for each CanvasDevice object.

Even ignoring these two points, there's still the other issue that implementing these methods involves some additional work that is error prone, and that Win2D is already doing anyway internally, since it also implements those methods for all built-in effects. So the proposal is to simply expose 3 more C exports to allow developers to simply call back into these and let Win2D do the work for them, by just reusing the already existing internal implementation. These would be supporting methods for ICanvasImageInterop.

API proposal

Three new APIs will be added to ABI.Microsoft.Graphics.Canvas in winrt/published/Microsoft.Graphics.Canvas.native.h:

//
// Exported methods to allow ICanvasImageInterop implementors to implement ICanvasEffect properly.
//
extern "C" __declspec(nothrow, dllexport) HRESULT __stdcall InvalidateSourceRectangleForICanvasImageInterop(
    ICanvasResourceCreatorWithDpi* resourceCreator,
    ICanvasImageInterop* image,
    uint32_t sourceIndex,
    Rect const* invalidRectangle);

extern "C" __declspec(nothrow, dllexport) HRESULT __stdcall GetInvalidRectanglesForICanvasImageInterop(
    ICanvasResourceCreatorWithDpi* resourceCreator,
    ICanvasImageInterop* image,
    uint32_t* valueCount,
    Rect** valueElements);

extern "C" __declspec(nothrow, dllexport) HRESULT __stdcall GetRequiredSourceRectanglesForICanvasImageInterop(
    ICanvasResourceCreatorWithDpi* resourceCreator,
    ICanvasImageInterop* image,
    Rect const* outputRectangle,
    uint32_t sourceEffectCount,
    ICanvasEffect* const* sourceEffects,
    uint32_t sourceIndexCount,
    uint32_t const* sourceIndices,
    uint32_t sourceBoundsCount,
    Rect const* sourceBounds,
    uint32_t valueCount,
    Rect* valueElements);

There would follow the same pattern as GetBoundsForICanvasImageInterop, and they would support authors of ICanvasImageInterop objects to then also easily implement ICanvasEffect. They'd just implement the WinRT interface and then pass the arguments back into Win2D to perform the computation of the results. Win2D would know how to realize ICanvasImageInterop objects, so the same logic it uses internally today would apply as well (it'd just be extended to also support ICanvasImageInterop and not just ICanvasImageInternal).

Going further

To support other scenarios as well and generally to give external authors the ability to efficiently access Win2D's device context pool, it would also be useful to expose that in some capacity. One example could be by allowing developers to retrieve a lease for a device context (in much the same way as Win2D does internally), so they could use it and then simply return. Something like:

class __declspec(uuid("A0928F38-F7D5-44DD-A5C9-E23D94734BBB"))
ID2D1DeviceContextLease : public IUnknown
{
public:
    IFACEMETHOD(GetD2DDeviceContext)(ID2D1DeviceContext** deviceContext) = 0;
};

class __declspec(uuid("454A82A1-F024-40DB-BD5B-8F527FD58AD0"))
ID2D1DeviceContextPool : public IUnknown
{
public:
    IFACEMETHOD(GetDeviceContextLease)(ID2D1DeviceContextLease** lease) = 0;
};

ID2D1DeviceContextPool would be added to the set of interfaces that CanvasDevice implements. Developers would then be able to just QueryInterface a device for ID2D1DeviceContextPool, and then get a lease from there. From it, they can retrieve the context, use it, then release the lease once they're done. When the lease is released, it'll automatically return the context into the pool. This would allow for efficiently getting a context whenever one is needed and only a device is available (for instance, from ICanvasImageInterop, which doesn't always have an available context).

I've asked @jkoritzinsky who confirmed this is a relatively common pattern in COM for this scenario, so there's precedent 🙂

@Sergio0694
Copy link
Member Author

All proposed features have been approved, implemented, and we just merged the PRs in the internal repo 🎉
Closing this issue, those changes should be pulled back into the public repo here soon as well 😄

getrou pushed a commit that referenced this issue Apr 20, 2023
…tLease APIs

> Public tracking issue: #894

This PR implements the `ID2D1DeviceContextPool` APIs. Part of overall improved support for `ICanvasImageInterop`. The new pool is useful whenever a device context is needed but only an `ICanvasDevice` is available. For instance, that can often be the case when `ICanvasImageInterop.GetD2DImage` is called back from Win2D.

### API breakdown

The following new APIs are added in this PR (in the public header):

```cpp
class __declspec(uuid("A0928F38-F7D5-44DD-A5C9-E23D94734BBB"))
ID2D1DeviceContextLease : public IUnknown
{
public:
    IFACEMETHOD(GetD2DDeviceContext)(ID2D1DeviceContext** deviceContext) = 0;
};

class __declspec(uuid("454A82A1-F024-40DB-BD5B-8F527FD58AD0"))
ID2D1DeviceContextPool : public IUnknown
{
public:
    IFACEMETHOD(GetDeviceContextLease)(ID2D1DeviceContextLease** lease) = 0;
};
```

## Additional notes

Other than tests here, I've also validated this end-to-end through ComputeSharp.D2D1.Uwp.
Integration work for this new APIs into ComputeSharp is in [ComputeSharp/dev/device-context-pool.](https://github.com/Sergio0694/ComputeSharp/tree/dev/device-context-pool).
getrou pushed a commit that referenced this issue Apr 20, 2023
…implementations

> Public tracking issue: #894

This PR implements the new C exports to allow external developers implementing `ICanvasImageInterop` to also properly implement `ICanvasEffect`. This PR includes two main changes:
- Refactor the internal `ICanvasEffect` methods to all point to 3 shared stubs.
- Implement the 3 new public APIs that also just use those same 3 shared stubs.

This allows all these APIs to reuse the same exact implementation within Win2D, and it's just a matter how they're exposed: either as instance methods on the WinRT `ICanvasEffect` API, or as static C exports.

### API breakdown

The following new APIs are added in this PR (in the public header):

```cpp
//
// Exported methods to allow ICanvasImageInterop implementors to implement ICanvasEffect properly.
//
extern "C" __declspec(nothrow, dllexport) HRESULT __stdcall InvalidateSourceRectangleForICanvasImageInterop(
    ICanvasResourceCreatorWithDpi* resourceCreator,
    ICanvasImageInterop* image,
    uint32_t sourceIndex,
    Rect const* invalidRectangle);

extern "C" __declspec(nothrow, dllexport) HRESULT __stdcall GetInvalidRectanglesForICanvasImageInterop(
    ICanvasResourceCreatorWithDpi* resourceCreator,
    ICanvasImageInterop* image,
    uint32_t* valueCount,
    Rect** valueElements);

extern "C" __declspec(nothrow, dllexport) HRESULT __stdcall GetRequiredSourceRectanglesForICanvasImageInterop(
    ICanvasResourceCreatorWithDpi* resourceCreator,
    ICanvasImageInterop* image,
    Rect const* outputRectangle,
    uint32_t sourceEffectCount,
    ICanvasEffect* const* sourceEffects,
    uint32_t sourceIndexCount,
    uint32_t const* sourceIndices,
    uint32_t sourceBoundsCount,
    Rect const* sourceBounds,
    uint32_t valueCount,
    Rect* valueElements);
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant