Graphics.DrawImage è troppo lento per immagini più grandi?

Attualmente sto lavorando a un gioco e desidero avere un menu principale con l’immagine di sfondo.

Tuttavia, trovo il metodo Graphics.DrawImage() molto lento. Ho fatto delle misurazioni. Supponiamo che MenuBackground sia la mia immagine risorsa con risoluzione 800 x 1200 pixel. Lo disegnerò su un altro 800 x 1200 bitmap (prima renderò tutto ad un buffer bitmap, poi lo ridimensionerò e infine lo disegnerò sullo schermo – è così che mi occupo della possibilità di risoluzioni di più giocatori, ma non dovrebbe influenzare in alcun modo, vedi il prossimo paragrafo).

Quindi ho misurato il seguente codice:

 Stopwatch SW = new Stopwatch(); SW.Start(); // First let's render background image into original-sized bitmap: OriginalRenderGraphics.DrawImage(Properties.Resources.MenuBackground, new Rectangle(0, 0, Globals.OriginalScreenWidth, Globals.OriginalScreenHeight)); SW.Stop(); System.Windows.Forms.MessageBox.Show(SW.ElapsedMilliseconds + " milliseconds"); 

Il risultato è tranquillo e mi sorprende: il Stopwatch misura qualcosa tra 40 - 50 milliseconds . E poiché l’immagine di sfondo non è l’unica cosa da disegnare, l’intero menu richiede circa 100 ms per essere visualizzato, il che implica un ritardo osservabile.

Ho provato a disegnarlo sull’object Graphics dato dall’evento Paint, ma il risultato era 30 - 40 milliseconds – non molto cambiato.

Quindi, significa che Graphics.DrawImage() è inutilizzabile per disegnare immagini più grandi? Se sì, cosa dovrei fare per migliorare le prestazioni del mio gioco?

Sì, è troppo lento.

Mi sono imbattuto in questo problema diversi anni fa mentre sviluppavo Paint.NET (sin dall’inizio, in realtà, ed era piuttosto frustrante!). Le prestazioni del rendering erano abissali, in quanto era sempre proporzionale alla dimensione della bitmap e non alle dimensioni dell’area che era stato detto di ridisegnare. Cioè, framerate è andato giù quando le dimensioni della bitmap sono aumentate, e il framerate non è mai salito quando la dimensione dell’area non valida / ridisegna è scesa quando si implementava OnPaint () e si chiamava Graphics.DrawImage (). Una piccola bitmap, ad esempio 800×600, funzionava sempre bene, ma le immagini più grandi (ad es. 2400×1800) erano molto lente. (Puoi comunque presumere, per il paragrafo precedente, che non fosse in corso alcuna operazione aggiuntiva, come ad esempio il ridimensionamento con un costoso filtro Bicubic, che avrebbe influito negativamente sulle prestazioni.)

È ansible forzare WinForms a utilizzare GDI invece di GDI + ed evitare persino la creazione di un object Graphics dietro le quinte, a quel punto è ansible sovrapporre un altro toolkit di rendering (es. Direct2D). Tuttavia, non è semplice. Lo faccio in Paint.NET, e puoi vedere cosa è richiesto usando qualcosa come Reflector nella class chiamata GdiPaintControl nella DLL di SystemLayer, ma per quello che stai facendo lo considererei l’ultima risorsa.

Tuttavia, la dimensione bitmap che stai utilizzando (800×1200) dovrebbe funzionare ancora abbastanza bene in GDI + senza dover ricorrere all’interop avanzato, a meno che tu non stia puntando a qualcosa di simile a un Pentium II a 300 MHz. Ecco alcuni suggerimenti che potrebbero aiutare:

  • Se si sta utilizzando una bitmap opaca (senza alfa / trasparenza) nella chiamata a Graphics.DrawImage() , e soprattutto se si tratta di una bitmap a 32 bit con un canale alfa (ma si sa che è opaco o non interessa) , quindi impostare Graphics.CompositingMode su CompositingMode.SourceCopy prima di chiamare DrawImage() (assicurarsi di DrawImage() sul valore originale dopo, altrimenti le primitive di disegno normali appariranno molto brutte). Questo salta un sacco di matematica per pixel aggiuntiva.
  • Assicurarsi che Graphics.InterpolationMode non sia impostato su qualcosa come InterpolationMode.HighQualityBicubic . L’uso di NearestNeighbor sarà il più veloce, anche se potrebbe esserci un allungamento che potrebbe non sembrare molto buono (a meno che non sia allungato esattamente di 2x, 3x, 4x, ecc.) Bilinear è di solito un buon compromesso. NearestNeighbor non dovrebbe mai essere utilizzato se la dimensione della bitmap corrisponde all’area a cui si sta disegnando, in pixel.
  • OnPaint() sempre nell’object Graphics a te OnPaint() in OnPaint() .
  • Fai sempre il tuo disegno su OnPaint . Se devi ridisegnare un’area, chiama Invalidate() . Se hai bisogno che il disegno avvenga in questo momento, chiama Update() dopo Invalidate() . Questo è un approccio ragionevole poiché i messaggi WM_PAINT (che risultano in una chiamata a OnPaint() ) sono messaggi a “bassa priorità”. Qualsiasi altra elaborazione da parte del window manager verrà eseguita per prima, e quindi si potrebbe finire con un sacco di frame saltando e agganciati altrimenti.
  • L’utilizzo di un System.Windows.Forms.Timer come un timer framerate / tick non funzionerà molto bene. Questi vengono implementati utilizzando SetTimer di Win32 e generano messaggi WM_TIMER che quindi generano l’evento Timer.Tick che viene generato e WM_TIMER è un altro messaggio a bassa priorità che viene inviato solo quando la coda dei messaggi è vuota. Stai meglio usando System.Threading.Timer e poi usando Control.Invoke() (per assicurarti di essere nel thread giusto!) E chiamando Control.Update() .
  • In generale, non utilizzare Control.CreateGraphics() . (corollario a ‘disegnare sempre in OnPaint() ‘ e ‘usare sempre la Graphics che ti è stata data da OnPaint() ‘)
  • Raccomando di non usare il gestore di eventi Paint. Invece, implementa OnPaint() nella class che stai scrivendo, che dovrebbe essere derivata da Control . Derivando da un’altra class, ad esempio PictureBox o UserControl , non aggiungerà alcun valore per te o aggiungerà un ulteriore sovraccarico. (BTW PictureBox è spesso frainteso, probabilmente non lo userete quasi mai.)

Spero possa aiutare.

GDI + probabilmente non è la scelta migliore per i giochi. DirectX / XNA o OpenGL dovrebbero essere preferiti in quanto utilizzano qualsiasi accelerazione grafica ansible e sono molto veloci.

GDI + non è un demone della velocità con qualsiasi mezzo. Qualsiasi seria manipolazione di immagini deve di solito entrare nel lato nativo delle cose (chiamate pInvoke e / o manipolazione tramite un puntatore ottenuto chiamando LockBits ).

Hai esaminato XNA / DirectX / OpenGL ?. Questi sono framework progettati per lo sviluppo del gioco e saranno di ordine di grandezza più efficienti e flessibili rispetto all’utilizzo di un framework UI come WinForms o WPF. Le librerie offrono tutte le associazioni C #.

È ansible eseguire la pInvoke nel codice nativo, utilizzando funzioni come BitBlt , ma è presente anche un sovraccarico con l’attraversamento del limite del codice gestito.