Disappointing Terrain Performance?

Blitz3D SDK Forums/Blitz3D SDK Programming/Disappointing Terrain Performance?

I was hoping for equivalent terrain performance by using the SDK with BlitzMax vs Blitz3D but it seems approximately 50% slower in any mode (debug/non-debug, windowed/fullscreen)

Example code:

BlitzMax:


SuperStrict

Import blitz3d.blitz3dsdk

bbBeginBlitz3D()

bbGraphics3D 800, 600, 32, 1

bbSetBuffer bbBackBuffer()

bbAmbientLight 255, 255, 255

Local Camera:Int = bbCreateCamera()
bbCameraClsColor Camera, 128, 128, 255

Local hmap_url:String = AppDir + "\temp_hmap.png"
CreateSampleHeightMap 256, 256, 1000, 32, False, hmap_url

Local scale:Float = 10.0

Local Terrain:Int = bbLoadTerrain(hmap_url)
bbTerrainDetail Terrain, 4000, True
bbScaleEntity Terrain, scale, 100.0, scale

Local ts:Float = bbTerrainSize(Terrain)

Local Ground:Int = bbLoadTexture(hmap_url)
bbScaleTexture Ground, ts, ts
bbEntityTexture Terrain, Ground

bbPositionEntity Camera, ((ts * scale) / 2.0), 0.0, ((ts * scale) / 2.0)

Local mxs:Int, mys:Int

Local renders:Int, old_ms:Int, fps:Int

While Not bbKeyHit(1) 

		mxs :+ (bbMouseXSpeed() / 3.0)
		mys :+ (bbMouseYSpeed() / 3.0)

		bbRotateEntity Camera, mys, -mxs, 0.0

		bbMoveMouse bbGraphicsWidth() / 2, bbGraphicsHeight() / 2
		
		bbMouseXSpeed()
		bbMouseYSpeed()
		
		If bbKeyDown(200) Or bbKeyDown(17) Or bbMouseDown(2) Then bbMoveEntity Camera, 0.0, 0.0, 0.1

		If bbKeyDown(208) Or bbKeyDown(31) Then bbMoveEntity Camera, 0.0, 0.0, -0.1

		If bbKeyDown(32) Then bbMoveEntity Camera, 0.1, 0.0, 0.0

		If bbKeyDown(30) Then bbMoveEntity Camera, -0.1, 0.0, 0.0

		Local x:Float = bbEntityX(Camera) 
		Local y:Float = bbEntityY(Camera) 
		Local z:Float = bbEntityZ(Camera) 

		Local terra_y:Float = bbTerrainY(Terrain, x, y , z) + 2.5 

		bbPositionEntity Camera, x, terra_y, z 

		bbRenderWorld 

		renders :+ 1

		If MilliSecs() -old_ms >= 1000
			old_ms = MilliSecs()
			fps = renders
			renders = 0
		EndIf

		bbText 5, 5, "FPS: " + fps

		bbFlip False

Wend

bbEndBlitz3D()

End

