Tại sao UIBezierPath nhanh hơn đường dẫn Core Graphics?


90

Tôi đang thử với các đường vẽ và tôi nhận thấy rằng trong ít nhất một số trường hợp, UIBezierPath hoạt động tốt hơn những gì tôi nghĩ là một Core Graphics tương đương. Các -drawRect:phương pháp dưới đây sẽ tạo ra hai con đường: một UIBezierPath, và một CGPath. Các đường dẫn giống hệt nhau ngoại trừ vị trí của chúng, nhưng vuốt CGPath mất khoảng gấp đôi thời gian vuốt UIBezierPath.

- (void)drawRect:(CGRect)rect
{
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    // Create the two paths, cgpath and uipath.
    CGMutablePathRef cgpath = CGPathCreateMutable();
    CGPathMoveToPoint(cgpath, NULL, 0, 100);

    UIBezierPath *uipath = [[UIBezierPath alloc] init];
    [uipath moveToPoint:CGPointMake(0, 200)];

    // Add 200 curve segments to each path.
    int iterations = 200;
    CGFloat cgBaseline = 100;
    CGFloat uiBaseline = 200;
    CGFloat xincrement = self.bounds.size.width / iterations;
    for (CGFloat x1 = 0, x2 = xincrement;
         x2 < self.bounds.size.width;
         x1 = x2, x2 += xincrement)
    {
        CGPathAddCurveToPoint(cgpath, NULL, x1, cgBaseline-50, x2, cgBaseline+50, x2, cgBaseline);
        [uipath addCurveToPoint:CGPointMake(x2, uiBaseline)
                  controlPoint1:CGPointMake(x1, uiBaseline-50)
                  controlPoint2:CGPointMake(x2, uiBaseline+50)];
    }
    [[UIColor blackColor] setStroke];
    CGContextAddPath(ctx, cgpath);

    // Stroke each path.
    [self strokeContext:ctx];
    [self strokeUIBezierPath:uipath];

    [uipath release];
    CGPathRelease(cgpath);
}

- (void)strokeContext:(CGContextRef)context
{
    CGContextStrokePath(context);
}

- (void)strokeUIBezierPath:(UIBezierPath*)path
{
    [path stroke];
}

Cả hai đường dẫn đều sử dụng CGContextStrokePath (), vì vậy tôi đã tạo các phương thức riêng biệt để vuốt từng đường dẫn để tôi có thể xem thời gian được sử dụng bởi từng đường dẫn trong Instruments. Dưới đây là các kết quả điển hình (gọi ngược cây); bạn có thể thấy -strokeContext:mất 9,5 giây, trong khi -strokeUIBezierPath:chỉ mất 5 giây.:

Running (Self)      Symbol Name
14638.0ms   88.2%               CGContextStrokePath
9587.0ms   57.8%                 -[QuartzTestView strokeContext:]
5051.0ms   30.4%                 -[UIBezierPath stroke]
5051.0ms   30.4%                  -[QuartzTestView strokeUIBezierPath:]

Có vẻ như UIBezierPath bằng cách nào đó đang tối ưu hóa đường dẫn mà nó tạo ra hoặc tôi đang tạo CGPath một cách ngây thơ. Tôi có thể làm gì để tăng tốc bản vẽ CGPath của mình?


2
+1 nghe có vẻ ngược đời.
Người chơi Grady

1
Tôi thường thấy CoreGraphics rất chậm khi vẽ các đường thẳng, đường dẫn, v.v. Tôi không biết tại sao nhưng tôi chủ yếu phải chuyển xuống OpenGL hoặc sử dụng Cocos2D để vẽ hiệu quả. Chắc chắn tôi hiểu rằng nó nhanh hơn, nhưng tôi không thực sự hiểu tại sao CG lại chậm hơn rất nhiều, vì nó nên được sử dụng chính OpenGL.
Accatyyc

4
UIBezierPathlà một lớp bao bọc xung quanh CGPathRef. Điều gì sẽ xảy ra nếu bạn chạy cả hai, giả sử, mười triệu lần, sau đó lấy giá trị trung bình, nhưng không sử dụng Công cụ mà không sử dụng hai NSDateđối tượng trước và sau các hoạt động.

