Canvas zwischenspeichern (DoubleBuffering langsamer?) gelöst

Rund um die LCL und andere Komponenten
RSE
Beiträge: 462
Registriert: Mi 30. Jul 2008, 13:11
OS, Lazarus, FPC: WinXP SP3 (L 0.9.28.2 FPC 2.2.4)
CPU-Target: 32Bit
Kontaktdaten:

Canvas zwischenspeichern (DoubleBuffering langsamer?) gelöst

Beitrag von RSE »

Hallo!

Ich habe mit Grafiken bisher nichts am Hut gehabt, außer auf Canvas herumzuzeichnen, um den Inhalt meiner Komponenten darzustellen. Ich habe bisher keine Ahnung von TBitmap, BitBlt, TGraphic etc.

Ich möchte den grafischen Inhalt eines Canvas in meinem Objekt zwischenspeichern (ein Buffer reicht vollkommen), um ihn später wieder auf das Canvas zu kopieren. Dieser Buffer braucht (soll) keine zusätzliche Funktionalität mitbringen, der Quellcode soll lediglich plattformunabhängig compilierbar bleiben.

Hintergrund: Ich möchte mein Steuerelement nicht bei jedem Aufruf von Paint komplett neu konstruieren müssen, wenn sich am Inhalt nichts geändert hat. Für den Fall, dass Paint nur aufgerufen wird, weil sich ein Fenster über meinem Steuerelement bewegt, reicht oben angestrebtes Vorgehen vollkommen aus und ist vor allem wesentlich schneller. Wird lediglich gescrollt, brauche ich den alten Inhalt nur an eine andere Stelle kopieren und nur den Rest neu zeichnen (es handelt sich um eine seeehhhr lange Liste, bei der beim Scrollen nicht einfach das gesamte Canvas verschoben werden kann).
Zuletzt geändert von RSE am Sa 21. Nov 2009, 18:34, insgesamt 2-mal geändert.
Seit er seinen neuen Computer hat, löst er alle Probleme, die er vorher nicht hatte!

Benutzeravatar
theo
Beiträge: 10904
Registriert: Mo 11. Sep 2006, 19:01

Re: Inhalt von Canvas zwischenspeichern

Beitrag von theo »

Mind. für GTK2 und Win32

Code: Alles auswählen

procedure TForm1.Button1Click(Sender: TObject);
var
  BMP: TBitmap;
  ADC: HDC;
begin
  BMP := TBitmap.Create;
  ADC := GetDC(Handle);
  BMP.LoadFromDevice(ADC);
  ReleaseDC(Handle,ADC);
  Image1.Picture.Bitmap:=BMP;
  BMP.free;
end;

RSE
Beiträge: 462
Registriert: Mi 30. Jul 2008, 13:11
OS, Lazarus, FPC: WinXP SP3 (L 0.9.28.2 FPC 2.2.4)
CPU-Target: 32Bit
Kontaktdaten:

Re: Inhalt von Canvas zwischenspeichern

Beitrag von RSE »

Folgendes scheint zu funktionieren:

Code: Alles auswählen

constructor TRSEListBox.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FBitmap := TBitmap.Create;
end;
 
procedure TRSEListBox.EraseBackground(DC: HDC);
begin
  if FDC <> 0 then
    Canvas.Draw(0,0,FBitmap)
  else
    inherited;
end;
 
procedure TRSEListBox.Paint;
begin
  if FDC = 0 then
    FDC := GetDC(Handle);  // initialize DC Handle
  ... // conditional Painting
  if Painted then
    FBitmap.LoadFromDevice(FDC);
end;
 
destructor TRSEListBox.Destroy;
begin
  FBitmap.Free;
  if FDC <> 0 then
    ReleaseDC(Handle,FDC);  // release DC Handle
  inherited Destroy;
end;
Ist das so richtig angewendet?

Edit:
FBitmap.LoadFromDevice(FDC); scheint ja ultralahm zu sein. Ich habe Canvas.Draw(0,0,FBitmap); schon erfolgreich gegen BitBlt(Canvas.Handle,0,0,FBitmap.Width,FBitmap.Height,FBitmap.Canvas.Handle,0,0,SRCCOPY); ersetzt, aber wenn ich FBitmap.LoadFromDevice(FDC); gegen BitBlt(FBitmap.Canvas.Handle,0,0,ClientWidth,ClientHeight,Canvas.Handle,GetClientRect.Left,GetClientRect.Top,SRCCOPY); ersetzte, dann funktioniert es nicht mehr.
Seit er seinen neuen Computer hat, löst er alle Probleme, die er vorher nicht hatte!

