Locking / Unlocking Buffers - Where and When

Started by kevin, February 18, 2007, 07:25:06 AM

Previous topic - Next topic

kevin




 Visit www.PlayBasic.com


Locking / Unlocking Buffers - Where and When ( February 18, 2007)




 Unlike the good old days of the 8/16 and early 32bit machines, when we write programs on the PC, were not alone!   As while our program is running, there are other background operating system tasks & program running also.   This means our programs have to share system resources with other programs.   So windows act's as a mediator of sorts.   Allocating time for our program to use certain device and then doing the same of other programs.

 Primarily when we think about shared resources we're thinking of Image Data, Hardware devices (mouse/keyboard/joystick/ Image Blitter/ 3D hardware /Sound etc etc ) and memory. While the majority of those are transparent to the PlayBasic programmer (as we're hidden the tedious tasks away), it is very advantageous to have some understanding of how windows manages the key ingredients in your games, AKA your images.

 Hopefully, you've aware that we have two or more primary types of images that our PB programs can create.  Images stored in Video memory & FX images (image stored in system memory) We won't concern our selves with when/why we should use either, or both, we just want to get some idea of how windows handles resources for us.

 Lets start from the start.  We when create a program we're also creating the screen graphics device. To the user the screen is the output display of what our program has been drawing. Behind the scenes, it actually handles various other devices for us also, but basically to the user it's nothing more than a big image.   This screens image will be stored in your computer Video Memory.  Which allows your computers graphics card to display it on your monitor, or even move it around in the video memory easily if need be. Yes, because video memory is shared commodity, windows can move stuff around when it's not being used !

 So how does this affect our programs ?  Well,  because nothing is nailed down inside windows, PB has to  constantly ask windows prior to accessing any of it's resources.


 Lets look at example. Often one of the first programs people write is something simple to render a bunch vector graphics elements to the screen.   In our example here we'll draw 5000 dots.

PlayBASIC Code: [Select]
OpenScreen 800,600,32,1

Do
; clear the screen to black (rgb(0,0,0)
Cls Rgb(0,0,0)

; draw a 5000 dots to the screen
For lp=0 to 5000
Dot Rnd(800),rnd(600)
next

; Show how fast the screen is updating
print Fps()

; show this screen image to the user.
Sync
loop



 This program creates a 800*600 display, draws 5000 dots to the screen and shows just how fast your computer can update the screen. or does it ?     I guess this comes from the assumption that we all think our computers are ultra fast today.   However, if you tried that demo you'll no doubt get a shock. It'll crawl...

 Why ?

  Now remember when we said that screen/images are shared resources?  Well, the problem in this case, is that every time we draw a single dot to the screen, PB has to beg windows for access to the screen. This begging process in windows speak, is called Surface Locking.

  So in order to draw a single dot, PB has do the following.


  Step 1) Request Lock on the screen surface and wait for it to be locked
  Step 2) Draw the dot
  Step 3) Unlock the screen surface.


 In the case of rendering a DOT, steps 2 & 3 are trivial,  the bottle neck occurs in step 1.

 When PB requests a lock on the surface, we're at the total mercy of windows to react quickly to this request each time.  Some times the locking is completed virtually instantly, but it generally seems to take anywhere from 1/2  millisecond to a few milliseconds or even more.   This might not sound like much time, but when we consider there's only 1000 milliseconds in one full second, and were going to be wasting the majority of that just locking and unlocking this surface constantly.

  Clearly there must be better way.



User Controlled Locking / Unlocking



  The solution to such problems lies firmly in the users hands.   If  you know your going to be drawing a batch of GFX items (as per our previous example) then  rather than force PB to constantly request a lock on the current surface each time it draws an item, you could do the locking and unlocking yourself.    To do this, we use the LockBuffer & UnlockBuffer commands.

 Lockbuffer will lock the current surface were drawing to.  The surface will remain locked until you call it's partner command "UnlockBuffer.   It should be noted,  that it's not advisiable to leave a surface locked indefinitely!    Windows tends can get very upset and close your program.  So prior to your drawing batch  lock  the surface, do your drawing,  then unlock it again.

 Lets put this new found knowledge to test and expand the previous example to include them.  This time we'll include Lock Buffer prior to draw our batch of dots, and the unlock once were done.


PlayBASIC Code: [Select]
OpenScreen 800,600,32,1

Do
; clear the screen to black (rgb(0,0,0)
Cls Rgb(0,0,0)

; Lock the Current Buffer
LockBuffer

; draw a 5000 dots to the screen
For lp=0 to 5000
Dot Rnd(800),rnd(600)
next

; now we unlock it.
UnLockBuffer

; Show how fast the screen is updating
print Fps()

; show this screen image to the user.
Sync
loop


 
 


Are there times when I shouldn't use lockBuffer ?


  Yes,  unfortunately when we lock a surface, there are few by products of this also.   While locking ensures that  windows won't try and move the surface while we're drawing to it,  it also restricts other devices  from doing anything to the surface until we've released our control over it.  This will mainly effect any rendering functionality that requires the graphics card GPU / blitter.  

  Some general guides when you've locked a surface,  will be to avoid attempting to the draw the following to that locked surface.

 * Text
 * Draw Video Images /3D image to the screen.
 * Cls / Box or any hardware accelerated primitives like polygons/boxes/rotated images.

 While nothing bad should happen,  what's likely to the occur is that item your drawing will unlock the before it starts to draw, or it'll simply refuse to draw anything since the surface was locked.  



stef


Hi!

Would it make sense that PB locks/unlocks automatically when rendering to screen?

Greetings
stef

kevin


It already is !   If you render anything, the target surface + source surfaces must be locked/unlocked.  When the surface the locked, nothing else can touch it until it's released. 


stef


Hm?! Yes!
But I mean automatically in a logical manner.
That it wouldn't be necessary use lockbuffer/unlockbuffer in your example above.

kevin


  Well, you could capture all drawing operations to a que, then draw them in order..    Hmm, sounds awfully  familiar doesn't it ? :)

stef

#5
Quote
  Well, you could capture all drawing operations to a que, then draw them in order..    Hmm, sounds awfully  familiar doesn't it ?

Never read before :)

