Optimising Particles - Pooling etc.

BlitzMax Forums/BlitzMax Programming/Optimising Particles - Pooling etc.

The forum is locked. These are the original blitzbasic.com forums, kept so they can still be read. No new topics, replies or edits can be made here. To join in, head over to the Blitz3D+ forums.

I’ve whipped up several rough tests:

EDIT 21-MAR-08: Tweaked and improved the tests a little. Added a test incorporating dmaz's list logic.

Array and index-related rendering:
 
'Particle System Test
'Array and index-related rendering

SuperStrict

Framework BRL.GLMax2D
'Framework BRL.D3D7Max2D

Import BRL.Random
Import BRL.PNGLoader

Global ScreenWidth:Int = 800
Global ScreenHeight:Int = 600
Global MaxParticles:Int = 2000
Global SpriteURL:String = "c:\program files\blitzmax\samples\birdie\games\tiledrop\media\part.png"
'Note: A sprite will automatically be created if one is not found at the url above

Graphics ScreenWidth, ScreenHeight, 32
TParticle.Init MaxParticles, SpriteURL

Local Renders:Int, FPS:Int
Local CurrentMS:Int, OldMS:Int
Local MemAlloced:Int = GCMemAlloced()

SetBlend ALPHABLEND

MoveMouse ScreenWidth / 2, ScreenHeight / 2

While Not KeyHit(KEY_ESCAPE)

	Cls

	TParticle.Spawn MouseX(), MouseY()
	
	TParticle.RenderLive
		
	If KeyHit(KEY_M) Then MemAlloced = GCMemAlloced()
	
	SetColor 255, 255, 255
	SetAlpha 1.0
	SetScale 1.0, 1.0
		
	Renders :+ 1
	CurrentMS = MilliSecs()	
		
	If (CurrentMS - OldMS) >= 1000
		OldMS = CurrentMS
		FPS = Renders
		Renders = 0
	EndIf

	DrawText FPS + " FPS", 10, 10
	DrawText "Memory: " + MemAlloced + " bytes", 10, 30
	DrawText "Live particles: " + TParticle.QtyAlive, 10, 70
	DrawText "Press M to refresh memory useage value.", 10, ScreenHeight - 30
	
	Flip 0

Wend


End


Type TParticle


	Const STATUS_DEAD:Int = 0
	Const STATUS_LIVE:Int = 1
	
	Global Particles:TParticle[]
	Global QtyAlive:Int
	
	Global ParticleImage:TImage
	
	
	Function Init(qty:Int, img_url:String= "")
	
		Local pixmap:TPixmap = LoadPixmap(img_url)
		
		If pixmap Then 
			Pixmap = ResizePixmap(pixmap, 64, 64) 				
			ParticleImage = LoadImage(pixmap)
		Else
			ParticleImage = createSprite()
		EndIf
		
		Particles = New TParticle[qty]
		
		For Local i:Int = 0 To Particles.length - 1
				
			Particles[i] = New TParticle
			
		Next
		
		QtyAlive = 0
		
		pixmap = Null
		GCCollect
		
	EndFunction
	
	
	Function Spawn(x:Int, y:Int)
			
		If QtyAlive < Particles.length
		
			QtyAlive :+ 1
			
			Particles[QtyAlive - 1].X = x
			Particles[QtyAlive - 1].Y = y
			
			Particles[QtyAlive - 1].XDir = Rnd(-0.1, 0.1)
			Particles[QtyAlive - 1].YDir = Rnd(-0.1, 0.1)
			Particles[QtyAlive - 1].R = Rand(32, 255)
			Particles[QtyAlive - 1].G = Rand(32, 255)
			Particles[QtyAlive - 1].B = Rand(32, 255)
			Particles[QtyAlive - 1].Size = Rnd(0.1, 0.5)
			Particles[QtyAlive - 1].Alpha = 1.0
			
		EndIf
			
	EndFunction
	
	
	Function RenderLive()
		
		For Local i:Int = 0 To QtyAlive - 1
	
			If Particles[i].Render() = STATUS_DEAD 
			
				Local temp:TParticle = Particles[QtyAlive - 1]
				temp.Render
				Particles[QtyAlive - 1] = Particles[i]
				Particles[i] = temp
				QtyAlive :- 1
				
			EndIf
			
		Next
		
	EndFunction
	
	
	'-------------------------------------
	
	
	Field X:Float, Y:Float
	Field XDir:Float, YDir:Float
	Field R:Byte, G:Byte, B:Byte
	Field Size:Float
	Field Alpha:Float


	Method Render:Int()
	
		SetColor R, G, B
		SetAlpha Alpha
		
		X :+ XDir
		Y :+ YDir
				
		SetScale Size, Size
		DrawImage ParticleImage, X, Y
	
		Alpha :- 0.00005
		
		Local Dead:Int = False
		
		If Alpha <= 0.0 Then Dead = True
		If X < 0 Or X > ScreenWidth Then Dead = True
		If Y < 0 Or Y > ScreenHeight Then Dead = True
				
		If Dead Then Return STATUS_DEAD Else Return STATUS_LIVE
		
	EndMethod


