2013-03-12 8 views
7

Ich arbeite an einer iOS-App, die es erfordert, Bézier-Kurven in Echtzeit als Antwort auf die Eingabe des Benutzers zu zeichnen. Zuerst habe ich mich entschieden, CoreGraphics zu verwenden, das eine fantastische Vektorgrafik-API hat. Ich stellte jedoch schnell fest, dass die Leistung schmerzhaft und quälend langsam war, bis zu dem Punkt, an dem die Framerate mit nur einer Kurve auf meinem Retina-iPad stark abfiel. (Zugegebenermaßen war dies ein schneller Test mit ineffizientem Code. Zum Beispiel wurde die Kurve in jedem Frame neu gezeichnet. Aber die heutigen Computer sind sicher schnell genug, um alle 1/60 Sekunden eine einfache Kurve zu zeichnen, oder ?!)Schmerzhaft langsame Softwarevektoren, insbesondere CoreGraphics vs. OpenGL

Nach diesem Experiment wechselte ich zu OpenGL und der MonkVG Bibliothek, und ich könnte nicht glücklicher sein. Ich kann jetzt HUNDERTE von Kurven gleichzeitig ohne jede Framerate-Drop, mit nur einer minimalen Auswirkung auf die Treue (für meinen Anwendungsfall).

  1. Ist es möglich, dass ich CoreGraphics irgendwie missbraucht habe (bis zu dem Punkt, wo es mehrere Größenordnungen langsamer war als die OpenGL-Lösung), oder ist die Leistung wirklich so schrecklich? Meine Vermutung ist, dass das Problem bei CoreGraphics liegt, basierend auf der Anzahl der StackOverflow/Forum-Fragen und Antworten bezüglich der CG-Leistung. (Ich habe mehrere Leute feststellen lassen, dass CG nicht dazu gedacht ist, in eine Run-Schleife zu gehen, und dass es nur für seltenes Rendering verwendet werden sollte.) Warum ist das technisch gesehen der Fall?
  2. Wenn CoreGraphics wirklich so langsam ist, wie in aller Welt funktioniert Safari so reibungslos? Ich hatte den Eindruck, dass Safari nicht hardwarebeschleunigt ist und dennoch Hunderte von Vektorzeichen gleichzeitig anzeigen muss, ohne dass Frames verloren gehen.
  3. Allgemeiner, wie bleiben Anwendungen mit starker Vektornutzung (Browser, Illustrator usw.) ohne Hardwarebeschleunigung so schnell? (Wie ich es verstehe, jetzt viele Browser und Grafiken Suiten sind mit einer Hardware-Beschleunigung Option, aber es ist oft nicht standardmäßig aktiviert.)

UPDATE:

ich geschrieben habe einen schnellen Test App, um die Leistung genauer zu messen. Unten ist der Code für meine benutzerdefinierte CALayer-Unterklasse.

Mit NUM_PATHS auf 5 und NUM_POINTS auf 15 (5 Kurvensegmente pro Pfad) eingestellt, läuft der Code mit 20fps im Nicht-Retina-Modus und 6fps im Retina-Modus auf meinem iPad 3. Der Profiler listet CGContextDrawPath mit 96% auf der CPU-Zeit. Ja - natürlich kann ich optimieren, indem ich mein Redraw-Rect beschränkt, aber was wäre, wenn ich wirklich, Full-Screen-Vektoranimation bei 60 fps brauche?

OpenGL isst diesen Test zum Frühstück. Wie ist es möglich, dass Vektorzeichnen so unglaublich langsam ist?

#import "CGTLayer.h" 

@implementation CGTLayer 

