From a8fec6c69a410fd47a562e2f314d326facb745f6 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 10 Mar 2023 11:08:34 +0000 Subject: [PATCH] Merged PR 24469: Port ICanvasImageInterop features and other new APIs from UWP branch This PR ports all the `ICanvasImageInterop` features and other new APIs from the UWP branch to the WASDK branch. The original PRs into the UWP branch are mostly unchanged, minus some tweaks here and there mostly around marshalling and some project file adjustments to make things work. Included cherry-picked PRs: - !23226 - !23817 - !23654 - !23663 - !24053 - !23861 --- README.md | 2 + winrt/lib/brushes/CanvasBrush.cpp | 2 +- winrt/lib/brushes/CanvasImageBrush.cpp | 13 +- winrt/lib/brushes/CanvasImageBrush.h | 2 +- winrt/lib/drawing/CanvasDevice.abi.idl | 30 +- winrt/lib/drawing/CanvasDevice.cpp | 103 +++- winrt/lib/drawing/CanvasDevice.h | 32 +- winrt/lib/drawing/CanvasDrawingSession.cpp | 5 +- winrt/lib/drawing/CanvasSwapChain.cpp | 2 +- winrt/lib/effects/CanvasEffect.cpp | 520 +++++++++++++----- winrt/lib/effects/CanvasEffect.h | 18 +- .../lib/effects/shader/PixelShaderEffect.cpp | 2 +- winrt/lib/effects/shader/PixelShaderEffect.h | 2 +- winrt/lib/images/CanvasBitmap.h | 27 +- winrt/lib/images/CanvasCommandList.cpp | 30 +- winrt/lib/images/CanvasCommandList.h | 9 +- winrt/lib/images/CanvasImage.cpp | 50 +- winrt/lib/images/CanvasImage.h | 94 +++- winrt/lib/images/CanvasVirtualBitmap.cpp | 25 +- winrt/lib/images/CanvasVirtualBitmap.h | 10 +- winrt/lib/pch.h | 10 + winrt/lib/winrt.lib.uap.vcxproj | 1 + .../Microsoft.Graphics.Canvas.native.h | 116 ++++ winrt/test.external/CanvasDeviceTests.cpp | 43 ++ .../test.external/CanvasImageInteropTests.cpp | 457 +++++++++++++++ winrt/test.external/pch.h | 2 + .../winrt.test.external.uap.vcxproj | 1 + .../winrt.test.external.uap.vcxproj.filters | 1 + .../graphics/CanvasBitmapUnitTest.cpp | 14 + .../graphics/CanvasDeviceUnitTests.cpp | 48 ++ .../graphics/CanvasEffectUnitTest.cpp | 211 ++++++- .../graphics/CanvasRenderTargetUnitTests.cpp | 1 + .../graphics/CanvasVirtualBitmapUnitTests.cpp | 18 +- .../graphics/PixelShaderEffectUnitTests.cpp | 55 +- winrt/test.internal/mocks/MockCanvasDevice.h | 14 + winrt/test.managed/NativeExportTests.cs | 290 ++++++++++ .../winrt.test.managed.uap.csproj | 2 + 37 files changed, 2041 insertions(+), 221 deletions(-) create mode 100644 winrt/test.external/CanvasImageInteropTests.cpp create mode 100644 winrt/test.managed/NativeExportTests.cs diff --git a/README.md b/README.md index 0221cb26..81c9e35e 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ rendering with GPU acceleration. It is available to C#, C++ and VB developers writing apps for WinUI3. It utilizes the power of Direct2D, and integrates seamlessly with XAML. +Visit the [DirectX Landing Page](https://devblogs.microsoft.com/directx/landing-page/) for more resources for DirectX developers. + ##### Where to get it - [NuGet package](http://www.nuget.org/packages/Microsoft.Graphics.Win2D) - [Source code](http://github.com/Microsoft/Win2D) diff --git a/winrt/lib/brushes/CanvasBrush.cpp b/winrt/lib/brushes/CanvasBrush.cpp index 9bf4c1a9..1565d9eb 100644 --- a/winrt/lib/brushes/CanvasBrush.cpp +++ b/winrt/lib/brushes/CanvasBrush.cpp @@ -55,7 +55,7 @@ IFACEMETHODIMP CanvasBrush::get_Device(ICanvasDevice** value) return ExceptionBoundary( [&] { - CheckInPointer(value); + CheckAndClearOutPointer(value); ThrowIfFailed(m_device.EnsureNotClosed().CopyTo(value)); }); } diff --git a/winrt/lib/brushes/CanvasImageBrush.cpp b/winrt/lib/brushes/CanvasImageBrush.cpp index 35bed180..8b49d245 100644 --- a/winrt/lib/brushes/CanvasImageBrush.cpp +++ b/winrt/lib/brushes/CanvasImageBrush.cpp @@ -118,7 +118,7 @@ void CanvasImageBrush::SetImage(ICanvasImage* image) else { // Use an image brush. - auto d2dImage = As(image)->GetD2DImage(m_device.EnsureNotClosed().Get(), nullptr, GetImageFlags::MinimalRealization); + auto d2dImage = ICanvasImageInternal::GetD2DImageFromInternalOrInteropSource(image, m_device.EnsureNotClosed().Get(), nullptr, WIN2D_GET_D2D_IMAGE_FLAGS_MINIMAL_REALIZATION); if (m_d2dImageBrush) { @@ -397,8 +397,9 @@ ComPtr CanvasImageBrush::GetD2DBrush(ID2D1DeviceContext* deviceConte // If our input image is an effect graph, make sure it is fully configured to match the target DPI. if (deviceContext) { - GetImageFlags effectFlags = ((flags & GetBrushFlags::AlwaysInsertDpiCompensation) != GetBrushFlags::None) ? GetImageFlags::AlwaysInsertDpiCompensation - : GetImageFlags::ReadDpiFromDeviceContext; + WIN2D_GET_D2D_IMAGE_FLAGS effectFlags = ((flags & GetBrushFlags::AlwaysInsertDpiCompensation) != GetBrushFlags::None) + ? WIN2D_GET_D2D_IMAGE_FLAGS_ALWAYS_INSERT_DPI_COMPENSATION + : WIN2D_GET_D2D_IMAGE_FLAGS_READ_DPI_FROM_DEVICE_CONTEXT; RealizeSourceEffect(deviceContext, effectFlags, 0); } @@ -429,10 +430,10 @@ IFACEMETHODIMP CanvasImageBrush::GetNativeResource(ICanvasDevice* device, float else { // If our input image is an effect graph, make sure it is fully configured to match the target DPI. - GetImageFlags effectFlags = GetImageFlags::AllowNullEffectInputs; + WIN2D_GET_D2D_IMAGE_FLAGS effectFlags = WIN2D_GET_D2D_IMAGE_FLAGS_ALLOW_NULL_EFFECT_INPUTS; if (dpi <= 0) - effectFlags |= GetImageFlags::AlwaysInsertDpiCompensation; + effectFlags |= WIN2D_GET_D2D_IMAGE_FLAGS_ALWAYS_INSERT_DPI_COMPENSATION; RealizeSourceEffect(nullptr, effectFlags, dpi); @@ -441,7 +442,7 @@ IFACEMETHODIMP CanvasImageBrush::GetNativeResource(ICanvasDevice* device, float }); } -void CanvasImageBrush::RealizeSourceEffect(ID2D1DeviceContext* deviceContext, GetImageFlags flags, float dpi) +void CanvasImageBrush::RealizeSourceEffect(ID2D1DeviceContext* deviceContext, WIN2D_GET_D2D_IMAGE_FLAGS flags, float dpi) { // Do we have a source image? ComPtr d2dImage; diff --git a/winrt/lib/brushes/CanvasImageBrush.h b/winrt/lib/brushes/CanvasImageBrush.h index 22768d52..981fcefe 100644 --- a/winrt/lib/brushes/CanvasImageBrush.h +++ b/winrt/lib/brushes/CanvasImageBrush.h @@ -98,7 +98,7 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas { na void SwitchToImageBrush(ID2D1Image* image); void SwitchToBitmapBrush(ID2D1Bitmap1* bitmap); void TrySwitchFromImageBrushToBitmapBrush(); - void RealizeSourceEffect(ID2D1DeviceContext* deviceContext, GetImageFlags flags, float dpi); + void RealizeSourceEffect(ID2D1DeviceContext* deviceContext, WIN2D_GET_D2D_IMAGE_FLAGS flags, float dpi); }; }}}}} diff --git a/winrt/lib/drawing/CanvasDevice.abi.idl b/winrt/lib/drawing/CanvasDevice.abi.idl index 615f1416..734bf83c 100644 --- a/winrt/lib/drawing/CanvasDevice.abi.idl +++ b/winrt/lib/drawing/CanvasDevice.abi.idl @@ -116,10 +116,13 @@ namespace Microsoft.Graphics.Canvas [eventremove] HRESULT DeviceLost([in] EventRegistrationToken token); // - // Identifies whether this error code matches a device lost. - // If this device was not actually lost, this method always - // returns false. + // Identifies whether this error code matches one of the HRESULTs that Win2D + // classifies as "device lost" error codes. If that is the case, the method + // then returns whether the current device is actually lost. If the HRESULT + // is instead not one that Win2D classifies as "device lost", this method + // will always returns false. // + [overload("IsDeviceLost")] HRESULT IsDeviceLost([in] int hresult, [out, retval] boolean* value); // @@ -142,6 +145,27 @@ namespace Microsoft.Graphics.Canvas // it calls ID2D1MultiThread::Leave. // HRESULT Lock([out, retval] CanvasLock** lock); + + // + // Checks whether the current device instance is lost. To get the exact + // HRESULT representing the reason it was lost, call GetDeviceLostReason(). + // + [overload("IsDeviceLost")] + HRESULT IsDeviceLost2([out, retval] boolean* value); + + // + // Gets the HRESULT representing the reason the device was lost. + // This method is only expected to be called when the device is + // actually lost. If that is not the case, it will just return 0. + // + // This method can be used to check the reason a device was lost + // from a handler for the DeviceLost event (eg. for telemetry). + // + // The returned HRESULT is not guaranteed to be recognized by the IsDeviceLost + // overload taking an input HRESULT, as is instead the raw error code from + // the underlying DirectX device wrapped by the current CanvasDevice instance. + // + HRESULT GetDeviceLostReason([out, retval] int* hresult); }; [STANDARD_ATTRIBUTES, activatable(VERSION), activatable(ICanvasDeviceFactory, VERSION), static(ICanvasDeviceStatics, VERSION)] diff --git a/winrt/lib/drawing/CanvasDevice.cpp b/winrt/lib/drawing/CanvasDevice.cpp index 020bd08e..579330f4 100644 --- a/winrt/lib/drawing/CanvasDevice.cpp +++ b/winrt/lib/drawing/CanvasDevice.cpp @@ -641,10 +641,11 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas return ExceptionBoundary( [&] { - GetResource(); // this ensures that Close() hasn't been called CheckInPointer(value); CheckInPointer(token); + GetResource(); // this ensures that Close() hasn't been called + ThrowIfFailed(m_deviceLostEventList.Add(value, token)); }); } @@ -663,6 +664,20 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas }); } + IFACEMETHODIMP CanvasDevice::IsDeviceLost2( + boolean* value) + { + return ExceptionBoundary( + [&] + { + CheckInPointer(value); + + GetResource(); // this ensures that Close() hasn't been called + + *value = GetDeviceRemovedErrorCode() != S_OK; + }); + } + IFACEMETHODIMP CanvasDevice::IsDeviceLost( int hresult, boolean* value) @@ -670,10 +685,49 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas return ExceptionBoundary( [&] { - GetResource(); // this ensures that Close() hasn't been called - CheckInPointer(value); + GetResource(); // this ensures that Close() hasn't been called + + // A bit of history. This method was added years ago, and the documentation for it is a bit confusing. + // + // The docs say: + // "IsDeviceLost will return true if the device is indeed lost, + // and the error code actually corresponds to device removal.". + // + // What that really means is that this method is checking whether the input error code belongs to an + // arbitrary set of possible device lost error codes (not even all of them) that various Win2D controls + // internally classify as representing a "device lost" event. It's essentially exposing the ability to + // match an HRESULT against this set to external users as well. Then, if the input HRESULT does match, + // this method returns whether the current device is lost (not necessarily for the reason given as input). + // + // As in, as long as the input error code is, say, DXGI_ERROR_DEVICE_HUNG or any other error code from + // this arbitrary set of error codes, this method will return whether the device is lost for *any* reason. + // + // In practice, this means that this method is really only useful in a very specific scenario (which is indeed + // documented): an exception handler that's matching the caught HRESULT against this "device lost" set, so that + // if this method returns true it can call CanvasDevice.RaiseDeviceLost() to notify registered handlers. This + // system is essentially working around the fact that the underlying D3D11 device doesn't have a proper device + // lost event (as is instead the case on D3D12), so it relies on callers to detect failures on their end. + // + // Unfortunately, this also means that outside of that very specific use case, this method provides not much + // utility to callers. The only information it can give callers is whether the device is lost or not, with the + // condition that they must pass an (unused) HRESULT that causes this condition below to be true. But even then, + // if this method is not called from a handler, it's also pretty awkward to use, as callers would have to just + // pick a random "device lost HRESULT" value to pass to this method just to get the check to pass, so they can + // check whether the device is actually lost or not. + // + // We can't really change this method, as it'd be a breaking change, and there's surely plenty of consumers + // relying on the exact way this system is structured (as it's the recommended way for external users to check + // and notify a device that it has been lost following an exception that was caught on their end while drawing). + // But even if we could adjust the implementation of this method, it's still not really a method with a useful + // signature in general outside of that very specific use case, as it's much better to just either tell callers + // whether the device is lost or not (regardless of the reason), or to give them the actual reason and allow + // them to then use whatever logic they want to handle that information. + // + // This is the reason for the two other APIs next to this method: + // - IsDeviceLost2 (exposed as an "IsDeviceLost" overload), simply returning whether the device is lost. + // - GetDeviceLostReason, returning the actual HRESULT from the underlying D3D11 device. if (DeviceLostException::IsDeviceLostHResult(hresult)) { *value = GetDeviceRemovedErrorCode() != S_OK; @@ -685,6 +739,19 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas }); } + IFACEMETHODIMP CanvasDevice::GetDeviceLostReason(int* hresult) + { + return ExceptionBoundary( + [&] + { + CheckInPointer(hresult); + + GetResource(); // this ensures that Close() hasn't been called + + *hresult = GetDeviceRemovedErrorCode(); + }); + } + IFACEMETHODIMP CanvasDevice::RaiseDeviceLost() { return ExceptionBoundary( @@ -704,6 +771,8 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas return ExceptionBoundary( [&] { + CheckAndClearOutPointer(value); + auto factory = GetD2DFactory(); auto lock = Make(As(factory).Get()); @@ -1036,6 +1105,34 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas }); } + // + // ID2D1DeviceContextPool + // + IFACEMETHODIMP CanvasDevice::GetDeviceContextLease(ID2D1DeviceContextLease** lease) + { + return ExceptionBoundary( + [&] + { + CheckAndClearOutPointer(lease); + + ThrowIfFailed(Make(this).CopyTo(lease)); + }); + } + + // + // ID2D1DeviceContextLease + // + IFACEMETHODIMP D2D1DeviceContextLease::GetD2DDeviceContext(ID2D1DeviceContext** deviceContext) + { + return ExceptionBoundary( + [&] + { + CheckAndClearOutPointer(deviceContext); + + ThrowIfFailed(m_deviceContext->QueryInterface(IID_PPV_ARGS(deviceContext))); + }); + } + ComPtr CanvasDevice::CreateGradientStopCollection( std::vector&& stops, D2D1_COLOR_SPACE preInterpolationSpace, diff --git a/winrt/lib/drawing/CanvasDevice.h b/winrt/lib/drawing/CanvasDevice.h index eb680de6..f15b178a 100644 --- a/winrt/lib/drawing/CanvasDevice.h +++ b/winrt/lib/drawing/CanvasDevice.h @@ -187,7 +187,8 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas CloakedIid, ICanvasResourceCreator, IDirect3DDevice, - CloakedIid) + CloakedIid, + CloakedIid) { InspectableClass(RuntimeClass_Microsoft_Graphics_Canvas_CanvasDevice, BaseTrust); @@ -255,8 +256,11 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas IFACEMETHOD(remove_DeviceLost)(EventRegistrationToken token) override; + IFACEMETHOD(IsDeviceLost2)(boolean* value) override; IFACEMETHOD(IsDeviceLost)(int hresult, boolean* value) override; + IFACEMETHOD(GetDeviceLostReason)(int* hresult) override; + IFACEMETHOD(RaiseDeviceLost)() override; IFACEMETHOD(Lock)(ICanvasLock** value) override; @@ -384,6 +388,11 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas IFACEMETHOD(GetInterface)(IID const&, void**) override; + // + // ID2D1DeviceContextPool + // + IFACEMETHOD(GetDeviceContextLease)(ID2D1DeviceContextLease** lease) override; + // // Internal // @@ -414,6 +423,27 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas static void LogCreateCanvasDevice(); }; + // + // ID2D1DeviceContextLease implementation that is returned by CanvasDevice::GetDeviceContextLease. + // This type is just a very thin COM object wrapping a DeviviceContextLease, which is the lease + // object holding a pooled ID2D1DeviceContext instance already used internally by Win2D effects. + // + class D2D1DeviceContextLease final : public RuntimeClass, ID2D1DeviceContextLease> + { + public: + D2D1DeviceContextLease(CanvasDevice* canvasDevice) + { + CheckInPointer(canvasDevice); + + m_deviceContext = canvasDevice->GetResourceCreationDeviceContext(); + } + + IFACEMETHOD(GetD2DDeviceContext)(ID2D1DeviceContext** deviceContext) override; + + private: + DeviceContextLease m_deviceContext; + }; + // // Singleton shared by all active CanvasDevice instances, responsible for tracking global diff --git a/winrt/lib/drawing/CanvasDrawingSession.cpp b/winrt/lib/drawing/CanvasDrawingSession.cpp index ed389467..a8f606f1 100644 --- a/winrt/lib/drawing/CanvasDrawingSession.cpp +++ b/winrt/lib/drawing/CanvasDrawingSession.cpp @@ -472,8 +472,7 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas { // If DrawBitmap cannot handle this request, we must use the DrawImage slow path. - auto internalImage = As(image); - auto d2dImage = internalImage->GetD2DImage(m_canvasDevice, m_deviceContext); + ComPtr d2dImage = ICanvasImageInternal::GetD2DImageFromInternalOrInteropSource(image, m_canvasDevice, m_deviceContext); auto d2dInterpolationMode = static_cast(m_interpolation); auto d2dCompositeMode = composite ? static_cast(*composite) @@ -3725,7 +3724,7 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas return ExceptionBoundary( [&] { - CheckInPointer(value); + CheckAndClearOutPointer(value); ThrowIfFailed(GetDevice().CopyTo(value)); }); diff --git a/winrt/lib/drawing/CanvasSwapChain.cpp b/winrt/lib/drawing/CanvasSwapChain.cpp index 944cdf97..6cd3a336 100644 --- a/winrt/lib/drawing/CanvasSwapChain.cpp +++ b/winrt/lib/drawing/CanvasSwapChain.cpp @@ -579,7 +579,7 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas return ExceptionBoundary( [&] { - CheckInPointer(value); + CheckAndClearOutPointer(value); auto& device = m_device.EnsureNotClosed(); diff --git a/winrt/lib/effects/CanvasEffect.cpp b/winrt/lib/effects/CanvasEffect.cpp index 2f87825e..78baa24a 100644 --- a/winrt/lib/effects/CanvasEffect.cpp +++ b/winrt/lib/effects/CanvasEffect.cpp @@ -106,12 +106,41 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas { na return GetImageBoundsImpl(this, resourceCreator, &transform, bounds); } + // + // ICanvasImageInterop + // + + IFACEMETHODIMP CanvasEffect::GetDevice(ICanvasDevice** device, WIN2D_GET_DEVICE_ASSOCIATION_TYPE* type) + { + return ExceptionBoundary([&] + { + CheckAndClearOutPointer(device); + CheckInPointer(type); + + *type = WIN2D_GET_DEVICE_ASSOCIATION_TYPE_UNSPECIFIED; + + ThrowIfClosed(); + + auto realizationDevice = RealizationDevice(); + + // RealizationDevice() returns an ICanvasDevice* (not a ComPtr), so we can + // just check if it's not null and forward to QueryInterface if that's the case. If instead + // the device is null, CheckAndClearOutPointer will have already set the argument to null. + if (realizationDevice) + { + ThrowIfFailed(realizationDevice->QueryInterface(IID_PPV_ARGS(device))); + } + + // The returned device is the current realization device, and can change + *type = WIN2D_GET_DEVICE_ASSOCIATION_TYPE_REALIZATION_DEVICE; + }); + } // // ICanvasImageInternal // - ComPtr CanvasEffect::GetD2DImage(ICanvasDevice* device, ID2D1DeviceContext* deviceContext, GetImageFlags flags, float targetDpi, float* realizedDpi) + ComPtr CanvasEffect::GetD2DImage(ICanvasDevice* device, ID2D1DeviceContext* deviceContext, WIN2D_GET_D2D_IMAGE_FLAGS flags, float targetDpi, float* realizedDpi) { ThrowIfClosed(); @@ -127,13 +156,13 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas { na auto lock = Lock(m_mutex); // Process the ReadDpiFromDeviceContext flag. - if ((flags & GetImageFlags::ReadDpiFromDeviceContext) != GetImageFlags::None) + if ((flags & WIN2D_GET_D2D_IMAGE_FLAGS_READ_DPI_FROM_DEVICE_CONTEXT) != WIN2D_GET_D2D_IMAGE_FLAGS_NONE) { if (TargetIsCommandList(deviceContext)) { // Command lists are DPI independent, so we always // need to insert DPI compensation when drawing to them. - flags |= GetImageFlags::AlwaysInsertDpiCompensation; + flags |= WIN2D_GET_D2D_IMAGE_FLAGS_ALWAYS_INSERT_DPI_COMPENSATION; } else { @@ -141,13 +170,13 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas { na targetDpi = GetDpi(deviceContext); } - flags &= ~GetImageFlags::ReadDpiFromDeviceContext; + flags &= ~WIN2D_GET_D2D_IMAGE_FLAGS_READ_DPI_FROM_DEVICE_CONTEXT; } // If this is a DPI compensation effect, we no longer need to insert any further compensation. if (IsEqualGUID(m_effectId, CLSID_D2D1DpiCompensation)) { - flags |= GetImageFlags::NeverInsertDpiCompensation; + flags |= WIN2D_GET_D2D_IMAGE_FLAGS_NEVER_INSERT_DPI_COMPENSATION; } // Check if device is the same as previous device. @@ -168,7 +197,7 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas { na return nullptr; } } - else if ((flags & GetImageFlags::MinimalRealization) == GetImageFlags::None) + else if ((flags & WIN2D_GET_D2D_IMAGE_FLAGS_MINIMAL_REALIZATION) == WIN2D_GET_D2D_IMAGE_FLAGS_NONE) { // Recurse through the effect graph to make sure child nodes are properly realized. RefreshInputs(flags, targetDpi, deviceContext); @@ -196,13 +225,13 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas { na if (!device) ThrowHR(E_INVALIDARG, Strings::GetResourceNoDevice); - GetImageFlags flags = GetImageFlags::AllowNullEffectInputs; + WIN2D_GET_D2D_IMAGE_FLAGS flags = WIN2D_GET_D2D_IMAGE_FLAGS_ALLOW_NULL_EFFECT_INPUTS; // If the caller did not specify target DPI, we always need to insert DPI compensation // (same as when using effects with DPI independent contexts such as command lists). if (dpi <= 0) { - flags |= GetImageFlags::AlwaysInsertDpiCompensation; + flags |= WIN2D_GET_D2D_IMAGE_FLAGS_ALWAYS_INSERT_DPI_COMPENSATION; } auto realizedEffect = GetD2DImage(device, nullptr, flags, dpi); @@ -470,12 +499,12 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas { na { DeviceContextLease m_deviceContext; ComPtr m_device; - GetImageFlags m_flags; + WIN2D_GET_D2D_IMAGE_FLAGS m_flags; float m_dpi; public: EffectRealizationContext(ICanvasResourceCreatorWithDpi* resourceCreator) - : m_flags(GetImageFlags::AllowNullEffectInputs) + : m_flags(WIN2D_GET_D2D_IMAGE_FLAGS_ALLOW_NULL_EFFECT_INPUTS) { CheckInPointer(resourceCreator); @@ -491,7 +520,7 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas { na // Special case for command lists, which always require DPI compensation. if (TargetIsCommandList(m_deviceContext.Get())) { - m_flags |= GetImageFlags::AlwaysInsertDpiCompensation; + m_flags |= WIN2D_GET_D2D_IMAGE_FLAGS_ALWAYS_INSERT_DPI_COMPENSATION; } } else @@ -501,10 +530,136 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas { na } } - ComPtr RealizeEffect(ICanvasEffect* effect) + static HRESULT InvalidateSourceRectangle( + ICanvasResourceCreatorWithDpi* resourceCreator, + IUnknown* image, + uint32_t sourceIndex, + Rect const* invalidRectangle) { - auto imageInternal = As(effect); - auto realizedEffect = imageInternal->GetD2DImage(m_device.Get(), m_deviceContext.Get(), m_flags, m_dpi); + return ExceptionBoundary( + [&] + { + CheckInPointer(image); + CheckInPointer(invalidRectangle); + + EffectRealizationContext realizationContext(resourceCreator); + + auto d2dEffect = realizationContext.RealizeEffect(image); + + if (sourceIndex >= d2dEffect->GetInputCount()) + ThrowHR(E_INVALIDARG); + + auto d2dInvalidRectangle = ToD2DRect(*invalidRectangle); + + ThrowIfFailed(realizationContext->InvalidateEffectInputRectangle(d2dEffect.Get(), sourceIndex, &d2dInvalidRectangle)); + }); + } + + static HRESULT GetInvalidRectangles( + ICanvasResourceCreatorWithDpi* resourceCreator, + IUnknown* image, + uint32_t* valueCount, + Rect** valueElements) + { + return ExceptionBoundary( + [&] + { + CheckInPointer(image); + CheckInPointer(valueCount); + CheckAndClearOutPointer(valueElements); + + EffectRealizationContext realizationContext(resourceCreator); + + auto d2dEffect = realizationContext.RealizeEffect(image); + + // Query the count. + UINT32 invalidRectangleCount; + + ThrowIfFailed(realizationContext->GetEffectInvalidRectangleCount(d2dEffect.Get(), &invalidRectangleCount)); + + // Query the rectangles. + std::vector result(invalidRectangleCount); + + ThrowIfFailed(realizationContext->GetEffectInvalidRectangles(d2dEffect.Get(), result.data(), invalidRectangleCount)); + + // Return the array. + auto resultArray = TransformToComArray(result.begin(), result.end(), FromD2DRect); + + resultArray.Detach(valueCount, valueElements); + }); + } + + // This method explicitly does not return an HRESULT, but instead just writes into a target buffer and throws if + // an error is encountered, requiring callers to use ExceptionBoundary themselves. This is different than the two + // APIs above, and it is done to give callers extra flexibility. Specifically, allowing them to pass an existing + // buffer (which can easily be done given the number of result is known, as it's just the number of source effects) + // means they can avoid the round-trip to a CoTaskMem-allocated buffer when they only want to fetch a single rectangle. + // Instead, they can just pass a buffer to a value on the stack and have this method write the result there. If instead + // they do want to return a COM array to callers, they can just preallocate the temporary D2D1_RECT_F vector and then + // convert that to the COM array to return on their own. + static void GetRequiredSourceRectangles( + ICanvasResourceCreatorWithDpi* resourceCreator, + IUnknown* 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, + D2D1_RECT_F* valueElements) + { + CheckInPointer(image); + CheckInPointer(outputRectangle); + CheckInPointer(sourceEffects); + CheckInPointer(sourceIndices); + CheckInPointer(sourceBounds); + CheckInPointer(valueElements); + + // All three source arrays must be the same size. + if (sourceEffectCount != sourceIndexCount || + sourceEffectCount != sourceBoundsCount || + sourceEffectCount != valueCount) + { + ThrowHR(E_INVALIDARG); + } + + EffectRealizationContext realizationContext(resourceCreator); + + auto d2dEffect = realizationContext.RealizeEffect(image); + + auto d2dOutputRectangle = ToD2DRect(*outputRectangle); + + // Convert parameter data to an array of D2D1_EFFECT_INPUT_DESCRIPTION structs. + std::vector inputDescriptions; + std::vector> keepAliveReferences; + + inputDescriptions.reserve(sourceEffectCount); + keepAliveReferences.reserve(sourceEffectCount); + + for (uint32_t i = 0; i < sourceEffectCount; i++) + { + CheckInPointer(sourceEffects[i]); + + auto d2dSourceEffect = realizationContext.RealizeEffect(sourceEffects[i]); + + if (sourceIndices[i] >= d2dSourceEffect->GetInputCount()) + ThrowHR(E_INVALIDARG); + + inputDescriptions.push_back(D2D1_EFFECT_INPUT_DESCRIPTION{ d2dSourceEffect.Get(), sourceIndices[i], ToD2DRect(sourceBounds[i]) }); + + keepAliveReferences.push_back(std::move(d2dSourceEffect)); + } + + // Query the input rectangles. + ThrowIfFailed(realizationContext->GetEffectRequiredInputRectangles(d2dEffect.Get(), &d2dOutputRectangle, inputDescriptions.data(), valueElements, sourceEffectCount)); + } + + private: + ComPtr RealizeEffect(IUnknown* effect) + { + auto realizedEffect = ICanvasImageInternal::GetD2DImageFromInternalOrInteropSource(effect, m_device.Get(), m_deviceContext.Get(), m_flags, m_dpi); return As(realizedEffect); } @@ -517,98 +672,15 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas { na IFACEMETHODIMP CanvasEffect::InvalidateSourceRectangle(ICanvasResourceCreatorWithDpi* resourceCreator, uint32_t sourceIndex, Rect invalidRectangle) { - return ExceptionBoundary( - [&] - { - EffectRealizationContext realizationContext(resourceCreator); - - auto d2dEffect = realizationContext.RealizeEffect(this); - - if (sourceIndex >= d2dEffect->GetInputCount()) - ThrowHR(E_INVALIDARG); - - auto d2dInvalidRectangle = ToD2DRect(invalidRectangle); - - ThrowIfFailed(realizationContext->InvalidateEffectInputRectangle(d2dEffect.Get(), sourceIndex, &d2dInvalidRectangle)); - }); + return EffectRealizationContext::InvalidateSourceRectangle(resourceCreator, reinterpret_cast(this), sourceIndex, &invalidRectangle); } IFACEMETHODIMP CanvasEffect::GetInvalidRectangles(ICanvasResourceCreatorWithDpi* resourceCreator, uint32_t* valueCount, Rect** valueElements) { - return ExceptionBoundary( - [&] - { - CheckInPointer(valueCount); - CheckAndClearOutPointer(valueElements); - - EffectRealizationContext realizationContext(resourceCreator); - - auto d2dEffect = realizationContext.RealizeEffect(this); - - // Query the count. - UINT32 invalidRectangleCount; - - ThrowIfFailed(realizationContext->GetEffectInvalidRectangleCount(d2dEffect.Get(), &invalidRectangleCount)); - - // Query the rectangles. - std::vector result(invalidRectangleCount); - - ThrowIfFailed(realizationContext->GetEffectInvalidRectangles(d2dEffect.Get(), result.data(), invalidRectangleCount)); - - // Return the array. - auto resultArray = TransformToComArray(result.begin(), result.end(), FromD2DRect); - - resultArray.Detach(valueCount, valueElements); - }); + return EffectRealizationContext::GetInvalidRectangles(resourceCreator, reinterpret_cast(this), valueCount, valueElements); } - - static std::vector GetRequiredSourceRectanglesImpl( - ICanvasEffect* effect, - ICanvasResourceCreatorWithDpi* resourceCreator, - Rect outputRectangle, - uint32_t sourceCount, - ICanvasEffect** sourceEffects, - uint32_t* sourceIndices, - Rect* sourceBounds) - { - EffectRealizationContext realizationContext(resourceCreator); - - auto d2dEffect = realizationContext.RealizeEffect(effect); - - auto d2dOutputRectangle = ToD2DRect(outputRectangle); - - // Convert parameter data to an array of D2D1_EFFECT_INPUT_DESCRIPTION structs. - std::vector inputDescriptions; - std::vector> keepAliveReferences; - - inputDescriptions.reserve(sourceCount); - keepAliveReferences.reserve(sourceCount); - - for (uint32_t i = 0; i < sourceCount; i++) - { - CheckInPointer(sourceEffects[i]); - - auto d2dSourceEffect = realizationContext.RealizeEffect(sourceEffects[i]); - - if (sourceIndices[i] >= d2dSourceEffect->GetInputCount()) - ThrowHR(E_INVALIDARG); - - inputDescriptions.push_back(D2D1_EFFECT_INPUT_DESCRIPTION{ d2dSourceEffect.Get(), sourceIndices[i], ToD2DRect(sourceBounds[i]) }); - - keepAliveReferences.push_back(std::move(d2dSourceEffect)); - } - - // Query the input rectangles. - std::vector result(sourceCount); - - ThrowIfFailed(realizationContext->GetEffectRequiredInputRectangles(d2dEffect.Get(), &d2dOutputRectangle, inputDescriptions.data(), result.data(), sourceCount)); - - return result; - } - - IFACEMETHODIMP CanvasEffect::GetRequiredSourceRectangle( ICanvasResourceCreatorWithDpi* resourceCreator, Rect outputRectangle, @@ -620,13 +692,31 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas { na return ExceptionBoundary( [&] { + // For this method and the ones below, we need to explicitly check the additional + // parameters these WinRT APIs expose (namely, the returned value pointer here, and + // the returned value array pointer in GetRequiredSourceRectangles below), as those + // two results are not directly computed by the shared stub. We can check that the + // input pointers are valid before invoking that stub, as all invalid arguments just + // throw E_INVALIDARG in case of failure, so the exact order they're checked isn't + // important. That is, checking the parameters "out of order" is not observable. CheckInPointer(value); - auto result = GetRequiredSourceRectanglesImpl(this, resourceCreator, outputRectangle, 1, &sourceEffect, &sourceIndex, &sourceBounds); + D2D1_RECT_F result; - assert(result.size() == 1); + EffectRealizationContext::GetRequiredSourceRectangles( + resourceCreator, + reinterpret_cast(this), + &outputRectangle, + 1, + &sourceEffect, + 1, + &sourceIndex, + 1, + &sourceBounds, + 1, + &result); - *value = FromD2DRect(result[0]); + *value = FromD2DRect(result); }); } @@ -646,20 +736,23 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas { na return ExceptionBoundary( [&] { - CheckInPointer(sourceEffects); - CheckInPointer(sourceIndices); - CheckInPointer(sourceBounds); CheckInPointer(valueCount); CheckAndClearOutPointer(valueElements); - // All three source arrays must be the same size. - if (sourceEffectCount != sourceIndexCount || - sourceEffectCount != sourceBoundsCount) - { - ThrowHR(E_INVALIDARG); - } + std::vector result(sourceEffectCount); - auto result = GetRequiredSourceRectanglesImpl(this, resourceCreator, outputRectangle, sourceEffectCount, sourceEffects, sourceIndices, sourceBounds); + EffectRealizationContext::GetRequiredSourceRectangles( + resourceCreator, + reinterpret_cast(this), + &outputRectangle, + sourceEffectCount, + sourceEffects, + sourceIndexCount, + sourceIndices, + sourceBoundsCount, + sourceBounds, + sourceEffectCount, + result.data()); auto resultArray = TransformToComArray(result.begin(), result.end(), FromD2DRect); @@ -667,6 +760,76 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas { na }); } + // + // Implementation of the public exports to support ICanvasEffect APIs for ICanvasImageInterop + // + +#if defined(ARCH_X86) +#pragma comment(linker, "/export:InvalidateSourceRectangleForICanvasImageInterop=_InvalidateSourceRectangleForICanvasImageInterop@16") +#endif + WIN2DAPI InvalidateSourceRectangleForICanvasImageInterop( + ICanvasResourceCreatorWithDpi* resourceCreator, + ICanvasImageInterop* image, + uint32_t sourceIndex, + Rect const* invalidRectangle) noexcept + { + return EffectRealizationContext::InvalidateSourceRectangle(resourceCreator, image, sourceIndex, invalidRectangle); + } + +#if defined(ARCH_X86) +#pragma comment(linker, "/export:GetInvalidRectanglesForICanvasImageInterop=_GetInvalidRectanglesForICanvasImageInterop@16") +#endif + WIN2DAPI GetInvalidRectanglesForICanvasImageInterop( + ICanvasResourceCreatorWithDpi* resourceCreator, + ICanvasImageInterop* image, + uint32_t* valueCount, + Rect** valueElements) noexcept + { + return EffectRealizationContext::GetInvalidRectangles(resourceCreator, image, valueCount, valueElements); + } + +#if defined(ARCH_X86) +#pragma comment(linker, "/export:GetRequiredSourceRectanglesForICanvasImageInterop=_GetRequiredSourceRectanglesForICanvasImageInterop@44") +#endif + WIN2DAPI 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) noexcept + { + return ExceptionBoundary( + [&] + { + // Same preemptive check for the output pointer, see notes in GetRequiredSourceRectangle above + CheckInPointer(valueElements); + + std::vector result(valueCount); + + // This API explicitly does not return a COM array, but instead allows callers to have it + // directly write the results into a buffer they own. See notes in EffectRealizationContext. + EffectRealizationContext::GetRequiredSourceRectangles( + resourceCreator, + image, + outputRectangle, + sourceEffectCount, + sourceEffects, + sourceIndexCount, + sourceIndices, + sourceBoundsCount, + sourceBounds, + valueCount, + result.data()); + + std::transform(result.begin(), result.end(), valueElements, FromD2DRect); + }); + } unsigned int CanvasEffect::GetSourceCount() { @@ -715,9 +878,9 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas { na // SetD2DInput options used by SetSource, InsertSource, and AppendSource. - const GetImageFlags SetSourceFlags = GetImageFlags::MinimalRealization | - GetImageFlags::AllowNullEffectInputs | - GetImageFlags::UnrealizeOnFailure; + const WIN2D_GET_D2D_IMAGE_FLAGS SetSourceFlags = WIN2D_GET_D2D_IMAGE_FLAGS_MINIMAL_REALIZATION | + WIN2D_GET_D2D_IMAGE_FLAGS_ALLOW_NULL_EFFECT_INPUTS | + WIN2D_GET_D2D_IMAGE_FLAGS_UNREALIZE_ON_FAILURE; void CanvasEffect::SetSource(unsigned int index, IGraphicsEffectSource* source) @@ -909,14 +1072,14 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas { na } - bool CanvasEffect::ApplyDpiCompensation(unsigned int index, ComPtr& inputImage, float inputDpi, GetImageFlags flags, float targetDpi, ID2D1DeviceContext* deviceContext) + bool CanvasEffect::ApplyDpiCompensation(unsigned int index, ComPtr& inputImage, float inputDpi, WIN2D_GET_D2D_IMAGE_FLAGS flags, float targetDpi, ID2D1DeviceContext* deviceContext) { auto& dpiCompensator = m_sources[index].DpiCompensator; bool hasDpiCompensation = (dpiCompensator != nullptr); bool needsDpiCompensation; - if ((flags & GetImageFlags::MinimalRealization) != GetImageFlags::None) + if ((flags & WIN2D_GET_D2D_IMAGE_FLAGS_MINIMAL_REALIZATION) != WIN2D_GET_D2D_IMAGE_FLAGS_NONE) { // In minimal mode, we don't yet know the target DPI. For instance this occurs when setting an // effect as the source of an image brush. We'll fix up later when the brush is drawn to a device @@ -932,8 +1095,8 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas { na // - we were not told never to include it // - either we were told to always include it, or the input DPI is different from the target - bool neverCompensate = (flags & GetImageFlags::NeverInsertDpiCompensation) != GetImageFlags::None; - bool alwaysCompensate = (flags & GetImageFlags::AlwaysInsertDpiCompensation) != GetImageFlags::None; + bool neverCompensate = (flags & WIN2D_GET_D2D_IMAGE_FLAGS_NEVER_INSERT_DPI_COMPENSATION) != WIN2D_GET_D2D_IMAGE_FLAGS_NONE; + bool alwaysCompensate = (flags & WIN2D_GET_D2D_IMAGE_FLAGS_ALWAYS_INSERT_DPI_COMPENSATION) != WIN2D_GET_D2D_IMAGE_FLAGS_NONE; needsDpiCompensation = (inputDpi != 0) && !neverCompensate && @@ -973,7 +1136,7 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas { na } - void CanvasEffect::RefreshInputs(GetImageFlags flags, float targetDpi, ID2D1DeviceContext* deviceContext) + void CanvasEffect::RefreshInputs(WIN2D_GET_D2D_IMAGE_FLAGS flags, float targetDpi, ID2D1DeviceContext* deviceContext) { auto& d2dEffect = GetResource(); @@ -988,14 +1151,14 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas { na if (!source) { - if ((flags & GetImageFlags::AllowNullEffectInputs) == GetImageFlags::None) + if ((flags & WIN2D_GET_D2D_IMAGE_FLAGS_ALLOW_NULL_EFFECT_INPUTS) == WIN2D_GET_D2D_IMAGE_FLAGS_NONE) ThrowFormattedMessage(E_INVALIDARG, Strings::EffectNullSource, i); } else { // Get the underlying D2D interface. This call recurses through the effect graph. float realizedDpi; - auto realizedSource = As(source)->GetD2DImage(RealizationDevice(), deviceContext, flags, targetDpi, &realizedDpi); + auto realizedSource = ICanvasImageInternal::GetD2DImageFromInternalOrInteropSource(source.Get(), RealizationDevice(), deviceContext, flags, targetDpi, &realizedDpi); bool resourceChanged = sourceInfo.UpdateResource(realizedSource.Get()); @@ -1011,53 +1174,114 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas { na } - bool CanvasEffect::SetD2DInput(ID2D1Effect* d2dEffect, unsigned int index, IGraphicsEffectSource* source, GetImageFlags flags, float targetDpi, ID2D1DeviceContext* deviceContext) + bool CanvasEffect::SetD2DInput(ID2D1Effect* d2dEffect, unsigned int index, IGraphicsEffectSource* source, WIN2D_GET_D2D_IMAGE_FLAGS flags, float targetDpi, ID2D1DeviceContext* deviceContext) { ComPtr realizedSource; float realizedDpi = 0; if (source) { - // Make sure the specified source is an ICanvasImage. + // Check if the specified source is an ICanvasImage. There are two possible scenarios to + // handle: ICanvasImageInternal (a Win2D effect) or ICanvasImageInterop (an external effect). ComPtr internalSource; + ComPtr interopSource; HRESULT hr = source->QueryInterface(IID_PPV_ARGS(&internalSource)); if (FAILED(hr)) { + // If QueryInterface failed in any way other than E_NOINTERFACE, we just rethrow. if (hr != E_NOINTERFACE) ThrowHR(hr); - if ((flags & GetImageFlags::UnrealizeOnFailure) == GetImageFlags::None) - ThrowFormattedMessage(E_NOINTERFACE, Strings::EffectWrongSourceType, index); + // If the input source is not an ICanvasImage, we now check to see if it's an ICanvasImageInterop. This is + // the only case other than ICanvasImage where setting the source is valid and we don't need to unrealize. + hr = source->QueryInterface(IID_PPV_ARGS(&interopSource)); - // If the source is not an ICanvasImage (eg. setting a Windows.UI.Composition resource), we must unrealize. - Unrealize(index); - m_sources[index] = source; - return false; - } - - // If the specified source has an associated device, validate that this matches the one we are realized on. - auto sourceWithDevice = MaybeAs(source); - - if (sourceWithDevice) - { - ComPtr sourceDevice; - ThrowIfFailed(sourceWithDevice->get_Device(&sourceDevice)); - - if (!IsSameInstance(RealizationDevice(), sourceDevice.Get())) + if (FAILED(hr)) { - if ((flags & GetImageFlags::UnrealizeOnFailure) == GetImageFlags::None) - ThrowFormattedMessage(E_INVALIDARG, Strings::EffectWrongDevice, index); - - // If the source is on a different device, we must unrealize. + if (hr != E_NOINTERFACE) + ThrowHR(hr); + + if ((flags & WIN2D_GET_D2D_IMAGE_FLAGS_UNREALIZE_ON_FAILURE) == WIN2D_GET_D2D_IMAGE_FLAGS_NONE) + ThrowFormattedMessage(E_NOINTERFACE, Strings::EffectWrongSourceType, index); + + // If the source is not an ICanvasImage (eg. setting a Windows.UI.Composition resource), we must unrealize. Unrealize(index); m_sources[index] = source; return false; } } + ComPtr sourceDevice; + bool sourceDeviceMustBeExactMatch; + + if (internalSource) + { + // If the specified source is bound to a creation device, validate that this matches the one we are realized on. + // A "creation device" is a canvas device that is used to create the native resources in a given canvas image + // implementation, such that the canvas device and the canvas image are bound for the entire lifetime of the + // canvas image itself. This applies to eg. CanvasBitmap, which directly wraps some native memory allocated on + // a specific device. In these cases, if the associated device doesn't match the one this effect is currently + // realized on, that is an error and the source is invalid. + // + // Note: this path is only taken if the source is an ICanvasImageInternal, as the ICanvasResourceWrapperWithDevice + // interface is internal. That is, if the source is an external effect, there is no way it could implement this. + auto sourceWithDevice = MaybeAs(source); + + if (sourceWithDevice) + { + // If the input is an ICanvasResourceWrapperWithDevice, get the current device from there. + ThrowIfFailed(sourceWithDevice->get_Device(&sourceDevice)); + + // As mentioned above, if the source is bound to a creation device, it must match the realization device. + sourceDeviceMustBeExactMatch = true; + } + else + { + // Otherwise, the source can be unrealized if it's currently using another device (eg. it's a CanvasEffect). + // As such, its underlying device could be null or a different one, and both cases are valid here. + sourceDeviceMustBeExactMatch = false; + } + } + else + { + WIN2D_GET_DEVICE_ASSOCIATION_TYPE deviceInfo; + + // Otherwise (the source is an ICanvasImageInterop), get the device from there. The device returned here will + // depend on the specific implementation of this external effect. That is, it could be null for an unrealized + // effect, it could be null for a lazily initialized device-bound resource, or it could be an already instantiated + // resource tied to a specific device. We can check that via the returned association type value. + ThrowIfFailed(interopSource->GetDevice(&sourceDevice, &deviceInfo)); + + // The only case where the device has to be an exact match is if the device is an owning device for the canvas image. + // In all other cases (including when the device is null), we can proceed to resolve the effects graph normally. + sourceDeviceMustBeExactMatch = deviceInfo == WIN2D_GET_DEVICE_ASSOCIATION_TYPE_CREATION_DEVICE; + } + + // If required by the source, as described above, validate that the current realization device matches the source associated device. + if (sourceDeviceMustBeExactMatch && !IsSameInstance(RealizationDevice(), sourceDevice.Get())) + { + if ((flags & WIN2D_GET_D2D_IMAGE_FLAGS_UNREALIZE_ON_FAILURE) == WIN2D_GET_D2D_IMAGE_FLAGS_NONE) + ThrowFormattedMessage(E_INVALIDARG, Strings::EffectWrongDevice, index); + + // If the source is on a different device, we must unrealize. + Unrealize(index); + m_sources[index] = source; + return false; + } + // Get the underlying D2D interface. This call recurses through the effect graph. - realizedSource = internalSource->GetD2DImage(RealizationDevice(), deviceContext, flags, targetDpi, &realizedDpi); + if (internalSource) + { + realizedSource = internalSource->GetD2DImage(RealizationDevice(), deviceContext, flags, targetDpi, &realizedDpi); + } + else + { + hr = interopSource->GetD2DImage(RealizationDevice(), deviceContext, flags, targetDpi, &realizedDpi, &realizedSource); + + if ((flags & WIN2D_GET_D2D_IMAGE_FLAGS_UNREALIZE_ON_FAILURE) == WIN2D_GET_D2D_IMAGE_FLAGS_NONE) + ThrowIfFailed(hr); + } if (!realizedSource) { @@ -1069,7 +1293,7 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas { na else { // Source is null. - if ((flags & GetImageFlags::AllowNullEffectInputs) == GetImageFlags::None) + if ((flags & WIN2D_GET_D2D_IMAGE_FLAGS_ALLOW_NULL_EFFECT_INPUTS) == WIN2D_GET_D2D_IMAGE_FLAGS_NONE) ThrowFormattedMessage(E_INVALIDARG, Strings::EffectNullSource, index); } @@ -1309,7 +1533,7 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas { na } - bool CanvasEffect::Realize(GetImageFlags flags, float targetDpi, ID2D1DeviceContext* deviceContext) + bool CanvasEffect::Realize(WIN2D_GET_D2D_IMAGE_FLAGS flags, float targetDpi, ID2D1DeviceContext* deviceContext) { assert(!HasResource()); diff --git a/winrt/lib/effects/CanvasEffect.h b/winrt/lib/effects/CanvasEffect.h index 2ec503a8..5d1d8952 100644 --- a/winrt/lib/effects/CanvasEffect.h +++ b/winrt/lib/effects/CanvasEffect.h @@ -37,7 +37,7 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas { na IGraphicsEffectD2D1Interop, ICanvasEffect, ICanvasImage, - CloakedIid, + ChainInterfaces, CloakedIid>, ChainInterfaces< MixIn>, IClosable, @@ -151,11 +151,17 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas { na IFACEMETHOD(GetBounds)(ICanvasResourceCreator* resourceCreator, Rect *bounds) override; IFACEMETHOD(GetBoundsWithTransform)(ICanvasResourceCreator* resourceCreator, Numerics::Matrix3x2 transform, Rect *bounds) override; + // + // ICanvasImageInterop + // + + IFACEMETHOD(GetDevice)(ICanvasDevice** device, WIN2D_GET_DEVICE_ASSOCIATION_TYPE* type) override; + // // ICanvasImageInternal // - virtual ComPtr GetD2DImage(ICanvasDevice* device, ID2D1DeviceContext* deviceContext, GetImageFlags flags, float targetDpi, float* realizedDpi = nullptr) override; + virtual ComPtr GetD2DImage(ICanvasDevice* device, ID2D1DeviceContext* deviceContext, WIN2D_GET_D2D_IMAGE_FLAGS flags, float targetDpi, float* realizedDpi = nullptr) override; // // ICanvasResourceWrapperNative @@ -270,15 +276,15 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas { na // On-demand creation of the underlying D2D image effect. - virtual bool Realize(GetImageFlags flags, float targetDpi, ID2D1DeviceContext* deviceContext); + virtual bool Realize(WIN2D_GET_D2D_IMAGE_FLAGS flags, float targetDpi, ID2D1DeviceContext* deviceContext); virtual void Unrealize(unsigned int skipSourceIndex = UINT_MAX, bool skipAllSources = false); private: ComPtr CreateD2DEffect(ID2D1DeviceContext* deviceContext, IID const& effectId); - bool ApplyDpiCompensation(unsigned int index, ComPtr& inputImage, float inputDpi, GetImageFlags flags, float targetDpi, ID2D1DeviceContext* deviceContext); - void RefreshInputs(GetImageFlags flags, float targetDpi, ID2D1DeviceContext* deviceContext); + bool ApplyDpiCompensation(unsigned int index, ComPtr& inputImage, float inputDpi, WIN2D_GET_D2D_IMAGE_FLAGS flags, float targetDpi, ID2D1DeviceContext* deviceContext); + void RefreshInputs(WIN2D_GET_D2D_IMAGE_FLAGS flags, float targetDpi, ID2D1DeviceContext* deviceContext); - bool SetD2DInput(ID2D1Effect* d2dEffect, unsigned int index, IGraphicsEffectSource* source, GetImageFlags flags, float targetDpi = 0, ID2D1DeviceContext* deviceContext = nullptr); + bool SetD2DInput(ID2D1Effect* d2dEffect, unsigned int index, IGraphicsEffectSource* source, WIN2D_GET_D2D_IMAGE_FLAGS flags, float targetDpi = 0, ID2D1DeviceContext* deviceContext = nullptr); ComPtr GetD2DInput(ID2D1Effect* d2dEffect, unsigned int index); void SetProperty(unsigned int index, IPropertyValue* propertyValue); diff --git a/winrt/lib/effects/shader/PixelShaderEffect.cpp b/winrt/lib/effects/shader/PixelShaderEffect.cpp index 485b99fe..26428771 100644 --- a/winrt/lib/effects/shader/PixelShaderEffect.cpp +++ b/winrt/lib/effects/shader/PixelShaderEffect.cpp @@ -216,7 +216,7 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas { na } - bool PixelShaderEffect::Realize(GetImageFlags flags, float targetDpi, ID2D1DeviceContext* deviceContext) + bool PixelShaderEffect::Realize(WIN2D_GET_D2D_IMAGE_FLAGS flags, float targetDpi, ID2D1DeviceContext* deviceContext) { // Validate that this device supports the D3D feature level of the pixel shader. if (!IsSupported(RealizationDevice())) diff --git a/winrt/lib/effects/shader/PixelShaderEffect.h b/winrt/lib/effects/shader/PixelShaderEffect.h index 74e6476c..6760b8a9 100644 --- a/winrt/lib/effects/shader/PixelShaderEffect.h +++ b/winrt/lib/effects/shader/PixelShaderEffect.h @@ -88,7 +88,7 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas { na protected: bool IsSupported(ICanvasDevice* device); - virtual bool Realize(GetImageFlags flags, float targetDpi, ID2D1DeviceContext* deviceContext) override; + virtual bool Realize(WIN2D_GET_D2D_IMAGE_FLAGS flags, float targetDpi, ID2D1DeviceContext* deviceContext) override; virtual void Unrealize(unsigned int skipSourceIndex, bool skipAllSources) override; IFACEMETHOD(GetSource)(unsigned int index, IGraphicsEffectSource** source) override; diff --git a/winrt/lib/images/CanvasBitmap.h b/winrt/lib/images/CanvasBitmap.h index 48b279ce..29c1757c 100644 --- a/winrt/lib/images/CanvasBitmap.h +++ b/winrt/lib/images/CanvasBitmap.h @@ -337,7 +337,7 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas RuntimeClassFlags, ICanvasResourceCreator, ICanvasResourceCreatorWithDpi, - CloakedIid, + ChainInterfaces, CloakedIid>, CloakedIid, CloakedIid, CloakedIid>, @@ -446,7 +446,7 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas return ExceptionBoundary( [&] { - CheckInPointer(value); + CheckAndClearOutPointer(value); ThrowIfFailed(m_device.CopyTo(value)); }); } @@ -492,8 +492,29 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas }); } + // + // ICanvasImageInterop + // + + IFACEMETHODIMP GetDevice(ICanvasDevice** device, WIN2D_GET_DEVICE_ASSOCIATION_TYPE* type) override + { + return ExceptionBoundary( + [&] + { + CheckAndClearOutPointer(device); + CheckInPointer(type); + + *type = WIN2D_GET_DEVICE_ASSOCIATION_TYPE_UNSPECIFIED; + + ThrowIfFailed(m_device.CopyTo(device)); + + // A canvas bitmap is uniquely owned by its creation device. + *type = WIN2D_GET_DEVICE_ASSOCIATION_TYPE_CREATION_DEVICE; + }); + } + // ICanvasImageInternal - virtual ComPtr GetD2DImage(ICanvasDevice*, ID2D1DeviceContext*, GetImageFlags, float /*targetDpi*/, float* realizedDpi) override + virtual ComPtr GetD2DImage(ICanvasDevice*, ID2D1DeviceContext*, WIN2D_GET_D2D_IMAGE_FLAGS, float /*targetDpi*/, float* realizedDpi) override { if (realizedDpi) *realizedDpi = m_dpi; diff --git a/winrt/lib/images/CanvasCommandList.cpp b/winrt/lib/images/CanvasCommandList.cpp index a3c01e6e..ae3c7607 100644 --- a/winrt/lib/images/CanvasCommandList.cpp +++ b/winrt/lib/images/CanvasCommandList.cpp @@ -94,7 +94,7 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas return ExceptionBoundary( [&] { - CheckInPointer(value); + CheckAndClearOutPointer(value); auto& device = m_device.EnsureNotClosed(); ThrowIfFailed(device.CopyTo(value)); @@ -108,6 +108,32 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas return __super::Close(); } + // + // ICanvasImageInterop + // + + IFACEMETHODIMP CanvasCommandList::GetDevice(ICanvasDevice** device, WIN2D_GET_DEVICE_ASSOCIATION_TYPE* type) + { + return ExceptionBoundary( + [&] + { + CheckAndClearOutPointer(device); + CheckInPointer(type); + + *type = WIN2D_GET_DEVICE_ASSOCIATION_TYPE_UNSPECIFIED; + + auto& localDevice = m_device.EnsureNotClosed(); + ThrowIfFailed(localDevice.CopyTo(device)); + + // Just like bitmaps, command lists are uniquely owned by a device that cannot change. + *type = WIN2D_GET_DEVICE_ASSOCIATION_TYPE_CREATION_DEVICE; + }); + } + + // + // ICanvasImage + // + IFACEMETHODIMP CanvasCommandList::GetBounds( ICanvasResourceCreator* resourceCreator, @@ -147,7 +173,7 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas ComPtr CanvasCommandList::GetD2DImage( ICanvasDevice* device, ID2D1DeviceContext* deviceContext, - GetImageFlags, + WIN2D_GET_D2D_IMAGE_FLAGS, float /*targetDpi*/, float* realizedDpi) { diff --git a/winrt/lib/images/CanvasCommandList.h b/winrt/lib/images/CanvasCommandList.h index 7646d847..1fa66505 100644 --- a/winrt/lib/images/CanvasCommandList.h +++ b/winrt/lib/images/CanvasCommandList.h @@ -11,7 +11,7 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas CanvasCommandList, ICanvasCommandList, ICanvasImage, - CloakedIid, + ChainInterfaces, CloakedIid>, CloakedIid, Effects::IGraphicsEffectSource) { @@ -54,13 +54,18 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas Numerics::Matrix3x2 transform, Rect* bounds) override; + // + // ICanvasImageInterop + // + + IFACEMETHOD(GetDevice)(ICanvasDevice** device, WIN2D_GET_DEVICE_ASSOCIATION_TYPE* type) override; // ICanvasImageInternal virtual ComPtr GetD2DImage( ICanvasDevice* device, ID2D1DeviceContext* deviceContext, - GetImageFlags flags, + WIN2D_GET_D2D_IMAGE_FLAGS flags, float targetDpi, float* realizedDpi) override; diff --git a/winrt/lib/images/CanvasImage.cpp b/winrt/lib/images/CanvasImage.cpp index 88f63ac7..ca81a30d 100644 --- a/winrt/lib/images/CanvasImage.cpp +++ b/winrt/lib/images/CanvasImage.cpp @@ -23,17 +23,33 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas return As(device)->GetResourceCreationDeviceContext(); } + template static Rect GetImageBoundsImpl( - ICanvasImageInternal* imageInternal, + T* image, ICanvasResourceCreator* resourceCreator, Numerics::Matrix3x2 const* transform) { + // This method is only allowed when T is ICanvasImageInterop or a derived type. + // This includes both ICanvasImageInterop (external effects) and ICanvasImageInternal. + static_assert(std::is_base_of::value); + ComPtr device; ThrowIfFailed(resourceCreator->get_Device(&device)); auto d2dDeviceContext = GetDeviceContextForGetBounds(device.Get(), resourceCreator); - auto d2dImage = imageInternal->GetD2DImage(device.Get(), d2dDeviceContext.Get()); + ComPtr d2dImage; + + // Check if T is ICanvasImageInternal. If so, we can call GetD2DImage without a QueryInterface call. + if constexpr (std::is_same::value) + { + d2dImage = image->GetD2DImage(device.Get(), d2dDeviceContext.Get()); + } + else + { + // If not, use the shared helper to get a D2D image from all supported image types. + d2dImage = ICanvasImageInternal::GetD2DImageFromInternalOrInteropSource(image, device.Get(), d2dDeviceContext.Get()); + } D2D1_MATRIX_3X2_F previousTransform; d2dDeviceContext->GetTransform(&previousTransform); @@ -67,6 +83,32 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas }); } + // Static helper for forwarding GetImageBoundsImpl support to ICanvasImageInterop consumers. + // This is the implementation of the public GetBoundsForICanvasImageInterop exported function. + +#if defined(ARCH_X86) +#pragma comment(linker, "/export:GetBoundsForICanvasImageInterop=_GetBoundsForICanvasImageInterop@16") +#endif + WIN2DAPI GetBoundsForICanvasImageInterop( + ICanvasResourceCreator* resourceCreator, + ICanvasImageInterop* image, + Numerics::Matrix3x2 const* transform, + Rect* bounds) noexcept + { + if (!transform) + transform = &Identity3x2(); + + return ExceptionBoundary( + [&] + { + CheckInPointer(image); + CheckInPointer(resourceCreator); + CheckInPointer(bounds); + + *bounds = GetImageBoundsImpl(image, resourceCreator, transform); + }); + } + // // CanvasImageFactory @@ -198,7 +240,7 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas auto canvasDevice = GetCanvasDevice(resourceCreator); auto d2dDevice = GetWrappedResource(canvasDevice); - auto d2dImage = As(image)->GetD2DImage(canvasDevice.Get(), nullptr, GetImageFlags::None, dpi); + auto d2dImage = ICanvasImageInternal::GetD2DImageFromInternalOrInteropSource(image, canvasDevice.Get(), nullptr, WIN2D_GET_D2D_IMAGE_FLAGS_NONE, dpi); auto adapter = m_adapter; auto istream = adapter->CreateStreamOverRandomAccessStream(stream); @@ -260,7 +302,7 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas // Configure the atlas effect to select what region of the source image we want to feed into the histogram. float realizedDpi; - auto d2dImage = As(image)->GetD2DImage(device.Get(), deviceContext.Get(), GetImageFlags::None, DEFAULT_DPI, &realizedDpi); + auto d2dImage = ICanvasImageInternal::GetD2DImageFromInternalOrInteropSource(image, device.Get(), deviceContext.Get(), WIN2D_GET_D2D_IMAGE_FLAGS_NONE, DEFAULT_DPI, &realizedDpi); if (realizedDpi != 0 && realizedDpi != DEFAULT_DPI) { diff --git a/winrt/lib/images/CanvasImage.h b/winrt/lib/images/CanvasImage.h index 2933009a..ada2d147 100644 --- a/winrt/lib/images/CanvasImage.h +++ b/winrt/lib/images/CanvasImage.h @@ -15,39 +15,41 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas using namespace ABI::Windows::Storage::Streams; - // Options for fine-tuning the behavior of ICanvasImageInternal::GetD2DImage. - enum class GetImageFlags - { - None = 0, - ReadDpiFromDeviceContext = 1, // Ignore the targetDpi parameter - read DPI from deviceContext instead - AlwaysInsertDpiCompensation = 2, // Ignore the targetDpi parameter - always insert DPI compensation - NeverInsertDpiCompensation = 4, // Ignore the targetDpi parameter - never insert DPI compensation - MinimalRealization = 8, // Do the bare minimum to get back an ID2D1Image - no validation or recursive realization - AllowNullEffectInputs = 16, // Allow partially configured effect graphs where some inputs are null - UnrealizeOnFailure = 32, // If an input is invalid, unrealize the effect and return null rather than throwing - }; - - DEFINE_ENUM_FLAG_OPERATORS(GetImageFlags) - - class __declspec(uuid("2F434224-053C-4978-87C4-CFAAFA2F4FAC")) - ICanvasImageInternal : public IUnknown + ICanvasImageInternal : public ICanvasImageInterop { public: + IFACEMETHODIMP GetD2DImage( + ICanvasDevice* device, + ID2D1DeviceContext* deviceContext, + WIN2D_GET_D2D_IMAGE_FLAGS flags, + float targetDpi, + float* realizeDpi, + ID2D1Image** ppImage) override + { + // GetD2DImage is exposed as a COM interface to external users, so make sure exceptions never cross the ABI boundary. + return ExceptionBoundary([&] + { + auto d2dImage = GetD2DImage(device, deviceContext, flags, targetDpi, realizeDpi); + + ThrowIfFailed(d2dImage.CopyTo(ppImage)); + }); + } + // For bitmaps and command lists, GetD2DImage is a trivial getter. // For effects it triggers on-demand realization, which requires extra information. // // The device parameter is required, while deviceContext is optional (but recommended) - // except when the ReadDpiFromDeviceContext flag is specified. This is because not all - // callers of GetD2DImage have easy access to a context. It is always possible to get a - // resource creation context from the device, but the context is only actually necessary - // if a new effect realization needs to be created, so it is more efficient to have the + // except when the WIN2D_GET_D2D_IMAGE_FLAGS_READ_DPI_FROM_DEVICE_CONTEXT flag is specified. + // This is because not all callers of GetD2DImage have easy access to a context. It is always + // possible to get a resource creation context from the device, but the context is only actually + // necessary if a new effect realization needs to be created, so it is more efficient to have the // implementation do this lookup only if/when it turns out to be needed. // // targetDpi passes in the DPI of the target device context. This is used to // determine when a D2D1DpiCompensation effect needs to be inserted. Behavior of - // this parameter can be overridden by the flag values ReadDpiFromDeviceContext, - // AlwaysInsertDpiCompensation, or NeverInsertDpiCompensation. + // this parameter can be overridden by the flag values WIN2D_GET_D2D_IMAGE_FLAGS_READ_DPI_FROM_DEVICE_CONTEXT, + // WIN2D_GET_D2D_IMAGE_FLAGS_ALWAYS_INSERT_DPI_COMPENSATION, or WIN2D_GET_D2D_IMAGE_FLAGS_NEVER_INSERT_DPI_COMPENSATION. // // realizedDpi returns the DPI of a source bitmap, or zero if the image does not // have a fixed DPI. A D2D1DpiCompensation effect will be inserted if targetDpi @@ -56,11 +58,55 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas virtual ComPtr GetD2DImage( ICanvasDevice* device, ID2D1DeviceContext* deviceContext, - GetImageFlags flags = GetImageFlags::ReadDpiFromDeviceContext, + WIN2D_GET_D2D_IMAGE_FLAGS flags = WIN2D_GET_D2D_IMAGE_FLAGS_READ_DPI_FROM_DEVICE_CONTEXT, float targetDpi = 0, float* realizedDpi = nullptr) = 0; - }; + // Static helper for internal callers to invoke GetD2DImage on multiple input types. That is, this method + // handles not just ICanvasImageInternal sources, but ICanvasImageInterop source as well. This way, callers + // don't have to worry about the second interop interface, and can just get an image from a single code path. + + static ComPtr GetD2DImageFromInternalOrInteropSource( + IUnknown* canvasImage, + ICanvasDevice* device, + ID2D1DeviceContext* deviceContext, + WIN2D_GET_D2D_IMAGE_FLAGS flags = WIN2D_GET_D2D_IMAGE_FLAGS_READ_DPI_FROM_DEVICE_CONTEXT, + float targetDpi = 0, + float* realizedDpi = nullptr) + { + // The input is an IUnknown* object, where callers pass in either ICanvasImage* or IGraphicsEffectSource. + // There are two possible cases for the input image being retrieved: + // - It is one of the built-in Win2D effects, so it will implement ICanvasImageInternal. + // - It is a custom Win2D-compatible effect, so it will implement ICanvasImageInterop. + ComPtr d2dImage; + ComPtr internalSource; + + // Check if the specified source is an ICanvasImageInternal (this is the most common case). + HRESULT hr = canvasImage->QueryInterface(IID_PPV_ARGS(&internalSource)); + + // If QueryInterface failed in any way other than E_NOINTERFACE, we just rethrow (just like in CanvasEffect::SetD2DInput). + if (FAILED(hr) && hr != E_NOINTERFACE) + ThrowHR(hr); + + // If the input was indeed an ICanvasImageInternal, invoke its GetD2DImage method normally. + if (SUCCEEDED(hr)) + { + d2dImage = internalSource->GetD2DImage(device, deviceContext, flags, targetDpi, realizedDpi); + } + else + { + // If the source is not an ICanvasImageInternal, it must be an ICanvasImageInterop object. + hr = As(canvasImage)->GetD2DImage(device, deviceContext, flags, targetDpi, realizedDpi, &d2dImage); + + // To match the behavior of ICanvasImageInternal::GetD2DImage in case of failure, check if the flags being used did have the flag + // WIN2D_GET_D2D_IMAGE_FLAGS_UNREALIZE_ON_FAILURE set. If not, and the call failed, then we explicitly throw from the returned HRESULT. + if ((flags & WIN2D_GET_D2D_IMAGE_FLAGS_UNREALIZE_ON_FAILURE) == WIN2D_GET_D2D_IMAGE_FLAGS_NONE) + ThrowIfFailed(hr); + } + + return d2dImage; + } + }; HRESULT GetImageBoundsImpl( ICanvasImageInternal* imageInternal, diff --git a/winrt/lib/images/CanvasVirtualBitmap.cpp b/winrt/lib/images/CanvasVirtualBitmap.cpp index b4c9c056..8e7a21b2 100644 --- a/winrt/lib/images/CanvasVirtualBitmap.cpp +++ b/winrt/lib/images/CanvasVirtualBitmap.cpp @@ -378,11 +378,32 @@ IFACEMETHODIMP CanvasVirtualBitmap::get_Device(ICanvasDevice** value) return ExceptionBoundary( [&] { - CheckInPointer(value); + CheckAndClearOutPointer(value); ThrowIfFailed(m_device.CopyTo(value)); }); } +// +// ICanvasImageInterop +// + +IFACEMETHODIMP CanvasVirtualBitmap::GetDevice(ICanvasDevice** device, WIN2D_GET_DEVICE_ASSOCIATION_TYPE* type) +{ + return ExceptionBoundary( + [&] + { + CheckAndClearOutPointer(device); + CheckInPointer(type); + + *type = WIN2D_GET_DEVICE_ASSOCIATION_TYPE_UNSPECIFIED; + + ThrowIfFailed(m_device.CopyTo(device)); + + // Just like a normal bitmap, a virtualized bitmap is also uniquely tied to its owning device. + *type = WIN2D_GET_DEVICE_ASSOCIATION_TYPE_CREATION_DEVICE; + }); +} + IFACEMETHODIMP CanvasVirtualBitmap::get_IsCachedOnDemand(boolean* value) { @@ -462,7 +483,7 @@ IFACEMETHODIMP CanvasVirtualBitmap::GetBoundsWithTransform(ICanvasResourceCreato } -ComPtr CanvasVirtualBitmap::GetD2DImage(ICanvasDevice* , ID2D1DeviceContext*, GetImageFlags, float, float* realizedDpi) +ComPtr CanvasVirtualBitmap::GetD2DImage(ICanvasDevice* , ID2D1DeviceContext*, WIN2D_GET_D2D_IMAGE_FLAGS, float, float* realizedDpi) { if (realizedDpi) *realizedDpi = 0; diff --git a/winrt/lib/images/CanvasVirtualBitmap.h b/winrt/lib/images/CanvasVirtualBitmap.h index 015748ee..2bfd6fc5 100644 --- a/winrt/lib/images/CanvasVirtualBitmap.h +++ b/winrt/lib/images/CanvasVirtualBitmap.h @@ -85,7 +85,7 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas ICanvasVirtualBitmap, ICanvasImage, IGraphicsEffectSource, - CloakedIid, + ChainInterfaces, CloakedIid>, CloakedIid) { InspectableClass(RuntimeClass_Microsoft_Graphics_Canvas_CanvasVirtualBitmap, BaseTrust); @@ -127,8 +127,14 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas IFACEMETHODIMP GetBounds(ICanvasResourceCreator*, Rect*) override; IFACEMETHODIMP GetBoundsWithTransform(ICanvasResourceCreator*, Matrix3x2, Rect*) override; + // + // ICanvasImageInterop + // + + IFACEMETHODIMP GetDevice(ICanvasDevice** device, WIN2D_GET_DEVICE_ASSOCIATION_TYPE* type) override; + // ICanvasImageInternal - ComPtr GetD2DImage(ICanvasDevice* , ID2D1DeviceContext*, GetImageFlags, float, float*) override; + ComPtr GetD2DImage(ICanvasDevice* , ID2D1DeviceContext*, WIN2D_GET_D2D_IMAGE_FLAGS, float, float*) override; }; }}}} diff --git a/winrt/lib/pch.h b/winrt/lib/pch.h index 84eea3b1..db180dff 100644 --- a/winrt/lib/pch.h +++ b/winrt/lib/pch.h @@ -14,6 +14,16 @@ #define NOMINMAX // Stop Windows from defining min() and max() macros that break STL #endif +#ifndef WIN2D_DLL_EXPORT +#define WIN2D_DLL_EXPORT // Mark public C APIs as being exported (whereas external consumers will import them) +#endif + +#if defined(_M_IX86) && defined(_MSC_VER) +#ifndef ARCH_X86 +#define ARCH_X86 // Used to detect the x86 architecture so fixups for C exports can be added +#endif +#endif + #include // Undef GetCurrentTime because the Win32 API in windows.h collides with Storyboard.GetCurrentTime diff --git a/winrt/lib/winrt.lib.uap.vcxproj b/winrt/lib/winrt.lib.uap.vcxproj index db18dae9..055b8d53 100644 --- a/winrt/lib/winrt.lib.uap.vcxproj +++ b/winrt/lib/winrt.lib.uap.vcxproj @@ -46,6 +46,7 @@ ..\Inc;$(EtwDirectory);%(AdditionalIncludeDirectories) ENABLE_WINRT_EXPERIMENTAL_TYPES=1;WRT_EXPORT;WINUI3;WIN2D_VERSION="$(Win2DVersion)";%(PreprocessorDefinitions) Level4 + stdcpp17 Console diff --git a/winrt/published/Microsoft.Graphics.Canvas.native.h b/winrt/published/Microsoft.Graphics.Canvas.native.h index 70c8195c..00c3a410 100644 --- a/winrt/published/Microsoft.Graphics.Canvas.native.h +++ b/winrt/published/Microsoft.Graphics.Canvas.native.h @@ -5,8 +5,19 @@ #pragma once #include +#include #include +#ifndef __cplusplus +#error "Requires C++" +#endif + +#ifdef WIN2D_DLL_EXPORT +#define WIN2DAPI extern "C" __declspec(dllexport) HRESULT __stdcall +#else +#define WIN2DAPI extern "C" __declspec(dllimport) HRESULT __stdcall +#endif + interface ID2D1Device1; namespace ABI @@ -17,7 +28,11 @@ namespace ABI { namespace Canvas { + using namespace ABI::Windows::Foundation; + interface ICanvasDevice; + interface ICanvasResourceCreator; + interface ICanvasResourceCreatorWithDpi; // // Interface provided by the CanvasDevice factory that is @@ -40,6 +55,107 @@ namespace ABI public: IFACEMETHOD(GetNativeResource)(ICanvasDevice* device, float dpi, REFIID iid, void** resource) = 0; }; + + // The type of canvas device returned by ICanvasImageInterop::GetDevice, describing how the device is associated with the canvas image. + typedef enum WIN2D_GET_DEVICE_ASSOCIATION_TYPE + { + WIN2D_GET_DEVICE_ASSOCIATION_TYPE_UNSPECIFIED = 0, // No device info is available, callers will handle the returned device with their own logic + WIN2D_GET_DEVICE_ASSOCIATION_TYPE_REALIZATION_DEVICE = 1, // The returned device is the one the image is currently realized on, if any + WIN2D_GET_DEVICE_ASSOCIATION_TYPE_CREATION_DEVICE = 2, // The returned device is the one that created the image resource, and cannot change + WIN2D_GET_DEVICE_ASSOCIATION_TYPE_FORCE_DWORD = 0xffffffff + } WIN2D_GET_DEVICE_ASSOCIATION_TYPE; + + // Options for fine-tuning the behavior of ICanvasImageInterop::GetD2DImage. + typedef enum WIN2D_GET_D2D_IMAGE_FLAGS + { + WIN2D_GET_D2D_IMAGE_FLAGS_NONE = 0, + WIN2D_GET_D2D_IMAGE_FLAGS_READ_DPI_FROM_DEVICE_CONTEXT = 1, // Ignore the targetDpi parameter - read DPI from deviceContext instead + WIN2D_GET_D2D_IMAGE_FLAGS_ALWAYS_INSERT_DPI_COMPENSATION = 2, // Ignore the targetDpi parameter - always insert DPI compensation + WIN2D_GET_D2D_IMAGE_FLAGS_NEVER_INSERT_DPI_COMPENSATION = 4, // Ignore the targetDpi parameter - never insert DPI compensation + WIN2D_GET_D2D_IMAGE_FLAGS_MINIMAL_REALIZATION = 8, // Do the bare minimum to get back an ID2D1Image - no validation or recursive realization + WIN2D_GET_D2D_IMAGE_FLAGS_ALLOW_NULL_EFFECT_INPUTS = 16, // Allow partially configured effect graphs where some inputs are null + WIN2D_GET_D2D_IMAGE_FLAGS_UNREALIZE_ON_FAILURE = 32, // If an input is invalid, unrealize the effect and set the output image to null + WIN2D_GET_D2D_IMAGE_FLAGS_FORCE_DWORD = 0xffffffff + } WIN2D_GET_D2D_IMAGE_FLAGS; + + DEFINE_ENUM_FLAG_OPERATORS(WIN2D_GET_D2D_IMAGE_FLAGS) + + // + // Interface implemented by all effects and also exposed to allow external users to implement custom effects. + // + class __declspec(uuid("E042D1F7-F9AD-4479-A713-67627EA31863")) + ICanvasImageInterop : public IUnknown + { + public: + IFACEMETHOD(GetDevice)(ICanvasDevice** device, WIN2D_GET_DEVICE_ASSOCIATION_TYPE* type) = 0; + + IFACEMETHOD(GetD2DImage)( + ICanvasDevice* device, + ID2D1DeviceContext* deviceContext, + WIN2D_GET_D2D_IMAGE_FLAGS flags, + float targetDpi, + float* realizeDpi, + ID2D1Image** ppImage) = 0; + }; + + // + // Supporting interfaces to allow external effects to access Win2D's pool of ID2D1DeviceContext objects for each device. + // + 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; + }; + + // + // Exported method to allow ICanvasImageInterop implementors to implement ICanvasImage properly. + // + WIN2DAPI GetBoundsForICanvasImageInterop( + ICanvasResourceCreator* resourceCreator, + ICanvasImageInterop* image, + Numerics::Matrix3x2 const* transform, + Rect* rect) noexcept; + + namespace Effects + { + interface ICanvasEffect; + + // + // Exported methods to allow ICanvasImageInterop implementors to implement ICanvasEffect properly. + // + WIN2DAPI InvalidateSourceRectangleForICanvasImageInterop( + ICanvasResourceCreatorWithDpi* resourceCreator, + ICanvasImageInterop* image, + uint32_t sourceIndex, + Rect const* invalidRectangle) noexcept; + + WIN2DAPI GetInvalidRectanglesForICanvasImageInterop( + ICanvasResourceCreatorWithDpi* resourceCreator, + ICanvasImageInterop* image, + uint32_t* valueCount, + Rect** valueElements) noexcept; + + WIN2DAPI 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) noexcept; + } } } } diff --git a/winrt/test.external/CanvasDeviceTests.cpp b/winrt/test.external/CanvasDeviceTests.cpp index 21f889c5..c63d7974 100644 --- a/winrt/test.external/CanvasDeviceTests.cpp +++ b/winrt/test.external/CanvasDeviceTests.cpp @@ -76,6 +76,49 @@ TEST_CLASS(CanvasDeviceTests) Assert::AreEqual(originalD2DDevice.Get(), newD2DDevice.Get()); } + TEST_METHOD(CanvasDevice_NativeInterop_DeviceContextLease) + { + auto canvasDevice = ref new CanvasDevice(); + + // CanvasDevice should be castable to ID2D1DeviceContextPool + auto deviceContextPool = As(canvasDevice); + + // Getting a device context lease should always succeed + ComPtr deviceContextLease; + ThrowIfFailed(deviceContextPool->GetDeviceContextLease(&deviceContextLease)); + Assert::IsNotNull(deviceContextLease.Get()); + + // Nobody else should have a reference to this lease + deviceContextLease->AddRef(); + Assert::AreEqual(deviceContextLease->Release(), 1ul); + + // Getting a device context from a lease should also always succeed + ComPtr deviceContext; + ThrowIfFailed(deviceContextLease->GetD2DDeviceContext(&deviceContext)); + Assert::IsNotNull(deviceContext.Get()); + + // Getting a device context from a lease again should always return the same instance. + // That is, a device context lease is just a thin wrapper around a single rented context. + ComPtr deviceContext2; + ThrowIfFailed(deviceContextLease->GetD2DDeviceContext(&deviceContext2)); + Assert::IsNotNull(deviceContext2.Get()); + Assert::IsTrue(As(deviceContext.Get()).Get() == As(deviceContext2.Get()).Get()); + + // Get the device from the canvas device + ComPtr d2D1Device; + ThrowIfFailed(As(canvasDevice)->GetNativeResource( + As(canvasDevice).Get(), + 0, + IID_PPV_ARGS(&d2D1Device))); + + // Also get the device from the rented device context + ComPtr d2D1DeviceFromDeviceContext; + deviceContext->GetDevice(&d2D1DeviceFromDeviceContext); + + // The underlying device from the canvas device should be the same one as the one from the context + Assert::IsTrue(As(d2D1Device.Get()).Get() == As(d2D1DeviceFromDeviceContext.Get()).Get()); + } + TEST_METHOD(CanvasDevice_GetSharedDevice_ReturnsExisting) { auto d1 = CanvasDevice::GetSharedDevice(false); diff --git a/winrt/test.external/CanvasImageInteropTests.cpp b/winrt/test.external/CanvasImageInteropTests.cpp new file mode 100644 index 00000000..f70b41b2 --- /dev/null +++ b/winrt/test.external/CanvasImageInteropTests.cpp @@ -0,0 +1,457 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +#include "pch.h" + +using ABI::Microsoft::Graphics::Canvas::ICanvasImageInterop; +using ABI::Microsoft::Graphics::Canvas::WIN2D_GET_DEVICE_ASSOCIATION_TYPE; +using ABI::Microsoft::Graphics::Canvas::WIN2D_GET_D2D_IMAGE_FLAGS; +using namespace Microsoft::Graphics::Canvas; +using namespace Microsoft::Graphics::Canvas::Effects; +using namespace Windows::Foundation::Collections; +using namespace Windows::UI; +using namespace Windows::Devices::Enumeration; +using namespace Platform; + +// A test shader that has no inputs and just sets all output pixels to 0: +// +// #define D2D_INPUT_COUNT 0 +// +// #include "d2d1effecthelpers.hlsli" +// +// D2D_PS_ENTRY(Execute) +// { +// return (float4)0; +// } +static unsigned char SetAllOutputPixelsToZeroShaderBytecode[] = { 0x44, 0x58, 0x42, 0x43, 0x05, 0xA9, 0xCD, 0x8B, 0xD6, 0x46, 0x6B, 0xAE, 0x85, 0xD9, 0x55, 0x8D, 0xE2, 0xC7, 0x1A, 0x0C, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x04, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x84, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x14, 0x01, 0x00, 0x00, 0x54, 0x01, 0x00, 0x00, 0xD0, 0x01, 0x00, 0x00, 0x52, 0x44, 0x45, 0x46, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x04, 0xFF, 0xFF, 0x08, 0x81, 0x04, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x20, 0x28, 0x52, 0x29, 0x20, 0x48, 0x4C, 0x53, 0x4C, 0x20, 0x53, 0x68, 0x61, 0x64, 0x65, 0x72, 0x20, 0x43, 0x6F, 0x6D, 0x70, 0x69, 0x6C, 0x65, 0x72, 0x20, 0x31, 0x30, 0x2E, 0x31, 0x00, 0x49, 0x53, 0x47, 0x4E, 0x54, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x53, 0x56, 0x5F, 0x50, 0x4F, 0x53, 0x49, 0x54, 0x49, 0x4F, 0x4E, 0x00, 0x53, 0x43, 0x45, 0x4E, 0x45, 0x5F, 0x50, 0x4F, 0x53, 0x49, 0x54, 0x49, 0x4F, 0x4E, 0x00, 0xAB, 0x4F, 0x53, 0x47, 0x4E, 0x2C, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x53, 0x56, 0x5F, 0x54, 0x41, 0x52, 0x47, 0x45, 0x54, 0x00, 0xAB, 0xAB, 0x53, 0x48, 0x44, 0x52, 0x38, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x03, 0xF2, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x08, 0xF2, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x01, 0x53, 0x54, 0x41, 0x54, 0x74, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x52, 0x49, 0x56, 0x33, 0x02, 0x00, 0x00, 0x44, 0x58, 0x42, 0x43, 0xD6, 0x7A, 0x9D, 0x4D, 0x69, 0x56, 0x64, 0xE1, 0x9D, 0x8E, 0x77, 0x5A, 0xDF, 0xB8, 0x3B, 0xB2, 0x01, 0x00, 0x00, 0x00, 0x33, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0xD8, 0x01, 0x00, 0x00, 0x4C, 0x49, 0x42, 0x46, 0xA8, 0x01, 0x00, 0x00, 0x44, 0x58, 0x42, 0x43, 0xD6, 0xF8, 0x28, 0x3A, 0x98, 0x66, 0x1E, 0x29, 0x38, 0xA0, 0xF7, 0x67, 0x3C, 0x3A, 0x35, 0x7A, 0x01, 0x00, 0x00, 0x00, 0xA8, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x9C, 0x00, 0x00, 0x00, 0xEC, 0x00, 0x00, 0x00, 0x2C, 0x01, 0x00, 0x00, 0x52, 0x44, 0x45, 0x46, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x04, 0x46, 0x4C, 0x08, 0x81, 0x04, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x52, 0x44, 0x31, 0x31, 0x3C, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x20, 0x28, 0x52, 0x29, 0x20, 0x48, 0x4C, 0x53, 0x4C, 0x20, 0x53, 0x68, 0x61, 0x64, 0x65, 0x72, 0x20, 0x43, 0x6F, 0x6D, 0x70, 0x69, 0x6C, 0x65, 0x72, 0x20, 0x31, 0x30, 0x2E, 0x31, 0x00, 0x4C, 0x46, 0x53, 0x30, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x32, 0x44, 0x5F, 0x66, 0x75, 0x6E, 0x63, 0x5F, 0x65, 0x6E, 0x74, 0x72, 0x79, 0x00, 0xAB, 0x53, 0x48, 0x44, 0x52, 0x38, 0x00, 0x00, 0x00, 0x40, 0x00, 0xF0, 0xFF, 0x0E, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x03, 0xF2, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x08, 0xF2, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x01, 0x53, 0x54, 0x41, 0x54, 0x74, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4C, 0x49, 0x42, 0x48, 0x53, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x20, 0x28, 0x52, 0x29, 0x20, 0x48, 0x4C, 0x53, 0x4C, 0x20, 0x53, 0x68, 0x61, 0x64, 0x65, 0x72, 0x20, 0x43, 0x6F, 0x6D, 0x70, 0x69, 0x6C, 0x65, 0x72, 0x20, 0x31, 0x30, 0x2E, 0x31, 0x00, 0x44, 0x32, 0x44, 0x5F, 0x66, 0x75, 0x6E, 0x63, 0x5F, 0x65, 0x6E, 0x74, 0x72, 0x79, 0x00 }; + +TEST_CLASS(CanvasImageInteropTests) +{ + // This class is a thin wrapper around an input effect, which forwards all API calls directly to it. + // It's used to validate that other Win2D APIs can accept an input that is only an ICanvasImageInterop. + class WrappedEffect : public RuntimeClass< + RuntimeClassFlags, + ABI::Microsoft::Graphics::Canvas::ICanvasImage, + ABI::Windows::Graphics::Effects::IGraphicsEffectSource, + ABI::Windows::Foundation::IClosable, + ICanvasImageInterop> + { + InspectableClass(L"EffectsAbi::IGraphicsEffectD2D1Interop::WrappedEffect", TrustLevel::BaseTrust); + + ComPtr m_canvasImage; + + public: + WrappedEffect(Microsoft::Graphics::Canvas::ICanvasImage^ canvasImage) + { + As(canvasImage).CopyTo(&m_canvasImage); + } + + // ICanvasImage + + IFACEMETHODIMP GetBounds( + ABI::Microsoft::Graphics::Canvas::ICanvasResourceCreator* resourceCreator, + ABI::Windows::Foundation::Rect* bounds + ) override + { + return m_canvasImage->GetBounds(resourceCreator, bounds); + } + + IFACEMETHODIMP GetBoundsWithTransform( + ABI::Microsoft::Graphics::Canvas::ICanvasResourceCreator* resourceCreator, + ABI::Windows::Foundation::Numerics::Matrix3x2 transform, + ABI::Windows::Foundation::Rect* bounds + ) override + { + return m_canvasImage->GetBoundsWithTransform(resourceCreator, transform, bounds); + } + + // IClosable + + IFACEMETHODIMP Close() override + { + return As(m_canvasImage.Get())->Close(); + } + + // ICanvasImageInterop + + IFACEMETHODIMP GetDevice(ABI::Microsoft::Graphics::Canvas::ICanvasDevice** device, WIN2D_GET_DEVICE_ASSOCIATION_TYPE* type) override + { + return As(m_canvasImage.Get())->GetDevice(device, type); + } + + IFACEMETHODIMP GetD2DImage( + ABI::Microsoft::Graphics::Canvas::ICanvasDevice* device, + ID2D1DeviceContext* deviceContext, + WIN2D_GET_D2D_IMAGE_FLAGS flags, + float targetDpi, + float* realizeDpi, + ID2D1Image** ppImage) override + { + return As(m_canvasImage.Get())->GetD2DImage( + device, + deviceContext, + flags, + targetDpi, + realizeDpi, + ppImage); + } + }; + + TEST_METHOD(WrappedEffect_FromCanvasBitmap_CanvasDrawingSessionDrawImage) + { + auto deviceContext = CreateTestD2DDeviceContext(); + auto drawingSession = GetOrCreate(deviceContext.Get()); + + auto canvasBitmap = CanvasBitmap::CreateFromColors( + drawingSession->Device, + ref new Platform::Array(256 * 256), + 256, + 256, + 96.0f, + CanvasAlphaMode::Ignore); + + auto wrappedEffect = Make(canvasBitmap); + + ICanvasImage^ canvasImage = reinterpret_cast(As(wrappedEffect.Get()).Get()); + + // Draw an image passing the wrapping effect implementing ICanvasImage externally + ComPtr d2dCommandList; + ThrowIfFailed(deviceContext->CreateCommandList(&d2dCommandList)); + deviceContext->SetTarget(d2dCommandList.Get()); + deviceContext->BeginDraw(); + drawingSession->DrawImage(canvasImage); + ThrowIfFailed(deviceContext->EndDraw()); + } + + TEST_METHOD(WrappedEffect_FromCanvasBitmap_CanvasDrawingSessionDrawImage_OnRentedContext) + { + auto canvasDevice = ref new CanvasDevice(); + + // Get an ID2D1DeviceContextLease object + ComPtr deviceContextLease; + ThrowIfFailed(As(canvasDevice)->GetDeviceContextLease(&deviceContextLease)); + + // Get the underlying context from the lease + ComPtr deviceContext; + ThrowIfFailed(deviceContextLease->GetD2DDeviceContext(&deviceContext)); + + // Same test as before, but using the rented device context + auto drawingSession = GetOrCreate(As(deviceContext.Get()).Get()); + + auto canvasBitmap = CanvasBitmap::CreateFromColors( + drawingSession->Device, + ref new Platform::Array(256 * 256), + 256, + 256, + 96.0f, + CanvasAlphaMode::Ignore); + + auto wrappedEffect = Make(canvasBitmap); + + ICanvasImage^ canvasImage = reinterpret_cast(As(wrappedEffect.Get()).Get()); + + ComPtr d2dCommandList; + ThrowIfFailed(deviceContext->CreateCommandList(&d2dCommandList)); + deviceContext->SetTarget(d2dCommandList.Get()); + deviceContext->BeginDraw(); + drawingSession->DrawImage(canvasImage); + ThrowIfFailed(deviceContext->EndDraw()); + } + + TEST_METHOD(WrappedEffect_FromCanvasEffect_CanvasDrawingSessionDrawImage) + { + auto deviceContext = CreateTestD2DDeviceContext(); + auto drawingSession = GetOrCreate(deviceContext.Get()); + + auto canvasBitmap = CanvasBitmap::CreateFromColors( + drawingSession->Device, + ref new Platform::Array(256 * 256), + 256, + 256, + 96.0f, + CanvasAlphaMode::Ignore); + + auto blurEffect = ref new GaussianBlurEffect(); + + blurEffect->Source = canvasBitmap; + + // Create a wrapper around GaussianBlurEffect to validate that ICanvasImageInterop also works correctly through a CanvasEffect-derived type + auto wrappedEffect = Make(blurEffect); + + ICanvasImage^ canvasImage = reinterpret_cast(As(wrappedEffect.Get()).Get()); + + ComPtr d2dCommandList; + ThrowIfFailed(deviceContext->CreateCommandList(&d2dCommandList)); + deviceContext->SetTarget(d2dCommandList.Get()); + deviceContext->BeginDraw(); + drawingSession->DrawImage(canvasImage); + ThrowIfFailed(deviceContext->EndDraw()); + } + + TEST_METHOD(WrappedEffect_FromCanvasEffect_CanRealizeOnDifferentDevices_CanvasDrawingSessionDrawImage) + { + auto dummyShaderEffect = ref new PixelShaderEffect(ref new Array(SetAllOutputPixelsToZeroShaderBytecode, sizeof(SetAllOutputPixelsToZeroShaderBytecode))); + + // Create a wrapper around the dummy effect (this is needed so the whole tree can be realized again, as there's no node bound to a device) + auto wrappedEffect = Make(dummyShaderEffect); + + // Draw the same effect twice (the first time it'll be realized on the device, the second time it'll unrealize and realize again on the new device) + for (auto i = 0; i < 2; i++) + { + auto deviceContext = CreateTestD2DDeviceContext(); + auto drawingSession = GetOrCreate(deviceContext.Get()); + + ICanvasImage^ canvasImage = reinterpret_cast(As(wrappedEffect.Get()).Get()); + + ComPtr d2dCommandList; + ThrowIfFailed(deviceContext->CreateCommandList(&d2dCommandList)); + deviceContext->SetTarget(d2dCommandList.Get()); + deviceContext->BeginDraw(); + drawingSession->DrawImage(canvasImage); + ThrowIfFailed(deviceContext->EndDraw()); + } + } + + TEST_METHOD(WrappedEffect_FromCanvasEffect_CanRealizeOnDifferentDevicesWhenUsedAsEffectSource_CanvasDrawingSessionDrawImage) + { + auto dummyShaderEffect = ref new PixelShaderEffect(ref new Array(SetAllOutputPixelsToZeroShaderBytecode, sizeof(SetAllOutputPixelsToZeroShaderBytecode))); + + // This test is the same as above, except this time the ICanvasImageInterop is used as a source for another effect. + // Once again, this has to ensure that the root effect can handle cases where the interop effect is realized on a + // different device. Since the device is not bound to a resource, it should still work and just unrealize/realize. + auto wrappedEffect = Make(dummyShaderEffect); + + auto blurEffect = ref new GaussianBlurEffect(); + + blurEffect->Source = reinterpret_cast(As(wrappedEffect.Get()).Get()); + + for (auto i = 0; i < 2; i++) + { + auto deviceContext = CreateTestD2DDeviceContext(); + auto drawingSession = GetOrCreate(deviceContext.Get()); + + ICanvasImage^ canvasImage = reinterpret_cast(As(blurEffect).Get()); + + ComPtr d2dCommandList; + ThrowIfFailed(deviceContext->CreateCommandList(&d2dCommandList)); + deviceContext->SetTarget(d2dCommandList.Get()); + deviceContext->BeginDraw(); + drawingSession->DrawImage(canvasImage); + ThrowIfFailed(deviceContext->EndDraw()); + } + } + + TEST_METHOD(WrappedEffect_FromCanvasBitmap_AsOtherEffectSource_CanvasDrawingSessionDrawImage) + { + auto deviceContext = CreateTestD2DDeviceContext(); + auto drawingSession = GetOrCreate(deviceContext.Get()); + + auto canvasBitmap = CanvasBitmap::CreateFromColors( + drawingSession->Device, + ref new Platform::Array(256 * 256), + 256, + 256, + 96.0f, + CanvasAlphaMode::Ignore); + + auto wrappedEffect = Make(canvasBitmap); + + auto blurEffect = ref new GaussianBlurEffect(); + + IGraphicsEffectSource^ effectSource = reinterpret_cast(As(wrappedEffect.Get()).Get()); + + // Assign the wrapper as source for an effect to validate that built-in effects can also accept external ICanvasImage objects + blurEffect->Source = effectSource; + + ComPtr d2dCommandList; + ThrowIfFailed(deviceContext->CreateCommandList(&d2dCommandList)); + deviceContext->SetTarget(d2dCommandList.Get()); + deviceContext->BeginDraw(); + drawingSession->DrawImage(blurEffect); + ThrowIfFailed(deviceContext->EndDraw()); + } + + TEST_METHOD(WrappedEffect_FromCanvasBitmap_GetBoundsForICanvasImageInterop) + { + auto deviceContext = CreateTestD2DDeviceContext(); + auto drawingSession = GetOrCreate(deviceContext.Get()); + + auto canvasBitmap = CanvasBitmap::CreateFromColors( + drawingSession->Device, + ref new Platform::Array(256 * 256), + 256, + 256, + 96.0f, + CanvasAlphaMode::Ignore); + + auto wrappedEffect = Make(canvasBitmap); + WIN2D_GET_DEVICE_ASSOCIATION_TYPE deviceType = WIN2D_GET_DEVICE_ASSOCIATION_TYPE::WIN2D_GET_DEVICE_ASSOCIATION_TYPE_UNSPECIFIED; + + ComPtr canvasDevice; + ComPtr canvasResourceCreator; + ThrowIfFailed(wrappedEffect->GetDevice(&canvasDevice, &deviceType)); + ThrowIfFailed(canvasDevice.CopyTo(IID_PPV_ARGS(&canvasResourceCreator))); + + // A canvas bitmap has an associated device from creation + Assert::IsNotNull(canvasDevice.Get()); + Assert::IsNotNull(canvasResourceCreator.Get()); + Assert::IsTrue(deviceType == WIN2D_GET_DEVICE_ASSOCIATION_TYPE::WIN2D_GET_DEVICE_ASSOCIATION_TYPE_CREATION_DEVICE); + + auto canvasImageInterop = As(canvasBitmap); + + ABI::Windows::Foundation::Rect rect; + + // Test the GetBoundsForICanvasImageInterop export with an external effect wrapping a CanvasBitmap + ThrowIfFailed(ABI::Microsoft::Graphics::Canvas::GetBoundsForICanvasImageInterop( + canvasResourceCreator.Get(), + canvasImageInterop.Get(), + nullptr, + &rect)); + + ABI::Windows::Foundation::Rect expectedBounds; + expectedBounds.X = 0; + expectedBounds.Y = 0; + expectedBounds.Width = canvasBitmap->Size.Width; + expectedBounds.Height = canvasBitmap->Size.Height; + + Assert::AreEqual(*reinterpret_cast(&expectedBounds), *reinterpret_cast(&rect)); + } + + TEST_METHOD(WrappedEffect_FromCanvasEffect_GetBoundsForICanvasImageInterop) + { + auto deviceContext = CreateTestD2DDeviceContext(); + auto drawingSession = GetOrCreate(deviceContext.Get()); + + auto canvasBitmap = CanvasBitmap::CreateFromColors( + drawingSession->Device, + ref new Platform::Array(256 * 256), + 256, + 256, + 96.0f, + CanvasAlphaMode::Ignore); + + auto blurEffect = ref new GaussianBlurEffect(); + + blurEffect->Source = canvasBitmap; + blurEffect->BorderMode = EffectBorderMode::Hard; + + auto wrappedEffect = Make(blurEffect); + WIN2D_GET_DEVICE_ASSOCIATION_TYPE deviceType = WIN2D_GET_DEVICE_ASSOCIATION_TYPE::WIN2D_GET_DEVICE_ASSOCIATION_TYPE_UNSPECIFIED; + + ComPtr canvasDevice; + ThrowIfFailed(wrappedEffect->GetDevice(&canvasDevice, &deviceType)); + + // An effect has no associated device until it's realized (and it isn't at this point yet) + Assert::IsNull(canvasDevice.Get()); + Assert::IsTrue(deviceType == WIN2D_GET_DEVICE_ASSOCIATION_TYPE::WIN2D_GET_DEVICE_ASSOCIATION_TYPE_REALIZATION_DEVICE); + + auto canvasImageInterop = As(canvasBitmap); + + ABI::Windows::Foundation::Rect rect; + + // Test GetBoundsForICanvasImageInterop with an effect going through a second Win2D effect first + ThrowIfFailed(ABI::Microsoft::Graphics::Canvas::GetBoundsForICanvasImageInterop( + As(drawingSession).Get(), + canvasImageInterop.Get(), + nullptr, + &rect)); + + ABI::Windows::Foundation::Rect expectedBounds; + expectedBounds.X = 0; + expectedBounds.Y = 0; + expectedBounds.Width = canvasBitmap->Size.Width; + expectedBounds.Height = canvasBitmap->Size.Height; + + Assert::AreEqual(*reinterpret_cast(&expectedBounds), *reinterpret_cast(&rect)); + } + + TEST_METHOD(WrappedEffect_FromCanvasBitmap_InvalidateSourceRectangleForICanvasImageInterop_ThrowE_NOINTERFACE) + { + auto deviceContext = CreateTestD2DDeviceContext(); + auto drawingSession = GetOrCreate(deviceContext.Get()); + + auto canvasBitmap = CanvasBitmap::CreateFromColors( + drawingSession->Device, + ref new Platform::Array(256 * 256), + 256, + 256, + 96.0f, + CanvasAlphaMode::Ignore); + + auto wrappedEffect = Make(canvasBitmap); + + Rect invalidRectangle = { 0, 0, 0, 0 }; + + // This wrapped effect wraps a canvas bitmap, meaning when ICanvasImageInterop::GetD2DImage is called, the + // resulting ID2D1Image will not also be an ID2D1Effect. In this case, an E_NOINTERFACE error is expected. + HRESULT result = ABI::Microsoft::Graphics::Canvas::Effects::InvalidateSourceRectangleForICanvasImageInterop( + As(drawingSession).Get(), + As(wrappedEffect.Get()).Get(), + 0, + reinterpret_cast(&invalidRectangle)); + + Assert::AreEqual(result, E_NOINTERFACE); + } + + TEST_METHOD(WrappedEffect_FromCanvasEffect_InvalidateSourceRectangleForICanvasImageInterop) + { + auto deviceContext = CreateTestD2DDeviceContext(); + auto drawingSession = GetOrCreate(deviceContext.Get()); + + auto canvasBitmap = CanvasBitmap::CreateFromColors( + drawingSession->Device, + ref new Platform::Array(256 * 256), + 256, + 256, + 96.0f, + CanvasAlphaMode::Ignore); + + auto blurEffect = ref new GaussianBlurEffect(); + + blurEffect->Source = canvasBitmap; + blurEffect->BorderMode = EffectBorderMode::Hard; + + auto wrappedEffect = Make(blurEffect); + + ABI::Windows::Foundation::Rect invalidRectangle = { 0, 0, 0, 0 }; + + // This test and the one below simply check that the C exports to support ICanvasEffect also work correctly + // when an effect that only implements ICanvasImageInterop (and not ICanvasImageInternal) is passed. This is + // just a sanity check, and the real tests with validation of all params/results are in the internal unit tests. + ThrowIfFailed(ABI::Microsoft::Graphics::Canvas::Effects::InvalidateSourceRectangleForICanvasImageInterop( + As(drawingSession).Get(), + As(wrappedEffect.Get()).Get(), + 0, + &invalidRectangle)); + } + + TEST_METHOD(WrappedEffect_FromCanvasEffect_GetInvalidRectanglesForICanvasImageInterop) + { + auto deviceContext = CreateTestD2DDeviceContext(); + auto drawingSession = GetOrCreate(deviceContext.Get()); + + auto canvasBitmap = CanvasBitmap::CreateFromColors( + drawingSession->Device, + ref new Platform::Array(256 * 256), + 256, + 256, + 96.0f, + CanvasAlphaMode::Ignore); + + auto blurEffect = ref new GaussianBlurEffect(); + + blurEffect->Source = canvasBitmap; + blurEffect->BorderMode = EffectBorderMode::Hard; + + auto wrappedEffect = Make(blurEffect); + + ComArray result; + + ThrowIfFailed(ABI::Microsoft::Graphics::Canvas::Effects::GetInvalidRectanglesForICanvasImageInterop( + As(drawingSession).Get(), + As(wrappedEffect.Get()).Get(), + result.GetAddressOfSize(), + result.GetAddressOfData())); + } +}; diff --git a/winrt/test.external/pch.h b/winrt/test.external/pch.h index 84d8120d..73d5fa20 100644 --- a/winrt/test.external/pch.h +++ b/winrt/test.external/pch.h @@ -37,6 +37,8 @@ #include #include +#include + #include #include diff --git a/winrt/test.external/winrt.test.external.uap.vcxproj b/winrt/test.external/winrt.test.external.uap.vcxproj index 948067c0..dd3c076b 100644 --- a/winrt/test.external/winrt.test.external.uap.vcxproj +++ b/winrt/test.external/winrt.test.external.uap.vcxproj @@ -48,6 +48,7 @@ + diff --git a/winrt/test.external/winrt.test.external.uap.vcxproj.filters b/winrt/test.external/winrt.test.external.uap.vcxproj.filters index c9744667..70b4eead 100644 --- a/winrt/test.external/winrt.test.external.uap.vcxproj.filters +++ b/winrt/test.external/winrt.test.external.uap.vcxproj.filters @@ -41,6 +41,7 @@ + diff --git a/winrt/test.internal/graphics/CanvasBitmapUnitTest.cpp b/winrt/test.internal/graphics/CanvasBitmapUnitTest.cpp index 98f9c811..3957e116 100644 --- a/winrt/test.internal/graphics/CanvasBitmapUnitTest.cpp +++ b/winrt/test.internal/graphics/CanvasBitmapUnitTest.cpp @@ -64,6 +64,7 @@ TEST_CLASS(CanvasBitmapUnitTest) ASSERT_IMPLEMENTS_INTERFACE(canvasBitmap, IDirect3DSurface); ASSERT_IMPLEMENTS_INTERFACE(canvasBitmap, ABI::Windows::Foundation::IClosable); ASSERT_IMPLEMENTS_INTERFACE(canvasBitmap, IDirect3DDxgiInterfaceAccess); + ASSERT_IMPLEMENTS_INTERFACE(canvasBitmap, ICanvasImageInterop); ASSERT_IMPLEMENTS_INTERFACE(canvasBitmap, ICanvasImageInternal); ASSERT_IMPLEMENTS_INTERFACE(canvasBitmap, ICanvasBitmapInternal); } @@ -110,6 +111,19 @@ TEST_CLASS(CanvasBitmapUnitTest) Assert::AreEqual(f.m_canvasDevice.Get(), actualDevice.Get()); } + TEST_METHOD_EX(CanvasBitmap_GetDevice_FromICanvasImageInterop) + { + Fixture f; + auto bitmap = CanvasBitmap::CreateNew(f.m_canvasDevice.Get(), f.m_testFileName, DEFAULT_DPI, CanvasAlphaMode::Premultiplied); + + ComPtr actualDevice; + WIN2D_GET_DEVICE_ASSOCIATION_TYPE deviceType = WIN2D_GET_DEVICE_ASSOCIATION_TYPE::WIN2D_GET_DEVICE_ASSOCIATION_TYPE_UNSPECIFIED; + As(bitmap)->GetDevice(&actualDevice, &deviceType); + + Assert::AreEqual(f.m_canvasDevice.Get(), actualDevice.Get()); + Assert::IsTrue(deviceType == WIN2D_GET_DEVICE_ASSOCIATION_TYPE::WIN2D_GET_DEVICE_ASSOCIATION_TYPE_CREATION_DEVICE); + } + TEST_METHOD_EX(CanvasBitmap_GetAlphaMode) { Fixture f; diff --git a/winrt/test.internal/graphics/CanvasDeviceUnitTests.cpp b/winrt/test.internal/graphics/CanvasDeviceUnitTests.cpp index fc05c70d..97e1b2d9 100644 --- a/winrt/test.internal/graphics/CanvasDeviceUnitTests.cpp +++ b/winrt/test.internal/graphics/CanvasDeviceUnitTests.cpp @@ -43,6 +43,8 @@ public: ASSERT_IMPLEMENTS_INTERFACE(canvasDevice, ABI::Windows::Foundation::IClosable); ASSERT_IMPLEMENTS_INTERFACE(canvasDevice, ICanvasResourceWrapperNative); ASSERT_IMPLEMENTS_INTERFACE(canvasDevice, ICanvasDeviceInternal); + ASSERT_IMPLEMENTS_INTERFACE(canvasDevice, IDirect3DDxgiInterfaceAccess); + ASSERT_IMPLEMENTS_INTERFACE(canvasDevice, ID2D1DeviceContextPool); } TEST_METHOD_EX(CanvasDevice_ForceSoftwareRenderer) @@ -555,6 +557,10 @@ public: boolean b; Assert::AreEqual(RO_E_CLOSED, canvasDevice->IsDeviceLost(0, &b)); + Assert::AreEqual(RO_E_CLOSED, canvasDevice->IsDeviceLost2(&b)); + + int hresult; + Assert::AreEqual(RO_E_CLOSED, canvasDevice->GetDeviceLostReason(&hresult)); Assert::AreEqual(RO_E_CLOSED, canvasDevice->RaiseDeviceLost()); @@ -570,6 +576,8 @@ public: Assert::AreEqual(E_INVALIDARG, canvasDevice->add_DeviceLost(nullptr, &token)); Assert::AreEqual(E_INVALIDARG, canvasDevice->add_DeviceLost(dummyDeviceLostHandler.Get(), nullptr)); Assert::AreEqual(E_INVALIDARG, canvasDevice->IsDeviceLost(0, nullptr)); + Assert::AreEqual(E_INVALIDARG, canvasDevice->IsDeviceLost2(nullptr)); + Assert::AreEqual(E_INVALIDARG, canvasDevice->GetDeviceLostReason(nullptr)); } class DeviceLostAdapter : public TestDeviceAdapter @@ -633,6 +641,46 @@ public: } } + TEST_METHOD_EX(CanvasDeviceLostTests_IsDeviceLost2_DeviceIsLost_ReturnsTrue) + { + DeviceLostFixture f; + auto canvasDevice = CanvasDevice::CreateNew(false); + + boolean isDeviceLost; + Assert::AreEqual(S_OK, canvasDevice->IsDeviceLost2(&isDeviceLost)); + Assert::IsTrue(!!isDeviceLost); + } + + TEST_METHOD_EX(CanvasDeviceLostTests_IsDeviceLost2_DeviceNotActuallyLost_ReturnsFalse) + { + Fixture f; + auto canvasDevice = CanvasDevice::CreateNew(false); + + boolean isDeviceLost; + Assert::AreEqual(S_OK, canvasDevice->IsDeviceLost2(&isDeviceLost)); + Assert::IsFalse(!!isDeviceLost); + } + + TEST_METHOD_EX(CanvasDeviceLostTests_GetDeviceLostReason_DeviceIsLost_ReturnsHResult) + { + DeviceLostFixture f; + auto canvasDevice = CanvasDevice::CreateNew(false); + + int hresult; + Assert::AreEqual(S_OK, canvasDevice->GetDeviceLostReason(&hresult)); + Assert::AreEqual(DXGI_ERROR_DEVICE_REMOVED, (HRESULT)hresult); + } + + TEST_METHOD_EX(CanvasDeviceLostTests_GetDeviceLostReason_DeviceNotActuallyLost_ReturnsS_OK) + { + Fixture f; + auto canvasDevice = CanvasDevice::CreateNew(false); + + int hresult; + Assert::AreEqual(S_OK, canvasDevice->GetDeviceLostReason(&hresult)); + Assert::AreEqual(S_OK, (HRESULT)hresult); + } + TEST_METHOD_EX(CanvasDeviceLostTests_IsDeviceLost_SomeArbitraryHr_DeviceNotActuallyLost_ReturnsFalse) { DeviceLostFixture f; diff --git a/winrt/test.internal/graphics/CanvasEffectUnitTest.cpp b/winrt/test.internal/graphics/CanvasEffectUnitTest.cpp index 02dc5134..3624dec2 100644 --- a/winrt/test.internal/graphics/CanvasEffectUnitTest.cpp +++ b/winrt/test.internal/graphics/CanvasEffectUnitTest.cpp @@ -38,6 +38,7 @@ public: ASSERT_IMPLEMENTS_INTERFACE(m_testEffect, IGraphicsEffectSource); ASSERT_IMPLEMENTS_INTERFACE(m_testEffect, ABI::Windows::Foundation::IClosable); ASSERT_IMPLEMENTS_INTERFACE(m_testEffect, ICanvasImage); + ASSERT_IMPLEMENTS_INTERFACE(m_testEffect, ICanvasImageInterop); ASSERT_IMPLEMENTS_INTERFACE(m_testEffect, ICanvasImageInternal); ASSERT_IMPLEMENTS_INTERFACE(m_testEffect, IGaussianBlurEffect); } @@ -226,7 +227,7 @@ public: mockEffect->MockGetInput = [&](UINT32, ID2D1Image** input) { - ThrowIfFailed(stubBitmap->GetD2DImage(nullptr, nullptr, (GetImageFlags)0, 0, nullptr).CopyTo(input)); + ThrowIfFailed(stubBitmap->GetD2DImage(nullptr, nullptr, WIN2D_GET_D2D_IMAGE_FLAGS_NONE, 0, nullptr).CopyTo(input)); }; mockEffect->MockGetInputCount = @@ -761,6 +762,34 @@ public: ThrowIfFailed(testEffect->InvalidateSourceRectangle(f.m_drawingSession.Get(), 0, rect)); } + TEST_METHOD_EX(CanvasEffect_InvalidateSourceRectangle_ForICanvasImageInterop) + { + Fixture f; + + auto testEffect = Make(m_blurGuid, 0, 1); + auto mockD2DEffect = Make(); + + f.m_deviceContext->CreateEffectMethod.SetExpectedCalls(1, [&](IID const& effectId, ID2D1Effect** effect) { return mockD2DEffect.CopyTo(effect); }); + + Rect rect = { 1, 2, 3, 4 }; + + Assert::AreEqual(E_INVALIDARG, InvalidateSourceRectangleForICanvasImageInterop(nullptr, As(testEffect.Get()).Get(), 0, &rect)); + Assert::AreEqual(E_INVALIDARG, InvalidateSourceRectangleForICanvasImageInterop(f.m_drawingSession.Get(), nullptr, 0, &rect)); + Assert::AreEqual(E_INVALIDARG, InvalidateSourceRectangleForICanvasImageInterop(f.m_drawingSession.Get(), As(testEffect.Get()).Get(), 1, &rect)); + + f.m_deviceContext->InvalidateEffectInputRectangleMethod.SetExpectedCalls(1, + [&](ID2D1Effect* effect, UINT32 input, D2D1_RECT_F const* invalidRect) + { + Assert::IsTrue(IsSameInstance(effect, mockD2DEffect.Get())); + Assert::AreEqual(0u, input); + Assert::AreEqual(rect, FromD2DRect(*invalidRect)); + + return S_OK; + }); + + ThrowIfFailed(InvalidateSourceRectangleForICanvasImageInterop(f.m_drawingSession.Get(), As(testEffect.Get()).Get(), 0, &rect)); + } + TEST_METHOD_EX(CanvasEffect_GetInvalidRectangles) { Fixture f; @@ -808,6 +837,54 @@ public: Assert::AreEqual(rect2, result[1]); } + TEST_METHOD_EX(CanvasEffect_GetInvalidRectangles_ForICanvasImageInterop) + { + Fixture f; + + auto testEffect = Make(m_blurGuid, 0, 1); + auto mockD2DEffect = Make(); + + f.m_deviceContext->CreateEffectMethod.SetExpectedCalls(1, [&](IID const& effectId, ID2D1Effect** effect) { return mockD2DEffect.CopyTo(effect); }); + + Rect rect1 = { 1, 2, 3, 4 }; + Rect rect2 = { 5, 6, 7, 8 }; + + ComArray result; + + Assert::AreEqual(E_INVALIDARG, GetInvalidRectanglesForICanvasImageInterop(nullptr, As(testEffect.Get()).Get(), result.GetAddressOfSize(), result.GetAddressOfData())); + Assert::AreEqual(E_INVALIDARG, GetInvalidRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), nullptr, result.GetAddressOfSize(), result.GetAddressOfData())); + Assert::AreEqual(E_INVALIDARG, GetInvalidRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As(testEffect.Get()).Get(), nullptr, result.GetAddressOfData())); + Assert::AreEqual(E_INVALIDARG, GetInvalidRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As(testEffect.Get()).Get(), result.GetAddressOfSize(), nullptr)); + + f.m_deviceContext->GetEffectInvalidRectangleCountMethod.SetExpectedCalls(1, + [&](ID2D1Effect* effect, UINT32* count) + { + Assert::IsTrue(IsSameInstance(effect, mockD2DEffect.Get())); + *count = 2; + + return S_OK; + }); + + f.m_deviceContext->GetEffectInvalidRectanglesMethod.SetExpectedCalls(1, + [&](ID2D1Effect* effect, D2D1_RECT_F* rects, UINT32 count) + { + Assert::IsTrue(IsSameInstance(effect, mockD2DEffect.Get())); + Assert::AreEqual(2u, count); + + rects[0] = ToD2DRect(rect1); + rects[1] = ToD2DRect(rect2); + + return S_OK; + }); + + ThrowIfFailed(GetInvalidRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As(testEffect.Get()).Get(), result.GetAddressOfSize(), result.GetAddressOfData())); + + Assert::AreEqual(2u, result.GetSize()); + + Assert::AreEqual(rect1, result[0]); + Assert::AreEqual(rect2, result[1]); + } + TEST_METHOD_EX(CanvasEffect_GetRequiredSourceRectangle) { Fixture f; @@ -857,6 +934,64 @@ public: Assert::AreEqual(expectedResult, result); } + TEST_METHOD_EX(CanvasEffect_GetRequiredSourceRectangle_ForICanvasImageInterop) + { + Fixture f; + + auto testEffect = Make(m_blurGuid, 0, 1); + auto inputEffect = Make(m_blurGuid, 0, 1); + + testEffect->SetSource(0, inputEffect.Get()); + + std::vector> mockEffects; + + f.m_deviceContext->CreateEffectMethod.SetExpectedCalls(2, + [&](IID const& effectId, ID2D1Effect** effect) + { + mockEffects.push_back(Make(effectId)); + return mockEffects.back().CopyTo(effect); + }); + + Rect outputRect = { 1, 2, 3, 4 }; + Rect inputBounds = { 5, 6, 7, 8 }; + Rect expectedResult = { 9, 10, 11, 12 }; + Rect result; + uint32_t inputIndex = 0; + ICanvasEffect* inputEffects = inputEffect.Get(); + ICanvasEffect* nullEffects = nullptr; + uint32_t invalidIndex = 1; + + Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(nullptr, As(testEffect.Get()).Get(), &outputRect, 1, &inputEffects, 1, &inputIndex, 1, &inputBounds, 1, &result)); + Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), nullptr, &outputRect, 1, &inputEffects, 1, &inputIndex, 1, &inputBounds, 1, &result)); + Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As(testEffect.Get()).Get(), &outputRect, 1, nullptr, 1, &inputIndex, 1, &inputBounds, 1, &result)); + Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As(testEffect.Get()).Get(), &outputRect, 1, &nullEffects, 1, &inputIndex, 1, &inputBounds, 1, &result)); + Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As(testEffect.Get()).Get(), &outputRect, 1, &inputEffects, 1, &invalidIndex, 1, &inputBounds, 1, &result)); + Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As(testEffect.Get()).Get(), &outputRect, 1, &inputEffects, 0, &inputIndex, 1, &inputBounds, 1, &result)); + Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As(testEffect.Get()).Get(), &outputRect, 1, &inputEffects, 1, &inputIndex, 0, &inputBounds, 1, &result)); + Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As(testEffect.Get()).Get(), &outputRect, 1, &inputEffects, 1, &inputIndex, 1, &inputBounds, 0, &result)); + Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As(testEffect.Get()).Get(), &outputRect, 1, &inputEffects, 1, &inputIndex, 1, &inputBounds, 1, nullptr)); + + f.m_deviceContext->GetEffectRequiredInputRectanglesMethod.SetExpectedCalls(1, + [&](ID2D1Effect* effect, D2D1_RECT_F const* output, D2D1_EFFECT_INPUT_DESCRIPTION const* desc, D2D1_RECT_F* result, UINT32 count) + { + Assert::IsTrue(IsSameInstance(mockEffects[0].Get(), effect)); + Assert::AreEqual(outputRect, FromD2DRect(*output)); + Assert::AreEqual(1u, count); + + Assert::IsTrue(IsSameInstance(mockEffects[1].Get(), desc[0].effect)); + Assert::AreEqual(0u, desc[0].inputIndex); + Assert::AreEqual(inputBounds, FromD2DRect(desc[0].inputRectangle)); + + result[0] = ToD2DRect(expectedResult); + + return S_OK; + }); + + ThrowIfFailed(GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As(testEffect.Get()).Get(), &outputRect, 1, &inputEffects, 1, &inputIndex, 1, &inputBounds, 1, &result)); + + Assert::AreEqual(expectedResult, result); + } + TEST_METHOD_EX(CanvasEffect_GetRequiredSourceRectangles) { Fixture f; @@ -932,6 +1067,80 @@ public: Assert::AreEqual(expectedResult2, result[1]); } + TEST_METHOD_EX(CanvasEffect_GetRequiredSourceRectangles_ForICanvasImageInterop) + { + Fixture f; + + auto testEffect = Make(m_blurGuid, 0, 2); + auto inputEffect1 = Make(m_blurGuid, 0, 1); + auto inputEffect2 = Make(m_blurGuid, 0, 1); + + testEffect->SetSource(0, inputEffect1.Get()); + testEffect->SetSource(1, inputEffect2.Get()); + + std::vector> mockEffects; + + f.m_deviceContext->CreateEffectMethod.SetExpectedCalls(3, + [&](IID const& effectId, ID2D1Effect** effect) + { + mockEffects.push_back(Make(effectId)); + return mockEffects.back().CopyTo(effect); + }); + + Rect outputRect = { 1, 2, 3, 4 }; + Rect inputBounds1 = { 5, 6, 7, 8 }; + Rect inputBounds2 = { 9, 10, 11, 12 }; + Rect expectedResult1 = { 13, 14, 15, 16 }; + Rect expectedResult2 = { 17, 18, 19, 20 }; + + ICanvasEffect* inputEffects[] = { inputEffect1.Get(), inputEffect2.Get() }; + uint32_t inputIndices[] = { 0, 0 }; + uint32_t badIndices1[] = { 1, 0 }; + uint32_t badIndices2[] = { 0, 1 }; + Rect inputBounds[] = { inputBounds1, inputBounds2 }; + + Rect results[2]; + + Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(nullptr, As(testEffect.Get()).Get(), &outputRect, 2, inputEffects, 2, inputIndices, 2, inputBounds, 2, results)); + Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), nullptr, &outputRect, 2, inputEffects, 2, inputIndices, 2, inputBounds, 2, results)); + Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As(testEffect.Get()).Get(), &outputRect, 2, nullptr, 2, inputIndices, 2, inputBounds, 2, results)); + Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As(testEffect.Get()).Get(), &outputRect, 2, inputEffects, 2, nullptr, 2, inputBounds, 2, results)); + Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As(testEffect.Get()).Get(), &outputRect, 2, inputEffects, 2, inputIndices, 2, nullptr, 2, results)); + Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As(testEffect.Get()).Get(), &outputRect, 2, inputEffects, 2, inputIndices, 2, inputBounds, 0, results)); + Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As(testEffect.Get()).Get(), &outputRect, 2, inputEffects, 2, inputIndices, 2, inputBounds, 2, nullptr)); + Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As(testEffect.Get()).Get(), &outputRect, 1, inputEffects, 2, inputIndices, 2, inputBounds, 2, results)); + Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As(testEffect.Get()).Get(), &outputRect, 2, inputEffects, 1, inputIndices, 2, inputBounds, 2, results)); + Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As(testEffect.Get()).Get(), &outputRect, 2, inputEffects, 2, inputIndices, 1, inputBounds, 2, results)); + Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As(testEffect.Get()).Get(), &outputRect, 2, inputEffects, 2, badIndices1, 2, inputBounds, 2, results)); + Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As(testEffect.Get()).Get(), &outputRect, 2, inputEffects, 2, badIndices2, 2, inputBounds, 2, results)); + + f.m_deviceContext->GetEffectRequiredInputRectanglesMethod.SetExpectedCalls(1, + [&](ID2D1Effect* effect, D2D1_RECT_F const* output, D2D1_EFFECT_INPUT_DESCRIPTION const* desc, D2D1_RECT_F* result, UINT32 count) + { + Assert::IsTrue(IsSameInstance(mockEffects[0].Get(), effect)); + Assert::AreEqual(outputRect, FromD2DRect(*output)); + Assert::AreEqual(2u, count); + + Assert::IsTrue(IsSameInstance(mockEffects[1].Get(), desc[0].effect)); + Assert::AreEqual(0u, desc[0].inputIndex); + Assert::AreEqual(inputBounds1, FromD2DRect(desc[0].inputRectangle)); + + Assert::IsTrue(IsSameInstance(mockEffects[2].Get(), desc[1].effect)); + Assert::AreEqual(0u, desc[1].inputIndex); + Assert::AreEqual(inputBounds2, FromD2DRect(desc[1].inputRectangle)); + + result[0] = ToD2DRect(expectedResult1); + result[1] = ToD2DRect(expectedResult2); + + return S_OK; + }); + + ThrowIfFailed(GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As(testEffect.Get()).Get(), &outputRect, 2, inputEffects, 2, inputIndices, 2, inputBounds, 2, results)); + + Assert::AreEqual(expectedResult1, results[0]); + Assert::AreEqual(expectedResult2, results[1]); + } + struct EffectRealizationContextFixture : public Fixture { ComPtr m_testEffect; diff --git a/winrt/test.internal/graphics/CanvasRenderTargetUnitTests.cpp b/winrt/test.internal/graphics/CanvasRenderTargetUnitTests.cpp index a8c79f7d..369fc910 100644 --- a/winrt/test.internal/graphics/CanvasRenderTargetUnitTests.cpp +++ b/winrt/test.internal/graphics/CanvasRenderTargetUnitTests.cpp @@ -75,6 +75,7 @@ TEST_CLASS(CanvasRenderTargetTests) ASSERT_IMPLEMENTS_INTERFACE(renderTarget, ICanvasBitmap); ASSERT_IMPLEMENTS_INTERFACE(renderTarget, ICanvasImage); ASSERT_IMPLEMENTS_INTERFACE(renderTarget, ABI::Windows::Foundation::IClosable); + ASSERT_IMPLEMENTS_INTERFACE(renderTarget, ICanvasImageInterop); ASSERT_IMPLEMENTS_INTERFACE(renderTarget, ICanvasImageInternal); } diff --git a/winrt/test.internal/graphics/CanvasVirtualBitmapUnitTests.cpp b/winrt/test.internal/graphics/CanvasVirtualBitmapUnitTests.cpp index 1d3bea6e..571e95e6 100644 --- a/winrt/test.internal/graphics/CanvasVirtualBitmapUnitTests.cpp +++ b/winrt/test.internal/graphics/CanvasVirtualBitmapUnitTests.cpp @@ -112,6 +112,22 @@ TEST_CLASS(CanvasVirtualBitmapUnitTest) Assert::IsTrue(IsSameInstance(f.Device.Get(), actualDevice.Get())); } + TEST_METHOD_EX(CanvasVirtualBitmap_CreateNew_Untransformed_CallsThrough_FromICanvasImageInterop) + { + Fixture f; + + auto imageSource = f.ExpectCreateImageSourceFromWic(D2D1_IMAGE_SOURCE_LOADING_OPTIONS_NONE, D2D1_ALPHA_MODE_PREMULTIPLIED); + + auto virtualBitmap = f.CreateVirtualBitmap(CanvasVirtualBitmapOptions::None, CanvasAlphaMode::Premultiplied); + + ComPtr actualDevice; + WIN2D_GET_DEVICE_ASSOCIATION_TYPE deviceType = WIN2D_GET_DEVICE_ASSOCIATION_TYPE::WIN2D_GET_DEVICE_ASSOCIATION_TYPE_UNSPECIFIED; + ThrowIfFailed(As(virtualBitmap)->GetDevice(&actualDevice, &deviceType)); + + Assert::IsTrue(IsSameInstance(f.Device.Get(), actualDevice.Get())); + Assert::IsTrue(deviceType == WIN2D_GET_DEVICE_ASSOCIATION_TYPE::WIN2D_GET_DEVICE_ASSOCIATION_TYPE_CREATION_DEVICE); + } + TEST_METHOD_EX(CanvasVirtualBitmap_CreateNew_PassesCorrectAlphaModeThrough) { for (auto alphaMode : { CanvasAlphaMode::Premultiplied, CanvasAlphaMode::Ignore }) @@ -230,7 +246,7 @@ TEST_CLASS(CanvasVirtualBitmapUnitTest) CreatedFixture f; float realizedDpi = 0; - auto d2dImage = f.VirtualBitmap->GetD2DImage(nullptr, nullptr, GetImageFlags::None, 0.0f, &realizedDpi); + auto d2dImage = f.VirtualBitmap->GetD2DImage(nullptr, nullptr, WIN2D_GET_D2D_IMAGE_FLAGS_NONE, 0.0f, &realizedDpi); Assert::IsTrue(IsSameInstance(f.ImageSource.Get(), d2dImage.Get())); Assert::AreEqual(0.0f, realizedDpi); diff --git a/winrt/test.internal/graphics/PixelShaderEffectUnitTests.cpp b/winrt/test.internal/graphics/PixelShaderEffectUnitTests.cpp index 0b711534..c0dddcf0 100644 --- a/winrt/test.internal/graphics/PixelShaderEffectUnitTests.cpp +++ b/winrt/test.internal/graphics/PixelShaderEffectUnitTests.cpp @@ -73,7 +73,6 @@ TEST_CLASS(PixelShaderEffectUnitTests) Assert::AreEqual(RO_E_CLOSED, As*>>(properties)->First(&iterator)); } - struct Fixture { ComPtr Factory; @@ -154,7 +153,7 @@ TEST_CLASS(PixelShaderEffectUnitTests) auto effect = Make(nullptr, nullptr, sharedState.Get()); // Realize the effect. - effect->GetD2DImage(f.CanvasDevice.Get(), f.DeviceContext.Get(), GetImageFlags::None, 0, nullptr); + effect->GetD2DImage(f.CanvasDevice.Get(), f.DeviceContext.Get(), WIN2D_GET_D2D_IMAGE_FLAGS_NONE, 0, nullptr); // This should have passed the constant buffer through to D2D. Assert::AreEqual(constants, f.EffectPropertyValues[(int)PixelShaderEffectProperty::Constants]); @@ -254,7 +253,7 @@ TEST_CLASS(PixelShaderEffectUnitTests) ThrowIfFailed(properties->Insert(HStringReference(L"foo").Get(), Make>(3).Get(), &replaced)); // Realize the effect. - effect->GetD2DImage(f.CanvasDevice.Get(), f.DeviceContext.Get(), GetImageFlags::None, 0, nullptr); + effect->GetD2DImage(f.CanvasDevice.Get(), f.DeviceContext.Get(), WIN2D_GET_D2D_IMAGE_FLAGS_NONE, 0, nullptr); // This should have passed the constant buffer containing 3 through to D2D. auto& d2dConstants = f.GetEffectPropertyValue(PixelShaderEffectProperty::Constants); @@ -282,7 +281,7 @@ TEST_CLASS(PixelShaderEffectUnitTests) ThrowIfFailed(effect->put_MaxSamplerOffset(23)); // Realize the effect. - effect->GetD2DImage(f.CanvasDevice.Get(), f.DeviceContext.Get(), GetImageFlags::None, 0, nullptr); + effect->GetD2DImage(f.CanvasDevice.Get(), f.DeviceContext.Get(), WIN2D_GET_D2D_IMAGE_FLAGS_NONE, 0, nullptr); // This should have passed the coordinate mapping state through to D2D. auto& d2dMapping = f.GetEffectPropertyValue(PixelShaderEffectProperty::CoordinateMapping); @@ -319,7 +318,7 @@ TEST_CLASS(PixelShaderEffectUnitTests) ThrowIfFailed(effect->put_Source2Interpolation(CanvasImageInterpolation::Anisotropic)); // Realize the effect. - effect->GetD2DImage(f.CanvasDevice.Get(), f.DeviceContext.Get(), GetImageFlags::None, 0, nullptr); + effect->GetD2DImage(f.CanvasDevice.Get(), f.DeviceContext.Get(), WIN2D_GET_D2D_IMAGE_FLAGS_NONE, 0, nullptr); // This should have passed the interpolation mode state through to D2D. auto& d2dInterpolation = f.GetEffectPropertyValue(PixelShaderEffectProperty::SourceInterpolation); @@ -335,6 +334,52 @@ TEST_CLASS(PixelShaderEffectUnitTests) Assert::AreEqual(D2D1_FILTER_ANISOTROPIC, d2dInterpolation.Filter[0]); Assert::AreEqual(D2D1_FILTER_MIN_MAG_MIP_POINT, d2dInterpolation.Filter[1]); } + + TEST_METHOD_EX(PixelShaderEffect_RealizeAndGetDevice_FromICanvasImageInterop) + { + Fixture f; + + auto sharedState = MakeSharedShaderState(); + auto effect = Make(nullptr, nullptr, sharedState.Get()); + + ComPtr canvasDevice; + WIN2D_GET_DEVICE_ASSOCIATION_TYPE deviceType = WIN2D_GET_DEVICE_ASSOCIATION_TYPE::WIN2D_GET_DEVICE_ASSOCIATION_TYPE_UNSPECIFIED; + ThrowIfFailed(As(effect)->GetDevice(&canvasDevice, &deviceType)); + + // The canvas device is just null initially (calling GetDevice should still succeed though) + Assert::IsNull(canvasDevice.Get()); + Assert::IsTrue(deviceType == WIN2D_GET_DEVICE_ASSOCIATION_TYPE::WIN2D_GET_DEVICE_ASSOCIATION_TYPE_REALIZATION_DEVICE); + + f.CanvasDevice.Get()->AddRef(); + + // The only reference to the canvas device is the one in the test fixture + Assert::AreEqual(f.CanvasDevice.Get()->Release(), 1ul); + + ComPtr image; + ThrowIfFailed(As(effect)->GetD2DImage(f.CanvasDevice.Get(), f.DeviceContext.Get(), WIN2D_GET_D2D_IMAGE_FLAGS_NONE, 0, nullptr, &image)); + + f.CanvasDevice.Get()->AddRef(); + + // The canvas device is now also stored in the effect as the realization device + Assert::AreEqual(f.CanvasDevice.Get()->Release(), 2ul); + + // The resulting image should not be null if the method returned S_OK + Assert::IsNotNull(image.Get()); + + deviceType = WIN2D_GET_DEVICE_ASSOCIATION_TYPE::WIN2D_GET_DEVICE_ASSOCIATION_TYPE_UNSPECIFIED; + ThrowIfFailed(As(effect)->GetDevice(&canvasDevice, &deviceType)); + + // Device type should just always be the same + Assert::IsTrue(deviceType == WIN2D_GET_DEVICE_ASSOCIATION_TYPE::WIN2D_GET_DEVICE_ASSOCIATION_TYPE_REALIZATION_DEVICE); + + f.CanvasDevice.Get()->AddRef(); + + // The canvas device now has 3 references (this ensures that ICanvasImageInterop::GetDevice also calls AddRef() on it) + Assert::AreEqual(f.CanvasDevice.Get()->Release(), 3ul); + + // The realization device should match + Assert::AreEqual(f.CanvasDevice.Get(), canvasDevice.Get()); + } }; diff --git a/winrt/test.internal/mocks/MockCanvasDevice.h b/winrt/test.internal/mocks/MockCanvasDevice.h index f8567d47..390c3afb 100644 --- a/winrt/test.internal/mocks/MockCanvasDevice.h +++ b/winrt/test.internal/mocks/MockCanvasDevice.h @@ -69,7 +69,9 @@ namespace canvas CALL_COUNTER_WITH_MOCK(GetDeviceRemovedErrorCodeMethod, HRESULT()); + CALL_COUNTER_WITH_MOCK(IsDeviceLost2Method, HRESULT(boolean*)); CALL_COUNTER_WITH_MOCK(IsDeviceLostMethod, HRESULT(int, boolean*)); + CALL_COUNTER_WITH_MOCK(GetDeviceLostReasonMethod, HRESULT(int*)); #if WINVER > _WIN32_WINNT_WINBLUE CALL_COUNTER_WITH_MOCK(CreateGradientMeshMethod, ComPtr(D2D1_GRADIENT_MESH_PATCH const*, UINT32)); @@ -142,6 +144,12 @@ namespace canvas return remove_DeviceLostMethod.WasCalled(token); } + IFACEMETHODIMP IsDeviceLost2( + boolean* value) + { + return IsDeviceLost2Method.WasCalled(value); + } + IFACEMETHODIMP IsDeviceLost( int hresult, boolean* value) @@ -149,6 +157,12 @@ namespace canvas return IsDeviceLostMethod.WasCalled(hresult, value); } + IFACEMETHODIMP GetDeviceLostReason( + int* hresult) + { + return GetDeviceLostReasonMethod.WasCalled(hresult); + } + IFACEMETHODIMP RaiseDeviceLost() { return RaiseDeviceLostMethod.WasCalled(); diff --git a/winrt/test.managed/NativeExportTests.cs b/winrt/test.managed/NativeExportTests.cs new file mode 100644 index 00000000..6331d28d --- /dev/null +++ b/winrt/test.managed/NativeExportTests.cs @@ -0,0 +1,290 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +using Microsoft.Graphics.Canvas; +using Microsoft.Graphics.Canvas.Effects; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Numerics; +using System.Runtime.InteropServices; +using Windows.Foundation; +using Windows.UI; +using WinRT; + +namespace test.managed +{ + [TestClass] + public unsafe class NativeExportTests + { + private static Guid UuidOfICanvasImageInterop = new Guid("E042D1F7-F9AD-4479-A713-67627EA31863"); + private static Guid UuidOfICanvasResourceCreator = new Guid("8F6D8AA8-492F-4BC6-B3D0-E7F5EAE84B11"); + private static Guid UuidOfICanvasResourceCreatorWithDpi = new Guid("1A75B512-E9FA-49E6-A876-38CAE194013E"); + private static Guid UuidOfICanvasEffect = new Guid("0EF96F8C-9B5E-4BF0-A399-AAD8CE53DB55"); + + [TestMethod] + public void GetBoundsForICanvasImageInterop_IsExportedCorrectly() + { + CanvasDevice canvasDevice = new CanvasDevice(); + + // Create a dummy 256x256 image + CanvasBitmap canvasBitmap = CanvasBitmap.CreateFromColors( + resourceCreator: canvasDevice, + colors: new Color[256 * 256], + widthInPixels: 256, + heightInPixels: 256, + dpi: 96.0f, + alpha: CanvasAlphaMode.Ignore); + + // Create some effect around it + InvertEffect invertEffect = new InvertEffect { Source = canvasBitmap }; + + // Get the ICanvasResourceCreator and ICanvasImageInterop objects for the device and effect + using (ComPtr canvasDeviceUnknown = ComPtr.FromObject(canvasDevice)) + using (ComPtr invertEffectUnknown = ComPtr.FromObject(invertEffect)) + using (ComPtr canvasDeviceResourceCreator = canvasDeviceUnknown.AsRIID(UuidOfICanvasResourceCreator)) + using (ComPtr invertEffectInterop = invertEffectUnknown.AsRIID(UuidOfICanvasImageInterop)) + { + Rect rect; + + // Invoke GetBoundsForICanvasImageInterop with the test objects + int hresult = GetBoundsForICanvasImageInterop( + resourceCreator: canvasDeviceResourceCreator.ThisPtr, + image: invertEffectInterop.ThisPtr, + transform: null, + rect: &rect); + + // The call must have been successful, and the size must match + Assert.AreEqual(0, hresult); + Assert.AreEqual(new Rect(0, 0, canvasBitmap.Size.Width, canvasBitmap.Size.Height), rect); + } + } + + [TestMethod] + public void InvalidateSourceRectangleForICanvasImageInterop_IsExportedCorrectly() + { + CanvasDevice canvasDevice = new CanvasDevice(); + + // Create a dummy 256x256 image + CanvasBitmap canvasBitmap = CanvasBitmap.CreateFromColors( + resourceCreator: canvasDevice, + colors: new Color[256 * 256], + widthInPixels: 256, + heightInPixels: 256, + dpi: 96.0f, + alpha: CanvasAlphaMode.Ignore); + + // Create some effect around it + InvertEffect invertEffect = new InvertEffect { Source = canvasBitmap }; + + // Create a render target and a drawing surface on it + CanvasRenderTarget canvasRenderTarget = new CanvasRenderTarget( + resourceCreator: canvasDevice, + width: 256, + height: 256, + dpi: 96.0f); + + // Get the ICanvasResourceCreator and ICanvasImageInterop objects for the device and effect + using (CanvasDrawingSession canvasDrawingSession = canvasRenderTarget.CreateDrawingSession()) + using (ComPtr canvasDrawingSessionUnknown = ComPtr.FromObject(canvasDrawingSession)) + using (ComPtr invertEffectUnknown = ComPtr.FromObject(invertEffect)) + using (ComPtr canvasDrawingSessionResourceCreatorWithDpi = canvasDrawingSessionUnknown.AsRIID(UuidOfICanvasResourceCreatorWithDpi)) + using (ComPtr invertEffectInterop = invertEffectUnknown.AsRIID(UuidOfICanvasImageInterop)) + { + Rect invalidRectangle = default; + + // Invoke InvalidateSourceRectangleForICanvasImageInterop with the test objects + int hresult = InvalidateSourceRectangleForICanvasImageInterop( + resourceCreator: canvasDrawingSessionResourceCreatorWithDpi.ThisPtr, + image: invertEffectInterop.ThisPtr, + sourceIndex: 0, + invalidRectangle: &invalidRectangle); + + // The call must have been successful + Assert.AreEqual(0, hresult); + } + } + + [TestMethod] + public void GetInvalidRectanglesForICanvasImageInterop_IsExportedCorrectly() + { + CanvasDevice canvasDevice = new CanvasDevice(); + + // Create a dummy 256x256 image + CanvasBitmap canvasBitmap = CanvasBitmap.CreateFromColors( + resourceCreator: canvasDevice, + colors: new Color[256 * 256], + widthInPixels: 256, + heightInPixels: 256, + dpi: 96.0f, + alpha: CanvasAlphaMode.Ignore); + + // Create some effect around it + InvertEffect invertEffect = new InvertEffect { Source = canvasBitmap }; + + // Create a render target and a drawing surface on it + CanvasRenderTarget canvasRenderTarget = new CanvasRenderTarget( + resourceCreator: canvasDevice, + width: 256, + height: 256, + dpi: 96.0f); + + // Get the ICanvasResourceCreator and ICanvasImageInterop objects for the device and effect + using (CanvasDrawingSession canvasDrawingSession = canvasRenderTarget.CreateDrawingSession()) + using (ComPtr canvasDrawingSessionUnknown = ComPtr.FromObject(canvasDrawingSession)) + using (ComPtr invertEffectUnknown = ComPtr.FromObject(invertEffect)) + using (ComPtr canvasDrawingSessionResourceCreatorWithDpi = canvasDrawingSessionUnknown.AsRIID(UuidOfICanvasResourceCreatorWithDpi)) + using (ComPtr invertEffectInterop = invertEffectUnknown.AsRIID(UuidOfICanvasImageInterop)) + { + uint count = 1; + Rect rect = default; + Rect* rectPtr = ▭ + + // Invoke GetInvalidRectanglesForICanvasImageInterop with the test objects + int hresult = GetInvalidRectanglesForICanvasImageInterop( + resourceCreator: canvasDrawingSessionResourceCreatorWithDpi.ThisPtr, + image: invertEffectInterop.ThisPtr, + valueCount: &count, + valueElements: &rectPtr); + + // The call must have been successful + Assert.AreEqual(0, hresult); + } + } + + [TestMethod] + public void GetRequiredSourceRectanglesForICanvasImageInterop_IsExportedCorrectly() + { + CanvasDevice canvasDevice = new CanvasDevice(); + + // Create a dummy 256x256 image + CanvasBitmap canvasBitmap = CanvasBitmap.CreateFromColors( + resourceCreator: canvasDevice, + colors: new Color[256 * 256], + widthInPixels: 256, + heightInPixels: 256, + dpi: 96.0f, + alpha: CanvasAlphaMode.Ignore); + + // Create some effects around it + InvertEffect invertEffect1 = new InvertEffect { Source = canvasBitmap }; + InvertEffect invertEffect2 = new InvertEffect { Source = invertEffect1 }; + + // Create a render target and a drawing surface on it + CanvasRenderTarget canvasRenderTarget = new CanvasRenderTarget( + resourceCreator: canvasDevice, + width: 256, + height: 256, + dpi: 96.0f); + + // Get the ICanvasResourceCreator and ICanvasImageInterop objects for the device and effects + using (CanvasDrawingSession canvasDrawingSession = canvasRenderTarget.CreateDrawingSession()) + using (ComPtr canvasDrawingSessionUnknown = ComPtr.FromObject(canvasDrawingSession)) + using (ComPtr invertEffect1Unknown = ComPtr.FromObject(invertEffect1)) + using (ComPtr invertEffect2Unknown = ComPtr.FromObject(invertEffect2)) + using (ComPtr canvasDrawingSessionResourceCreatorWithDpi = canvasDrawingSessionUnknown.AsRIID(UuidOfICanvasResourceCreatorWithDpi)) + using (ComPtr invertEffect2Interop = invertEffect2Unknown.AsRIID(UuidOfICanvasImageInterop)) + using (ComPtr invertEffect1Abi = invertEffect1Unknown.AsRIID(UuidOfICanvasEffect)) + { + Rect outputRect = new Rect(0, 0, 256, 256); + uint sourceIndices = 0; + Rect sourceBounds = new Rect(0, 0, 256, 256); + Rect resultRect; + + // Invoke GetRequiredSourceRectanglesForICanvasImageInterop with the test objects + int hresult = GetRequiredSourceRectanglesForICanvasImageInterop( + resourceCreator: canvasDrawingSessionResourceCreatorWithDpi.ThisPtr, + image: invertEffect2Interop.ThisPtr, + outputRectangle: &outputRect, + sourceEffectCount: 1, + sourceEffects: &invertEffect1Abi.ThisPtr, + sourceIndexCount: 1, + sourceIndices: &sourceIndices, + sourceBoundsCount: 1, + sourceBounds: &sourceBounds, + valueCount: 1, + valueElements: &resultRect); + + // The call must have been successful + Assert.AreEqual(0, hresult); + } + } + + [DllImport("Microsoft.Graphics.Canvas.dll", PreserveSig = true, ExactSpelling = true)] + public static extern int GetBoundsForICanvasImageInterop(void* resourceCreator, void* image, Matrix3x2* transform, Rect* rect); + + [DllImport("Microsoft.Graphics.Canvas.dll", PreserveSig = true, ExactSpelling = true)] + public static extern int InvalidateSourceRectangleForICanvasImageInterop( + void* resourceCreator, + void* image, + uint sourceIndex, + Rect* invalidRectangle); + + [DllImport("Microsoft.Graphics.Canvas.dll", PreserveSig = true, ExactSpelling = true)] + public static extern int GetInvalidRectanglesForICanvasImageInterop( + void* resourceCreator, + void* image, + uint* valueCount, + Rect** valueElements); + + [DllImport("Microsoft.Graphics.Canvas.dll", PreserveSig = true, ExactSpelling = true)] + public static extern int GetRequiredSourceRectanglesForICanvasImageInterop( + void* resourceCreator, + void* image, + Rect* outputRectangle, + uint sourceEffectCount, + void** sourceEffects, + uint sourceIndexCount, + uint* sourceIndices, + uint sourceBoundsCount, + Rect* sourceBounds, + uint valueCount, + Rect* valueElements); + + /// + /// A small helper for working with COM types. + /// + private struct ComPtr : IDisposable + { + /// + /// The underlying IUnknown object. + /// + public void* ThisPtr; + + /// + /// Creates a new instance from the input RCW. + /// + /// The input RCW to unwrap. + /// The resulting instance. + public static ComPtr FromObject(object obj) + { + return new ComPtr { ThisPtr = (void*)((IWinRTObject)obj).NativeObject.GetRef() }; + } + + /// + /// Invokes QueryInterface for a given RIID. + /// + /// The target RIID. + /// The resulting instance. + public ComPtr AsRIID(Guid guid) + { + Marshal.ThrowExceptionForHR(Marshal.QueryInterface((IntPtr)ThisPtr, ref guid, out IntPtr ppv)); + + return new ComPtr { ThisPtr = (void*)ppv }; + } + + /// + public void Dispose() + { + if (ThisPtr != null) + { + void* thisPtr = ThisPtr; + + ThisPtr = null; + + Marshal.Release((IntPtr)thisPtr); + } + } + } + } +} diff --git a/winrt/test.managed/winrt.test.managed.uap.csproj b/winrt/test.managed/winrt.test.managed.uap.csproj index 7120c681..b2f9d01a 100644 --- a/winrt/test.managed/winrt.test.managed.uap.csproj +++ b/winrt/test.managed/winrt.test.managed.uap.csproj @@ -19,6 +19,7 @@ true true 10.0.17763.0 + true @@ -121,6 +122,7 @@ +