Recent

Author Topic: Faster Graphics?  (Read 17131 times)

wotanica

  • Guest
Faster Graphics?
« on: September 23, 2010, 12:50:43 am »
First let me congratulate those that have coded the graphics subsystem in Lazarus. It's rare to find such complete implementation of EVERYTHING in one library. I have been going over the classes and units these past two days - and man you guys have been busy!  :)

But I am a bit worried about graphics performance in my Lazarus components.
I coded a small image viewer (code here: http://delphimax.wordpress.com/2010/09/20/platform-independent-image-component-for-lazarus/) but the speed of Draw() and Copy() (a.k.a "blitting") and Fillrect to the background is well... slow to say the least (on the mac - not so much on windows).

The same code runs flawlessly on Delphi with no lagging (i use winapi bitblt in delphi), but on my mac - wich is a very fast and powerfull machine, it lags behind in higher resolutions.

The natural solution would be to backbuffer the loaded image in a DIB and then use blitting all the way -- but is bitblt emulated in Lazarus? I seem to remember someone saying that. If so, does it call a pixelwriter for each pixel to copy - or does it copy data row-by-row? Is it a wrapper for a call to the native OS, or implemented in pure pascal?

Also, I noticed in the pixmap unit that the Canvas parameter is passed as static rather than constant. That's a lot of CPU overhead for a parameter that is never altered, it will be copied for each call (!) It should be const, especially the procedures that are called hundreds if not thousands of times (such as pixelwriters).

You can also gain some speed by passing parameters in records, and it doesnt make the code harder to maintain either, for example:

Code: [Select]
Type

PDrawLineInfo = ^TDrawLineInfo;
TDrawlineInfo = Record
  ldiFormat:TPixelFormat;
  ldix1:Integer;
  ldiy1:Integer;
  (.. etc..)
End;

(* The method below has little stack penalty and the compiler
 will probably use a register when optimizing.
Either way the overhead is avoided which is
critical when it comes to graphics performance *)

Procedure FastDrawLine(Const Info:PDrawLineInfo);
Begin
  (* No testing here. This method should only be called by
      the canvas implementation AFTER everything is OK.
      Including clipping *)
end;

Also passing raw pixeldata as static longwords should be avoided. You can get a speed boost by typecasting the data:

Code: [Select]
(* this is quite slow *)
Procedure WritePixel(MyPixel:TRGBQuadData;TargetAddr:TUniPTR);
Begin
(.. do stuff ..)
end;

(* This is faster when called hundreds of times.
It will be even faster by passing an info-record pointer
if more variables are required *)
Procedure WritePixel32(Const MyPixel;var Target);
Begin
   TRGBQuad(target):=TRGBQuad(mypixel);
end;

Are there any tips to how i can speed up normal canvas drawing? Any equ. to DIBS that are platform independent? There is no hope in hell that i can port over my graphics-editor to Lazarus for Mac & Linux using the normal canvas interface. This is a real bummer for me, I'm sort of banking on Lazarus for Mac development in 2011 :(

Any plans for a revision of the wonderfull graphics sub-system?
« Last Edit: September 23, 2010, 12:35:48 pm by felipemdc »

felipemdc

  • Administrator
  • Hero Member
  • *
  • Posts: 3541
Re: Faster Graphics?
« Reply #1 on: September 23, 2010, 12:11:19 pm »
But I am a bit worried about graphics performance in my Lazarus components. I coded a small image viewer (code here: http://delphimax.wordpress.com/2010/09/20/platform-independent-image-component-for-lazarus/) but the speed of Draw() and Copy() (a.k.a "blitting") and Fillrect to the background is well... slow to say the least (on the mac - not so much on windows).

Your code lacks appropriate buffering. You shouldn't redraw everything in every Paint event. Instead draw to a buffer and in the OnPaint event just copy this bitmap to the screen. Then update the image only when necessary.

Also I am not sure about using TScrollingWinControl as a base for a fast drawing component, I personally always use TCustomControl.

Could you put this example project in a .zip file hosted somewhere?

Quote
The natural solution would be to backbuffer the loaded image in a DIB and then use blitting all the way -- but is bitblt emulated in Lazarus?

I would recommend avoiding the WinAPI compatibility whenever possible. For double buffering just use a standard TBitmap and Canvas.Draw(0, 0, MyBitmap);

Quote
Is it a wrapper for a call to the native OS, or implemented in pure pascal?

It just uses the native API for that. So there can be differences in the end result between platforms.

The solution for having everything the same everywhere is using TLazIntfImage

Quote
Also, I noticed in the pixmap unit that the Canvas parameter is passed as static rather than constant. That's a lot of CPU overhead for a parameter that is never altered, it will be copied for each call (!) It should be const, especially the procedures that are called hundreds if not thousands of times (such as pixelwriters).

Objects are never passed by value, only a pointer is passed, always.

Quote
Are there any tips to how i can speed up normal canvas drawing?

buffering, drawing only the minimum necessary, draw only as much as the user can see (never more then 30Hz) never use TCanvas.Pixels, TLazIntfImage is the solution in some cases, depends on what exactly you are doing.

PS: I modified your text to fix the layouting of the forum.
« Last Edit: September 23, 2010, 12:36:18 pm by felipemdc »

wotanica

  • Guest
Re: Faster Graphics?
« Reply #2 on: September 23, 2010, 02:26:37 pm »
But how can a backbuffer make it faster?
If it's slow now, just copying from a bitmap onto the control canvas, it has become slower still by copying twice (first drawing to a bitmap, then blitting to the controlcanvas). Or are you saying canvas functions on a TBitmap (memory bitmap) is faster than drawing directly to the component surface? Or should i use the cliprect to get a fix on what to blit?

In my code you see:

Code: [Select]
 (* Copy pixels *)
  canvas.CopyRect(dst,FPicture.bitmap.Canvas,src);

Is this the problem? Using canvas as the access medium?
Above the "Canvas" is the control canvas, while the source canvas is, as you see, a TPicture object's child bitmap.

Testing my code: On my blog when you move your mousepointer over the code (see url above) you get some icons in the topright corner, including "view source" as ascii text. Simply copy paste into notepad and save as a .pas file. Then start a new lazarus project, add units to uses clause, create the imageviewer manually - and use FViewer.picture.loadfromfile() to get it working.

Also why TScrollingWinControl?
Well, Graphics32 does it, ImageEn does it, almost every pro componentvendors use this class as a basis (or provide their own version). It felt natural to go for this on Lazarus as well. Why implement scrollbars when they are already included? Well, that was the theory anyways :)

And ofcourse - thank you for helping out :)
« Last Edit: September 23, 2010, 03:12:37 pm by wotanica »

