Sehr kurze Textanzeige auf dem Canvas

Für Fragen von Einsteigern und Programmieranfängern...
Benutzeravatar
Aidex
Beiträge: 22
Registriert: Do 24. Sep 2020, 07:02
OS, Lazarus, FPC: Win10 64bit, Laz v2.0.10
CPU-Target: AMD64

Re: Sehr kurze Textanzeige auf dem Canvas

Beitrag von Aidex »

Moin!
Um das Problem mit dem nachträglichen Zeichnen zu umgehen, wurde in Windows DirectDraw als Teil von DirectX eingeführt.
DirectDraw ist zwischenzeitlich in Direct3D aufgegangen. Dafür gibt's auch FreePascal-Units, siehe ...
https://wiki.freepascal.org/FPC_and_DirectX/de
Wie ich dort aber lese, ist die Programmierung aufwändig - und ich habe leider keine Erfahrung damit. Deshalb dies nur als Hinweis zu dem Thema.
Grüße, Jörg

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 4562
Registriert: So 7. Jan 2007, 10:20
OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
CPU-Target: 32Bit (64Bit)
Wohnort: Niederösterreich
Kontaktdaten:

Re: Sehr kurze Textanzeige auf dem Canvas

Beitrag von af0815 »

Bevor ich DirectDraw verwende, würde ich eine plattformunabhängige Lösung nehmen.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

logo.holzaepfel
Beiträge: 8
Registriert: Sa 3. Apr 2021, 15:44
OS, Lazarus, FPC: Winux (L 0.9.xy FPC 2.2.z)
CPU-Target: xxBit
Kontaktdaten:

Re: Sehr kurze Textanzeige auf dem Canvas

Beitrag von logo.holzaepfel »

Guten Morgen und vielen Dank für all die Rückmeldungen... tatsächlich bin ich auch der Meinung, WENN ich schon komplett in die Tiefen eintauche, dann würde ich auch etwas plattformunabhängiges wählen anstatt mit mit DirectDraw herumzuprügeln.

Aber: tatsächlich habe ich noch etwas umgebaut und dadurch eine weitere Stabilisierung erreichen können. Natürlich ist es nicht optimal, an der Priorisierung einer App herumzufummeln, aber so läuft es was runder - und tatsächlich macht das sleep(1) vor dem Canvas.FillRect noch einmal gewaltig was aus.

Code: Alles auswählen

  SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
  SetProcessPriorityBoost(GetCurrentProcess(), False);
  SetThreadPriority(GetCurrentThreadId, THREAD_PRIORITY_TIME_CRITICAL);

  iWidth := 0;
  iHeight := 0;

  // Ggf. vorherige Textausgabe leeren
  Canvas.FillRect(Canvas.ClipRect);

  // Breite und Höhe der Textausgabe ermitteln
  Canvas.GetTextSize(aText, iWidth, iHeight);

  // Position der Textausgabe ermitteln
  iLeft := (Canvas.Width - 4 - iWidth) div 2;
  iTop := (Canvas.Height - 40 - iHeight) div 2;

  // Text ausgeben
  Canvas.TextOut(iLeft, iTop, aText);

  //Cleanup Routine
  iStartTime := GetTickCount64;
  repeat
    Application.ProcessMessages;
    iRunTime := GetTickCount64;
  until iRunTime - iStartTime >= fDisplayInterval;

  SetThreadPriority(GetCurrentThreadId, THREAD_PRIORITY_NORMAL);
  SetProcessPriorityBoost(GetCurrentProcess(), True);
  SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS);

  sleep(1);
  Canvas.FillRect(Canvas.ClipRect); 

Jetzt versuche ich, dem Rat von Siro folgend, GetTickCount64 durch den Ansatz mit QueryPerformanceCounter zu ersetzen. Aber ich scheitere kläglich! Es sind ein paar Punkte, die ich nicht nachvollziehen bzw. umsetzen kann:
- wahrscheinlich ist soll PerformanceFrequency eine lokale Variable sein, die mit QueryPerformanceFrequency abgefragt werden kann...
- fDisplayInterval ist ja bei mir als integer definiert. Die Mulitplikation mit PerformanceFrequency schlägt fehl, weil dieses ja ein LARGE_INTEGER ist und beides nicht miteinander harmoniert. Daher schlägt auch die Division und das Round fehl. Irgendwie habe ich da einen Knoten drin, den ich nicht entwirren kann. Einfaches casten der Werte geht ja nicht und ich habe keine Konvertierungsmöglichkeiten entdeckt (sowas wie IntToLargeInteger bzw. LargeIntegerToInt).
siro hat geschrieben:
So 4. Apr 2021, 21:07