EndType


Function CreateSprite:TImage()

	Local size:Int = 64

	Local img:TImage = CreateImage(size, size, 1, DYNAMICIMAGE|MASKEDIMAGE)
	
	SetBlend SOLIDBLEND
	SetColor 255, 255, 255
	DrawOval 0, 0, Size, Size	
	
	GrabImage(img, 0, 0)

	Return img

EndFunction



Array and status-related rendering:
 
'Particle System Test
'Array and status-related rendering

SuperStrict

Framework BRL.GLMax2D
'Framework BRL.D3D7Max2D

Import BRL.Random
Import BRL.PNGLoader

Global ScreenWidth:Int = 800
Global ScreenHeight:Int = 600
Global MaxParticles:Int = 2000
Global SpriteURL:String = "c:\program files\blitzmax\samples\birdie\games\tiledrop\media\part.png"
'Note: A sprite will automatically be created if one is not found at the url above

Graphics ScreenWidth, ScreenHeight, 32
TParticle.Init MaxParticles, SpriteURL

Local Renders:Int, FPS:Int
Local CurrentMS:Int, OldMS:Int
Local MemAlloced:Int = GCMemAlloced()

SetBlend ALPHABLEND

MoveMouse ScreenWidth / 2, ScreenHeight / 2

While Not KeyHit(KEY_ESCAPE)

	Cls

	TParticle.Spawn MouseX(), MouseY()

	TParticle.RenderLive
		
	If KeyHit(KEY_M) Then MemAlloced = GCMemAlloced()
	
	SetColor 255, 255, 255
	SetAlpha 1.0
	SetScale 1.0, 1.0
		
	Renders :+ 1
	CurrentMS = MilliSecs()	
		
	If (CurrentMS - OldMS) >= 1000
		OldMS = CurrentMS
		FPS = Renders
		Renders = 0
	EndIf

	DrawText FPS + " FPS", 10, 10
	DrawText "Memory: " + MemAlloced + " bytes", 10, 30
	DrawText "Live particles: " + TParticle.LiveCount, 10, 70
	DrawText "Press M to refresh memory useage value.", 10, ScreenHeight - 30
	
	Flip 0

Wend


End


