Lazarus

Programming => Graphics and Multimedia => Graphics => Topic started by: furious programming on March 07, 2021, 10:40:01 pm

Title: Blocking th canvas while rendering — is it possible?
Post by: furious programming on March 07, 2021, 10:40:01 pm
Suppose there are few bitmaps. A portion of each of these bitmaps is copied to the window canvas with the Form.Canvas.CopyRect. Every time I run CopyRect or paint anything else on the window canvas (text, lines, etc.), the window canvas is repainted and I can immediately see the changes on my monitor.

I don't like it, I don't want this behavior. I would prefer the window canvas to be repainted once after rendering is finished, and not every time I paint something on it. I am thinking of some kind of blockage of the canvas so that I can block the canvas, paint everything, then unlock it and then the changes will pop onto the screen.

Is such a blockade available and possible to set up?


As a brief explanation, I will add that in my project everything is rendered on the three back buffers and finally these buffers are painted on the window canvas. This makes rendering efficient. Now I'm implementing one more optimization so that instead of painting the entire back buffer onto the window canvas, I will only copy a few small parts of it. Thanks to this, I will save even more computing power.

And just for the time of copying these fragments, I would like to lock the canvas so that the changes visible in the window appear after the copying process is finished, and not after each CopyRect separately.
Title: Re: Blocking th canvas while rendering — is it possible?
Post by: 440bx on March 07, 2021, 11:03:01 pm
I'm not sure that what I'm going to suggest will work in the case you've described but, I think you should have a look at the following:

1. Using LockWindowUpdate
2. WM_SETREDRAW

See MSDN for details.  Maybe one of them can do what you want.

HTH.
Title: Re: Blocking th canvas while rendering — is it possible?
Post by: winni on March 07, 2021, 11:27:44 pm
Hi!

Don't struggle against the default behaviour. Change your design:

Create a TBitmap with the dimensions of the Form.canvas.
Render all you different bitmaps to the bitmap.canvas.

If everything is done then copy the bitmap.canvas to the Form.canvas.

Done

Winni
Title: [SOLVED] Re: Blocking th canvas while rendering — is it possible?
Post by: furious programming on March 07, 2021, 11:52:01 pm
@winni: that's exactly what I got done. The difference, however, is that I have three back buffers (each for a different section of the window) on which I render everything and finally paint them on the window canvas. This solution is effective, but as a rule, 90% of the back buffer area in subsequent frames does not change at all, so it makes no sense to paint all this buffer in the window with Draw, and just copy the updated 10% with few CopyRect calls.

When rendering data on the back buffers, I can determine which parts of it should eventually be copied onto the window canvas. The entire back buffer (the main one) has a size of 650×450 pixels, but it would be enough to copy a few fragments from it in sizes such as 50×50 or 100×20 pixels, which should take less time for the processor.


@440bx: looks interesting, thank you for the hint — I'll check it out.

Edit: I checked it — LockWindowUpdate do what I need. But the problem is that flipping back buffers to the window canvas take more time than without locking (probably because the whole window is re-rendered after unlocking, not the updated regions). In summary, my assumptions was incorrect — without locking the window, flipping buffers is faster.
Title: Re: Blocking th canvas while rendering — is it possible?
Post by: circular on March 08, 2021, 07:04:20 pm
You can invalidate only the rectangular areas that change. If you do so in the OnPaint event you can do as if you would draw the whole form but as a matter of fact only the invalidate part will be redrawn.
Title: Re: Blocking th canvas while rendering — is it possible?
Post by: furious programming on March 12, 2021, 12:11:33 pm
I have done it this way that the window class has several methods which are used to render different window elements (not directly — they just call the appropriate methods of the appropriate renderers, passing the window canvas reference in the parameter):

Code: Pascal  [Select][+][-]
  1. type
  2.   TMainForm = class(TForm)
  3.     procedure FormPaint(ASender: TObject);
  4.   {..}
  5.   public
  6.     procedure InvalidateBody();
  7.     procedure InvalidateHeader();
  8.     procedure InvalidateFooter();
  9.     procedure InvalidateVersion();
  10.   public
  11.     procedure InvalidateFrameRate();
  12.     procedure InvalidateFrameLoad();
  13.     {$IFDEF MODE_DEBUG}
  14.     procedure InvalidateSceneKind();
  15.     procedure InvalidateInputMouse();
  16.     procedure InvalidateInputKeyboard();
  17.     {$ENDIF}
  18.   private
  19.     {$IFDEF MODE_RELEASE}
  20.     FShowFrameRate: Boolean;
  21.     {$ENDIF}
  22.   end;

The OnPaint event triggers all of them, so that the entire window surface is repainted (black fill, background graphic, three buffers, and debug information):

Code: Pascal  [Select][+][-]
  1. procedure TMainForm.FormPaint(ASender: TObject);
  2. begin
  3.   Core.Renderers.Ground.RenderGround(Canvas);
  4.  
  5.   if Core.Looper.Started then
  6.   begin
  7.     InvalidateBody();
  8.     InvalidateHeader();
  9.     InvalidateFooter();
  10.     InvalidateVersion();
  11.  
  12.     InvalidateFrameRate();
  13.     InvalidateFrameLoad();
  14.  
  15.     {$IFDEF MODE_DEBUG}
  16.     InvalidateSceneKind();
  17.  
  18.     InvalidateInputMouse();
  19.     InvalidateInputKeyboard();
  20.     {$ENDIF}
  21.   end;
  22. end;

In contrast, the thread only calls methods that render three buffers and debug information.

Code: Pascal  [Select][+][-]
  1. procedure TLooper.Invalidate();
  2. begin
  3.   MainForm.InvalidateFrameRate();
  4.   MainForm.InvalidateFrameLoad();
  5.  
  6.   {$IFDEF MODE_DEBUG}
  7.   if Core.Scanner.Scene.Changed then
  8.     MainForm.InvalidateSceneKind();
  9.   {$ENDIF}
  10.  
  11.   MainForm.InvalidateBody();
  12.   MainForm.InvalidateHeader();
  13.   MainForm.InvalidateFooter();
  14.   MainForm.InvalidateVersion();
  15.  
  16.   {$IFDEF MODE_DEBUG}
  17.   MainForm.InvalidateInputMouse();
  18.   MainForm.InvalidateInputKeyboard();
  19.   {$ENDIF}
  20. end;

Due to the fact that only some parts of the window are repainted, rendering a given frame in the window is on average 5-10% faster than repainting the entire window (i.e. calling the OnPaint event using the built-in Invalidate method).

If you want to see how big these three buffers are and where in the window they are rendered, check out the screenshot (attachment). What needs to be repainted in each frame are header, body and footer (the debug data at the top and bottom of the window is not rendered in the release version). Currently, these three buffers are painted on the window canvas simply with Form.Canvas.Draw, then (in debug mode) the frames are painted with Form.Canvas.FrameRect and the text with Form.Canvas.TextOut.

In debug mode, when white frames with titles are also rendered, these frames will flicker occasionally. They are flickering because they are not painted on the back buffer — it is natural. This is no problem (it's just debug mode after all), but it does show that painting anything on the canvas of the window repaints it on the screen. If so, painting three back buffers within one frame (header, body, and footer) repaints the window three times instead of once.

So I am looking for a solution that will block the window repaint option while painting these buffers, because maybe thanks to this I will save some computing power. It would be nice if the lock allows me to paint three buffers and then only repaints the area that has changed. BeginPaint and EndPaint sound interesting.
TinyPortal © 2005-2018