2012-01-03 15 views
13

Sto lavorando su un'app iOS che visualizza i dati come un grafico a linee. Il grafico viene disegnato come CGPath in uno schermo intero personalizzato UIView e contiene al massimo 320 punti di dati. I dati vengono aggiornati di frequente e il grafico deve essere ridisposto di conseguenza - una frequenza di aggiornamento di 10/sec sarebbe piacevole.Prestazioni quando si disegnano spesso CGPaths

Finora così facile. Sembra, tuttavia, che il mio approccio richieda molto tempo per la CPU. L'aggiornamento del grafico con 320 segmenti a 10 volte al secondo comporta un carico della CPU del 45% per il processo su un iPhone 4S.

Forse sottovaluto il lavoro grafico sotto il cofano, ma per me il carico della CPU sembra molto per quel compito.

Di seguito è la mia funzione drawRect() che viene chiamata ogni volta che un nuovo set di dati è pronto. N contiene il numero di punti e points è un vettore CGPoint* con le coordinate da disegnare.

- (void)drawRect:(CGRect)rect { 

    CGContextRef context = UIGraphicsGetCurrentContext(); 

    // set attributes 
    CGContextSetStrokeColorWithColor(context, [UIColor lightGrayColor].CGColor); 
    CGContextSetLineWidth(context, 1.f); 

    // create path 
    CGMutablePathRef path = CGPathCreateMutable(); 
    CGPathAddLines(path, NULL, points, N+1); 

    // stroke path 
    CGContextAddPath(context, path); 
    CGContextStrokePath(context); 

    // clean up 
    CGPathRelease(path); 
} 

Ho provato rendere il percorso di un offline CGContext prima prima di aggiungerlo al layer corrente come suggerito here, ma senza alcun risultato positivo. Ho anche lavorato con un approccio al CALayer direttamente, ma anche questo non ha fatto alcuna differenza.

Qualche suggerimento su come migliorare le prestazioni per questa attività? Oppure il rendering rende semplicemente più lavoro per la CPU che realizzo? OpenGL avrebbe senso/differenza?

Grazie/Andi

Aggiornamento: Ho anche provato ad utilizzare UIBezierPath invece di CGPath. Questo post here fornisce una bella spiegazione del perché non è stato di aiuto. Tweaking CGContextSetMiterLimit et al. anche non ha portato grande sollievo.

Aggiornamento n. 2: Alla fine sono passato a OpenGL. È stata una curva di apprendimento ripida e frustrante, ma l'aumento delle prestazioni è semplicemente incredibile. Tuttavia, gli algoritmi di anti-aliasing di CoreGraphics fanno un lavoro migliore di quello che si può ottenere con il 4x multicampionamento in OpenGL.

+0

Il tuo colore è una costante. Spostalo se drawRect e continua a riutilizzarlo anziché richiederne uno nuovo ogni volta. Idem per il percorso. Poiché StrokePath() "svuota il percorso", è possibile riutilizzare lo stesso percorso oggetto più e più volte. Cosa cambia? – verec

+0

La documentazione afferma che il percorso è stato svuotato, ma almeno nel mio codice non lo è. Continua a crescere. Per quanto riguarda il colore, hai un punto. Era brutto, ma non la soluzione. Grazie. –

+0

Intendi UIBezierPath, giusto? NSBezierPath esiste solo su Mac. –

risposta

7

Questo post here fornisce una bella spiegazione del perché non è stato di aiuto.

Spiega anche perché il tuo metodo drawRect: è lento.

Stai creando un oggetto CGPath ogni volta che disegni. Non hai bisogno di farlo; è sufficiente creare un nuovo oggetto CGPath ogni volta che si modifica il set di punti. Spostare la creazione di CGPath in un nuovo metodo che si chiama solo quando cambia la serie di punti e mantenere l'oggetto CGPath tra le chiamate a quel metodo. Avere drawRect: semplicemente recuperarlo.

Hai già trovato che il rendering è la cosa più costosa che stai facendo, il che è positivo: non puoi rendere il rendering più veloce, vero? Effettivamente, dovrebbe fare idealmente il , ma il tuo obiettivo dovrebbe essere quello di far passare il tempo trascorso il rendering il più vicino possibile al 100%, il che significa spostare tutto il resto, il più possibile, dal codice di disegno.

+0