Type TParticle


	Const STATUS_DEAD:Int = 0
	Const STATUS_LIVE:Int = 1

	Global Particles:TParticle[]
	Global LiveCount:Int

	Global ParticleImage:TImage
	
	
	Function Init(qty:Int, img_url:String= "")
	
		Local pixmap:TPixmap = LoadPixmap(img_url)
		
		If pixmap Then 
			Pixmap = ResizePixmap(pixmap, 64, 64) 				
			ParticleImage = LoadImage(pixmap)
		Else
			ParticleImage = createSprite()
		EndIf

		Particles = New TParticle[qty]
		
		For Local i:Int = 0 To Particles.length - 1
				
			Particles[i] = New TParticle
			Particles[i].Status = STATUS_DEAD
			
		Next
		
		LiveCount = 0
		
		pixmap = Null
		GCCollect

	EndFunction
	
	
	Function Spawn(x:Int, y:Int)
			
		For Local p:TParticle = EachIn Particles
		
			If p.Status = STATUS_DEAD
			
				p.X = x
				p.Y = y
				p.XDir = Rnd(-0.1, 0.1)
				p.YDir = Rnd(-0.1, 0.1)
				p.R = Rand(32, 255)
				p.G = Rand(32, 255)
				p.B = Rand(32, 255)
				p.Size = Rnd(0.1, 0.5)
				p.Alpha = 1.0
				p.Status = STATUS_LIVE
			
				LiveCount :+ 1
			
				Exit
			
			EndIf
		
		Next	
			
	EndFunction
	
	
	Function RenderLive()
		
		For Local p:TParticle = EachIn Particles
	
			p.Render() 
		
		Next
		
	EndFunction
	
	
	'-------------------------------------
	
	
	Field X:Float, Y:Float
	Field XDir:Float, YDir:Float
	Field R:Byte, G:Byte, B:Byte
	Field Size:Float
	Field Alpha:Float
	Field Status:Byte

	Method Render()
	
		If Status = STATUS_LIVE
	
			SetColor R, G, B
			SetAlpha Alpha
		
			X :+ XDir
			Y :+ YDir
			
			SetScale Size, Size
			DrawImage ParticleImage, X, Y
	
			Alpha :- 0.00005

			Local Dead:Int = False
			
			If Alpha <= 0.0 Then Dead = True
			If X < 0 Or X > ScreenWidth Then Dead = True
			If Y < 0 Or Y > ScreenHeight Then Dead = True
				
			If Dead 
				Status = STATUS_DEAD
				LiveCount :- 1
			EndIf
		
		EndIf
		
	EndMethod


EndType


Function CreateSprite:TImage()

	Local size:Int = 64

	Local img:TImage = CreateImage(size, size, 1, DYNAMICIMAGE|MASKEDIMAGE)
	
	SetBlend SOLIDBLEND
	SetColor 255, 255, 255
	DrawOval 0, 0, Size, Size	
	
	GrabImage(img, 0, 0)

	Return img

EndFunction



List and real-time particle creation / deletion:
 
'Particle System Test
'List and real-time particle creation / deletion

SuperStrict

Framework BRL.GLMax2D
'Framework BRL.D3D7Max2D

Import BRL.Random
Import BRL.PNGLoader

Global ScreenWidth:Int = 800
Global ScreenHeight:Int = 600
Global MaxParticles:Int = 2000
Global SpriteURL:String = "c:\program files\blitzmax\samples\birdie\games\tiledrop\media\part.png"
'Note: A sprite will automatically be created if one is not found at the url above

Graphics ScreenWidth, ScreenHeight, 32
TParticle.Init MaxParticles, SpriteURL

Local Renders:Int, FPS:Int
Local CurrentMS:Int, OldMS:Int
Local MemAlloced:Int = GCMemAlloced()

SetBlend ALPHABLEND

MoveMouse ScreenWidth / 2, ScreenHeight / 2

While Not KeyHit(KEY_ESCAPE)

	Cls

	TParticle.Spawn MouseX(), MouseY()

	TParticle.RenderLive
		
	If KeyHit(KEY_M) Then MemAlloced = GCMemAlloced()
	
	SetColor 255, 255, 255
	SetAlpha 1.0
	SetScale 1.0, 1.0
		
	Renders :+ 1
	CurrentMS = MilliSecs()	
		
	If (CurrentMS - OldMS) >= 1000
		OldMS = CurrentMS
		FPS = Renders
		Renders = 0
	EndIf

	DrawText FPS + " FPS", 10, 10
	DrawText "Memory: " + MemAlloced + " bytes", 10, 30
	DrawText "Live particles: " + TParticle.LiveCount, 10, 70
	DrawText "Press M to refresh memory useage value.", 10, ScreenHeight - 30
	
	Flip 0

Wend


End


