2009-06-05 11 views
9

Estoy desarrollando una aplicación de dibujo en modo retenido en GDI +. La aplicación puede dibujar formas simples en un lienzo y realizar una edición básica. La matemática que hace esto está optimizada para el último byte y no es un problema. Estoy dibujando en un panel que está usando Controlstyles.DoubleBuffer incorporado.Winforms: Cómo acelerar Invalidate()?

Ahora, mi problema surge si ejecuto mi aplicación maximizada en un monitor grande (HD en mi caso). Si trato de dibujar una línea desde una esquina del lienzo (grande) hasta la diagonal opuesta a la otra, comenzará a lag y la CPU se disparará hacia arriba.

Cada objeto gráfico en mi aplicación tiene un cuadro delimitador. Por lo tanto, cuando invalido el cuadro delimitador de una línea que va de una esquina de la aplicación maximizada a la diagonal opuesta, ese cuadro delimitador es prácticamente tan grande como el lienzo. Cuando un usuario dibuja una línea, esta invalidación del cuadro delimitador ocurre así en el evento mousemove, y hay un retraso claro visible. Este retraso también existe si la línea es el único objeto en el lienzo.

He intentado optimizar esto de muchas maneras. Si dibujo una línea más corta, la CPU y el retraso disminuyen. Si elimino el Invalidate() y guardo el resto del código, la aplicación es rápida. Si uso una Región (que solo abarca la figura) para invalidar en lugar de la caja delimitadora, es igual de lenta. Si divido el cuadro delimitador en una serie de cuadros más pequeños que se encuentran uno detrás del otro, reduciendo así el área de invalidación, no se puede ver ninguna ganancia de rendimiento visible.

Por lo tanto, estoy perdido aquí. ¿Cómo puedo acelerar la invalidación?

En una nota lateral, Paint.Net y Mspaint sufren de las mismas deficiencias. Word y PowerPoint, sin embargo, parecen ser capaces de pintar una línea como se describió anteriormente sin retraso y sin carga de CPU en absoluto. Por lo tanto, es posible lograr los resultados deseados, la pregunta es ¿cómo?

+0

Word y Powerpoint no usan GDI + ... No creo que GDI + alguna vez haya sido elogiado por su velocidad (a través de GDI normal). –

+0

¿Ha intentado utilizar un generador de perfiles para ver dónde ocurre realmente la desaceleración? He hecho un montón de este tipo de cosas (hace años, en Delphi) y nunca he encontrado Invalidate() para tomar una cantidad de tiempo discernible. Pintar, por otro lado, fue "divertido" correr lo suficientemente rápido como para pegar una línea. – Bevan

+0

Bueno, creo que Delphi es mucho más rápido para este tipo de trabajo en cualquier caso. Como dice Henk, GDI + no es conocido por su velocidad, es más probable que carezca de ella. Sin embargo, el código de dibujo real no es el cuello de botella. La invalidación de la gran área parece ser. Estoy considerando usar mi propio backbuffer y ajustar manualmente la imagen para ver si puedo obtener un aumento en el rendimiento. ¿Podría recomendar algún perfilador en particular para VS2005? – Pedery

Respuesta

7

Para elementos básicos de visualización como líneas, se debe considerar rompiéndolos en unas pocas piezas si es absolutamente necesario invalidar su totalidad límites por ciclo de dibujo.

La razón de esto es que GDI + (así como también GDI) invalida áreas en formas rectangulares, tal como lo especifica con su cuadro delimitador. Puede verificar esto usted mismo probando algunas líneas horizontales y verticales en comparación con las líneas donde la pendiente es similar al aspecto de su área de visualización.

Entonces, digamos que su lienzo es de 640x480. Si dibuja una línea de 0,0 a 639,479; Invalidar() invalidará toda la región de 0,0 a 639,0 en la parte superior hacia abajo a 0,479 a 639,479 en la parte inferior. Una línea horizontal desde, digamos, 0,100 a 639,100 resulta en un rectángulo de solo 1 píxel de alto.

Las regiones tendrán el mismo problema porque las regiones se tratan como conjuntos de extensiones horizontales agrupadas. Entonces, para una gran línea diagonal que va de una esquina a la otra, para que coincida con el cuadro delimitador que ha configurado, una región debería especificar cada conjunto de píxeles en cada línea vertical o en el cuadro delimitador completo.

Así que, como solución, si tiene una línea muy grande, divídala en cuartos o octavos y el rendimiento debería aumentar considerablemente. Al revivir el ejemplo anterior, si solo divide a la mitad por dos partes, reducirá el área total invalidada a 0,0 x 319,239 más 320,240 x 639,479.

Aquí hay un ejemplo visual de un cuarto de divisiones. El área rosa es lo que se invalida. Desafortunadamente SO no me deja publicar imágenes o más de 1 enlace, pero esto debería ser suficiente para explicar todo.

(línea de división en cuartos, invalidado total de área es de 1/4 de la superficie)

a 640x480 extent with 4 equal sized boxes carved behind a line drawn across the diagonal

O, en lugar de especificar un cuadro de límite, es posible que desee considerar la reescritura de sus actualizaciones para que se solo dibuje las partes de los elementos que coinciden con la región que debe actualizarse. Realmente depende de cuántos objetos necesiten participar en una actualización dibujada. Si tiene miles de objetos en un marco determinado, puede considerar ignorar todas las áreas invalidadas y simplemente volver a dibujar toda la escena.

+0

