Graphics.Transform非常低效,我该怎么办呢?

时间:2015-12-23 07:34:18

标签: c# winforms performance graphics

我正在编写一个粒子引擎并注意到它的速度比应有的慢得多(我编写的高度未经优化的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功率。无论如何,任何帮助都会非常感激,试图优化这个...也许我只是启用/禁用旋转来提高性能,我们会看到......

3 个答案:

答案 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方法 - RotateTransformScaleTransformTranslateTransform(并制作确保在完成后始终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()。这可能会让您获得一些性能提升。