Last active
May 29, 2024 17:08
-
-
Save dongle-the-gadget/b182ed36d9c30836a8c2281a980b6d4f to your computer and use it in GitHub Desktop.
WinUI 3 + Windows.UI.Composition
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<Window | |
x:Class="WinUIThreeTest.MainWindow" | |
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | |
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | |
xmlns:local="using:WinUIThreeTest" | |
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" | |
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | |
mc:Ignorable="d"> | |
<SwapChainPanel Name="SwapChainPanel"> | |
<Button Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" x:Name="myButton" Click="myButton_Click">Click Me</Button> | |
</SwapChainPanel> | |
</Window> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// WinUI 3 + Windows.UI.Composition | |
// Thanks to: | |
// ysc3839 (GitHub): SetWindowCompositionAttribute implementation. | |
// ADeltaX for InteropComposition implementation (https://blog.adeltax.com/interopcompositor-and-coredispatcher/). | |
// Ahmed Walid for the first implementation of Windows.UI.Composition for WinUI 3. | |
// This is a reimplementation based on Ahmed's tutorial: https://twitter.com/AhmedWalid605/status/1513871287361409028. | |
// NOTE: This code is not ready for production use. | |
#include "pch.h" | |
#include "MainWindow.xaml.h" | |
#if __has_include("MainWindow.g.cpp") | |
#include "MainWindow.g.cpp" | |
#endif | |
typedef enum _WINDOWCOMPOSITIONATTRIB | |
{ | |
WCA_ACCENT_POLICY = 19 | |
} WINDOWCOMPOSITIONATTRIB; | |
typedef struct _WINDOWCOMPOSITIONATTRIBDATA | |
{ | |
WINDOWCOMPOSITIONATTRIB Attrib; | |
PVOID pvData; | |
SIZE_T cbData; | |
} WINDOWCOMPOSITIONATTRIBDATA; | |
typedef enum _ACCENT_STATE | |
{ | |
ACCENT_ENABLE_HOSTBACKDROP = 5 | |
} ACCENT_STATE; | |
typedef struct _ACCENT_POLICY | |
{ | |
ACCENT_STATE AccentState; | |
DWORD AccentFlags; | |
DWORD GradientColor; | |
DWORD AnimationId; | |
} ACCENT_POLICY; | |
typedef BOOL(WINAPI* pfnGetWindowCompositionAttribute)(HWND, WINDOWCOMPOSITIONATTRIBDATA*); | |
typedef BOOL(WINAPI* pfnSetWindowCompositionAttribute)(HWND, WINDOWCOMPOSITIONATTRIBDATA*); | |
#include <comutil.h> | |
#include <dcomp.h> | |
#include <dxgi1_3.h> | |
#include <d3d11_2.h> | |
#include <d2d1_2.h> | |
#include <d2d1_2helper.h> | |
#include <winrt/Windows.UI.h> | |
#include <winrt/Windows.UI.Core.h> | |
#include <winrt/Windows.UI.Composition.h> | |
#include <windows.ui.composition.interop.h> | |
#include <microsoft.ui.xaml.window.h> | |
#include <winrt/microsoft.ui.interop.h> | |
#include <winrt/Microsoft.UI.Windowing.h> | |
#include <microsoft.ui.xaml.media.dxinterop.h> | |
#pragma comment(lib, "dxgi") | |
#pragma comment(lib, "d3d11") | |
#pragma comment(lib, "d2d1") | |
#pragma comment(lib, "dcomp") | |
using namespace winrt; | |
using namespace winrt::Microsoft::UI::Xaml; | |
using namespace winrt::Windows::UI; | |
using namespace winrt::Windows::UI::Composition; | |
using namespace winrt::Windows::UI::Core; | |
LRESULT CALLBACK WndProc( | |
HWND, | |
UINT, | |
WPARAM, | |
LPARAM | |
); | |
WNDPROC prevWndProc; | |
DECLARE_INTERFACE_IID_(IInternalCoreDispatcherStatic, IInspectable, "4B4D0861-D718-4F7C-BEC7-735C065F7C73") | |
{ | |
STDMETHOD(GetForCurrentThread)( | |
CoreDispatcher * ppDispatcher | |
) PURE; | |
STDMETHOD(GetOrCreateForCurrentThread)( | |
CoreDispatcher * ppDispatcher | |
) PURE; | |
}; | |
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; | |
}; | |
DECLARE_INTERFACE_IID_(InteropCompositionTarget, IUnknown, "EACDD04C-117E-4E17-88F4-D1B12B0E3D89") | |
{ | |
STDMETHOD(SetRoot)(THIS_ | |
IN IDCompositionVisual * visual) PURE; | |
}; | |
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; | |
}; | |
DECLARE_INTERFACE_IID_(IInteropCompositorPartnerCallback, IUnknown, "9bb59fc9-3326-4c32-bf06-d6b415ac2bc5") | |
{ | |
STDMETHOD(NotifyDirty)(THIS_) PURE; | |
STDMETHOD(NotifyDeferralState)(THIS_ | |
bool deferRequested) PURE; | |
}; | |
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; | |
}; | |
com_ptr<IDCompositionDesktopDevice> dcompDevice; | |
com_ptr<ID2D1Device> d2dDevice; | |
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; | |
auto m_compositor = interopCompositor.as<Compositor>(); | |
dcompDevice = interopCompositor.as<IDCompositionDesktopDevice>(); | |
com_ptr<IDCompositionTarget> dcompTarget; | |
auto res = dcompDevice->CreateTargetForHwnd(hwnd, FALSE, dcompTarget.put()); | |
if (res != S_OK) | |
return false; | |
auto containerVisual = m_compositor.CreateContainerVisual(); | |
auto compTarget = dcompTarget.try_as<CompositionTarget>(); | |
if (compTarget) | |
{ | |
compTarget.Root(containerVisual); | |
*compositionTarget = compTarget.as<IUnknown>().detach(); | |
} | |
else | |
{ | |
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; | |
} | |
winrt::com_ptr< ::IDXGISwapChain1 > swapChain; | |
SpriteVisual spriteVisual{ nullptr }; | |
namespace winrt::WinUIThreeTest::implementation | |
{ | |
MainWindow::MainWindow() | |
{ | |
InitializeComponent(); | |
} | |
void MainWindow::myButton_Click(IInspectable const&, RoutedEventArgs const&) | |
{ | |
HRESULT result; | |
com_ptr<ID3D11Device> direct3dDevice; | |
com_ptr<IDXGIDevice1> dxgiDevice; | |
com_ptr<ID2D1Factory2> d2dFactory2; | |
result = D3D11CreateDevice(0, | |
D3D_DRIVER_TYPE_HARDWARE, | |
NULL, | |
D3D11_CREATE_DEVICE_BGRA_SUPPORT, | |
NULL, | |
0, | |
D3D11_SDK_VERSION, | |
direct3dDevice.put(), | |
nullptr, | |
nullptr); | |
result = direct3dDevice->QueryInterface(dxgiDevice.put()); | |
result = D2D1CreateFactory( | |
D2D1_FACTORY_TYPE::D2D1_FACTORY_TYPE_SINGLE_THREADED, | |
__uuidof(ID2D1Factory2), | |
(void**)d2dFactory2.put()); | |
d2dFactory2->CreateDevice( | |
dxgiDevice.get(), | |
d2dDevice.put()); | |
result = DCompositionCreateDevice3( | |
dxgiDevice.get(), | |
__uuidof(dcompDevice), | |
(void**)dcompDevice.put()); | |
com_ptr<ID2D1DeviceContext> m_d2dContext; | |
d2dDevice->CreateDeviceContext( | |
D2D1_DEVICE_CONTEXT_OPTIONS_NONE, | |
m_d2dContext.put() | |
); | |
Windows::UI::Composition::Compositor compositor{ nullptr }; | |
ContainerVisual root{ nullptr }; | |
IUnknown* target{ nullptr }; | |
auto windowNative{ this->try_as<::IWindowNative>() }; | |
winrt::check_bool(windowNative); | |
HWND hWnd{ 0 }; | |
windowNative->get_WindowHandle(&hWnd); | |
InitializeInteropCompositor(hWnd, d2dDevice.get(), &compositor, &target, &root); | |
// Here I'm just lazy so I used AppWindow, feel free to replace this code with GetWindowRect. | |
// Note that swap chain and composition uses logical pixels. | |
// Start of sizing code | |
auto windowId = Microsoft::UI::GetWindowIdFromWindow(hWnd); | |
auto appWindow = Microsoft::UI::Windowing::AppWindow::GetFromWindowId(windowId); | |
auto windowSize = appWindow.Size(); | |
auto m_dpi = GetDpiForWindow(hWnd); | |
windowSize.Width /= m_dpi / 96; | |
windowSize.Height /= m_dpi / 96; | |
// End of sizing code | |
DXGI_SWAP_CHAIN_DESC1 swapChainDesc{ 0 }; | |
swapChainDesc.Width = windowSize.Width; | |
swapChainDesc.Height = windowSize.Height; | |
swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; | |
swapChainDesc.Stereo = false; | |
swapChainDesc.SampleDesc.Count = 1; | |
swapChainDesc.SampleDesc.Quality = 0; | |
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; | |
swapChainDesc.BufferCount = 2; | |
swapChainDesc.Scaling = DXGI_SCALING_STRETCH; | |
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; | |
swapChainDesc.Flags = 0; | |
winrt::com_ptr< ::IDXGIAdapter > dxgiAdapter; | |
dxgiDevice->GetAdapter(dxgiAdapter.put()); | |
winrt::com_ptr< ::IDXGIFactory2 > dxgiFactory; | |
dxgiFactory.capture(dxgiAdapter, &IDXGIAdapter::GetParent); | |
dxgiFactory->CreateSwapChainForComposition( | |
direct3dDevice.get(), | |
&swapChainDesc, | |
nullptr, | |
swapChain.put()); | |
spriteVisual = compositor.CreateSpriteVisual(); | |
Windows::Foundation::Numerics::float2 float2; | |
float2.x = (float)windowSize.Width; | |
float2.y = (float)windowSize.Height; | |
spriteVisual.Size(float2); | |
// Here we set the composition attribute to allow host backdrops. | |
// Remove the following lines of code if you don't need it. | |
// NOTE: If you remove the host backdrop code you must replace compositor.CreateHostBackdropBrush() with a composition brush of your choice. | |
// Start of host backdrop code. | |
HMODULE hUser = GetModuleHandle(L"user32.dll"); | |
if (hUser) | |
{ | |
pfnSetWindowCompositionAttribute setWindowCompositionAttribute = (pfnSetWindowCompositionAttribute)GetProcAddress(hUser, "SetWindowCompositionAttribute"); | |
if (setWindowCompositionAttribute) | |
{ | |
ACCENT_POLICY accent = { ACCENT_ENABLE_HOSTBACKDROP, 0, 0, 0 }; | |
WINDOWCOMPOSITIONATTRIBDATA data; | |
data.Attrib = WCA_ACCENT_POLICY; | |
data.pvData = &accent; | |
data.cbData = sizeof(accent); | |
setWindowCompositionAttribute(hWnd, &data); | |
} | |
} | |
// End of host backdrop code. | |
spriteVisual.Brush(compositor.CreateHostBackdropBrush()); | |
root.Children().InsertAtTop(spriteVisual); | |
com_ptr<ID3D11Texture2D> backBuffer; | |
swapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer)); | |
D2D1_BITMAP_PROPERTIES1 bitmapProperties = | |
D2D1::BitmapProperties1( | |
D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW, | |
D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE), | |
m_dpi, | |
m_dpi | |
); | |
com_ptr<IDXGISurface> dxgiBackBuffer; | |
swapChain->GetBuffer(0, IID_PPV_ARGS(&dxgiBackBuffer)); | |
com_ptr<ID2D1Bitmap1> m_d2dTargetBitmap; | |
m_d2dContext->CreateBitmapFromDxgiSurface( | |
dxgiBackBuffer.get(), | |
&bitmapProperties, | |
m_d2dTargetBitmap.put() | |
); | |
m_d2dContext->SetTarget(m_d2dTargetBitmap.get()); | |
SwapChainPanel().as<ISwapChainPanelNative>()->SetSwapChain(swapChain.get()); | |
#ifdef GWL_WNDPROC | |
prevWndProc = (WNDPROC)SetWindowLong(hWnd, GWL_WNDPROC, (LONG)WndProc); | |
#else | |
prevWndProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)WndProc); | |
#endif | |
myButton().Content(box_value(L"Clicked")); | |
} | |
} | |
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) | |
{ | |
if (msg == WM_SIZE) | |
{ | |
auto width = LOWORD(lParam); | |
auto height = HIWORD(lParam); | |
DXGI_MODE_DESC modeDesc; | |
modeDesc.Width = width; | |
modeDesc.Height = height; | |
swapChain->ResizeTarget(&modeDesc); | |
Windows::Foundation::Numerics::float2 float2; | |
float2.x = (float)width; | |
float2.y = (float)height; | |
spriteVisual.Size(float2); | |
} | |
return CallWindowProc(prevWndProc, hWnd, msg, wParam, lParam); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include "MainWindow.g.h" | |
namespace winrt::WinUIThreeTest::implementation | |
{ | |
struct MainWindow : MainWindowT<MainWindow> | |
{ | |
MainWindow(); | |
void myButton_Click(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::RoutedEventArgs const& args); | |
}; | |
} | |
namespace winrt::WinUIThreeTest::factory_implementation | |
{ | |
struct MainWindow : MainWindowT<MainWindow, implementation::MainWindow> | |
{ | |
}; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include <windows.h> | |
#include <unknwn.h> | |
#include <restrictederrorinfo.h> | |
#include <hstring.h> | |
// Undefine GetCurrentTime macro to prevent | |
// conflict with Storyboard::GetCurrentTime | |
#undef GetCurrentTime | |
#include <winrt/Windows.Foundation.h> | |
#include <winrt/Windows.Foundation.Collections.h> | |
#include <winrt/Windows.ApplicationModel.Activation.h> | |
#include <winrt/Microsoft.UI.Composition.h> | |
#include <winrt/Microsoft.UI.Xaml.h> | |
#include <winrt/Microsoft.UI.Xaml.Controls.h> | |
#include <winrt/microsoft.UI.Xaml.Input.h> | |
#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h> | |
#include <winrt/Microsoft.UI.Xaml.Data.h> | |
#include <winrt/Microsoft.UI.Xaml.Interop.h> | |
#include <winrt/Microsoft.UI.Xaml.Markup.h> | |
#include <winrt/Microsoft.UI.Xaml.Media.h> | |
#include <winrt/Microsoft.UI.Xaml.Navigation.h> | |
#include <winrt/Microsoft.UI.Xaml.Shapes.h> | |
#include <winrt/Microsoft.UI.Dispatching.h> | |
#include <wil/cppwinrt_helpers.h> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment