2013-03-12 6 views
7

Sto lavorando a un'app per iOS che richiede il disegno di curve di Bézier in tempo reale in risposta all'input dell'utente. All'inizio, ho deciso di provare a utilizzare CoreGraphics, che ha una fantastica API di disegno vettoriale. Tuttavia, ho scoperto rapidamente che la performance era dolorosamente, atrocemente lenta, al punto in cui il framerate ha iniziato a cadere gravemente con una sola curva sul mio iPad retina. (Certo, si trattava di un test rapido con codice inefficiente. Ad esempio, la curva veniva ridisegnata su ogni frame, ma sicuramente i computer di oggi sono abbastanza veloci da gestire una semplice curva ogni 1/60 di secondo, giusto ?!)Vettori software dolorosamente lenti, in particolare CoreGraphics vs. OpenGL

Dopo questo esperimento, sono passato a OpenGL e alla libreria MonkVG e non potrei essere più felice. Ora posso rendere HUNDREDS delle curve simultaneamente senza alcuna caduta di framerate, con un impatto minimo sulla fedeltà (per il mio caso d'uso).

  1. E 'possibile che io abusato CoreGraphics in qualche modo (al punto in cui è stato diversi ordini di grandezza più lento rispetto alla soluzione OpenGL), o è la prestazione davvero così terribile? La mia impressione è che il problema si trova con CoreGraphics, basato sul numero di StackOverflow/forum domande e risposte riguardanti le prestazioni CG. (Ho visto diverse persone affermare che CG non è pensato per andare in un ciclo di esecuzione, e che dovrebbe essere usato solo per il rendering infrequente.) Perché è questo il caso, tecnicamente parlando?
  2. Se CoreGraphics è davvero così lento, come mai Safari funziona così bene? Avevo l'impressione che Safari non fosse accelerato dall'hardware, eppure deve mostrare centinaia (se non migliaia) di caratteri vettoriali contemporaneamente senza perdere alcun frame.
  3. Più in generale, in che modo le applicazioni con un vettore pesante (browser, Illustrator, ecc.) Rimangono così veloci senza l'accelerazione hardware? (A quanto mi risulta, molti browser e le suite grafica ora con un'opzione di accelerazione hardware, ma non è spesso attivati ​​per impostazione predefinita.)

UPDATE:

Ho scritto un test rapido app per misurare più accuratamente le prestazioni. Di seguito è riportato il codice per la sottoclasse CALayer personalizzata.

Con NUM_PATHS impostato su 5 e NUM_POINTS impostato su 15 (5 segmenti di curva per percorso), il codice viene eseguito a 20fps in modalità non-retina e 6fps in modalità retina sul mio iPad 3. Il profiler elenca CGContextDrawPath ad avere il 96% del tempo della CPU. Sì, ovviamente, posso ottimizzare limitando il mio redirect rect, ma se avessi davvero davvero bisogno di animazione vettoriale a schermo intero a 60fps?

OpenGL mangia questo test per la colazione. Com'è possibile che il disegno vettoriale sia così incredibilmente lento?

#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

È difficile dire se si è utilizzato in modo improprio la grafica principale senza visualizzare il codice o almeno una descrizione più dettagliata. Hai creato un nuovo CGPathRef su ciascun frame (esplicitamente o implicitamente) o ne hai creato uno in anticipo e riutilizzalo? Scommetto che avrebbe un impatto sulle prestazioni. – benzado

+0

Forse ho creato un nuovo CGPathRef per ogni fotogramma, ma dovrò ricontrollare. (Ma anche se lo facessi, non posso immaginare un miglioramento delle prestazioni di diversi ordini di grandezza, sai?) So che ho provato a limitare i miei ridisegni solo a ogni segmento appena aggiunto della spline, ma anche quello non mi è stato di grande aiuto. – Archagon

+1

Ho creato un'app che ha ridisegnato un percorso complesso diverse volte per ogni frame utilizzando Core Graphics. Le prestazioni erano buone, persino migliori del previsto. Il percorso consisteva di circa 100 elementi, larghezza della linea fino a 100 px, con molti tappi rotondi tra le parti non connesse. Sono rimasto impressionato dalle prestazioni quando disegno a schermo intero su un iPad 2 e 3 (con risoluzione retina). –

risposta

3

In primo luogo, vedere Why is UIBezierPath faster than Core Graphics path? e assicurarsi che si sta configurando il vostro percorso in modo ottimale. Per impostazione predefinita, CGContext aggiunge molte opzioni "carine" ai percorsi che possono aggiungere un sovraccarico. Se li spegni, probabilmente troverai miglioramenti di velocità drammatici.

