Quick Links: Download Gideros Studio | Gideros Documentation | Gideros community chat | DONATE
How to animate many squares most efficiently — Gideros Forum

How to animate many squares most efficiently

totebototebo Member
edited July 2016 in General questions
[UPDATE]

As it stands, these are the fastest ways to draw many squares:

1. Particles with a square texture
2. many Pixels
3. A single Mesh with many vertices
4. Many shapes
5. Many meshes
6. Many bitmaps (very slow)

Animation is fastest without using a tween library, such as MovieClip or GTween.

[/UPDATE]



Quick question: I want many moving squares on the screen at once. Which would be more efficient?

1. Square bitmaps loaded from a spritesheet
2. Square shapes created at runtime
3. Something else?

I'm betting on 1, but there may be a better option?
My Gideros games: www.totebo.com
«1

Comments

  • tkhnomantkhnoman Member
    edited November 2015
    2, it render faster ( i forgot whether it mesh or shape, or both, but no texture = faster )
  • I would have said 1 :) , check with os.timer. in my tests shapes are slower than bitmaps
  • @totebo there is also
    http://giderosmobile.com/forum/discussion/comment/31638#Comment_31638

    Since hgy29 rewrote part of the engine I am not sure this still applies, please let us know your findings.

    I can tell you that in my game on 2015.09 is much faster to place many bitmaps (with the same texture) than drawing the same number of shapes, but I didn't try it with meshes. :)
  • totebototebo Member
    edited November 2015
    [Edit: Code now correctly measures Mesh and Shape and added startup timer from @pie]

    Thanks @pie. Ok, the results are in.

    This is the test scenario, based on my requirements:

    - 1000 constantly animated objects of the same size
    - Each object has a random transparency and colour

    I tested this with Gideros v2015.09 player on an iPod Touch 5th Generation (one of my slowest test devices).

    Average fps across four devices:

    Shape: 34.75
    Mesh: 34.5
    Bitmap: 28.75

    Shape wins overall, with Mesh good second and Bitmap slowest.
    local sprite = Sprite.new()
    sprite:setY(100)
    stage:addChild( sprite )
     
     
    texturePack = TexturePack.new("spritesheet.txt", "spritesheet.png", true )
    local size = Bitmap.new(texturePack:getTextureRegion("square.png")):getWidth()
    local mcs = {}
    local startTime = os.timer() --in the beginning of the file
     
    local test = "bitmap"
     
    if test == "bitmap" then
     
     	-- iPad Mini Retina: 57 fps
    	-- iPod touch 5th generation: 25fps
    	-- iPad 2: 17 fps	
    	-- Nexus 6: 16 fps
     
     
     
    	for i=1, 1000 do
     
    		local bitmap = Bitmap.new(texturePack:getTextureRegion("square.png"))
    		local r = math.random(20)/20
    		local g = math.random(20)/20
    		local b = math.random(20)/20
    		bitmap:setColorTransform( r, g, b, math.random(10)/10)
     
    		-- Move mesh
    		local target_x = math.random(100)
    		local target_y = math.random(100)
    		local mc = MovieClip.new{
    			{1, 100, bitmap, {x = {0, target_x, "inOutSine"}, y = {0, target_y, "inOutSine"}}},	
    			{101, 200, bitmap, {x = {target_x, 0, "inOutSine"}, y = {target_y, 0, "inOutSine"}}},	
    		}
    		mc:setGotoAction(200,1)
    		table.insert ( mcs, mc ) -- To avoid being garbage collected		
     
    		sprite:addChild( bitmap )
     
    	end
     
    	 --and this in every loop end
    	local endPlacingTime =  os.timer()- startTime
    	print("objects placed in",  endPlacingTime)
     
    elseif test == "mesh" then
     
     	-- iPad Mini Retina: 58 fps
    	-- iPad 2: 33 fps	
    	-- iPod touch 5th generation: 28 fps
    	-- Nexus 6: 19 fps
     
     
     
    	for i=1, 1000 do
     
    		-- Create mesh
    		local mesh = Mesh.new()
    		mesh:setVertexArray(0, 0,   size, 0,   size, size,   0, size)
    		mesh:setIndexArray(1, 2, 3,     1, 3, 4)
    		local r = math.random(20)/20
    		local g = math.random(20)/20
    		local b = math.random(20)/20
    		mesh:setColorTransform( r, g, b, math.random(10)/10)
     
    		-- Move mesh
    		local target_x = math.random(100)
    		local target_y = math.random(100)
    		local mc = MovieClip.new{
    			{1, 100, mesh, {x = {0, target_x, "inOutSine"}, y = {0, target_y, "inOutSine"}}},	
    			{101, 200, mesh, {x = {target_x, 0, "inOutSine"}, y = {target_y, 0, "inOutSine"}}},	
    		}
    		mc:setGotoAction(200,1)
    		table.insert ( mcs, mc ) -- To avoid being garbage collected	
     
     
    		sprite:addChild( mesh )
     
    	end
     
    	 --and this in every loop end
    	local endPlacingTime =  os.timer()- startTime
    	print("objects placed in",  endPlacingTime)
     
    elseif test == "shape" then
     
     
    	-- iPad Mini Retina: 58 fps
     	-- iPad 2: 34 fps	
    	-- iPod touch 5th generation: 29 fps
    	-- Nexus 6: 18 fps
     
    	for i=1, 1000 do
     
    		-- Create shape
    		local shape = Shape.new()
    		shape:setFillStyle(Shape.SOLID, 0xffffff, 1)
    		shape:beginPath()
    		shape:moveTo(0,0)
    		shape:lineTo(size, 0)
    		shape:lineTo(size, size)
    		shape:lineTo(0, size)
    		shape:lineTo(0, 0)
    		shape:endPath()
    		local r = math.random(20)/20
    		local g = math.random(20)/20
    		local b = math.random(20)/20
    		shape:setColorTransform( r, g, b, math.random(10)/10)
     
    		-- Move shape
    		local target_x = math.random(100)
    		local target_y = math.random(100)
    		local mc = MovieClip.new{
    			{1, 100, shape, {x = {0, target_x, "inOutSine"}, y = {0, target_y, "inOutSine"}}},	
    			{101, 200, shape, {x = {target_x, 0, "inOutSine"}, y = {target_y, 0, "inOutSine"}}},	
    		}
    		mc:setGotoAction(200,1)
    		table.insert ( mcs, mc ) -- To avoid being garbage collected
     
    		sprite:addChild( shape )
     
    	end
     
    	--and this in every loop end
    	local endPlacingTime =  os.timer()- startTime
    	print("objects placed in",  endPlacingTime)
     
    end
     
    -- Mem and fps
    local sceneDebug = SceneDebug.new()
    sceneDebug:addEventListener( Event.ENTER_FRAME, sceneDebug.update, sceneDebug )
    stage:addChild( sceneDebug )
    zip
    zip
    Squares.zip
    6K
    My Gideros games: www.totebo.com
  • totebototebo Member
    edited November 2015
    I noticed I mixed Mesh and Shape up in my code. The results are thus (after settling for 15 seconds or so):

    Bitmap: 26fps
    Mesh: 29fps
    Shape: 29fps
    My Gideros games: www.totebo.com
  • piepie Member
    edited November 2015
    @totebo, I can confirm your findings (on galaxy s2 fps are doubled though) :
    thknoman was right, mesh is the best option. Thank you both for undermining my certainties :D
    In the past I changed shapes with bitmaps in my game because they were providing a better performance, maybe it depends on the setup, but now I have to test it again..

    I added two lines to know how much time they take to be placed, but it's not so different since we're speaking of ms.
    I run different tests and wrote down the lowest and highest values I got.
    local startTime = os.timer() --in the beginning of the file
     
    --and this in every loop end
    local endPlacingTime =  os.timer()- startTime
    print("objects placed in",  endPlacingTime)
    [edit]
    Ah you're right! I trusted you... :) so mesh is still the best performer

    bitmap 36-48 fps and 480-490kb
    objects placed in 0.16-0.20

    mesh 39 - 53 fps and 570kb
    objects placed in 0.14-0.22

    shape 34-53 fps and 580 kb.
    objects placed in 0.24-0.28




  • Sorry for the confusion! :)

    In my test it's pretty even, but Shape has the slight edge. Can you verify Mesh is faster in your case?
    My Gideros games: www.totebo.com
  • i was surprised to see shapes performing better than meshes. now it makes sense.
  • totebototebo Member
    edited November 2015
    Edited the post above. The tests now show average fps across four devices:

    Shape: 34.75
    Mesh: 34.5
    Bitmap: 28.75

    Nexus 6 (Android) is performing very poorly. Any ideas why it differs so much from its iOS cousins?
    My Gideros games: www.totebo.com
  • There will be a new shape type soon - vector shapes.

    @hgy29 Will they be faster than the current shape type?

    Likes: totebo, antix

    Coder, video game industry veteran (since the '80s, ❤'s assembler), arrested - never convicted hacker (in the '90s), dad of five, he/him (if that even matters!).
    https://deluxepixel.com
    +1 -1 (+2 / -0 )Share on Facebook
  • hgy29hgy29 Maintainer
    I doubt the vector Shape implementation I'm working on will be faster for simple squares. Internally everything is converted into a mesh by Gideros, that is to say a set of triangles. Whatever the technique you use, a rectangle is just two triangles.
    Vector shapes will be more efficient when drawing complex polygons or curves.
    However a particle shader, as the one used for Box2D particle system, may be much more efficient than shapes or meshes.
  • @hgy29 particle shader sounds interesting! Is that on the roadmap, or something that is usable now?
    My Gideros games: www.totebo.com
  • Coder, video game industry veteran (since the '80s, ❤'s assembler), arrested - never convicted hacker (in the '90s), dad of five, he/him (if that even matters!).
    https://deluxepixel.com
  • Thanks @SinisterSoft. Have you (or anyone else) played with this? I need to be able to pick up collisions for each individual square, which probably makes Liquidfun a touch overkill?
    My Gideros games: www.totebo.com
  • There is a liquid fun demo in the examples folder.

    But maybe something that just does squares as fast as possible, with the ability to have different scales and colours would be a good thing to suggest on the github page?
    Coder, video game industry veteran (since the '80s, ❤'s assembler), arrested - never convicted hacker (in the '90s), dad of five, he/him (if that even matters!).
    https://deluxepixel.com
  • john26john26 Maintainer
    The problem is you are creating 1000 meshes so of course it is not faster than shapes! What you need to do is create a single mesh with 1000 vertices. That's the whole point of a mesh. There may be some technical reasons not to do this but I suspect they can be overcome. If you have a single mesh, MovieClip may not work but the solution is just to use an ENTER_FRAME to update the mesh coordinates each frame.

    Sorry I didn't look at this earlier!
  • Wow, that sound great! Meshes are a bit of a mystery to me. How would you create a 1000 vertice mesh?
    My Gideros games: www.totebo.com
  • keszeghkeszegh Member
    edited November 2015
    it's best if you extend the mesh class with some kind of 'add4gon' function. in my drawing app i do antialiasing also, and thus i draw 3 quadrangles (one as required and 2 on the two sides fading out to emulate antialias), that is, 6 triangles. so for me adding a 'segment' to my mesh looks like see below.

    also i use a nice trick i can do to have several 'brushes', which can be useful for particles too. in my texture associated with the mesh there are 64x64 textures next to each other horizontally. so if the 'brush' value is 2 in my function, then it will draw the segment using the 2nd block of these textures. that's the way you can practically use several textures in one mesh, they just have to be put next to each other in one big texture.
    function Frame:renderSegmentAt(i,a1x,a1y, a2x,a2y, b1x,b1y, b2x,b2y, a3x,a3y, b3x,b3y, a4x,a4y, b4x,b4y,color_curr,brush)
     
      self:setTextureCoordinates(i*8+1,  (brush-1)*64+4, 0,i*8+2,  (brush-1)*64+59,0,i*8+3,  (brush-1)*64+4,63, i*8+4, (brush-1)*64+59,63,i*8+5,  (brush-1)*64,0, i*8+6,  (brush-1)*64,63,i*8+7,  (brush-1)*64+63,0, i*8+8,  (brush-1)*64+63,63)
     
      self:setVertices(i*8+1, a1x, a1y, i*8+2, a2x, a2y,i*8+3, b1x, b1y, i*8+4, b2x, b2y,i*8+5, a3x, a3y, i*8+6, b3x, b3y,i*8+7, a4x, a4y, i*8+8, b4x, b4y)
     
      self:setColors(i*8+1, color_curr, 1, i*8+2, color_curr, 1,i*8+3, color_curr, 1, i*8+4, color_curr, 1,i*8+5, color_curr, 0, i*8+6, color_curr, 0,i*8+7, color_curr, 0, i*8+8, color_curr, 0)
     
      self:setIndices(i*18+1, i*8+1, i*18+2, i*8+3, i*18+3, i*8+4,i*18+4, i*8+1, i*18+5, i*8+4, i*18+6, i*8+2,i*18+7, i*8+5, i*18+8, i*8+6, i*18+9, i*8+3,i*18+10, i*8+5, i*18+11, i*8+3, i*18+12, i*8+1,i*18+13, i*8+2, i*18+14, i*8+4, i*18+15, i*8+8,i*18+16, i*8+2, i*18+17, i*8+8, i*18+18, i*8+7)
     
    end
    draws something like in the attachment (click on it to see vertex names).
    stroke smoothing explanation.png
    473 x 91 - 6K
  • john26john26 Maintainer
    Accepted Answer
    Here's how to do it (using your geometry, @totebo)
    function rgb(r,g,b)
       return math.floor(r)*65536+math.floor(g)*256+math.floor(b)
    end
     
    local mesh=Mesh.new()
    local size=50
     
    for i=1,1000 do
      local vstart=(i-1)*4
      local istart=(i-1)*6
     
      local x = math.random(100)
      local y = math.random(100)
     
      mesh:setVertex(vstart+1,x+0,y+0)
      mesh:setVertex(vstart+2,x+size,y+0)
      mesh:setVertex(vstart+3,x+size,y+size)
      mesh:setVertex(vstart+4,x+0,y+size)
     
      local r = math.random(255)
      local g = math.random(255)
      local b = math.random(255)
      local a = math.random()
     
      local hexcol=rgb(r,g,b)
     
      mesh:setColor(vstart+1,hexcol,a)
      mesh:setColor(vstart+2,hexcol,a)
      mesh:setColor(vstart+3,hexcol,a)
      mesh:setColor(vstart+4,hexcol,a)
     
      mesh:setIndex(istart+1,vstart+1)
      mesh:setIndex(istart+2,vstart+2)
      mesh:setIndex(istart+3,vstart+3)
      mesh:setIndex(istart+4,vstart+1)
      mesh:setIndex(istart+5,vstart+3)
      mesh:setIndex(istart+6,vstart+4)
    end
     
    stage:addChild(mesh)
    mesh:setY(100)
     
    -- Mem and fps
    local sceneDebug = SceneDebug.new()
    sceneDebug:addEventListener( Event.ENTER_FRAME, sceneDebug.update, sceneDebug )
    stage:addChild( sceneDebug )
    So I have a single mesh with 4000 vertices and 6000 indices. I have not animated it yet, to do that you would have to manually go through and change the vertices using mesh:setVertex(i,x,y) to move vertex i to (x,y). Probably MovieClip cannot be used.

    Haven't checked it on Android yet but can immediately see the difference on my PC.
    zip
    zip
    mesh.zip
    2K

    Likes: totebo

    +1 -1 (+1 / -0 )Share on Facebook
  • Another speedup that people might not know about, put:
    local random,floor=math.random,math.floor
    at the beginning of your code.

    Then change the math.random and math.floor to just random and floor. It will be faster.
    Coder, video game industry veteran (since the '80s, ❤'s assembler), arrested - never convicted hacker (in the '90s), dad of five, he/him (if that even matters!).
    https://deluxepixel.com
  • Thanks @john26! That seems quite a bit faster (haven't tested on device yet). Now I have to wrap my head around mesh:setVertex(i,x,y). I move stuff with MovieClip now, which won't work out of the box I guess.
    My Gideros games: www.totebo.com
  • It may be possible if setX and setY were available for a mesh vertex? Is it? If not then you could ask for it to be added in github?
    Coder, video game industry veteran (since the '80s, ❤'s assembler), arrested - never convicted hacker (in the '90s), dad of five, he/him (if that even matters!).
    https://deluxepixel.com
  • totebototebo Member
    edited November 2015
    I've created a Square class that emulates the behaviour of a sprite. Then I use GTween (I can't use MovieClip because it requires a Sprite) to animate 1,000 squares. The result?

    On a Nexus 6 which benchmarked 18fps in the fastest previous test (using Shapes):

    Without animation: 60fps
    With animation: ~16fps

    Gah!

    Will be looking through the code to see if I've messed something up, or if it's simply GTween that sucks a lot of CPU.
    My Gideros games: www.totebo.com
  • totebototebo Member
    edited November 2015
    Ok, this is interesting. Replacing GTween with a simple setPosition() on each frame boosts the fps to around 35 on the same device (ie. more than doubles fps). GTween must slow things down a lot, maybe out of necessity.

    MovieClip has proven faster than GTween in my previous projects. Is there a way to modify MovieClip to use getters and setters on any object, rather than just Sprites (like GTween)?

    The Gideros project is attached. Improvements very much welcome!
    zip
    zip
    Squares2.zip
    9K
    My Gideros games: www.totebo.com
  • hgy29hgy29 Maintainer
    You said you created a Square class, if you made it inherit from Sprite, then Movieclip would have been able to tween it provided you change the attribute names (something else than x or y for position).

    PS: plural of vertex is vertices...

    Likes: SinisterSoft

    +1 -1 (+1 / -0 )Share on Facebook
  • totebototebo Member
    edited November 2015
    Excellent idea @hgy29. Works a treat!

    So, is it fast? On my Nexus 6, wait for it: 16fps.

    I'm giving up trying to get speedy squares. For now. Anyone got ideas, shoot!
    My Gideros games: www.totebo.com
  • And thanks for the "vertices" heads up. I should have known from my 3D classes at uni...
    My Gideros games: www.totebo.com
  • john26john26 Maintainer
    You just need to do what I told you above! You must abandon movie clips and gtweens and move mesh triangles using only setVertex to animate the mesh. Use a single enter frame to loop over all vertices and update their positions. You must have a single mesh with thousands of vertices not thousands of meshes with a few vertices each.
Sign In or Register to comment.