What is faster?

Blitz3D Forums/Blitz3D Beginners Area/What is faster?

PowerPC603(Posted 2009) [#1]
Hi,

I've no real problem with this, because it's working, but I wanna optimize my code to render faster.

I've designed a level of a house for my Arkanoid game and I have snowflakes swirling down on that level.
Those snowflakes are just tiny spheres.
Every frame, there is a given chance (20%) that a snowflake (sphere) is created, and it's positioned above the level and at a random X coordinate.

Also every frame, all snowflakes are translated downwards and a bit to the left/right, to simulate that they are swirling down instead of just straight down.
Once they reach the ground, they are freed.

After a while, there are over 200 snowflakes on my screen (separate spheres and thus, separate surfaces).
It can be even more if I increase the chance-parameter for my CreateSnow function.

On my laptop it's no problem, but slower computers will slow down because of so many surfaces.


I've been thinking about one big single-surface mesh, where each snowflake would be a quad with a small texture (8x8 for example).
Then every frame, the entire single-surface mesh would need to be freed and re-created, based on the positions of the snowflakes.

What would be faster?
- just create snowflakes as spheres every frame, like it is now?
- free the mesh (all snowflake quads combined into one single surface mesh) and recreate it every frame?

To see the snow, with separate spheres (levels 1 and 2 have snow):
http://users.telenet.be/vge/Arkanoid3D/Arkanoid3D.zip


Guy Fawkes(Posted 2009) [#2]
free the mesh after they touch the ground or the object in the way. but allow for it to repeat the process if u need to.

this should free the memory the "snowflakes" use.


_PJ_(Posted 2009) [#3]
I had a simple particle system that used sprites for snowflakes that would float down with a slight left/right wobble.

Spheres are unnecessary for siple things like snoowflakes I think, so quads would be best.

The code was based on the "Simple Starfield" code in the archives, and so, there were only ever around 20 snowflakes, one these disappeareed /hit the ground, they were 'recycled' by just re-positioning them back to a location above the field of view where they began falling again.

There's no need to destroy and recreaate them each time :)

Also, if you use AddMesh you could probably link a few snoeflakes together, even if they're not physically anywhere near each other, (just make sure the linked ones keep the same height) meaning you only need to do the calculations for the added-mesh, not every flake!


PowerPC603(Posted 2009) [#4]
Quads could be better, I agree.
The spheres were faster to implement and was only for testing.

But to add several quads to one mesh (AddMesh), this will prevent the code from wobbling each snowflake separately.
If I would add 10 snowflakes into one mesh, and then do the wobbling, they will all wobble the same way, as it's one mesh.
That's not really what I'm after, it may look unreal.

The snowflakes are already freed when they hit the ground (the mesh/entity is freed and the snowflake type-instance is deleted). I use types to hold their position and the entity. It's easier this way to keep track of all entities.
The snowflakes are positioned in front of everything else, as I wanted the snowflakes to be visible all the way down, so they won't hit anything else but the ground.

Isn't this what single surface systems do?
Create quads and add them all to one big mesh to keep the surface-count minimal.
And if one quad is deleted or moved, the entire big-mesh is re-created?

For 200 snowflakes, this would mean a big-mesh of 800 vertices and 400 triangles.
I guess re-creating this bigmesh every frame would be faster than rendering 200 separate surfaces.
But I don't know for sure, that's why I'm asking, as other people may have more experience with single-surface systems and how they work.


PowerPC603(Posted 2009) [#5]
My code for the snowflakes is really small as you can see:
Function DrawSnow(Chance)
	Local SnowBall.TSnowBall ; A reference to a snowball instance
	Local X ; The X-position where the snowball should be created

	; If a snowball should be created
	If Rand(0, 100) <= Chance Then
		SnowBall.TSnowBall = New TSnowBall
		SnowBall\Entity = CreateSphere(3)
		ScaleEntity SnowBall\Entity, 0.35, 0.35, 0.35
		PositionEntity SnowBall\Entity, Rand(1, 150), 150, -5
	EndIf

	; Let all snowballs fall down
	For SnowBall.TSnowBall = Each TSnowBall
		; If the snowball is still falling (height is higher than 0.0)
		If EntityY#(SnowBall\Entity) > 0.0 Then
			; Let the snowball fall down (and move it a bit left and right)
			TranslateEntity SnowBall\Entity, Rnd#(-0.15, 0.15), -0.15, 0
		Else
			; If the snowball has reached the ground, free it's entity and delete the snowball instance
			FreeEntity SnowBall\Entity
			Delete SnowBall
		EndIf
	Next
End Function


In my mainloop, I call this function every frame, so the snowflakes are processed each frame.


Stevie G(Posted 2009) [#6]
Single surface is much faster when throwing alot of quads around the screen when compared to lots of individual surfaces. You don't need to clear and recreate each quad when one is removed, simply move the vertices off screen so that they aren't visible and recycle. Alternatively, you can use entityfx 32+2 to enable vertexalpha and make them invisible.

There's no reason why you can't also have rotation on the quads. Personally I find it simpler to create a pivot for each quad, do rotations and movement on the pivots and amend the vertexcoords of each vertex of the quad based on the pivots position and rotation.


PowerPC603(Posted 2009) [#7]
Just got home and tried the single surface method I was thinking about.
For 200 snowflakes, it didn't improve much.
But I've tried 1000 snowflakes, the single surface method was significantly faster.

; Snow test
; Setup screen resolution
Const ScreenX = 1024, ScreenY = 768
; Setup graphics
Graphics3D ScreenX, ScreenY, 0, 1
; Use the backbuffer
SetBuffer BackBuffer()
SeedRnd 255


; Setup globals
Global StartCounting, StopCounting
Global Snow



; Setup types
Type TSnowBall
	Field X#
	Field Y#
	Field Entity
End Type



;*******************************************************************************
; Setup camera (create camera, position camera, projection mode (orthographic), zoom factor)
;*******************************************************************************
Global camera = CreateCamera()
PositionEntity camera, 97, 60, -1000
CameraProjMode camera, 2
CameraZoom camera, 0.01



For i = 1 To 1000
	SnowBall.TSnowBall = New TSnowBall
	SnowBall\X# = Rnd#(0.0, 150.0)
	SnowBall\Y# = Rnd#(0.0, 120.0)
;	SnowBall\Entity = CreateMesh()
;	Surf = CreateSurface(SnowBall\Entity)
;	v0 = AddVertex(Surf, -0.35, -0.35, 0)
;	v1 = AddVertex(Surf, -0.35, 0.35, 0)
;	v2 = AddVertex(Surf, 0.35, 0.35, 0)
;	v3 = AddVertex(Surf, 0.35, -0.35, 0)
;	t0 = AddTriangle(Surf, v0, v1, v2)
;	t1 = AddTriangle(Surf, v2, v3, v0)
;	PositionEntity SnowBall\Entity, SnowBall\X#, SnowBall\Y#, -5
Next



; Mainloop
While Not KeyHit(1)
	; Draw the snow
	DrawSnowSS()

	; Render the world
	RenderWorld

	; Flip the backbuffer and frontbuffer (draw everything to the screen)
	Flip False
Wend

End



Function DrawSnowSS()
	Local SnowBall.TSnowBall ; A reference to a snowball instance
	Local Surf

	; Free the current bigmesh
	FreeEntity Snow
	; Create a new bigmesh with one surface
	Snow = CreateMesh()
	Surf = CreateSurface(Snow)

	; Recreate the Snow bigmesh
	For SnowBall.TSnowBall = Each TSnowBall
		v0 = AddVertex(Surf, SnowBall\X# - 0.35, SnowBall\Y# - 0.35, 0)
		v1 = AddVertex(Surf, SnowBall\X# - 0.35, SnowBall\Y# + 0.35, 0)
		v2 = AddVertex(Surf, SnowBall\X# + 0.35, SnowBall\Y# + 0.35, 0)
		v3 = AddVertex(Surf, SnowBall\X# + 0.35, SnowBall\Y# - 0.35, 0)
		t0 = AddTriangle(Surf, v0, v1, v2)
		t1 = AddTriangle(Surf, v2, v3, v0)
	Next
End Function


As you can see here, this test-program creates 1000 TSnowBall instances and gives them a random position on the screen.
In the mainloop, the function DrawSnowSS() is called every frame, which frees the "Snow" single-surface mesh which contains all the quads and re-creates it from scratch.

Since I've disabled vsync (flip false) and have Fraps enabled, this gave me a framerate of 500 fps.

When I comment the DrawSnowSS() function call in the mainloop and uncomment the commented lines in the for-next loop above the mainloop, this drops to 100 fps.
This creates 1000 separate quads, so Renderworld needs to render 1000 separate surfaces.

When the number of snowflakes gets increased to 5000 snowflakes, using separate quads reduces framerate to 20 fps.
Using the DrawSnowSS() function (like in the above example), framerate is 120 fps.

So I can better use the single-surface method for my snowflakes.


PowerPC603(Posted 2009) [#8]
@Stevie G:
I guess this would require the TSnowFlake instances to hold the vertex coords too (or at least the handle to them, as returned by the AddVertex commands), in case they need to swirl down (move the snowflakes a bit left or right)?

Would this be even faster?
Instead of re-adding all the vertices to the bigmesh every frame, just move the vertices offscreen when the snowflake has hit the ground?
And set that TSnowBall instance to inactive (extra field)? So it can be used later again without the need for deleting the instance?


Yasha(Posted 2009) [#9]
You certainly don't need to free the mesh every frame! If you go for that approach, just use ClearSurface instead, which will be slightly faster than freeing the whole entity and creating a new one.

There should be no speed difference between clearing the surface each frame and adding the vertices anew, and simply moving them to a dead position, so just do whichever one is simpler to code, as internally the two methods ought to be handled in much the same way (I would say clearing the surface, but haven't experimented with recycling).


PowerPC603(Posted 2009) [#10]
I haven't thought about recycling instances before, so I gave it a try.

I've reworked the testprogram to try recycling the TSnowBall instances and not re-creating the bigmesh every frame.

Now, when there is no recycled snowball instance, a new snowball instance will be created, it's coordinates initialized (somewhere above the playfied) and a quad is added to the bigmesh.
If there is a recycled snowball instance, it's used again (recycled-field reset to false).

Then the function looks up all non-recycled snowball instances and moves them downwards (they swirl down).
The position of the snowball is updated and the vertices are moved accordingly.

If the snowball hits the ground, the snowball instance is set as "recycled", it's coordinates are re-initialized (somewhere above the playfield) and it's vertices are moved accordingly, so it can be used again immediately when it's needed.

; Snow test
; Setup screen resolution
Const ScreenX = 1024, ScreenY = 768
; Setup graphics
Graphics3D ScreenX, ScreenY, 0, 1
; Use the backbuffer
SetBuffer BackBuffer()



; Setup globals
Global Snow = CreateMesh()
Global Surf = CreateSurface(Snow)
Global Counter



; Setup types
Type TSnowBall
	Field X#, Y#
	Field v0, v1, v2, v3
	Field Recycled
End Type



;*******************************************************************************
; Setup camera (create camera, position camera, projection mode (orthographic), zoom factor)
;*******************************************************************************
Global camera = CreateCamera()
PositionEntity camera, 97, 60, -1000
CameraProjMode camera, 2
CameraZoom camera, 0.01



; Mainloop
While Not KeyHit(1)
	; Draw the snow
	DrawSnow(100)

	; Render the world
	RenderWorld

	; Flip the backbuffer and frontbuffer (draw everything to the screen)
	Flip False
Wend

End



Function DrawSnow(Chance)
	Local SnowBall.TSnowBall ; A reference to a snowball instance

	; If a snowball should be created
	If Rand(1, 100) <= Chance Then
		; Check if there is any recycled snowball instance
		SnowBall.TSnowBall = RecycledSnowBall()
		; Create a new instance (if there is no recycled instance)
		If SnowBall = Null Then
			Counter = Counter + 1 : DebugLog Counter
			; There is no recycled snowball instance, so create a new snowball instance
			SnowBall.TSnowBall = New TSnowBall : SnowBall\Recycled = False
			; Set X and Y coordinates for the snowball
			SnowBall\X# = Rnd#(0.0, 150.0)
			SnowBall\Y# = 150.0
			; Add the vertices and triangles for this snowball to the bigmesh
			SnowBall\v0 = AddVertex(Surf, SnowBall\X# - 0.35, SnowBall\Y# - 0.35, -5)
			SnowBall\v1 = AddVertex(Surf, SnowBall\X# - 0.35, SnowBall\Y# + 0.35, -5)
			SnowBall\v2 = AddVertex(Surf, SnowBall\X# + 0.35, SnowBall\Y# + 0.35, -5)
			SnowBall\v3 = AddVertex(Surf, SnowBall\X# + 0.35, SnowBall\Y# - 0.35, -5)
			t0 = AddTriangle(Surf, SnowBall\v0, SnowBall\v1, SnowBall\v2)
			t1 = AddTriangle(Surf, SnowBall\v2, SnowBall\v3, SnowBall\v0)
		Else
			; If there is a recycled snowball instance, re-use it
			SnowBall\Recycled = False
		EndIf
	EndIf

	; Let the snowballs swirl down
	For SnowBall.TSnowBall = Each TSnowBall
		; If the snowball isn't a recycled one
		If SnowBall\Recycled = False Then
			; If the snowball hasn't reached the ground yet
			If SnowBall\Y# > 0.0 Then
				; Let the snowball swirl down
				SnowBall\X# = SnowBall\X# + Rnd#(-0.15, 0.15) : SnowBall\Y# = SnowBall\Y# - 0.15
				; Move it's vertices accordingly
				VertexCoords Surf, SnowBall\v0, SnowBall\X# - 0.35, SnowBall\Y# - 0.35, -5
				VertexCoords Surf, SnowBall\v1, SnowBall\X# - 0.35, SnowBall\Y# + 0.35, -5
				VertexCoords Surf, SnowBall\v2, SnowBall\X# + 0.35, SnowBall\Y# + 0.35, -5
				VertexCoords Surf, SnowBall\v3, SnowBall\X# + 0.35, SnowBall\Y# - 0.35, -5
			Else
				; If the snowball has hit the ground, re-initialize the coordinates (just outside the screen) and recycle the instance
				SnowBall\X# = Rnd#(0.0, 150.0) : SnowBall\Y# = 150.0
				VertexCoords Surf, SnowBall\v0, SnowBall\X# - 0.35, SnowBall\Y# - 0.35, -5
				VertexCoords Surf, SnowBall\v1, SnowBall\X# - 0.35, SnowBall\Y# + 0.35, -5
				VertexCoords Surf, SnowBall\v2, SnowBall\X# + 0.35, SnowBall\Y# + 0.35, -5
				VertexCoords Surf, SnowBall\v3, SnowBall\X# + 0.35, SnowBall\Y# - 0.35, -5
				; Set this instance as recycled
				SnowBall\Recycled = True
			EndIf
		EndIf
	Next
End Function

; This function checks if there are any recycled snowball instances and returns it
Function RecycledSnowBall.TSnowBall()
	Local SnowBall.TSnowBall

	For SnowBall.TSnowBall = Each TSnowBall
		If SnowBall\Recycled = True Then Return SnowBall
	Next
End Function


This works even faster.
With the chance to create a snowball set to 100, there are a little over 1000 snowballs generated (I've used the Counter variable to keep track of how many snowball instances are created).
Framerate is now 720 fps.
Instead of 500 fps when re-creating the bigmesh every frame.

Another increase of 220 fps. Yay.

This will be the maximum I'll ever get i guess.

When the world is completely empty, my laptop gets a framerate of 1500 fps.
So, not bad to process over 1000 snowball instances and update their vertex-coords every frame.


PowerPC603(Posted 2009) [#11]
As this works perfectly, I'm gonna try editing my blocks too.

Right now, each block is a separate entity.
In the first level, there are 273 blocks visible, so that means 273 entities to render every frame.

I've created a testprogram too to check if I can move the correct block to another position and it works.
Every time a collision occurs between a ball and the combined block mesh (where 273 blocks are added into one mesh), I check the triangle that was hit.

Then I find the correct Block instance to which this triangle belongs, as the Block instance records the first triangle of that block and the last one.
It's just looping through all Block instances and check if the triangle is between the first and last triangles of that block.

This will greatly improve renderspeed I guess.
The blocks that are destroyed, are simply moved outside the screen by moving their vertices (the block instances also record the first and last vertex indexes of the block).


PowerPC603(Posted 2009) [#12]
I've changed my blocks to be one big mesh instead of 273 separate block entities.

When I use Flip False, I'm getting over 500 fps now, throughout the entire game.
This is alot faster than it was before.
With heavy snow, my fps was going under 60fps.

http://users.telenet.be/vge/Arkanoid3D/Arkanoid3D.zip