pluto
Lazarusforum e. V.
Beiträge: 7192
Registriert: So 19. Nov 2006, 12:06
OS, Lazarus, FPC: Linux Mint 19.3
CPU-Target: AMD
Wohnort: Oldenburg(Oldenburg)

Re: Inhalt von Canvas zwischenspeichern

Beitrag von pluto »

Für den Fall, dass Paint nur aufgerufen wird, weil sich ein Fenster über meinem Steuerelement bewegt
Meines Wissens wird Paint aufgerufen wenn: Das Fenster überdenkt wird, wenn sich die Fenster Größe verändert hat.
BitBlt(FBitmap.Canvas.Handle,0,0,ClientWidth,ClientHeight,Canvas.Handle,GetClientRect.Left,GetClientRect.Top,SRCCOPY); ersetzte, dann funktioniert es nicht mehr
Erstens
Das ist auch kein Wunder, du hast "FBitmap.Canvas.Handle" mit "Canvas.Handle" vertauscht.
Ich nehme an, "FBitmap.Canvas.Handle" ist dein Buffer, der alles zwischenspeichern soll, und "Canvas" ist das von "TCustomControl".
Die ersten Fünft Parameter sind für den Ausgabe, Canvas gedacht.

Zweitens
Du setzt voraus, das dein Buffer, größer ist als der Ausgabe Canvas, richtig ? Auf Dauer kann das zu langsam sein. Das habe ich bei meiner "Text Engine" gesehen. Aber es kommt drauf an was du genau machst.
FBitmap sollte nur so groß sein, wie dein Anzeige Canvas. Dann kannst du alles in den "Negativen" Bereich verschieben, Hier ein Auszug, von meiner "Text Engine"

Code: Alles auswählen

procedure TPLTE_ObjectBaseSurface.Paint;
begin
  if Visible then begin
    Style.ToCanvas(canvas);
    sy:=top-ScrollRect.Top; sx:=Left-ScrollRect.Left;
 
    if self is TPLTE_ObjectSTD_Text then
      Canvas.FillRect(sx, sy, (sx+Width)-ExtraWidht,(sy+Height)-ExtraHeight)
    else begin
      if self is TPLTE_Container_paragraph then begin
        Canvas.Rectangle(sx, sy, (sx+Width)-ExtraWidht,(sy+(Height-top))-ExtraHeight);
      end
      else
        Canvas.Rectangle(sx, sy, (sx+Width)-ExtraWidht,(sy+Height)-ExtraHeight);
    end;
  end;   
end;
Scrollrect, ist das Rect, was du verschiebst mit der ScrollBar. Canvas ist deine Ausgabe canvas.
In meinen Fall habe ich das sogar so gemacht: In der Main-Paint Methode kann entschieden, werden ob der interne Buffer genutzt werden soll oder nicht. Siehe hier zu auch das HTML-Panel.
MFG
Michael Springwald

RSE
Beiträge: 462
Registriert: Mi 30. Jul 2008, 13:11
OS, Lazarus, FPC: WinXP SP3 (L 0.9.28.2 FPC 2.2.4)
CPU-Target: 32Bit
Kontaktdaten:

Re: Inhalt von Canvas zwischenspeichern

Beitrag von RSE »

