Jump to content
pcoder

How to break up an OnPaint event?

Recommended Posts

Sometimes my draw in OnPaint (WM_Paint) is very slow (kind of UI freezing).
Now I would prefer to exit the paint handler earlier (based on time measurement (getTickCount))

and continue the draw in subsequent OnPaint events (like progressive drawing).

Is that possible under Windows? And how can I trigger the paint events?

  • Like 1

Share this post


Link to post
6 hours ago, pcoder said:

Sometimes my draw in OnPaint (WM_Paint) is very slow (kind of UI freezing).

Then you are likely doing too much work in your drawing code.  Or you are doing other things besides just drawing.  Painting should be a quick task. Draw only what can be seen for the current state of your existing data, nothing more.  Do not manage your data/UI in any way other than drawing.  If you need to manipulate your data/UI, that has to be done outside of a paint cycle.

6 hours ago, pcoder said:

Now I would prefer to exit the paint handler earlier (based on time measurement (getTickCount))

and continue the draw in subsequent OnPaint events (like progressive drawing).

That is not how UI painting works.  You have to draw whatever your current state represents.  After the painting is finished, if a state change occurs, then you can trigger a repaint to draw the updated state.

6 hours ago, pcoder said:

Is that possible under Windows?

No.  Every paint cycle is a complete redraw from scratch.  For each window/control that is being painted, you have to draw it entirely in a single cycle. And then again in the next cycle. And so on.

6 hours ago, pcoder said:

And how can I trigger the paint events?

You can Invalidate() an individual form/control to trigger a new paint cycle for it, but that requires you to redraw it entirely from scratch when that next cycle begins,

Edited by Remy Lebeau
  • Like 1

Share this post


Link to post

If the drawing code is too time consuming for being called in OnPaint, you might consider drawing to a bitmap when something changed and draw that bitmap in OnPaint.

  • Like 4

Share this post


Link to post
15 hours ago, pcoder said:

Sometimes my draw in OnPaint (WM_Paint) is very slow (kind of UI freezing).

Maybe you are doing computation in the OnPaint event. Decouple computation from presentation (the drawing).

For the drawing itself, avoid drawing pixel by pixel on screen. Avoid having hundreds of components to draw. Avoid drawing invisible parts.

 

Painting in a bitmap and then blasting the bitmap on screen could be faster than painting on screen directly.

  • Like 2

Share this post


Link to post

Thanks for all!  I will use a bitmap and draw into it whenever I have a time slot.
The main problem is a real slow color-font (text draw 100+ times slower than with other color-fonts).
I will also try distribution over several WM_Paint (each displays whole (but possibly unfinished) bitmap)),
will see... (should look better than freeze). As Remy said, the framework is not prepared for this,

so I will extend the wmPaint method to trigger the next paint. Initial tests have shown that this works.

procedure TmyControl.WMPaint(var Message: TWMPaint);
begin
wantMorePaintTime := false;
inherited;
if wantMorePaintTime then
    postMessage( handle, iwm_wantPaintTime, 0, 0); //  handler calls self.invalidate;
end;

 

Share this post


Link to post
35 minutes ago, pcoder said:

The main problem is a real slow color-font (text draw 100+ times slower than with other color-fonts).

Of course they are dead slow.

 

I would suggest to build your own class that handle drawing a a single text string on its own small BMP (sized according to the text and the font), so there texts strings will have their own BMP that will invalidate only when you change the text, and simple draw procedure needing canvas and coordinates ( and may be a rect for clipping if needed), this what i do and this will make things faster enough for drawing on your own BMP or directly on the needed canvas.

Share this post


Link to post

Each textline needs up to 2 seconds :), varies on whether DirectWrite has found a way to cache something.

I also have found that DirectWrite textdraw to bitmap DC is slower than DirectWrite textdraw to window DC.

But I'm still happy with the progressive display now. And users have the option to select a different font:)

 

The challenge (I should have mentioned it earlier) was, the user clicks a button to generate a different image on display.
Thus a waiting time between button click and resulting display is impossible to avoid, regardless of internal concept.

Edited by pcoder

Share this post


Link to post

Something that is often overlooked is to limit the redraw frequency. If you redraw the display on every update and there are many updates per second, you may save a lot of time on triggering the invalidate at a capped rate, i.e. not do a full redraw every time, but update once every fixed time interval.  Unless you are doing video processing - a redraw at maximum twice per second should be more than sufficient?

  • Like 2

Share this post


Link to post

Yes, in most cases more than sufficient.

Edited by pcoder

Share this post


Link to post
5 hours ago, Lars Fosdal said:

