performance - fmx - kscomponents




Warum zeichnet eine Linie, die weniger als 1,5 Pixel dick ist, doppelt so langsam wie eine 10 Pixel dicke Linie? (2)

Ich spiele nur mit FireMonkey herum, um zu sehen, ob das grafische Zeichnen schneller ist als GDI oder Graphics32 (momentan meine bevorzugte Bibliothek).

Um zu sehen, wie schnell es ist, habe ich einige Tests durchgeführt, aber ich habe ein seltsames Verhalten:

Zeichnen dünner Linien (<1,5 Pixel breit) scheint im Vergleich zu dickeren Linien extrem langsam zu sein:

  • Vertikale Achse: CPU-Ticks, um 1000 Zeilen zu malen
  • Horizontale Achse: Strichstärke *

Die Ergebnisse sind ziemlich stabil; Zeichnung wird immer viel schneller, wenn die Linienstärke mehr als 1 Pixel breit ist.

In anderen Bibliotheken scheint es schnelle Algorithmen für einzelne Zeilen zu geben, und dicke Linien sind langsamer, weil zuerst ein Polygon erstellt wird. Warum also ist FireMonkey anders herum?

Ich brauche meistens Single-Pixel-Linien, also sollte ich Linien vielleicht anders malen?

Die Tests wurden mit diesem Code ausgeführt:

// 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;

Aktualisieren

@Steve Wellens: In der Tat sind vertikale Linien und horizontale Linien viel schneller. Es gibt tatsächlich einen Unterschied zwischen horizontalen und vertikalen:

Diagonale Linien: blau, horizontale Linien: grün, vertikale Linien: rot

Bei vertikalen Linien besteht ein scharfer Unterschied zwischen Linien, die weniger als 1 Pixel breit sind. Bei diagonalen Linien gibt es eine Neigung zwischen 1,0 und 1,5.

Das Seltsame ist, dass es kaum einen Unterschied zwischen dem Malen einer horizontalen Linie von 1 Pixel und dem Malen eines von 20 Pixeln gibt. Ich denke, hier beginnt die Hardwarebeschleunigung einen Unterschied zu machen?


In anderen Bibliotheken scheint es schnelle Algorithmen für einzelne Zeilen zu geben, und dicke Linien sind langsamer, weil zuerst ein Polygon erstellt wird. Warum also ist FireMonkey anders herum?

In Graphics32 wird der Linienalgorithmus von Bresenham verwendet, um Linien zu beschleunigen, die mit einer Breite von 1 Pixel gezeichnet werden und die definitiv schnell sein sollten. FireMonkey verfügt nicht über einen eigenen nativen Rasterizer, sondern delegiert die Malvorgänge an andere APIs (in Windows delegiert es entweder an Direct2D oder GDI +).

Was Sie beobachten, ist in der Tat die Leistung des Direct2D Rasterizers und ich kann bestätigen, dass ich ähnliche Beobachtungen zuvor gemacht habe (ich habe viele verschiedene Rasterizer getestet.) Hier ist ein Beitrag, der sich speziell mit der Leistung des Direct2D Rasterizers befasst (btw , es ist keine allgemeine Regel, dass dünne Linien langsamer gezeichnet werden, besonders nicht in meinem eigenen Rasterizer):

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

Wie Sie aus der Grafik sehen können, hat Direct2D eine sehr gute Leistung für Ellipsen und dicke Linien, aber viel schlechtere Leistung in den anderen Benchmarks (wo mein eigener Rasterizer schneller ist).

Ich brauche meistens Single-Pixel-Linien, also sollte ich Linien vielleicht anders malen?

Ich habe ein neues FireMonkey-Backend (ein neuer TCanvas-Nachfolger) implementiert, das auf meiner eigenen Raster-Engine VPR basiert. Es sollte für dünne Linien und für Text schneller sein als Direct2D (obwohl es polygonale Rastertechniken verwendet). Es kann immer noch einige Vorbehalte geben, die behoben werden müssen, damit es 100% nahtlos als ein Firemonkey-Backend funktioniert. Mehr Infos hier:

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


Zusammenfassung: Antialiasing Subpixeldicke Linien ist harte Arbeit und erfordert eine Reihe von schmutzigen Tricks, um auszugeben, was wir intuitiv erwarten zu sehen.