pluto hat geschrieben:
Für den Fall, dass Paint nur aufgerufen wird, weil sich ein Fenster über meinem Steuerelement bewegt
Meines Wissens wird Paint aufgerufen wenn: Das Fenster überdenkt wird, wenn sich die Fenster Größe verändert hat.
Genau. Ersteres ist mein Einsatzzweck für FBitmap, das mir als Minimalstruktur zum Buffern empfohlen wurde. Ein Bitvektor würde mir theoretisch auch reichen, weil ich ja wieder in den gleichen Canvas hineinkopiere. Wie gesagt, habe bisher keine große Ahnung von den Grafiksachen.
pluto hat geschrieben:
BitBlt(FBitmap.Canvas.Handle,0,0,ClientWidth,ClientHeight,Canvas.Handle,GetClientRect.Left,GetClientRect.Top,SRCCOPY); ersetzte, dann funktioniert es nicht mehr
Erstens
Das ist auch kein Wunder, du hast "FBitmap.Canvas.Handle" mit "Canvas.Handle" vertauscht.
Ich nehme an, "FBitmap.Canvas.Handle" ist dein Buffer, der alles zwischenspeichern soll, und "Canvas" ist das von "TCustomControl".
Die ersten Fünft Parameter sind für den Ausgabe, Canvas gedacht.
Deine Annahmen sind korrekt, das hätte ich noch dazuschreiben sollen... Lese ich daraus, dass BitBlt u.U. direkt in den Grafikspeicher kopiert? Ich dachte damit könnt ich auch den Buffer füllen...
pluto hat geschrieben:Zweitens
Du setzt voraus, das dein Buffer, größer ist als der Ausgabe Canvas, richtig ? Auf Dauer kann das zu langsam sein. Das habe ich bei meiner "Text Engine" gesehen. Aber es kommt drauf an was du genau machst.
FBitmap sollte nur so groß sein, wie dein Anzeige Canvas.
Eigentlich wollte ich, dass FBitmap genau so groß wie mein Anzeigecanvas ist. Ich hatte wohl angenommen, dass die Größensetzung meines Buffers FBitmap automatisch geschieht. Das sollte ich wohl besser im Vorfeld mit FBitmap.SetSize tun. ClientRect.Left und ClientRect.Top hätte besser 0 heißen sollen... war schon spät, als ich das geproggt habe.
pluto hat geschrieben:Siehe hier zu auch das HTML-Panel.
Die Kompo ist mir bisher nie aufgefallen, werd sie mir mal ansehen. Dort wird auch im Bitmap gebuffert hab ich schon gesehen.
Seit er seinen neuen Computer hat, löst er alle Probleme, die er vorher nicht hatte!

pluto
Lazarusforum e. V.
Beiträge: 7192
Registriert: So 19. Nov 2006, 12:06
OS, Lazarus, FPC: Linux Mint 19.3
CPU-Target: AMD
Wohnort: Oldenburg(Oldenburg)

Re: Inhalt von Canvas zwischenspeichern

Beitrag von pluto »

Lese ich daraus, dass BitBlt u.U. direkt in den Grafikspeicher kopiert? Ich dachte damit könnt ich auch den Buffer füllen...
Die Funktion Kopiert nur den gewünschten Bereich von Canvas A zu Canvas B.

Eigentlich wollte ich, dass FBitmap genau so groß wie mein Anzeigecanvas ist. Ich hatte wohl angenommen, dass die Größensetzung meines Buffers FBitmap automatisch geschieht.
Das Passiert nicht Automatisch, dass könntest du z.b. bei Resize einbauen.
Die Kompo ist mir bisher nie aufgefallen, werd sie mir mal ansehen. Dort wird auch im Bitmap gebuffert hab ich schon gesehen.
Das ist eine wahre Goldgrube..... Kann ich dir nur empfehlen. Sie ist allerdings recht umfangreich.
MFG
Michael Springwald

RSE
Beiträge: 462
Registriert: Mi 30. Jul 2008, 13:11
OS, Lazarus, FPC: WinXP SP3 (L 0.9.28.2 FPC 2.2.4)
CPU-Target: 32Bit
Kontaktdaten:

Re: Inhalt von Canvas zwischenspeichern

Beitrag von RSE »

pluto hat geschrieben:Die Funktion Kopiert nur den gewünschten Bereich von Canvas A zu Canvas B.
Das klappt jetzt. FBitmap.SetSize hat gefehlt...
Offenbar ist das große Bitmap selbst der Teil, der die große (merkliche) Verzögerung beim Zeichnen meiner Kompo verursacht. Kann man das nicht durch irgendwas schnelleres ersetzen? 1000x1000px (Das entspricht also etwa 3-4MB) müssen sich doch schneller sichern und rücksichern lassen als 10x pro Sekunde, selbst auf meiner Laptopgrafikkarte Intel Mobile 965 (obwohl das damit ja nix zu tun haben dürfte). So benötigt ja das Puffern einen erheblich größeren Zeitaufwand als das Neukonstruieren (damit meine ich Zeile für Zeile den entsprechenden Text auf´s Canvas zeichnen) der gesamten Fläche. Kann man nicht den entsprechenden Speicherbereich vom Canvas ausfindig machen und diesen einfach kopieren? Kann man TCanvas nicht direkt instanzieren?