felipemdc

  • Administrator
  • Hero Member
  • *
  • Posts: 3541
Re: Faster Graphics?
« Reply #3 on: September 23, 2010, 03:46:10 pm »
But how can a backbuffer make it faster?

Because then you don't update the image in each repaint, just update it when needed.

Quote
If it's slow now

Define slow. Here is an example of how to execute a speed test: http://wiki.lazarus.freepascal.org/EpikTimer#EpikTimer_as_a_profiler

Quote
Testing my code...

Google for "Free file hosting". 1st match: http://www.mediafire.com/

wotanica

  • Guest
Re: Faster Graphics?
« Reply #4 on: September 23, 2010, 04:10:28 pm »
Your missing my point.

Let's say I paint to a backbuffer. This involves:
  • trigger on resize or scroll
  • Painting background (e.g fillrect with self.color)
  • Copying pixels from FPicture.Bitmap to my backbuffer

Then the paint event executes.
I check the cliprect and copy from the backbuffer to the canvas.
In the current model this results in two (possibly large) copy operations. From picture to backbuffer, and backbuffer to control surface.

All of this makes perfect sense in a non-scrolling component. But the main feature in a scrolling window is, well, scrolling. Which means that the invalidated area will always be (more or less) the entire relative clientrect.

The only way to make that process faster, is to scroll the component-pixeldata (ScrollDC) and just paint the invalidated area. For example, if I scroll 16 pixels downwards, then I would have to move the current content upwards by 16 pixels, and repaint only the bottom-most 16 scanlines.

Hm. Looks like i have to create a scanline scroller through the interfaced bitmap.
Ok. Trying.

felipemdc

  • Administrator
  • Hero Member
  • *
  • Posts: 3541
