Performance fix part 2

First of all - sorry for my English :slight_smile:

We all know a large number of elements on screen kills Corona performance and when You do it somewhere cute bunny dies. Lets do some benchmark ( http://www.concat.pl/temp/tile_test.zip ) and kill some of this little bastards :

[lua]display.setStatusBar( display.HiddenStatusBar )

–system.activate( “multitouch” )

local fps = require(“fps”)
local performance = fps.PerformanceOutput.new()

– SET UP DISPLAY GROUP “TREE”
– “p2” is the parallax layer in the distant back.
local p2_moveGroup = display.newGroup()

– “p1” is the parallax layer next closest, visually.
local p1_moveGroup = display.newGroup()

– “p0” is the ‘core’ movement group. This will scroll at the exact mouse/touch movement speed.
– note that this group has 2 ‘child’ display groups. You can have multiple child groups scrolling at the same speed.
– this technique (multiple child groups) can be applied to any parallax layer, if you need more z-index layering.
– remember the rule of thumb: layers declared LATER appear in FRONT of other layers visually.
local p0_moveGroup = display.newGroup()
local p0_subGroup1 = display.newGroup() ; p0_moveGroup:insert(p0_subGroup1)
–local p0_subGroup2 = display.newGroup() ; p0_moveGroup:insert(p0_subGroup2)

– set local “pointers” to movement groups (used in moveCamera function below)
local p2 = p2_moveGroup
local p1 = p1_moveGroup
local p0 = p0_moveGroup

– DECLARE WORLD LIMITS
– in general, XMax and YMax will be your ‘world size’.
– for example, a world of 960 x 1200 will use these numbers as the XMax and YMax values.
– tweaking of these numbers will be necessary to suit your own game, background sizes, etc.
local worldLimits = { XMin=0 , YMin=0 , XMax=10000 , YMax=10000 }

local testTexture = {}
local testTextureId = {}

local n = 0
– Arrange textures
for i = 1, 100 do
for j = 1, 100 do
n = n + 1
testTexture[n] = display.newImageRect( p0_subGroup1, “texture.png”, 100, 100, true )
testTexture[n].x = (j*100) - 50
testTexture[n].y = (i*100) - 50
testTexture[n].isVisible = true
testTexture[n].alpha = 0.5
table.insert(testTextureId, #testTextureId+1, n)

end
end

– basic visible elementh table
–[[local visibleElem = {}
local m = 0
for k = 1, 15 do
for l = 1, 15 do
local increaseX = l + (m*100)
table.insert(visibleElem, #visibleElem+1, increaseX)
testTexture[increaseX].alpha = 0.5
testTexture[increaseX].isVisible = true
end
m = m + 1
end ]]–

– these two lines adjust the world limits based on the device screen size
local adjustedXMax = display.contentWidth-worldLimits.XMax
local adjustedYMax = display.contentHeight-worldLimits.YMax

– 2 testing functions! This gives you a tap position in ‘world’ coordinates, not screen coordinates.
local function clearTestDot(event)
event:removeSelf() ; event = nil
end

local function worldTouchPoint(event)
local adjustedX = event.x - p0.x
local adjustedY = event.y - p0.y

local touchpoint = display.newCircle( p0_moveGroup, adjustedX, adjustedY, 12 )
touchpoint:setFillColor(255,255,255,255)
transition.to( touchpoint, { time=500, alpha=0.0, onComplete=clearTestDot } )
end

– “touchSensor” spans the entire ‘world’ size. Any multitouch/tap on this ‘sensor’ produces a reaction.
– note its alpha setting of “0”… you don’t want to see it in the game!
local touchSensor = display.newRect( p0_subGroup1, 0, 0, worldLimits.XMax, worldLimits.YMax )
touchSensor:setFillColor(255,255,255,0)

performance.group.x, performance.group.y = display.contentWidth/2, 0;

local function moveCamera( event )

– when touch first begins…
if ( event.phase == “began” ) then

– set core moveGroup to ‘focused’
display.getCurrentStage():setFocus( p0, event.id )
– set touch location to relative position (relative to moveGroup)
p0.x0 = event.x - p0.x
p0.y0 = event.y - p0.y

– when touch moves…
elseif ( event.phase == “moved” ) then

– set ‘predicted’ new X and Y positions for core moveGroup
p0.newX = event.x - p0.x0
p0.newY = event.y - p0.y0

– in all following calculations, adjust decimals for parallax scrolling rate
– a smaller decimal moves a layer less, so use smaller multipliers as you move back in visual space
– in this demo, “0.4” is used for the near parallax, “0.2” used for the distant back parallax

– if new X position is within world limits, move it there!
if ( p0.newX <= worldLimits.XMin and p0.newX >= adjustedXMax ) then
p0.x = p0.newX ; p1.x = p0.x*0.4 ; p2.x = p0.x*0.2
– if not, lock to world limits to prevent further scrolling
elseif ( p0.newX > worldLimits.XMin ) then
p0.x = worldLimits.XMin ; p1.x = p0.x*0.4 ; p2.x = p0.x*0.2
elseif ( p0.newX < worldLimits.XMax ) then
p0.x = adjustedXMax ; p1.x = p0.x*0.4 ; p2.x = p0.x*0.2
end

– if new Y position is within world limits, move it there!
if ( p0.newY <= worldLimits.YMin and p0.newY >= adjustedYMax ) then
p0.y = p0.newY ; p1.y = p0.y*0.4 ; p2.y = p0.y*0.2
– if not, lock to world limits to prevent further scrolling
elseif ( p0.newY > worldLimits.YMin ) then
p0.y = worldLimits.YMin ; p1.y = p0.y*0.4 ; p2.y = p0.y*0.2
elseif ( p0.newY < worldLimits.YMax ) then
p0.y = adjustedYMax ; p1.y = p0.y*0.4 ; p2.y = p0.y*0.2
end

– when touch ends…
elseif ( event.phase == “ended” or event.phase == “cancelled” ) then
– un-focus core moveGroup
display.getCurrentStage():setFocus( p0, nil )

end

return true
end

p0:addEventListener( “touch”, moveCamera )
p0:addEventListener( “tap”, worldTouchPoint )[/lua]

Desire Z benchmark result is 3-4fps (min: 1fps) It sucks… But if we change this line:
[lua]testTexture[n].isVisible = true[/lua]
to:
[lua]testTexture[n].isVisible = false[/lua]

Desire Z benchmark result is 18-20fps (min: 16fps) So we have clear way to large preformance increase - hide out of screen elements / paging algorithm like this:

http://www.youtube.com/watch?v=MN9Ri5I6Pc8

_This video demonstrates the Isogenic Game Engine’s advanced paging system allowing pretty smooth paging across a HUGE 2000 x 2000 tile map - 4 million tiles are populated on the map with random “grass” tiles and I’ve also added a few “pavement” tiles as well. You can also see that four independent viewports are running in the demo.

Each tile is a diametric 20 x 10 pixel image and each paging tile is 1000 x 500 pixels (50 x 50 tiles) and is loaded dynamically. This demo is running on a local Apache web server and paging will not be this fast when loading data from the web, however as a performance test against the paging algorithm, I believe this represents excellent results._

Regards

[import]uid: 12704 topic_id: 9438 reply_id: 309438[/import]

Yeah um, I downloaded your demo, and I read your post, and I am failing to see:

  1. The relevance of the Isogenic Game Engine. It has nothing to do with corona
  2. Your demo manages 30 fps reasonably consistantly but each tile is 100x100 pixels - ie it’s showing a maximum of 20 of these tiles onscreen at once. What is so special?

I know we all want to do our scrolly map as quickly as possible, but I just don’t understand how this post helps with that at all. What am I missing?

Barry [import]uid: 46639 topic_id: 9438 reply_id: 34590[/import]

Hah silly me, why did I bother looking at framerates in the simulator? :slight_smile:
Ok I changed your config file to 60fps, and then built it for my ipod touch.

errr… when moving I’m getting anywhere from 20 to 5 fps?

OK this whole performance thing is really really confusing me.

What I’ll do is create what I believe is a reasonable, standardised framework, and I’ll provide a test map in Tiled format, and we can each see about getting the best framerates on it (is it OK if I use your frame-rate display code?).

I think despite us all wanting the same goal, we are all getting bogged down in the methods, without any actual real useful end result.

Barry [import]uid: 46639 topic_id: 9438 reply_id: 34593[/import]

First of all - sorry but I’m little drunk today :slight_smile:

You miss the point. Look at this code:

[lua]display.setStatusBar( display.HiddenStatusBar )

–system.activate( “multitouch” )

local fps = require(“fps”)
local performance = fps.PerformanceOutput.new()
local Abs = math.abs
local Floor = math.floor

– SET UP DISPLAY GROUP “TREE”
– “p2” is the parallax layer in the distant back.
local p2_moveGroup = display.newGroup()

– “p1” is the parallax layer next closest, visually.
local p1_moveGroup = display.newGroup()

– “p0” is the ‘core’ movement group. This will scroll at the exact mouse/touch movement speed.
– note that this group has 2 ‘child’ display groups. You can have multiple child groups scrolling at the same speed.
– this technique (multiple child groups) can be applied to any parallax layer, if you need more z-index layering.
– remember the rule of thumb: layers declared LATER appear in FRONT of other layers visually.
local p0_moveGroup = display.newGroup()
local p0_subGroup1 = display.newGroup() ; p0_moveGroup:insert(p0_subGroup1)
–local p0_subGroup2 = display.newGroup() ; p0_moveGroup:insert(p0_subGroup2)

– set local “pointers” to movement groups (used in moveCamera function below)
local p2 = p2_moveGroup
local p1 = p1_moveGroup
local p0 = p0_moveGroup

– DECLARE WORLD LIMITS
– in general, XMax and YMax will be your ‘world size’.
– for example, a world of 960 x 1200 will use these numbers as the XMax and YMax values.
– tweaking of these numbers will be necessary to suit your own game, background sizes, etc.
local worldLimits = { XMin=0 , YMin=0 , XMax=10000 , YMax=10000 }


– PRIVATE: TABLE UTIL

local function val_To_Str ( v )
if “string” == type( v ) then
v = string.gsub( v, “\n”, “\n” )
if string.match( string.gsub(v,"[^’"]",""), ‘^"+$’ ) then
return “’” … v … “’”
end
return ‘"’ … string.gsub(v,’"’, ‘\"’ ) … ‘"’
else
return “table” == type( v ) and table.tostring( v ) or
tostring( v )
end
end

local function key_To_Str ( k )
if “string” == type( k ) and string.match( k, “^[_%a][_%a%d]*$” ) then
return k
else
return “[” … val_To_Str( k ) … “]”
end
end

local function table_To_Str( tbl )
local result, done = {}, {}
for k, v in ipairs( tbl ) do
table.insert( result, val_To_Str( v ) )
done[k] = true
end
for k, v in pairs( tbl ) do
if not done[k] then
table.insert( result,
key_To_Str( k ) … “=” … val_To_Str( v ) )
end
end
return table.concat(result)
–table.concat( result, “,” )
end

local testTexture = {}
local testTextureId = {}

local n = 0
– Arrange textures
for i = 1, 100 do
for j = 1, 100 do
n = n + 1
testTexture[n] = display.newImageRect( p0_subGroup1, “texture.png”, 100, 100, true )
testTexture[n].x = (j*100) - 50
testTexture[n].y = (i*100) - 50
testTexture[n].isVisible = false
table.insert(testTextureId, n, n)

end
end

– basic visible elementh table
local visibleElem = {}
local m = 0
for k = 1, 15 do
for l = 1, 15 do
local increaseX = l + (m*100)
table.insert(visibleElem, increaseX)
testTexture[increaseX].alpha = 0.5
testTexture[increaseX].isVisible = true
end
m = m + 1
end

– these two lines adjust the world limits based on the device screen size
local adjustedXMax = display.contentWidth-worldLimits.XMax
local adjustedYMax = display.contentHeight-worldLimits.YMax

– 2 testing functions! This gives you a tap position in ‘world’ coordinates, not screen coordinates.
local function clearTestDot(event)
event:removeSelf() ; event = nil
end

local function worldTouchPoint(event)
local adjustedX = event.x - p0.x
local adjustedY = event.y - p0.y

local touchpoint = display.newCircle( p0_moveGroup, adjustedX, adjustedY, 12 )
touchpoint:setFillColor(255,255,255,255)
transition.to( touchpoint, { time=500, alpha=0.0, onComplete=clearTestDot } )
end

– “touchSensor” spans the entire ‘world’ size. Any multitouch/tap on this ‘sensor’ produces a reaction.
– note its alpha setting of “0”… you don’t want to see it in the game!
local touchSensor = display.newRect( p0_subGroup1, 0, 0, worldLimits.XMax, worldLimits.YMax )
touchSensor:setFillColor(255,255,255,0)

performance.group.x, performance.group.y = display.contentWidth/2, 0;


– UGLY AND INEFFICENT function!!!

local mathX
local mathY
local positionXY
local tempYArray = {}
local tempVisibleElem = {}
local tempY
local tempX

local function clickPosition (x, y)

local x = x
local y = y
local yToArray

mathX = Abs(Floor(x*0.01)) + 1
mathY = Abs(Floor(y*0.01)) + 1

–[[if (mathY == 1) then
positionXY = mathX
else
positionXY = (mathY*100) + mathX
end ]]–

for i,v in ipairs(visibleElem) do
testTexture[v].isVisible = false
table.remove(visibleElem,i)
end

tempYArray = {}
for k = -6, 7 do
tempY = mathY + k
if (tempY > 0) then
table.insert(tempYArray, #tempYArray + 1 ,tempY)
end
end

tempVisibleElem = {}

for l = -6, 7 do
tempX = mathX + (l*100)
if (tempX > 0) then
for i,v in ipairs(tempYArray) do
positionXY = (mathY*100) + mathX
testTexture[positionXY].alpha = 0.5
testTexture[positionXY].isVisible = true
end
end
end

–print (#visibleElem)
–return positionXY
end

local function moveCamera( event )

– when touch first begins…
if ( event.phase == “began” ) then

– set core moveGroup to ‘focused’
display.getCurrentStage():setFocus( p0, event.id )
– set touch location to relative position (relative to moveGroup)
p0.x0 = event.x - p0.x
p0.y0 = event.y - p0.y

– when touch moves…
elseif ( event.phase == “moved” ) then

– set ‘predicted’ new X and Y positions for core moveGroup
p0.newX = event.x - p0.x0
p0.newY = event.y - p0.y0

clickPosition (p0.x0, p0.y0)

– in all following calculations, adjust decimals for parallax scrolling rate
– a smaller decimal moves a layer less, so use smaller multipliers as you move back in visual space
– in this demo, “0.4” is used for the near parallax, “0.2” used for the distant back parallax

– if new X position is within world limits, move it there!
if ( p0.newX <= worldLimits.XMin and p0.newX >= adjustedXMax ) then
p0.x = p0.newX ; p1.x = p0.x*0.4 ; p2.x = p0.x*0.2
– if not, lock to world limits to prevent further scrolling
elseif ( p0.newX > worldLimits.XMin ) then
p0.x = worldLimits.XMin ; p1.x = p0.x*0.4 ; p2.x = p0.x*0.2
elseif ( p0.newX < worldLimits.XMax ) then
p0.x = adjustedXMax ; p1.x = p0.x*0.4 ; p2.x = p0.x*0.2
end

– if new Y position is within world limits, move it there!
if ( p0.newY <= worldLimits.YMin and p0.newY >= adjustedYMax ) then
p0.y = p0.newY ; p1.y = p0.y*0.4 ; p2.y = p0.y*0.2
– if not, lock to world limits to prevent further scrolling
elseif ( p0.newY > worldLimits.YMin ) then
p0.y = worldLimits.YMin ; p1.y = p0.y*0.4 ; p2.y = p0.y*0.2
elseif ( p0.newY < worldLimits.YMax ) then
p0.y = adjustedYMax ; p1.y = p0.y*0.4 ; p2.y = p0.y*0.2
end

– when touch ends…
elseif ( event.phase == “ended” or event.phase == “cancelled” ) then
– un-focus core moveGroup
display.getCurrentStage():setFocus( p0, nil )

end

return true
end

p0:addEventListener( “touch”, moveCamera )
p0:addEventListener( “tap”, worldTouchPoint )[/lua]

This time we display texture in viewport only and other have isVisible = false properties set. Preformace is about 30fps compared to 1fps in first example. So if You will display in viewport texture only You will have very good preformance for Lime.

So solution is to hide (isVisible = false) textures outside of viewport.

Regards [import]uid: 12704 topic_id: 9438 reply_id: 34706[/import]

OK performance is better, but is it *best*?
What happens with your method in a map with tiles of say 48x48? [import]uid: 46639 topic_id: 9438 reply_id: 34719[/import]