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
This commit is contained in:
Sergio Pedri 2023-03-10 11:08:34 +00:00
Родитель b340c3a009
Коммит a8fec6c69a
37 изменённых файлов: 2041 добавлений и 221 удалений

Просмотреть файл

@ -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)

Просмотреть файл

@ -55,7 +55,7 @@ IFACEMETHODIMP CanvasBrush::get_Device(ICanvasDevice** value)
return ExceptionBoundary(
[&]
{
CheckInPointer(value);
CheckAndClearOutPointer(value);
ThrowIfFailed(m_device.EnsureNotClosed().CopyTo(value));
});
}

Просмотреть файл

@ -118,7 +118,7 @@ void CanvasImageBrush::SetImage(ICanvasImage* image)
else
{
// Use an image brush.
auto d2dImage = As<ICanvasImageInternal>(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<ID2D1Brush> 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<ID2D1Image> d2dImage;

Просмотреть файл

@ -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);
};
}}}}}

Просмотреть файл

@ -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)]

Просмотреть файл

@ -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<CanvasLock>(As<ID2D1Multithread>(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<D2D1DeviceContextLease>(this).CopyTo(lease));
});
}
//
// ID2D1DeviceContextLease
//
IFACEMETHODIMP D2D1DeviceContextLease::GetD2DDeviceContext(ID2D1DeviceContext** deviceContext)
{
return ExceptionBoundary(
[&]
{
CheckAndClearOutPointer(deviceContext);
ThrowIfFailed(m_deviceContext->QueryInterface(IID_PPV_ARGS(deviceContext)));
});
}
ComPtr<ID2D1GradientStopCollection1> CanvasDevice::CreateGradientStopCollection(
std::vector<D2D1_GRADIENT_STOP>&& stops,
D2D1_COLOR_SPACE preInterpolationSpace,

Просмотреть файл

@ -187,7 +187,8 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas
CloakedIid<ICanvasDeviceInternal>,
ICanvasResourceCreator,
IDirect3DDevice,
CloakedIid<IDirect3DDxgiInterfaceAccess>)
CloakedIid<IDirect3DDxgiInterfaceAccess>,
CloakedIid<ID2D1DeviceContextPool>)
{
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<RuntimeClassFlags<ClassicCom>, 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

Просмотреть файл

@ -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<ICanvasImageInternal>(image);
auto d2dImage = internalImage->GetD2DImage(m_canvasDevice, m_deviceContext);
ComPtr<ID2D1Image> d2dImage = ICanvasImageInternal::GetD2DImageFromInternalOrInteropSource(image, m_canvasDevice, m_deviceContext);
auto d2dInterpolationMode = static_cast<D2D1_INTERPOLATION_MODE>(m_interpolation);
auto d2dCompositeMode = composite ? static_cast<D2D1_COMPOSITE_MODE>(*composite)
@ -3725,7 +3724,7 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas
return ExceptionBoundary(
[&]
{
CheckInPointer(value);
CheckAndClearOutPointer(value);
ThrowIfFailed(GetDevice().CopyTo(value));
});

Просмотреть файл

@ -579,7 +579,7 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas
return ExceptionBoundary(
[&]
{
CheckInPointer(value);
CheckAndClearOutPointer(value);
auto& device = m_device.EnsureNotClosed();

Просмотреть файл

@ -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<ICanvasDevice>), 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<ID2D1Image> CanvasEffect::GetD2DImage(ICanvasDevice* device, ID2D1DeviceContext* deviceContext, GetImageFlags flags, float targetDpi, float* realizedDpi)
ComPtr<ID2D1Image> 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<ICanvasDevice> 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<ID2D1Effect> RealizeEffect(ICanvasEffect* effect)
static HRESULT InvalidateSourceRectangle(
ICanvasResourceCreatorWithDpi* resourceCreator,
IUnknown* image,
uint32_t sourceIndex,
Rect const* invalidRectangle)
{
auto imageInternal = As<ICanvasImageInternal>(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<D2D1_RECT_F> result(invalidRectangleCount);
ThrowIfFailed(realizationContext->GetEffectInvalidRectangles(d2dEffect.Get(), result.data(), invalidRectangleCount));
// Return the array.
auto resultArray = TransformToComArray<Rect>(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<D2D1_EFFECT_INPUT_DESCRIPTION> inputDescriptions;
std::vector<ComPtr<ID2D1Effect>> 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<ID2D1Effect> RealizeEffect(IUnknown* effect)
{
auto realizedEffect = ICanvasImageInternal::GetD2DImageFromInternalOrInteropSource(effect, m_device.Get(), m_deviceContext.Get(), m_flags, m_dpi);
return As<ID2D1Effect>(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<IUnknown*>(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<D2D1_RECT_F> result(invalidRectangleCount);
ThrowIfFailed(realizationContext->GetEffectInvalidRectangles(d2dEffect.Get(), result.data(), invalidRectangleCount));
// Return the array.
auto resultArray = TransformToComArray<Rect>(result.begin(), result.end(), FromD2DRect);
resultArray.Detach(valueCount, valueElements);
});
return EffectRealizationContext::GetInvalidRectangles(resourceCreator, reinterpret_cast<IUnknown*>(this), valueCount, valueElements);
}
static std::vector<D2D1_RECT_F> 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<D2D1_EFFECT_INPUT_DESCRIPTION> inputDescriptions;
std::vector<ComPtr<ID2D1Effect>> 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<D2D1_RECT_F> 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<IUnknown*>(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<D2D1_RECT_F> result(sourceEffectCount);
auto result = GetRequiredSourceRectanglesImpl(this, resourceCreator, outputRectangle, sourceEffectCount, sourceEffects, sourceIndices, sourceBounds);
EffectRealizationContext::GetRequiredSourceRectangles(
resourceCreator,
reinterpret_cast<IUnknown*>(this),
&outputRectangle,
sourceEffectCount,
sourceEffects,
sourceIndexCount,
sourceIndices,
sourceBoundsCount,
sourceBounds,
sourceEffectCount,
result.data());
auto resultArray = TransformToComArray<Rect>(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<D2D1_RECT_F> 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<ID2D1Image>& inputImage, float inputDpi, GetImageFlags flags, float targetDpi, ID2D1DeviceContext* deviceContext)
bool CanvasEffect::ApplyDpiCompensation(unsigned int index, ComPtr<ID2D1Image>& 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<ICanvasImageInternal>(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<ID2D1Image> 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<ICanvasImageInternal> internalSource;
ComPtr<ICanvasImageInterop> 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<ICanvasResourceWrapperWithDevice>(source);
if (sourceWithDevice)
{
ComPtr<ICanvasDevice> 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<ICanvasDevice> 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<ICanvasResourceWrapperWithDevice>(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());

Просмотреть файл

@ -37,7 +37,7 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas { na
IGraphicsEffectD2D1Interop,
ICanvasEffect,
ICanvasImage,
CloakedIid<ICanvasImageInternal>,
ChainInterfaces<CloakedIid<ICanvasImageInternal>, CloakedIid<ICanvasImageInterop>>,
ChainInterfaces<
MixIn<CanvasEffect, ResourceWrapper<ID2D1Effect, CanvasEffect, IGraphicsEffect>>,
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<ID2D1Image> GetD2DImage(ICanvasDevice* device, ID2D1DeviceContext* deviceContext, GetImageFlags flags, float targetDpi, float* realizedDpi = nullptr) override;
virtual ComPtr<ID2D1Image> 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<ID2D1Effect> CreateD2DEffect(ID2D1DeviceContext* deviceContext, IID const& effectId);
bool ApplyDpiCompensation(unsigned int index, ComPtr<ID2D1Image>& inputImage, float inputDpi, GetImageFlags flags, float targetDpi, ID2D1DeviceContext* deviceContext);
void RefreshInputs(GetImageFlags flags, float targetDpi, ID2D1DeviceContext* deviceContext);
bool ApplyDpiCompensation(unsigned int index, ComPtr<ID2D1Image>& 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<IGraphicsEffectSource> GetD2DInput(ID2D1Effect* d2dEffect, unsigned int index);
void SetProperty(unsigned int index, IPropertyValue* propertyValue);

Просмотреть файл

@ -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()))

Просмотреть файл

@ -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;

Просмотреть файл

@ -337,7 +337,7 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas
RuntimeClassFlags<WinRtClassicComMix>,
ICanvasResourceCreator,
ICanvasResourceCreatorWithDpi,
CloakedIid<ICanvasImageInternal>,
ChainInterfaces<CloakedIid<ICanvasImageInternal>, CloakedIid<ICanvasImageInterop>>,
CloakedIid<ICanvasBitmapInternal>,
CloakedIid<IDirect3DDxgiInterfaceAccess>,
CloakedIid<ICanvasResourceWrapperWithDevice>>,
@ -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<ID2D1Image> GetD2DImage(ICanvasDevice*, ID2D1DeviceContext*, GetImageFlags, float /*targetDpi*/, float* realizedDpi) override
virtual ComPtr<ID2D1Image> GetD2DImage(ICanvasDevice*, ID2D1DeviceContext*, WIN2D_GET_D2D_IMAGE_FLAGS, float /*targetDpi*/, float* realizedDpi) override
{
if (realizedDpi)
*realizedDpi = m_dpi;

Просмотреть файл

@ -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<ID2D1Image> CanvasCommandList::GetD2DImage(
ICanvasDevice* device,
ID2D1DeviceContext* deviceContext,
GetImageFlags,
WIN2D_GET_D2D_IMAGE_FLAGS,
float /*targetDpi*/,
float* realizedDpi)
{

Просмотреть файл

@ -11,7 +11,7 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas
CanvasCommandList,
ICanvasCommandList,
ICanvasImage,
CloakedIid<ICanvasImageInternal>,
ChainInterfaces<CloakedIid<ICanvasImageInternal>, CloakedIid<ICanvasImageInterop>>,
CloakedIid<ICanvasResourceWrapperWithDevice>,
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<ID2D1Image> GetD2DImage(
ICanvasDevice* device,
ID2D1DeviceContext* deviceContext,
GetImageFlags flags,
WIN2D_GET_D2D_IMAGE_FLAGS flags,
float targetDpi,
float* realizedDpi) override;

Просмотреть файл

@ -23,17 +23,33 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas
return As<ICanvasDeviceInternal>(device)->GetResourceCreationDeviceContext();
}
template <typename T>
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<ICanvasImageInterop, T>::value);
ComPtr<ICanvasDevice> device;
ThrowIfFailed(resourceCreator->get_Device(&device));
auto d2dDeviceContext = GetDeviceContextForGetBounds(device.Get(), resourceCreator);
auto d2dImage = imageInternal->GetD2DImage(device.Get(), d2dDeviceContext.Get());
ComPtr<ID2D1Image> d2dImage;
// Check if T is ICanvasImageInternal. If so, we can call GetD2DImage without a QueryInterface call.
if constexpr (std::is_same<ICanvasImageInternal, T>::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<ID2D1Device>(canvasDevice);
auto d2dImage = As<ICanvasImageInternal>(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<ICanvasImageInternal>(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)
{

Просмотреть файл

@ -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<ID2D1Image> 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<ID2D1Image> 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<ID2D1Image> d2dImage;
ComPtr<ICanvasImageInternal> 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<ICanvasImageInterop>(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,

Просмотреть файл

@ -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<ID2D1Image> CanvasVirtualBitmap::GetD2DImage(ICanvasDevice* , ID2D1DeviceContext*, GetImageFlags, float, float* realizedDpi)
ComPtr<ID2D1Image> CanvasVirtualBitmap::GetD2DImage(ICanvasDevice* , ID2D1DeviceContext*, WIN2D_GET_D2D_IMAGE_FLAGS, float, float* realizedDpi)
{
if (realizedDpi)
*realizedDpi = 0;

Просмотреть файл

@ -85,7 +85,7 @@ namespace ABI { namespace Microsoft { namespace Graphics { namespace Canvas
ICanvasVirtualBitmap,
ICanvasImage,
IGraphicsEffectSource,
CloakedIid<ICanvasImageInternal>,
ChainInterfaces<CloakedIid<ICanvasImageInternal>, CloakedIid<ICanvasImageInterop>>,
CloakedIid<ICanvasResourceWrapperWithDevice>)
{
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<ID2D1Image> GetD2DImage(ICanvasDevice* , ID2D1DeviceContext*, GetImageFlags, float, float*) override;
ComPtr<ID2D1Image> GetD2DImage(ICanvasDevice* , ID2D1DeviceContext*, WIN2D_GET_D2D_IMAGE_FLAGS, float, float*) override;
};
}}}}

Просмотреть файл

@ -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 <windows.h>
// Undef GetCurrentTime because the Win32 API in windows.h collides with Storyboard.GetCurrentTime

Просмотреть файл

@ -46,6 +46,7 @@
<AdditionalIncludeDirectories>..\Inc;$(EtwDirectory);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>ENABLE_WINRT_EXPERIMENTAL_TYPES=1;WRT_EXPORT;WINUI3;WIN2D_VERSION="$(Win2DVersion)";%(PreprocessorDefinitions)</PreprocessorDefinitions>
<WarningLevel>Level4</WarningLevel>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>

Просмотреть файл

@ -5,8 +5,19 @@
#pragma once
#include <inspectable.h>
#include <windows.foundation.numerics.h>
#include <wrl.h>
#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;
}
}
}
}

Просмотреть файл

@ -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<ABI::Microsoft::Graphics::Canvas::ID2D1DeviceContextPool>(canvasDevice);
// Getting a device context lease should always succeed
ComPtr<ABI::Microsoft::Graphics::Canvas::ID2D1DeviceContextLease> 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<ID2D1DeviceContext> 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<ID2D1DeviceContext> deviceContext2;
ThrowIfFailed(deviceContextLease->GetD2DDeviceContext(&deviceContext2));
Assert::IsNotNull(deviceContext2.Get());
Assert::IsTrue(As<IUnknown>(deviceContext.Get()).Get() == As<IUnknown>(deviceContext2.Get()).Get());
// Get the device from the canvas device
ComPtr<ID2D1Device1> d2D1Device;
ThrowIfFailed(As<ABI::Microsoft::Graphics::Canvas::ICanvasResourceWrapperNative>(canvasDevice)->GetNativeResource(
As<ABI::Microsoft::Graphics::Canvas::ICanvasDevice>(canvasDevice).Get(),
0,
IID_PPV_ARGS(&d2D1Device)));
// Also get the device from the rented device context
ComPtr<ID2D1Device> 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<IUnknown>(d2D1Device.Get()).Get() == As<IUnknown>(d2D1DeviceFromDeviceContext.Get()).Get());
}
TEST_METHOD(CanvasDevice_GetSharedDevice_ReturnsExisting)
{
auto d1 = CanvasDevice::GetSharedDevice(false);

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -37,6 +37,8 @@
#include <ComArray.h>
#include <collection.h>
#include <windows.foundation.h>
#include <windowsnumerics.h>
#include <Microsoft.Graphics.Canvas.native.h>

Просмотреть файл

@ -48,6 +48,7 @@
<ClCompile Include="CanvasCreateResourcesEventArgsTests.cpp" />
<ClCompile Include="CanvasDeviceTests.cpp" />
<ClCompile Include="CanvasBitmapTests.cpp" />
<ClCompile Include="CanvasImageInteropTests.cpp" />
<ClCompile Include="CanvasSvgAttributeTests.cpp" />
<ClCompile Include="CanvasVirtualBitmapTests.cpp" />
<ClCompile Include="CanvasEffectsTests.cpp" />

Просмотреть файл

@ -41,6 +41,7 @@
<ClCompile Include="ColorManagementProfileTests.cpp" />
<ClCompile Include="CanvasSvgDocumentTests.cpp" />
<ClCompile Include="CanvasSvgAttributeTests.cpp" />
<ClCompile Include="CanvasImageInteropTests.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />

Просмотреть файл

@ -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<ICanvasDevice*>(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<ICanvasDevice> actualDevice;
WIN2D_GET_DEVICE_ASSOCIATION_TYPE deviceType = WIN2D_GET_DEVICE_ASSOCIATION_TYPE::WIN2D_GET_DEVICE_ASSOCIATION_TYPE_UNSPECIFIED;
As<ICanvasImageInterop>(bitmap)->GetDevice(&actualDevice, &deviceType);
Assert::AreEqual<ICanvasDevice*>(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;

Просмотреть файл

@ -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;

Просмотреть файл

@ -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<TestEffect>(m_blurGuid, 0, 1);
auto mockD2DEffect = Make<MockD2DEffectThatCountsCalls>();
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<ICanvasImageInterop>(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<ICanvasImageInterop>(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<ICanvasImageInterop>(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<TestEffect>(m_blurGuid, 0, 1);
auto mockD2DEffect = Make<MockD2DEffectThatCountsCalls>();
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<Rect> result;
Assert::AreEqual(E_INVALIDARG, GetInvalidRectanglesForICanvasImageInterop(nullptr, As<ICanvasImageInterop>(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<ICanvasImageInterop>(testEffect.Get()).Get(), nullptr, result.GetAddressOfData()));
Assert::AreEqual(E_INVALIDARG, GetInvalidRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As<ICanvasImageInterop>(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<ICanvasImageInterop>(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<TestEffect>(m_blurGuid, 0, 1);
auto inputEffect = Make<TestEffect>(m_blurGuid, 0, 1);
testEffect->SetSource(0, inputEffect.Get());
std::vector<ComPtr<MockD2DEffectThatCountsCalls>> mockEffects;
f.m_deviceContext->CreateEffectMethod.SetExpectedCalls(2,
[&](IID const& effectId, ID2D1Effect** effect)
{
mockEffects.push_back(Make<MockD2DEffectThatCountsCalls>(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<ICanvasImageInterop>(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<ICanvasImageInterop>(testEffect.Get()).Get(), &outputRect, 1, nullptr, 1, &inputIndex, 1, &inputBounds, 1, &result));
Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As<ICanvasImageInterop>(testEffect.Get()).Get(), &outputRect, 1, &nullEffects, 1, &inputIndex, 1, &inputBounds, 1, &result));
Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As<ICanvasImageInterop>(testEffect.Get()).Get(), &outputRect, 1, &inputEffects, 1, &invalidIndex, 1, &inputBounds, 1, &result));
Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As<ICanvasImageInterop>(testEffect.Get()).Get(), &outputRect, 1, &inputEffects, 0, &inputIndex, 1, &inputBounds, 1, &result));
Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As<ICanvasImageInterop>(testEffect.Get()).Get(), &outputRect, 1, &inputEffects, 1, &inputIndex, 0, &inputBounds, 1, &result));
Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As<ICanvasImageInterop>(testEffect.Get()).Get(), &outputRect, 1, &inputEffects, 1, &inputIndex, 1, &inputBounds, 0, &result));
Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As<ICanvasImageInterop>(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<ICanvasImageInterop>(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<TestEffect>(m_blurGuid, 0, 2);
auto inputEffect1 = Make<TestEffect>(m_blurGuid, 0, 1);
auto inputEffect2 = Make<TestEffect>(m_blurGuid, 0, 1);
testEffect->SetSource(0, inputEffect1.Get());
testEffect->SetSource(1, inputEffect2.Get());
std::vector<ComPtr<MockD2DEffectThatCountsCalls>> mockEffects;
f.m_deviceContext->CreateEffectMethod.SetExpectedCalls(3,
[&](IID const& effectId, ID2D1Effect** effect)
{
mockEffects.push_back(Make<MockD2DEffectThatCountsCalls>(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<ICanvasImageInterop>(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<ICanvasImageInterop>(testEffect.Get()).Get(), &outputRect, 2, nullptr, 2, inputIndices, 2, inputBounds, 2, results));
Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As<ICanvasImageInterop>(testEffect.Get()).Get(), &outputRect, 2, inputEffects, 2, nullptr, 2, inputBounds, 2, results));
Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As<ICanvasImageInterop>(testEffect.Get()).Get(), &outputRect, 2, inputEffects, 2, inputIndices, 2, nullptr, 2, results));
Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As<ICanvasImageInterop>(testEffect.Get()).Get(), &outputRect, 2, inputEffects, 2, inputIndices, 2, inputBounds, 0, results));
Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As<ICanvasImageInterop>(testEffect.Get()).Get(), &outputRect, 2, inputEffects, 2, inputIndices, 2, inputBounds, 2, nullptr));
Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As<ICanvasImageInterop>(testEffect.Get()).Get(), &outputRect, 1, inputEffects, 2, inputIndices, 2, inputBounds, 2, results));
Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As<ICanvasImageInterop>(testEffect.Get()).Get(), &outputRect, 2, inputEffects, 1, inputIndices, 2, inputBounds, 2, results));
Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As<ICanvasImageInterop>(testEffect.Get()).Get(), &outputRect, 2, inputEffects, 2, inputIndices, 1, inputBounds, 2, results));
Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As<ICanvasImageInterop>(testEffect.Get()).Get(), &outputRect, 2, inputEffects, 2, badIndices1, 2, inputBounds, 2, results));
Assert::AreEqual(E_INVALIDARG, GetRequiredSourceRectanglesForICanvasImageInterop(f.m_drawingSession.Get(), As<ICanvasImageInterop>(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<ICanvasImageInterop>(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<TestEffect> m_testEffect;

Просмотреть файл

@ -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);
}

Просмотреть файл

@ -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<ICanvasDevice> actualDevice;
WIN2D_GET_DEVICE_ASSOCIATION_TYPE deviceType = WIN2D_GET_DEVICE_ASSOCIATION_TYPE::WIN2D_GET_DEVICE_ASSOCIATION_TYPE_UNSPECIFIED;
ThrowIfFailed(As<ICanvasImageInterop>(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);

Просмотреть файл

@ -73,7 +73,6 @@ TEST_CLASS(PixelShaderEffectUnitTests)
Assert::AreEqual(RO_E_CLOSED, As<IIterable<IKeyValuePair<HSTRING, IInspectable*>*>>(properties)->First(&iterator));
}
struct Fixture
{
ComPtr<MockD2DFactory> Factory;
@ -154,7 +153,7 @@ TEST_CLASS(PixelShaderEffectUnitTests)
auto effect = Make<PixelShaderEffect>(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<Nullable<int>>(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<int>(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<CoordinateMappingState>(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<SourceInterpolationState>(PixelShaderEffectProperty::SourceInterpolation);
@ -335,6 +334,52 @@ TEST_CLASS(PixelShaderEffectUnitTests)
Assert::AreEqual<int>(D2D1_FILTER_ANISOTROPIC, d2dInterpolation.Filter[0]);
Assert::AreEqual<int>(D2D1_FILTER_MIN_MAG_MIP_POINT, d2dInterpolation.Filter[1]);
}
TEST_METHOD_EX(PixelShaderEffect_RealizeAndGetDevice_FromICanvasImageInterop)
{
Fixture f;
auto sharedState = MakeSharedShaderState();
auto effect = Make<PixelShaderEffect>(nullptr, nullptr, sharedState.Get());
ComPtr<ICanvasDevice> canvasDevice;
WIN2D_GET_DEVICE_ASSOCIATION_TYPE deviceType = WIN2D_GET_DEVICE_ASSOCIATION_TYPE::WIN2D_GET_DEVICE_ASSOCIATION_TYPE_UNSPECIFIED;
ThrowIfFailed(As<ICanvasImageInterop>(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<ID2D1Image> image;
ThrowIfFailed(As<ICanvasImageInterop>(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<ICanvasImageInterop>(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<ICanvasDevice*>(f.CanvasDevice.Get(), canvasDevice.Get());
}
};

Просмотреть файл

@ -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<ID2D1GradientMesh>(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();

Просмотреть файл

@ -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 = &rect;
// 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);
/// <summary>
/// A small helper for working with COM types.
/// </summary>
private struct ComPtr : IDisposable
{
/// <summary>
/// The underlying <c>IUnknown</c> object.
/// </summary>
public void* ThisPtr;
/// <summary>
/// Creates a new <see cref="ComPtr"/> instance from the input RCW.
/// </summary>
/// <param name="obj">The input RCW to unwrap.</param>
/// <returns>The resulting <see cref="ComPtr"/> instance.</returns>
public static ComPtr FromObject(object obj)
{
return new ComPtr { ThisPtr = (void*)((IWinRTObject)obj).NativeObject.GetRef() };
}
/// <summary>
/// Invokes <c>QueryInterface</c> for a given RIID.
/// </summary>
/// <param name="guid">The target RIID.</param>
/// <returns>The resulting <see cref="ComPtr"/> instance.</returns>
public ComPtr AsRIID(Guid guid)
{
Marshal.ThrowExceptionForHR(Marshal.QueryInterface((IntPtr)ThisPtr, ref guid, out IntPtr ppv));
return new ComPtr { ThisPtr = (void*)ppv };
}
/// <inheritdoc/>
public void Dispose()
{
if (ThisPtr != null)
{
void* thisPtr = ThisPtr;
ThisPtr = null;
Marshal.Release((IntPtr)thisPtr);
}
}
}
}
}

Просмотреть файл

@ -19,6 +19,7 @@
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<Import Project="..\..\build\Win2D.cs.props" />
<PropertyGroup>
@ -121,6 +122,7 @@
</ApplicationDefinition>
</ItemGroup>
<ItemGroup>
<Compile Include="NativeExportTests.cs" />
<Compile Include="InkTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="App.xaml.cs">