为什么画一条小于1.5像素的线的速度是画一条10像素厚的线的两倍?

时间:2012-01-23 00:50:25

标签: performance delphi graphics line firemonkey

我正在玩FireMonkey,看看图形绘画是否比GDI或Graphics32(我目前选择的库)更快。

要知道它有多快,我已经进行了一些测试,但我遇到了一些奇怪的行为:

与较粗的线相比,绘制细线(<1.5像素宽)似乎非常慢: Performance

  • 垂直轴:cpu刻度以绘制1000行
  • 横轴:线刻度*

结果非常稳定;一旦线宽超过1个像素宽,绘图总是变得更快。

在其他库中,似乎有单行快速算法,粗线较慢,因为首先创建了多边形,为什么FireMonkey反过来呢?

我主要需要单像素线,所以我应该以不同的方式画线吗?

使用以下代码运行测试:

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

更新

@Steve Wellens: 实际上,垂直线和水平线要快得多。 水平和垂直之间实际上有区别:

Difference between Diagonal, Horitonzal and Vertical lines 对角线:蓝色,水平线:绿色,垂直线:红色

对于垂直线,小于1像素宽的线条之间存在明显差异。对角线的斜率介于1.0和1.5之间。

奇怪的是,绘制1个像素的水平线和绘制20个像素中的一个之间几乎没有任何区别。我想这是硬件加速开始产生影响的地方吗?

2 个答案:

答案 0 :(得分:28)

总结:抗锯齿亚像素厚度线是一项艰苦的工作,需要一些肮脏的技巧来输出我们直观期望看到的内容。

您所看到的额外努力几乎可以肯定是由于抗锯齿。当线厚度小于一个像素并且线不直接位于一行器件像素的中心时,为该线绘制的每个像素将是部分亮度像素。为了确保这些部分值足够亮以使线条不会消失,需要做更多的工作。

由于视频信号在水平扫描上运行(想想CRT,而不是LCD),因此图形操作传统上一直专注于处理一条水平扫描线。

这是我的猜测:

为了解决某些粘滞问题,光栅化器有时会“轻推”线条,以便更多虚拟像素与设备像素对齐。如果.25像素厚的水平线正好在设备扫描线A和B之间的一半,那么该线可能会完全消失,因为它没有足够强的记录来点亮扫描线A或B中的任何像素。因此,光栅化器可能会轻推在虚拟坐标中“向下”移动一点点,以便它与扫描线B设备像素对齐,并产生一个强烈亮的水平线。

对于垂直线也可以这样做,但如果您的图形卡/驱动程序在水平扫描线操作上过度聚焦(可能很多),则可能不会这样做。

因此,在这种情况下,水平线会渲染得非常快,因为根本不会执行抗锯齿,而且可以在一条扫描线上完成。

垂直线需要对穿过该线的每条水平扫描线进行抗锯齿分析。对于垂直线,光栅化器可能有一个特殊情况,只考虑左右像素来计算抗锯齿值。

对角线没有捷径。它到处都是锯齿状,因此在整个过程中都有大量的抗锯齿工作。抗锯齿计算必须考虑(子样本)目标点周围的整个点矩阵(至少4个,可能是8个)来确定给设备像素的部分值的多少。对于垂直或水平线,矩阵可以完全简化或消除,但对于对角线则不能。

还有一个额外的项目实际上只关注子像素厚度线:我们如何避免子像素厚度线完全消失或者线条没有穿过器件像素中心的明显间隙?在扫描线上计算抗锯齿值之后,如果没有明确的“信号”或由虚拟线引起的充分照明的设备像素,光栅化器可能会返回并“更加努力”或应用一些增强启发式来获得更强的信号与地板比率,使得表示虚拟线路的设备像素是有形且连续的。

两个相邻设备像素的亮度为40%即可。如果扫描线的唯一光栅化器输出是5%的两个相邻像素,则眼睛将感知线中的间隙。不行。

当线条的厚度超过1.5个设备像素时,每条扫描线上至少会有一个光线充足的设备像素,并且不需要返回并尝试更加努力。

为什么1.5是线宽的幻数?问毕达哥拉斯。如果您的设备像素的宽度和高度为1个单位,则方形设备像素的对角线长度为sqrt(1 ^ 2 + 1 ^ 2)= sqrt(2)= 1.41ish。当线条粗细大于设备像素的对角线长度时,无论线条的角度如何,扫描线输出中都应至少有一个“光线充足”的像素。

无论如何,那是我的理论。

答案 1 :(得分:6)

  

在其他库中,似乎有单行快速算法,粗线较慢,因为首先创建了多边形,为什么FireMonkey反过来呢?

在Graphics32中,Bresenham的线算法用于加速以1px宽度绘制的线条,这绝对应该很快。 FireMonkey没有自己的本机光栅化器,而是将绘制操作委托给其他API(在Windows中,它将委托给Direct2D或GDI +。)

您所观察到的实际上是Direct2D光栅化器的性能,我可以确认我之前已经做过类似的观察(我已经对许多不同的光栅化器进行了基准测试。)这里有一篇专门讨论Direct2D性能的帖子光栅化器(顺便说一下,细线绘制速度不是一般规则,特别是在我自己的光栅化器中不是这样):

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

从图中可以看出,Direct2D对于椭圆和粗线具有非常好的性能,但在其他基准测试(我自己的光栅化器更快)中表现更差。

  

我主要需要单像素线,所以我应该以不同的方式绘制线条吗?

我实现了一个新的FireMonkey后端(一个新的TCanvas后代),它依赖于我自己的光栅化引擎VPR。对于细线和文本,它应该比Direct2D更快(即使它使用多边形光栅化技术。)可能仍有一些需要解决的注意事项,以使其作为Firemonkey后端100%无缝地工作。更多信息:

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