Seite 1 von 2

Wechsel zwischen Fullscreen- und Windowed

Verfasst: 03.01.2007 23:06
von ZeHa
Servus,

zugegeben, das ist jetzt nicht grad der grandioseste Tip, aber falls einer von euch noch am Grübeln war, wie er sein Spiel so programmieren soll, daß ein reibungsloser Wechsel zwischen Fullscreen- und WindowedScreen möglich ist, OHNE daß die Grafiken jedesmal neu geladen werden können, hat hier eine funktionierende Lösung.

DarkDragon hat vorgeschlagen, ein maximiertes OpenGL-Fenster zu benutzen. Wer aber lieber die PB-internen Sprite-Befehle benutzen will, kann folgendes machen:


HINWEIS: (EDIT)
Es lohnt sich definitiv, die nachfolgende Diskussion im Thread zu lesen, da die hier beschriebene Lösung Nachteile hat was den Speicherverbrauch angeht usw, DD und Kaeru haben da auch ein paar Lösungsvorschläge parat. Also bitte kritisch betrachten und nur verwenden, wenn's in eurem Fall gut paßt.

Code: Alles auswählen

Structure spr
  address.l
  number.l
EndStructure

Global NewList spriteList.spr()


; Diese Procedure lädt ein Sprite in den Speicher, das hat
; den Vorteil, daß das Sprite bei einem CloseScreen()
; nicht wieder gelöscht wird!
; Handhabung ist genau gleich wie bei LoadSprite(), mit
; der Ausnahme, daß man keine Flags angeben kann.
; Diese kann man aber in der Procedure catchGraphics()
; angeben (weiter unten).

Procedure.l loadSpriteToMemory(number, filename$)
  If ReadFile(0, filename$)
    ;logger("loading sprite #" + Str(number) + " (" + filename$ + ")")
    
    AddElement(spriteList())
    *newSpriteData = AllocateMemory(Lof(0))
    
    ReadData(0, *newSpriteData, Lof(0))
    CloseFile(0)
    
    spriteList()\address = *newSpriteData
    spriteList()\number = number
    
    ProcedureReturn 1
  EndIf
  
  ProcedureReturn 0
EndProcedure



; Das hier stellt die Procedure dar, die im eigentlichen Spiel
; am Anfang aufgerufen werden muß, um alle Grafiken zu laden.
; Hier kann man vorgehen wie gewohnt, nur daß man halt
; loadSpriteToMemory() aufruft und nicht LoadSprite().

Procedure loadGraphics()
  Static loaded = 0
  
  If loaded
    ProcedureReturn
  EndIf

  loadSpriteToMemory(666, "the beast.bmp")
  loadSpriteToMemory(667, "neighbor of the beast.bmp")
  ;...
  ;...
  ;...
  
  loaded = 1
EndProcedure



; Hier werden die Grafiken vom normalen RAM in den Videospeicher
; übertragen. Wie bereits erwähnt, wer Flags benötigt, sollte diese
; natürlich hier in den CatchSprite()-Aufruf reinschreiben.

Procedure catchGraphics()
  TransparentSpriteColor(-1, RGB(255,0,255))
  
  ForEach spriteList()
    CatchSprite(spriteList()\number, spriteList()\address)
  Next
EndProcedure
Das (oben) ist der Code, um die Sprites zu laden. Sie werden somit nur ein einziges Mal geladen und gehen beim CloseScreen() nicht verloren.
Um sie benutzen zu können, müssen sie anschließend noch gecatcht werden mit catchGraphics(). Wer will, kann diesen Aufruf natürlich auch direkt in die loadGraphics() einbauen (innerhalb von "if loaded").

Hier nun noch ein einfacher Toggle-Mechanismus. Am besten die Funktion toggleScreenMode() auf F12 legen oder so, und ausprobieren ;)

Code: Alles auswählen

Global fullscreen.b
Global openScreenAvailable.b   ;um sich zu vergewissern, daß bereits ein Screen geöffnet wurde

#GAME_WINDOW = 0



; öffnet einen neuen Screen im Fullscreen-Modus

