Pourquoi dessiner une ligne de moins de 1,5 Pixel d'épaisseur deux fois plus lentement que dessiner une ligne de 10 pixels d'épaisseur?

Je suis juste en train de jouer avec FireMonkey pour voir si la peinture graphique est plus rapide que GDI ou Graphics32 (Ma bibliothèque de choix pour le moment).

Pour voir comment il est rapide, j'ai effectué quelques tests, mais je croise certains comportement étrange:

tracer des lignes minces (<1,5 pixel de large) semble être extrêmement lent comparé à des lignes plus épaisses: Performance

  • axe Vertical: picotements cpu à peindre 1000 lignes
  • axe Horizontal: ligne tickness*

les résultats sont assez stables; le dessin devient toujours beaucoup plus rapide Une fois que l'épaisseur de la ligne est de plus de 1 pixel de large.

dans d'autres bibliothèques, il semble y avoir des algorithmes rapides pour les lignes simples, et les lignes épaisses sont plus lentes parce qu'un polygone est créé en premier, alors pourquoi FireMonkey est l'inverse?

j'ai surtout besoin d'un pixel lignes, de sorte devrais-je peindre les lignes d'une manière différente peut-être?

Les tests ont été exécutés avec ce code:

// 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 := 000000;
    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;

mise à Jour

@Steve Wellens: En effet, les lignes verticales et horizontales sont beaucoup plus rapides. Il y a en fait une différence entre les horizontales et les verticales:

Difference between Diagonal, Horitonzal and Vertical linesDiagonale des lignes: bleu, lignes Horizontales: vert, les lignes Verticales: rouge

avec les lignes verticales, il y a une différence marquée entre les lignes qui sont moins de 1 pixel de large. Avec les lignes diagonales, il y a une pente entre 1,0 et 1,5.

ce qui est étrange, c'est qu'il n'y a guère de différence entre peindre une ligne horizontale de 1 pixel et peindre une ligne de 20 pixels. Je suppose que c'est là l'accélération matérielle commence à faire une différence?

32
demandé sur dthorpe 2012-01-23 04:50:25

2 réponses

résumé: les lignes D'épaisseur sous-pixel Antialiasantes sont un travail difficile et nécessitent un certain nombre d'astuces sales pour produire ce que nous attendons intuitivement de voir.

L'effort supplémentaire que vous voyez est presque certainement due à antialiasing. Lorsque l'épaisseur de la ligne est inférieure à un pixel et que la ligne ne s'assoit pas exactement au centre d'une rangée de pixels de périphérique, chaque pixel dessiné pour la ligne sera un pixel de luminosité partielle. Pour s'assurer que ces valeurs partielles sont suffisamment claires de sorte que le ligne ne disparaît pas, plus de travail est nécessaire.

puisque les signaux vidéo fonctionnent sur un balayage horizontal (pensez CRT, pas LCD), les opérations graphiques se concentrent traditionnellement sur le traitement des choses une ligne de balayage horizontal à la fois.

Voici mon estimation:

pour résoudre certains problèmes collants, les rasterizers donnent parfois des "coups de pinceau" aux lignes, de sorte qu'un plus grand nombre de leurs pixels virtuels s'alignent avec les pixels de l'appareil. Si un .La ligne horizontale de 25 pixels d'épaisseur est exactement à mi-chemin entre la ligne de balayage de l'appareil A et B, cette ligne peut complètement disparaître parce qu'elle ne s'enregistre pas assez fortement pour éclairer n'importe quels pixels dans scanline A ou B. Ainsi, le rasterizer pourrait pousser la ligne "vers le bas" un petit peu dans les coordonnées virtuelles de sorte qu'il s'alignera avec les pixels de périphérique scanline B et produira une belle ligne horizontale fortement éclairée.

la même chose peut être faite pour les lignes verticales, mais ne l'est probablement pas si votre carte graphique/pilote est hyperfocusé sur les opérations scanline horizontales (autant être.)

donc, dans ce scénario, une ligne horizontale serait rendue très rapidement parce qu'il n'y a pas d'antialiasing à effectuer du tout, et tout peut être fait en une seule ligne de balayage.

une ligne verticale nécessiterait une analyse antialiasante pour chaque ligne Scan horizontale qui croise la ligne. Le rasterizer peut avoir un cas spécial pour les lignes verticales pour ne considérer que les pixels gauche et droite pour calculer les valeurs antialiasing.

