Code to test this (run it with a main monitor that has a lower frequency than the second monitor, for example the main monitor is 60Hz and the secondary monitor is 144Hz. If you turn off DPI aware the secondary monitor will run at it's native frequency fine, but if you turn it on it will only run as fast as the main monitor's frequency):
Code: Select all
EnableExplicit
DeclareModule Fonts
#Max_Fonts = 10
#Font_Num_Characters = 95
#Font_Num_Characters_Numbers = 10
#Font_Num_Characters_Uppercase = 26
#Font_Num_Characters_Lowercase = 26
#Font_Num_Characters_Symbol = 33
#Font_Characters_Select = 1 ; used if you just want to select a few
#Font_Characters_Numbers = 2
#Font_Characters_Uppercase = 4
#Font_Characters_Lowercase = 8
#Font_Characters_Symbol_1 = 16
#Font_Characters_Symbol_2 = 32
#Font_Characters_Symbol_3 = 64
#Font_Characters_Symbol_4 = 128
#Font_Characters_All = 256
Structure Font_Character_Structure
Sprite_ID.i
Image_ID.i
Width.i
Height.i
Loaded.i
EndStructure
Structure Font_Structure
Name.s
Filename.s
Size.i
Character.Font_Character_Structure[#Font_Num_Characters]
Crop_Top_Ratio.d
Crop_Bottom_Ratio.d
Crop_Left_Ratio.d
Crop_Right_Ratio.d
Colour.i
Back_Colour.i
Available.i ; 0 = select, 1 = numbers, 2 = uppercase, 4 = lowercase, 8 = symbol 1, 16 = symbol 2, 32 = symbol 3, 64 = symbol 4, 128 = all
Select_Available.s
Border.i
Border_Colour.i
Loaded.i
EndStructure
Dim *Fonts.Font_Structure(#Max_Fonts)
Declare Initialise(Array *Fonts.Font_Structure(1))
Declare.s GetChar(Char.i)
Declare.i GetCharNum(Char.s)
Declare.i GetNum(ASCII.i)
Declare.i CreateFont(Array *Fonts.Font_Structure(1), Font_ID.i, Flags.i=0)
Declare.i GetCharacterWidth(Array *Fonts.Font_Structure(1), Font_ID.i, Char.s)
Declare.i GetCharacterHeight(Array *Fonts.Font_Structure(1), Font_ID.i, Char.s)
Declare DisplayCharacterSprite(Array *Fonts.Font_Structure(1), Font_ID.i, Char.s, x.i, y.i)
Declare DisplayCharacterImage(Array *Fonts.Font_Structure(1), Font_ID.i, Char.s, x.i, y.i)
Declare DisplayStringSprite(Array *Fonts.Font_Structure(1), Font_ID.i, Text.s, x.i, y.i, Spacing.i=0)
Declare DisplayStringImage(Array *Fonts.Font_Structure(1), Font_ID.i, Text.s, x.i, y.i, Spacing.i=0)
Declare.i GetStringWidth(Array *Fonts.Font_Structure(1), Font_ID.i, Text.s)
Declare Shutdown(Array *Fonts.Font_Structure(1))
EndDeclareModule
Module Fonts
Procedure Initialise(Array *Fonts.Font_Structure(1))
Protected c.i
For c = 0 To #Max_Fonts
*Fonts(c) = AllocateStructure(Font_Structure)
Next c
EndProcedure
Procedure.s GetChar(Char.i)
Select Char
Case 0 To 9: ProcedureReturn(Chr(48+Char))
Case 10 To 35: ProcedureReturn(Chr(55+Char))
Case 36 To 61: ProcedureReturn(Chr(61+Char))
Case 62 To 77: ProcedureReturn(Chr(Char-30))
Case 78 To 84: ProcedureReturn(Chr(Char-20))
Case 85 To 90: ProcedureReturn(Chr(6+Char))
Case 91 To 94: ProcedureReturn(Chr(32+Char))
EndSelect
EndProcedure
Procedure.i GetCharNum(Char.s)
Protected ASCII.i = Asc(Char)
Select ASCII
Case 48 To 57:ProcedureReturn(ASCII-48)
Case 65 To 90:ProcedureReturn(ASCII-55)
Case 97 To 122:ProcedureReturn(ASCII-61)
Case 32 To 47:ProcedureReturn(ASCII+30)
Case 58 To 64:ProcedureReturn(ASCII+20)
Case 91 To 96:ProcedureReturn(ASCII-6)
Case 123 To 126:ProcedureReturn(ASCII-32)
EndSelect
EndProcedure
Procedure.i GetNum(ASCII.i)
Select ASCII
Case 48 To 57:ProcedureReturn(ASCII-48)
Case 65 To 90:ProcedureReturn(ASCII-55)
Case 97 To 122:ProcedureReturn(ASCII-61)
Case 32 To 47:ProcedureReturn(ASCII+30)
Case 58 To 64:ProcedureReturn(ASCII+20)
Case 91 To 96:ProcedureReturn(ASCII-6)
Case 123 To 126:ProcedureReturn(ASCII-32)
EndSelect
EndProcedure
Procedure.i CheckCreateCharacter(Array *Fonts.Font_Structure(1), Font_ID.i, c.i)
; Checks whether to create the character based on the settings provided
Protected.i Result = 0
If *Fonts(Font_ID)\Available & #Font_Characters_Select
If FindString(*Fonts(Font_ID)\Select_Available, GetChar(c)):Result = 1:EndIf
EndIf
If *Fonts(Font_ID)\Available & #Font_Characters_Numbers And c>=0 And c<=9:Result = 1:EndIf
If *Fonts(Font_ID)\Available & #Font_Characters_Uppercase And c>=10 And c<=35:Result = 1:EndIf
If *Fonts(Font_ID)\Available & #Font_Characters_Lowercase And c>=36 And c<=61:Result = 1:EndIf
If *Fonts(Font_ID)\Available & #Font_Characters_Symbol_1 And c>=62 And c<=77:Result = 1:EndIf
If *Fonts(Font_ID)\Available & #Font_Characters_Symbol_2 And c>=78 And c<=84:Result = 1:EndIf
If *Fonts(Font_ID)\Available & #Font_Characters_Symbol_3 And c>=85 And c<=90:Result = 1:EndIf
If *Fonts(Font_ID)\Available & #Font_Characters_Symbol_4 And c>=91 And c<=94:Result = 1:EndIf
If *Fonts(Font_ID)\Available & #Font_Characters_All:Result = 1:EndIf
ProcedureReturn Result
EndProcedure
Procedure.i CreateFont(Array *Fonts.Font_Structure(1), Font_ID.i, Flags.i=0)
Protected Char.s
Protected c.i
Protected Crop_Left.i
Protected Crop_Right.i
Protected Crop_Top.i
Protected Crop_Bottom.i
If *Fonts(Font_ID)\Filename <> ""
If FileSize(*Fonts(Font_ID)\Filename) = -1
Debug "Font file does not exist: " + *Fonts(Font_ID)\Filename
ProcedureReturn 0
EndIf
RegisterFontFile(*Fonts(Font_ID)\Filename)
EndIf
LoadFont(Font_ID, *Fonts(Font_ID)\Name, *Fonts(Font_ID)\Size, Flags)
Restore Font_Characters_Numbers
For c = 0 To #Font_Num_Characters-1
Read.s Char
If CheckCreateCharacter(*Fonts(), Font_ID, c)
; create temp image so that the font can be selected
Temp_Image = CreateImage(#PB_Any, 1, 1, 32)
StartDrawing(ImageOutput(Temp_Image))
DrawingFont(FontID(Font_ID))
*Fonts(Font_ID)\Character[c]\Width = TextWidth(Char)
*Fonts(Font_ID)\Character[c]\Height = TextHeight(Char)
StopDrawing()
FreeImage(Temp_Image)
If *Fonts(Font_ID)\Crop_Top_Ratio > 0 And *Fonts(Font_ID)\Crop_Top_Ratio < 1
Crop_Top = *Fonts(Font_ID)\Character[c]\Height * *Fonts(Font_ID)\Crop_Top_Ratio
EndIf
If *Fonts(Font_ID)\Crop_Bottom_Ratio > 0 And *Fonts(Font_ID)\Crop_Bottom_Ratio < 1
Crop_Bottom = *Fonts(Font_ID)\Character[c]\Height * *Fonts(Font_ID)\Crop_Bottom_Ratio
EndIf
If *Fonts(Font_ID)\Crop_Left_Ratio > 0 And *Fonts(Font_ID)\Crop_Left_Ratio < 1
Crop_Left = *Fonts(Font_ID)\Character[c]\Width * *Fonts(Font_ID)\Crop_Left_Ratio
EndIf
If *Fonts(Font_ID)\Crop_Right_Ratio > 0 And *Fonts(Font_ID)\Crop_Right_Ratio < 1
Crop_Right = *Fonts(Font_ID)\Character[c]\Width * *Fonts(Font_ID)\Crop_Right_Ratio
EndIf
*Fonts(Font_ID)\Character[c]\Width = *Fonts(Font_ID)\Character[c]\Width - (Crop_Left + Crop_Right)
*Fonts(Font_ID)\Character[c]\Height = *Fonts(Font_ID)\Character[c]\Height - (Crop_Top + Crop_Bottom)
*Fonts(Font_ID)\Character[c]\Sprite_ID = CreateSprite(#PB_Any, *Fonts(Font_ID)\Character[c]\Width, *Fonts(Font_ID)\Character[c]\Height, #PB_Sprite_AlphaBlending)
If Not *Fonts(Font_ID)\Character[c]\Sprite_ID
Debug "CreateFont: sprite creation failed"
Debug "It must be run after the following:"
Debug "- InitSprite()"
Debug "- OpenScreen() or OpenWindowedScreen()"
ProcedureReturn 0
EndIf
If StartDrawing(SpriteOutput(*Fonts(Font_ID)\Character[c]\Sprite_ID))
DrawingMode(#PB_2DDrawing_AllChannels)
DrawingFont(FontID(Font_ID))
DrawText(-Crop_Left, -Crop_Top, Char, *Fonts(Font_ID)\Colour, *Fonts(Font_ID)\Back_Colour)
If *Fonts(Font_ID)\Border
DrawingMode(#PB_2DDrawing_AllChannels | #PB_2DDrawing_Outlined)
Box(0, 0, *Fonts(Font_ID)\Character[c]\Width, *Fonts(Font_ID)\Character[c]\Height, *Fonts(Font_ID)\Border_Colour)
EndIf
StopDrawing()
EndIf
*Fonts(Font_ID)\Character[c]\Image_ID = CreateImage(#PB_Any, *Fonts(Font_ID)\Character[c]\Width, *Fonts(Font_ID)\Character[c]\Height, 32)
If StartDrawing(ImageOutput(*Fonts(Font_ID)\Character[c]\Image_ID))
DrawingMode(#PB_2DDrawing_AllChannels)
DrawingFont(FontID(Font_ID))
DrawText(-Crop_Left, -Crop_Top, Char, *Fonts(Font_ID)\Colour, *Fonts(Font_ID)\Back_Colour)
If *Fonts(Font_ID)\Border
DrawingMode(#PB_2DDrawing_AllChannels | #PB_2DDrawing_Outlined)
Box(0, 0, *Fonts(Font_ID)\Character[c]\Width, *Fonts(Font_ID)\Character[c]\Height, *Fonts(Font_ID)\Border_Colour)
EndIf
StopDrawing()
EndIf
*Fonts(Font_ID)\Character[c]\Loaded = 1
EndIf
*Fonts(Font_ID)\Loaded = 1
Next c
ProcedureReturn 1
EndProcedure
Procedure DisplayCharacterSprite(Array *Fonts.Font_Structure(1), Font_ID.i, Char.s, x.i, y.i)
Protected ASCII.i = Asc(Char)
Protected.i CharNum = GetCharNum(Char)
If *Fonts(Font_ID)\Character[CharNum]\Loaded
DisplayTransparentSprite(*Fonts(Font_ID)\Character[CharNum]\Sprite_ID, x, y)
EndIf
EndProcedure
Procedure DisplayCharacterImage(Array *Fonts.Font_Structure(1), Font_ID.i, Char.s, x.i, y.i)
; draws a character to the output as an image
Protected.i ASCII = Asc(Char)
Protected.i CharNum = GetCharNum(Char)
If *Fonts(Font_ID)\Character[CharNum]\Loaded
DrawAlphaImage(ImageID(*Fonts(Font_ID)\Character[CharNum]\Image_ID), x, y)
EndIf
EndProcedure
Procedure.i GetCharacterWidth(Array *Fonts.Font_Structure(1), Font_ID.i, Char.s)
Protected.i ASCII = Asc(Char)
Protected.i Return_Width = 0
Protected.i CharNum = GetCharNum(Char)
If *Fonts(Font_ID)\Character[CharNum]\Loaded
Return_Width = *Fonts(Font_ID)\Character[CharNum]\Width
Else
Return_Width = 0
EndIf
ProcedureReturn Return_Width
EndProcedure
Procedure.i GetCharacterHeight(Array *Fonts.Font_Structure(1), Font_ID.i, Char.s)
Protected.i ASCII = Asc(Char)
Protected.i CharNum = GetCharNum(Char)
Protected.i Return_Height = 0
If *Fonts(Font_ID)\Character[CharNum]\Loaded
Return_Height = *Fonts(Font_ID)\Character[CharNum]\Height
Else
Return_Height = 0
EndIf
ProcedureReturn Return_Height
EndProcedure
Procedure DisplayStringSprite(Array *Fonts.Font_Structure(1), Font_ID.i, Text.s, x.i, y.i, Spacing.i=0)
; Displays the string on the screen using sprites
; Spacing is used to monospace the text
Protected.i ASCII
Protected.i Width
Protected.i CharNum
Protected.i i
Structure Text_Array_Structure
Index.u[0]
EndStructure
Protected *Text_Array.Text_Array_Structure = @Text
For i = 0 To Len(Text)-1
ASCII = *Text_Array\Index[i]
CharNum = GetNum(ASCII)
If *Fonts(Font_ID)\Character[CharNum]\Loaded
DisplayTransparentSprite(*Fonts(Font_ID)\Character[CharNum]\Sprite_ID, x, y)
Width = *Fonts(Font_ID)\Character[CharNum]\Width
EndIf
If Spacing > 0
x = x + Spacing
Else
x = x + Width
EndIf
Next i
EndProcedure
Procedure DisplayStringImage(Array *Fonts.Font_Structure(1), Font_ID.i, Text.s, x.i, y.i, Spacing.i=0)
; Displays the string to the output using images
; Spacing is used to monospace the text
Protected.s Char
Protected.i Width
Protected.i CharNum
For i = 1 To Len(Text)
Char = Mid(Text, i, 1)
CharNum = GetCharNum(Char)
If *Fonts(Font_ID)\Character[CharNum]\Loaded
DrawAlphaImage(ImageID(*Fonts(Font_ID)\Character[CharNum]\Image_ID), x, y)
Width = *Fonts(Font_ID)\Character[CharNum]\Width
EndIf
If Spacing > 0
x = x + Spacing
Else
x = x + Width
EndIf
Next i
EndProcedure
Procedure.i GetStringWidth(Array *Fonts.Font_Structure(1), Font_ID.i, Text.s)
Protected.i Return_Width = 0
Protected.s Char
Protected.i CharNum
Protected.i i
Return_Width = 0
For i = 1 To Len(Text)
Char = Mid(Text, i, 1)
CharNum = GetCharNum(Char)
If *Fonts(Font_ID)\Character[CharNum]\Loaded
Return_Width = Return_Width + *Fonts(Font_ID)\Character[CharNum]\Width
EndIf
Next i
ProcedureReturn Return_Width
EndProcedure
Procedure Shutdown(Array *Fonts.Font_Structure(1))
; Note: the screen needs to be open for this to work
Protected.i c1
Protected.i c2
For c1 = 0 To #Max_Fonts
For c2 = 0 To #Font_Num_Characters-1
If *Fonts(c1)\Character[c2]\Loaded
FreeSprite(*Fonts(c1)\Character[c2]\Sprite_ID)
FreeImage(*Fonts(c1)\Character[c2]\Image_ID)
EndIf
Next c2
If *Fonts(c1)\Loaded:FreeFont(c1):EndIf
FreeStructure(*Fonts(c1))
Next c1
EndProcedure
DataSection
Font_Characters_Numbers:
Data.s "0","1","2","3","4","5","6","7","8","9" ; 0-9
Font_Characters_Alphabetic_Uppercase:
Data.s "A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z" ; 10-35
Font_Characters_Alphabetic_Lowercase:
Data.s "a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z" ; 36-61
Font_Characters_Symbol_1:
Data.s " ","!",#DQUOTE$,"#","$","%","&","'","(",")","*","+",",","-",".","/" ; 62-77
Font_Characters_Symbol_2:
Data.s ":",";","<","=",">","?","@" ; 78-84
Font_Characters_Symbol_3:
Data.s "[","\","]","^","_","`" ; 85-90
Font_Characters_Symbol_4:
Data.s "{","|","}","~" ; 91-94
EndDataSection
EndModule
#Balls = 10
#Max_Monitors_Available = 32
#Max_FPS_Samples = 500; maximum number of FPS samples
Structure Monitor_Structure
Frequency.i
Width.i
Height.i
Name.s
Combo_Item.i
X.i
Y.i
EndStructure
Structure Coords_Structure
x.i
y.i
Direction_X.i
Direction_Y.i
Speed.d
EndStructure
InitSprite()
Fonts::Initialise(Fonts::*Fonts())
With Fonts::*Fonts(0)
\Name = "Consolas"
\Size = 72
\Crop_Bottom_Ratio = 0.15
\Crop_Top_Ratio = 0.13
\Colour = RGBA(255, 255, 255, 255)
\Back_Colour = RGBA(0, 0, 0, 0)
\Available = Fonts::#Font_Characters_Numbers | Fonts::#Font_Characters_Select
\Select_Available = " FPSrameTim"
\Border = 0
\Border_Colour = RGBA(255, 0, 0, 255)
EndWith
Dim Monitors.Monitor_Structure(#Max_Monitors_Available)
Dim Balls.Coords_Structure(#Balls)
Dim FPS_Sample.i(#Max_FPS_Samples) ; array for holding FPS samples in milliseconds
Define.i Sprite_Size = 200
Define.i Radius = Sprite_Size/2-1
Define.i Num_Monitors = ExamineDesktops()
Define.i Monitor = 0
Define.i c
For c = 0 To Num_Monitors-1
Monitors(c)\Width = DesktopWidth(c)
Monitors(c)\Height = DesktopHeight(c)
Monitors(c)\Name = DesktopName(c)
Monitors(c)\X = DesktopX(c)
Monitors(c)\Y = DesktopY(c)
Monitors(c)\Frequency = DesktopFrequency(c)
Next c
OpenWindow(0, 100, 100, 300, 200, "Select Monitor")
Define.i Select_Monitor_Text = TextGadget(#PB_Any, 50, 20, 200, 20, "Select a monitor:")
Define.i Combo_Box = ComboBoxGadget(#PB_Any, 50, 40, 200, 30)
For c = 0 To Num_Monitors-1
Define.s Text = Str(c)+":"+Monitors(c)\Name+" - "+Monitors(c)\Width+" x "+Monitors(c)\Height
Monitors(c)\Combo_Item = AddGadgetItem(Combo_Box, c, Text)
Next c
SetGadgetState(Combo_Box, 0)
Define.i Button_OK = ButtonGadget(#PB_Any, 50, 120, 80, 30, "OK")
Define.i Button_Cancel = ButtonGadget(#PB_Any, 170, 120, 80, 30, "Cancel")
Define.i Event
Define.i Quit
Define.i Gadget
Define.i Selected
Repeat
Event = WaitWindowEvent()
Select Event
Case #PB_Event_CloseWindow
Quit = 1
Case #PB_Event_Gadget
Gadget = EventGadget()
Select Gadget
Case Button_Cancel
Quit = 1
Case Button_OK
Selected = 1
EndSelect
EndSelect
Until Event = #PB_Event_CloseWindow Or Quit Or Selected
If Quit
End
EndIf
Monitor = GetGadgetState(Combo_Box)
OpenWindow(0, Monitors(Monitor)\X, Monitors(Monitor)\Y, Monitors(Monitor)\Width, Monitors(Monitor)\Height, "FPS Test", #PB_Window_Maximize | #PB_Window_BorderLess)
Define.i WindowWidth = WindowWidth(0)*DesktopResolutionX()
Define.i WindowHeight = WindowHeight(0)*DesktopResolutionY()
OpenWindowedScreen(WindowID(0), 0, 0, WindowWidth, WindowHeight, #False, 0, 0, #PB_Screen_WaitSynchronization)
Fonts::CreateFont(Fonts::*Fonts(), 0)
For c = 0 To #Balls-1
CreateSprite(c, Sprite_Size, Sprite_Size, #PB_Sprite_AlphaBlending)
If StartDrawing(SpriteOutput(c))
DrawingMode(#PB_2DDrawing_AllChannels)
Circle(Sprite_Size/2, Sprite_Size/2, Radius, RGBA(Random(255, 0), Random(255, 0), Random(255, 0), 150))
StopDrawing()
Balls(c)\x = Random(WindowWidth-Sprite_Size, 0)
Balls(c)\y = Random(WindowHeight-Sprite_Size, 0)
Balls(c)\Direction_X = Random(1, 0)
Balls(c)\Direction_Y = Random(1, 0)
Balls(c)\Speed = Random(5, 1)
EndIf
Next c
InitKeyboard()
Define.i FPS_Samples = Monitors(Monitor)\Frequency
Define.q FPS_Begin_Time = ElapsedMilliseconds()
Define.q FPS_Last_Frame_Time
Define.i FPS_Average_Index
Define.d FPS_Average_Sum
Define.d FPS
Define.i Start_Time
Define.i FPS_Initialised
Define.q Frame_Time
Define.i Char_Height = Fonts::GetCharacterHeight(Fonts::*Fonts(), 0, "0")
Define.i Char_Spacing = 4
Repeat
ClearScreen(#Black)
Start_Time = ElapsedMilliseconds()
FPS_Last_Frame_Time = ElapsedMilliseconds() - FPS_Begin_Time
FPS_Begin_Time = ElapsedMilliseconds()
FPS_Average_Sum = FPS_Average_Sum - FPS_Sample(FPS_Average_Index)
FPS_Average_Sum = FPS_Average_Sum + FPS_Last_Frame_Time
FPS_Sample(FPS_Average_Index) = FPS_Last_Frame_Time
FPS_Average_Index = FPS_Average_Index + 1
If FPS_Average_Index = FPS_Samples
FPS_Average_Index = 0
FPS_Initialised = 1
EndIf
If FPS_Initialised
FPS = 1000 / (FPS_Average_Sum / FPS_Samples)
EndIf
For c = 0 To #Balls-1
DisplayTransparentSprite(c, Balls(c)\x, Balls(c)\y)
If Balls(c)\Direction_X = 1
Balls(c)\x = Balls(c)\x + Balls(c)\Speed
Else
Balls(c)\x = Balls(c)\x - Balls(c)\Speed
EndIf
If Balls(c)\Direction_Y = 1
Balls(c)\y = Balls(c)\y + Balls(c)\Speed
Else
Balls(c)\y = Balls(c)\y - Balls(c)\Speed
EndIf
If Balls(c)\x > WindowWidth-Sprite_Size
Balls(c)\Direction_X = 0
Balls(c)\x = (WindowWidth-Sprite_Size) - (Balls(c)\x - (WindowWidth-Sprite_Size))
EndIf
If Balls(c)\x < 0
Balls(c)\Direction_X = 1
Balls(c)\x = 0 - Balls(c)\x
EndIf
If Balls(c)\y > WindowHeight-Sprite_Size
Balls(c)\Direction_Y = 0
Balls(c)\y = (WindowHeight-Sprite_Size) - (Balls(c)\y - (WindowHeight-Sprite_Size))
EndIf
If Balls(c)\y < 0
Balls(c)\Direction_Y = 1
Balls(c)\y = 0 - Balls(c)\y
EndIf
Next c
Fonts::DisplayStringSprite(Fonts::*Fonts(), 0, "FPS: "+FormatNumber(FPS, 0), 0, 0)
Fonts::DisplayStringSprite(Fonts::*Fonts(), 0, "Frame:"+Str(FPS_Average_Index), 0, Char_Height + Char_Spacing)
Fonts::DisplayStringSprite(Fonts::*Fonts(), 0, "Time: "+Str(Frame_Time), 0, 2*Char_Height + Char_Spacing)
FlipBuffers()
Frame_Time = ElapsedMilliseconds() - Start_Time
Event = WindowEvent()
ExamineKeyboard()
Until Event = #PB_Event_CloseWindow Or KeyboardPushed(#PB_Key_Escape)
CloseScreen()