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