【问题标题】:C# How to Improve Efficiency in Direct2D DrawingC# 如何提高 Direct2D 绘图的效率
【发布时间】:2019-08-12 14:53:15
【问题描述】:

早上好,

我一直在自学一些 C# 中的 Direct2D 编程,利用可用的本机包装器(目前使用 d2dSharp,但也尝试过 SharpDX)。但是,我遇到了效率问题,基本绘图 Direct2D 绘图方法需要大约 250 毫秒来绘制 45,000 个基本多边形。我看到的性能与 Windows GDI+ 相当,甚至更慢。我希望有人可以看看我所做的事情并提出一种方法,我可以显着缩短绘图所需的时间。

背景是我有一个个人项目,我正在开发一个基本但功能强大的 CAD 界面,该界面能够执行各种任务,包括 2D 有限元分析。为了使它完全有用,界面需要能够显示数以万计的原始元素(多边形、圆形、矩形、点、弧线等)。

我最初使用 Windows GDI+ (System.Drawing) 编写绘图方法,性能非常好,直到我在任何给定时间在屏幕上达到大约 3,000 个元素。每当用户平移、缩放、绘制新元素、删除元素、移动、旋转等时,屏幕都必须更新。现在,为了提高效率,我使用四叉树数据结构来存储我的元素,我只绘制实际上落在控制窗口范围内的元素。这在放大时有很大帮助,但显然,当完全缩小并显示所有元素时,它没有任何区别。我还使用计时器和滴答事件以刷新率 (60 Hz) 更新屏幕,因此我不会尝试每秒更新数千次或每次鼠标事件。

这是我第一次使用 DirectX 和 Direct2D 进行编程,所以我肯定会在这里学习。话虽如此,我花了几天时间查看教程、示例和论坛,但找不到太多帮助。我尝试了十几种不同的绘图、预处理、多线程等方法。我的代码如下

循环和绘制元素的代码

List<IDrawingElement> elementsInBounds = GetElementsInDraftingWindow();

_d2dContainer.Target.BeginDraw();
_d2dContainer.Target.Clear(ColorD2D.FromKnown(Colors.White, 1));

if (elementsInBounds.Count > 0)
{
    Stopwatch watch = new Stopwatch();
    watch.Start();

    #region Using Drawing Element DrawDX Method

    foreach (IDrawingElement elem in elementsInBounds)
    {
        elem.DrawDX(ref _d2dContainer.Target, ref _d2dContainer.Factory, ZeroPoint, DrawingScale, _selectedElementBrush, _selectedElementPointBrush);
    }

    #endregion

    watch.Stop();
    double drawingTime = watch.ElapsedMilliseconds;
    Console.WriteLine("DirectX drawing time = " + drawingTime);
    watch.Reset();
    watch.Start();

    Matrix3x2 scale = Matrix3x2.Scale(new SizeFD2D((float)DrawingScale, (float)DrawingScale), new PointFD2D(0, 0));
    Matrix3x2 translate = Matrix3x2.Translation((float)ZeroPoint.X, (float)ZeroPoint.Y);

    _d2dContainer.Target.Transform = scale * translate;

    watch.Stop();
    double transformTime = watch.ElapsedMilliseconds;
    Console.WriteLine("DirectX transform time = " + transformTime);
}

多边形的 DrawDX 函数

public override void DrawDX(ref WindowRenderTarget rt, ref Direct2DFactory fac, Point zeroPoint, double drawingScale, SolidColorBrush selectedLineBrush, SolidColorBrush selectedPointBrush)
    {
        if (_pathGeometry == null)
        {
            CreatePathGeometry(ref fac);
        }

        float brushWidth = (float)(Layer.Width / (drawingScale));
        brushWidth = (float)(brushWidth * 2);

        if (Selected == false)
        {
            rt.DrawGeometry(Layer.Direct2DBrush, brushWidth, _pathGeometry);
            //Note that _pathGeometry is a PathGeometry
        }
        else
        {
            rt.DrawGeometry(selectedLineBrush, brushWidth, _pathGeometry);
        }
    }

创建 Direct2D 工厂和渲染目标的代码