Function createSampleHeightMap:Float[,](width:Int = 128, height:Int = 128, numhills:Int = 1000, maxhsize:Int = 32, island:Int = True, fname:String = "")

	'Generate

	Local Map:Float[width, height]

    SeedRnd(MilliSecs())
  
	If island
		numhills :* 0.2
		maxhsize :* 0.5
	EndIf

    For Local n:Int = 1 To numhills

		Local radius:Int = Rand(0, maxhsize)
		Local centrex:Int = Rand(0, Width - 1)
		Local centrez:Int = Rand(0, Height - 1)
		
		If island
			Local theta:Int = Rand(0, 359)
			Local distx:Int = Rand(0, (Width * 0.5) - radius)
			Local distz:Int = Rand(0, (Height * 0.5) - radius)
	  		centrex = (Width * 0.5) + (Cos(theta) * distx)
	  		centrez = (Height * 0.5) + (Sin(theta) * distz)
		EndIf

    	Local startx:Int = centrex - radius
      	If startx < 0 startx = 0

      	Local endx:Int = centrex + radius
	  	If endx > Width endx = Width

      	Local startz:Int = centrez - radius
      	If startz < 0 startz = 0

      	Local endz:Int = centrez + radius
      	If endz > Height endz = Height

      	For Local x:Int = startx To (endx - 1)
       		For Local z:Int = startz To (endz - 1)
	   			Local y:Float = (radius * radius) - (((x - centrex) * (x - centrex)) + ((z - centrez) * (z - centrez)))
	    		If y > 0.0 Map[x, z] :+ y
	    	Next
	  	Next
    
    Next

	'Normalise
	
	Local minv:Float = 10^38, maxv:Float = 10^-38
  
  	For Local x:Int = 0 To (Width - 1)
    	For Local z:Int = 0 To (Height - 1)
 	 		If Map[x, z] < minv minv = Map[x, z] Else If Map[x, z] > maxv maxv = Map[x, z]
     	Next
   	Next

   	For Local x:Int = 0 To (Width - 1)
     	For Local z:Int = 0 To (Height - 1)
			Map[x, z] = (Map[x, z] - minv) / (maxv - minv)
		Next
  	Next

	'Convert to byte Array
	
	Local arr:Byte[,] = New Byte[width, height]

  	For Local x:Int = 0 To (Width - 1)
    	For Local z:Int = 0 To (Height - 1)

			Local v:Int = Floor(Map[x, z] * 255)

			arr[x, z] = Byte(v)

		Next
	Next
		
	'Render to pixmap and save to file
	
	Local pm:TPixmap = CreatePixmap(width, height, PF_RGB888)

    For Local x:Int = 0 To (Width - 1)
    	For Local z:Int = 0 To (Height - 1)
    		WritePixel(pm, x, z, arr[x, z] Shl 16 | arr[x, z] Shl 8 | arr[x, z])
    	Next
    Next

	If fname Then SavePixmapPNG(pm, fname, 9)	
	
	Return Map
	
End Function




Blitz3D:


Graphics3D 800, 600, 32, 1

SetBuffer BackBuffer()

AmbientLight 255, 255, 255

Local camera = CreateCamera() 
CameraClsColor Camera, 128, 128, 255

Local hmap_url$ = "temp_hmap.png"

Local scale# = 10.0

Local Terrain = LoadTerrain(hmap_url$)
TerrainDetail Terrain, 4000, True
ScaleEntity Terrain, scale, 100.0, scale

Local ts# = TerrainSize(Terrain)

Local Ground = LoadTexture(hmap_url$)
ScaleTexture Ground, ts, ts
EntityTexture Terrain, Ground

PositionEntity Camera, ((ts * scale) / 2.0), 0.0, ((ts * scale) / 2.0)

Local mxs, mys

Local renders, old_ms, fps

While Not KeyHit(1) 

		mxs = mxs + (MouseXSpeed() / 3.0)
		mys = mys + (MouseYSpeed() / 3.0)

		RotateEntity camera, mys, -mxs, 0.0

		MoveMouse GraphicsWidth() / 2, GraphicsHeight() / 2
		
		MouseXSpeed()
		MouseYSpeed()
		
		If KeyDown(200) Or KeyDown(17) Or MouseDown(2) Then MoveEntity Camera, 0.0, 0.0, 0.1

		If KeyDown(208) Or KeyDown(31) Then MoveEntity Camera, 0.0, 0.0, -0.1

		If KeyDown(32) Then MoveEntity Camera, 0.1, 0.0, 0.0

		If KeyDown(30) Then MoveEntity Camera, -0.1, 0.0, 0.0

		Local x# = EntityX(Camera) 
		Local y# = EntityY(Camera) 
		Local z# = EntityZ(Camera) 

		Local terra_y# = TerrainY(Terrain, x, y, z) + 2.5 

		PositionEntity Camera, x, terra_y, z 
	
		RenderWorld 

		renders = renders + 1

		If MilliSecs() -old_ms >= 1000
			old_ms = MilliSecs()
			fps = renders
			renders = 0
		EndIf

		Text 5, 5, "FPS: " + fps

		Flip False

Wend


End




I’m achieving 190fps in BlitzMax vs 360fps in Blitz3D. Can anyone else test on their system?

My system: Athlon 3200+, 256MB ATI X800GT.

