Anfängerfrage zu Sprites und DoubleBuffering

Anfängerfragen zum Programmieren mit PureBasic.
Rokur
Beiträge: 167
Registriert: 29.12.2005 09:58
Computerausstattung: Intel Core2 Quad (4x2,4 GHz), 4096 MB RAM, GForce 8800GTX 786 MB
Windows XP 32 Bit, PureBasic 4.40 (x86)

Beitrag von Rokur »

Auf bei Vollbildscreens kann es vorkommen das die Grafikausgabe durch andere Programme überdeckt wird und daher neu gezeichnet werden muss, z.B. wenn der Anwender per Alt+Tab wechselt, oder sich eine andere Anwendung per Popup in den Vordergrund drängt.

Was ist daran nicht "ökologisch"? Wenn du pro Schleifendurchlauf ein Delay(1) einbaust, dann frisst das Programm auch nicht die ganze CPU-Leistung (ohne den Delay hättest du allerdings eine Auslastung von 100%). Der Umstand das CPU und Grafikkarte dennoch ständig was zu tun haben stört auch nicht weiter, denn wenn dein Programm im Vordergrund läuft benötigt eh kein anderes Programm die Grafikkarte.

Wenn du etwas zeitgesteuert machen willst brauchst du so oder so einen Timer.

/edit: Hier mal ein Beispiel mit Doublebuffering und Zeitsteuerung. Ich habe zwar statt Sprites einfache Rechtecke per Box() ausgegeben, aber das Prinzip dürfte klar sein.

Code: Alles auswählen

#app = "Doublebuffering Beispiel"

InitSprite()
InitKeyboard()

ExamineDesktops()