Code: Alles auswählen

// SIRO :  ersetzt durch:
// Das Timing sollte nun im Mikrosekundenbereich stimmen
  QueryPerformanceCounter(iStartTime);
  iEndTime:=iStartTime + Round(fDisplayInterval * performanceFrequency / 1000);
  repeat
    QueryPerformanceCounter(iRunTime);
//    Application.ProcessMessages;   // !!!!!!!! <==== hier ist eine BREMSE, so ca. 15 Millisekunden  !!!!!
// nimmt man das raus, geht es aber so gut wie garnicht mehr.......
  until iRunTime > iEndTime;

siro
Beiträge: 437
Registriert: Di 23. Aug 2016, 14:25
OS, Lazarus, FPC: Windows 10
CPU-Target: 64Bit
Wohnort: Berlin

Re: Sehr kurze Textanzeige auf dem Canvas

Beitrag von siro »

ich habe deine Variablen als int64 definiert.

Code: Alles auswählen

var
  iStartTime: int64;
  iEndTime :int64;
  iRunTime: int64;     

QueryPerformanceFrequency muss man nur einmal im Programm aufrufen, am besten bei Form.create
Die Variable performanceFrequency habe ich hier Gloabl angelegt:

Code: Alles auswählen

uses
  frmSpeedEdit, frmItemCountEdit, inifiles, LCLProc;

var performanceFrequency:int64;    

Code: Alles auswählen

procedure TMainForm.FormCreate(Sender: TObject);
begin
  QueryPerformanceFrequency(performanceFrequency);
dein fDisplayInterval kann so bleiben als integer

Siro
Grüße von Siro
Bevor ich "C" ertragen muß, nehm ich lieber Lazarus...

siro
Beiträge: 437
Registriert: Di 23. Aug 2016, 14:25
OS, Lazarus, FPC: Windows 10
CPU-Target: 64Bit
Wohnort: Berlin

Re: Sehr kurze Textanzeige auf dem Canvas

Beitrag von siro »

ein kleiner Test zeigt:

Code: Alles auswählen

var f:int64;

procedure TForm1.FormCreate(Sender: TObject);
begin
  QueryPerformanceFrequency(f);
  caption:=IntToStr(f);                          // bei mir 10.000.000 ==> Der Zähler hat eine Auflösung von 1/f ==> 100ns 
end;

procedure TForm1.Button1Click(Sender: TObject);
var t1,t2:int64;
begin
  QueryPerformanceCounter(t1);
  sleep(1);
  QueryPerformanceCounter(t2);
  caption:=FloatToStr((t2-t1) / f);  // Umrechnung in Sekunden
end;     
sleep(1) benötigt mal 3, mal über 10 Millisekunden.
Und das verlängert natürlich die Anzeige und damit deinen sichtbaren Text.

Siro
Grüße von Siro
Bevor ich "C" ertragen muß, nehm ich lieber Lazarus...

logo.holzaepfel
Beiträge: 8
Registriert: Sa 3. Apr 2021, 15:44
OS, Lazarus, FPC: Winux (L 0.9.xy FPC 2.2.z)
CPU-Target: xxBit
Kontaktdaten:

Re: Sehr kurze Textanzeige auf dem Canvas

Beitrag von logo.holzaepfel »

Hallo Siro,

vielen Dank! Bei mir meckert allerdings QueryPerformanceCounter, dass die übergebene Variable Int64 sei und nicht LARGE_INTEGER...
siro hat geschrieben:
Mo 5. Apr 2021, 11:48
ich habe deine Variablen als int64 definiert.

Code: Alles auswählen

var
  iStartTime: int64;
  iEndTime :int64;
  iRunTime: int64;
//Edit: Okay, ich das Problem bei mir gefunden: ich hatte für

Code: Alles auswählen

SetProcessPriorityBoost

die Unit "jwawinbase" in meine Uses Klausel aufgenommen. Dort gibt es die Funktionen QueryPerformanceCounter sowie QueryPerformanceFrequency ebenfalls... allerdings nur mit LARGE_INTEGER Parametern. Sobald diese Unit eingebunden wird, greift meine Anwendung auf diese Definitionen zu.
Leider habe ich noch nicht herausfinden können, wie ich Funktionen aus "func.inc" gezielt aufrufen kann, wenn gleichnamige Pendants in einer anderen Unit definiert sind. Wäre es "func.pas" würde ich entsprechend func.QueryPerformanceCounter verwenden... aber so?