1
@WTP, kết quả nhất quán trên thiết bị và trong trình mô phỏng và không thay đổi dù -drawRect:được gọi vài chục lần hay vài trăm lần. Tôi đã thử nó với 80000 đoạn đường cong trên trình mô phỏng (quá nhiều so với thiết bị). Kết quả luôn giống nhau: CGPath mất khoảng gấp đôi UIBezierPath, mặc dù cả hai đều sử dụng CGContextStrokePath () để vẽ. Có vẻ như rõ ràng rằng đường dẫn mà UIBezierPath tạo bằng cách nào đó hiệu quả hơn đường dẫn mà tôi tạo bằng CGPathAddCurveToPoint (). Tôi muốn biết cách tạo các đường dẫn hiệu quả như UIBezierPath.
Caleb

Câu trả lời:


154

Bạn đã đúng trong đó UIBezierPathchỉ đơn giản là một trình bao bọc mục tiêu-c cho Đồ họa lõi, và do đó sẽ hoạt động tương đối. Sự khác biệt (và lý do cho đồng bằng hiệu suất của bạn) là CGContexttrạng thái của bạn khi vẽ CGPathtrực tiếp của bạn hoàn toàn khác với thiết lập đó bởi UIBezierPath. Nếu bạn nhìn vào UIBezierPath, nó có cài đặt cho:

  • lineWidth,
  • lineJoinStyle,
  • lineCapStyle,
  • miterLimit
  • flatness

Khi kiểm tra cuộc gọi (gỡ bỏ) tới [path stroke], bạn sẽ lưu ý rằng nó cấu hình ngữ cảnh đồ họa hiện tại dựa trên các giá trị trước đó trước khi thực hiện CGContextStrokePathcuộc gọi. Nếu bạn làm điều tương tự trước khi vẽ CGPath của mình, nó sẽ hoạt động tương tự:

- (void)drawRect:(CGRect)rect
{
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    // Create the two paths, cgpath and uipath.
    CGMutablePathRef cgpath = CGPathCreateMutable();
    CGPathMoveToPoint(cgpath, NULL, 0, 100);

    UIBezierPath *uipath = [[UIBezierPath alloc] init];
    [uipath moveToPoint:CGPointMake(0, 200)];

    // Add 200 curve segments to each path.
    int iterations = 80000;
    CGFloat cgBaseline = 100;
    CGFloat uiBaseline = 200;
    CGFloat xincrement = self.bounds.size.width / iterations;
    for (CGFloat x1 = 0, x2 = xincrement;
         x2 < self.bounds.size.width;
         x1 = x2, x2 += xincrement)
    {
        CGPathAddCurveToPoint(cgpath, NULL, x1, cgBaseline-50, x2, cgBaseline+50, x2, cgBaseline);
        [uipath addCurveToPoint:CGPointMake(x2, uiBaseline)
                  controlPoint1:CGPointMake(x1, uiBaseline-50)
                  controlPoint2:CGPointMake(x2, uiBaseline+50)];
    }
    [[UIColor blackColor] setStroke];
    CGContextAddPath(ctx, cgpath);

    // Stroke each path
    CGContextSaveGState(ctx); {
        // configure context the same as uipath
        CGContextSetLineWidth(ctx, uipath.lineWidth);
        CGContextSetLineJoin(ctx, uipath.lineJoinStyle);
        CGContextSetLineCap(ctx, uipath.lineCapStyle);
        CGContextSetMiterLimit(ctx, uipath.miterLimit);
        CGContextSetFlatness(ctx, uipath.flatness);
        [self strokeContext:ctx];
        CGContextRestoreGState(ctx);
    }
    [self strokeUIBezierPath:uipath];

    [uipath release];
    CGPathRelease(cgpath);
}

- (void)strokeContext:(CGContextRef)context
{
    CGContextStrokePath(context);
}

- (void)strokeUIBezierPath:(UIBezierPath*)path
{
    [path stroke];
}

Ảnh chụp nhanh từ Dụng cụ: Ảnh chụp nhanh các thiết bị cho thấy hiệu suất ngang nhau


6
Cảm ơn bạn đã dành thời gian xem xét vấn đề này và viết một lời giải thích rõ ràng như vậy. Đây thực sự là một câu trả lời tuyệt vời.
Caleb

14
+1 cho biểu tượng atari bruce lee ... và có thể cho câu trả lời.
Grady Player

5
Vì vậy, ... sự khác biệt hiệu suất 2x là một hoặc nhiều cài đặt cgcontext - ví dụ: có lẽ một cái gì đó như: "lineWidth của 2.0 hoạt động kém hơn lineWidth của 1.0" ...?
Adam

2
FWIW Tôi luôn thấy độ rộng dòng 1,0 là nhanh nhất - giả định của tôi là do hiện tượng mitering trở thành một vấn đề đối với độ rộng> 1px.
Đánh dấu Aufflick vào
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.