图形.转换效率非常低,对此我该怎么办

本文关键字:我该怎么办 非常低 转换 效率 图形 | 更新日期: 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()之前调用它。这可能会提高您的性能。