Das HTMLPanel ist so umfangreich, dass ich da keinen Überblick bekomme... Ich finde die Stelle nicht, an der das Zeichnen beginnt (Aufruf aller Nodes oder so). In Paint wird nur dieses Designtime-Bild gemalt.
Seit er seinen neuen Computer hat, löst er alle Probleme, die er vorher nicht hatte!

Benutzeravatar
theo
Beiträge: 10904
Registriert: Mo 11. Sep 2006, 19:01

Re: Inhalt von Canvas zwischenspeichern

Beitrag von theo »

Hast du's schon "direttissima" getestet?
Auf GTK2 scheint das z.B. auf den ersten Blick zu funzen:

procedure TForm1.Button1Click(Sender: TObject);
var ADC:HDC;
begin
ADC := GetDC(Handle);
BitBlt(ADC,0,-50,300,300,ADC,0,0,SRCCOPY);
ReleaseDC(Handle,ADC);
end;

RSE
Beiträge: 462
Registriert: Mi 30. Jul 2008, 13:11
OS, Lazarus, FPC: WinXP SP3 (L 0.9.28.2 FPC 2.2.4)
CPU-Target: 32Bit
Kontaktdaten:

Re: Inhalt von Canvas zwischenspeichern

Beitrag von RSE »

Das würde ja lediglich ein Verschieben verursachen. Ich wollte DoubleBuffering implementieren (hab ich soeben gefunden, dass das so heißt). Inzwischen hab ich auch bemerkt, dass meine ganze Herangehensweise noch suboptimal ist. Ich sollte besser auf dem Buffer konstruieren und diesen dann auf das AnzeigeCanvas kopieren. Bei Aufruf von Paint teste ich, ob der Buffer noch aktuell ist, aktualisiere ihn ggf. und kopiere ihn dann auf das AusgabeCanvas. Das werd ich jetzt erstmal so umbauen. Bisher hatte ich immer direkt auf dem Ausgabecanvas herumgezeichnet, diesem zum Schluss in den Buffer kopiert und den dann beim nächsten Paint zuerst wieder auf den Ausgabepuffer zurückkopiert...
Seit er seinen neuen Computer hat, löst er alle Probleme, die er vorher nicht hatte!

RSE
Beiträge: 462
Registriert: Mi 30. Jul 2008, 13:11
OS, Lazarus, FPC: WinXP SP3 (L 0.9.28.2 FPC 2.2.4)
CPU-Target: 32Bit
Kontaktdaten:

Re: Inhalt von Canvas zwischenspeichern

Beitrag von RSE »

Hm, das Zeichnen auf TBitmap.Canvas dauert so lange. Warum????? Das wird doch gar nicht dargestellt, es handelt sich lediglich um Operationen im Speicher... Muss man da erst irgendwelche Darstellungsroutinen abschalten?
Seit er seinen neuen Computer hat, löst er alle Probleme, die er vorher nicht hatte!

Benutzeravatar
theo
Beiträge: 10904
Registriert: Mo 11. Sep 2006, 19:01

Re: Inhalt von Canvas zwischenspeichern

Beitrag von theo »

Jetzt müsstest du schon den Code zeigen, ich weiss nicht wovon du genau sprichst, bzw. wie du das machst.
Der Teufel liegt da manchmal im Detail.

RSE
Beiträge: 462
Registriert: Mi 30. Jul 2008, 13:11
OS, Lazarus, FPC: WinXP SP3 (L 0.9.28.2 FPC 2.2.4)
CPU-Target: 32Bit
Kontaktdaten:

Re: Inhalt von Canvas zwischenspeichern

Beitrag von RSE »

Kein Problem:

Code: Alles auswählen

...
TRSEListBox = class(TCustomControl)
...
Buf: TBitmap;
ItemHeight: Integer; // Höhe jedes Items in Pixel
...
procedure TRSEListBox.DrawItem(APos: Integer); // 1 Zeile zeichnen
var
  ARect: TRect;
begin
  ARect := Rect(0,APos*ItemHeight,ClientWidth,(APos+1)*ItemHeight);
  Buf.Canvas.Brush.Color := Color;
  Buf.Canvas.FillRect(ARect);
end;
 
procedure TRSEListBox.Paint;
var
  i: Word;
