I am having some serious issues with WPF and using DrawingContext, or specifically VisualDrawingContext coming from overriding OnRender on an element or if using DrawingVisual.RenderOpen().
The problem is this allocates a lot. For example, it seems to be allocating a byte[] buffer each time a drawing context is used.
Examples, of how drawing context is used.
using (var drawingContext = m_drawingVisual.RenderOpen())
{
// Many different drawingContext.Draw calls
// E.g. DrawEllipse, DrawRectangle etc.
}
or
override void OnRender(DrawingContext drawingContext)
{
// Many different drawingContext.Draw calls
// E.g. DrawEllipse, DrawRectangle etc.
}
This causes a lot of allocations, causing some unwanted garbage collections. So yes I need this, and please stay on topic :).
What are the options for drawing in WPF with zero or low number of managed heap allocations? Reusing objects is fine, but I have yet to find a way to do this... or doesn't then have issues with DependencyProperty and allocations around/inside it.
I do know about WritableBitmapEx but was hoping for a solution that does not involve rasterising to predefined bitmap, but instead proper "vector" graphics that can still be zoomed for example.
NOTE: CPU usage is a concern but much less than the massive garbage pressure caused by this.
UPDATE: I am looking for a solution for .NET Framework 4.5+, if there is anything in later versions e.g. 4.7 that might help answer this then that is fine. But it is for the desktop .NET Framework.
UPDATE 2: A brief description of the two main scenarios. All examples have been profiled with CLRProfiler, and it shows clearly that lots of allocations occur due to this and that this is a problem for our use case. Note that this is example code intended to convey the principles not the exact code.
A: This scenario is shown below. Basically, an image is shown and some overlay graphics are drawn via a custom DrawingVisualControl, which then uses using (var drawingContext = m_drawingVisual.RenderOpen()) to get a drawing context and then draws via that. Lots of ellipse, rectangles and text is drawn. This example also shows some scaling stuff, this is just for zooming etc.
<Viewbox x:Name="ImageViewbox" VerticalAlignment="Center" HorizontalAlignment="Center">
<Grid x:Name="ImageGrid" SnapsToDevicePixels="True" ClipToBounds="True">
<Grid.LayoutTransform>
<ScaleTransform x:Name="ImageTransform" CenterX="0" CenterY="0"
ScaleX="{Binding ElementName=ImageScaleSlider, Path=Value}"
ScaleY="{Binding ElementName=ImageScaleSlider, Path=Value}" />
</Grid.LayoutTransform>
<Image x:Name="ImageSource" RenderOptions.BitmapScalingMode="NearestNeighbor" SnapsToDevicePixels="True"
MouseMove="ImageSource_MouseMove" />
<v:DrawingVisualControl x:Name="DrawingVisualControl" Visual="{Binding DrawingVisual}"
SnapsToDevicePixels="True"
RenderOptions.BitmapScalingMode="NearestNeighbor"
IsHitTestVisible="False" />
</Grid>
</Viewbox>
The `DrawingVisualControl is defined as:
public class DrawingVisualControl : FrameworkElement
{
public DrawingVisual Visual
{
get { return GetValue(DrawingVisualProperty) as DrawingVisual; }
set { SetValue(DrawingVisualProperty, value); }
}
private void UpdateDrawingVisual(DrawingVisual visual)
{
var oldVisual = Visual;
if (oldVisual != null)
{
RemoveVisualChild(oldVisual);
RemoveLogicalChild(oldVisual);
}
AddVisualChild(visual);
AddLogicalChild(visual);
}
public static readonly DependencyProperty DrawingVisualProperty =
DependencyProperty.Register("Visual",
typeof(DrawingVisual),
typeof(DrawingVisualControl),
new FrameworkPropertyMetadata(OnDrawingVisualChanged));
private static void OnDrawingVisualChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var dcv = d as DrawingVisualControl;
if (dcv == null) { return; }
var visual = e.NewValue as DrawingVisual;
if (visual == null) { return; }
dcv.UpdateDrawingVisual(visual);
}
protected override int VisualChildrenCount
{
get { return (Visual != null) ? 1 : 0; }
}
protected override Visual GetVisualChild(int index)
{
return this.Visual;
}
}
B: The second scenario involves drawing a moving "grid" of data e.g. 20 rows of 100 columns, with elements consisting of a border and text with different colors to display some status. The grid moves depending on external input, and for now is only updated 5-10 times per second. 30 fps would be better. This, thus, updates 2000 items in an ObservableCollection tied to a ListBox (with VirtualizingPanel.IsVirtualizing="True") and the ItemsPanel being a Canvas. We can't even show this during our normal use case, since it allocates so much that the GC pauses become way too long and frequent.
<ListBox x:Name="Items" Background="Black"
VirtualizingPanel.IsVirtualizing="True" SnapsToDevicePixels="True">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type vm:ElementViewModel}">
<Border Width="{Binding Width_mm}" Height="{Binding Height_mm}"
Background="{Binding BackgroundColor}"
BorderBrush="{Binding BorderColor}"
BorderThickness="3">
<TextBlock Foreground="{Binding DrawColor}" Padding="0" Margin="0"
Text="{Binding TextResult}" FontSize="{Binding FontSize_mm}"
TextAlignment="Center" VerticalAlignment="Center"
HorizontalAlignment="Center"/>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Canvas.Left" Value="{Binding X_mm}"/>
<Setter Property="Canvas.Top" Value="{Binding Y_mm}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True"
Width="{Binding CanvasWidth_mm}"
Height="{Binding CanvasHeight_mm}"
/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
There is a lot of data binding here, and the box'ing of value types do incur a lot of allocations, but that is not the main problem here. It is the allocations done by WPF.