图形.转换效率非常低,对此我该怎么办
本文关键字:我该怎么办 非常低 转换 效率 图形 | 更新日期: 2023-09-27 18:27:46
我正在编写一个粒子引擎,并注意到它比应该的速度慢得多(我编写了高度未优化的3D C++粒子引擎,可以以60帧/秒的速度渲染50k个粒子,这一个在1.2k左右降至32帧/秒。),我对代码进行了一些分析,假设粒子的渲染或旋转是CPU最密集的操作,然而,我发现事实上,图形对象的这两个小属性实际上消耗了我70%以上的性能。。。。
public void RotateParticle(Graphics g, RectangleF r,
RectangleF rShadow, float angle,
Pen particleColor, Pen particleShadow)
{
//Create a matrix
Matrix m = new Matrix();
PointF shadowPoint = new PointF(rShadow.Left + (rShadow.Width / 1),
rShadow.Top + (rShadow.Height / 1));
PointF particlePoint = new PointF(r.Left + (r.Width / 1),
r.Top + (r.Height / 2));
//Angle of the shadow gets set to the angle of the particle,
//that way we can rotate them at the same rate
float shadowAngle = angle;
m.RotateAt(shadowAngle, shadowPoint);
g.Transform = m;
//rotate and draw the shadow of the Particle
g.DrawRectangle(particleShadow, rShadow.X, rShadow.Y, rShadow.Width, rShadow.Height);
//Reset the matrix for the next draw and dispose of the first matrix
//NOTE: Using one matrix for both the shadow and the partice causes one
//to rotate at half the speed of the other.
g.ResetTransform();
m.Dispose();
//Same stuff as before but for the actual particle
Matrix m2 = new Matrix();
m2.RotateAt(angle, particlePoint);
//Set the current draw location to the rotated matrix point
//and draw the Particle
g.Transform = m2;
g.DrawRectangle(particleColor, r.X, r.Y, r.Width, r.Height);
m2.Dispose();
}
影响我表现的是以下几行:
g.Transform = m;
g.Transform = m2;
有一点背景,图形对象从painteventargs中获取,然后用渲染粒子方法将粒子渲染到屏幕上,该方法调用该方法进行任何旋转,多线程不是解决方案,因为图形对象不能在多个线程之间共享。这里有一个链接到我运行的代码分析,这样你就可以看到正在发生的事情:
https://gyazo.com/229cfad93b5b0e95891eccfbfd056020
我有点认为这是一件没有帮助的事情,因为它看起来像是属性本身正在破坏性能,而不是我实际做过的任何事情(尽管我相信还有改进的空间),尤其是因为类调用的dll使用了最多的cpu功率。无论如何,在尝试优化这一点时,任何帮助都将不胜感激。。。也许我会启用/禁用旋转来提高性能,我们会看到。。。
好吧,您应该仔细考虑一下您看到的概要文件结果。当您指定Transform属性时,还有一些其他正在发生。您可以通过注意ResetTransform()不需要任何成本来推理。当然,该方法也会更改Transform属性。
请注意,DrawRectangle()应该是一种昂贵的方法,因为它实际上是将踏板放在金属上并生成真实绘图命令的方法。我们无法从您的屏幕截图中看到它的成本,不能超过30%。这还远远不够。
我认为您在这里看到的是GDI/plus的一个模糊功能,它批处理绘图命令。换言之,它在内部生成一个绘图命令列表,并在必要时将其传递给视频驱动程序。本机winapi有一个函数明确强制刷新该列表,它就是GdiFlush()。然而,这并不是由.NET Graphics类公开的,而是自动完成的。
因此,一个非常有吸引力的理论是,当您分配Transform属性时,GDI+内部调用GdiFlush()。因此,您看到的成本实际上是前一个DrawRectangle()调用的成本。
你需要通过给它更多的批处理机会来取得进展。非常喜欢Graphics类方法,它可以让你绘制大量的项目。换句话说,不要画每个单独的粒子,而是画很多。您会喜欢DrawRectangles()、DrawLines()和DrawPath()。不幸的是,没有DrawPolygons(),这是你真正喜欢的,从技术上讲,你可以pinvoke PolyPolyPolygon(),但这很难开始。
如果我的理论不正确,请注意,你不需要Graphics.Transform。你也可以使用Matrix.TransformPoints()和Graphics.DrawPolygon()。你是否真的能取得成功还有点疑问,Graphics类不直接使用GPU加速,所以它永远不会与DirectX竞争。
我不确定以下内容是否有帮助,但值得一试。通过Graphics
方法(RotateTransform、ScaleTransform、TranslateTransform)使用预先分配的Graphics.Transform
,而不是分配/分配/处置新的Matrix
(并确保在完成时始终重置变换)。
Graphics
不包含Matrix.RotateAt
方法的直接等价物,但制作一个并不困难
public static class GraphicsExtensions
{
public static void RotateTransformAt(this Graphics g, float angle, PointF point)
{
g.TranslateTransform(point.X, point.Y);
g.RotateTransform(angle);
g.TranslateTransform(-point.X, -point.Y);
}
}
然后你可以像这样更新你的代码,看看这是否有助于
public void RotateParticle(Graphics g, RectangleF r,
RectangleF rShadow, float angle,
Pen particleColor, Pen particleShadow)
{
PointF shadowPoint = new PointF(rShadow.Left + (rShadow.Width / 1),
rShadow.Top + (rShadow.Height / 1));
PointF particlePoint = new PointF(r.Left + (r.Width / 1),
r.Top + (r.Height / 2));
//Angle of the shadow gets set to the angle of the particle,
//that way we can rotate them at the same rate
float shadowAngle = angle;
//rotate and draw the shadow of the Particle
g.RotateTransformAt(shadowAngle, shadowPoint);
g.DrawRectangle(particleShadow, rShadow.X, rShadow.Y, rShadow.Width, rShadow.Height);
g.ResetTransform();
//Same stuff as before but for the actual particle
g.RotateTransformAt(angle, particlePoint);
g.DrawRectangle(particleColor, r.X, r.Y, r.Width, r.Height);
g.ResetTransform();
}
您能创建一个屏幕外缓冲区来绘制粒子,并让OnPaint
简单地渲染屏幕外缓冲区时吗?如果您需要定期更新屏幕,您可以使OnScreen控件/画布无效,例如使用Timer
Bitmap bmp;
Graphics gOff;
void Initialize() {
bmp = new Bitmap(width, height);
gOff = bmp.FromImage();
}
private void OnPaint(object sender, System.Windows.Forms.PaintEventArgs e) {
e.Graphics.DrawImage(bmp, 0, 0);
}
void RenderParticles() {
foreach (var particle in Particles)
RotateParticle(gOff, ...);
}
另一个注意事项是,每次调用RotateParticle
都要创建一个矩阵对象,有什么理由吗?我还没有尝试过,但MSDN文档似乎建议Graphics.Transform
上的get和set将始终创建一个副本。因此,您可以将Matrix
对象保留在类级别,并将其用于转换。只需确保在使用Matrix.Reset()
之前调用它。这可能会提高您的性能。