OpenScreen(DesktopWidth(0),DesktopHeight(0),DesktopDepth(0),#app)

Define quit.l = #False

LoadFont(0,"Courier New",12)

Structure sprite_daten
  x.l
  y.l
  width.l
  height.l
  color.l
EndStructure

Define sprites_gezeichnet.l = -1
Define sprite_delay.l = 2000
Define sprite_timer.l = ElapsedMilliseconds() + sprite_delay
Dim sprites.sprite_daten(3)

sprites(0)\x = 55
sprites(0)\y = 85
sprites(0)\width = 30
sprites(0)\height = 10
sprites(0)\color = RGB(255,0,0)

sprites(1)\x = 90
sprites(1)\y = 90
sprites(1)\width = 40
sprites(1)\height = 50
sprites(1)\color = RGB(0,255,0)

sprites(2)\x = 155
sprites(2)\y = 105
sprites(2)\width = 25
sprites(2)\height = 90
sprites(2)\color = RGB(0,0,255)

sprites(3)\x = 200
sprites(3)\y = 120
sprites(3)\width = 40
sprites(3)\height = 50
sprites(3)\color = RGB(0,0,0)

Repeat
  Delay(1) ;wg. CPU-Auslastung
  FlipBuffers()
  ClearScreen(RGB(127,127,127))
  ;Fenster zeichnen
  StartDrawing(ScreenOutput())
    DrawingMode(#PB_2DDrawing_Default)
    Box(50,50,500,300,RGB(100,100,100))
    DrawingMode(#PB_2DDrawing_Outlined)
    Box(50,50,500,300,RGB(0,0,0))
    DrawingMode(#PB_2DDrawing_Transparent)
    DrawingFont(FontID(0))
    DrawText(55,55,#app,RGB(255,255,255))
    ;Fensterinhalte zeichnen
    For i = 0 To sprites_gezeichnet - 1
      Box(sprites(i)\x,sprites(i)\y,sprites(i)\width,sprites(i)\height,sprites(i)\color)
    Next i
    If ElapsedMilliseconds() >= sprite_timer And sprites_gezeichnet <= ArraySize(sprites(),1)
      sprites_gezeichnet+1
      sprite_timer = ElapsedMilliseconds() + sprite_delay
    EndIf
  StopDrawing()
  ;Benutzereingaben verarbeiten
  ExamineKeyboard()
  If KeyboardPushed(#PB_Key_Escape)
    quit = #True
  EndIf
Until quit = #True
Benutzeravatar
Kurzer
Beiträge: 1617
Registriert: 25.04.2006 17:29
Wohnort: Nähe Hamburg

Beitrag von Kurzer »

Also die Aussage "ökologisch" bezog sich für meinen Teil auf das "Bauchgefühl eines Puristen/Minimalisten", der ich mal war [Amiga, lange her usw.].

Wieviel mehr an Strom durch diese Schleife verbraucht wird, wird vermutlich nur schwer meßbar sein, aber "es fühlt sich für mich einfach nicht gut an", wenn ich ein Standbild 75 mal pro Sekunde hinzeichnen muß, um es sofort danach wieder zu löschen, wieder zu zeichnen usw...

Doubble buffering ist in vielen Anwendungsfällen die bessere Methode, aber meiner Erfahrung nach eben nicht immer. Es kann Dinge auch verkomplizieren und die CPU-Last in die Höhe treiben.
Okay, wenn der Anwender den Desktop nach vorn geholt hat und der Screen dadurch überschrieben wurde, dann muß man natürlich alles neuzeichnen.

Aber um mal bei meinem Beispiel des Graphen zu bleiben.

Angenommen, Du hasten einen Screen mit 800 x 600 Pixel und willst darauf gemessene Temperaturwerte als fortlaufen Graphen anzeigen (Oszilloscope, EKG, Krankenhaus.. ich denke es ist klar was gemeint ist).

Der Graph soll beginnend am linken Rand des Screens hin zum rechten Rand gezeichnet werden (X-Achse). Die höhe der Temperatur wird dabei auf der Y-Achse eingetragen. Weiterhin wird jede Sekunde eine Messung vorgenommen.

Nach "alter Methode" würde man den gemessenen Wert nun einfach an aktueller X Position gemäß Temperaturwert auf der Y-Achse plotten und den X Zähler um einen Pixel erhöhen. Dann wieder eine Messung durchführen, den Wert eintragen (jetzt an X Position 1), den X Zähler erhöhen usw.

Am rechten Rand angekommen setzt man den X Zähler zurück.
(Vor dem plotten des Pixels wird natürlich die aktuelle Spalte gelöscht, also quasi eine schwarze Linie von X,Ymin nach X,Ymax gezogen, damit der vorherige Pixel gelöscht wird.

Mit doubble buffering musst Du Deine Messwerte erst in ein internes Array schreiben (Speicherbedarf steigt) und dann mußt Du bei jedem Framedurchlauf (nicht nur bei jeder Messung, nein bei jedem Framedurchlauf!) den gesamten Graphen von X=0 bis zur aktuellen X Position aus dem Array lesen und ihn auf den backbuffer plotten.
Man spart sich zwar das Löschen des alten Pixels, aber um welchen Preis? -> Ein vielfaches an CPU-Last und komplizierter Code.

Du mußt dafür extra noch eine Schleife schreiben, die Dir die Pixel wieder restauriert, welche Du gezwungenermaßen gerade eben erst mit einem ClearScreen gelöscht hast.

Von daher stehe ich noch immer dazu: Es wäre schön, wenn man auch die Möglichkeit hätte ohne double buffer zu arbeiten.
"Never run a changing system!" | "Unterhalten sich zwei Alleinunterhalter... Paradox, oder?"
PB 6.12 x64, OS: Win 11 24H2 x64, Desktopscaling: 150%, CPU: I7 12700 H, RAM: 32 GB, GPU: Intel(R) Iris(R) Xe Graphics | NVIDIA GeForce RTX 3070
Useralter in 2025: 57 Jahre.
Rokur
Beiträge: 167
Registriert: 29.12.2005 09:58
Computerausstattung: Intel Core2 Quad (4x2,4 GHz), 4096 MB RAM, GForce 8800GTX 786 MB
Windows XP 32 Bit, PureBasic 4.40 (x86)

Beitrag von Rokur »

Und wenn der Benutzer zum Desktop wechselt oder sich ein anderes Programm in den Vordergrund drängt, dann sind alle Messwerte weg.

Ein Array mit einer Hand von Zahlen zu durchlaufen und die paar Pixel zu zeichnen, das schafft auch ein 386er noch ohne Mühe, die CPU-Auslastung dürfte mit dem Delay(1) wenige Prozent nicht übersteigen. :wink:
Der zusätzliche Speicherbedarf für ein paar Werte beträgt auch nur einen Bruchteil des restlichen Programms, weil du den Grafikspeicher usw. ja sowiso brauchst, egal wie oft du darauf zeichnest.

Ein Array hätte übrigens den Vorteil, jederzeit auf vergangene Messwerte zugreifen zu können, wenn du z.B. den Durchschnittswert der letzten 10 Messungen mit anzeigen willst usw., das brauchst du vielleicht am Anfang nicht, aber wenn das Programm wächst bist du froh wenn du nicht wieder alles komplett umbauen musst.

Aber ich verstehe was du meinst. Der Umstieg auf neue Technologien oder Prinzipien bedarf erst mal einer Eingewöhnung. Das ist das Gleiche wie wenn man jahrelang prozedural programmiert und dann auf eine objektorientierte Sprache umsteigst. Da klatscht man seinen Spaghetti-Code auch erstmal in wenige, sehr lange Methoden, so wie man das gewöhnt ist. Die Aufteilung in viele Objekte aus nur wenigen Zeilen mit Vererbungen usw. kommt einem erstmal wahnsinnig sinnlos und kompliziert vor. Aber nach und nach erschließt sich einem dann der Nutzen und irgendwann ist es ganz normal so zu arbeiten.

Wenn du ohne Doublebuffering arbeiten möchtest, wieso benutzt du dann einen Screen? Ein Rahmenloses, maximiertes Fenster würds da genauso tun.
Benutzeravatar
Kurzer
Beiträge: 1617
Registriert: 25.04.2006 17:29
Wohnort: Nähe Hamburg

Beitrag von Kurzer »

Rokur hat geschrieben:Wenn du ohne Doublebuffering arbeiten möchtest, wieso benutzt du dann einen Screen? Ein Rahmenloses, maximiertes Fenster würds da genauso tun.
Will ich momentan ja gar nicht, der Originalposter in diesem Beitrag ist CptGreenwood. <)
Aber ich kann seine Beweggründe verstehen und hab mich einfach bewogen gefühlt, sein Anliegen mit eigenen Worten noch einmal zu verdeutlichen.

Klar geht das auch mit einem rahmenlosen Fenster, aber ich denke die Diskussion wird jetzt zu philosophisch. :lol:
Es geht nicht und damit ist der Fisch nunmal geputzt.
"Never run a changing system!" | "Unterhalten sich zwei Alleinunterhalter... Paradox, oder?"
PB 6.12 x64, OS: Win 11 24H2 x64, Desktopscaling: 150%, CPU: I7 12700 H, RAM: 32 GB, GPU: Intel(R) Iris(R) Xe Graphics | NVIDIA GeForce RTX 3070
Useralter in 2025: 57 Jahre.
Kaeru Gaman
Beiträge: 17389
Registriert: 10.11.2004 03:22

Beitrag von Kaeru Gaman »

einfach mit einem rahmenlosen fenster ist disfug,
denn der fensterhintergrund wird definitiv gelöscht wenn man ein fremdfenster drüberzieht.

also, wenn man auf eine Fensteroberfläche darstellen will, plotte man auf ein Image und stellt das in einem ImageGadget dar,
da fällt dann das DoubleBuffering komplett weg, und es treten auch keine timing-probleme auf.
http://www.purebasic.fr/german/viewtopic.php?t=19394


das DoubleBuffering umgeht man "under the hood" ebenfalls, wenn man auf einem modernen system (XP oder höher, DX7 oder höher) einen Windowedscreen verwendet!

der benutzt nämlich letztendlich gar keinen DoubleBuffer mehr, sondern simuliert ihn nur!

Code: Alles auswählen

#TimeStep = 50
#Width    = 800
#Height   = 600

InitSprite()
OpenWindow(0, 0,0, #Width,#Height, "Windowed Screen")
  OpenWindowedScreen( WindowID(0), 0,0, #Width,#Height, 0,0,0)


Timer = ElapsedMilliseconds() + #TimeStep
Repeat
  Event = WaitWindowEvent(10)
  Select Event
    Case #Null
      If Timer < ElapsedMilliseconds()
        Timer + #TimeStep
        N + #Width / 100
        X = N % #Width
        Y = #Height / 2 + #Height / 2.5 * Sin( N / ( #Width / 10 ) )
        StartDrawing( ScreenOutput() )
          Circle( X, Y, #Height / 40 , $C04020 )
          Circle( X, Y, #Height / 60 , $40C0F0 )
        StopDrawing()
        FlipBuffers()
      EndIf
    Case #PB_Event_CloseWindow
      EXIT = 1
  EndSelect
Until EXIT
Der Narr denkt er sei ein weiser Mann.
Der Weise weiß, dass er ein Narr ist.
Burstnibbler
Beiträge: 58
Registriert: 04.10.2008 12:10

Beitrag von Burstnibbler »

Nur mal so aus Spaß : Bedingungsabhängiges Flipbuffern :freak:

Code: Alles auswählen

EnableExplicit
;
Enumeration
 #Sprite_BG
 #Sprite_Ovl1
 #Sprite_Ovl2
EndEnumeration 
#Trigger=2000 ; Millisekunden
;
Define.l Y, Timer, Counter, FlipCounter, Draw=#True
;
InitSprite()
InitKeyboard()
;
OpenScreen(800,600,32,"Flipper")
CreateSprite(#Sprite_BG,800,600,0)
CreateSprite(#Sprite_Ovl1,300,150,0)
CreateSprite(#Sprite_Ovl2,250,100,0)
;
StartDrawing(SpriteOutput(#Sprite_BG)) 
 For Y=0 To 599 
  Line(0,Y,800,1,RGB(255*Y/599,0,0)) 
 Next 
StopDrawing()
StartDrawing(SpriteOutput(#Sprite_Ovl1)) 
 Box(0,0,300,150,#Blue)
StopDrawing()
StartDrawing(SpriteOutput(#Sprite_Ovl2)) 
 Box(0,0,250,100,#White)
StopDrawing()
;
Timer=ElapsedMilliseconds()
;
Repeat
ExamineKeyboard()
 ;
 If Draw
  FlipCounter+1 : Draw!1
  DisplaySprite(#Sprite_BG,0,0)
  StartDrawing(ScreenOutput())
   DrawingMode(#PB_2DDrawing_Transparent) 
   DrawText(0,0,"Flipbuffers-Aufrufe: "+Str(FlipCounter),#White) 
  StopDrawing()
  Select Counter
   Case 1
    DisplaySprite(#Sprite_Ovl1,250,225)
   Case 2
    DisplaySprite(#Sprite_Ovl1,250,225)
    DisplaySprite(#Sprite_Ovl2,275,250)
  EndSelect
  FlipBuffers(2) 
 EndIf
 ;
 If ElapsedMilliseconds()-Timer>=#Trigger
  Timer=ElapsedMilliseconds()
  Draw!1 : Counter+1 : Counter%3
 EndIf
 ;
Until KeyboardPushed(#PB_Key_Escape)
Mfg,
Burstnibbler
Benutzeravatar
Kurzer
Beiträge: 1617
Registriert: 25.04.2006 17:29
Wohnort: Nähe Hamburg

Beitrag von Kurzer »

Kaeru Gaman hat geschrieben:...das DoubleBuffering umgeht man "under the hood" ebenfalls, wenn man auf einem modernen system (XP oder höher, DX7 oder höher) einen Windowedscreen verwendet!

der benutzt nämlich letztendlich gar keinen DoubleBuffer mehr, sondern simuliert ihn nur!
:shock: Alter! Das isses doch. :lol: Gut zu wissen!

Und guck mal, wenn wir das noch genau so groß machen wie den Desktop und das Fenster rahmenlos setzen... was hamma dann? :lol:
Einen Quasi-Screen ohne Dubble-Buffer. Geil-o-mat! :allright:

Code: Alles auswählen

ExamineDesktops()

#TimeStep = 50
DWidth.i    = DesktopWidth(0)
DHeight.i   = DesktopHeight(0)

InitSprite()
OpenWindow(0, 0,0, DWidth,DHeight, "Windowed Screen", #PB_Window_BorderLess)
  OpenWindowedScreen( WindowID(0), 0,0, DWidth,DHeight, 0,0,0)

Timer = ElapsedMilliseconds() + #TimeStep

Repeat
  Event = WaitWindowEvent(10)
  If Timer < ElapsedMilliseconds()
    Timer + #TimeStep
    N + DWidth / 100
    X = N % DWidth
    Y = DHeight / 2 + DHeight / 2.5 * Sin( N / ( DWidth / 10 ) )
    StartDrawing( ScreenOutput() )
      Circle( X, Y, DHeight / 40 , $C04020 )
      Circle( X, Y, DHeight / 60 , $40C0F0 )
    StopDrawing()
    FlipBuffers()
  EndIf

  Select Event
    Case #PB_Event_CloseWindow
      EXIT = 1
  EndSelect
Until EXIT
Beenden mit ALT + F4 ;)

PS: Die Eventabfrage #Null hab ich mal rausgenommen und das drawen vor das Select gesetzt, dann ruckelts nicht mehr, wenn man die Maus bewegt.

PPS: Und man kann sogar locker mit ALT + TAB zwischen "Screen" und anderen Fenstern wechseln, ohne das was von der Grafik zerpflückt wird.
Schön, daß wir drüber geredet haben. :lol:
"Never run a changing system!" | "Unterhalten sich zwei Alleinunterhalter... Paradox, oder?"
PB 6.12 x64, OS: Win 11 24H2 x64, Desktopscaling: 150%, CPU: I7 12700 H, RAM: 32 GB, GPU: Intel(R) Iris(R) Xe Graphics | NVIDIA GeForce RTX 3070
Useralter in 2025: 57 Jahre.
Kaeru Gaman
Beiträge: 17389
Registriert: 10.11.2004 03:22

Beitrag von Kaeru Gaman »

> Beenden mit ALT + F4

ahja, da machste halt noch n examinekeyboard rein, und setzt EXIT bei ESC...

> Die Eventabfrage #Null hab ich mal rausgenommen und das drawen vor das Select gesetzt, dann ruckelts nicht mehr, wenn man die Maus bewegt.

besser timern, aber so geht das schon für quick'n'dirty...

nur niemals vergessen, alle events zu processen...


mein beispiel war für geringe framerate bzw. sporadisches neuzeichnen ausgelegt.
bei "normaler" framerate von 30+ muss man die schleife etwas dynamischer gestalten.
Der Narr denkt er sei ein weiser Mann.
Der Weise weiß, dass er ein Narr ist.
Antworten