Autor Téma: Náhrada za DoubleBuffering?  (Přečteno 844 krát)

Offline age.new

  • Plnoletý
  • ***
  • Příspěvků: 234
  • Karma: 0
Náhrada za DoubleBuffering?
« kdy: 24-04-2020, 09:38:37 »
Vážená komunito,

zlobí mě neustálé chyby kreslení FMX komponent (TShape) i obyčejné kreslení na Canvas (layoutu, panelu). Nejčastěji se setkávám s problémy, kdy prvek přechází z neviditelného na viditelný (a naopak). Vznikají duchové, nedokreslené části atp. Samozřejmě vždy kreslím v Paint proceduře s try-finally a BeginScene a EndScene. Zkoušel jsem i BeginUpdate a EndUpdate u formulářů, ale bez viditelného zlepšení.

Ve VCL se toto většinou vyřešilo pomocí DoubleBuffering, které ale bylo z FMX odebráno. Je zde nějaký jiný způsob jak donutit FMX, aby kreslilo "správně"?

Ještě bych měl drobný dotaz ohledně ScaledLayoutu. Tato komponenta funguje dobře, akorát bych potřeboval upravit zaokrouhlovací logiku. Změnou velikosti layoutu se vše překreslí, ale vlivem zaokrouhlení se občas z 1px čáry stane 0,3px. Jindy má čára stín, protože šířka vyjde na 1,3px... je možné toto nějak ovlivnit? Nastavit celočíselné zaokrouhlování.

Děkuji.
« Poslední změna: 24-04-2020, 09:48:13 od age.new »

Offline Radek Červinka

  • Administrátoři
  • Padawan
  • *****
  • Příspěvků: 2730
  • Karma: 105
    • Verze Delphi: D2007, DXE + 2 poslední
    • O Delphi v češtině
Re:Náhrada za DoubleBuffering?
« Odpověď #1 kdy: 24-04-2020, 10:29:55 »
Myslim si, ze neco delas spatne. Ale tezko radit, kdyz neni kod.
Ukaz nejakou minimalni vec, ktera se da zkusit a pak se uvidi.
Vypln si v profilu verzi Delphi jinak to nema cenu zkouset.
Embarcadero MVP - Czech republic

Offline age.new

  • Plnoletý
  • ***
  • Příspěvků: 234
  • Karma: 0
Re:Náhrada za DoubleBuffering?
« Odpověď #2 kdy: 24-04-2020, 10:47:56 »
Kdybych mohl dát nějakou rozumnou část kódu, udělal bych to. Ale bez objektů, dalších unitů a formulářů by to bylo nepochopitelné. Kód by ani nešel přeložit a otestovat...

Pokusím se napsat malou ukázku, aby to bylo snadno k otestování.

Offline Radek Červinka

  • Administrátoři
  • Padawan
  • *****
  • Příspěvků: 2730
  • Karma: 105
    • Verze Delphi: D2007, DXE + 2 poslední
    • O Delphi v češtině
Re:Náhrada za DoubleBuffering?
« Odpověď #3 kdy: 24-04-2020, 11:07:36 »
Verze Delphi? Platforma?
Embarcadero MVP - Czech republic

Offline age.new

  • Plnoletý
  • ***
  • Příspěvků: 234
  • Karma: 0
Re: Náhrada za DoubleBuffering?
« Odpověď #4 kdy: 24-04-2020, 11:44:19 »
Vytvořil jsem ukázkovou aplikaci a na té to není moc dobře vidět. Je to možná i tím, že projekt je obsáhlejší a na hlavním formu jsou frejmy a layouty vzájemně se překrývající... nevím. U rychlé animace je občas možné zahlédnout posun. Jakoby to nakreslilo polovinu canvasu a zbytek posunulo (jakoby to byl další snímek). Kód je zde:

Kód: Delphi [Vybrat]
  1. object Form1: TForm1
  2.   Left = 0
  3.   Top = 0
  4.   BorderIcons = [biSystemMenu]
  5.   Caption = 'Form1'
  6.   ClientHeight = 600
  7.   ClientWidth = 1000
  8.   FormFactor.Width = 320
  9.   FormFactor.Height = 480
  10.   FormFactor.Devices = [Desktop]
  11.   OnCreate = FormCreate
  12.   OnDestroy = FormDestroy
  13.   DesignerMasterStyle = 0
  14.   object ScaledLayout1: TScaledLayout
  15.     Align = Fit
  16.     OriginalWidth = 1000.000000000000000000
  17.     OriginalHeight = 600.000000000000000000
  18.     Size.Width = 1000.000000000000000000
  19.     Size.Height = 600.000000000000000000
  20.     Size.PlatformDefault = False
  21.     OnPaint = ScaledLayout1Paint
  22.   end
  23.   object Label1: TLabel
  24.     Align = Client
  25.     StyledSettings = [Family, Size, Style]
  26.     Size.Width = 1000.000000000000000000
  27.     Size.Height = 600.000000000000000000
  28.     Size.PlatformDefault = False
  29.     TextSettings.FontColor = claRed
  30.     TextSettings.VertAlign = Leading
  31.     Text = 'Label1'
  32.   end
  33. end
  34.  
  35. unit Unit1;
  36.  
  37. interface
  38.  
  39. uses
  40.   System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, System.Diagnostics,
  41.   FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Layouts, FMX.Ani, FMX.Controls.Presentation, FMX.StdCtrls;
  42.  
  43. const
  44.   TARGET_FPS = 30;
  45.   TARGET_SPEED = 40;
  46.  
  47. type
  48.   TLoop = class(TAnimation)
  49.   private
  50.     StopWatch:        TStopWatch;
  51.     LastElapsedTicks: int64;
  52.     Delta:            double;
  53.   protected
  54.     procedure ProcessAnimation; override;
  55.   end;
  56.  
  57.   TForm1 = class(TForm)
  58.     ScaledLayout1: TScaledLayout;
  59.     Label1: TLabel;
  60.     procedure FormCreate(Sender: TObject);
  61.     procedure FormDestroy(Sender: TObject);
  62.     procedure ScaledLayout1Paint(Sender: TObject; Canvas: TCanvas; const ARect: TRectF);
  63.   public
  64.     Loop: TLoop;
  65.     RectPosX: double;
  66.   end;
  67.  
  68. var
  69.   Form1: TForm1;
  70.  
  71. implementation
  72.  
  73. {$R *.fmx}
  74.  
  75. procedure TLoop.ProcessAnimation;
  76. var
  77.   TickDelta: int64;
  78.   TimeElapsed: int64;
  79. begin
  80.   TimeElapsed      := StopWatch.ElapsedTicks;
  81.   TickDelta        := TimeElapsed - LastElapsedTicks;
  82.   LastElapsedTicks := TimeElapsed;
  83.   Delta            := abs(TickDelta / StopWatch.Frequency) * TARGET_FPS;
  84.  
  85.   Form1.RectPosX := Form1.RectPosX - TARGET_SPEED * Delta;
  86.  
  87.   Form1.Label1.Text := FormatFloat('0.#####', Delta) + ' / ' + FormatFloat('0.#', Delta * TARGET_FPS);
  88.  
  89.   if Form1.RectPosX <= -100 then Form1.RectPosX := 1000;
  90.  
  91.   Form1.ScaledLayout1.Repaint;
  92. end;
  93.  
  94. procedure TForm1.FormCreate(Sender: TObject);
  95. begin
  96.   Loop              := TLoop.Create(self);
  97.   Loop.Loop         := true;
  98.   Loop.Name         := 'AnimationLoop';
  99.   Loop.Parent       := self;
  100.   Loop.AniFrameRate := TARGET_FPS;
  101.  
  102.   RectPosX := Form1.Width;
  103.  
  104.   TAnimator.StartAnimation(self, 'AnimationLoop');
  105.   Loop.StopWatch := TStopWatch.StartNew();
  106. end;
  107.  
  108. procedure TForm1.FormDestroy(Sender: TObject);
  109. begin
  110.   TAnimator.StopAnimation(self, 'AnimationLoop');
  111.  
  112.   if Assigned(Loop) then Loop.Free;
  113. end;
  114.  
  115. procedure TForm1.ScaledLayout1Paint(Sender: TObject; Canvas: TCanvas; const ARect: TRectF);
  116. var
  117.   Rect: TRectF;
  118.   Wratio: double;
  119.   Hratio: double;
  120. begin
  121.   if (Form1.Width < 1) or (Form1.Height < 1) then exit;
  122.  
  123.   Canvas.BeginScene();
  124.   try
  125.     Canvas.ClearRect(ARect, TAlphaColorRec.Black);
  126.  
  127.     Wratio := ARect.Width / 1000;
  128.     Hratio := ARect.Height / 600;
  129.  
  130.     Rect := TRectF.Create(RectPosX * Wratio, 50 * Hratio, (RectPosX + 100) * Wratio, 550 * Hratio);
  131.     if Rect.Left < 0 then Rect.Left := 0;
  132.     if Rect.Right > 1000 * Wratio then Rect.Right := 1000 * Wratio;
  133.  
  134.     Canvas.ClearRect(Rect, TAlphaColorRec.Silver);
  135.  
  136.   finally
  137.     Canvas.EndScene;
  138.   end;
  139. end;
  140.  
  141. end.
  142.  