I should probably use a chunked mesh terrain anyway. The following BlitzMax code runs at over 1000fps:


SuperStrict

Import blitz3d.blitz3dsdk

bbBeginBlitz3D()

bbGraphics3D 800, 600, 32, 1

bbSetBuffer bbBackBuffer()

Local hmap_url:String = "temp_hmap.png"

bbAmbientLight 255, 255, 255

Local Camera:Int = bbCreateCamera()
bbCameraRange Camera, 0.1, 1000.0
bbCameraClsColor Camera, 64, 164, 196

Local Terrain:TChunkedTerrain = New TChunkedTerrain

Local temp:Float[,] = createSampleHeightMap(256, 256, 1000, 32, False, hmap_url)
Terrain.CreateMeshes(temp, 64)

temp = Null
GCCollect

'Terrain.LoadFromFile(hmap_url, 64)

'Terrain.SetUV 32	'<-- BUGGY

Terrain.TextureFromPixmap(LoadPixmap(hmap_url), 1, 0, 1)
Terrain.Scale 10.0, 100.0, 10.0
Terrain.Position 0.0, 0.0, 0.0

'bbClearTextureFilters
'Terrain.TextureChunk bbLoadTexture("grass.png", 1 + 8), Terrain.GetChunk(0.0, 0.0), 0, 1

bbPositionEntity Camera, Terrain.CentreX(), Terrain.Y, Terrain.CentreZ()

Local MoveSpeed:Float = 0.1

Local old_ms:Int, renders:Int, fps:Int

Local mxs:Float, mys:Float

'bbWireFrame True

While Not bbKeyHit(1) 

		mxs :+ (bbMouseXSpeed() / 3.0)
		mys :+ (bbMouseYSpeed() / 3.0)

		bbRotateEntity Camera, mys, -mxs, 0.0

		bbMoveMouse bbGraphicsWidth() / 2, bbGraphicsHeight() / 2
		
		bbMouseXSpeed()
		bbMouseYSpeed()
		
		If bbKeyDown(200) Or bbKeyDown(17) Or bbMouseDown(2) Then bbMoveEntity Camera, 0.0, 0.0, MoveSpeed

		If bbKeyDown(208) Or bbKeyDown(31) Then bbMoveEntity Camera, 0.0, 0.0, -MoveSpeed

		If bbKeyDown(32) Then bbMoveEntity Camera, MoveSpeed, 0.0, 0.0

		If bbKeyDown(30) Then bbMoveEntity Camera, -MoveSpeed, 0.0, 0.0

		Local camx:Float = bbEntityX(Camera)
		Local camz:Float = bbEntityZ(Camera)
		Local camy:Float = Terrain.MeshY(camx, camz) + 5.0

		bbPositionEntity Camera, camx, camy, camz 

		Terrain.DoVisibleChunks camx, camz

		bbRenderWorld 

		renders :+ 1

		If MilliSecs() -old_ms >= 1000
			old_ms = MilliSecs()
			fps = renders
			renders = 0
		EndIf

		bbText 5, 5, "FPS: " + fps

		bbFlip False

Wend

Terrain.Destroy

bbEndBlitz3D()

End


'********** CHUNKED TERRAIN CLASS **********