Il prossimo problema che ho riscontrato con le curve grafiche di Bézier di Core Graphics è quando si hanno molti componenti in una curva singola (vedevo dei problemi quando ho esaminato circa 3000-5000 elementi). Ho trovato quantità molto sorprendenti di tempo trascorso in CGPathAdd.... Ridurre il numero di elementi nel tuo percorso può essere una grande vittoria. Dai miei discorsi con il team Core Graphics dello scorso anno, questo potrebbe essere stato un bug in Core Graphics e potrebbe essere stato risolto. Non ho re-testato.


EDIT: sto vedendo 18-20FPS in Retina su un iPad 3 apportando le seguenti modifiche:

spostare il CGContextStrokePath() fuori del ciclo. Non dovresti accarezzare ogni percorso. Dovresti accarezzare una volta alla fine. Questo richiede il mio test da ~ 8FPS a ~ 12FPS.

Spegnere l'anti-aliasing (che probabilmente è disattivato per impostazione predefinita nei test OpenGL):

CGContextSetShouldAntialias(ctx, false); 

Che mi viene a 18-20FPS (Retina) e fino a circa 40fps non Retina.

Non so cosa stai vedendo in OpenGL. Ricorda che la grafica core è progettata per rendere le cose belle; OpenGL è progettato per rendere le cose veloci. Core Graphics si basa su OpenGL; quindi mi aspetterei sempre che il codice OpenGL ben scritto sia più veloce.

+0

Ho aggiunto un esempio di codice sopra: 20 fps in modalità SD, 6 fps in modalità retina per solo 5 curve che animano ad un tentativo di 60 fps. L'utilizzo del metodo UIBezierPath ha solo aggiunto un paio di fotogrammi al secondo. – Archagon

4

Non si dovrebbe davvero confrontare il disegno di Core Craphics con OpenGL, si stanno confrontando funzionalità completamente diverse per scopi molto diversi.

In termini di qualità dell'immagine, Core Graphics e Quartz saranno molto superiori a OpenGL con meno sforzo. Il framework Core Graphics è progettato per un aspetto ottimale, linee e curve naturalmente antialias e uno smalto associato alle interfacce utente Apple. Ma questa qualità dell'immagine ha un prezzo: velocità di rendering.

OpenGL d'altra parte è progettato con velocità come priorità. Alte prestazioni, il disegno veloce è difficile da battere con OpenGL. Ma questa velocità ha un costo: è molto più difficile ottenere una grafica liscia e lucida con OpenGL. Esistono molte strategie diverse per fare qualcosa di "semplice" come antialiasing in OpenGL, cosa che è più facilmente gestita da Quartz/Core Graphics.

+0

Quello di cui sono principalmente sorpreso è semplicemente * quanto * il lento CoreGraphics è per la grafica vettoriale. Non sembra un compito particolarmente difficile, ma forse sto sottovalutando quanto sia difficile spingere quasi un milione di pixel al secondo. – Archagon

+0

No, ciò che stai sottovalutando è tutto il lavoro matematico che si svolge sotto il cofano per creare linee morbide e curve quando si calcolano le ombre per i pixel adiacenti. Con OpenGL, devi farlo da solo. – johnbakers

+0

Per estendere questa linea di argomenti, @Archagon: i punti di confronto non sono molto equivalenti. Safari non è lento come la tua app di test perché non sta cercando di rasterizzare le curve di Bézier a schermo intero a 60fps. Il tipo di rendering (e quindi di scorrimento) è un processo molto diverso, che implica lavori di rasterizzazione più piccoli e un sacco di memorizzazione nella cache. – rickster

1

tuo rallentamento è a causa di questa riga di codice:

[self setNeedsDisplay]; 

è necessario modificare questo per:

[self setNeedsDisplayInRect:changedRect]; 

E 'a voi per calcolare quale rettangolo è cambiato ogni fotogramma, ma se lo fai correttamente, probabilmente vedrai un miglioramento delle prestazioni di ordine di grandezza senza altri cambiamenti.

3

Disclaimer: Sono l'autore di MonkVG.

La ragione principale che MonkVG è molto più veloce quindi CoreGraphics in realtà non è così tanto che è implementato con OpenGL ES come supporto di rendering, ma perché "trucchi" di tessellating i contorni in poligoni prima è fatto alcun resa . La tassellatura del contorno è in realtà dolorosamente lenta e se si dovessero generare dinamicamente contorni vedresti un grande rallentamento. Il grande vantaggio di un supporto OpenGL (verso CoreGraphics che utilizza il rendering bitmap diretto) è che qualsiasi trasformazione come una traslazione, una rotazione o uno scaling non impone una ricapitolazione completa dei contorni - è essenzialmente per "libero".

Problemi correlati