Re: Faster Graphics?
« Reply #5 on: September 23, 2010, 04:35:42 pm »
Of course that my tips are vague and not necessarily the best solution. One can't really tell without doing a real analysis of your code, profiling and testing multiple solutions. I am just providing alternative ideas.

Still on the topic, for scrolling I usually draw the entire image in my background buffer, and then when scrolling changes, I just copy a different section of it to the screen.

Also note that even if the user is scrolling crazy fast, there is no need to draw the image to the screen faster then 30Hz, as the human eye can't see faster then that.

I think that there are already enough ideas here that you could profile the code and see what makes things faster and what not, and also what part of the code is the slowest.

wotanica

  • Guest
Re: Faster Graphics?
« Reply #6 on: September 23, 2010, 04:49:27 pm »
Of course that my tips are vague and not necessarily the best solution. One can't really tell without doing a real analysis of your code, profiling and testing multiple solutions. I am just providing alternative ideas.

And I am very thankfull for all your tips :)
Thank you for your help, I realize that Lazarus is different from Delphi - hence I ask more :)

I came up with this crude scroller (3 minute coding):
Code: [Select]
Procedure ScrollRasterY(Const Obj:TLazIntFImage;Factor:Integer);
  var
    x: Integer;
    target:Integer;
    src,dst:Pointer;
    FRowSize:Int64;
  Begin
    if abs(Factor)<Obj.Height then
    Begin
      FRowSize:=Int64(obj.GetDataLineStart(1)) - Int64(obj.GetDataLineStart(0));
      if FRowSize<0 then
      FRowSize:=abs(FRowSize);

      Caption:='RowSize=' + IntToStr(FRowSize);

      if (Factor<0) then
      Begin
        (* scroll upwards *)

          Factor:=abs(factor);
          Target:=0;
          for x:=Factor to Obj.Height do
          Begin
            dst:=obj.GetDataLineStart(Target);
            src:=obj.GetDataLineStart(x-1);
            Move(src^,dst^,FRowSize);
            inc(Target);
          end;

      end else
      if (Factor>0) then
      Begin
        (* scroll downwards *)
      end;

    end;
  end;                          


eny

  • Hero Member
  • *****
  • Posts: 1588
Re: Faster Graphics?
« Reply #7 on: September 23, 2010, 04:56:42 pm »
I use the Vampyre imaging library for fast image painting.
I have built a photo mgmt application that uses TDrawGrid to display the images.
I use the Vampyre functions to directly draw on the TDrawGrid canvas and with smooth scrolling enabled it looks really, wel... smooth  :D
All posts based on: Win10 (Win64); Lazarus 1.8.0 'stable' (#56594 win64) unless specified otherwise...

wotanica

  • Guest
Re: Faster Graphics?
« Reply #8 on: September 23, 2010, 05:22:36 pm »
I use the Vampyre imaging library for fast image painting.
I have built a photo mgmt application that uses TDrawGrid to display the images.
I use the Vampyre functions to directly draw on the TDrawGrid canvas and with smooth scrolling enabled it looks really, wel... smooth  :D

Ah! Now that is interesting.
Does it work on the mac or is it windows only?

Do you have a drawgrid example i could look at?

eny

  • Hero Member
  • *****
  • Posts: 1588
Re: Faster Graphics?
« Reply #9 on: September 23, 2010, 05:29:38 pm »
Does it work on the mac or is it windows only?
I've only used it for Windows, but according to the homepage it's X-platform.

Quote
Do you have a drawgrid example i could look at?
Not at hand, but it's pretty straightforward: on every TDrawGrid.OnDrawCell message I draw the right image (i.e. photo) with a VIL function.
The help info in the lib should be enough to get you going.

Note that I had a problem compiling it the last time I upgraded Lazarus.
But I posted a fix for that problem on the VIL forum.
« Last Edit: September 23, 2010, 05:37:38 pm by eny »
All posts based on: Win10 (Win64); Lazarus 1.8.0 'stable' (#56594 win64) unless specified otherwise...

wotanica

  • Guest
Re: Faster Graphics?
« Reply #10 on: September 23, 2010, 11:34:49 pm »
The help info in the lib should be enough to get you going.

Thank you so much! Just what i was looking for :)