Jerky animation (windows) lazarus / 39-Graphics ---------------------------------------------------------------------- hedgehog · 2025-12-26 09:01 Hello everyone! I made a pretty simple animation: a ball spins around the center of the screen. The rendering is done in three different ways: 1) LCL 2) BGRA 3) Direct2D In all three cases, I see that the animation isn't truly smooth. I see the ball twitching sometimes, and it really annoys me! Since I tried three completely different rendering methods, I can conclude that this isn't the problem (considering that Direct2D significantly reduces CPU load). I need help: how do I make the animation smooth? What am I doing wrong? What's important am I missing? My guess: 1) The GetTickCount64 function isn't accurate enough. 2) The frame is displayed on the screen at different intervals after the OnPaint event. ---------------------------------------------------------------------- Handoko · 2025-12-26 09:51 Have you tried doing the same using OpenGL? I am not familiar with DirectX, not much I can say. My guess is you didn't enable double buffer feature. In OpenGL we use SwapBuffer command so clearing the scene is done behind the scene. ---------------------------------------------------------------------- hedgehog · 2025-12-26 10:32 Hi, Handoko Double buffering is enabled. And now I think that might be the problem. It feels like the old buffer is sometimes being drawn to the screen. But I don't have the knowledge to investigate. ---------------------------------------------------------------------- TBMan · 2025-12-26 14:59 Quote from: hedgehog on December 26, 2025, 11:32:23 amHi, Handoko Double buffering is enabled. And now I think that might be the problem. It feels like the old buffer is sometimes being drawn to the screen. But I don't have the knowledge to investigate. There has to be a way to control it. I do things like this in games: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i Procedure repaint;begin   if Updatescreen then // global boolean   begin        nextactivepage;            // select canvas to draw on        doAnimationOrDraw;     //  do your stuff        NextVisualPage;          // show it        UpdateScreen := false;  // don't redraw if nothing happened so reset the toggle   end;end;  But I'm using my own code. This is why I avoid using someone else's boiler plate libraries. So you have to dig into the graphics library and learn it. If it helps, wrap the code in procedures that you will remember. ---------------------------------------------------------------------- Gigatron · 2025-12-26 16:01 Hi Here is a version using glCanvas, it's quick coded; Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit Unit1; {$mode objfpc}{$H+} interface uses  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls,  OpenGLContext, glCanvas, Math; type  { TForm1 }  TForm1 = class(TForm)    OpenGLControl1: TOpenGLControl;    Timer1: TTimer;    procedure FormCreate(Sender: TObject);    procedure OpenGLControl1Paint(Sender: TObject);    procedure Timer1Timer(Sender: TObject);  private    angle: single;  public  end; var  Form1: TForm1; implementation {$R *.lfm} { TForm1 } procedure TForm1.FormCreate(Sender: TObject);begin  glInit(OpenGLControl1);  glEnableVSync(true);  angle := 0;end; procedure TForm1.OpenGLControl1Paint(Sender: TObject);var  xs, xc: single;  cx, cy,rd: integer;begin  cx := OpenGLControl1.Width div 2;  cy := OpenGLControl1.Height div 2;  rd := 300;  glFillScreen(OpenGLControl1, 0.1, 0.1, 0.1, 1.0);   sincos(angle, xs, xc);  // red circle before then white circle  glCircle(cx, cy, rd, 100, 1.0, 0.0, 0.0, 1.0, true);  glCircle(cx + Round(xs * (rd-40)), cy + Round(xc * (rd-40)), 40, 50, 1.0, 1.0, 1.0, 1.0, true);   OpenGLControl1.SwapBuffers;end; procedure TForm1.Timer1Timer(Sender: TObject);begin  angle := angle - 0.03;  if angle > 2 * pi then angle := angle - 2 * pi;   OpenGLControl1.Invalidate;end; end.   And modified code with your variables; Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit Unit1; {$mode objfpc}{$H+} interface uses  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls,  OpenGLContext, glCanvas, Math; type  { TForm1 }  TForm1 = class(TForm)    OpenGLControl1: TOpenGLControl;    Timer1: TTimer;    procedure FormCreate(Sender: TObject);    procedure OpenGLControl1Paint(Sender: TObject);    procedure Timer1Timer(Sender: TObject);  private    angle: single;  public  end; var  Form1: TForm1; implementation {$R *.lfm} { TForm1 } procedure TForm1.FormCreate(Sender: TObject);begin  glInit(OpenGLControl1);  glEnableVSync(true);  angle := 0;end; procedure TForm1.OpenGLControl1Paint(Sender: TObject);var  xs, xc, dist: single;  cx, cy, wh: integer;begin  cx := OpenGLControl1.Width div 2;  cy := OpenGLControl1.Height div 2;  wh := Min(OpenGLControl1.Width, OpenGLControl1.Height);   glFillScreen(OpenGLControl1, 0.1, 0.1, 0.1, 1.0);   sincos(angle, xs, xc);   dist := 2 * wh / 5;   // Cercle red puis white  glCircle(cx, cy, wh div 2, 100, 1.0, 0.0, 0.0, 1.0, true);  glCircle(cx + Round(dist * xc), cy + Round(dist * xs),           wh div 10, 50, 1.0, 1.0, 1.0, 1.0, true);   OpenGLControl1.SwapBuffers;end; procedure TForm1.Timer1Timer(Sender: TObject);begin  angle := angle + 0.03;  if angle < -2 * pi then angle := angle + 2 * pi;   OpenGLControl1.Invalidate;end; end. Hope this will help; Regards ---------------------------------------------------------------------- TBMan · 2025-12-26 17:10 This is the "nextactivepage" Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i OpenGLControl1.SwapBuffers;   And this looks like the message to paint the visual screen Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   OpenGLControl1.Invalidate; ---------------------------------------------------------------------- Roland57 · 2025-12-26 19:11 @hedgehog Here is a (simplified) version of your project, using TApplicationsProperties. Inspired by this example. ---------------------------------------------------------------------- hedgehog · 2025-12-27 12:28 Thanks, guys! Gigatron, your example works great! I'll take a closer look at OpenGL. I added an FPS counter to your code. And here's what I noticed: If sync is enabled: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i glEnableVSync(true);the FPS is 60 (as it should be). If sync is disabled: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i glEnableVSync(false);the FPS is 63-64 (and the ball's movement is jerky). In other words, the rendering rate doesn't match the screen refresh rate. So, the cause of the jerky animation has been identified! ---------------------------------------------------------------------- Boleeman · 2026-01-01 12:37 Not as smooth as the hedhog glEnableVSync(true) using Gigatron's glcanvas version, but I gave it a shot with Tbgrabmp. ---------------------------------------------------------------------- hedgehog · 2026-01-02 14:00 Hi, Boleeman! Roland57 also suggested this method (OnIdle). While it allows for smoother animation, it has one (in my opinion, critical) drawback: it's very processor intensive. ---------------------------------------------------------------------- Roland57 · 2026-01-03 20:37 Quote from: hedgehog on January 02, 2026, 03:00:20 pmWhile it allows for smoother animation, it has one (in my opinion, critical) drawback: it's very processor intensive. Yes, it's true. I checked (under Linux): the program uses more than 90% of the CPU. ---------------------------------------------------------------------- Boleeman · 2026-01-03 22:26 Yes I noticed that too when making TBgrabmp animations with anti- aliased ellipses, the animations were quite slow and CPU intensive, so I used normal non-aliased ellipses. Java and JavaScript animation programs with anti-aliased ellipses don't seem to have that problem (bigger playback buffer in Web browser?). With Lazarus animation programs that was a big problem so I needed to downgrade by using non- aliased ellipses and normal rectangles instead of rounded rectangles. Looks like GigaTron's glCanvas is the solution for animations containing anti-aliased ellipses (and possibly round rectangles?). Could glCanvas possibly handle round rectangles animations better? I converted a JavaScript animation code ages ago with filled round squares (which worked really well) but in the Lazarus version the unfilled round rectangles rotated with a stilted action (similar to the anti-aliased ellipses) and seemed to be very CPU intensive. I wonder if there is a way for Circular to add an extra animation buffer (when animating) to anti-aliased ellipses and round rectangles in his excellent TBgrabmp package and perhaps to Canvas2D? Also if the JavaScript animations are less jerky (due to being less CPU intensive) could perhaps TBgrabmp make use of the same API's that JavaScript uses for anti-aliased ellipses and round rectangles so they are drawn more quickly? ---------------------------------------------------------------------- hedgehog · 2026-01-04 06:19 Hi all. The main problem is that the LCL updates the buffer 64 times per second, while the video driver (in my case) updates the screen 60 times per second. Therefore, collisions occur 4 times per second, which are visible visually (in my demo, the ball twitches). After spending several hours reading forums, I realized that the problem is hidden deep within the LCL. If we use the OnIdle event to initiate redrawing, the buffer redraws one after another without any delay (naturally, this places a significant load on the CPU). Our application draws (let's say) 300 frames every second, of which only 60 are actually displayed. 20% efficiency, like a steam locomotive. However, many more collisions occur, but they are smaller in magnitude. I don't think this is the goal we should strive for. When using hardware acceleration (Direct2D for Windows), you'll simply render at 500 frames per second instead of 300. So, the animation will be smooth if you synchronize the LCL buffer redraw with the video driver frame updates. OpenGL accomplishes this using VSync. ---------------------------------------------------------------------- Gigatron · 2026-01-04 21:26 Quote from: Boleeman on January 03, 2026, 11:26:47 pmYes I noticed that too when making TBgrabmp animations with anti-aliased ellipses, the animations were quite slow and CPU intensive, so I used normal non-aliased ellipses. Java and JavaScript animation programs with anti-aliased ellipses don't seem to have that problem (bigger playback buffer in Web browser?). With Lazarus animation programs that was a big problem so I needed to downgrade by using non-aliased ellipses and normal rectangles instead of rounded rectangles. Looks like GigaTron's glCanvas is the solution for animations containing anti-aliased ellipses (and possibly round rectangles?). Could glCanvas possibly handle round rectangles animations better? I converted a JavaScript animation code ages ago with filled round squares (which worked really well) but in the Lazarus version the unfilled round rectangles rotated with a stilted action (similar to the anti-aliased ellipses) and seemed to be very CPU intensive. I wonder if there is a way for Circular to add an extra animation buffer (when animating) to anti-aliased ellipses and round rectangles in his excellent TBgrabmp package and perhaps to Canvas2D? Also if the JavaScript animations are less jerky (due to being less CPU intensive) could perhaps TBgrabmp make use of the same API's that JavaScript uses for anti-aliased ellipses and round rectangles so they are drawn more quickly? Hi and Happy new year Here is the gl version hope it's ok; Regards ---------------------------------------------------------------------- Boleeman · 2026-01-05 01:09 Wow GigaTron. Thanks GigaTron for helping out with that animation. That animates so smoothly (Like the fade in and out effect as well) Probably even better than the JavaScript version I came across. I kinda gave up on TBgrabmp to do animations with round rectangles and anti-aliased ellipses. Was getting frustrated with animations, but for still renderings TBgrabmp gives nice results. Looks like from now on I will try to make use of your great GLCanvas for any animations and CPU intensive renderings (Like the Glynn set fractal I just uploaded yesterday), as it works really well. Happy New Year GigaTron. Once again, thanks for illustrating your great Glcanvas control. Just finished Lulu's OpenGL conversion of my TBgrabmp Glynn Fractal. Rendering with TBgrabmp took ages, whereas the OGLCScene OpenGL was instantaneous. Looks like OpenGL is the go for CPU intensive programs. I also tried multi-threading but struggled to get a working version happening (the Wikis and other web page sources were confusing). ---------------------------------------------------------------------- hedgehog · 2026-01-05 12:13 This is an impressive demonstration of OpenGL's capabilities. On my computer, the process took up 5% of the CPU and 6% of the GPU. These are quite respectable results. I have a question: If OpenGL can perform frame rate synchronization, then perhaps this capability can be added to LCL? ---------------------------------------------------------------------- Roland57 · 2026-01-11 07:59 Here is something else I tried: an animation using PTCPas and Cairo. It is based on an example shipped with PTCPas. I wasn't sure how I could connect the pixel buffer to a Cairo surface, but fortunately the first thing I tried worked.  The result is very satisfying (to my eyes), but the program uses 60% of the CPU. The Smoothris demo uses 80%. Maybe we could make a report to PTCPas author about this. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i {   ptc_cairo_01.pas  Example of animation using PTCPas and Cairo  Based upon /examples/buffer.pp} program BufferExample; {$MODE objfpc} uses  ptc, cairo; var  console: IPTCConsole;  format: IPTCFormat;  width, height: Integer;  pixels: PUint32 = nil;  (*  x, y, r, g, b: Integer;  i: Integer;  *)  sf: pcairo_surface_t;  cr: pcairo_t;  angle: double;  begin  try    try      { create console }    console := TPTCConsoleFactory.CreateNew;       { create format }    format := TPTCFormatFactory.CreateNew(32, $00FF0000, $0000FF00, $000000FF);       { open the console }      console.Open('PTCPas & Cairo example', 480, 480, format);            { get console dimensions }      width := console.Width;      height := console.Height;       { allocate a buffer of pixels }      pixels := GetMem(width * height * SizeOf(Uint32));      FillChar(pixels^, width * height * SizeOf(Uint32), 0);            sf := cairo_image_surface_create_for_data(pbyte(pixels), CAIRO_FORMAT_ARGB32, width, height, cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width));      cr := cairo_create(sf);            cairo_scale(cr, width, height);      cairo_translate(cr, 1/2, 1/2);            angle := 0;            { loop until a key is pressed }      while not console.KeyPressed do    begin        angle := angle + PI/500;        (*        { draw random pixels }        for i := 1 to 100 do        begin          { get random position }          x := Random(width);          y := Random(height);           { get random color }          r := Random(256);          g := Random(256);          b := Random(256);         { draw color [r,g,b] at position [x,y] }          pixels[x + y * width] := (r shl 16) or (g shl 8) or b;        end;        *)              cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);        cairo_paint(cr);                cairo_arc(cr, 0.0, 0.0, 1/2, 0.0, 2*PI);        cairo_set_source_rgb(cr, 1.0, 0.0, 0.0);        cairo_fill(cr);                 cairo_arc(cr, 1/4 * Cos(angle), 1/4 * Sin(angle), 1/4, 0.0, 2*PI);        cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);        cairo_fill(cr);                { load pixels to console }        console.Load(pixels, width, height, width * 4, format, TPTCPaletteFactory.CreateNew);         { update console }        console.Update;      end;            cairo_destroy(cr);      cairo_surface_destroy(sf);          finally      { free pixels buffer }      FreeMem(pixels);      if Assigned(console) then        console.close;    end;  except    on error: TPTCError do      { report error }      error.report;  end;end. ---------------------------------------------------------------------- Fred vS · 2026-01-11 19:23 Quote from: Roland57 on January 11, 2026, 08:59:15 amThe result is very satisfying (to my eyes), but the program uses 60% of the CPU. The Smoothris demo uses 80%. Maybe we could make a report to PTCPas author about this. Hello Roland. WoW once again.  But on my Noble Ubuntu system, the program uses 1.9% of the processor, which seems very reasonable to me... (see picture). ---------------------------------------------------------------------- Roland57 · 2026-01-12 06:19 Quote from: Fred vS on January 11, 2026, 08:23:07 pmBut on my Noble Ubuntu system, the program uses 1.9% of the processor Interesting to know. Thank you for the feedback Fred. ---------------------------------------------------------------------- hedgehog · 2026-01-26 15:21 It looks like Delphi has solved the problem of jerky animation. They implemented the Display Link Service. Quote from the Embarcadero website: QuoteStarting with RAD Studio 13.0, the Display Link Service for FMX is now the central engine for all application animations. This frame-accurate system leverages the display’s actual refresh rate to synchronize and drive all UI updates and animations. <...> the service ensures that animation processing is tightly aligned with VSync events, resulting in smoother and more consistent visuals across all platforms.https://docwiki.embarcadero.com/RADStudio/Florence/en/Fi reMonkey_Animation_Effects Is it possible to do this in Lazarus? ---------------------------------------------------------------------- hedgehog · 2026-05-08 16:45 So, I continued my research. With the help of D2D, and some magic, I got a smooth animation on the form. I redefined the WMPaint method, and it broke the standard redraw. Now you can see a smooth 60 fps animation. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.WMPaint(var Message: TLMPaint);begin  if ([csDestroying,csLoading]*ComponentState<>[]) or (not HandleAllocated) then    exit;  FCanvasD2D.BeginPaint;  try    Render(FCanvasD2D);  finally    FCanvasD2D.EndPaint;  end;end; I used this article on the Embarcadero website. https://docwiki.embarcadero.com/RADStudio/Athens/en/Using_the_Direct2D _Canvas#Using_Direct2D_canvas_exclusively