Type TParticle


	Const STATUS_DEAD:Int = 0
	Const STATUS_LIVE:Int = 1

	Global MaxParticles:Int
	Global Particles:TList
	Global LiveCount:Int

	Global ParticleImage:TImage
	
	
	Function Init(qty:Int, img_url:String= "")
	
		Local pixmap:TPixmap = LoadPixmap(img_url)
		If pixmap Then 
			Pixmap = ResizePixmap(pixmap, 64, 64) 				
			ParticleImage = LoadImage(pixmap)
		Else
			ParticleImage = createSprite()
		EndIf 
		
		MaxParticles = qty
		Particles = New TList		
		LiveCount = 0
		
		pixmap = Null
		GCCollect
		
	EndFunction
	
	
	Function Spawn(x:Int, y:Int)
			
		If LiveCount < MaxParticles
		
			Local p:TParticle = New TParticle
			p.X = x
			p.Y = y
			p.XDir = Rnd(-0.1, 0.1)
			p.YDir = Rnd(-0.1, 0.1)
			p.R = Rand(32, 255)
			p.G = Rand(32, 255)
			p.B = Rand(32, 255)
			p.Size = Rnd(0.1, 0.5)
			p.Alpha = 1.0
						
			Particles.AddLast p
		
			LiveCount :+ 1
		
		EndIf
			
	EndFunction
	
	
	Function RenderLive()
		
		For Local p:TParticle = EachIn Particles
	
			If p.Render() = STATUS_DEAD 
				Particles.Remove p
				LiveCount :- 1
			EndIf
		
		Next
		
	EndFunction
	
	
	'-------------------------------------
	
	
	Field X:Float, Y:Float
	Field XDir:Float, YDir:Float
	Field R:Byte, G:Byte, B:Byte
	Field Size:Float
	Field Alpha:Float

	Method Render:Int()
	
		SetColor R, G, B
		SetAlpha Alpha
		
		X :+ XDir
		Y :+ YDir
		
		SetScale Size, Size
		DrawImage ParticleImage, X, Y
	
		Alpha :- 0.00005
	
		Local Dead:Int = False
		
		If Alpha <= 0.0 Then Dead = True
		If X < 0 Or X > ScreenWidth Then Dead = True
		If Y < 0 Or Y > ScreenHeight Then Dead = True

		If Dead Then Return STATUS_DEAD Else Return STATUS_LIVE

	EndMethod


EndType


Function CreateSprite:TImage()

	Local size:Int = 64

	Local img:TImage = CreateImage(size, size, 1, DYNAMICIMAGE|MASKEDIMAGE)
	
	SetBlend SOLIDBLEND
	SetColor 255, 255, 255
	DrawOval 0, 0, Size, Size	
	
	GrabImage(img, 0, 0)

	Return img

EndFunction



Particle pool list and live particle list:
 
'Particle System Test
'Particle pool list and live particle list

SuperStrict

Framework BRL.GLMax2D
'Framework BRL.D3D7Max2D

Import BRL.Random
Import BRL.PNGLoader

Global ScreenWidth:Int = 800
Global ScreenHeight:Int = 600
Global MaxParticles:Int = 2000
Global SpriteURL:String = "c:\program files\blitzmax\samples\birdie\games\tiledrop\media\part.png"
'Note: A sprite will automatically be created if one is not found at the url above

Graphics ScreenWidth, ScreenHeight, 32
TParticle.Init MaxParticles, SpriteURL

Local Renders:Int, FPS:Int
Local CurrentMS:Int, OldMS:Int
Local MemAlloced:Int = GCMemAlloced()

SetBlend ALPHABLEND

MoveMouse ScreenWidth / 2, ScreenHeight / 2

While Not KeyHit(KEY_ESCAPE)

	Cls

	TParticle.Spawn MouseX(), MouseY()
	
	TParticle.RenderLive
		
	If KeyHit(KEY_M) Then MemAlloced = GCMemAlloced()
	
	SetColor 255, 255, 255
	SetAlpha 1.0
	SetScale 1.0, 1.0
		
	Renders :+ 1
	CurrentMS = MilliSecs()	
		
	If (CurrentMS - OldMS) >= 1000
		OldMS = CurrentMS
		FPS = Renders
		Renders = 0
	EndIf

	DrawText FPS + " FPS", 10, 10
	DrawText "Memory: " + MemAlloced + " bytes", 10, 30
	DrawText "Live particles: " + TParticle.LiveCount, 10, 70
	DrawText "Press M to refresh memory useage value.", 10, ScreenHeight - 30
	
	Flip 0

