¿Graphics.DrawImage es demasiado lento para imágenes más grandes?

Actualmente estoy trabajando en un juego y deseo tener un menú principal con la imagen de fondo.

Sin embargo, el método Graphics.DrawImage() me parece realmente lento. He hecho algunas mediciones. Supongamos que MenuBackground es mi imagen de recursos con una resolución de 800 x 1200 píxeles. Lo dibujaré en otro bitmap de 800 x 1200 (primero renderizo todo en un bitmap de buffer, luego lo escalo y finalmente lo dibujo en la pantalla, así es como trato la posibilidad de resoluciones de múltiples jugadores. Pero no debería afectar de ninguna manera, mira el siguiente párrafo).

Así que he medido el siguiente código:

 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"); 

El resultado es bastante sorprendente para mí: el Stopwatch mide algo entre 40 - 50 milliseconds . Y como la imagen de fondo no es lo único que se dibujará, el menú completo tarda más de 100 ms en mostrarse, lo que implica un retraso observable.

Intenté dibujarlo en el objeto Graphics dado por el evento Paint, pero el resultado fue de 30 - 40 milliseconds , sin mucho cambio.

Entonces, ¿significa que Graphics.DrawImage() se puede usar para dibujar imágenes más grandes? Si es así, ¿qué debo hacer para mejorar el rendimiento de mi juego?

Sí, es muy lento.

Me encontré con este problema hace varios años mientras desarrollaba Paint.NET (desde el principio, de hecho, ¡y fue bastante frustrante!). El rendimiento de la representación fue abismal, ya que siempre fue proporcional al tamaño del bitmap y no al tamaño del área que se le dijo que volviera a dibujar. Es decir, la velocidad de fotogtwigs disminuyó a medida que el tamaño del bitmap subió, y la velocidad de fotogtwigs nunca aumentó ya que el tamaño del área no válida / redibujado disminuyó al implementar OnPaint () y llamar a Graphics.DrawImage (). Un pequeño bitmap, digamos 800×600, siempre funcionó bien, pero las imágenes más grandes (por ejemplo, 2400×1800) fueron muy lentas. (Puede suponer, para el párrafo anterior de todos modos, que no estaba ocurriendo nada extra, como escalar con algún costoso filtro Bicúbico, lo que habría afectado negativamente el rendimiento).

Es posible forzar a WinForms a utilizar GDI en lugar de GDI + y evitar incluso la creación de un objeto Graphics detrás de las escenas, en cuyo punto puede superponer otro toolkit de representación (p. Ej., Direct2D). Sin embargo, no es simple. Hago esto en Paint.NET, y puedes ver lo que se requiere al usar algo como Reflector en la clase llamada GdiPaintControl en la DLL de SystemLayer, pero para lo que estás haciendo lo consideraría como un último recurso.

Sin embargo, el tamaño de bitmap que está usando (800×1200) debería funcionar correctamente en GDI + sin tener que recurrir a la interoperabilidad avanzada, a menos que tenga como objective algo tan bajo como un Pentium II de 300MHz. Aquí hay algunos consejos que pueden ayudar:

  • Si está utilizando un bitmap opaco (sin alfa / transparencia) en la llamada a Graphics.DrawImage() , y especialmente si se trata de un bitmap de 32 bits con un canal alfa (pero sabe que es opaco, o no le importa) , luego configure Graphics.CompositingMode en CompositingMode.SourceCopy antes de llamar a DrawImage() (asegúrese de volver a establecer el valor original después, de lo contrario, las primitivas de dibujo normales se verán muy feas). Esto omite una gran cantidad de matemática de mezcla adicional por píxel.
  • Asegúrese de que Graphics.InterpolationMode no esté configurado en algo como InterpolationMode.HighQualityBicubic . Usar NearestNeighbor será el más rápido, aunque si hay algún estiramiento puede que no se vea muy bien (a menos que se extienda exactamente 2x, 3x, 4x, etc.) Bilinear suele ser un buen compromiso. Nunca debe usar nada más que NearestNeighbor si el tamaño del bitmap coincide con el área en la que está dibujando, en píxeles.
  • Siempre dibuje en el objeto Graphics que se le entregó en OnPaint() .
  • Siempre haz tu dibujo en OnPaint . Si necesita volver a dibujar un área, llame a Invalidate() . Si necesita que el sorteo ocurra en este momento, llame a Update() después de Invalidate() . Este es un enfoque razonable ya que los mensajes WM_PAINT (que da como resultado una llamada a OnPaint() ) son mensajes de “baja prioridad”. Cualquier otro procesamiento por parte del administrador de ventanas se hará primero, y de esta manera usted podría terminar con muchos saltos y enganches de cuadros.
  • Usar un Temporizador System.Windows.Forms.Timer como un temporizador de framerate / tick no funcionará muy bien. Estos se implementan utilizando el SetTimer de Win32 y dan como resultado mensajes WM_TIMER que luego dan como resultado el evento Timer.Tick y WM_TIMER es otro mensaje de baja prioridad que se envía solo cuando la cola de mensajes está vacía. Es mejor utilizar System.Threading.Timer y luego usar Control.Invoke() (para asegurarse de que está en el hilo correcto) y llamar a Control.Update() .
  • En general, no use Control.CreateGraphics() . (Corolario de ‘dibujar siempre en OnPaint() ‘ y ‘usar siempre los Graphics que te da OnPaint() ‘)
  • Recomiendo no usar el controlador de eventos Paint. En su lugar, implemente OnPaint() en la clase que está escribiendo, que debería derivarse de Control . Deriva de otra clase, por ejemplo, PictureBox o UserControl , no agregará ningún valor para usted o agregará gastos adicionales. (Por cierto, PictureBox menudo es mal interpretado. Probablemente nunca querrás usarlo).

Espero que ayude.

GDI + probablemente no sea la mejor opción para los juegos. DirectX / XNA u OpenGL deben ser preferidos ya que utilizan la aceleración de gráficos que sea posible y son muy rápidos.

GDI + no es un demonio de velocidad de ninguna manera. Cualquier manipulación seria de la imagen generalmente tiene que entrar en el lado nativo de las cosas (llamadas de invocación y / o manipulación a través de un puntero obtenido llamando a LockBits ).

¿Has mirado en XNA / DirectX / OpenGL ?. Estos son marcos diseñados para el desarrollo de juegos y serán órdenes de magnitud más eficientes y flexibles que el uso de un marco de interfaz de usuario como WinForms o WPF. Todas las bibliotecas ofrecen enlaces C #.

Puede invocar en el código nativo, utilizando funciones como BitBlt , pero también hay gastos generales asociados con el cruce del límite del código administrado.