2015-12-23 12 views
8

Tôi đang viết một động cơ hạt và nhận thấy nó chậm hơn rất nhiều so với (Tôi đã viết các công cụ hạt 3D C++ không được tối ưu hóa cao có thể khiến các hạt 50k ở tốc độ 60 khung hình/giây, giảm xuống còn 32 khung hình/giây ở khoảng 1,2 k ..), tôi đã thực hiện một số phân tích về mã giả định kết xuất của các hạt hoặc phép quay là hoạt động chuyên sâu nhất của CPU, tuy nhiên tôi phát hiện ra rằng trên thực tế hai thuộc tính nhỏ này của đối tượng đồ họa thực sự chiếm hơn 70% hiệu suất của tôi ....Graphics.Transform là không hiệu quả ồ ạt, tôi có thể làm gì với điều này?

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ì đang giết chết hiệu suất của tôi là đặc biệt những dòng này:

g.Transform = m; 
g.Transform = m2; 

Một nền nhỏ, đối tượng đồ họa được lấy từ painteventargs, sau đó nó hiển thị các hạt cho màn hình trong phương thức render particles, gọi phương thức này để thực hiện bất kỳ phép quay nào, đa luồng không phải là giải pháp như đối tượng đồ họa không thể được chia sẻ giữa nhiều luồng. Dưới đây là một liên kết đến các phân tích mã Tôi chạy chỉ để bạn có thể xem những gì đang xảy ra cũng như:

https://gyazo.com/229cfad93b5b0e95891eccfbfd056020

Tôi kinda suy nghĩ này là một cái gì đó mà có thể không thực sự được giúp đỡ bởi vì nó trông giống như bất động sản chính nó là phá hủy hiệu suất và không phải bất cứ điều gì tôi đã thực sự thực hiện (mặc dù tôi chắc chắn có chỗ để cải thiện), đặc biệt là kể từ khi dll lớp gọi là sử dụng sức mạnh cpu nhất. Dù sao, bất kỳ trợ giúp sẽ được đánh giá rất cao trong việc cố gắng tối ưu hóa điều này ... có lẽ tôi sẽ chỉ kích hoạt/vô hiệu hóa vòng quay để tăng hiệu suất, chúng tôi sẽ ...

+0

Ngoài ra tôi xin lỗi vì hình ảnh, tôi biết nó thực sự nhỏ, nếu bạn nheo mắt, bạn có thể thấy các con số mặc dù! : P –

Trả lời

3

Vâng, bạn nên làm xước đầu của bạn một thời gian trên kết quả hồ sơ mà bạn nhìn thấy. Có một cái gì đó khác đang diễn ra khi bạn chỉ định thuộc tính Biến đổi. Một cái gì đó bạn có thể lý do bằng cách lưu ý rằng ResetTransform() không chi phí bất cứ điều gì. Không có ý nghĩa tất nhiên, phương pháp đó cũng thay đổi thuộc tính Transform. Và cần lưu ý rằng nó phải là DrawRectangle() nên là phương pháp đắt tiền vì đó là phương pháp thực sự đặt bàn đạp vào kim loại và tạo ra các lệnh vẽ thực. Chúng tôi không thể thấy chi phí từ ảnh chụp màn hình của bạn, không được vượt quá 30%. Điều đó không đủ gần.

Tôi nghĩ rằng những gì bạn thấy ở đây là một tính năng tối nghĩa của GDI/plus, nó lệnh vẽ. Nói cách khác, nội bộ nó tạo ra một danh sách các lệnh vẽ và không chuyển chúng cho trình điều khiển video cho đến khi nó phải. Các winapi bản địa có một chức năng mà rõ ràng buộc danh sách đó được flushed, nó là GdiFlush(). Tuy nhiên, đó không phải là lớp .NET Graphics, nó được thực hiện tự động.

Vì vậy, một lý thuyết khá hấp dẫn là GDI + gọi nội bộ GdiFlush() khi bạn chỉ định thuộc tính Biến đổi. Vì vậy, chi phí bạn đang xem là thực tế là chi phí của một cuộc gọi DrawRectangle() trước đó.

Bạn cần tiến lên bằng cách cho nó nhiều cơ hội hơn để thực hiện hàng loạt. Rất ủng hộ phương thức lớp đồ họa cho phép bạn vẽ một số lượng lớn các mục. Nói cách khác, không vẽ từng hạt riêng lẻ mà vẽ nhiều. Bạn sẽ thích DrawRectangles(), DrawLines(), DrawPath(). Thật không may là DrawPolygons(), một trong những bạn thực sự thích, về mặt kỹ thuật bạn có thể pinvoke PolyPolygon() nhưng đó là khó khăn để có được đi.

Nếu lý thuyết của tôi không đúng thì lưu ý rằng bạn không cần Graphics.Transform. Bạn cũng có thể sử dụng Matrix.TransformPoints() và Graphics.DrawPolygon(). Cho dù bạn thực sự có thể vượt lên được một chút nghi ngờ, lớp đồ họa không sử dụng tăng tốc GPU trực tiếp để nó không bao giờ cạnh tranh tốt với DirectX.

+0

câu trả lời rắn, có ý nghĩa, và nó sẽ giải thích rất nhiều về lý do tại sao mỗi cuộc gọi vẽ cá nhân là rất mãnh liệt. Tôi sẽ xem xét điều này khi tôi đi làm về và cho bạn biết nó như thế nào, cảm ơn rất nhiều! –

3

Tôi không chắc chắn những điều sau đây có thể trợ giúp không , nhưng đáng để thử. Thay vì phân bổ/chỉ định/xử lý mới Matrix, hãy sử dụng số Graphics.Transform preallocated qua Graphics phương thức - RotateTransform, ScaleTransform, TranslateTransform (và đảm bảo luôn luôn ResetTransform khi hoàn tất).

Các Graphics không chứa tương đương trực tiếp của Matrix.RotateAt phương pháp, nhưng nó không phải là khó để làm một

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); 
    } 
} 

Sau đó, bạn có thể cập nhật mã của bạn như thế này và xem giúp

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(); 
} 
+0

Điều này thực sự đã giúp FPS một chút, nó không thực sự giúp đỡ khi các hạt lớn nhưng khi chúng nhỏ hơn có một chút về hiệu năng, không may nó vẫn mất 64 fps ở khoảng 1700 hạt. Ngoài ra luân phiên là rất khác nhau với phương pháp này, nhưng trong một cách tốt, nó trông mát mẻ :) –

1

Bạn có thể tạo bộ đệm ngoài màn hình để vẽ hạt của mình và có OnPaint chỉ cần hiển thị bộ đệm tắt màn hình không? Nếu bạn cần phải thường xuyên cập nhật màn hình của bạn, bạn có thể làm mất hiệu lực kiểm soát trên màn hình của bạn/vải, nói bằng một 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, ...); 
} 


Ngày lưu ý khác, vì lý do nào để tạo ra một đối tượng ma trận mỗi khi bạn gọi RotateParticle ? Tôi đã không thử nó, nhưng các tài liệu MSDN dường như đề nghị rằng có được và thiết lập trên Graphics.Transform sẽ luôn luôn tạo ra một bản sao. Vì vậy, bạn có thể giữ đối tượng Matrix ở cấp độ lớp nói và sử dụng nó để chuyển đổi. Chỉ cần chắc chắn để gọi Matrix.Reset() trước khi sử dụng nó. Điều này có thể giúp bạn cải thiện hiệu suất.

Các vấn đề liên quan