Well, if anybody gets something to work properly, please post it in here. Until then I'm stuck with FlipBuffers(#PB_Screen_WaitSynchronization) and the corresponding high CPU load...
Remember: I'm trying to become CPU and refresh rate independent with limited CPU load...
Of course, by the time we've figured out a solution, the PBdevs have (hopefully) released a version that does do it properly...
Smooth animation / sprite movement
( PB6.00 LTS Win11 x64 Asrock AB350 Pro4 Ryzen 5 3600 32GB GTX1060 6GB - upgrade incoming...)
( The path to enlightenment and the PureBasic Survival Guide right here... )
( The path to enlightenment and the PureBasic Survival Guide right here... )
This will get you the scan line, without any drop in performance, if you utilize a high performance timer you can then use the scan line to time your refresh.
Code: Select all
IDD.IDirectDraw7
If OpenLibrary(0,"ddraw.dll")
If CallFunction(0,"DirectDrawCreateEx",0,@IDD.IDirectDraw7,?IID_IDirectDraw7,0) <> 0
MessageRequester("Warning:","Couldn't init DirectDraw",0)
End
Else
bopen = #True
EndIf
Else
MessageRequester("Warning:","Couldn't init DirectDraw",0)
End
EndIf
;****************** the business
If bopen
line.w
ret = IDD\GetScanLine(@line)
EndIf
;*******************
DataSection
IID_IDirectDraw7:
Data.l $15e65ec0
Data.w $3b9c, $11d2
Data.b $b9, $2f, $00, $60, $97, $97, $ea, $5b
EndDataSection
;can call any of these I expect, like the above
; STDMETHOD(Compact)(THIS) PURE;
; STDMETHOD(CreateClipper)(THIS_ DWORD, LPDIRECTDRAWCLIPPER FAR*, IUnknown FAR * ) PURE;
; STDMETHOD(CreatePalette)(THIS_ DWORD, LPPALETTEENTRY, LPDIRECTDRAWPALETTE FAR*, IUnknown FAR * ) PURE;
; STDMETHOD(CreateSurface)(THIS_ LPDDSURFACEDESC, LPDIRECTDRAWSURFACE FAR *, IUnknown FAR *) PURE;
; STDMETHOD(DuplicateSurface)( THIS_ LPDIRECTDRAWSURFACE, LPDIRECTDRAWSURFACE FAR * ) PURE;
; STDMETHOD(EnumDisplayModes)( THIS_ DWORD, LPDDSURFACEDESC, LPVOID, LPDDENUMMODESCALLBACK ) PURE;
; STDMETHOD(EnumSurfaces)(THIS_ DWORD, LPDDSURFACEDESC, LPVOID,LPDDENUMSURFACESCALLBACK ) PURE;
; STDMETHOD(FlipToGDISurface)(THIS) PURE;
; STDMETHOD(GetCaps)( THIS_ LPDDCAPS, LPDDCAPS) PURE;
; STDMETHOD(GetDisplayMode)( THIS_ LPDDSURFACEDESC) PURE;
; STDMETHOD(GetFourCCCodes)(THIS_ LPDWORD, LPDWORD ) PURE;
; STDMETHOD(GetGDISurface)(THIS_ LPDIRECTDRAWSURFACE FAR *) PURE;
; STDMETHOD(GetMonitorFrequency)(THIS_ LPDWORD) PURE;
; STDMETHOD(GetScanLine)(THIS_ LPDWORD) PURE;
; STDMETHOD(GetVerticalBlankStatus)(THIS_ LPBOOL ) PURE;
; STDMETHOD(Initialize)(THIS_ GUID FAR *) PURE;
; STDMETHOD(RestoreDisplayMode)(THIS) PURE;
; STDMETHOD(SetCooperativeLevel)(THIS_ HWND, DWORD) PURE;
; STDMETHOD(SetDisplayMode)(THIS_ DWORD, DWORD,DWORD) PURE;
; STDMETHOD(WaitForVerticalBlank)(THIS_ DWORD, HANDLE ) PURE;
@idle: I reached the same conclusion and I just finished working on the code to do this (in 3 days). If you would have posted this sooner it would have saved me some sleep.idle wrote:This will get you the scan line, without any drop in performance, if you utilize a high performance timer you can then use the scan line to time your refresh.
Sorry I hadn't had the time to look into it until this morning, at least you got it sorted.Demivec wrote:@idle: I reached the same conclusion and I just finished working on the code to do this (in 3 days). If you would have posted this sooner it would have saved me some sleep.idle wrote:This will get you the scan line, without any drop in performance, if you utilize a high performance timer you can then use the scan line to time your refresh.
A little bit later today, after I've slept, I post a demo that includes a method that uses DirectDraw with the GetScanLine, then computes the display frequency, then uses this information for a lazy, but precise timer to FlipBuffers() in the demo that Kaeru posted. Whew!
I have all of the above actually working well. Since there is an element of research to this, I still have to work out the effects, both positive and negative, of a minor aspect of the timing adjustments.
I have all of the above actually working well. Since there is an element of research to this, I still have to work out the effects, both positive and negative, of a minor aspect of the timing adjustments.
Now we're talking! Gonna' have a good look at this next week, see what I can cook up...
( PB6.00 LTS Win11 x64 Asrock AB350 Pro4 Ryzen 5 3600 32GB GTX1060 6GB - upgrade incoming...)
( The path to enlightenment and the PureBasic Survival Guide right here... )
( The path to enlightenment and the PureBasic Survival Guide right here... )
@blueznl: Well, this should get you started...but let me digress for a moment. I noticed that SetFrameRate() also eats up the CPU time and by using the method demonstrated in the following code, it is also unnecessary.blueznl wrote:Now we're talking! Gonna' have a good look at this next week, see what I can cook up...
I realized after the former comment I posted that all the work I was going through to determine the display frequency wasn't needed (though I suceeded in doing it anyway). bluznl you had found the method for me by using ExamineDesktops() and DesktopFrequency(). Again, if I had only found those sooner.
So what I will end up demonstrating is only a lazy, but precise timer to use with FlipBuffers(#PB_Screen_WaitSynchronization). The timing method was given in the link that Kaeru had posted and no doubt it has been shown elsewhere as well. It uses API functions to change the timing resolution of Delay() as well as needing a high performance timer. If need be you may be able to do away with the high performance timer. The method gives away program time while it waits for the next screen refresh. When the time gets close it then sits and waits until it is refreshed. The result is it shares time with other programs, doesn't hog the CPU (good for laptop batteries too) and avoids tears and screwy sprite animations.
I decided to post the demonstration with your code instead. I didn't mess with the code too much, though I did add a few things. When you run it you can push "+" to add more sprites up to the maximum of 1000 or push "-" to take sprites away. With 1000 sprites it uses only about 38% of the CPU and with 10 it uses 6%.
When you test it tell me if you can let your ship sit there on the screen with trying to dodge something. There's no collision detection, so no worries.
Here it is:
Code: Select all
;most of game code is blueznl's
;Demivec (added screen timing method) and the code marked with ;---start ;---stop
Enumeration
#w_main
;
#f_exit
#f_none
;
#i_arrow ; image used for player
#i_rock1 ; image used for rock1
#i_rock2 ; image used for rock2
#i_rock3 ; image used for rock3
;
EndEnumeration
;
Global i_arrow_h.l
;
Structure spr
alive.l ; set to #false to disable this sprite
sprite2d_nr.l ; 2d sprite used
sprite3d_nr.l ; 3d sprite used
a.f ; object orientation
Rotation.f ; rotation speed in degrees / second, does not affect vector
Z.f ; object size factor (1 = original size)
x.f ; x-coordinate
y.f ; y-coordinate
w.l ; width in pixels
h.l ; height in pixels
v_angle.f ; vector angle in degrees
v_speed.f ; speed in pixels per second
v_mode.l ; #true to use vector, #false to use x / y components
v_x.f ; horizontal speed in pixels per second
v_y.f ; same vertical
EndStructure
;--start (deals with timing method)
Global highPerfTimerFreq.q ;holds frequency of the high performance timer
Define prevEndOfFrame.q, timer.q
Define ticksToWait.q, ticksPassed.q, i.l, gapTicks.q
Define.l framerate, timeGap
QueryPerformanceFrequency_(@highPerfTimerFreq.q)
If highPerfTimerFreq = 0
MessageRequester("Warning:","High performance timer not available",0)
End
EndIf
;this will enable finer resolution of time when using Delay(), must use timeEndPeriod_(1) before program ends
timeBeginPeriod_(1)
gapTicks = (highPerfTimerFreq * 10) / 1000 ;this is 10 ms to shorten the wait time by, just to be safe
QueryPerformanceCounter_(@timer)
prevEndOfFrame = timer
;--stop
;
InitSprite()
InitSprite3D()
InitKeyboard()
;
ExamineDesktops()
framerate = DesktopFrequency(0)
;--start (deals with timing method)
ticksToWait = highPerfTimerFreq / framerate - gapTicks
;--stop
framelength.f = 1 / framerate ; time per frame in seconds
; Debug framerate
;
screen_w.l = 1280 ; screen width
screen_h.l = 1024 ; screen height
screen_z.l = 32 ; color depth
OpenScreen(screen_w,screen_h,screen_z,"SpriteEngine")
;SetFrameRate(framerate)
;
; sorry, no luck with windowed screens yet...
;
; screen_w.l = 800
; screen_h.l = 600
; screen_z.l = 32
; w_main_h.l = OpenWindow(#w_main,10,10,screen_w,screen_h,"SpriteEngine",#PB_Window_ScreenCentered)
; OpenWindowedScreen(w_main_h,0,0,screen_w,screen_h,0,0,0)
; SetFrameRate(framerate)
;
Sprite3DQuality(0)
;
; player sprite
;
#spr_player = 0
;
i_arrow_h = CreateImage(#i_arrow,32,32,32)
StartDrawing(ImageOutput(#i_arrow))
Box(0,0,31,31,RGB(0,0,0))
FrontColor(RGB(0,255,0))
LineXY(2,30,16,2)
LineXY(16,2,30,30)
LineXY(30,30,16,8)
LineXY(16,8,2,30)
StopDrawing()
;
CreateSprite(#spr_player,32,32,#PB_Sprite_Texture)
StartDrawing(SpriteOutput(#spr_player))
DrawImage(i_arrow_h,0,0)
StopDrawing()
CreateSprite3D(#spr_player,#spr_player)
;
;--start
; rock sprites
;
#spr_rock1 = 1
i_rock1_h = CreateImage(#i_rock1,128,128,32)
StartDrawing(ImageOutput(#i_rock1))
Box(0,0,127,127,RGB(0,0,0))
FrontColor(RGB(255,255,255))
LineXY(14,38,14,73):LineXY(15,39,15,73):LineXY(16,40,16,73)
LineXY(14,73,40,112):LineXY(15,73,40,111):LineXY(16,73,40,110):LineXY(17,73,41,110)
LineXY(40,112,78,98):LineXY(40,111,78,97):LineXY(40,110,78,96)
LineXY(78,98,90,111):LineXY(78,97,90,110):LineXY(78,96,90,109):LineXY(79,96,91,109)
LineXY(90,111,112,86):LineXY(90,110,111,86):LineXY(90,109,110,86):LineXY(89,109,109,86)
LineXY(112,86,79,63):LineXY(111,86,78,63):LineXY(110,86,78,62):LineXY(108,86,76,62)
LineXY(79,63,112,50):LineXY(78,63,111,50):LineXY(78,62,110,50):LineXY(78,61,110,49)
LineXY(112,50,112,39):LineXY(111,50,111,39):LineXY(110,50,110,39)
LineXY(112,39,77,14):LineXY(111,39,77,15):LineXY(110,39,77,16)
LineXY(77,14,39,14):LineXY(77,15,40,15):LineXY(77,16,41,16)
LineXY(39,14,51,38):LineXY(40,15,52,39):LineXY(41,16,53,40)
LineXY(51,38,14,38):LineXY(52,39,15,39):LineXY(53,40,16,40)
StopDrawing()
;
CreateSprite(#spr_rock1,128,128,#PB_Sprite_Texture)
StartDrawing(SpriteOutput(#spr_rock1))
DrawImage(i_rock1_h,0,0)
StopDrawing()
;
#spr_rock2 = 2
i_rock2_h = CreateImage(#i_rock2,128,128,32)
StartDrawing(ImageOutput(#i_rock2))
Box(0,0,127,127,RGB(0,0,0))
FrontColor(RGB(255,255,255))
LineXY(14,41,14,88):LineXY(15,41,15,88):LineXY(16,40,16,88)
LineXY(14,88,36,110):LineXY(15,88,36,109):LineXY(16,88,36,108)
LineXY(36,110,78,110):LineXY(36,109,78,109):LineXY(37,108,79,108)
LineXY(78,110,110,89):LineXY(78,109,109,89):LineXY(79,108,109,88):LineXY(79,107,108,88)
LineXY(110,89,98,66):LineXY(109,89,97,66):LineXY(109,88,97,65):LineXY(107,87,97,66)
LineXY(98,66,112,41):LineXY(97,66,111,41):LineXY(97,65,111,40)
LineXY(112,41,87,18):LineXY(111,41,87,19):LineXY(111,40,88,19):LineXY(109,40,87,20):LineXY(108,40,86,20)
LineXY(87,18,64,40):LineXY(87,19,64,41):LineXY(88,19,65,41):LineXY(89,19,64,43)
LineXY(64,40,38,18):LineXY(64,41,38,19):LineXY(65,41,39,19):LineXY(64,42,38,20):LineXY(63,42,37,20)
LineXY(38,18,14,41):LineXY(38,19,15,41):LineXY(39,19,17,40)
StopDrawing()
CreateSprite(#spr_rock2,128,128,#PB_Sprite_Texture)
StartDrawing(SpriteOutput(#spr_rock2))
DrawImage(i_rock2_h,0,0)
StopDrawing()
#spr_rock3 = 3
i_rock3_h = CreateImage(#i_rock3,128,128,32)
StartDrawing(ImageOutput(#i_rock3))
Box(0,0,127,127,RGB(0,0,0))
FrontColor(RGB(255,255,255))
LineXY(14,41,25,67):LineXY(15,41,26,67):LineXY(16,41,27,67)
LineXY(25,67,14,89):LineXY(26,67,15,89):LineXY(27,67,16,89)
LineXY(14,89,39,110):LineXY(15,89,39,109):LineXY(16,89,39,108):LineXY(17,89,40,108)
LineXY(39,110,52,100):LineXY(39,109,52,99):LineXY(39,108,52,98)
LineXY(52,100,87,110):LineXY(52,99,87,109):LineXY(52,98,87,108)
LineXY(87,110,110,79):LineXY(87,109,109,79):LineXY(87,108,108,79):LineXY(87,107,107,79)
LineXY(110,79,88,54):LineXY(109,79,87,54):LineXY(108,79,86,54)
LineXY(88,54,112,40):LineXY(87,54,111,40):LineXY(86,54,110,40):LineXY(85,53,109,40)
LineXY(112,40,88,18):LineXY(111,40,88,19):LineXY(110,40,88,20):LineXY(110,41,88,21)
LineXY(88,18,64,31):LineXY(88,19,64,32):LineXY(88,20,64,33)
LineXY(64,31,39,18):LineXY(64,32,39,19):LineXY(64,33,39,20)
LineXY(39,18,14,41):LineXY(39,19,15,41):LineXY(39,20,16,41)
StopDrawing()
CreateSprite(#spr_rock3,128,128,#PB_Sprite_Texture)
StartDrawing(SpriteOutput(#spr_rock3))
DrawImage(i_rock3_h,0,0)
StopDrawing()
;--stop
;
#spr_max = 1000
Dim spr.spr(#spr_max)
;
With spr(0)
\alive = #True
\sprite2d_nr = #spr_player
\sprite3d_nr = #spr_player
\a = 0
\Rotation = 0
\Z = 1
\x = screen_w / 2
\y = screen_h / 2
\w = SpriteWidth( \sprite2d_nr)
\h = SpriteHeight( \sprite2d_nr)
\v_angle = 0
\v_speed = 0
\v_mode.l = #False
\v_x.f = 0
\v_y.f = 0
EndWith
;--start
Procedure setRock(index.l,sprite2d.l,Size.f = 1.0)
Protected sprite3d.l
Shared spr()
sprite3d = CreateSprite3D(#PB_Any,sprite2d)
With spr(index)
\alive = #True
\sprite2d_nr = sprite2d
\sprite3d_nr = sprite3d
\a = 0
\Rotation = -0.45 / Size + 0.225 * Random(1)
If Random(1): \Rotation = -\Rotation: EndIf
\Z = 1
\x = (3 * screen_w / 10) - Random(screen_w / 5) + (Random(1) * screen_w * 0.6)
\y = (3 * screen_h / 10) - Random(screen_h / 5) + (Random(1) * screen_h * 0.6)
\w = SpriteWidth( \sprite2d_nr) * Size
\h = SpriteHeight( \sprite2d_nr) * Size
\v_angle = Random(360)
\v_speed = 75 + Random(25) / Size
\v_mode.l = #True
\v_x.f = 0
\v_y.f = 0
EndWith
EndProcedure
setRock(1,#spr_rock1)
setRock(2,#spr_rock2)
setRock(3,#spr_rock3)
setRock(4,#spr_rock1,0.5)
setRock(5,#spr_rock3,0.5)
setRock(6,#spr_rock1,0.25)
setRock(7,#spr_rock2,0.25)
setRock(8,#spr_rock3,0.25)
setRock(9,#spr_rock1,0.25)
;--stop
;
spr_number.l = 10 ; number of sprites to process
;
action = #f_none
Repeat
;
; calculate movement etc.
;
n = 0
While n < spr_number
With spr(n)
If \alive
\a = \a + \Rotation
ZoomSprite3D( \sprite3d_nr, \w, \h)
RotateSprite3D( \sprite3d_nr , \a , 0)
If \v_mode
\v_x = \v_speed * Sin( \v_angle / 180 * #PI )
\v_y = 0 - \v_speed * Cos( \v_angle / 180 * #PI )
EndIf
\x = \x + \v_x * framelength
\y = \y + \v_y * framelength
;
; wrap outside screen borders
;
If \x > screen_w + \w
\x = 0 - \w
ElseIf \x < 0 - \w
\x = screen_w + \w
EndIf
If \y > screen_h + \h
\y = 0 - \h
ElseIf \y < 0 - \h
\y = screen_h + \h
EndIf
;
EndIf
EndWith
n = n+1
Wend
;
; display all active sprites
;
ClearScreen(0)
Start3D()
n = 0
While n < spr_number
With spr(n)
If \alive
DisplaySprite3D( \sprite3d_nr , \x , \y , 255)
EndIf
EndWith
n = n+1
Wend
Stop3D()
;
;FlipBuffers(#PB_Screen_WaitSynchronization)
;--start (deals with timing method)
While 1
QueryPerformanceCounter_(@timer);
ticksPassed = timer - prevEndOfFrame
timeGap = ticksToWait - ticksPassed
If timer < prevEndOfFrame Or timeGap <= 0: Break: EndIf
If (timeGap > highPerfTimerFreq * 1 / 1000)
Delay(1)
Else
For i = 0 To 9
Delay(0) ; causes thread to give up its timeslice
Next
EndIf
Wend
; ** the rest will wait FlipBuffers
FlipBuffers(#PB_Screen_WaitSynchronization)
prevEndOfFrame = timer
;--stop
;
ExamineKeyboard()
If KeyboardPushed(#PB_Key_Escape)
action = #f_exit
EndIf
If KeyboardPushed(#PB_Key_Add)
If spr_number < #spr_max
sprite.l = Random(2)
Select sprite
Case 0
sprite = #spr_rock1
Case 1
sprite = #spr_rock2
Case 2
sprite = #spr_rock3
EndSelect
Select Random(6)
Case 0
setRock(spr_number,sprite)
Case 1,2
setRock(spr_number,sprite,0.5)
Default
setRock(spr_number,sprite,0.25)
EndSelect
spr_number + 1
EndIf
EndIf
If KeyboardPushed(#PB_Key_Subtract)
If spr_number > 2
spr_number - 1
FreeSprite3D(spr(spr_number)\sprite3d_nr)
spr(spr_number)\alive = 0
EndIf
EndIf
With spr(0)
;
; move the player object using A / W / S / D / UP / DN / LFT / RGHT
;
If KeyboardPushed(#PB_Key_Left)
\Rotation = \Rotation - 10 * framelength ; with inertia
; \a = \a - 300 * framelength ; without inertia
ElseIf KeyboardPushed(#PB_Key_Right)
\Rotation = \Rotation + 10 * framelength
; \a = \a + 300 * framelength
ElseIf KeyboardPushed(#PB_Key_Up)
\v_x = \v_x + 500 * Sin( \a / 180 * #PI ) * framelength
\v_y = \v_y - 500 * Cos( \a / 180 * #PI ) * framelength
ElseIf KeyboardPushed(#PB_Key_Down)
\v_x = \v_x - 100 * Sin( \a / 180 * #PI ) * framelength
\v_y = \v_y + 100 * Cos( \a / 180 * #PI ) * framelength
ElseIf KeyboardPushed(#PB_Key_A)
; \x = \x - 100 * framelength ; without inertia
\v_x = \v_x - 100 * framelength ; with inertia
ElseIf KeyboardPushed(#PB_Key_D)
; \x = \x + 100 * framelength
\v_x = \v_x + 100 * framelength
ElseIf KeyboardPushed(#PB_Key_W)
; \y = \y - 100 * framelength
\v_y = \v_y - 100 * framelength
ElseIf KeyboardPushed(#PB_Key_S)
; \y = \y + 100 * framelength
\v_y = \v_y + 100 * framelength
EndIf
EndWith
;
Until action = #f_exit
CloseScreen()
;--start (deals with timing method)
timeEndPeriod_(1)
;--stop
Last edited by Demivec on Mon Sep 01, 2008 6:23 am, edited 1 time in total.
Hmmm. Interesting. One of my CPU cores still moves up to 25% as if it is fully loaded. Did you test it on a single core or multi core CPU?
( PB6.00 LTS Win11 x64 Asrock AB350 Pro4 Ryzen 5 3600 32GB GTX1060 6GB - upgrade incoming...)
( The path to enlightenment and the PureBasic Survival Guide right here... )
( The path to enlightenment and the PureBasic Survival Guide right here... )
Alas, I only have a single core for testing. If you are using multi core, what are the other CPU's doing? I'm just curious because I really don't have any answers, just observations. I couldn't get the animation to work perfectly without waiting for 10 ms for a vertical blank via the FlipBuffers(). If that period could be shortened it will save even more CPU. I couldn't determine a way to calculate that amount of time (10 ms) and had to do it empirically.blueznl wrote:Hmmm. Interesting. One of my CPU cores still moves up to 25% as if it is fully loaded. Did you test it on a single core or multi core CPU?
I noticed that if I am running other active programs, especially ones that hog CPU for drawing purposes like, well, drawing programs, it will keep the CPU use higher even when they are not being actively used.