Wend


End


Type TParticle


	Const STATUS_DEAD:Int = 0
	Const STATUS_LIVE:Int = 1

	Global ParticlePool:TList
	Global LiveParticles:TList
	Global LiveCount:Int
	
	Global ParticleImage:TImage

	
	Function Init(qty:Int, img_url:String= "")
	
		Local pixmap:TPixmap = LoadPixmap(img_url)
		If pixmap Then 
			Pixmap = ResizePixmap(pixmap, 64, 64) 				
			ParticleImage = LoadImage(pixmap)
		Else
			ParticleImage = createSprite()
		EndIf 

		ParticlePool = New TList
		
		For Local n:Int = 1 To qty
			Local p:TParticle = New TParticle
			ParticlePool.AddLast p
		Next
		
		LiveParticles = New TList
		
		LiveCount = 0
		
		pixmap = Null
		GCCollect
		
	EndFunction
	
	
	Function Spawn(x:Int, y:Int)
			
		Local p:TParticle = Null	
			
		If Not(ParticlePool.IsEmpty()) 
			
			p = TParticle(ParticlePool.RemoveFirst())
	
			p.X = x
			p.Y = y
			p.XDir = Rnd(-0.1, 0.1)
			p.YDir = Rnd(-0.1, 0.1)
			p.R = Rand(32, 255)
			p.G = Rand(32, 255)
			p.B = Rand(32, 255)
			p.Size = Rnd(0.1, 0.5)
			p.Alpha = 1.0
						
			LiveParticles.AddLast p
					
			LiveCount :+ 1

		EndIf
			
	EndFunction
	
	
	Function RenderLive()
		
		For Local p:TParticle = EachIn LiveParticles
	
			If p.Render() = STATUS_DEAD 
				LiveParticles.Remove p
				ParticlePool.AddLast p
				LiveCount :- 1
			EndIf
		
		Next
		
	EndFunction
	
	
	'-------------------------------------
	
	
	Field X:Float, Y:Float
	Field XDir:Float, YDir:Float
	Field R:Byte, G:Byte, B:Byte
	Field Size:Float
	Field Alpha:Float

	Method Render:Int()
	
		SetColor R, G, B
		SetAlpha Alpha
		
		X :+ XDir
		Y :+ YDir
				
		SetScale Size, Size
		DrawImage ParticleImage, X, Y
	
		Alpha :- 0.00005
	
		Local Dead:Int = False
		
		If Alpha <= 0.0 Then Dead = True
		If X < 0 Or X > ScreenWidth Then Dead = True
		If Y < 0 Or Y > ScreenHeight Then Dead = True

		If Dead Then Return STATUS_DEAD Else Return STATUS_LIVE

	EndMethod


EndType


Function CreateSprite:TImage()

	Local size:Int = 64

	Local img:TImage = CreateImage(size, size, 1, DYNAMICIMAGE|MASKEDIMAGE)
	
	SetBlend SOLIDBLEND
	SetColor 255, 255, 255
	DrawOval 0, 0, Size, Size	
	
	GrabImage(img, 0, 0)

	Return img

EndFunction




'Particle pool list and live particle list
'(Incorporating dmaz's non-BRL list logic):

'Particle System Test
'Particle pool list and live particle list
'(Incorporating dmaz's non-BRL list logic)

SuperStrict

Framework BRL.GLMax2D
'Framework BRL.D3D7Max2D

Import BRL.Random
Import BRL.PNGLoader

Global ScreenWidth:Int = 800
Global ScreenHeight:Int = 600
Global MaxParticles:Int = 2000
Global SpriteURL:String = "c:\program files\blitzmax\samples\birdie\games\tiledrop\media\part.png"
'Note: A sprite will automatically be created if one is not found at the url above

Graphics ScreenWidth, ScreenHeight, 32
TParticle.Init MaxParticles, SpriteURL

Local Renders:Int, FPS:Int
Local CurrentMS:Int, OldMS:Int
Local MemAlloced:Int = GCMemAlloced()

SetBlend ALPHABLEND

MoveMouse ScreenWidth / 2, ScreenHeight / 2