Peter, grazie per questa risposta! Capisco che il rendering è ciò che chiedo alla CPU di fare e che cosa sta facendo. Sono stato sorpreso solo di occuparlo molto di questo. Penso che darò una possibilità a OpenGL e vedere se può aiutarmi a scaricare un po 'di lavoro sulla GPU. –

+0

Ho lo stesso problema .. e devo creare un nuovo CGPath ogni volta che arriva un nuovo punto ... e questo è 128 volte al secondo –

0

Hai provato a utilizzare UIBezierPath invece? UIBezierPath utilizza CGPath sotto il cofano, ma sarebbe interessante vedere se le prestazioni differiscono per qualche motivo sottile. Da Apple's Documentation:

Per la creazione di percorsi in iOS, si consiglia di utilizzare UIBezierPath invece di funzioni CGPath a meno che non avete bisogno di alcune delle capacità che solo core grafico fornisce, come l'aggiunta di ellissi ai percorsi. Per maggiori informazioni sulla creazione e il rendering percorsi in UIKit, vedere “disegnare forme Utilizzando Bezier percorsi.”

mi piacerebbe sarebbe anche provare a impostare diverse proprietà sul CGContext, in particolare linea diversa unisco stili utilizzando CGContextSetLineJoin(), a vedi se questo fa alcuna differenza.

Hai profilato il tuo codice utilizzando lo strumento Time Profiler in Strumenti? Questo è probabilmente il modo migliore per scoprire dove si verifica effettivamente il collo di bottiglia delle prestazioni, anche quando il collo di bottiglia si trova da qualche parte all'interno dei framework di sistema.

+0

I ho aggiornato il mio post con un commento su NSBezierPath e modificato i parametri della linea.Il Time Profiler mostra che la maggior parte delle calorie (83%) vengono masterizzate all'interno di aa_render che fa parte di CG. –

+0

Che dire della creazione di profili utilizzando Strumenti? Qualsiasi informazione? –

+0

(Siamo spiacenti, Ho colpito il ritorno dopo la prima frase;)) –

0

Non sono esperto in questo, ma quello che dubito prima è che potrebbe essere necessario del tempo per aggiornare i "punti" piuttosto che il rendering stesso. In questo caso, potresti semplicemente smettere di aggiornare i punti e ripetere il rendering dello stesso percorso, e vedere se ci vuole quasi lo stesso tempo della CPU. In caso contrario, è possibile migliorare le prestazioni concentrandosi sull'algoritmo di aggiornamento.

Se è veramente il problema del rendering, penso che OpenGL dovrebbe sicuramente migliorare le prestazioni perché in teoria renderà tutte le 320 linee allo stesso tempo.

+0

L'aggiornamento dei dati non è il problema (ho controllato in Time Profiler). Ho anche evitato di ripetere lo stesso percorso più e più volte, poiché nella vita reale anche i dati stanno cambiando. E non volevo semplificare la vita di CG semplicemente aggiornando la stessa bitmap;) A questo punto, sto combattendo per evitare OpenGL, ma se devo ... –

4

A seconda di come si traccia il percorso, è possibile che disegnare 300 percorsi separati sia più veloce di un percorso con 300 punti. La ragione di ciò è che spesso l'algoritmo del disegno cercherà di capire linee sovrapposte e come rendere le intersezioni "perfette" - quando forse si desidera solo che le linee si sovrappongano opache l'una con l'altra. Molti algoritmi di sovrapposizione e intersezione sono N ** 2 o così di complessità, quindi la velocità del disegno si ridimensiona con il quadrato del numero di punti in un percorso.

Dipende dalle opzioni esatte (alcune di default) che si utilizzano. Devi provarlo.

0

L'ho fatto usando Metal nel mio progetto. C'è ovviamente ulteriore complessità nell'uso di Metal, quindi, a seconda dei requisiti di prestazione, può o meno essere adatto.

Con Metal, tutto il lavoro viene eseguito nella GPU e l'utilizzo della CPU sarà quasi zero. Sulla maggior parte dei dispositivi iOS puoi rendere diverse centinaia o migliaia di curve senza problemi, a 60 FPS.

Ecco alcuni esempi di codice che ho scritto, potrebbe fornire un buon punto di partenza per qualcuno: https://github.com/eldade/ios_metal_bezier_renderer

Problemi correlati