private void CreateD2DResources(float dpiX, float dpiY)
    {
        Factory = Direct2DFactory.CreateFactory(FactoryType.SingleThreaded, DebugLevel.None, FactoryVersion.Auto);

        RenderTargetProperties props = new RenderTargetProperties(
            RenderTargetType.Default, new PixelFormat(DxgiFormat.B8G8R8A8_UNORM,
            AlphaMode.Premultiplied), dpiX, dpiY, RenderTargetUsage.None, FeatureLevel.Default);

        Target = Factory.CreateWindowRenderTarget(_targetPanel, PresentOptions.None, props);
        Target.AntialiasMode = AntialiasMode.Aliased;

        if (_selectionBoxLeftStrokeStyle != null)
        {
            _selectionBoxLeftStrokeStyle.Dispose();
        }

        _selectionBoxLeftStrokeStyle = Factory.CreateStrokeStyle(new StrokeStyleProperties1(LineCapStyle.Flat,
                LineCapStyle.Flat, LineCapStyle.Flat, LineJoin.Bevel, 10, DashStyle.Dash, 0, StrokeTransformType.Normal), null);
    }

我创建了一个 Direct2D 工厂并渲染目标一次,并始终保持对它们的引用(这样我就不会每次都重新创建)。在创建绘图层(描述颜色、宽度等)时,我还会创建所有画笔。因此,我不会在每次绘制时都创建一个新画笔,而只是引用一个已经存在的画笔。与几何相同,如第二个代码-sn-p 所示。我创建了一次几何图形,并且仅在元素本身移动或旋转时才更新几何图形。否则,我只是在绘制后对渲染目标应用变换。

根据我的秒表,循环和调用 elem.DrawDX 方法所需的时间大约为 225-250 毫秒(对于 45,000 个多边形)。应用转换所需的时间为 0-1 毫秒,因此瓶颈似乎在 RenderTarget.DrawGeometry() 函数中。

我已经使用 RenderTarget.DrawEllipse() 或 RenderTarget.DrawRectangle() 进行了相同的测试,因为我已经了解到使用 DrawGeometry 比 DrawRectangle 或 DrawEllipse 慢,因为矩形/椭圆几何是事先已知的。然而,在我所有的测试中,我使用哪个绘图函数并不重要,相同数量的元素的时间总是大致相等的。

我尝试构建一个多线程 Direct2D 工厂并通过任务运行绘图函数,但这要慢得多(大约慢两倍)。 Direct2D 方法似乎正在使用我的显卡(启用了硬件加速),因为当我监控我的显卡使用情况时,它会在屏幕更新时达到峰值(我的笔记本电脑有一个 NVIDIA Quadro 移动显卡)。

为冗长的帖子道歉。我希望这是对我尝试过的事情的足够背景和描述。提前感谢您的帮助!

编辑#1 因此,将代码从使用 foreach 迭代列表更改为使用 for 迭代数组,从而将绘制时间缩短了一半!我没有意识到列表比数组慢多少(我知道有一些性能优势,但没有意识到这一点!)。然而,绘制仍然需要 125 毫秒。这要好得多,但仍然不顺利。还有其他建议吗?

【问题讨论】:

  • 一个 list 并不慢 - foreach 是。 List&lt;T&gt; 比一般使用数组更快更好。
  • 见这篇文章:codingsight.com/foreach-or-for-that-is-the-question 自己没有审查过,看起来它可能不是 100% 正确,但似乎有一些迹象表明列表比数组慢。不过,我将仅使用列表进行检查。看看会发生什么。
  • 文章有2星是有原因的。 List 只是一个具有内部逻辑的数组,可以根据需要调整其大小,但在访问元素时并不适用。
  • 啊,好的。 Direct2D 绝对应该比 Win32 GDI 快(即使两者都被加速)。我不是 D2D 专家,所以我无法为您提供太多帮助 - 请确保您的输出表面配置正确。您可能还需要考虑双缓冲或三缓冲,以实现快速输出和有效使用与上次绘制相比没有变化的剪辑。
  • 您的大错误是您认为在 CAD 图层中进行大规模绘图很容易。它不是,你需要很长时间来全面优化它。重绘一切永远不会有助于扩大规模。使用多层和其他技术。最快的重绘是不需要做的重绘。还要意识到画笔和文本布局很糟糕(而且我真的很糟糕效率低下)。一个包含 10 个字符的 DirectWrite TextLayout 会消耗 12KByte 的内存。分析一切并优化一切。并计划在快速绘图层上工作 6 个月。

标签: c# direct2d


【解决方案1】:

Direct2D 可以与 P/Invoke 一起使用 请参阅示例“VB Direct2D 像素完美碰撞” 来自https://social.msdn.microsoft.com/Forums/en-US/cea42526-4b82-454d-9d79-2e1d94083552/collisions?forum=vbgeneral 动画是完美的,即使是在 VB 中完成的

【讨论】:

    猜你喜欢
    • 2019-04-27
    • 1970-01-01
    • 2018-04-18
    • 2013-07-03
    • 1970-01-01
    • 2012-09-20
    • 2017-04-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多