While Not KeyHit(KEY_ESCAPE)

	Cls

	TParticle.Spawn MouseX(), MouseY()

	TParticle.RenderLive
	
	If KeyHit(KEY_M) Then MemAlloced = GCMemAlloced()
	
	SetColor 255, 255, 255
	SetAlpha 1.0
	SetScale 1.0, 1.0
		
	Renders :+ 1
	CurrentMS = MilliSecs()	
		
	If (CurrentMS - OldMS) >= 1000
		OldMS = CurrentMS
		FPS = Renders
		Renders = 0
	EndIf

	DrawText FPS + " FPS", 10, 10
	DrawText "Memory: " + MemAlloced + " bytes", 10, 30
	DrawText "Live particles: " + TParticle.LiveCount, 10, 70
	DrawText "Press M to refresh memory useage value.", 10, ScreenHeight - 30
	
	Flip 0

Wend


End


Type TParticle


	Const STATUS_DEAD:Int = 0
	Const STATUS_LIVE:Int = 1

	Global ParticlePool:TParticle
	Global LiveParticles:TParticle
	Global LiveCount:Int
	
	Global ParticleImage:TImage

	
	Function Init(qty:Int, img_url:String= "")
	
		Local pixmap:TPixmap = LoadPixmap(img_url)
		If pixmap Then 
			Pixmap = ResizePixmap(pixmap, 64, 64) 				
			ParticleImage = LoadImage(pixmap)
		Else
			ParticleImage = createSprite()
		EndIf 

		For Local n:Int = 1 To qty
			Local p:TParticle = New TParticle
			p.NextParticle = ParticlePool
			ParticlePool = p
		Next
		
		LiveCount = 0
		
		pixmap = Null
		GCCollect
		
	EndFunction
	
	
	Function Spawn(x:Int, y:Int)
			
		Local p:TParticle = Null	
			
		If ParticlePool <> Null
			
			p = ParticlePool
			ParticlePool = p.NextParticle
				
			p.X = x
			p.Y = y
			p.XDir = Rnd(-0.1, 0.1)
			p.YDir = Rnd(-0.1, 0.1)
			p.R = Rand(32, 255)
			p.G = Rand(32, 255)
			p.B = Rand(32, 255)
			p.Size = Rnd(0.1, 0.5)
			p.Alpha = 1.0
						
			p.NextParticle = LiveParticles
			LiveParticles = p
					
			LiveCount :+ 1

		EndIf
			
	EndFunction
	
	
	Function RenderLive()
		
		Local lp:TParticle
		Local np:TParticle
		Local p:TParticle = LiveParticles
		While p
			np = p.NextParticle
			If p.Render() = STATUS_DEAD 
				If lp
					lp.NextParticle = p.NextParticle
				Else
					LiveParticles = p.NextParticle
				EndIf
				p.NextParticle = ParticlePool
				ParticlePool = p
				LiveCount :- 1
			Else
				lp = p
			EndIf
			p = np
		Wend
		
	EndFunction
	
	
	'-------------------------------------
	
	
	Field X:Float, Y:Float
	Field XDir:Float, YDir:Float
	Field R:Byte, G:Byte, B:Byte
	Field Size:Float
	Field Alpha:Float
	Field NextParticle:TParticle

	Method Render:Int()
	
		SetColor R, G, B
		SetAlpha Alpha
		
		X :+ XDir
		Y :+ YDir
				
		SetScale Size, Size
		DrawImage ParticleImage, X, Y
	
		Alpha :- 0.00005
	
		Local Dead:Int = False
		
		If Alpha <= 0.0 Then Dead = True
		If X < 0 Or X > ScreenWidth Then Dead = True
		If Y < 0 Or Y > ScreenHeight Then Dead = True

		If Dead Then Return STATUS_DEAD Else Return STATUS_LIVE

	EndMethod


EndType


Function CreateSprite:TImage()

	Local size:Int = 64

	Local img:TImage = CreateImage(size, size, 1, DYNAMICIMAGE|MASKEDIMAGE)
	
	SetBlend SOLIDBLEND
	SetColor 255, 255, 255
	DrawOval 0, 0, Size, Size	
	
	GrabImage(img, 0, 0)

	Return img

EndFunction