早上好,
我自学了一些使用C#编写Direct2D程序的技术,利用现有的本地包装器(目前使用d2dSharp,但也尝试过SharpDX)。然而,我遇到了效率问题,基本的Direct2D绘图方法需要大约250毫秒才能绘制45,000个基本多边形。我看到的性能与Windows GDI+相当,甚至更慢。我希望有人可以看看我的代码,并提出可能显著改善绘制时间的方法。
这背后的背景是,我有一个个人项目,正在开发一个基本但功能齐全的CAD接口,能够执行各种任务,包括二维有限元分析。为了使其有用,界面需要能够显示数万个原始元素(多边形、圆、矩形、点、弧等)。
我最初使用Windows GDI+(System.Drawing)编写了绘图方法,在任何给定时间屏幕上显示的元素不超过3,000时,性能相当不错。每当用户进行平移、缩放、绘制新元素、删除元素、移动、旋转等操作时,都必须更新屏幕。现在,为了提高效率,我利用四叉树数据结构存储元素,只绘制实际位于控件窗口范围内的元素。这在放大时有很大帮助,但显然,在完全缩小和显示所有元素时没有任何作用。我还使用计时器和tick事件以刷新率(60 Hz)更新屏幕,因此我不会尝试每秒数千次或每个鼠标事件都进行更新。
这是我第一次使用DirectX和Direct2D进行编程,所以我肯定正在学习。也就是说,我花了几天时间查看教程、示例和论坛,并没有找到太多有用的东西。我已经尝试了一打不同的绘图、预处理、多线程等方法。以下是我的代码:
创建Direct2D工厂和渲染目标的代码
我创建了一个Direct2D工厂和渲染目标,并在始终保持对它们的引用(这样我就不需要每次重新创建)。当绘图层(描述颜色、宽度等信息)创建时,我也同时创建所有的笔刷。因此,我不是每次绘制都创建新的笔刷,而是引用已经存在的笔刷。正如第二段代码片段所示,几何图形也是一样。我只在创建时创建几何图形,并且仅在元素本身移动或旋转时更新几何图形。否则,在绘制后,我只需对渲染目标应用变换即可。
根据我的计时器显示,循环调用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毫秒才能完成绘制。这要好得多,但仍不够平滑。您还有其他建议吗?
我自学了一些使用C#编写Direct2D程序的技术,利用现有的本地包装器(目前使用d2dSharp,但也尝试过SharpDX)。然而,我遇到了效率问题,基本的Direct2D绘图方法需要大约250毫秒才能绘制45,000个基本多边形。我看到的性能与Windows GDI+相当,甚至更慢。我希望有人可以看看我的代码,并提出可能显著改善绘制时间的方法。
这背后的背景是,我有一个个人项目,正在开发一个基本但功能齐全的CAD接口,能够执行各种任务,包括二维有限元分析。为了使其有用,界面需要能够显示数万个原始元素(多边形、圆、矩形、点、弧等)。
我最初使用Windows GDI+(System.Drawing)编写了绘图方法,在任何给定时间屏幕上显示的元素不超过3,000时,性能相当不错。每当用户进行平移、缩放、绘制新元素、删除元素、移动、旋转等操作时,都必须更新屏幕。现在,为了提高效率,我利用四叉树数据结构存储元素,只绘制实际位于控件窗口范围内的元素。这在放大时有很大帮助,但显然,在完全缩小和显示所有元素时没有任何作用。我还使用计时器和tick事件以刷新率(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工厂和渲染目标,并在始终保持对它们的引用(这样我就不需要每次重新创建)。当绘图层(描述颜色、宽度等信息)创建时,我也同时创建所有的笔刷。因此,我不是每次绘制都创建新的笔刷,而是引用已经存在的笔刷。正如第二段代码片段所示,几何图形也是一样。我只在创建时创建几何图形,并且仅在元素本身移动或旋转时更新几何图形。否则,在绘制后,我只需对渲染目标应用变换即可。
根据我的计时器显示,循环调用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毫秒才能完成绘制。这要好得多,但仍不够平滑。您还有其他建议吗?
foreach
才是。List<T>
通常非常快,并且比使用数组更好。 - xxbbcc