- (id) init 
{ 
    self = [super init]; 
    if (self) 
    { 
     self.backgroundColor = [[UIColor grayColor] CGColor]; 
     displayLink = [[CADisplayLink displayLinkWithTarget:self selector:@selector(updatePoints:)] retain]; 
     [displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; 
     initialized = false; 

     previousTime = 0; 
     frameTimer = 0; 
    } 
    return self; 
} 

- (void) updatePoints:(CADisplayLink*)displayLink 
{ 
    for (int i = 0; i < NUM_PATHS; i++) 
    { 
     for (int j = 0; j < NUM_POINTS; j++) 
     { 
      points[i][j] = CGPointMake(arc4random()%768, arc4random()%1024); 
     } 
    } 

    for (int i = 0; i < NUM_PATHS; i++) 
    { 
     if (initialized) 
     { 
      CGPathRelease(paths[i]); 
     } 

     paths[i] = CGPathCreateMutable(); 

     CGPathMoveToPoint(paths[i], &CGAffineTransformIdentity, points[i][0].x, points[i][0].y); 

     for (int j = 0; j < NUM_POINTS; j += 3) 
     { 
      CGPathAddCurveToPoint(paths[i], &CGAffineTransformIdentity, points[i][j].x, points[i][j].y, points[i][j+1].x, points[i][j+1].y, points[i][j+2].x, points[i][j+2].y); 
     } 
    } 

    [self setNeedsDisplay]; 

    initialized = YES; 

    double time = CACurrentMediaTime(); 

    if (frameTimer % 30 == 0) 
    { 
     NSLog(@"FPS: %f\n", 1.0f/(time-previousTime)); 
    } 

    previousTime = time; 
    frameTimer += 1; 
} 

- (void)drawInContext:(CGContextRef)ctx 
{ 
// self.contentsScale = [[UIScreen mainScreen] scale]; 

    if (initialized) 
    { 
     CGContextSetLineWidth(ctx, 10); 

     for (int i = 0; i < NUM_PATHS; i++) 
     { 
      UIColor* randomColor = [UIColor colorWithRed:(arc4random()%RAND_MAX/((float)RAND_MAX)) green:(arc4random()%RAND_MAX/((float)RAND_MAX)) blue:(arc4random()%RAND_MAX/((float)RAND_MAX)) alpha:1]; 
      CGContextSetStrokeColorWithColor(ctx, randomColor.CGColor); 

      CGContextAddPath(ctx, paths[i]); 
      CGContextStrokePath(ctx); 
     } 
    } 
} 

@end 
+2

Es ist schwer zu sagen, ob Sie Core Graphics missbraucht haben, ohne Ihren Code oder zumindest eine detailliertere Beschreibung zu sehen. Haben Sie für jeden Frame (explizit oder implizit) einen neuen CGPathRef erstellt oder haben Sie einen im Voraus erstellt und erneut verwendet? Ich wette, das hätte Auswirkungen auf die Performance. – benzado

+0

Ich habe vielleicht jeden Frame ein neues CGPathRef erstellt, aber ich muss es nochmal überprüfen. (Aber selbst wenn ich es täte, kann ich mir nicht vorstellen, dass sich die Leistung um mehrere Größenordnungen verbessert, weißt du?) Ich weiß, dass ich versucht habe, meine Neuzeichnungen auf jedes neu hinzugefügte Segment des Splines zu beschränken, aber selbst das half nicht sehr. – Archagon

+1

Ich habe eine App erstellt, die einen komplexen Pfad mehrere Male in jedem Frame mithilfe von Core Graphics neu erstellt. Die Performance war gut, sogar besser als erwartet. Der Pfad bestand aus ungefähr 100 Elementen, Linienbreite bis zu 100 px, mit vielen runden Kappen zwischen nicht verbundenen Teilen. Ich war beeindruckt von der Leistung beim Vollbild auf einem iPad 2 und 3 (mit Retina-Auflösung). –

Antwort

3

Zuerst sehen Why is UIBezierPath faster than Core Graphics path? und stellen Sie sicher, dass Sie Ihren Weg optimal sind konfigurieren. Standardmäßig fügt CGContext eine Menge "hübscher" Optionen zu Pfaden hinzu, die viel Overhead hinzufügen können. Wenn Sie diese ausschalten, werden Sie wahrscheinlich dramatische Geschwindigkeitsverbesserungen feststellen.

Das nächste Problem, das ich mit Core Graphics Bézier Kurven gefunden habe, ist, wenn Sie viele Komponenten in einer einzigen Kurve haben (Ich sah Probleme, als ich über 3000-5000 Elemente ging). Ich fand sehr überraschende Mengen an Zeit in CGPathAdd... verbracht. Reduzieren Sie die Anzahl der Elemente in Ihrem Pfad kann ein großer Gewinn sein. Aus meinen Gesprächen mit dem Core Graphics Team letztes Jahr, könnte dies ein Fehler in Core Graphics gewesen sein und wurde möglicherweise behoben. Ich habe es nicht nochmal getestet.


EDIT: Ich bin 18-20FPS in Retina zu sehen auf einem iPad 3 durch folgende Änderungen vornehmen:

die CGContextStrokePath() außerhalb der Schleife bewegen. Sie sollten nicht jeden Pfad streichen. Du solltest einmal am Ende streicheln. Dies dauert meinen Test von ~ 8FPS bis ~ 12FPS.

Anti-Aliasing deaktivieren (standardmäßig in Ihrem OpenGL Test ausgeschaltet ist wahrscheinlich):

CGContextSetShouldAntialias(ctx, false); 

Das hat mich zu 18-20FPS (Retina) und bis zu etwa 40fps Nicht-Retina bekommt.

Ich weiß nicht, was Sie in OpenGL sehen. Denken Sie daran, dass Core Graphics entworfen wurde, um Dinge schön zu machen; OpenGL wurde entwickelt, um Dinge schnell zu machen. Core Graphics setzt auf OpenGL; also würde ich immer erwarten, dass gut geschriebener OpenGL-Code schneller ist.

+0

Ich habe oben ein Codebeispiel hinzugefügt: 20 fps im SD-Modus, 6 fps im Retina-Modus für nur 5 Kurven, die bei einem Versuch von 60 fps animiert werden. Bei Verwendung der UIBezierPath-Methode wurden nur einige Frames pro Sekunde hinzugefügt. – Archagon

4

Sie sollten Core Cipherics Zeichnung nicht wirklich mit OpenGL vergleichen, Sie vergleichen völlig verschiedene Funktionen für sehr unterschiedliche Zwecke.

In Bezug auf die Bildqualität werden Core Graphics und Quartz mit geringerem Aufwand weit besser als OpenGL sein. Das Core Graphics-Framework ist für ein optimales Erscheinungsbild, natürlich antialiasierte Linien und Kurven und eine mit Apple-UIs verbundene Politur konzipiert. Aber diese Bildqualität hat ihren Preis: Rendering-Geschwindigkeit.

OpenGL auf der anderen Seite ist mit Geschwindigkeit als Priorität ausgelegt. Hohe Leistung, schnelles Zeichnen ist mit OpenGL schwer zu übertreffen. Aber diese Geschwindigkeit hat ihren Preis: Es ist viel schwieriger, glatte und glatte Grafiken mit OpenGL zu bekommen. Es gibt viele verschiedene Strategien, um etwas so "einfach" wie Antialiasing in OpenGL zu machen, etwas, das von Quartz/Core Graphics leichter gehandhabt werden kann.

+0

Worüber ich hauptsächlich überrascht bin, ist * wie langsam CoreGraphics für Vektorgrafiken ist. Es scheint keine besonders schwierige Aufgabe zu sein, aber vielleicht unterschätze ich, wie schwer es ist, fast eine Million Pixel pro Sekunde zu pushen. – Archagon

+0

Nein, was Sie unterschätzt haben, ist all die mathematische Arbeit, die unter der Haube ausgeführt wird, um weiche Linien und Kurven zu erzeugen, wenn Schatten für benachbarte Pixel berechnet werden. Mit OpenGL müssen Sie das selbst machen. – johnbakers

+0

Um diese Argumentationslinie zu erweitern, @Archagon: Ihre Vergleichspunkte sind nicht sehr gleichwertig. Safari ist nicht so langsam wie Ihre Test-App, weil es nicht versucht, Bézier-Kurven im Vollbildmodus mit 60 fps zu rastern. Das Rendern (und dann das Scrollen) ist ein ganz anderer Prozess, bei dem kleinere Rasterisierungsjobs und viel Caching erforderlich sind. – rickster

1

Ihre Verlangsamung ist wegen dieser Codezeile:

[self setNeedsDisplay]; 

Sie müssen dies ändern:

[self setNeedsDisplayInRect:changedRect]; 

Es ist an Ihnen zu berechnen, welche Rechteck jeden Rahmen geändert hat, aber wenn Wenn Sie dies richtig machen, werden Sie wahrscheinlich eine Leistungsverbesserung von mehr als einer Größenordnung ohne weitere Änderungen sehen.

3

Haftungsausschluss: Ich bin der Autor von MonkVG.

Der wichtigste Grund dafür, dass MonkVG so viel schneller ist dann so eigentlich Core Graphics ist nicht viel, dass es mit OpenGL ES als Träger macht implementiert ist, sondern weil es „betrügt“ durch die Konturen in Polygone tessellating vor jedes Rendering erfolgt . Die Kontur-Tessellation ist tatsächlich schmerzhaft langsam, und wenn Sie dynamisch Konturen erzeugen würden, würden Sie eine große Verlangsamung sehen. Der große Vorteil eines OpenGL-Backings (siehe CoreGraphics mit direktem Bitmap-Rendering) ist, dass jede Transformation wie eine Translation, Rotation oder Skalierung keine vollständige Re-Tessellation der Konturen erzwingt - es ist im Wesentlichen für "frei".

Verwandte Themen