Skip to content

Commit

Permalink
Merge pull request #464 from Sergio0694/dev/device-context-pool
Browse files Browse the repository at this point in the history
Add support for ID2D1DeviceContextPool Win2D APIs
  • Loading branch information
Sergio0694 authored Feb 24, 2023
2 parents 66d9328 + 1b02720 commit 7eb97f6
Show file tree
Hide file tree
Showing 9 changed files with 176 additions and 57 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using TerraFX.Interop;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;

#pragma warning disable CS0649, IDE1006

namespace ABI.Microsoft.Graphics.Canvas;

/// <summary>
/// An interface for a COM object that provides access to a pooled <see cref="ID2D1DeviceContext"/> object.
/// </summary>
[Guid("A0928F38-F7D5-44DD-A5C9-E23D94734BBB")]
[NativeTypeName("class ID2D1DeviceContextLease : IUnknown")]
[NativeInheritance("IUnknown")]
internal unsafe struct ID2D1DeviceContextLease
{
public void** lpVtbl;

/// <inheritdoc cref="IUnknown.QueryInterface"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[VtblIndex(0)]
public HRESULT QueryInterface([NativeTypeName("const IID &")] Guid* riid, void** ppvObject)
{
return ((delegate* unmanaged[Stdcall]<ID2D1DeviceContextLease*, Guid*, void**, int>)this.lpVtbl[0])((ID2D1DeviceContextLease*)Unsafe.AsPointer(ref this), riid, ppvObject);
}

/// <inheritdoc cref="IUnknown.AddRef"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[VtblIndex(1)]
[return: NativeTypeName("ULONG")]
public uint AddRef()
{
return ((delegate* unmanaged[Stdcall]<ID2D1DeviceContextLease*, uint>)this.lpVtbl[1])((ID2D1DeviceContextLease*)Unsafe.AsPointer(ref this));
}

/// <inheritdoc cref="IUnknown.Release"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[VtblIndex(2)]
[return: NativeTypeName("ULONG")]
public uint Release()
{
return ((delegate* unmanaged[Stdcall]<ID2D1DeviceContextLease*, uint>)this.lpVtbl[2])((ID2D1DeviceContextLease*)Unsafe.AsPointer(ref this));
}

/// <summary>
/// Provides access to the <see cref="ID2D1DeviceContext"/> object wrapped by the current instance.
/// </summary>
/// <param name="deviceContext">The resulting <see cref="ID2D1DeviceContext"/> object wrapped by the current instance.</param>
/// <returns>The <see cref="HRESULT"/> for the operation.</returns>
/// <remarks>The returned <see cref="ID2D1DeviceContext"/> cannot be used once the owning lease has been released.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[VtblIndex(3)]
public HRESULT GetD2DDeviceContext(ID2D1DeviceContext** deviceContext)
{
return ((delegate* unmanaged[Stdcall]<ID2D1DeviceContextLease*, ID2D1DeviceContext**, int>)this.lpVtbl[3])((ID2D1DeviceContextLease*)Unsafe.AsPointer(ref this), deviceContext);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using TerraFX.Interop;
using TerraFX.Interop.Windows;

#pragma warning disable CS0649, IDE1006

namespace ABI.Microsoft.Graphics.Canvas;

/// <summary>
/// An interface for an object that provides access to pooled <see cref="TerraFX.Interop.DirectX.ID2D1DeviceContext"/> objects.
/// </summary>
[Guid("454A82A1-F024-40DB-BD5B-8F527FD58AD0")]
[NativeTypeName("class ID2D1DeviceContextPool : IUnknown")]
[NativeInheritance("IUnknown")]
internal unsafe struct ID2D1DeviceContextPool
{
public void** lpVtbl;

/// <inheritdoc cref="IUnknown.QueryInterface"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[VtblIndex(0)]
public HRESULT QueryInterface([NativeTypeName("const IID &")] Guid* riid, void** ppvObject)
{
return ((delegate* unmanaged[Stdcall]<ID2D1DeviceContextPool*, Guid*, void**, int>)this.lpVtbl[0])((ID2D1DeviceContextPool*)Unsafe.AsPointer(ref this), riid, ppvObject);
}

/// <inheritdoc cref="IUnknown.AddRef"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[VtblIndex(1)]
[return: NativeTypeName("ULONG")]
public uint AddRef()
{
return ((delegate* unmanaged[Stdcall]<ID2D1DeviceContextPool*, uint>)this.lpVtbl[1])((ID2D1DeviceContextPool*)Unsafe.AsPointer(ref this));
}

/// <inheritdoc cref="IUnknown.Release"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[VtblIndex(2)]
[return: NativeTypeName("ULONG")]
public uint Release()
{
return ((delegate* unmanaged[Stdcall]<ID2D1DeviceContextPool*, uint>)this.lpVtbl[2])((ID2D1DeviceContextPool*)Unsafe.AsPointer(ref this));
}

/// <summary>
/// Retrieves a new <see cref="ID2D1DeviceContextLease"/> object from the current instance.
/// </summary>
/// <param name="lease">The resulting <see cref="ID2D1DeviceContextLease"/> object to use.</param>
/// <returns>The <see cref="HRESULT"/> for the operation.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[VtblIndex(3)]
public HRESULT GetDeviceContextLease(ID2D1DeviceContextLease** lease)
{
return ((delegate* unmanaged[Stdcall]<ID2D1DeviceContextPool*, ID2D1DeviceContextLease**, int>)this.lpVtbl[3])((ID2D1DeviceContextPool*)Unsafe.AsPointer(ref this), lease);
}
}
46 changes: 33 additions & 13 deletions src/ComputeSharp.D2D1.Uwp/Extensions/ICanvasDeviceExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,23 +37,43 @@ public static HRESULT GetD2DDevice(this ref ICanvasDevice canvasDevice, ID2D1Dev
}

/// <summary>
/// Creates a new <see cref="ID2D1DeviceContext"/> from an input <see cref="ICanvasDevice"/>.
/// Retrieves a usable <see cref="ID2D1DeviceContext"/> from an input <see cref="ICanvasDevice"/> if one is not available.
/// If a new one had to be rented from the pool, it also returns the owning <see cref="ID2D1DeviceContextLease"/> instance.
/// </summary>
/// <param name="canvasDevice">The input <see cref="ICanvasDevice"/> object.</param>
/// <param name="d2D1DeviceContext">The resulting <see cref="ID2D1DeviceContext"/> object.</param>
/// <returns>The <see cref="HRESULT"/> for the operation.</returns>
public static HRESULT CreateD2DDeviceContext(this ref ICanvasDevice canvasDevice, ID2D1DeviceContext** d2D1DeviceContext)
/// <param name="d2D1DeviceContext">The current <see cref="ID2D1DeviceContext"/> object, if available.</param>
/// <param name="d2D1DeviceContextEffective">The resulting <see cref="ID2D1DeviceContext"/> instance to use.</param>
/// <param name="d2D1DeviceContextLease">The resulting <see cref="ID2D1DeviceContextLease"/> object, if used.</param>
/// <remarks>
/// When this method returns, <paramref name="d2D1DeviceContextEffective"/> is guaranteed to not be <see langword="null"/>.
/// </remarks>
public static void GetEffectiveD2DDeviceContextWithOptionalLease(
this ref ICanvasDevice canvasDevice,
ID2D1DeviceContext* d2D1DeviceContext,
ID2D1DeviceContext** d2D1DeviceContextEffective,
ID2D1DeviceContextLease** d2D1DeviceContextLease)
{
using ComPtr<ID2D1Device1> d2D1Device1 = default;

// Get the underlying ID2D1Device1 object
canvasDevice.GetD2DDevice(d2D1Device1.GetAddressOf()).Assert();
// If there is no input device context, just create a new one from the input canvas device
if (d2D1DeviceContext is null)
{
using (ComPtr<ID2D1DeviceContextPool> d2D1DeviceContextPool = default)
{
// Get the ID2D1DeviceContextPool interface reference
canvasDevice.QueryInterface(
riid: Win32.__uuidof<ID2D1DeviceContextPool>(),
ppvObject: d2D1DeviceContextPool.GetVoidAddressOf()).Assert();

// Create the new device context
HRESULT hresult = d2D1Device1.Get()->CreateDeviceContext(
options: D2D1_DEVICE_CONTEXT_OPTIONS.D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
deviceContext: d2D1DeviceContext);
// Get a new ID2D1DeviceContextLease object from the retrieved pool
d2D1DeviceContextPool.Get()->GetDeviceContextLease(d2D1DeviceContextLease).Assert();
}

return hresult;
// Get the underlying device context from the lease (this is much faster than creating a new device context)
(*d2D1DeviceContextLease)->GetD2DDeviceContext(d2D1DeviceContextEffective).Assert();
}
else
{
// Otherwise, just use the input device context
*d2D1DeviceContextEffective = new ComPtr<ID2D1DeviceContext>(d2D1DeviceContext);
}
}
}
2 changes: 1 addition & 1 deletion src/ComputeSharp.D2D1.Uwp/Helpers/ResourceManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ internal static class ResourceManager
/// <summary>
/// Gets or creates an <see cref="IGraphicsEffectSource"/> instance for a native resource.
/// </summary>
/// <param name="device">The input canvas device (as a marshalled <see cref="global::Microsoft.Graphics.Canvas.CanvasDevice"/>).</param>
/// <param name="device">The input canvas device (as a marshalled <see cref="Microsoft.Graphics.Canvas.CanvasDevice"/>).</param>
/// <param name="resource">The input native resource to create a wrapper for.</param>
/// <param name="dpi">The realization DPIs for <paramref name="resource"/></param>
/// <returns>The resulting <see cref="IGraphicsEffectSource"/> wrapper instance.</returns>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,17 @@ private bool Realize(WIN2D_GET_D2D_IMAGE_FLAGS flags, float targetDpi, ID2D1Devi
{
Guid effectId = PixelShaderEffect.For<T>.Instance.Id;

using ComPtr<ID2D1DeviceContext> d2D1DeviceContextEffective = default;
using ComPtr<ID2D1DeviceContextLease> d2D1DeviceContextLease = default;

// We need to realize the current effect (ComputeSharp.D2D1's ID2D1Effect), which needs a device context
this.canvasDevice.Get()->GetEffectiveD2DDeviceContextWithOptionalLease(
d2D1DeviceContext: deviceContext,
d2D1DeviceContextEffective: d2D1DeviceContextEffective.GetAddressOf(),
d2D1DeviceContextLease: d2D1DeviceContextLease.GetAddressOf());

// Try to create an instance of the effect in use and store it in the current object
HRESULT hresult = deviceContext->CreateEffect(effectId: &effectId, effect: d2D1Effect);
HRESULT hresult = d2D1DeviceContextEffective.Get()->CreateEffect(effectId: &effectId, effect: d2D1Effect);

// Check if creation failed due to the effect not being registered. In that case, register
// it and then try again. This is much faster than check whether the effect is registered
Expand All @@ -264,13 +273,13 @@ private bool Realize(WIN2D_GET_D2D_IMAGE_FLAGS flags, float targetDpi, ID2D1Devi
using ComPtr<ID2D1Factory1> d2D1Factory1 = default;

// Get the ID2D1Factory1 object to register the effect (required by D2D1PixelShaderEffect)
deviceContext->GetFactory1(d2D1Factory1.GetAddressOf()).Assert();
d2D1DeviceContextEffective.Get()->GetFactory1(d2D1Factory1.GetAddressOf()).Assert();

// Register the effect with the factory (pass the same D2D1 draw transform mapper factory that was used before)
D2D1PixelShaderEffect.RegisterForD2D1Factory1<T>(d2D1Factory1.Get(), out _);

// Try to create the effect again
hresult = deviceContext->CreateEffect(effectId: &effectId, effect: d2D1Effect);
hresult = d2D1DeviceContextEffective.Get()->CreateEffect(effectId: &effectId, effect: d2D1Effect);
}

// Rethrow any other errors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,25 +299,21 @@ private bool ApplyDpiCompensation(
// Create an ID2D1Effect for the DPI compensation, if there isn't one already
if (!sourceReference.HasDpiCompensationEffect)
{
using ComPtr<ID2D1DeviceContext> d2D1DeviceContextEffective = default;

// We need to create a DPI compensation effect, so a device context must be available.
// If there is no input device context, just create a new one from the realization device.
if (d2D1DeviceContext is null)
{
this.canvasDevice.Get()->CreateD2DDeviceContext(d2D1DeviceContextEffective.GetAddressOf()).Assert();
}
else
using (ComPtr<ID2D1DeviceContext> d2D1DeviceContextEffective = default)
using (ComPtr<ID2D1DeviceContextLease> d2D1DeviceContextLease = default)
{
// Otherwise, just use the input device context
*&d2D1DeviceContextEffective = new ComPtr<ID2D1DeviceContext>(d2D1DeviceContext);
// Just like when realizing the current effect, rent a device context from the pool if one is not available
this.canvasDevice.Get()->GetEffectiveD2DDeviceContextWithOptionalLease(
d2D1DeviceContext: d2D1DeviceContext,
d2D1DeviceContextEffective: d2D1DeviceContextEffective.GetAddressOf(),
d2D1DeviceContextLease: d2D1DeviceContextLease.GetAddressOf());

// Create the DPI compensation effect
d2D1DeviceContextEffective.Get()->CreateEffect(
effectId: (Guid*)Unsafe.AsPointer(ref Unsafe.AsRef(in CLSID.CLSID_D2D1DpiCompensation)),
effect: d2D1EffectDpiCompensation.GetAddressOf()).Assert();
}

// Create the DPI compensation effect
d2D1DeviceContextEffective.Get()->CreateEffect(
effectId: (Guid*)Unsafe.AsPointer(ref Unsafe.AsRef(in CLSID.CLSID_D2D1DpiCompensation)),
effect: d2D1EffectDpiCompensation.GetAddressOf()).Assert();

D2D1_BORDER_MODE d2D1BorderMode = D2D1_BORDER_MODE_HARD;
D2D1_DPICOMPENSATION_INTERPOLATION_MODE d2D1DpiCompensationInterpolationMode = D2D1_DPICOMPENSATION_INTERPOLATION_MODE_LINEAR;

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,4 @@ public void GetFactory(ID2D1Factory** factory)
{
((delegate* unmanaged[Stdcall]<ID2D1Device1*, ID2D1Factory**, void>)(lpVtbl[3]))((ID2D1Device1*)Unsafe.AsPointer(ref this), factory);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
[VtblIndex(4)]
public HRESULT CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS options, ID2D1DeviceContext** deviceContext)
{
return ((delegate* unmanaged[Stdcall]<ID2D1Device1*, D2D1_DEVICE_CONTEXT_OPTIONS, ID2D1DeviceContext**, int>)(lpVtbl[4]))((ID2D1Device1*)Unsafe.AsPointer(ref this), options, deviceContext);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
<Compile Include="$(MSBuildThisFileDirectory)DirectX\um\d2d1\ID2D1Image.cs" />
<Compile Include="$(MSBuildThisFileDirectory)DirectX\um\d2d1_1\D2D1_CHANNEL_DEPTH.cs" />
<Compile Include="$(MSBuildThisFileDirectory)DirectX\um\d2d1_1\D2D1_BUFFER_PRECISION.cs" />
<Compile Include="$(MSBuildThisFileDirectory)DirectX\um\d2d1_1\D2D1_DEVICE_CONTEXT_OPTIONS.cs" />
<Compile Include="$(MSBuildThisFileDirectory)DirectX\um\d2d1_1\D2D1_PROPERTY.cs" />
<Compile Include="$(MSBuildThisFileDirectory)DirectX\um\d2d1_1\D2D1_PROPERTY_TYPE.cs" />
<Compile Include="$(MSBuildThisFileDirectory)DirectX\um\d2d1_1\ID2D1CommandList.cs" />
Expand Down

0 comments on commit 7eb97f6

Please sign in to comment.