Closed HO-COOH closed 3 months ago
static winrt::Windows::System::DispatcherQueueController createSystemDispatcherQueueController()
{
DispatcherQueueOptions options
{
sizeof(DispatcherQueueOptions),
DQTYPE_THREAD_CURRENT,
DQTAT_COM_STA
};
::ABI::Windows::System::IDispatcherQueueController* ptr{ nullptr };
winrt::check_hresult(CreateDispatcherQueueController(options, &ptr));
return { ptr, take_ownership_from_abi };
}
Does the call to CreateDispatcherQueueController actually work? The documentation for DispatcherQueueOptions states that you shouldn't do what you are doing.
"This field is relevant only if threadType is DQTYPE_THREAD_DEDICATED. Use DQTAT_COM_NONE when DispatcherQueueOptions.threadType is DQTYPE_THREAD_CURRENT."
@DarranRowe I found this code in winui3 gallery. Using any other value results the same
Then there are three things that I can think of.
1) System Xaml needs to be initialised. Have you tried calling Windows.UI.Xaml.Hosting.WindowsXamlManager.InitializeForCurrentThread? 2) This needs the default Xaml metadata, so this would need an instance of a runtime class derived from Windows.UI.Xaml.Application initialised on the thread. 3) For some reason, this requires a CoreWindow, and that isn't available in desktop applications.
They are listed in the order that I think is most likely. For the first two, think of Xaml Islands. Also, remember that system Xaml isn't related to WinUI 3 Xaml, so WinUI 3 wouldn't initialise the Xaml under the Windows root namespace.
--Edit-- Just wrote a little test, and it seems to be 1.
Then there are three things that I can think of.
- System Xaml needs to be initialised. Have you tried calling Windows.UI.Xaml.Hosting.WindowsXamlManager.InitializeForCurrentThread?
- This needs the default Xaml metadata, so this would need an instance of a runtime class derived from Windows.UI.Xaml.Application initialised on the thread.
- For some reason, this requires a CoreWindow, and that isn't available in desktop applications.
They are listed in the order that I think is most likely. For the first two, think of Xaml Islands. Also, remember that system Xaml isn't related to WinUI 3 Xaml, so WinUI 3 wouldn't initialise the Xaml under the Windows root namespace.
--Edit-- Just wrote a little test, and it seems to be 1.
How did you get WindowsXamlManager
? This class does not exist in the generated Windows.UI.Xaml.Hosting.h
header.
Right, when the project is set up for a Windows Store type project, like WinUI 3 projects are, C++/WinRT doesn't generate the entire set of contracts. You would need to reference the WindowsDesktop extension SDK. The issue is, the UWP referencing doesn't work for a desktop application. The way I do it is to manually edit the .vcxproj file to add the reference.
The use of $(TargetPlatformVersion)
is to make sure that the reference always matches the version of the Windows SDK, so you don't need to edit the project every time. The only other issue is, if you didn't install the Windows SDK in the default location then you would need to add an extra path to the extension sdk search path. If you don't then Visual Studio will fail to find the extension sdk:
2>D:\Programs64\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\amd64\Microsoft.Common.CurrentVersion.targets(2671,5): error MSB3774: Could not find SDK "WindowsDesktop, Version=10.0.26100.0".
This is because Visual Studio is hard coded to only look in Program Files (x86). So as an example, I actually have the Windows SDK installed on a separate drive, along with Visual Studio and some other applications.
So I have to add this directory to the paths that Visual Studio will look for.
But as I mentioned, this is only needed if the Windows SDK is not installed in the default location.
This feels like an XY problem. Why are you trying to use WUX.Media.LoadedImageSurface in a WinUI 3 project?
It's because the system backdrops truely do use WU.Composition.CompositionBrush.
[contract(Microsoft.Foundation.WindowsAppSDKContract, 1.1)]
[uuid(397DAFE4-B6C2-5BB9-951D-F5707DE8B7BC)]
interface ICompositionSupportsSystemBackdrop : IInspectable
{
[propget] HRESULT SystemBackdrop([out] [retval] Windows.UI.Composition.CompositionBrush** value);
[propput] HRESULT SystemBackdrop([in] Windows.UI.Composition.CompositionBrush* value);
}
This is the IDL generated from the 1.5 metadata for ICompositionSupportsSystemBackdrop as one example. WUX.Media.LoadedImageSurface probably just gave the easiest way of loading an image into a composition brush.
Are there non-XAML ways to get an image into a composition brush then?
@DarranRowe Your method is unreasonably complicated. WASDK team better enable use of Windows.UI.Composition
by default in the project template
My method isn't unreasonably complicated, it was only telling you how to use the runtime class that you wanted to use. That response also served to indicate that this wasn't a Windows App SDK issue since the System Xaml is where your problem was, and initialising System Xaml using the hosting API resolved your initial problem completely. The use of Windows.UI.Composition here is a different thing entirely, and is enabled by default since all of Windows.UI.Composition is in the Universal API Contract. The issue is, as documented by Windows.UI.Composition.CompositionDrawingSurface, there are three ways to load the CompositionDrawingSurface. Using Direct2D and ICompositionDrawingSurfaceInterop is really your only option if you don't want to have anything extra but want to use Windows.UI.Composition. Win2D is also available if you don't mind adding that. As you already found out, using LoadedImageSurface requires initialising System Xaml and using MediaPlayer for a static image is a bit much.
If you want to use runtime classes under Windows.UI.Xaml without using the hosting interfaces to initialise it, then you really should submit a feature request for Microsoft.UI.Xaml.Application.Start to initialise Windows.UI.Xaml too.
@DarranRowe Don't get me wrong, I really appreciate your solution. As it happens many times before, I just want them to either: document the usage with winui3 clearly so nobody gets wasting their time (since WUC
namespace is used by this public SystemBackdrop
API) OR have it working out-of-the-box by initializing whatever system xaml in the project template's startup code (also that WinUI3 project template is provided by WASDK team I guess). Manually editing any vcxproj
file is not an acceptable solution to me.
As I stated though, Windows.UI.Composition is fully usable regardless. In this case, it is a System Xaml helper which is the issue. The biggest thing I always assume when working with System Xaml is that anything under Windows.UI.Xaml is unusable until either Windows.UI.Xaml.Application.Start has been called, or Windows.UI.Xaml.Hosting.WindowsXamlManager.InitializeForCurrentThread has been called. The only surprising thing I know of is that Windows.UI.Color.ColorHelper.ToDisplayName calls into Windows.UI.Xaml.dll. In the end, I would say that any documentation for System Xaml is outside the scope of the Windows App SDK, and the documentation for this is in the MicrosoftDocs/winrt-api repository. You could always file an issue or a pr over there. As for using Windows.UI.Composition in a WinUI 3 application. For some quick and dirty pointers as to how you would do this using Direct2D/Direct3D, you would follow the general steps of:
1) Initialise Direct3D and/or Direct2D and the Compositor, if needed. 2) Create the CompositionGraphicsDevice. 3) Create the CompositionDrawingSurface. 4) Draw to the surface.
winrt::Windows::UI::Composition::CompositionGraphicsDevice MainWindow::create_graphics_device()
{
if (!m_compositor)
{
m_compositor = winrt::Windows::UI::Composition::Compositor();
}
if (!is_d2d_init())
{
check_hresult(init_d2d());
}
auto comp_interop = m_compositor.as<ABI::Windows::UI::Composition::ICompositorInterop>();
winrt::Windows::UI::Composition::CompositionGraphicsDevice graphics_device{ nullptr };
//This uses an ID2D1Device to create the CompositionGraphicsDevice, you can also use ID3D11Device too.
check_hresult(comp_interop->CreateGraphicsDevice(m_d2d1_device.get(), reinterpret_cast<ABI::Windows::UI::Composition::ICompositionGraphicsDevice **>(winrt::put_abi(graphics_device))));
return graphics_device;
}
Once you have the CompositionGraphicsDevice, then you can use one of the surface creation members to create a surface. Once you have the surface, you can then use ICompositionSurfaceInterop to control the drawing.
std::pair<winrt::com_ptr<ID2D1DeviceContext>, POINT> MainWindow::begin_draw(const winrt::Windows::UI::Composition::CompositionDrawingSurface &drawing_surface)
{
auto drawing_surface_interop = drawing_surface.as<ABI::Windows::UI::Composition::ICompositionDrawingSurfaceInterop>();
//You must use ID2D1DeviceContext, ID2D1DeviceContext1 onwards will not work.
winrt::com_ptr<ID2D1DeviceContext> ds;
POINT up_off{};
check_hresult(drawing_surface_interop->BeginDraw(nullptr, IID_PPV_ARGS(ds.put()), &up_off));
return std::make_pair(ds, up_off);
}
Once you have finished drawing to the surface, then you must call EndDraw.
void MainWindow::end_draw(const winrt::Windows::UI::Composition::CompositionDrawingSurface &drawing_surface)
{
auto drawing_surface_interop = drawing_surface.as<ABI::Windows::UI::Composition::ICompositionDrawingSurfaceInterop>();
check_hresult(drawing_surface_interop->EndDraw());
}
There is some quirkiness here, but it is easy to explain the behaviour when you understand that the desktop targetted version of Windows.UI.Composition uses the underlying DirectComposition.
Anyway, the things to note using this. If you pass in a ID3D11Device, then you can only get a IDXGISurface/ID3D11Texture2D from BeginDraw. If you pass in a ID2D1Device, it does the equivalent of ID2D1Device::CreateDeviceContext, ID2D1DeviceContext::CreateBitmapFromDxgiSurface, sets the newly created bitmap as the target for the ID2D1DeviceContext using ID2D1DeviceContext::SetTarget and then returns the ID2D1DeviceContext.
Using System Xaml does allow for some shortcuts, because it needs to use Direct2D and Windows.UI.Composition to draw. So it has its own compositor and Direct2D device. However, if you use Direct2D and your own compositor instance, then you don't need to initialise System Xaml and thus you don't have to edit the .vcxproj file manually.
This is why I personally consider this outside of the scope of the Windows App SDK, since it uses the inbuilt system functionality, and you can do what you want without editing anything. It is just the shortcut under the Windows.UI.Xaml namespace that doesn't work unless System Xaml is initialised.
@DarranRowe I got
Exception thrown at 0x00007FFBDBD2567C (KernelBase.dll) in WinUI3Example.exe: WinRT originate error - 0x802B000A : 'Cannot create instance of type 'Windows.UI.Xaml.Controls.XamlControlsResources' [Line: 11 Position: 40]'.
when I do
auto m_manager = winrt::Windows::UI::Xaml::Hosting::WindowsXamlManager::InitializeForCurrentThread();
Additional context:
I did not add reference through editing .vcxproj
tho. I did it with adding reference inside visual studio's project reference dialog:
In regards to the additional context.
https://github.com/microsoft/WindowsAppSDK/assets/52577874/36a87530-4468-402b-9511-0aa6605aafe7
https://github.com/microsoft/WindowsAppSDK/assets/52577874/2a6c0d9e-c3fb-4b6a-a4d3-529812be4744
As I have previously stated in this thread, I do not use the default installation locations for the Windows SDK and Visual Studio, and Visual Studio hard codes the Extension SDK path. That is why I have to override the SDKExtensionDirectoryRoot property if I want to use any of this. You can see this for yourself in the Microsoft.Cpp.AppContainerApplication.props file in that is part of MSBuild/Visual Studio.
<SDKExtensionDirectoryRoot Condition="'$(SDKExtensionDirectoryRoot)' == '' and '$(SDKIdentifier)' != ''">$(MSBuildProgramFiles32)\Microsoft SDKs\Windows Kits\10;$(MSBuildProgramFiles32)\Windows Kits\10</SDKExtensionDirectoryRoot>
Anyway, for that error, that is doubly curious and problematic. It also pushes the goal post further. I'm going to have to work out what I missed with my test. However, at the very least it means that it is going to require an application (something derived from Windows.UI.Xaml.Application) active on the thread. Because this also means that there are multiple frameworks active, then it also means that System Xaml really should go onto a separate thread. Anyway, I'm going to have to investigate this a little more.
Describe the bug
I wanted to use an image as a
Windows.UI.Composition.ICompositionSurface
as an input to some win2d effect, and output aWindows.UI.Composition.CompositionBrush
used inMicorsoft.UI.Xaml.Window
'sSystemBackdrop
brush. But I got exceptions in usingWindows.UI.Xaml.Media.LoadedImageSurface.StartLoadFromUri
, even after I created aDispatcherQueueController
on the winui3's ui thread.Steps to reproduce the bug
Initialize a
DispatcherQueueController
in the ui threadExpected behavior
No response
Screenshots
No response
NuGet package version
Windows App SDK 1.5.3: 1.5.240428000
Packaging type
Packaged (MSIX)
Windows version
Windows 11 version 22H2 (22621, 2022 Update)
IDE
Visual Studio 2022
Additional context
No response