Hey Jesús, cheers for the response.
Problems with load times have been highlighted in the past by more clients (many times this is happening because big resource dictionaries are merged in each XAML instead of using the global app dictionary, you should avoid this)
Our main theme resource dictionaries are already in the shared global application resources, but we do have a bunch of resource dictionaries which are merged all over the place which contain control templates with a bunch of path geometry on a canvas which we use as an image library. I'll move these out of our user controls up to the application layer which should hopefully improve the load times. Essentially we have our vector images in separate resource dictionaries which are merged with several separate controls:
<UserControl>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- A list of vector image databases, each resource dictionary just contains a bunch of control templates with SVG path info representing our images -->
<ResourceDictionary Source="Images/Vector/IconsDatabase.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<!-- We use a custom control with a controltemplate containing the icon path data. See below for the Icon_A key in a separate resource dictionary -->
<local:VectorImageControl Template="{StaticResource Icon_A}" />
</UserControl>
And IconsDatabase.xaml just contains a bunch of our icons in a resource database. Each image is a control template which has a canvas wrapped in a viewbox with a bunch of SVG paths:
<ResourceDictionary>
<ControlTemplate x:Key="Icon_A">
<Viewbox>
<Canvas Name="Document" Width="..." Height="..." Clip="...">
<Canvas Name="Layer_1" Width="..." Height="..." Canvas.Left="..." Canvas.Top="...">
<Path Data="..." Stretch="Fill" Fill="..." Name="Path" Width="..." Height="..." Canvas.Left="..." Canvas.Top="..." />
</Canvas>
</Canvas>
</ViewBox>
</ControlTemplate>
</ResourceDictionary>
Each vector image is a different control template. Our pipeline to create these resource dictionaries is Illustrator -> Microsoft Expression -> XAML. Then we have a custom tool I wrote which takes those individual XAML files from Expression and merges them together into a single resource dictionary and allows the artists to give them a unique key and to provide fill colours for the paths. There is a separate resource dictionary for each "category" of images, e.g. we may have "IconsDatabase.xaml", "ShapesDatabase.xaml", "CommonDatabase.xaml", "SectionADatabase.xaml" etc. Right now I have it so the user controls merge the image resource dictionaries that they require, but I may move this up to the application layer as you suggest which should hopefully make things load quicker.
We can prioritize this task. I assume in this case you will start loading the XAML a few frames before being used? So you will need extra logic to foretell what UI you are going to need, right?
That's correct. We have a window which is always loaded which contain things like loading screens. Depending on which section of the game we are entering we load a window and attach it as a child of the parent window, e.g. we have something like the following:
<window>
<Grid x:Name="CommonUIElements">
<local:LoadingScreenControl x:Name="LoadingScreen" />
</Grid>
<Grid x:Name="ActiveWindows"></Grid>
</window>
Then when we enter say "Section A" of our game, I enable the loading screen control which plays an animation. I then call Noesis::GUI::LoadXaml to instance the SectionAWindow, and then attach it as a child of ActiveWindow:
void UIManager::LoadSectionA()
{
auto pWindow = DynamicPtrCast<Window>(GUI::LoadXaml(Uri("sectiona.xaml")); // Stalls main thread here for ~2 seconds on PC release
Grid *pWindowGrid = FindName<Grid>("ActiveWindows");
UIElementCollection *pGridCollection = nullptr != pWindowGrid ? pWindowGrid->GetChildren() : nullptr;
if (nullptr != pGridCollection)
{
pGridCollection->Clear();
pGridCollection->Add(pWindow);
}
}
So if this was to happen on another "UI Load" thread that would be perfectly find since we're basically in a loading screen and we already have an overlay which is over the top of the scene. Once the elements had been loaded and the visual tree had been created, if the ownership could be passed back to main thread that would be a perfect solution for me.
We have clients using an extra thread for the UI: the main thread, the UI thread where all views are updated, and the render thread where interaction with the GPU happens. But in general this tends to over complicate things and we do not recommend it
Yes I agree, I'd like to avoid this if possible. We currently just have main thread and render thread. All UI is loaded and accessed on main thread. If I had to create another thread for the UI then all the UI interaction code in our game logic would also have to be moved to the ui thread which is a large change I'd like to avoid. The normal CPU cost is relatively low, we just see spikes when the XAML files are parsed and the Visual Hierarchy is being built. We're also seeing mini spikes during gameplay as objects become visible and templates are applied. This is particularly noticeable for large ItemControls. I think I can just get around this by forcing the elements to apply their templates sooner rather than when they become visible.
(long term) We want to implement binary xaml, a converter that transforms XAMLs to optimized runtime format. We had something like this in Noesis v1.0 and we definitely need something similar in v3.
This sounds like a good plan.
If you could create a ticket (private if you need it) with a XAML showing the issue, I would like to profile it because I am pretty sure there are things we can optimize.
I think it sounds like the first things I need to do is sort out my resource dictionaries. Once I've done that I'll take another quick look. If it's still causing noticeable stalls I'll try and set up something via a private ticket. Much appreciated.
Cheers,
-Steven