V každém případě jsem připojil obrázek kde je vidět, jak neviditelná komponenta vykreslila obrys. Obrys je neúplný, protože v prostřední části frejmu se kreslil jiný objekt. Tato grafická chyba se opraví jakmile vybudím repaint frejmu nebo neviditelného objektu.

Je možné, že kdyby problematická komponenta nebyla na ScaleLayoutu, tak by to bylo v pořádku.

Delphi 10.1 Berlin (update 2) a platforma Windows 10.
« Poslední změna: 24-04-2020, 11:46:20 od age.new »

Offline Radek Červinka

  • Administrátoři
  • Padawan
  • *****
  • Příspěvků: 2730
  • Karma: 105
    • Verze Delphi: D2007, DXE + 2 poslední
    • O Delphi v češtině
Re:Náhrada za DoubleBuffering?
« Odpověď #5 kdy: 24-04-2020, 12:05:13 »
Zkus nejprve RTFM a dodrzovat pravidla

if Canvas.BeginScene then

Kód: Delphi [Vybrat]
  1. if Canvas.BeginScene then
  2.   try
  3.     Canvas.xxx   //drawing image operations
  4.     ...
  5.   finally
  6.     Canvas.EndScene;
  7.   end;
  8.  
  9.  

b) myslim, ze vsechno co aktualizuje UI v TLoop.ProcessAnimation
musi byt volane z kontextu hlavniho threadu, tj. pres TThread.Synchronize()


ted se na to nemuzu podivat tak to ber jen jako napovedu
Embarcadero MVP - Czech republic

Offline age.new

  • Plnoletý
  • ***
  • Příspěvků: 234
  • Karma: 0
Re:Náhrada za DoubleBuffering?
« Odpověď #6 kdy: 24-04-2020, 12:50:19 »
Ten Canvas.BeginScene ještě otestuji. Předělám to ale z TAnimation na klasické vlákno. Snad to kreslení na Canvas trochu vylepší.

K těm "duchům" u objektů se změnou viditelnosti, tam Paint nepoužívám nikde. Pouze měním property.

Překvapuje mě, že se s tím setkávám pouze já, respektive Vy pane Červinko programujete mnohem déle a jistě i více do hloubky - nezaznamenal jste žádné grafické anomálie při používání FMX (Layouts, Shapes...)?

« Poslední změna: 24-04-2020, 12:54:17 od age.new »

Offline Radek Červinka

  • Administrátoři
  • Padawan
  • *****
  • Příspěvků: 2730
  • Karma: 105
    • Verze Delphi: D2007, DXE + 2 poslední
    • O Delphi v češtině