Something that is often overlooked is to limit the redraw frequency. If you redraw the display on every update and there are many updates per second, you may save a lot of time on triggering the invalidate at a capped rate, i.e. not do a full redraw every time, but update once every fixed time interval.  Unless you are doing video processing - a redraw at maximum twice per second should be more than sufficient?

But overclocking is fun!

Using paint; override; with say TShape a tGraphicalControl allows using inherited to draw existing shape and add text as needed. Not using inherited means drawing your own polygon. The sample simply draws over the TShape control Ok But the focus rect yielded some relics.

In the past it was necessary to exit a timer event when busy or in debug mode. Even with example switches the following stacks up clicks events. I put a call to Button8click in the formPaint and it ran but slowly. 

 

Otherwise just invalidate the controls as needed. 

var Button8clickIsBusy: Integer = 0;
var OneShot: Boolean = False;

procedure TForm1.Button8Click(Sender: TObject);
const
  Red = clRed;
  Green = clGreen;
  Colors: Array of Tcolor = [clGreen,clRed,clBlue,clYellow];

var
  sw: TStopwatch;
  sX: string;

         function RandomFontColor: Tcolor;
         begin
           var Clr := High(Colors);
           var R := Random(Clr);
           Result := Colors[R];
         end;

begin
  sw.Reset;
  sw.Start;
  If Oneshot then Exit;
  If Button8clickIsBusy > 0
    then begin
           Canvas.TextOut(10,10,'Drawing on Canvas');
           Caption := 'Busy ' + Button8clickIsBusy.ToString;
           Exit;
         end;
  //Canvas.Unlock; // may need
  Inc(Button8clickIsBusy);
  OneShot := True;

  const
    L = Shape1.Left + 5;
  const
    T = Shape1.Top + 5;
  var
    SP: Integer := 0;
  var
    X := -50;
  //Brush.Color := clmoneygreen;
  //Canvas.FillRect(BoundsRect);  //hosed by style and not max windowstate
  Repeat
    sX := X.ToString;
    Canvas.Font.Color := RandomFontColor;
    Canvas.Font.Size := X;
    Canvas.TextOut(20, 20, sX);
    canvas.DrawFocusRect(Rect(22,22,X+X-3,(X-3)*2));

    //on shape
    Canvas.Font.Color := RandomFontColor;
    Canvas.Font.Size := -X;
    Canvas.TextOut(L, T, sX);
    Sleep(20);
    Canvas.Font.Color := clWindowFrame;
    canvas.DrawFocusRect(Rect(22,22,X+X-3,(X-3)*2));

    Inc(X);
  Until x > 50;

  Button8clickIsBusy := 0;
  sw.Stop;

  Canvas.TextOut( 10,100,'Done et='+ sw.ElapsedMilliseconds.ToString);
  Caption := 'Busy ' + Button8clickIsBusy.ToString;
  Oneshot := False;
end;

 

 

 

 

  

 

 

 

 

    

 

Share this post


Link to post
10 hours ago, Kas Ob. said:

Of course they are dead slow.

SVG color fonts are probably slow. All the other color font types (COLR, SBIX, CBDT) should perform more or less on par with regular OpenType fonts.

Share this post


Link to post

Most common fonts are acceptable, but even "Noto Color Emoji" (2.038) can be very slow on certain systems.
The file description displays COLRv1, but I don't know if DirectWrite actually uses this format.

Share this post


Link to post
3 hours ago, pcoder said:

Most common fonts are acceptable, but even "Noto Color Emoji" (2.038) can be very slow on certain systems.

Probably because of its size. Most Noto fonts are huuuge.

 

Noto Color Emoji contains both COLR and SVG data. The COLR data is ~1Mb while the SVG data is ~18Mb...

 

3 hours ago, pcoder said:

The file description displays COLRv1, but I don't know if DirectWrite actually uses this format.

I'm not sure what you are saying here.
Microsoft is the primary author of the COLR format and DirectWrite supports all the common color glyph formats:
https://learn.microsoft.com/en-us/windows/win32/directwrite/color-fonts

 

However, the client has to specify what format to use during Shaping and if the client asks for SVG glyphs then that is what it will use.

https://learn.microsoft.com/en-us/windows/win32/api/dcommon/ne-dcommon-dwrite_glyph_image_formats

 

You can try subsetting the font to exclude the SVG data and see if that makes a difference (apart from the 18 Mb data saved 🙂)

  • Like 1

Share this post


Link to post

COLRv1 was just an info shown in the file properties dialog.
The glyph rendering looks vector based and has color gradients, so SVG or COLRv1.

 

Format-selection is for lower-level handling, but I use only RenderTarget.drawTextLayout() with D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT.
It would be useful here (also for end-users) if DirectWrite would offer a parameter to specify a preferred format.
If possible I try to avoid lower-level handling, even more code according to the preview

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×