Hola, y gracias por su respuesta. Desafortunadamente, si hubiera leído mi respuesta a la respuesta n. ° 2, ya probé lo que describes y no funciona. Sin embargo, eso ES sorprendente. La escena probablemente no contenga más de unas pocas docenas de elementos, pero eso depende realmente del usuario. prácticamente no hay ganancia en hacerlo de esta manera. La actualización de solo un cuarto de la línea (como prueba) mejoró (como se esperaba), y en este caso casi hay una correlación lineal entre la CPU y el área actualizada. – Pedery

+0

Debo subrayar que esto solo es un problema en mi pantalla grande, 1980x1050, cuando ejecuto el programa maximizado. La carga de la CPU sube hasta aproximadamente un 50%, los redibujados se ralentizan considerablemente, e incluso los eventos del mouse se omiten del bucle de mensajes. – Pedery

+0

Hmmm, I tener un pregunta, entonces. ¿Está manejando el mensaje WM_ERASEBKGND? Si no lo está, intente agregar un controlador para ello en su aplicación y suprima cualquier dibujo sobre ese mensaje de Windows para la ventana que aloja su lienzo GDI +. Como está dibujando toda la escena en el lienzo GDI +, esto debería ahorrarle algo de esfuerzo a la aplicación. También intente utilizar Spy ++ para examinar el orden y la frecuencia de los mensajes WM_PAINT enviados a esa ventana también, para ver si se producen solicitudes de redibujado duplicadas o repetidas. – meklarian

1

Para aclarar: ¿Está el usuario dibujando una línea recta , o su línea en realidad es un grupo de segmentos de línea que conecta puntos del mouse? Si la línea es una línea recta desde el origen hasta el punto actual del mouse, no invalide() y en su lugar use un pincel XOR para dibujar una línea que se pueda deshacer y luego anule la línea anterior, solo invalidando cuando el usuario termine de dibujar.

Si está dibujando un grupo de pequeños segmentos de línea, simplemente invalide el cuadro delimitador del segmento más reciente.

+0

Bueno, esta es una aplicación CAD y utilicé la metáfora de "línea" para simplificar. En realidad estoy dibujando un rectángulo estrecho en un ángulo, por lo tanto, consta de cuatro operaciones GDI + drawline más una invalidación del lienzo.Las líneas pueden tener cualquier color e intersecar muchos otros objetos gráficos en el escenario, así que no estoy seguro de que XOR sea el camino correcto ... – Pedery

1

¿Qué tal tener un hilo diferente que "publicar actualizaciones" en el lienzo real.

Image paintImage; 
private readonly object paintObject = new object(); 
public _ctor() { paintImage = new Image(); } 

override OnPaint(PaintEventArgs pea) { 
    if (needUpdate) { 
     new Thread(updateImage).Start(); 
    } 
    lock(paintObject) { 
     pea.DrawImage(image, 0, 0, Width, Height); 
    } 
} 

public void updateImage() { 
    // don't draw to paintImage directly (it might cause threading issues) 
    Image img = new Image(); 
    using (Graphics g = img.GetGraphics()) { 
     foreach (PaintableObject po in renderObjects) { 
      g.DrawObject(po); 
     } 
    } 
    lock(paintObject){ 
     using (Graphics g = paintImage.GetGraphics()) { 
      g.DrawImage(img, 0, 0, g.Width, g.Height); 
     } 
    } 
    needUpdate = false; 
} 

Sólo una idea, así que no he probado el código ;-)

+0

Esto podría funcionar, pero lo dudo. En primer lugar, tendría que alejarme del buffer doble GDI + incorporado (que aparentemente tiene un rendimiento bastante bueno) y, en segundo lugar, el cuello de botella parece ser la invalidación misma. El resto de los gastos generales es insignificante. Dado que GDI + es pura manipulación de software de gráficos (sin aceleración de HW), simplemente significaría mover el problema de un hilo al siguiente. Sin embargo, me interesa saber si alguien tiene alguna experiencia en la optimización de la representación mediante el uso de hilos separados. Otra opción podría ser utilizar un enfoque acelerado de HW. – Pedery

+0

Hm, si es "Invalidate()" que es lento, ¿qué tal si solo llama a OnPaint (nuevo PaintEventArgs (CreateGraphics(), null || constRectangle)? – Patrick

0

Si ha utilizado regiones correctamente, como describió, debería funcionar lo suficientemente rápido. Significa que su problema es causado por algunas circunstancias o errores que no describió. Haga lo siguiente:

Cree el código más pequeño que muestra el problema.

Esto siempre debe ser el segundo paso durante la depuración, si no puede encontrar la respuesta en varios minutos. Si no puede reproducir su problema con una implementación más pequeña, simplemente significa que es causado por algunos de sus errores que no se han descrito aquí. De lo contrario, podemos ayudar.

1

Realmente no se puede acelerar Invalidar. La razón por la que es lenta es porque publica un evento WM_PAINT en la cola de mensajes. Eso luego se filtra y eventualmente se llama su OnPaint. Lo que debe hacer es pintar directamente en su control durante el evento MouseMove.

En cualquier control que haga que requiera cierta medida de animación fluida, mi evento OnPaint generalmente solo llama a una función PaintMe. De esa manera puedo usar esa función para volver a dibujar el control en cualquier momento.

+0

Claro, y puedo ver lo que dices. Quiere decir que debería actualizar() mi control en lugar de invalidar, en función de un esquema de fps. Sin embargo, esto probablemente forzará aún más a la CPU. Recuerde que esto es solo un problema cuando invalido partes grandes de una pantalla HD. Realmente estaba buscando respuestas como si este proceso pudiera acelerarse con éxito mediante blitting manual, es decir, si pintar en un GDI + bmp y usar DX para pintar esa imagen en una superficie DX lo haría más rápido. – Pedery