2012-01-23 25 views
32

Estoy jugando con FireMonkey para ver si la pintura gráfica es más rápida que GDI o Graphics32 (mi biblioteca preferida en este momento).¿Por qué dibujar una línea de menos de 1,5 píxeles de grosor dos veces más lenta que dibujar una línea de 10 píxeles de grosor?

Para ver lo rápido que es, he realizado algunas pruebas, pero me encuentro con algún comportamiento extraño:

dibujar líneas finas (1,5 < píxeles de ancho) que parece ser extremadamente lentas líneas más gruesas en comparación: Performance

  • eje vertical: cpu garrapatas para pintar 1000 líneas
  • eje horizontal: Tickness línea *

Los resultados son bastante estables; el dibujo siempre se vuelve mucho más rápido una vez que el grosor de la línea es más de 1 píxel de ancho.

En otras bibliotecas parece haber algoritmos rápidos para líneas individuales, y las líneas gruesas son más lentas porque primero se crea un polígono, entonces ¿por qué FireMonkey es al revés?

Principalmente necesito líneas de un píxel, así que ¿debería pintar líneas de una manera diferente?

Las pruebas se realizaron con este código:

// draw random lines, and copy result to clipboard, to paste in excel 
procedure TForm5.PaintBox1Paint(Sender: TObject; Canvas: TCanvas); 
var 
    i,iWidth:Integer; 
    p1,p2: TPointF; 
    sw:TStopWatch; 
const 
    cLineCount=1000; 