Re:Náhrada za DoubleBuffering?
« Odpověď #7 kdy: 24-04-2020, 13:20:30 »
Ten Canvas.BeginScene ještě otestuji. Předělám to ale z TAnimation na klasické vlákno. Snad to kreslení na Canvas trochu vylepší.

K těm "duchům" u objektů se změnou viditelnosti, tam Paint nepoužívám nikde. Pouze měním property.

Překvapuje mě, že se s tím setkávám pouze já, respektive Vy pane Červinko programujete mnohem déle a jistě i více do hloubky - nezaznamenal jste žádné grafické anomálie při používání FMX (Layouts, Shapes...)?


Změna property přece vyvolá kreslení, a pokud nejsi synchronizovan, tak je to ekvivalent. To same u vlaken. Jakakoliv vizualni zmena musi byt z kontextu hlavniho vlakna, to plati i ve VCL nebo kdekoliv jinde. Pak nebudes mit problemy.
Embarcadero MVP - Czech republic

Offline age.new

  • Plnoletý
  • ***
  • Příspěvků: 234
  • Karma: 0
Re:Náhrada za DoubleBuffering?
« Odpověď #8 kdy: 24-04-2020, 13:54:18 »
Možná to dělám špatně. Uvedu příklad:

Běží mi procedura na klik tlačítka, která provádí nějakou náročnou činnost (práce se soubory např.) a podle výsledků se v průběhu cyklů mění viditelnost a barva jiných objektů (např. TRectangle).
1) OnClick begin
2) přístup do 1. souboru a čtení řádků v cyklu
3) výsledek zobrazím v TRectangle s nějakým property nastavením
4) přístup do 2. souboru a čtení řádků v cyklu
5) výsledek zobrazím v TRectangle s nějakým property nastavením
..
99) OnClick end;

Jak toto mám "synchronizovat"? ...
Property u TRectangle měnit musím a o vše by se mělo postarat Delphi. Nenapadá mě ani, jak jinak to dělat.

...

Já už mám z FMX i špatné sny. Mrzí mě, že si neschovávám přesné kódy, nebo alespoň video plochy, ale pár příkladů na které jsem narazil za posledních tři týdny:

- když má subkomponenta TRectangle v "constructoru" rodiče nastavené visible=true, tak změna na visible=false při nějaké jiné události objekt neskryje - TRectangle zůstane pořád vidět i při visible=false ... zbylé subkomponenty, které měli v "constructoru" visible=false se zobrazovali a skrývali v pořádku.

- Když kreslím na TImage a upravuji velikost bitmapy např. na nějaký definovaný rozměr, tak ve Windows je to ok, ale stejná procedura na Androidu dopadne špatně, resp. bitmapa má jinou velikost. Zjistil jsem, že TImage a změna bitmapy ve visible=false na Androidu prostě nefunguje jak má. TImage jsem nahradil TRectangle a vše je ok.

- TRectangle v jiném TRectangle. Když je vnořený nastavený na visible=false, tak se prostě někdy objeví rámeček "duch". Zajímavé je, že když se vnější TRectangle mění, resp. provádí nějakou činnost vyvolávající repaint, tak se rámeček "duch" objevuje a skrývá ... asi podle nálady.

- vyvolání změny Text property u TText objektu v momentě kreslení jiného objektu (v OnPaint proceduře) zmastí vzhled textu onoho TText. Jakoby se prali canvasy obou objektů.

... jistě bych si časem vzpomněl i na další ...

« Poslední změna: 24-04-2020, 13:59:29 od age.new »

Offline Radek Červinka

  • Administrátoři
  • Padawan
  • *****
  • Příspěvků: 2730
  • Karma: 105
    • Verze Delphi: D2007, DXE + 2 poslední
    • O Delphi v češtině
Re:Náhrada za DoubleBuffering?
« Odpověď #9 kdy: 24-04-2020, 14:11:54 »
Možná to dělám špatně. Uvedu příklad:

Běží mi procedura na klik tlačítka, která provádí nějakou náročnou činnost (práce se soubory např.) a podle výsledků se v průběhu cyklů mění viditelnost a barva jiných objektů (např. TRectangle).
1) OnClick begin
2) přístup do 1. souboru a čtení řádků v cyklu
3) výsledek zobrazím v TRectangle s nějakým property nastavením
4) přístup do 2. souboru a čtení řádků v cyklu
5) výsledek zobrazím v TRectangle s nějakým property nastavením
..
99) OnClick end;

Jak toto mám "synchronizovat"? ...
Property u TRectangle měnit musím a o vše by se mělo postarat Delphi. Nenapadá mě ani, jak jinak to dělat.
...

Všechno co trvá déle musí být ve vláknu nebo jinak ti to Android odstřelí když se mu to bude zdát dlouho, třeba použij něco ze System.Threading.

Kód: Delphi [Vybrat]
  1.  
  2.  
  3.  
  4. procedure THeaderFooterwithNavigation.mStatus(const Sender: TObject; AContentLength, AReadCount: Int64;
  5.   var Abort: Boolean);
  6. begin
  7.   TThread.Synchronize(nil,
  8.           procedure
  9.           begin
  10.             ProgressBar1.Max := AContentLength;
  11.             ProgressBar1.Value := AReadCount;
  12.           end);
  13.  
  14. end;
  15.  
  16. procedure THeaderFooterwithNavigation.btnDownClick(Sender: TObject);
  17. begin
  18.   TThread.CreateAnonymousThread(
  19.     procedure
  20.     var
  21.       mem: TMemoryStream;
  22.       oClient : THTTPClient;
  23.       sFile: string;
  24.     begin
  25.       oClient := THTTPClient.Create;
  26.       oClient.OnReceiveData := mStatus;
  27.       mem := TMemoryStream.Create;
  28.       sFile := edtFile.Text;
  29.       oClient.Get(sFile, mem);
  30.         mem.Position := 0;
  31.         mem.SaveToFile(xxxxxxxx);
  32.         TThread.Synchronize(nil,
  33.         procedure
  34.         begin
  35.           ShowMessage('Staženo, velikost:'+mem.Size.ToString());
  36.         end
  37.         );
  38.  
  39.       FreeAndNil(oClient);
  40.     end).Start;
  41.  
  42. end;
  43.  


Když aktualizuješ něco v UI, tak jedině přes TThread.Synchronize, ideálně přes anonymní metodu viz ten mStatus nebo

Kód: Delphi [Vybrat]
  1.      TThread.Synchronize(nil,
  2.      procedure
  3.      begin
  4.        ShowMessage('Hej rup');
  5.      end
  6.      )
  7.  






Embarcadero MVP - Czech republic

Offline Radek Červinka

  • Administrátoři
  • Padawan
  • *****
  • Příspěvků: 2730
  • Karma: 105
    • Verze Delphi: D2007, DXE + 2 poslední
    • O Delphi v češtině
Re:Náhrada za DoubleBuffering?
« Odpověď #10 kdy: 24-04-2020, 14:13:29 »
>aktualizuješ něco v UI
z jineho vlakna jsem chtel napsat
Embarcadero MVP - Czech republic

Offline age.new

  • Plnoletý
  • ***
  • Příspěvků: 234
  • Karma: 0
Re:Náhrada za DoubleBuffering?
« Odpověď #11 kdy: 24-04-2020, 14:46:11 »
Schválně jsem zkusil
Kód: Delphi [Vybrat]
  1.   // text s životy
  2.   TThread.Synchronize(nil,
  3.     procedure
  4.     begin
  5.       Frame_Workout.Text_Lives.Text := intToStr(Frame_Workout.Lives);
  6.     end);
  7.  

a výsledek je stejně špatný jako bez  TThread.Synchronize, viz. příloha. Ta červená trojka/dvojka. Z nějakého důvodu se to překreslilo jen napůl.

