我正在编写一个粒子引擎并注意到它的速度比应有的慢得多(我编写的高度未经优化的3D C ++粒子引擎能够以60 fps渲染50k粒子,这一粒子在周围降至32 fps 1.2k ..),我对代码进行了一些分析,假设渲染粒子或旋转是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功率。无论如何,任何帮助都会非常感激,试图优化这个...也许我只是启用/禁用旋转来提高性能,我们会看到......
答案 0 :(得分:5)
嗯,你应该在你看到的个人资料结果上刮一会儿。分配Transform属性时会发生 else 。你可以通过注意到ResetTransform()没有任何成本来解释。当然没有意义,该方法也会改变Transform属性。
请注意它应该是DrawRectangle()应该是昂贵的方法,因为那是实际将踏板放到金属上并生成真正的绘图命令的方法。我们无法从您的屏幕截图中看到它的成本,但不能超过30%。这还不够。
我认为你在这里看到的是GDI / plus的一个模糊特征,它是批量绘图命令。换句话说,在内部它会生成一个绘图命令列表,并且在它必须之前不会将它们传递给视频驱动程序。原生winapi有一个明确强制刷新列表的函数,它是GdiFlush()。然而,.NET Graphics类没有公开它,它是自动完成的。
因此,一个非常有吸引力的理论是,当您分配Transform属性时,GDI +会在内部调用GdiFlush()。因此,您所看到的成本实际上之前的DrawRectangle()调用的成本。
您需要为其提供更多批处理机会。非常强烈支持使用Graphics类方法绘制大量项目。换句话说,不要绘制每个单独的粒子,而是绘制许多粒子。你喜欢DrawRectangles(),DrawLines(),DrawPath()。不幸的是没有DrawPolygons(),你真正喜欢的那个,从技术上来说你可以调整PolyPolygon(),但很难开始。
如果我的理论不正确,请注意您不需要Graphics.Transform。您还可以使用Matrix.TransformPoints()和Graphics.DrawPolygon()。无论你是否能真正获得成功都有点令人怀疑,Graphics类并没有直接使用GPU加速,所以它永远不会与DirectX竞争。
答案 1 :(得分:3)
我不确定以下内容是否会有所帮助,但值得一试。不要分配/分配/处置新的Matrix
,而是使用预先分配的Graphics.Transform
通过Graphics
方法 - RotateTransform,ScaleTransform,TranslateTransform(并制作确保在完成后始终ResetTransform。
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();
}
答案 2 :(得分:1)
你能创建一个离屏缓冲区来绘制你的粒子,并让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
上获取和设置将始终创建一个副本。因此,您可以将Matrix
对象保持在类级别并将其用于转换。请务必在使用之前致电Matrix.Reset()
。这可能会让您获得一些性能提升。