首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >WPF中的低分配图

WPF中的低分配图
EN

Stack Overflow用户
提问于 2017-07-06 19:38:34
回答 3查看 1.5K关注 0票数 8

对于WPF和使用DrawingContext,我遇到了一些严重的问题,特别是来自于在元素上重写OnRender或如果使用DrawingVisual.RenderOpen()VisualDrawingContext

问题是这分配了很多钱。例如,每次使用绘图上下文时,它似乎都会分配一个byte[]缓冲区。

示例,如何使用绘图上下文。

代码语言:javascript
复制
using (var drawingContext = m_drawingVisual.RenderOpen())
{
    // Many different drawingContext.Draw calls
    // E.g. DrawEllipse, DrawRectangle etc.
}

代码语言:javascript
复制
override void OnRender(DrawingContext drawingContext)
{
    // Many different drawingContext.Draw calls
    // E.g. DrawEllipse, DrawRectangle etc.
}

这会导致大量的分配,导致一些不必要的垃圾收集。所以,是的,我需要这个,请停留在主题上:)。

在具有零或低数目托管堆分配的WPF中,有哪些选项?重用对象很好,但我还没有找到办法.或者在DependencyProperty和它的周围/内部分配上没有问题。

我确实了解WritableBitmapEx,但我希望找到一种解决方案,不需要对预定义的位图进行栅格化,而是提供适当的“矢量”图形,例如仍然可以放大。

注意: CPU的使用是一个问题,但远低于由此造成的巨大垃圾压力。

更新:我正在为.NET框架4.5+寻找一个解决方案,如果在以后的版本(例如4.7 )中有什么可以帮助解决这个问题的话,那就好了。但它适用于桌面.NET框架。

更新2:两种主要场景的简要说明。所有的例子都已经使用CLRProfiler进行了描述,它清楚地表明,许多分配都是由于这一点,这对于我们的用例来说是一个问题。请注意,这是示例代码,目的是传达原则,而不是确切的代码。

A:这个场景如下所示。基本上,会显示一个图像,并通过自定义DrawingVisualControl绘制一些覆盖图形,然后使用using (var drawingContext = m_drawingVisual.RenderOpen())获取绘图上下文,然后通过该上下文绘制。绘制了大量的椭圆、矩形和文本。这个例子也显示了一些缩放的东西,这只是为了放大等等。

代码语言:javascript
复制
<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>