Die zusätzliche Anstrengung, die Sie sehen, ist fast sicher auf Antialiasing zurückzuführen. Wenn die Liniendicke weniger als ein Pixel ist und die Linie nicht direkt in der Mitte einer Reihe von Gerätepixeln sitzt, ist jedes für die Linie gezeichnete Pixel ein Teilhelligkeitspixel. Um sicherzustellen, dass diese Teilwerte hell genug sind, damit die Linie nicht verschwindet, ist mehr Arbeit erforderlich.

Da Videosignale auf einem horizontalen Sweep arbeiten (denken Sie CRT, nicht LCD), konzentrieren sich Grafikoperationen traditionell auf die Verarbeitung von jeweils einer horizontalen Scanlinie.

Hier ist meine Vermutung:

Um bestimmte klebrige Probleme zu lösen, "stupsen" Rasterisierer manchmal Linien, so dass mehr ihrer virtuellen Pixel mit Gerätepixeln ausgerichtet sind. Wenn eine 0,25 Pixel dicke horizontale Linie genau zwischen der Gerätescanlinie A und B liegt, kann diese Linie vollständig verschwinden, da sie nicht stark genug registriert, um irgendwelche Pixel in der Scanlinie A oder B zu beleuchten Linie "down" ein kleines bisschen in virtuellen Koordinaten, so dass es mit Scanline B Gerät Pixel ausgerichtet ist und eine schöne stark beleuchtete horizontale Linie.

Das gleiche gilt für vertikale Linien, aber wahrscheinlich nicht, wenn Ihre Grafikkarte / Treiber auf horizontale Scanline-Operationen (wie viele) hyperfokussiert ist.

In diesem Szenario würde eine horizontale Linie sehr schnell rendern, da überhaupt kein Antialiasing ausgeführt werden muss, und dies alles kann in einer Scanlinie durchgeführt werden.

Eine vertikale Linie würde eine Antialiasing-Analyse für jede horizontale Scanlinie erfordern, die die Linie kreuzt. Der Rasterizer kann einen speziellen Fall für vertikale Linien haben, um nur die linken und rechten Pixel zu berücksichtigen, um Antialiasing-Werte zu berechnen.

Eine diagonale Linie hat keine Abkürzungen. Es hat überall Schuppen, also gibt es überall Antialiasing-Arbeit. Die Antialias-Berechnung muss eine ganze Matrix von Punkten (mindestens 4, wahrscheinlich 8) um ​​den Zielpunkt herum (Subsample) berücksichtigen, um zu entscheiden, wie viel von einem Teilwert dem Bauelement Pixel geben soll. Die Matrix kann für vertikale oder horizontale Linien, aber nicht für Diagonalen vereinfacht oder ganz weggelassen werden.

Es gibt einen zusätzlichen Punkt, der nur für Subpixeldicklinien von Bedeutung ist: Wie vermeiden wir, dass die Unterpixeldickelinie vollständig verschwindet oder merkliche Lücken aufweist, in denen die Linie nicht die Mitte eines Gerätepixels schneidet? Es ist wahrscheinlich, dass nach dem Berechnen der Antialias-Werte auf einer Abtastlinie, wenn es kein klares "Signal" oder ein ausreichend beleuchtetes Vorrichtungs-Pixel durch die virtuelle Linie gibt, der Rasterer zurückgehen und "etwas härter arbeiten" oder einige Verstärkungsheuristiken anwenden muss erhalten ein stärkeres Signal-zu-Boden-Verhältnis, so dass die Gerätepixel, die die virtuelle Linie darstellen, fühlbar und kontinuierlich sind.

Zwei benachbarte Gerätepixel bei 40% Helligkeit sind in Ordnung. Wenn der einzige Rasterizer, der für die Scanlinie ausgegeben wird, zwei benachbarte Pixel bei 5% ist, wird das Auge eine Lücke in der Linie wahrnehmen. Nicht ok.

Wenn die Zeile mehr als 1,5 Pixel groß ist, haben Sie immer mindestens ein gut ausgeleuchtetes Gerätepixel auf jeder Scanlinie und müssen nicht mehr zurückgehen und sich anstrengen.

Warum ist 1.5 die magische Zahl für Liniendicke? Frag Pythagoras. Wenn Ihr Gerätepixel eine Einheit in Breite und Höhe ist, dann ist die Länge der Diagonale des quadratischen Gerätepixels sqrt (1 ^ 2 + 1 ^ 2) = sqrt (2) = 1.41ish. Wenn Ihre Linienstärke größer als die Länge der Diagonale eines Gerätepixels ist, sollten Sie mindestens einen "gut ausgeleuchteten" Pixel im Scanline-Ausgang haben, unabhängig vom Winkel der Linie.

Das ist sowieso meine Theorie.







firemonkey