0
votes

Overview

I need to draw lots of graphics onto a canvas, in this case a TPaintBox canvas. The paintbox is child to a TScrollBox and the size of the paintbox could be very large in height and width, for example 5000x5000.

I have a TList which holds my own objects, each object has its own X and Y property as well as its own graphic. In the OnPaint method of the paintbox I iterate through each object in my list and then draw each objects graphic onto the paintbox canvas at coordinates that are stored in the objects X and Y position.

Obviously drawing all this slows down the application and becomes very heavy, so I needed a way to optimise this.

The obvious way is to somehow utilise the GPU rather than the CPU but that is likely much more complex, Firemonkey could help me but I am strictly running in a VCL type project not FMX.

Possible Solution?

I came across CreateRectRgn and providing I understand it correctly, thought that if I could paint on the paintbox with a custom region, for example in the visible area of the scrollbox this should improve performance a lot. So with that in mind I tried something along the lines of:

procedure TForm1.PaintBox1Paint(Sender: TObject);    
var
  MyRgn: HRGN;
begin
  // iterate and draw objects onto FBuffer (offscreen bitmap) first,
  // note: FBuffer size is the same as the scrollboxes clientwidth
  // and clientheight
  //begin
  //  ...
  //end;

  // create and paint on a region (visible area of the scrollbox) on
  // the paintbox rather than painting the whole paintbox.
  MyRgn := CreateRectRgn(0, 0, ScrollBox1.ClientWidth, ScrollBox1.ClientHeight);
  try
    SelectClipRgn(PaintBox1.Canvas.Handle, MyRgn);
    PaintBox1.Canvas.Draw(ScrollBox1.HorzScrollBar.Position,
      ScrollBox1.VertScrollBar.Position, FBuffer);
    SelectClipRgn(PaintBox1.Canvas.Handle, HRGN(nil));
finally
  DeleteObject(MyRgn);
end;

Question

I suppose first and foremost is whether this even a good approach to optimizing how I paint on the paintbox canvas or not, what other possible options do I have? The only other thing of note that I do is check the X and Y of each object and if it is outside the visible area of the scrollbox I do not paint it.

I am still getting familiar with the idea of creating a clipping region, but the above code sample feels wrong. I don't like the idea of constantly creating and deleting the region, especially within the paint method.

My project is getting fairly large, in fact I am actually putting this into a custom control but when dealing with hundreds of objects and painting them on the paintbox canvas I gradually get a slowdown and I am fairly sure it is to do with either me not correctly implementing the clipping region or the way that I constantly create, draw and then delete the region from the OnPaint method.

Is there a more practical suited way to achieve this? Perhaps it is possible to create the region at form create and destroy it on form destroy (or from the custom controls constructor/destructor) for example? But then how would I resize the clipping region if the form/control for example is resized?

I could really use some advice and get some clarity on this problem I am facing to help me better understand what I might be able to do better or differently.

Thanks.

1
A GraphicControl (PaintBox) operates on a DC obtained for its parent (ScrollBox). What I mean is, painting is already clipped to the scrollbox's client area. For more detail, see TWinControl.PaintControls.Sertac Akyuz
@SertacAkyuz wow I never knew that. If that is the case, then I need to find another way of optimizing the painting because I am sure it was even slower before I tried adding my own clipping region. Regardless of this my question and doubts remain, particularly on whether I was in fact setting up the clipping region correctly or not regardless if the parent of the paintbox does this for me or not. I am sure my task can be approached in a better way but learning or correcting mistakes on the way is always welcome.Craig
@SertacAkyuz Also, suppose much later on when I actually get everything put into a custom control, it would likely be better for me to publish a canvas on the scrollbox and paint directly on that instead of using a child paintbox, I would surely then also need a clipping region area for the scrollbox canvas?Craig
Another modification should account for the speed increase. Normally drawing that the system disregards should not account for a significant amount of time, maybe preparation for that drawing - calculation, etc... You should profile and see what takes time. ... I'm having a hard time understanding your design. Your off screen bitmap is the size of the scroll box, so what is it that you paint to the invisible section of the paint box?Sertac Akyuz
An own canvas or a paintbox sub-component - shouldn't matter performance wise, a paintbox is a very light-weight component. Believe me, a wincontrol clips its DC to the size of its child controls before making them paint themselves.Sertac Akyuz

1 Answers

2
votes

Based on your code sample and comment in it you seem to be first drawing all of your objects into an off-screen Bitmap.

If that is the case then using or not using of Clip regions won't make much of a difference because the bottleneck is in the code that draws all of your objects onto an off-screen bitmap.

The best solution to optimize your code is to only draw those objects that are in your visible area.

So now your main question should be what is the best way to figure out which of your objects are in visible are and which not.

And since in your comments you mentioned that you are making a map-like control I think the best way would be to divide your map into grid composed of multiple sectors and then store your objects data for each sector based on objects position. Each sector would be a separate TList.

This would allow you to only iterate through a small number of your objects based on visible sectors which should improve your performance quite a bit already.

Now what the size of these sectors should be depends on the common size of your objects.

Also when deciding which sectors to draw always make sure you also draw a little past the visible area so that objects whose position is crossing the sectors borders and might be only partially visible are still drawn. If none of your objects dimensions exceed the size of individual sector then drawing all visible sectors plus one line or row outward would suffice.