What about using images (not drawing commands)

The example below should run faster!
But lockbuffer/unlockbuffer seems to be useless here

Sorry for using same/similar example again
it's using 3dimage for particles, drawn to a screensized image

use LMB / RMB for decrease/increase particles

needs PB1.65 or above!

OpenScreen 800,600,32,2

Type particle
x#,y#
dx#,dy#
angle#
speed#
alpha#
EndType


Dim Object As particle List

Global sun
Global ground
global sunpart
global partim
global NumbOfParticles
global MaxParticles=20

drawstuff()


Do
RenderToScreen
Cls RGB(0,0,255)
Print FPS()
print MaxParticles
print NumbOfParticles
print getarrayelements(object(),0)


if leftmousebutton()=1
flushmouse
MaxParticles=MaxParticles-5
endif
if rightmousebutton()=1
flushmouse
MaxParticles=MaxParticles+5
endif

if MaxParticles<0 then MaxParticles=0


if NumbOfParticles<MaxParticles
Addpart()
endif

calcparticles()

rendertoscreen

drawimage partim,0,0,1
DrawAlphaImage sun,600-40+Rnd(2),100-40+Rnd(2),alpha#,1
DrawAlphaImage ground,0,300,1.0-alpha#,1

alpha#=Sin(angle#)
alpha#=Abs(alpha#)
angle#=angle#+0.5


Sync
Loop

Function drawstuff()

sun=GetFreeImage()
Create3DImage sun,80,80
RenderToImage sun
CircleC 40,40,40,1,RGB(255,255,0)

ground=GetFreeImage()
Create3DImage ground,800,300
RenderToImage ground
Cls RGB(0,140,200)

sunpart=GetFreeImage()
Create3dImage sunpart,12,12
RenderToImage sunpart
CircleC 6,6,6,1,RGB(255,0,0)

partim=getfreeimage()
createimage partim,800,600

EndFunction



Function Addpart()

For x= 0 To 6

Object = New particle
Object.X# =600
Object.y# =100
Object.Angle# =Rnd(360)
Object.Speed# =1
Object.dx# =Cos(Object.angle#)*object.speed#
Object.dy# =Sin(Object.angle#)*object.speed#
Object.alpha#  =abs(Sin(Object.angle#))

Next

EndFunction




Function calcparticles()

rendertoimage partim

NumbOfParticles=0

//lockbuffer
For Each Object()

Object.x#=Object.x#+Object.dx#
Object.y#=Object.y#+Object.dy#


If PointInBox(Object.x#,Object.y#,10,10,790,590)=False

Object = NULL
Continue  

EndIf


DrawalphaImage sunpart,Object.x#,Object.y#,Object.alpha#,1

Object.alpha#=Object.alpha#+0.01
if Object.alpha#>1 then Object.alpha#=0

inc NumbOfParticles

Next

//UnLockBuffer


EndFunction




kevin


Stef,

  Read the last section -> Are there times when I shouldn't use lock ?




stef

#7
Hi!
Ok,Ok, I read again! :)

I'm a bit confused from posting these articles (imageblitting,lockbuffer..) at same time when you come up with PBFX/3D.
(I'm most interested in '2.5D' (I need alphablending,rotation, and zooming/scaling))

But does this (your article)  mean that 'lockbuffer' is probably unnecessary/not useful for PBFX?

Again to my example above:
With 1000 (12x12, alphablended) images it runs with about fps26 with PB1.65 on my laptop.
Is it possible to make it faster?
Could you image that it will run faster on further PBFX/3D releases?

Greetings
stef



kevin

#8
QuoteBut does this (your article)  mean that 'lockbuffer' is probably unnecessary/not useful for PBFX?  

 There's is no definitive answer..  It obviously depends on what your drawing, how your drawing it and to where.


QuoteWith 1000 (12x12, alphablended) images it runs with about fps26 with PB1.65 on my laptop.Is it possible to make it faster?
Could you image that it will run faster on further PBFX/3D releases?

  Yes, which has been covered before !

  Using Direct 3D is not all warm and fuzzy,  nor is it a magic solution,  there are some big fat bottle necks to overcome if it also.  The biggest is avoiding Render State changes +  Draw Primitive calls!    (Mentioned here (login required) Here & Here (login required) )

Effectively this causes your app to stall for much the same reason as calling the 2D blitter constantly makes it stall.   See: I like Big Blits

   Lets reiterate what currently occurs.  

    * Drawing any 3D primitive (triangles/images) ='s 1 direct 3D draw call of   ( + any required texture state changes).  

  The best way around this is drawing more polygons from the same texture/draw states in one hit.    Hence why i'm rebuilding Cameras,Sprites,Map etc to support this.  

  There are numerous tidbits in the update blogs about this,  Like Perspective Cameras (login required) & Here (login required)