You'll see on the screen a visual fx (circles going from left to right, like a starfield), and lines. These lines are showing visually how long is during parts of our code, using the display as a time basis.
The space between two lines represents time taken by our code.
What this program does is to synchronize code on the (virtual) screen beam's position. It starts the benchmark after the ClearScreen() and draws the first line. Then it computes and draws the FX, and then draws the second line. Then it draws the technical details like FPS and information text, and after that the third line. So you visually see how much time each part has taken, by the distance between two lines. More computing, more sprites, and the second line will go down. The distance between second and third will not vary much, as it's always the same thing computed from frame to frame.
If it's going to the bottom, you'll encounter what is called a "frame drop" : the computing needs more than one frame , so can't be synchronized with the display... The next frame will not contain updated drawing from the double buffer, as it's not finished. It gives an ugly visual slowdown that is the enemy of realtime artists like demomakers !

If the code took less than 16 ms on 60Hz display, it means we have 60 imgs/secs and the best fluidity!

(note to demomakers, next job to do : overscan

[Edit 1]Here's a little explanation (please forgive my bad english and shortcuts to simplify explanations).
It's an old demomaker tip to have a clue on how much time code is taking.
The old CRT cathodic screen had a phosphorous surface, a material that emits light when receiving electrons. To display pixels, an electron beam, that covers the whole screen, was going from left to right, and from top to bottom. For a 60 imgs/sec display, it was doing it 60 times/sec (easy, no?). It's what you see in your graphic adapter display parameters (60 Hz).
On most of ancient computers (even on PC), it was relatively easy to sync the CPU to the display. So, we changed the screen background color to see visually how long was our code. It was far easier to optimise code this way than stopping code and looking numbers, and faster than drawing numbers on the screen.
For example, on my good old Amiga 1200, the shortest CPU instruction (the minimum distance between two colour changes) was approx. 8 hires pixels long. Amiga was special, as lot of parts of the hardware was synced with the display : part of the RAM (Chip-RAM), and a dedicated coprocessor (the copper) was able to alter hardware registers based on beam's position. It was terrific.
Note on the double buffer
On a double buffered screen, we're drawing on a back buffer, and the system waits for the VBL* beam position to swap the displayed buffer and the back buffer. If the drawing operations are not finished before the VBL, the swap can't occur, and there's a frame drop : the same frame is displayed two times.
*VBL : Vertical Blank, when the beam has to come back from the lower end to the beginning of the screen.
[Edit 2] Changed title to be more adequate to english users. (original title originated from a french thread)
[Edit 3] Cleanup and 4.60 fix
[Edit 4] Cleanup and 5.51 (x64) fix
[Edit 5] x64 and x86 fix
[Edit 6] Integrated kvitaliy FPS counter, and updated explanations.
Code: Select all
;*****************************************************************************
;* RasterLine and vertical blank (VBL) benchmark
;* Realtime visual benchmark. Press space to add "stars". The time is given
;* by the space between blue and green line.
;* By djes (djes@free.fr) 03/24/2009
;* Thanks to Stefan Moebius for VBL sync examples
;* Note : I do not check functions return values
;* 02/28/2012 : PB 4.60 fix
;* 02/07/2017 : PB 5.51 (x64 & x86) fix
;*****************************************************************************
Structure D3DRaster_STATUS
InVBlank.l
ScanLine.l
EndStructure
Structure Pos
x.l
y.l
Speed.l
EndStructure
;*****************************************************************************
Define.D3DRaster_STATUS Raster
Define.IDirect3DDevice9 D3DDeviceInterface
Global NewList Stars.Pos()
;*****************************************************************************
; Waits for the vblank to start - takes all the cpu
; Attend le début de la synchro vbl
Procedure VBLWait()
Shared D3DDeviceInterface, Raster, Exit
Repeat
ExamineKeyboard()
If KeyboardPushed(#PB_Key_Escape)
Exit=#True
EndIf
D3DDeviceInterface\GetRasterStatus(0, @Raster)
Until Raster\InVBlank=#True Or Exit
EndProcedure
;*****************************************************************************
; Waits for the vblank to finish - takes all the cpu
; Attend la fin de la synchro vbl
Procedure VBLEndWait()
Shared D3DDeviceInterface, Raster, Exit
Repeat
ExamineKeyboard()
If KeyboardPushed(#PB_Key_Escape)
Exit=#True
EndIf
D3DDeviceInterface\GetRasterStatus(0, @Raster)
Until Raster\InVBlank=#False Or Exit
EndProcedure
;*****************************************************************************
; Looks for the raster beam position and draws there a coloured line
; Récupère la position du Raster et y affiche une ligne de la couleur indiquée
Procedure RasterLine(Color.l)
Shared Raster, D3DDeviceInterface
StartDrawing(ScreenOutput())
D3DDeviceInterface\GetRasterStatus(0, @Raster)
LineXY(0, Raster\ScanLine, 1023, Raster\ScanLine, Color)
StopDrawing()
EndProcedure
;*****************************************************************************
Procedure CreateSprites()
CreateSprite(0, 64, 64)
StartDrawing(SpriteOutput(0))
For y = -31 To 31
For x = -31 To 31
i.d = Sqr(Pow(x/32, 2)+Pow(y/32, 2))
If i < 1
Plot(32 + x, 32 + y, RGB(255 - 255 * i, 64 - 64 * i, 128 - 64 * i))
EndIf
Next x
Next y
;Circle(32, 32, 31, RGB($FF, $FF, $FF))
StopDrawing()
EndProcedure
;*****************************************************************************
;Opens screen and get the device - Ouvre l'écran et récupère le device
Procedure InitDisplay()
Shared D3DDeviceInterface
OpenScreen(1024, 768, 32, "", #PB_Screen_NoSynchronization, 60)
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!extrn PB_Screen_Direct3DDevice
!MOV dword EAX, [PB_Screen_Direct3DDevice]
!MOV dword [v_D3DDeviceInterface],EAX
CompilerElseIf #PB_Compiler_Processor = #PB_Processor_x86
!extrn _PB_Screen_Direct3DDevice
!MOV dword EAX, [_PB_Screen_Direct3DDevice]
!MOV dword [v_D3DDeviceInterface],EAX
CompilerElse
MessageRequester("Alert", "Unsupported processor")
End
CompilerEndIf
EndProcedure
;*****************************************************************************
; Checks if the user has switched (and that we have lost focus) (ALT-TAB)
; Vérifie si l'utilisateur n'a pas changé d'appli (en nous faisant donc perdre le focus) (ALT-TAB)
Procedure FullScreenCheck()
; We're lowering our priority - on baisse notre priorité
SetPriorityClass_( GetCurrentProcess_(), #NORMAL_PRIORITY_CLASS)
SetThreadPriority_( GetCurrentThread_() , #THREAD_PRIORITY_NORMAL)
ReleaseMouse(1)
CloseScreen()
; Opens a visible window with a taskbar button - Ouvre une fenêtre visible, avec un bouton dans la barre des tâches
OpenWindow(0, 0, 0, 256, 256, "RasterLines", #PB_Window_Minimize)
; Wait til our window is coming back to foreground - Attend que notre fenêtre repasse au premier plan
Repeat
; Now our events are to be processed with WaitWindowEvent, else IsScreenActive() will never say that our window has the focus again
; it should be written in the doc !
; Maintenant les événements sont à gérer avec WaitWindowEvent, sinon IsScreenActive() ne dit jamais que notre fenêtre a à nouveau le focus
; il faudrait écrire ça dans la doc !!!
Event = WaitWindowEvent()
Until Event = #PB_Event_ActivateWindow
CloseWindow(0)
ReleaseMouse(0)
; Better recreate the screen - il vaut mieux recréer l'écran
InitDisplay()
; And the sprites too (have to!) - et les sprites aussi (indispensable)
CreateSprites()
; Gives to the system some time to rest - laisse un peu le temps au système de récupérer
;Delay(2000)
; We're waiting for the synchro a new time - On réattend la synchro
VBLWait()
SetPriorityClass_( GetCurrentProcess_(), #HIGH_PRIORITY_CLASS)
SetThreadPriority_( GetCurrentThread_() , #THREAD_PRIORITY_ABOVE_NORMAL) ;warning : keyboard lock
EndProcedure
Procedure NewStar()
AddElement(Stars())
Stars()\x = Random(1024 + 128) - 64
Stars()\y = Random(768 + 128) - 64
Stars()\Speed = Random(16) + 1
EndProcedure
;*****************************************************************************
;- START
;*****************************************************************************
InitSprite()
InitKeyboard()
InitMouse()
InitDisplay()
CreateSprites()
For i = 1 To 5
NewStar()
Next i
Exit=#False
; We're giving max priority to our process, as it should not be interrupted by other tasks
; On donne une priorité maximale à notre processus
SetPriorityClass_( GetCurrentProcess_(), #REALTIME_PRIORITY_CLASS)
SetThreadPriority_( GetCurrentThread_() , #THREAD_PRIORITY_TIME_CRITICAL) ;warning : keyboard lock
;*****************************************************************************
;- MAIN LOOP
;*****************************************************************************
Repeat
; Waits for the sync - attends la synchro
VBLWait()
; Flips buffers without the PB's sync - flippe le buffer sans utiliser la synchro PB
FlipBuffers()
; During VBL, lowers a bit our priority to give some time to the OS (especially to handle the keyboard)
; Pendant la VBL, baisse un peu notre priorité pour donner un peu de temps au système (pour gérer le clavier surtout)
SetPriorityClass_( GetCurrentProcess_(), #HIGH_PRIORITY_CLASS)
SetThreadPriority_( GetCurrentThread_() , #THREAD_PRIORITY_NORMAL)
ExamineKeyboard()
ExamineMouse()
If KeyboardPushed(#PB_Key_Escape)
Exit = #True
EndIf
If KeyboardPushed(#PB_Key_Space)
NewStar()
EndIf
If Not IsScreenActive()
FullScreenCheck()
EndIf
; We're waiting for the VBL end (could be better !!!)
; On attend la fin de la VBL (à améliorer) pour être sûr de ne pas sauter une frame
VBLEndWait()
;we're giving max priority to our process - on donne une priorité maximale à notre processus
SetPriorityClass_( GetCurrentProcess_(), #REALTIME_PRIORITY_CLASS)
SetThreadPriority_( GetCurrentThread_() , #THREAD_PRIORITY_TIME_CRITICAL) ;warning : keyboard lock
ClearScreen(0)
;- BENCHMARK START
; Draws the first raster line (base of our benchmark)
RasterLine($FF0000)
; Computes something (here an FX)
ForEach Stars()
Stars()\x + Stars()\Speed
If Stars()\x > 1023
Stars()\x - 1088
EndIf
Next
ForEach Stars()
DisplayTransparentSprite(0, Stars()\x, Stars()\y, Stars()\Speed << 4)
Next
; Draws the second RasterLine to see (visually) how much time our fx is taking
; the less space between the two lines, the fastest our effect
; Affiche une RasterLine pour visualiser combien de temps prend notre effet
RasterLine($FF00)
; Technical infos drawing
StartDrawing(ScreenOutput())
; FPS
Now = ElapsedMilliseconds()
If (Now-Ticks) >= 1000
Ticks = Now
FramePerSec.s = "FPS: " + Str( FrameCounter )
FrameCounter = 0
EndIf
FrameCounter + 1
DrawText(2,2, FramePerSec )
DrawingMode(#PB_2DDrawing_Transparent)
Text.s = "Press SPACE to add balls ; ESC to quit"
DrawText(512 - TextWidth(Text)/2, 300, Text, #Blue)
Text.s = "Stars Nb : " + Str(ListSize(Stars()))
DrawText(512 - TextWidth(Text)/2, 320, Text, #Red)
StopDrawing()
;Draws another raster line to see how much time was taken by this drawing
RasterLine($FF)
;- BENCHMARK END
Until Exit
CloseScreen()
End