Canvas zwischenspeichern (DoubleBuffering langsamer?) gelöst
-
- 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
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).
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!
Re: Inhalt von Canvas zwischenspeichern
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;
-
- 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
Folgendes scheint zu funktionieren:
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.
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;
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!
-
- 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
Meines Wissens wird Paint aufgerufen wenn: Das Fenster überdenkt wird, wenn sich die Fenster Größe verändert hat.Für den Fall, dass Paint nur aufgerufen wird, weil sich ein Fenster über meinem Steuerelement bewegt
ErstensBitBlt(FBitmap.Canvas.Handle,0,0,ClientWidth,ClientHeight,Canvas.Handle,GetClientRect.Left,GetClientRect.Top,SRCCOPY); ersetzte, dann funktioniert es nicht mehr
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;
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
Michael Springwald
-
- 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
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:Meines Wissens wird Paint aufgerufen wenn: Das Fenster überdenkt wird, wenn sich die Fenster Größe verändert hat.Für den Fall, dass Paint nur aufgerufen wird, weil sich ein Fenster über meinem Steuerelement bewegt
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:ErstensBitBlt(FBitmap.Canvas.Handle,0,0,ClientWidth,ClientHeight,Canvas.Handle,GetClientRect.Left,GetClientRect.Top,SRCCOPY); ersetzte, dann funktioniert es nicht mehr
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.
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: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.
Die Kompo ist mir bisher nie aufgefallen, werd sie mir mal ansehen. Dort wird auch im Bitmap gebuffert hab ich schon gesehen.pluto hat geschrieben:Siehe hier zu auch das HTML-Panel.
Seit er seinen neuen Computer hat, löst er alle Probleme, die er vorher nicht hatte!
-
- 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
Die Funktion Kopiert nur den gewünschten Bereich von Canvas A zu Canvas B.Lese ich daraus, dass BitBlt u.U. direkt in den Grafikspeicher kopiert? Ich dachte damit könnt ich auch den Buffer füllen...
Das Passiert nicht Automatisch, dass könntest du z.b. bei Resize einbauen.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 ist eine wahre Goldgrube..... Kann ich dir nur empfehlen. Sie ist allerdings recht umfangreich.Die Kompo ist mir bisher nie aufgefallen, werd sie mir mal ansehen. Dort wird auch im Bitmap gebuffert hab ich schon gesehen.
MFG
Michael Springwald
Michael Springwald
-
- 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
Das klappt jetzt. FBitmap.SetSize hat gefehlt...pluto hat geschrieben:Die Funktion Kopiert nur den gewünschten Bereich von Canvas A zu Canvas B.
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!
Re: Inhalt von Canvas zwischenspeichern
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;
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;
-
- 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
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!
-
- 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
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!
Re: Inhalt von Canvas zwischenspeichern
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.
Der Teufel liegt da manchmal im Detail.
-
- 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
Kein Problem:
Das ist der gesamte Code meiner Komponente, was das Zeichnen betrifft. Ich hab auch extra noch mal alles andere auskommentiert, es ist immernoch lahm.
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;
Seit er seinen neuen Computer hat, löst er alle Probleme, die er vorher nicht hatte!
Re: Inhalt von Canvas zwischenspeichern
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).
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).
-
- 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
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)...
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!
-
- 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
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" ?
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
Michael Springwald