Procedure setFullscreenMode()
  If IsWindow(#GAME_WINDOW)
    CloseWindow(#GAME_WINDOW)
  EndIf
  
  If openScreenAvailable
    CloseScreen()
  EndIf
  
  If OpenScreen(800, 600, 32, "STOP THIS CAR")
    ClearScreen(RGB(0,0,0))
    FlipBuffers()
    
    loadGraphics()
    catchGraphics()
    
    openScreenAvailable = 1
    ProcedureReturn 1
  EndIf
  
  ProcedureReturn 0
EndProcedure



; öffnet ein neues Fenster und darin einen Screen

Procedure setWindowedMode()
  If openScreenAvailable
    CloseScreen()
  EndIf
  
  If OpenWindow(0, 0, 0, 800, 600, "STOP THIS CAR", #PB_Window_ScreenCentered)
    If OpenWindowedScreen(WindowID(#GAME_WINDOW), 0, 0, 800, 600, 0, 0, 0)
      ClearScreen(RGB(0,0,0))
      FlipBuffers()
      
      loadGraphics()
      catchGraphics()
      
      openScreenAvailable = 1
      ProcedureReturn 1
    EndIf
  EndIf
  
  ProcedureReturn 0
EndProcedure



; diese Funktion muß beim Togglen aufgerufen werden, also
; z.B. bei einem Druck auf F12 oder bei der Auswahl des
; entsprechenden Menüpunktes im Hauptmenü des Spiels

Procedure toggleScreenMode()
  If fullScreen = 1
    fullScreen = 0
    setWindowedMode()
  Else
    fullScreen = 1
    setFullscreenMode()
  EndIf
  
  ;logger("toggled fullscreen mode.")
EndProcedure

Verfasst: 04.01.2007 08:29
von #NULL
prima.
nur man hat man eben den doppelten speicherverbrauch. und catchsprite kostet auch zeit, wenn es auch weniger ist.

..hab grad mal getestet. bei einem 256x128 bmp braqucht CatchSprite bei mir etwa halb so lange. bei einem 32x16 bmp ist die geschwindigkeit beinahe gleich. vielleicht hab ich aber auch bei dem test irgendwas nicht bedacht(?)

Code: Alles auswählen

InitSprite()
OpenWindow(0,0,0,10,10,"")
OpenWindowedScreen(WindowID(0),0,0,10,10,0,0,0)

;file.s="C:\test\shape256.bmp"
file.s="C:\test\shape32.bmp"

n=1000

Dim pic(n)

t1=ElapsedMilliseconds()
For i=0 To n
  pic(i)=LoadSprite(#PB_Any,file)
  ;Debug IsSprite(pic(i))
Next
t1=ElapsedMilliseconds()-t1

;Debug ""

Dim pic(n)
Dim *p(n)
For i=0 To n
  ReadFile(0,file)
  *p(i)=AllocateMemory( Lof(0) )
  ReadData(0, *p(i), Lof(0) )
  CloseFile(0)
Next


t2=ElapsedMilliseconds()
For i=0 To n
  pic(i)=CatchSprite( #PB_Any, *p(i) )
  ;Debug IsSprite( pic(i) )
Next
t2=ElapsedMilliseconds()-t2


MessageRequester("",Str(t1)+#CRLF$+Str(t2))

Verfasst: 04.01.2007 09:42
von ZeHa
Ich hab bisher keinen genauen Test gemacht, aber einen Unterschied habe ich auf jeden Fall bemerkt. Zudem wirst Du auch einen sehr starken Unterschied feststellen, wenn Dein Spiel zum Beispiel von einer CD gestartet wird - da lohnt sich das auf jeden Fall.

Das mit dem doppelten Speicherverbrauch ist auf jeden Fall ein Nachteil, daher sollte man sich das schon gut überlegen. Bei unserem momentanen Spiel ist die Menge allerdings tragbar, daher werden wir das nun so auch einsetzen.

Eine andere Möglichkeit wäre es evtl, beim Wechsel des Screen-Modus sämtliche Sprites in den RAM zu kopieren, diese danach wieder in den VideoRAM zu schreiben und aus dem RAM wieder zu löschen. Das kostet dann nur kurze Zeit doppelten Speicher, dafür aber auch wieder mehr Zeit.

Bei Deinem Test bin ich mir nicht ganz sicher, ob der so hinhaut. Du lädst 1000x das gleiche Sprite, und es wäre zumindest denkbar, daß das mehrfache Einlesen einer Datei allgemein schneller geht als wenn es sich um 1000 verschiedene handelt (z.B. könnte ich mir vorstellen, daß das Betriebssystem einem da ein wenig unter die Arme greift - das ist aber reine Spekulation).

In meinem Fall läuft es zudem so ab, daß ich nicht explizit angebe, welche Sprites geladen werden, sondern daß eine Schleife sich drum kümmert und die entsprechenden Dateinamen generiert. Wenn die Dateien vorhanden sind, werden sie geladen. Das geht dann auch noch mal 'ne Ecke länger - daher ist das catchen in dem Fall für mich auch schneller, weil nun mit ForEach durch die Liste iteriert wird, und da sind natürlich nur Elemente drin, die tatsächlich benötigt werden. In der Schleife beim Laden wird dagegen jedes möglicherweise existierende Sprite geladen (in meinem Fall sind das 4096 Durchläufe, tatsächlich existieren aber nur ca. 50 Sprites davon).

Re: Wechsel zwischen Fullscreen- und Windowed

Verfasst: 04.01.2007 09:47
von DarkDragon
ZeHa hat geschrieben:DarkDragon hat vorgeschlagen, ein maximiertes OpenGL-Fenster zu benutzen. Wer aber lieber die PB-internen Sprite-Befehle benutzen will, kann folgendes machen:
Ne, hab ich nicht vorgeschlagen, ich hab vorgeschlagen zuerst nen WindowedScreen zu öffnen auf nem Fenster und beim wechsel zu Fullscreen einfach das Fenster Maximieren und auf Borderless schalten.

Verfasst: 04.01.2007 09:55
von ZeHa
In irgendeinem Thread hast Du aber was von OpenGL geschrieben ;)

Aber beim maximierten Fenster muß man sich dessen bewußt sein, daß die Framerate nicht individuell setzbar ist sondern von der derzeitigen Desktop-Einstellung abhängt.

Verfasst: 04.01.2007 10:02
von DarkDragon
ZeHa hat geschrieben:In irgendeinem Thread hast Du aber was von OpenGL geschrieben ;)

Aber beim maximierten Fenster muß man sich dessen bewußt sein, daß die Framerate nicht individuell setzbar ist sondern von der derzeitigen Desktop-Einstellung abhängt.
In dem Thread gings dann grad um OpenGL, wie man das als Vollbild macht, aber du kannst das ja auch auf PB übertragen.

Jo, klar desshalb nimmt man auch zum maximieren an eine bestimmte auflösung noch ChangeDisplaySettings...

Verfasst: 04.01.2007 10:55
von Kaeru Gaman
yup. da muss ich DD uneingeschränkt zustimmen.

die changedisplaysettings-geschichte hatten wir in letzter zeit öfter.
das ermöglicht andere tatsächliche auflösungen, und andere refreshs als aufm desktop.

aber das sollte so eigentlich auch gar nicht nötig sein:
a) wenn dein game mit derselben routine in nem windowedscreen laufen soll,
musst du dich eh auf die gegebene refresh-rate einstellen.
(außer, du verwendest sowieso nen change ganz zu beginn)
b) der windowedscreen auf autostretch ermöglicht ja eine kleinere rechnerische auflösung.
klar, ob und wieweit AA stattfindet, hängt von den hardwareeinstellungen ab
-> kein einheitliches bild auf verschieden konfigurierten systemen.
aber: der blackscreen beim umschalten entfällt.

muss man sich halt überlegen, was einem wichtiger ist.

aber seit XP besteht sowieso kein wirklicher unterschied mehr
zwischen dem maximierten windowedscreen mit angepasster auflösung,
und dem sogenannten fullscreen.
bei NT-Basierten systemen bin ich mir nicht sicher...

Verfasst: 04.01.2007 11:21
von ZeHa
Naja wenn man aber die Auflösung direkt ändert, verschieben sich Icons, man muß die Taskbar von Hand ausblenden etc etc, das ist immer so 'ne Sache. Da finde ich ein OpenScreen() einfach simpler, vor allem halt auch, weil es "wasserdicht" ist. Da gab es bei mir noch nie Probleme. In anderen Fällen kann es schon auch mal vorkommen, daß Dein Spiel abstürzt und Du hinterher immer noch auf 320x240 in Deinem Windows rumeiern darfst oder nur noch 60 Hz statt 85 hast oder so.

Klar, einen Screen zu stretchen ist auch noch 'ne Möglichkeit, aber das finde ich ehrlich gesagt keine gute Lösung, weil das oftmals scheußlich aussieht, sei es wegen AA oder wegen Interpolation. Da ist mir eine tatsächliche Auflösungsänderung lieber (zumindest auf CRTs).

Verfasst: 04.01.2007 11:26
von Kaeru Gaman
nein eben nicht.

die programmseitige auflösungsänderung per API läßt icons und taskleiste komplett in ruhe!

deswegen hab ich mir ja son mini-tool geschrieben, um die auflösung mal schnell ändern zu können,
wenn ein kleiner free-game mit nem 640x480 windowedscreen daherkommt,
da würde ich ja auf meinem 1280x1024 nix erkennen.

...hab ich vor nem monat erst gepostet den code.. is echt winzig... ich such ma...

hier isser
http://www.purebasic.fr/german/viewtopi ... 528#125528

PS:
das "hängenbleiben" in der falschen auflösung ist ein problem bei älteren programmen.
ich weiß nicht genau, womit das zusammenhängt, aber das sollte hier nicht auftreten,
da beim beenden des programms die auflösung automatisch zurückgestellt wird, von windows selber.
wie du siehst, hab ich beim "exit" kein zurücksetzen drin, ich beende einfach nur.

Verfasst: 04.01.2007 11:35
von ZeHa
Ahh okay, ich werd's mir mal anschauen. Aber wie sieht es mit der Wiederholfrequenz aus? Die kann ich ja nicht einfach so dem User "aufzwingen", wie würdest Du das dann lösen? Oder fragst Du einfach alle funktionierenden vorher ab?

BTW, für Deinen Code könntest Du doch hier auch einen Thread eröffnen, ist sicherlich noch für andere brauchbar ;)