begin
  Buf.SetSize(ClientWidth,((ClientHeight div ItemHeight)+1)*ItemHeight);
  for i := 0 to (ClientHeight div ItemHeight) do
    DrawItem(i);
  BitBlt(Canvas.Handle,0,0,ClientWidth,ClientHeight,Buf.Canvas.Handle,0,0,SRCCOPY); // hier wird ggf. das unterste Item abgeschnitten, falls ClientHeight mod ItemHeight <> 0
end;
 
constructor TRSEListBox.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  Buf := TBitmap.Create;
end;
 
destructor TRSEListBox.Destroy;
begin
  Buf.Free;
  inherited Destroy;
end;
 
procedure TRSEListBox.EraseBackground(DC: HDC);
begin
  // kein Löschen des Hintergrundes
end;
Das ist der gesamte Code meiner Komponente, was das Zeichnen betrifft. Ich hab auch extra noch mal alles andere auskommentiert, es ist immernoch lahm.
Seit er seinen neuen Computer hat, löst er alle Probleme, die er vorher nicht hatte!

Benutzeravatar
theo
Beiträge: 10904
Registriert: Mo 11. Sep 2006, 19:01

Re: Inhalt von Canvas zwischenspeichern

Beitrag von theo »

Das
Buf.SetSize(ClientWidth,((ClientHeight div ItemHeight)+1)*ItemHeight);
Würde ich mal in OnBoundsChanged resp. OnResize reintun. Das muss nicht immer gemacht werden und kostet Zeit.
Ansonsten wird's schwierig.
Wenn es deine Anwendung zulässt, könntest du nat. immer nur den invalidierten Teil neu zeichnen. (in Canvas.ClipRect, partiell invalidieren InvalidateRect() afair).

RSE
Beiträge: 462
Registriert: Mi 30. Jul 2008, 13:11
OS, Lazarus, FPC: WinXP SP3 (L 0.9.28.2 FPC 2.2.4)
CPU-Target: 32Bit
Kontaktdaten:

Re: Inhalt von Canvas zwischenspeichern

Beitrag von RSE »

Wenn ich direkt auf self.Canvas zeichne ist das alles viel schneller, das ist das, was ich nicht verstehe. Der gleiche Code, nur direkt gezeichnet. Selbst wenn ich den Buffer gar nicht auf self.Canvas übertrage, ist es lahm. Also ist TCustomControl.Canvas viel schneller als TBitmap.Canvas... das ist doch idiotisch. Es muss doch etwas geben, das schneller ist als TCustomControl.Canvas, weil es gar nicht am Bildschirm ausgegeben wird. Zumindest war ich immer der Meinung, dass die GDI-Grafikausgabe das langsame Glied ist.

Edit:
Ich sehne mich an die Zeit zurück, als man noch ab $B800 direkt im Mirror vom Bildschirmspeicher herumschreiben konnte (Dos-Textbildschirm)...
Zuletzt geändert von RSE am Fr 20. Nov 2009, 18:08, insgesamt 1-mal geändert.
Seit er seinen neuen Computer hat, löst er alle Probleme, die er vorher nicht hatte!

pluto
Lazarusforum e. V.
Beiträge: 7192
Registriert: So 19. Nov 2006, 12:06
OS, Lazarus, FPC: Linux Mint 19.3
CPU-Target: AMD
Wohnort: Oldenburg(Oldenburg)

Re: Inhalt von Canvas zwischenspeichern

Beitrag von pluto »

Bei meiner "Text Engine" mache ich das in etwa so: Der Buffer hat die Größe des Anzeige Canvas. Die Größe vom Buffer wird NUR einmal gesetzt, wie theo schon richtig gesagt habe. Nun kommt ein Trick den ich im HTML-Panel gesehen habe, der Canvas(Buffer) ist austauschbar. Bei mir wird er nur für das Scroll gebraucht, weil es sonst Flackert.
Du wolltest ihn ja dazu verwenden, den Inhalt nicht ständig neu zeichnen zu müssen. Das Dauert so lange, weil du alles doppelt machen musst:
Du musst ja erst alles in den Buffer zeichnen und ihn anschließend wieder auf den Anzeige-Canvas Kopieren. Aber anders geht es nicht.

Bei mir läuft das recht schnell. Bei mir dauert etwas anders länger. Der Trick ist einfach, die Größe nur zu verändern wenn es unbedingt nötig ist.

PS: Were das ein Thema für mein zweites Tutorial ? Scrollen und "Double Buffer" ?
MFG
Michael Springwald

Antworten