Craig
Craig

Reputation: 1946

How may I improve the way I am currently applying clipping to a canvas area?

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.

Upvotes: 0

Views: 521

Answers (1)

SilverWarior
SilverWarior

Reputation: 8331

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.

Upvotes: 2

Related Questions