Une ligne diagonale n'a pas de raccourcis. Il a jaggies partout, Il ya donc beaucoup de travail antialiasing à faire tout au long. Le calcul d'antialias doit considérer (sous-échantillon) une matrice entière de points (au moins 4, probablement 8) autour du point cible pour décider combien d'une valeur partielle pour donner le pixel de l'appareil. La matrice peut être simplifiée ou éliminée entièrement pour les lignes verticales ou horizontales, mais pas pour les diagonales.

il y a un élément supplémentaire qui n'est vraiment qu'un souci pour les lignes d'épaisseur de sous-pixel: Comment éviter la ligne d'épaisseur de sous-pixel de disparaître entièrement ou ayant des lacunes notables où la ligne ne traverse pas le centre d'un pixel d'appareil? Il est probable qu'après le calcul des valeurs d'antialias sur une ligne de balayage, s'il n'y a pas de "signal" clair ou de pixel de périphérique suffisamment éclairé causé par la ligne virtuelle, le rasterizer doit revenir en arrière et "essayer plus dur" ou appliquer quelques heuristiques de renforcement pour obtenir un rapport signal / plancher plus fort de sorte que les pixels de périphérique représentant la ligne virtuelle sont tangibles et continue.

deux pixels périphériques adjacents à 40% de luminosité est ok. Si la seule sortie de rasterizer pour la ligne de balayage est deux pixels adjacents à 5%, l'oeil percevra un écart dans la ligne. Pas ok.

lorsque la ligne est de plus de 1,5 pixels de périphérique en épaisseur, vous aurez toujours au moins un pixel de périphérique bien éclairé sur chaque ligne de scan et vous n'aurez pas besoin de revenir en arrière et d'essayer plus fort.

pourquoi 1,5 est le nombre magique pour l'épaisseur de la ligne? Demandez À Pythagore. Si votre le pixel de l'appareil est 1 unité dans la largeur et la hauteur, alors la longueur de la diagonale du pixel de l'appareil carré est sqrt(1^2 + 1^2) = sqrt (2) = 1,41 ish. Lorsque l'épaisseur de votre ligne est supérieure à la longueur de la diagonale d'un pixel périphérique, vous devriez toujours avoir au moins un pixel "bien éclairé" dans la sortie scanline quel que soit l'angle de la ligne.

C'est ma théorie, de toute façon.

28
répondu dthorpe 2012-01-24 18:40:37

dans d'autres bibliothèques, il semble y avoir des algorithmes rapides pour les lignes simples, et les lignes épaisses sont plus lentes parce qu'un polygone est créé en premier, alors pourquoi FireMonkey est l'inverse?

dans Graphics32, l'algorithme des lignes de Bresenham est utilisé pour accélérer les lignes qui sont dessinées avec une largeur de 1px et qui devraient certainement être rapides. FireMonkey n'a pas son propre rasterizer natif, mais il délègue les opérations de peinture à d'autres APIs (dans Windows, il déléguera à soit Direct2D, soit GDI+.)

ce que vous observez est en fait la performance du rasterizer Direct2D et je peux confirmer que j'ai déjà fait des observations similaires (j'ai comparé de nombreux rasterizers différents.) Voici un post qui parle spécifiquement de la performance du rasterizer Direct2D (btw, ce n'est pas une règle générale que les lignes minces sont dessinées plus lentement, surtout pas dans mon propre rasterizer):

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

comme vous pouvez le voir sur le graphique, Direct2D a de très bonnes performances pour les ellipses et les lignes épaisses, mais une performance bien pire dans les autres benchmarks (où mon propre rasterizer est plus rapide.)

j'ai surtout besoin de lignes d'un seul pixel, donc devrais-je peindre les lignes d'une manière différente peut-être?

j'ai implémenté un nouveau backend FireMonkey (un nouveau TCanvas descendent), qui s'appuie sur mon propre moteur rasterizer VPR. Elle devrait être plus rapide que Direct2D pour les lignes minces et pour le texte (même si elle utilise des techniques de fracturation polygonale.) Il peut encore y avoir certaines mises en garde qui doivent être prises en compte afin de le faire fonctionner à 100% de façon transparente comme un backend Firemonkey. Plus d'infos ici:

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

6
répondu Mattias Andersson 2012-01-26 01:52:06