While looking for alternatives to replace GDI, I was trying to test Delphi's 2010 TDirect2DCanvas performance in Windows 7.
I tested it by drawing a huge polyline using Direct2D and the result was absurdly slow, even with 500 times less data than the amount I've ran the same test using GDI (and I didn't even use a bitmap as backbuffer in GDI, I just drew to the form canvas directly).
So I guess either:
a) Direct2D is slower than GDI;
b) TDirect2DCanvas is slow;
c) I'm doing something wrong
and hopefully it's c).
The test code I wrote is:
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls, Direct2D, D2D1;
type
TForm2 = class(TForm)
private
{ Private declarations }
FD2DCanvas: TDirect2DCanvas;
FData: array[0..50000] of TPoint;
public
procedure CreateWnd; override;
procedure WMSize(var Message: TWMSize); message WM_SIZE;
procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
{ Public declarations }
end;
var
Form2: TForm2;
implementation
uses utils;
{$R *.dfm}
procedure TForm2.CreateWnd;
var
i: Integer;
begin
inherited;
FD2DCanvas := TDirect2DCanvas.Create(Handle);
for i := 0 to High(FData) do begin
FData[i].X := Random(Self.ClientWidth div 2);
FData[i].Y := Random(Self.ClientHeight);
end;
end;
procedure TForm2.WMPaint(var Message: TWMPaint);
var
PaintStruct: TPaintStruct;
begin
BeginPaint(Handle, PaintStruct);
try
FD2DCanvas.BeginDraw;
try
FD2DCanvas.Polyline(FData);
finally
FD2DCanvas.EndDraw;
end;
finally
EndPaint(Handle, PaintStruct);
end;
end;
procedure TForm2.WMSize(var Message: TWMSize);
begin
if Assigned(FD2DCanvas) then begin
ID2D1HwndRenderTarget(FD2DCanvas.RenderTarget).Resize(D2D1SizeU(ClientWidth, ClientHeight));
end;
end;
end.
Also, I'm really willing to draw long polylines in real code, as a system I'm working on need to draw plenty of ~2500 points polylines (at least 10K of them).
I've found out earlier that Direct2D doesn't seem to like polylines, it draws faster if you use a lot of single lines (2 points polylines).
Thanks to Chris Bensen I found out the slowness was with large polylines while using anti-aliasing. So I disabled anti-aliasing as Chris suggested and performance went from ~6000ms to ~3500ms for drawing 50k lines.
Things could still be improved because Direct2D just doesn't handle well polylines while using anti-aliasing. With anti-aliasing disabled it's just the opposite.
Now the time for drawing with Direct2D the 50k lines, if I draw the large polyline without anti-aliasing, is ~50ms. Nice, eh!
The thing is that GDI is still faster than Direct2D if I draw to a bitmap and after it's done I BitBlt the result back to the form, it paints at ~35ms, and with the same graphics quality. And, Direct2D also seems to be using a backbuffer already (it just draws when EndDraw()
is called).
So, can this be improved somehow to make using Direct2D worth speed-wise?
Here's the updated code:
type
TArray = array[0..1] of TPoint;
PArray = ^TArray;
procedure TForm2.WMPaint(var Message: TWMPaint);
var
PaintStruct: TPaintStruct;
begin
FD2DCanvas.RenderTarget.SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
BeginPaint(Handle, PaintStruct);
try
FD2DCanvas.BeginDraw;
try
FD2DCanvas.Pen.Color := clRed;
FD2DCanvas.Polyline(FData);
finally
FD2DCanvas.EndDraw;
end;
finally
EndPaint(Handle, PaintStruct);
end;
end;
By the way, even if I use Chris' suggestion of creating the geometry beforehand the speed is about the same speed as GDI, but still not faster.
My computer is running Direct3D and OpenGL apps normally and here's dxDiag output: http://mydxdiag.pastebin.com/mfagLWnZ
I'll be glad if anyone can explain me why is this slowness. Sample code is much appreciated.
The problem is antialiasing is turned on. Disable antialiasing and the performance of Direct2D will be on par or faster than GDI. To do that after TDirect2DCanvas is created, make this call:
FD2DCanvas.RenderTarget.SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
TDirect2DCanvas is interface compatible where possible with TCanvas so it can be a drop in replacement with TCanvas, so some of the drawing routines are are a bit inefficient. For example, Polyline creates a geometry each time it is called and throws it away. To increase performance keeping the geometry around.
Take a look at the implementation for TDirect2DCanvas.Polyline and hoist that out into your application for something like this:
procedure TForm2.CreateWnd;
var
i: Integer;
HR: HRESULT;
Sink: ID2D1GeometrySink;
begin
...
D2DFactory.CreatePathGeometry(FGeometry);
HR := FGeometry.Open(Sink);
try
Sink.BeginFigure(D2D1PointF(FData[0].X + 0.5, FData[0].Y + 0.5),
D2D1_FIGURE_BEGIN_HOLLOW);
try
for I := Low(FData) + 1 to High(FData) - 1 do
Sink.AddLine(D2D1PointF(FData[I].X + 0.5, FData[I].Y + 0.5));
finally
Sink.EndFigure(D2D1_FIGURE_END_OPEN);
end;
finally
hr := Sink.Close;
end;
And then draw it like so:
procedure TForm2.WMPaint(var Message: TWMPaint);
begin
FD2DCanvas.BeginDraw;
FD2DCanvas.Pen.Color := clRed;
FD2DCanvas.RenderTarget.DrawGeometry(FGeometry, FD2DCanvas.Pen.Brush.Handle);
FD2DCanvas.EndDraw;
end;