Nečekal bych, že změna property u TText musí být nějak "synchronizována". A to se pokazí jen jeden TText. Nad a pod ním jsou dva další, které se aktualizují taky a u těch problém nenastává. Nedomnívám se, že to je problém kódu, ale toho, že ten objekt TText nějak koliduje s Layoutem, na který kreslím (ten je úplně v pozadí pod vším)...

« Poslední změna: 24-04-2020, 14:49:27 od age.new »

Offline vandrovnik

  • Guru
  • *****
  • Příspěvků: 1122
  • Karma: 47
    • Verze Delphi: 10.3
Re:Náhrada za DoubleBuffering?
« Odpověď #12 kdy: 24-04-2020, 15:33:15 »
Není problém v tom, že ten text špatně vyhodnotí, jak velká oblast se má překreslit? Nešlo by po změně textu zavolat Invalidate (nebo nějakou obdobu, nejsem si teď jistý, zda ho FMX má) nějaké větší oblasti, aby bylo vidět, zda to pomůže?

Offline age.new

  • Plnoletý
  • ***
  • Příspěvků: 234
  • Karma: 0
Re:Náhrada za DoubleBuffering?
« Odpověď #13 kdy: 24-04-2020, 20:43:47 »
Problém s tím TText je patrně kvůli volání Text property v místě, když se kreslí na canvas layoutu. Pan Červinka v tomto má pravdu - je potřeba najít lepší místo pro vložení kódu se změnou Text property u TText.

Bohužel používám frejmy z toho každý má 5 až 10 layoutů které se vzájemně mohou krýt. Property měním jak je třeba a něco jako synchronizace u VCL neznám. U FMX si ztěžka zvykám po deseti letech s VCL.

Pan Červinka zmínil i manuál. Od XE2 k Berlin čas od času narazím na změnu v základních unitech. Hlavně u Androida. Na netu potom nacházím kódy z XE6 a pod., které v Berlin verzi již nefungují. Manuál proto až na posledním místě ... a nejlepší stránky http://www.delphibasics.co.uk/ už holt nejsou aktualizované :(
« Poslední změna: 24-04-2020, 20:48:21 od age.new »

Offline age.new

  • Plnoletý
  • ***
  • Příspěvků: 234
  • Karma: 0
Re:Náhrada za DoubleBuffering?
« Odpověď #14 kdy: 28-04-2020, 08:15:45 »
Další zvláštnost s kreslením ve FMX.

Mám pole TRectangle nazvané Rect_Segments, které dynamicky vytvářím:

Kód: Delphi [Vybrat]
  1. var
  2.   Rect_Segments: array of TRectangle;
  3. ...
  4. ...
  5.  
  6. SetLength(Rect_Segments, 100);
  7. for iTmp := 0 to length(Rect_Segments) - 1 do
  8. begin
  9.   Rect_Segments[iTmp] := TRectangle.Create(Frame_Workout);
  10.   with Rect_Segments[iTmp] do
  11.   begin
  12.     Parent := Frame_Workout.ScaledLayout_Content;
  13.     ...
  14.     ...
  15.   end;
  16. end;
  17.  

Důležitá je Create a parent část. Proměnnou Rect_Segments jsem měl v public části frejmu Frame_Workout. Pouze jsem vzal tuto proměnnou a dal ji mezi globální proměnné, tj. již není součástí třídy Frame_Workout ani žádné jiné! V příloze je obrázek, kde je vidět, jak se pokazí kreslení. Dodávám, že žádná jiná změna v kódu nebyla provedena. Nepoužívám žádné Paint procedury, pouze rectangly z pole Rect_Segments rozmístím na frejmu Frame_Workout a nechávám "FMX framework" aby se o vše postaral jak by měl...

Jak může vzniknout chyba v kreslení TShape objektů když "pouze" přesunu referenční proměnnou mezi globální proměnné?

Dělám něco špatně???

Děkuji.



 

« Poslední změna: 28-04-2020, 08:17:17 od age.new »