Mengapa UIBezierPath lebih cepat dari jalur Core Graphics?

90

Saya bermain-main dengan jalur menggambar, dan saya perhatikan bahwa setidaknya dalam beberapa kasus, UIBezierPath mengungguli apa yang saya pikir akan setara dengan Grafik Inti. The -drawRect:metode di bawah ini menciptakan dua jalur: satu UIBezierPath, dan satu CGPath. Jalurnya identik kecuali lokasinya, tetapi membelai CGPath membutuhkan waktu kira-kira dua kali lebih lama daripada membelai 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];
}

Kedua jalur menggunakan CGContextStrokePath (), jadi saya membuat metode terpisah untuk menggores setiap jalur sehingga saya dapat melihat waktu yang digunakan oleh setiap jalur di Instrumen. Di bawah ini adalah hasil tipikal (pohon panggilan terbalik); Anda dapat melihat bahwa -strokeContext:membutuhkan 9,5 detik, sedangkan -strokeUIBezierPath:hanya membutuhkan 5 detik .:

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:]

Sepertinya UIBezierPath entah bagaimana mengoptimalkan jalur yang dibuatnya, atau saya membuat CGPath dengan cara yang naif. Apa yang dapat saya lakukan untuk mempercepat gambar CGPath saya?

Caleb
sumber
2
+1 yang terdengar kontra-intutif.
Grady Player
1
Saya biasanya menemukan CoreGraphics menjadi sangat lambat saat menggambar garis, jalur, dan sebagainya. Saya tidak tahu mengapa tetapi saya kebanyakan harus turun ke OpenGL atau menggunakan Cocos2D untuk menggambar yang efisien. Tentu saya mengerti bahwa ini lebih cepat, tetapi saya tidak begitu mengerti mengapa CG jauh lebih lambat, mengingat seharusnya menggunakan OpenGL itu sendiri.
Accatyyc
4
UIBezierPathadalah pembungkus CGPathRef. Bagaimana jika Anda menjalankan keduanya, katakanlah, sepuluh juta kali, lalu ambil rata-ratanya, tetapi jangan gunakan Instrumen tetapi dua NSDateobjek sebelum dan sesudah operasi.
1
@WTP, hasil konsisten di perangkat dan di simulator dan tidak berubah apakah -drawRect:dipanggil beberapa lusin kali atau beberapa ratus. Saya sudah mencobanya dengan sebanyak 80.000 segmen kurva pada simulator (terlalu banyak untuk perangkat). Hasilnya selalu hampir sama: CGPath membutuhkan waktu dua kali lebih lama dari UIBezierPath, meskipun keduanya menggunakan CGContextStrokePath () untuk menggambar. Tampaknya jelas bahwa jalur yang dibangun UIBezierPath entah bagaimana lebih efisien daripada yang saya buat dengan CGPathAddCurveToPoint (). Saya ingin tahu bagaimana membangun jalur yang efisien seperti yang dilakukan UIBezierPath.
Caleb

Jawaban:

154

Anda benar karena UIBezierPathitu hanyalah pembungkus objektif-c untuk Core Graphics, dan karena itu akan bekerja dengan cara yang sama. Perbedaan (dan alasan delta kinerja Anda) adalah CGContextstatus Anda saat menggambar CGPathsecara langsung sangat berbeda dengan penyiapan tersebut UIBezierPath. Jika Anda lihat UIBezierPath, ini memiliki pengaturan untuk:

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

Saat memeriksa panggilan (pembongkaran) ke [path stroke], Anda akan mencatat bahwa itu mengkonfigurasi konteks grafik saat ini berdasarkan nilai-nilai sebelumnya sebelum melakukan CGContextStrokePathpanggilan. Jika Anda melakukan hal yang sama sebelum menggambar CGPath Anda, ini akan melakukan hal yang sama:

- (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];
}

Jepretan dari Instrumen: Cuplikan instrumen menunjukkan kinerja yang setara

Stuart Carnie
sumber
6
Terima kasih telah meluangkan waktu untuk melihat ini dan menulis penjelasan yang begitu jelas. Ini benar-benar jawaban yang bagus.
Caleb
14
1 untuk ikon atari bruce lee ... dan mungkin untuk jawabannya.
Grady Player
5
Jadi ... perbedaan kinerja 2x adalah satu atau lebih dari pengaturan cgcontext - misalnya mungkin sesuatu seperti: "lineWidth 2.0 berkinerja lebih buruk daripada lineWidth 1.0" ...?
Adam
2
FWIW Saya selalu menemukan lebar garis 1.0 sebagai yang tercepat - asumsi saya adalah karena mitering menjadi masalah untuk lebar> 1px.
Mark Aufflick