InteropCompositor? What is this?
Remember Windows.UI.Composition.Compositor in UWP apps? That’s InteropCompositor.
Ok enough, so what is different from a normal “Compositor”? You can cast that into IDCompositionDesktopDevice. And create InteropVisuals (IDCompositionVisual that can be castable into Windows.UI.Composition.Visual).
How do I create an InteropCompositor?
Before we start, look at this (Win32) Composition sample: Windows.UI.Composition-Win32-Samples. You can see that you must create a DispatcherQueueController and then you can create a Compositor. Now try to cast that Compositor into IDCompositionDesktopDevice. It will fail because it is just a simple Compositor.
On the other hand, when you are creating an InteropCompositor, dcomp will make it IDComposition*Device castable.
The base
For this tutorial we will modify a pre-existing code created in the previous article (DWM Thumbnails. But with IDCompositionVisual.) to make it use Windows.UI.Composition.* instead.
Download the code here: DWM Thumbnail/VirtualDesktop IDCompositionVisual example (github.com)
Set up your project to use:
- Language standard C++17 (or newer)
- C++/WinRT (install it via nuget)
Now, compile it before continuing, it should build successfully, and C++/WinRT should have had generated winrt projection headers without issues.
Add these include directives:
1 2 3 4 5 6 7 8 9 |
//For this part you need C++/WinRT extension #include <winrt/Windows.Foundation.h> #include <winrt/Windows.Foundation.Collections.h> #include <winrt/Windows.UI.h> #include <winrt/Windows.UI.Core.h> #include <winrt/Windows.UI.Composition.h> #include <winrt/Windows.UI.Composition.Desktop.h> //This is for ABI #include <windows.ui.composition.interop.h> |
And using namespaces
1 2 3 4 5 6 |
//using namespace using namespace winrt; using namespace winrt::Windows::UI; using namespace winrt::Windows::UI::Composition; using namespace winrt::Windows::UI::Composition::Desktop; using namespace winrt::Windows::UI::Core; |
Nice. Now let’s add InteropCompositor interfaces.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
//-------- Interop Composition interfaces //Windows::UI::Composition::HwndTarget (14393-15063) DECLARE_INTERFACE_IID_(HwndTarget, IUnknown, "6677DA68-C80C-407A-A4D2-3AA118AD7C46") { STDMETHOD(GetRoot)(THIS_ OUT ABI::Windows::UI::Composition::IVisual** value) PURE; STDMETHOD(SetRoot)(THIS_ IN ABI::Windows::UI::Composition::IVisual * value) PURE; }; //Windows::UI::Composition::InteropCompositorTarget DECLARE_INTERFACE_IID_(InteropCompositionTarget, IUnknown, "EACDD04C-117E-4E17-88F4-D1B12B0E3D89") { STDMETHOD(SetRoot)(THIS_ IN IDCompositionVisual * visual) PURE; }; //Windows::UI::Composition::IInteropCompositorPartner DECLARE_INTERFACE_IID_(IInteropCompositorPartner, IUnknown, "e7894c70-af56-4f52-b382-4b3cd263dc6f") { STDMETHOD(MarkDirty)(THIS_) PURE; STDMETHOD(ClearCallback)(THIS_) PURE; STDMETHOD(CreateManipulationTransform)(THIS_ IN IDCompositionTransform * transform, IN REFIID iid, OUT VOID * *result) PURE; STDMETHOD(RealClose)(THIS_) PURE; }; //Windows.UI.Composition.IInteropCompositorPartnerCallback DECLARE_INTERFACE_IID_(IInteropCompositorPartnerCallback, IUnknown, "9bb59fc9-3326-4c32-bf06-d6b415ac2bc5") { STDMETHOD(NotifyDirty)(THIS_) PURE; STDMETHOD(NotifyDeferralState)(THIS_ bool deferRequested) PURE; }; //Windows::UI::Composition::IInteropCompositorFactoryPartner DECLARE_INTERFACE_IID_(IInteropCompositorFactoryPartner, IInspectable, "22118adf-23f1-4801-bcfa-66cbf48cc51b") { STDMETHOD(CreateInteropCompositor)(THIS_ IN IUnknown* renderingDevice, IN IInteropCompositorPartnerCallback* callback, IN REFIID iid, OUT VOID** instance ) PURE; STDMETHOD(CheckEnabled)(THIS_ OUT bool* enableInteropCompositor, OUT bool* enableExposeVisual ) PURE; }; |
HwndTarget is used to set a composition visual as compositor visual root, and this is for build 14393 and 15063. For 16299 and newer we will use CompositionTarget instead.
IInteropCompositorFactoryPartner is the interface of our interest as this will let us create InteropCompositor.
Let’s read the parameters: renderingDevice
is basically ID2D1Device, callback
is your made (with winrt::make) IInteropCompositorPartnerCallback implemented interface and it can be null, iid
is either IInteropCompositorPartner, Windows.UI.Composition.Compositor or IDComposition*Device* guid and instance
is your interface.
Now add a ComPtr to ID2D1Device
1 |
ComPtr<ID2D1Device> d2dDevice; |
And replace this: (in CreateDevice function)
1 2 3 4 5 6 7 8 9 10 11 12 |
bool CreateDevice() { ....... if (DCompositionCreateDevice3( dxgiDevice.Get(), __uuidof(dcompDevice), (void**)dcompDevice.GetAddressOf()) != S_OK) { return false; } ...... |
with this:
1 2 3 4 5 6 |
if (d2dFactory2->CreateDevice( dxgiDevice.Get(), d2dDevice.GetAddressOf()) != S_OK) { return false; } |
Because we need a D2DDevice to create an InteropCompositor.
Now, let’s add a parameter to DemoCreateWindowThumbnail and DemoCreateMultiWindowThumbnail.
void DemoCreateWindowThumbnail(HWND myWnd, IDCompositionVisual2** ppWindowVisual,
SIZE* thumbSize)
void DemoCreateMultiWindowThumbnail(HWND myWnd, IDCompositionVisual2** ppVirtualDesktopVisual,
SIZE* thumbSize)
and assign the content on both functions:
1 2 3 4 5 6 7 8 9 10 11 |
void DemoCreateWindowThumbnail(HWND myWnd, IDCompositionVisual2** ppWindowVisual, SIZE* thumbSize) { //A window target of your choice HWND targetWindow = (HWND)FindWindow(L"Shell_TrayWnd", NULL); //Query the exact thumbnail source size SIZE windowSize{}; lDwmpQueryWindowThumbnailSourceSize(targetWindow, FALSE, &windowSize); *thumbSize = windowSize; ....... |
1 2 3 4 5 6 7 8 9 10 11 |
void DemoCreateMultiWindowThumbnail(HWND myWnd, IDCompositionVisual2** ppVirtualDesktopVisual, SIZE* thumbSize) { HTHUMBNAIL hThumbVirtualDesktop; auto virtualDeskRes = lDwmpCreateSharedMultiWindowVisual(myWnd, dcompDevice.Get(), (void**)ppVirtualDesktopVisual, &hThumbVirtualDesktop); auto monitorSize = RECT{ 0, 0, 1920, 1080 }; auto targetSize = SIZE{ 960, 540 }; *thumbSize = targetSize; .......... |
As for this demo we want to know the size of the DCompositionVisual to be able to make animation on its center point (size / 2)
And now let’s make the function to create our InteropCompositor!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
bool InitializeInteropCompositor(HWND hwnd, IUnknown* d2dDevice, Compositor* compositor, IUnknown** compositionTarget, ContainerVisual* rootVisual) { auto interopCompositorFactory = winrt::get_activation_factory<Compositor, IInteropCompositorFactoryPartner>(); com_ptr<IInteropCompositorPartner> interopCompositor; auto interopRes = interopCompositorFactory->CreateInteropCompositor(d2dDevice, NULL, winrt::guid_of<IInteropCompositorPartner>(), interopCompositor.put_void()); if (interopRes != S_OK) return false; //Get as Compositor and as IDCompositionDevice auto m_compositor = interopCompositor.as<Compositor>(); dcompDevice = interopCompositor.as<IDCompositionDesktopDevice>().detach(); //Create a target for our window com_ptr<IDCompositionTarget> dcompTarget; auto res = dcompDevice->CreateTargetForHwnd(hwnd, TRUE, dcompTarget.put()); if (res != S_OK) return false; //Create a container visual to hold all our visuals and then set it as root. //InteropCompositionTarget derives from DesktopWindowTarget which ultimately derives from CompositionTarget - 16299+ //InteropCompositionTarget derives from HwndTarget - 14393-15063 auto containerVisual = m_compositor.CreateContainerVisual(); auto compTarget = dcompTarget.try_as<CompositionTarget>(); if (compTarget) { compTarget.Root(containerVisual); *compositionTarget = compTarget.as<IUnknown>().detach(); } else { //We are on 15063 or 14393 //Get "raw" pointer to IVisual winrt::com_ptr<ABI::Windows::UI::Composition::IVisual> visualAbi; winrt::get_unknown(containerVisual)->QueryInterface(visualAbi.put()); auto hwndTarget = dcompTarget.as<HwndTarget>(); hwndTarget->SetRoot(visualAbi.get()); *compositionTarget = hwndTarget.as<IUnknown>().detach(); } *compositor = m_compositor; *rootVisual = containerVisual; return true; } |
This function will create a Windows.UI.Composition.Visual container to hold our IDCompositionVisual visual
1 2 3 4 5 6 7 8 9 10 11 12 |
Visual DCompVisualToVisual(Compositor compositor, IDCompositionVisual2* dcompVisual, SIZE dcompVisualSize) { winrt::com_ptr<IDCompositionVisual2> dcompVisualContainer; compositor.as<IDCompositionDesktopDevice>()->CreateVisual(dcompVisualContainer.put()); dcompVisualContainer->AddVisual(dcompVisual, TRUE, nullptr); auto visualContainer = dcompVisualContainer.as<Visual>(); visualContainer.Size({ (float)dcompVisualSize.cx, (float)dcompVisualSize.cy }); return visualContainer; } |
And finally, the last function to create an animation using Composition.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
void CreateAnimationForVisual(Compositor compositor, Visual visual) { auto animationScale = compositor.CreateVector3KeyFrameAnimation(); auto spring = compositor.CreateCubicBezierEasingFunction({ .41F, .51999998F }, { .00F, .94F }); animationScale.InsertKeyFrame(.15F, { 1.F, 1.F, 1.F }, spring); animationScale.InsertKeyFrame(.3F, { .5F, .5F, .5F }, spring); animationScale.InsertKeyFrame(.6F, { .5F, .5F, .5F }, spring); animationScale.InsertKeyFrame(.85F, { 1.F, 1.F, 1.F }, spring); animationScale.Duration(std::chrono::milliseconds(3000)); animationScale.IterationBehavior(AnimationIterationBehavior::Forever); visual.CenterPoint({ visual.Size() / 2, 0.f }); visual.StartAnimation(L"Scale", animationScale); } |
Let’s glue it together in our main function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow) { init_apartment(); if (!InitPrivateDwmAPIs() || !InitPrivateUser32APIs() || !CreateDevice()) return 0; auto myWnd = CreateWindowInternal(L"Noice window", L"DwmThumbExampleClass", WS_EX_OVERLAPPEDWINDOW | WS_EX_NOREDIRECTIONBITMAP, WS_OVERLAPPEDWINDOW); ShowWindow(myWnd, SW_SHOW); //Create Compositor, ContainerVisual, *target, and set dcompDevice. Compositor compositor{ nullptr }; ContainerVisual root{ nullptr }; IUnknown* target{ nullptr }; auto resInit = InitializeInteropCompositor(myWnd, d2dDevice.Get(), &compositor, &target, &root); ComPtr<IDCompositionVisual2> thumbVisual; SIZE thumbVisualSize{}; DemoCreateWindowThumbnail(myWnd, thumbVisual.GetAddressOf(), &thumbVisualSize); //ComPtr<IDCompositionVisual2> thumbVisual; //SIZE thumbVisualSize{}; //DemoCreateMultiWindowThumbnail(myWnd, thumbVisual.GetAddressOf(), &thumbVisualSize); //Wrap the IDCompositionVisual into a Windows.UI.Composition.Visual and insert it into the Composition root auto thumbCompositionVisual = DCompVisualToVisual(compositor, thumbVisual.Get(), thumbVisualSize); root.Children().InsertAtTop(thumbCompositionVisual); //Set an animation for the visual! CreateAnimationForVisual(compositor, thumbCompositionVisual); MSG msg{}; while (GetMessage(&msg, nullptr, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } //ComPtr automatically releases for you. } |
Compile and run! You should have a zoom in-out animation!
Bonus – CoreDispatcher
You can create a CoreDispatcher if you need to create a DispatcherQueue (16299+) and to process events (instead of the classic message loop).
How? Add this interface:
1 2 3 4 5 6 7 8 9 10 |
//Windows::UI::Core::IInternalCoreDispatcherStatic (14393+) DECLARE_INTERFACE_IID_(IInternalCoreDispatcherStatic, IInspectable, "4B4D0861-D718-4F7C-BEC7-735C065F7C73") { STDMETHOD(GetForCurrentThread)( winrt::Windows::UI::Core::CoreDispatcher* ppDispatcher ) PURE; STDMETHOD(GetOrCreateForCurrentThread)( winrt::Windows::UI::Core::CoreDispatcher* ppDispatcher ) PURE; }; |
And make this helper function:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
CoreDispatcher GetOrCreateCoreDispatcher() { //CoreDispatcher internally creates a DispatcherQueue if it doesn't exist (and if you are on 15063 or newer) auto dispatcher = winrt::try_get_activation_factory<CoreDispatcher, IInternalCoreDispatcherStatic>(); //On 10586 there isn't a way to create a CoreDispatcher (unless you create a CoreWindow, lol.) if (!dispatcher) return nullptr; CoreDispatcher coreDispatcher{ nullptr }; dispatcher->GetOrCreateForCurrentThread(&coreDispatcher); return coreDispatcher; } |
In Main function, create a CoreDispatcher using GetOrCreateCoreDispatcher() and replace the message loop with:
1 |
coreDispatcher.ProcessEvents(CoreProcessEventsOption::ProcessUntilQuit); |
You should end up something like this:
1 2 3 4 5 6 7 8 9 |
........ //Set an animation for the visual! CreateAnimationForVisual(compositor, thumbCompositionVisual); CoreDispatcher coreDispatcher = GetOrCreateCoreDispatcher(); coreDispatcher.ProcessEvents(CoreProcessEventsOption::ProcessUntilQuit); //ComPtr automatically releases for you. } |
Notes:
You can’t cast Windows.UI.Composition.Visual created from compositor.Create*Visual to IDCompositionVisual.
You can cast IDCompositionVisual to Windows.UI.Composition.Visual that are created from IDCompositionDesktopDevice.CreateVisual
Why? IDCompositionDesktopDevice.CreateVisual is covered by InteropCompositor API and that produces an InteropVisual. On the other hand, compositor.Create*Visual is covered only by Compositor API and that produces a simple Visual.
Compatibility:
This code is tested and confirmed to work on build 14393 and later
Builds before 14393 won’t work at all.
Full demo code: DWM Thumbnail/VirtualDesktop USING Windows.UI.Composition (github.com)
Hey man, really appreciate these kind of blog posts/tutorials. Seems you’re about the only one discussing these things in this detail.
Many thanks and keep it up!