Type TChunkedTerrain
		
		
	Field ChunkSize:Int

	Field Chunks:Int[,]
	Field Cols:Int
	Field Rows:Int
	
	Field X:Float
	Field Y:Float
	Field Z:Float
	
	Field XScale:Float
	Field YScale:Float
	Field ZScale:Float

	Field XSize:Float
	Field ZSize:Float


	Method New()
	
		X = 0.0
		Y = 0.0
		Z = 0.0
				
		XScale = 1.0
		YScale = 1.0
		ZScale = 1.0
	
	EndMethod
	
	
	Method Destroy()
	
		For Local col:Int = 0 To Cols - 1
			For Local row:Int = 0 To Rows - 1
				If Chunks[col, row] Then bbFreeEntity Chunks[col, row]
				Chunks[col, row] = Null
			Next
		Next	
	
		Chunks = Null
		
		GCCollect		
	
	EndMethod
	
	
	'Chunk Size should be less than the heightmap size and a power of two (eg, 16, 32, 64, etc)
	Method LoadFromFile:Int(hmap_url:String, chunk_size:Int = 64)
		
		Local pix:TPixmap = LoadPixmap(hmap_url)
		If Not pix Return False

		Local map:Float[,] = New Float[pix.width, pix.height]

		For Local px:Int = 0 To pix.width - 1
			For Local pz:Int = 0 To pix.height - 1
				Local r:Byte = ReadPixel(pix, px, pz)
				map[px, pz] = Float(r) / 255.0
			Next
		Next
		
		CreateMeshes map, chunk_size
		
		Scale XScale, YScale, ZScale
		
		map = Null
		GCCollect
		
		Return True

	EndMethod


	Method CreateMeshes(arr:Float[,] Var, chunk_size:Int = 64)
		
		If Not arr Then Return
		
		Local dims:Int[] = arr.Dimensions()

		Local map_size:Int

		If dims[0] < dims[1] Then map_size = dims[0] Else map_size = dims[1]
		
		Local map:Float[,] = New Float[map_size, map_size]
						
		For Local px:Int = 0 To map_size - 1
			For Local pz:Int = 0 To map_size - 1
				map[px, (map_size - pz - 1)] = arr[px, pz]
			Next
		Next
		
		ChunkSize = chunk_size		
				
		Cols = (map_size / ChunkSize)
		Rows = (map_size / ChunkSize)
		
		Chunks = New Int[Cols, Rows]
				
		For Local col:Int = 0 To Cols - 1
			For Local row:Int = 0 To Rows - 1
		
				Local px:Int = (col * ChunkSize) - col
				Local pz:Int = (row * ChunkSize) - row
				Chunks[col, row] = CreateChunk(map, px, pz, ChunkSize + 1, ChunkSize + 1)
				
				bbEntityPickMode Chunks[col, row], 2

			Next
		Next
		
		Position X, Y, Z
		
		map = Null
		GCCollect
		
		Return

	EndMethod
	
	
	Method CreateChunk:Int(map:Float[,] Var, px:Int, pz:Int, w:Int, h:Int, parent:Int = 0)
	Rem
		* Ported And slightly modified from Blitz3D code:
  		  <a href="http://www.blitzbasic.com/codearcs/codearcs.php?code=1609" target="_blank">http://www.blitzbasic.com/codearcs/codearcs.php?code=1609</a>
	EndRem

		Local mesh:Int = bbCreateMesh(parent)
		Local surf:Int = bbCreateSurface(mesh)

		For Local lz:Int = 0 To h
			For Local lx:Int = 0 To w
				bbAddVertex surf, lx, 0, lz, (1.0 / lx), (1.0 / lz)
			Next
		Next
	
		For Local lz:Int = 1 To (h - 2)
			For Local lx:Int = 1 To (w - 2)
				bbAddTriangle surf, lx + ((w + 1) * lz), lx + ((w + 1) * lz) + (w + 1), (lx + 1) + ((w + 1) * lz)
				bbAddTriangle surf, (lx + 1) + ((w + 1) * lz), lx + ((w + 1) * lz) + (w + 1), (lx + 1)+((w + 1) * lz) + (w + 1)
			Next
		Next

		For Local lx:Int = 1 To (w - 1)
			For Local lz:Int = 1 To (h - 1)
				Local index:Int = lx + ((w + 1) * lz)
				bbVertexCoords surf, index, bbVertexX(surf, index), map[px + lx, pz + lz], bbVertexZ(surf, index)
				bbVertexTexCoords surf, index, lx, -lz 
			Next
		Next

		bbUpdateNormals mesh
		
		Return Mesh
	
	EndMethod
	
	
	'** BUGGY - seams visible between chunks
	Method SetUV(tile_size:Int = 1)
		
		For Local col:Int = 0 To Cols - 1
			For Local row:Int = 0 To Rows - 1
			
				Local surf:Int = bbGetSurface(Chunks[col, row], 1)
				
				For Local lx:Int = 1 To ChunkSize
					For Local lz:Int = 1 To ChunkSize 
						Local index:Int = lx + ((ChunkSize + 2) * lz)
						bbVertexTexCoords surf, index, (lx * tile_size), -(lz * tile_Size)
					Next
				Next
				
			Next
		Next
				
	EndMethod


	Method TextureFromFile:Int(tex_url:String = "", flags:Int = 1, frame:Int = 0, index:Int = 0)
	
		Local tex:TPixmap = LoadPixmap(tex_url)
		
		Return TextureFromPixmap(tex, flags, frame, index)

	EndMethod


	Method TextureFromPixmap:Int(tex:TPixmap, flags:Int = 1, frame:Int = 0, index:Int = 0)

		If Not tex Then Return False
		
		If PixmapFormat(tex) <> PF_RGBA8888 Then tex = ConvertPixmap(tex, PF_RGBA8888)
		
		tex = YFlipPixmap(ResizePixmap(tex, ChunkSize * Cols, ChunkSize * Rows))
		
		For Local col:Int = 0 To Cols - 1
			For Local row:Int = 0 To Rows - 1
		
				Local px:Int = (col * ChunkSize) - col
				Local pz:Int = (row * ChunkSize) - row
		
				Local fname:String = "temp_chunk_tex_" + col + "_" + row + ".png"
				Local pm:TPixmap = PixmapWindow(tex, px, pz, ChunkSize + 1, ChunkSize + 1)
				SavePixmapPNG(YFlipPixmap(pm), fname, 9)

				TextureChunk(bbLoadTexture(fname, flags), Chunks[col, row], frame, index)
				
				DeleteFile fname
				
			Next
		Next
		
		Return True
		
	EndMethod
	

	Method TextureChunk(tex:Int, mesh:Int, frame:Int = 0, index:Int = 0)
	
		If Not tex Then Return

		bbScaleTexture tex, ChunkSize + 1, ChunkSize + 1
		
		bbEntityTexture mesh, tex, frame, index
		
	EndMethod
	

	Method Scale(sx:Float, sy:Float, sz:Float)
	
		XScale = sx
		YScale = sy
		ZScale = sz
		
		For Local c:Int = 0 To Cols - 1
			For Local r:Int = 0 To Rows - 1
				bbScaleEntity Chunks[c, r], XScale, YScale, ZScale
			Next
		Next				
	
		XSize = ChunkSize * Cols * XScale
		ZSize = ChunkSize * Rows * ZScale
			
	EndMethod
	
	
	Method Position(wx:Float, wy:Float, wz:Float)
	
		X = wx
		Y = wy
		Z = wz
		
		For Local col:Int = 0 To Cols - 1
			For Local row:Int = 0 To Rows - 1
				Local px:Int = X + (col * ChunkSize * XScale) - (col * XScale)
				Local pz:Int = Z + (row * ChunkSize * ZScale) - (row * ZScale)
				bbPositionEntity Chunks[col, row], px - XScale, Y, pz - ZScale
			Next
		Next
	
	EndMethod
	

	Method MeshY:Float(wx:Float = 0.0, wz:Float = 0.0)

		If (wx < X) Or (wz < Z) Then Return Y
		If wx > X + (ChunkSize * Cols * XScale) Then Return Y
		If wz > Z + (ChunkSize * Rows * ZScale) Then Return Y

		bbLinePick(wx, Y + YScale, wz, 0.0, -(YScale * 2), 0.0)
		
		Return bbPickedY()

	EndMethod


	Method GetChunk:Int(wx:Float = 0.0, wz:Float = 0.0)

		If (wx < X) Or (wz < Z) Then Return Null
		If wx > X + (ChunkSize * Cols * XScale) Then Return Null
		If wz > Z + (ChunkSize * Rows * ZScale) Then Return Null

		Local col:Int = (wx - X) / (ChunkSize * XScale)
		Local row:Int = (wz - Z) / (ChunkSize * ZScale)

		Return Chunks[col, row]

	EndMethod
	
	
	Method CentreX:Float()
		
		Return X + ((Cols * ChunkSize * XScale) / 2)
		
	EndMethod
	
	
	Method CentreZ:Float()
		
		Return Z + ((Rows * ChunkSize * ZScale) / 2)
		
	EndMethod
	
	
	Method DoVisibleChunks(wx:Float, wz:Float, range:Int = 1)
	
		For Local col:Int = 0 To Cols - 1
			For Local row:Int = 0 To Rows - 1
				bbHideEntity Chunks[col, row]
			Next
		Next
		
		Local current_col:Int = (wx - X) / (ChunkSize * XScale)
		Local start_col:Int = current_col - range
		If start_col < 0 Then start_col = 0
		Local end_col:Int = current_col + range
		If end_col >= Cols Then end_col = Cols - 1
		
		Local current_row:Int = (wz - Z) / (ChunkSize * ZScale)
		Local start_row:Int = current_row - range
		If start_row < 0 Then start_row = 0
		Local end_row:Int = current_row + range
		If end_row >= Rows Then end_row = Rows - 1
			
		For Local col:Int = start_col To end_col
			For Local row:Int = start_row To end_row
				bbShowEntity Chunks[col, row]
			Next
		Next
		
	EndMethod
	
	