//Edit 2: Ich bekomme allerdings nun auch eine Exception ( Exception-Klasse RunError(215) bzw. EIntOverflow: Arithmetic Overflow) bei

Code: Alles auswählen

iEndTime := iStartTime + Round(fDisplayInterval * iPerformanceFrequency / 1000);

siro
Beiträge: 437
Registriert: Di 23. Aug 2016, 14:25
OS, Lazarus, FPC: Windows 10
CPU-Target: 64Bit
Wohnort: Berlin

Re: Sehr kurze Textanzeige auf dem Canvas

Beitrag von siro »

sorry, gelöscht. ich glaube ich verstehe anscheinend das Problem jetzt nicht....
wozu brauche ich func.inc ??
und was soll der LARGE_INTEGER ??? der ist nicht kompatibel zum int64 ??? merke ich grade.
Da blick ich jetzt nicht mehr durch..... :shock:

Code: Alles auswählen

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls,
  jwawinbase,jwawintype;   // ursprünglich war das die unit "windows"    

var f:LARGE_INTEGER;

procedure TForm1.FormCreate(Sender: TObject);
begin
  QueryPerformanceFrequency(f);
end;

procedure TForm1.Button1Click(Sender: TObject);
var t1,t2:LARGE_INTEGER;
begin
  QueryPerformanceCounter(t1);
  sleep(1);
  QueryPerformanceCounter(t2);
  caption:=FloatToStr((t2.QuadPart - t1.QuadPart) / f.QuadPart);
end;
Grüße von Siro
Bevor ich "C" ertragen muß, nehm ich lieber Lazarus...

Warf
Beiträge: 1561
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: MacOS | Win 10 | Linux
CPU-Target: x86_64
Wohnort: Aachen

Re: Sehr kurze Textanzeige auf dem Canvas

Beitrag von Warf »

logo.holzaepfel hat geschrieben:
Mo 5. Apr 2021, 10:51
Aber: tatsächlich habe ich noch etwas umgebaut und dadurch eine weitere Stabilisierung erreichen können. Natürlich ist es nicht optimal, an der Priorisierung einer App herumzufummeln, aber so läuft es was runder - und tatsächlich macht das sleep(1) vor dem Canvas.FillRect noch einmal gewaltig was aus.

Code: Alles auswählen

  SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
  SetProcessPriorityBoost(GetCurrentProcess(), False);
  SetThreadPriority(GetCurrentThreadId, THREAD_PRIORITY_TIME_CRITICAL);

  iWidth := 0;
  iHeight := 0;

  // Ggf. vorherige Textausgabe leeren
  Canvas.FillRect(Canvas.ClipRect);

  // Breite und Höhe der Textausgabe ermitteln
  Canvas.GetTextSize(aText, iWidth, iHeight);

  // Position der Textausgabe ermitteln
  iLeft := (Canvas.Width - 4 - iWidth) div 2;
  iTop := (Canvas.Height - 40 - iHeight) div 2;

  // Text ausgeben
  Canvas.TextOut(iLeft, iTop, aText);

  //Cleanup Routine
  iStartTime := GetTickCount64;
  repeat
    Application.ProcessMessages;
    iRunTime := GetTickCount64;
  until iRunTime - iStartTime >= fDisplayInterval;

  SetThreadPriority(GetCurrentThreadId, THREAD_PRIORITY_NORMAL);
  SetProcessPriorityBoost(GetCurrentProcess(), True);
  SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS);

  sleep(1);
  Canvas.FillRect(Canvas.ClipRect); 
Das sleep ist exakt antithetisch zu dem was du erreichen willst. Sleep implementiert non busy waiting, es nimmt also deinen Prozess von der CPU. Ein Sleep von 1 ms ist eigentlich immer mindestens 10 ms oder mehr. Es bringt nix vorher mit busy waiting exakt zu warten um dann mit einem Sleep praktisch dann nochmal eine mehr oder weniger zufälligen wartezeit draufzusetzen

Das problem ist wie gesagt das Canvas selbst, die unterliegende API ist für so hochfrequentes Zeichnen nicht ausgelegt. Du kannst versuchen so viel zu tricksen wie du willst, das ist ein Windows problem, und von deiner Seite aus kannst du da nix machen.