“`DrawingVisualControl”定义为:

代码语言:javascript
复制
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:第二个场景涉及绘制一个移动的数据“网格”,例如20行100列,元素由边框和不同颜色的文本组成,以显示某些状态。网格的移动取决于外部输入,目前只更新5-10次/秒。30 fps会更好。因此,这将更新ObservableCollection中与ListBox绑定的2000项(使用VirtualizingPanel.IsVirtualizing="True"),ItemsPanelCanvas。我们甚至不能在我们的正常用例中展示这一点,因为它分配的太多,以至于GC暂停变得太长和太频繁。

代码语言:javascript
复制
<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>

这里有很多数据绑定,值类型的装箱确实会引起大量的分配,但这并不是主要的问题。这是WPF所做的分配。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2017-08-19 12:37:57

WinForms Canvas解决方案存在一些问题,特别是由于WindowsFormsHost与WPF的交互方式而引起的所谓的“空域”问题。为了保持简短,这意味着不能在主机上绘制WPF可视化。

这可以通过认识到,由于我们无论如何都需要双缓冲,所以我们最好将缓冲区放入WriteableBitmap中,然后再通过Image控件像往常一样绘制。

可以使用如下所示的实用程序类来区分这一点:

代码语言:javascript
复制
using System;
using System.Drawing;
using System.Windows;
using SWM = System.Windows.Media;
using SWMI = System.Windows.Media.Imaging;

public class GdiGraphicsWriteableBitmap
{
    readonly Action<Rectangle, Graphics> m_draw;
    SWMI.WriteableBitmap m_wpfBitmap = null;
    Bitmap m_gdiBitmap = null;

    public GdiGraphicsWriteableBitmap(Action<Rectangle, Graphics> draw)
    {
        if (draw == null) { throw new ArgumentNullException(nameof(draw)); }
        m_draw = draw;
    }

    public SWMI.WriteableBitmap WriteableBitmap => m_wpfBitmap;

    public bool IfNewSizeResizeAndDraw(int width, int height)
    {
        if (m_wpfBitmap == null ||
            m_wpfBitmap.PixelHeight != height ||
            m_wpfBitmap.PixelWidth != width)
        {
            Reset();
            // Can't dispose wpf
            const double Dpi = 96;
            m_wpfBitmap = new SWMI.WriteableBitmap(width, height, Dpi, Dpi,
                SWM.PixelFormats.Bgr24, null);
            var ptr = m_wpfBitmap.BackBuffer;
            m_gdiBitmap = new Bitmap(width, height, m_wpfBitmap.BackBufferStride,
                System.Drawing.Imaging.PixelFormat.Format24bppRgb, ptr);

            Draw();

            return true;
        }
        return false;
    }

    public void Draw()
    {
        if (m_wpfBitmap != null)
        {
            m_wpfBitmap.Lock();
            int width = m_wpfBitmap.PixelWidth;
            int height = m_wpfBitmap.PixelHeight;
            {
                using (var g = Graphics.FromImage(m_gdiBitmap))
                {
                    m_draw(new Rectangle(0, 0, width, height), g);
                }
            }
            m_wpfBitmap.AddDirtyRect(new Int32Rect(0, 0, width, height));
            m_wpfBitmap.Unlock();
        }
    }

    // If window containing this is not shown, one can Reset to stop draw or similar...
    public void Reset()
    {
        m_gdiBitmap?.Dispose();
        m_wpfBitmap = null;
    }
}

然后将ImageSource绑定到XAML中的Image

代码语言:javascript
复制
    <Grid x:Name="ImageContainer" SnapsToDevicePixels="True">
        <Image x:Name="ImageSource" 
               RenderOptions.BitmapScalingMode="HighQuality" SnapsToDevicePixels="True">
        </Image>
    </Grid>

以及调整网格上的处理大小以使WriteableBitmap在大小上匹配,例如:

代码语言:javascript
复制
public partial class SomeView : UserControl
{
    ISizeChangedViewModel m_viewModel = null;

    public SomeView()
    {
        InitializeComponent();

        this.DataContextChanged += OnDataContextChanged;
    }

    void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        if (m_viewModel != null)
        {
            this.ImageContainer.SizeChanged -= ImageSource_SizeChanged;
        }
        m_viewModel = e.NewValue as ISizeChangedViewModel;
        if (m_viewModel != null)
        {
            this.ImageContainer.SizeChanged += ImageSource_SizeChanged;
        }
    }

    private void ImageSource_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        var newSize = e.NewSize;
        var width = (int)Math.Round(newSize.Width);
        var height = (int)Math.Round(newSize.Height);
        m_viewModel?.SizeChanged(width, height);
    }
}

通过这种方式,您可以使用WinForms/GDI+进行绘图,如果您愿意,可以使用零堆分配,甚至可以使用WriteableBitmapEx。请注意,您随后在DrawString incl中获得了很好的GDI+支持。MeasureString

缺点是这是光栅化的,有时可能会有一些插值问题。因此,也要确保在父窗口/用户控件上设置UseLayoutRounding="True"

票数 1
EN

Stack Overflow用户

发布于 2017-07-12 07:07:11

一些投入

您的代码是不可用的,所以我只能建议。在性能方面,请使用Microsoft提供的分析工具。您可以找到工具这里

您可以阅读的另一个重要链接是WPF图形

注:-尝试使用绘图组

票数 1
EN

Stack Overflow用户

发布于 2017-07-16 07:48:39

使用WindowsFormsHost,如演练:使用XAML在WPF中承载Windows窗体控件和GDI+中所述,用于绘图。这不是一个完美的解决方案,但就目前而言,这是我能找到的最好的选择。

代码语言:javascript
复制
<Grid>
    <WindowsFormsHost x:Name="WinFormsHost>
        <custom:Canvas x:Name="Canvas" />
    </WindowsFormsHost>
</Grid>

然后创建一个自定义控件并重写OnPaint,如下所示:

代码语言:javascript
复制
public partial class Canvas 
    : UserControl
{
    // Implementing custom double buffered graphics, since this is a lot
    // faster both when drawing and with respect to GC, since normal
    // double buffered graphics leaks disposable objects that the GC needs to finalize
    protected BufferedGraphicsContext m_bufferedGraphicsContext = 
        new BufferedGraphicsContext();
    protected BufferedGraphics m_bufferedGraphics = null;
    protected Rectangle m_currentClientRectangle = new Rectangle();

    public Canvas()
    {
        InitializeComponent();
        Setup();
    }

    private void Setup()
    {
        SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint |
                 ControlStyles.Opaque | ControlStyles.ResizeRedraw, true);
        DoubleBuffered = false;

        this.Dock = DockStyle.Fill;
    }

    private void DisposeManagedResources()
    {
        m_bufferedGraphicsContext.Dispose();
        if (m_bufferedGraphics != null)
        {
            m_bufferedGraphics.Dispose();
        }
    }

    protected override void OnPaintBackground(PaintEventArgs e)
    {
        // Background paint is done in OnPaint
        // This reduces the "leaks" of System.Windows.Forms.Internal.DeviceContext 
        // and the amount of "GC" handles created considerably
        // as found by using CLR Profiler
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        // Specifically not calling base here since we draw entire area ourselves
        // base.OnPaint(e);
        // Should this be disposed? 
        using (e)
        using (var targetGraphics = e.Graphics)
        {
            ReallocBufferedGraphics(targetGraphics);

            // Use buffered graphics object
            var graphics = m_bufferedGraphics.Graphics;
            // Raise paint event
            PaintEvent?.Invoke(this.ClientRectangle, e.ClipRectangle, graphics);

            // Render to target graphics i.e. paint event args graphics
            m_bufferedGraphics.Render(targetGraphics);
        }
    }

    protected virtual void ReallocBufferedGraphics(Graphics graphics)
    {
        Rectangle newClientRectangle = this.ClientRectangle;

        // Realloc if new client rectangle is not contained within the current
        // or if no buffered graphics exists
        bool reallocBufferedGraphics = ShouldBufferBeReallocated(newClientRectangle);
        if (reallocBufferedGraphics)
        {
            if (m_bufferedGraphics != null)
            {
                m_bufferedGraphics.Dispose();
            }
            m_bufferedGraphics = m_bufferedGraphicsContext.Allocate(
                 graphics, newClientRectangle);
            m_currentClientRectangle = newClientRectangle;
        }
    }

    protected virtual bool ShouldBufferBeReallocated(Rectangle newClientRectangle)
    {
        return !m_currentClientRectangle.Contains(newClientRectangle) || 
                m_bufferedGraphics == null;
    }

    /// <summary>
    /// PaintEvent with <c>clientRectangle, clipRectangle, graphics</c> for the canvas.
    /// </summary>
    public event Action<Rectangle, Rectangle, Graphics> PaintEvent;
}

更新:更新后的画布控件才是真正的堆分配。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/44957522

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档