EndType


'******************************


Function createSampleHeightMap:Float[,](width:Int = 128, height:Int = 128, numhills:Int = 1000, maxhsize:Int = 32, island:Int = True, fname:String = "")

	'Generate

	Local Map:Float[width, height]

    SeedRnd(MilliSecs())
  
	If island
		numhills :* 0.2
		maxhsize :* 0.5
	EndIf

    For Local n:Int = 1 To numhills

		Local radius:Int = Rand(0, maxhsize)
		Local centrex:Int = Rand(0, Width - 1)
		Local centrez:Int = Rand(0, Height - 1)
		
		If island
			Local theta:Int = Rand(0, 359)
			Local distx:Int = Rand(0, (Width * 0.5) - radius)
			Local distz:Int = Rand(0, (Height * 0.5) - radius)
	  		centrex = (Width * 0.5) + (Cos(theta) * distx)
	  		centrez = (Height * 0.5) + (Sin(theta) * distz)
		EndIf

    	Local startx:Int = centrex - radius
      	If startx < 0 startx = 0

      	Local endx:Int = centrex + radius
	  	If endx > Width endx = Width

      	Local startz:Int = centrez - radius
      	If startz < 0 startz = 0

      	Local endz:Int = centrez + radius
      	If endz > Height endz = Height

      	For Local x:Int = startx To (endx - 1)
       		For Local z:Int = startz To (endz - 1)
	   			Local y:Float = (radius * radius) - (((x - centrex) * (x - centrex)) + ((z - centrez) * (z - centrez)))
	    		If y > 0.0 Map[x, z] :+ y
	    	Next
	  	Next
    
    Next

	'Normalise
	
	Local minv:Float = 10^38, maxv:Float = 10^-38
  
  	For Local x:Int = 0 To (Width - 1)
    	For Local z:Int = 0 To (Height - 1)
 	 		If Map[x, z] < minv minv = Map[x, z] Else If Map[x, z] > maxv maxv = Map[x, z]
     	Next
   	Next

   	For Local x:Int = 0 To (Width - 1)
     	For Local z:Int = 0 To (Height - 1)
			Map[x, z] = (Map[x, z] - minv) / (maxv - minv)
		Next
  	Next

	'Convert to byte Array
	
	Local arr:Byte[,] = New Byte[width, height]

  	For Local x:Int = 0 To (Width - 1)
    	For Local z:Int = 0 To (Height - 1)

			Local v:Int = Floor(Map[x, z] * 255)

			arr[x, z] = Byte(v)

		Next
	Next
		
	'Render to pixmap and save to file
	
	If fname
	
		Local pm:TPixmap = CreatePixmap(width, height, PF_RGB888)

    	For Local x:Int = 0 To (Width - 1)
    		For Local z:Int = 0 To (Height - 1)
    			WritePixel(pm, x, z, arr[x, z] Shl 16 | arr[x, z] Shl 8 | arr[x, z])
	    	Next
    	Next

		SavePixmapPNG(pm, fname, 9)	
	
	EndIf
	
	Return Map
	
End Function




It’s an updated version of code I wrote for MiniB3d earlier in the year. I just haven’t figured out how to eliminate the seams between chunks when tiling textures.