Hier ist was ich machen würde: Ich würde die SDL benutzen, und dann über einen SDL main loop Arbeiten. Damit habe ich früher mal als ich noch ein bisschen mehr im bereich Spieleentwicklung gemacht habe für simple 2D zeichenarbeiten Problemlos bis zu 1000 frames per second hinbekommen. SDL ist auch im Grunde einfach genug als das man da schnell rein kommt.

Du wirst dann natürlich immernoch durch den Scheduler gebremst, aber das ist nicht so schlimm, denn solang der rechner nicht super heftig ausgelastet ist, hat man durch moderne multicore prozessoren meißt einen kompletten kern für sich allein wenn man einen non sleeping infinite loop hat wie ihn SDL erzeugt, und selbst wenn der Scheduler dich runterschmeißt, sollte das selten genug vorkommen das im Durchschnitt das Ganze genau genug ist.

Das einzige Harte Limit ist der Bildschirm. Zwar kannst du mit SDL extrem hohe FPS zahlen erreichen, bringt aber nix wenn der Bildschirm nur alle 16 ms die auch anzeigen kann. damit musst du leben, 140 hz Monitore sind ziemlich teuer, und die meisten Leute brauchen sie nicht, ich würde also nicht davon ausgehen das der Kunde/Patient so einen hat. Also mit 5ms anzeigen wird nix, aber bis 20ms kannst du so zumindest mal runter gehen

logo.holzaepfel
Beiträge: 8
Registriert: Sa 3. Apr 2021, 15:44
OS, Lazarus, FPC: Winux (L 0.9.xy FPC 2.2.z)
CPU-Target: xxBit
Kontaktdaten:

Re: Sehr kurze Textanzeige auf dem Canvas

Beitrag von logo.holzaepfel »

@Siro...

Ja, ich kam da auch gerade etwas durcheinander. Aus irgendeinem Grund wurde immer auf die jwawinbase Definition von QueryPerformanceFrequency und QueryPerformanceCounter zurückgegriffen, wenn jwawinbase in der USES Klausel stand. Hatte ich das nicht darin stehen und öffnete die Definition einer der beiden Funktionen mit Strg+Mausklick wurde die func.inc bzw. redef.inc geöffnet.

Wenn ich nun die Funktionen mit so aufrufe:

Code: Alles auswählen

windows.QueryPerformanceCounter(int64Variable)
klappt's auch mit Int64.

@Warf: Ja, du hast recht... Das Sleep ist eigentlich genau das Gegenteil von dem, was ich erreichen will. Spannenderweise habe ich den Eindruck gehabt, dass hier die Anzeige des Textes tatsächlich häufiger erfolgte (auch für eben eine winzige Zeitspanne) als ohne.
Ich glaube dir, dass das mit SDL wahrscheinlich noch eleganter zu lösen wäre.. aber das ist mir zu aufwändig, mich da noch hineinzuarbeiten. Dennoch vielen Dank!

kupferstecher
Beiträge: 299
Registriert: Do 17. Nov 2016, 11:52

Re: Sehr kurze Textanzeige auf dem Canvas

Beitrag von kupferstecher »

Ich hab mich jetzt auch mal daran probiert und einfach ein TLabel auf einem Formular plaziert und darüber noch ein TShape gelegt. Auf Knopfdruck dann das Shape für kurze Zeit ausgeblendet:

Code: Alles auswählen

  Shape1.Hide;
  Application.ProcessMessages;
  sleep(20);
  Shape1.Show; 
Die Hoffnung dahinter ist, dass das Aus- und Einblenden des Shapes durch die Grafikkarte erfolgt, also minimaler CPU-Aufwand nötig ist, der das Timing verhageln kann. Bei sleep(10) funktioniert es nicht zuverlässig. Nach meiner Erfahrung auf Win7 ist eine Zeitscheibe etwa 13ms lang, ein sleep(10) dürften also 13ms sein, ein sleep(20) dann 26ms. Bei ersterem Wert vermute ich, kommt die Anzeige schon nicht hinterher. 25ms wären 40Hz, die Anzeige wird bei mir mit 59Hz angegeben (externer Monitor über DVI), das würde also passen. Eine Erhöhung auf sleep(30) (->39ms) macht die Schrift dann intensiver, ist also auch plausibel.

Antworten