Mi piace pensare alle prestazioni in termini di " limiti ". È un modo pratico per concettualizzare un sistema abbastanza complicato e interconnesso. Quando hai un problema di prestazioni, fai la domanda: "Quali limiti sto colpendo?" (Oppure: "Sono associato CPU / GPU?")
Puoi suddividerlo in più livelli. Al livello più alto hai la CPU e la GPU. Potrebbe essere associato alla CPU (GPU inattiva in attesa di CPU) o associato alla GPU (CPU in attesa su GPU). Ecco un buon post sul blog sull'argomento.
Puoi scomporlo ulteriormente. Sul lato CPU , potresti utilizzare tutti i tuoi cicli sui dati già presenti nella cache della CPU. Oppure potresti avere una memoria limitata , lasciando la CPU inattiva in attesa che i dati arrivino dalla memoria principale ( quindi ottimizza il layout dei dati ). Potresti analizzarlo ulteriormente.
(Mentre sto facendo una vasta panoramica delle prestazioni relative a XNA, farò notare che un'allocazione di un tipo di riferimento ( class
non struct
), sebbene normalmente economica, potrebbe innescare il garbage collector, che brucerà molti cicli, specialmente su Xbox 360 . Vedi qui per i dettagli).
Per quanto riguarda la GPU , inizierò indicandoti questo eccellente post sul blog che ha molti dettagli. Se desideri un livello di dettaglio folle sulla pipeline, leggi questa serie di post sul blog . ( Eccone uno più semplice ).
Per dirla semplicemente, alcuni dei più grandi sono: " limite di riempimento " (quanti pixel puoi scrivere sul backbuffer - spesso quanto sovraccarico puoi avere), " limite di shader " (quanto complicati possono essere i tuoi shader e quanti dati è possibile inviare), " limite texture-fetch / texture-larghezza di banda " (quanti dati texture è possibile accedere).
E, ora, arriviamo a quello grande - che è quello che stai davvero chiedendo - dove la CPU e la GPU devono interagire (tramite le varie API e driver). Liberamente c'è il " limite batch " e la " larghezza di banda ". (Nota che la prima parte della serie che ho menzionato in precedenza approfondisce i dettagli.)
Ma, fondamentalmente, un batch ( come già sapete ) accade ogni volta che chiamate una delle GraphicsDevice.Draw*
funzioni (o parte di XNA, come SpriteBatch
, fa questo per voi). Come avrete sicuramente letto, ne ottenete alcune migliaia * di questi per frame. Questo è un limite della CPU, quindi compete con l'altro utilizzo della CPU. Fondamentalmente è il driver a impacchettare tutto ciò che gli hai detto di disegnare e inviarlo alla GPU.
E poi c'è la larghezza di banda della GPU. Ecco quanti dati grezzi puoi trasferire lì. Ciò include tutte le informazioni sullo stato che vanno con i batch - tutto dall'impostazione dello stato di rendering e delle costanti / parametri dello shader (che include cose come matrici world / view / project), ai vertici quando si usano le DrawUser*
funzioni. Esso comprende anche eventuali chiamate verso SetData
e GetData
in trame, tamponi, ecc vertex
A questo punto dovrei dire che tutto ciò su cui puoi fare affidamento SetData
(trame, buffer dei vertici e degli indici, ecc.), Così come Effect
s - rimane nella memoria della GPU. Non viene costantemente inviato nuovamente alla GPU. Un comando draw che fa riferimento a quei dati viene semplicemente inviato con un puntatore a tali dati.
(Inoltre: puoi solo inviare comandi di disegno dal thread principale, ma puoi farlo SetData
su qualsiasi thread.)
XNA complica le cose un po 'con i suoi rendere le classi di stato ( BlendState
, DepthStencilState
, ecc). Questi dati di stato viene inviato per chiamata draw (in ogni lotto). Non sono sicuro al 100%, ma ho l'impressione che sia inviato pigramente (invia solo lo stato che cambia). In entrambi i casi, i cambiamenti di stato sono economici fino al punto di libero, rispetto al costo di un batch.
Infine, l'ultima cosa da menzionare è la pipeline GPU interna . Non vuoi forzarlo a scaricare scrivendo sui dati che deve ancora leggere o leggendo i dati che deve ancora scrivere. Un flush della pipeline significa che attende che le operazioni finiscano, in modo che tutto sia in uno stato coerente quando si accede ai dati.
I due casi GetData
particolari a RenderTarget2D
cui prestare attenzione sono: fare appello a qualcosa di dinamico, in particolare su un oggetto su cui la GPU potrebbe scrivere. Questo è estremamente negativo per le prestazioni - non farlo.
L'altro caso si rivolge SetData
ai buffer vertice / indice. Se devi farlo spesso, usa un DynamicVertexBuffer
(anche DynamicIndexBuffer
). Questi consentono alla GPU di sapere che cambieranno spesso e di fare un po 'di buffering interno per evitare lo svuotamento della pipeline.
(Si noti inoltre che i buffer dinamici sono più veloci dei DrawUser*
metodi, ma devono essere pre-allocati alla dimensione massima richiesta.)
... E questo è praticamente tutto ciò che so sulle prestazioni di XNA :)