begin 
    Memo1.Lines.Clear; 
    // draw 1000 different widths, from tickness 0.01 to 10 
    for iWidth := 1 to 1000 do 
    begin 
    Caption := IntToStr(iWidth); 
    Canvas.BeginScene; 
    Canvas.Clear(claLightgray); 
    Canvas.Stroke.Kind := TBrushKind.bkSolid; 
    Canvas.Stroke.Color := $55000000; 
    Canvas.StrokeThickness :=iWidth/100; 
    sw := sw.StartNew; 
    // draw 1000 random lines 
    for I := 1 to cLineCount do 
    begin 
     p1.Create(Random*Canvas.Width,Random*Canvas.Height); 
     p2.Create(Random*Canvas.Width,Random*Canvas.Height); 
     Canvas.DrawLine(p1,p2,0.5); 
    end; 
    Canvas.EndScene; 
    sw.Stop; 
    Memo1.Lines.Add(Format('%f'#9'%d', [Canvas.StrokeThickness, Round(sw.ElapsedTicks/cLineCount)])); 
    end; 
    Clipboard.AsText := Memo1.Text; 
end; 

actualización

@Steve Wellens: De hecho, las líneas verticales y las líneas horizontales son mucho más rápido. De hecho, hay una diferencia entre los horizontales y los verticales:

Difference between Diagonal, Horitonzal and Vertical lines líneas diagonales:, líneas horizontales de color azul: verde, líneas verticales: rojo

con líneas verticales, hay una diferencia clara entre las líneas que son menos de 1 píxel de ancho Con las líneas diagonales hay una pendiente entre 1.0 y 1.5.

Lo extraño es que apenas hay diferencia entre pintar una línea horizontal de 1 píxel y pintar uno de 20 píxeles. Supongo que aquí es donde la aceleración del hardware comienza a marcar la diferencia.

+3

¿Es posible hacer algún tipo de decisiones anti-aliasing, como decidir si cualquier píxel * * de la línea debe ser dibujada? –

+1

Estoy de acuerdo (tal vez no está usando el algoritmo de línea Bresenham). Puede probar esto dibujando una línea vertical u horizontal pura. –

+2

@SteveWellens, usa el algoritmo antialias de Wu – OnTheFly

Respuesta

28

Resumen: Las líneas de grosor de subpíxeles con antialiasing son un trabajo arduo y requieren una cantidad de trucos sucios para producir lo que intuitivamente esperamos ver.

El esfuerzo extra que está viendo se debe casi con certeza al antialiasing. Cuando el grosor de la línea es inferior a un píxel y la línea no se sienta de lleno en el centro de una fila de píxeles del dispositivo, cada píxel dibujado para la línea será un píxel de brillo parcial. Para asegurarse de que esos valores parciales son lo suficientemente brillantes como para que la línea no desaparezca, se requiere más trabajo.

Dado que las señales de video operan en un barrido horizontal (piense en CRT, no en LCD), las operaciones de gráficos tradicionalmente se enfocan en procesar cosas en una línea de escaneo horizontal a la vez.

Aquí es mi suposición:

para resolver ciertos problemas pegajosos, rasterizadores veces "codazo" líneas de modo que más de sus píxeles virtuales se alinean con píxeles de dispositivo. Si una línea horizontal de 0,25 píxeles de espesor está exactamente a medio camino entre la línea de exploración A y B del dispositivo, esa línea puede desaparecer por completo porque no se registra lo suficiente como para iluminar ningún píxel en la línea A o B. Por lo tanto, el rasterizador podría empujar línea "hacia abajo" un poco en las coordenadas virtuales para que se alinee con los píxeles del dispositivo B de la línea de exploración y produzca una línea horizontal agradablemente iluminada.

Lo mismo se puede hacer para las líneas verticales, pero probablemente no lo esté si su tarjeta gráfica/controlador está hiperfocalizada en las operaciones de escaneo horizontal (como muchas otras).

Por lo tanto, en este escenario, una línea horizontal se renderizaría muy rápido porque no se realizaría ningún antialiasing, y todo se puede hacer en una línea de escaneo.

Una línea vertical requeriría un análisis antialiasing para cada línea de exploración horizontal que cruza la línea. El rasterizador puede tener un caso especial para líneas verticales para considerar solo los píxeles izquierdo y derecho para calcular los valores de antialiasing.

Una línea diagonal no tiene atajos. Tiene jaggies en todas partes, por lo que hay mucho trabajo antialiasing para hacer en todas partes. El cálculo antialias debe considerar (submuestra) una matriz completa de puntos (al menos 4, probablemente 8) alrededor del punto objetivo para decidir cuánto de un valor parcial le dará al dispositivo píxel. La matriz se puede simplificar o eliminar por completo para líneas verticales u horizontales, pero no para diagonales.

Hay un elemento adicional que realmente solo preocupa a las líneas de grosor subpíxel: ¿cómo evitamos que la línea de grosor subpixel desaparezca por completo o tenga huecos notables donde la línea no cruza el centro del píxel de un dispositivo? Es probable que después de que los valores antialias se calculen en una línea de exploración, si no hay una "señal" clara o un píxel del dispositivo suficientemente iluminado causado por la línea virtual, el rasterizador debe retroceder e "intentar más" o aplicar alguna heurística de refuerzo a obtener una relación señal/piso más fuerte para que los píxeles del dispositivo que representan la línea virtual sean tangibles y continuos.

Dos píxeles de dispositivos adyacentes con un 40% de brillo están bien. Si la única salida de rasterizador para la línea de exploración es dos píxeles adyacentes al 5%, el ojo percibirá un espacio en la línea. No está bien.

Cuando la línea tiene más de 1.5 píxeles de dispositivo en grosor, siempre tendrá al menos un píxel de dispositivo bien iluminado en cada línea de exploración y no necesita retroceder e intentar más.

¿Por qué 1.5 es el número mágico para el grosor de línea? Pregúntele a Pitágoras. Si el píxel de su dispositivo tiene 1 unidad de ancho y alto, entonces la longitud de la diagonal del píxel del dispositivo cuadrado es sqrt (1^2 + 1^2) = sqrt (2) = 1.41ish. Cuando el grosor de su línea es mayor que la longitud de la diagonal del píxel de un dispositivo, siempre debe tener al menos un píxel "bien iluminado" en la salida del escáner, sin importar el ángulo de la línea.

Esa es mi teoría, de todos modos.

+3

+1, buena respuesta! – TLama

6

En otras bibliotecas parece haber algoritmos rápidos para líneas individuales, y las líneas gruesas son más lentas porque primero se crea un polígono, entonces ¿por qué FireMonkey es al revés?

En Graphics32, el algoritmo de línea de Bresenham se utiliza para acelerar las líneas que se dibujan con un ancho de 1px y que sin duda debería ser rápido.FireMonkey no tiene su propio rasterizador nativo, sino que delega las operaciones de pintura a otras API (en Windows, se delegará en Direct2D o GDI +).

Lo que está observando es, de hecho, el rendimiento del rasterizador Direct2D y yo puedo confirmar que he hecho observaciones similares anteriormente (he comparado muchos rasterizadores diferentes). Aquí hay una publicación que habla específicamente sobre el rendimiento del rasterizador Direct2D (por cierto, no es una regla general que las líneas delgadas se dibujen más lentamente, especialmente no en mi propia impresora de trama):

http://www.graphics32.org/news/newsgroups.php?article_id=10249

Como se puede ver en el gráfico, Direct2D tiene muy buena performan cina de elipses y líneas gruesas, pero mucho peor peformance en los otros puntos de referencia (donde mi propia impresora de trama es más rápido.)

Yo sobre todo necesito líneas de un solo píxel, por lo que debería pintar líneas de una manera diferente, tal vez?

Implementé un nuevo back-end FireMonkey (un nuevo descendiente TCanvas), que se basa en mi propio motor rasterizador VPR. Debería ser más rápido que Direct2D para líneas finas y para texto (aunque está usando técnicas de rasterización poligonal). Todavía puede haber algunas advertencias que deben abordarse para que funcione 100% sin problemas como un backend Firemonkey. Más información aquí:

http://graphics32.org/news/newsgroups.php?article_id=11565

+0

Debo añadir que he revisado mi teoría anterior de que se trata de técnicas de eliminación de oclusiones. Creo que es más probable que el caso en el que una gran cantidad de geometría cubra el mismo píxel (es decir, más de un borde) se trate de manera diferente. –

Cuestiones relacionadas