Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Sprites vs Meshes

edited July 2012 in General Posts: 725

Looking for a bit of clarity on the use of sprites vs meshes. I put together a sample bit of code to test the relative speed difference between the two and as suspected the mesh approach vastly outperforms the sprite approach.

My question is, what can sprites do that meshes cannot? Or in other words, when should I be using sprites instead of meshes? Are sprites basically intended as a simple method of getting images up and running ( a single command "sprite" as opposed to the four mesh(), texture, addRect and setRectTex) or am I missing something.

Many thanks in advance

-- Use this function to perform your initial setup
function setup()
   --gameState is the current state of play
   -- 1 is sprites
   -- 2 is meshes
   iparameter("gameState",1,2,1)
   iparameter("size",25,100,40)
   g=mesh()
   g.texture="Planet Cute:Brown Block"
   xpos=0 --used for the movement of the background
   ground={}
   roof={}
   for i=1,200 do
       ground[i]=math.random(20)
       roof[i]=math.random(20)
       end
   FPS = 0
   watch("FPS")
   timeInterval = 0
   frameCount = 0
end

-- This function gets called once every frame
function draw()
--FPS print out from Minesweeper by @Reefwing

  frameCount = frameCount + 1
  timeInterval = timeInterval + DeltaTime

  if timeInterval > 1 then

      FPS = math.floor((frameCount / timeInterval)+0.5)
      timeInterval = 0
      frameCount = 0
  end
elementcount=0
background(0)
if gameState==1 then
   for i = 1,(1.5*WIDTH/size) do
       for h=1,ground[i] do
          sprite("Planet Cute:Ramp South",xpos+size*i-size,-50+(size/2)*h,size,size)
       elementcount = elementcount + 1
       end
       for h=1,roof[i] do
           sprite("Planet Cute:Ramp South",xpos+size*i-size,HEIGHT+50-(size/2)*h,size,size)
        elementcount = elementcount + 1
       end
   end
elseif gameState==2 then
g:clear()
  for i = 1,(1.5*WIDTH/size) do
       for h=1,ground[i] do
           local idx = g:addRect(xpos+size*i-size,-50+(size/2)*h,size,size)
            g:setRectTex(idx, 0, 0, 1, 1)
           elementcount = elementcount + 1
       end

       for h=1,roof[i] do
           local idx = g:addRect(xpos+size*i-size,HEIGHT+50-(size/2)*h,size,size)
           g:setRectTex(idx, 0, 0, 1, 1)
           elementcount = elementcount + 1
       end
   end
g:draw()
end

   xpos=xpos-1
      if xpos<-1*size then
       xpos=0
       end
       temp=ground[1]
       tempr=roof[1]
       for c=1,#ground-1 do
           ground[c]=ground[c+1]
           roof[c]=roof[c+1]
       end

       ground[#ground]=temp
       roof[#roof]=tempr
       text("Elements drawn: "..elementcount,WIDTH/2,HEIGHT/2)
end

Comments

  • Posts: 18

    Nice example!
    I think it would be great to have a simple way to use meshes as easy as the sprite function..
    ..maybe an automatic texture-atlas-creation system in the background..
    something like "spriteMesh(texture,x,y,...)"

  • Posts: 202

    Meshes are faster to draw. You provide one texture and extra information like coordinates and the rest is done low-level.

    Sprites on the other hand are easier to handle (and easier to understand if you've never seen meshes), especially if they appear as single instances.

    The sprite has always been there, it's like a fundamental building block. The mesh is the power-up.

  • Posts: 725

    @Codeslinger -thanks for the clarification

  • Posts: 447

    @derhannes, I think TTL was planning something like that at some point, but don't know if it went forward.

  • Posts: 447

    @derhannes, that said anyone can implement that, no special codea sauce needed. You'd have a library that overwrites the sprite function to use meshes instead, and just use that everywhere. It'd be cool eh?

  • Posts: 179

    Yeah, now that we have readImage, it'd be almost trivial to make I think. The result could be: add this tab to your program that uses sprites and it will get much faster with no recoding necessary. Maybe I will mess around with that a bit this weekend.

    The only problem is, if I can make sprites as fast as meshes, then maybe no one bothers to read my nice mesh tutorial anymore :-?

  • edited July 2012 Posts: 179

    Turbo sprite attempt #1:

    function sprite(t,xp,yp,wp,hp)
        local x = xp or 0
        local y = yp or 0
        local w, h
        if wp and hp then
            w = wp
            h = hp
        else
            w, h = spriteSize(t)
            w = w * ContentScaleFactor --fix for retina display
            h = h * ContentScaleFactor
        end
        local m = mesh()
        m.texture = t
        local mode = spriteMode()
        if mode == CENTER then
            m:addRect(x,y,w,h)
        elseif mode == RADIUS then
            m:addRect(x,y,w*2,h*2)
        elseif mode == CORNER then
            x = x + math.floor(w/2)
            y = y + math.floor(h/2)
            m:addRect(x,y,w,h)
        elseif mode == CORNERS then
            w = w - x
            h = h - y
            x = x + math.floor(w/2)
            y = y + math.floor(h/2)
            m:addRect(x,y,w,h)
        end
        m:draw()
    end
    

    That is a fully working mesh replacement for sprites. The only problem is, it is slightly slower than the Codea sprite function, so it is basically worthless so far. I need to rewrite it to cache images to memory as I think this will be faster. To really make it fast, I would need to code it to reuse the same mesh between calls, but that seems a little complex. Feel free to contribute if anyone feels like it.

  • Posts: 179

    Added image caching and caching of image sizes, speed slightly increased. Unfortunately, it is now about the same speed as the stock sprite function, not faster. Unless I think of some clever way to reuse my mesh between sprite calls, I will give up on this project. Here is the updated code, which, if added to your project, will make it run about the same as it did without this code:


    imgBox ={} --caching images in this box sw ={} --cache sprite widths sh = {} -- cache sprite heights function sprite(t,xp,yp,wp,hp)     if imgBox[t] == nil then imgBox[t] = readImage(t) end     local x = xp or 0     local y = yp or 0     local w, h     if wp and hp then         w = wp         h = hp     else         if sw[t] == nil then             sw[t], sh[t] = spriteSize(imgBox[t])                  sw[t] = sw[t] * ContentScaleFactor --fix for retina display             sh[t] = sh[t] * ContentScaleFactor               end           w = sw[t]         h = sh[t]     end     local m = mesh()     m.texture = imgBox[t]     local mode = spriteMode()     if mode == CENTER then         m:addRect(x,y,w,h)     elseif mode == RADIUS then         m:addRect(x,y,w*2,h*2)     elseif mode == CORNER then         x = x + math.floor(w/2)         y = y + math.floor(h/2)         m:addRect(x,y,w,h)     elseif mode == CORNERS then         w = w - x         h = h - y         x = x + math.floor(w/2)         y = y + math.floor(h/2)         m:addRect(x,y,w,h)     end     m:draw() end
  • SimeonSimeon Admin Mod
    edited July 2012 Posts: 4,889

    @Vega what you really want is to build a texture atlas as-you-go, then have all sprite() calls hook into that atlas, and all sprite() calls simply add to a mesh. This is pretty tricky to code in a way that replicates the "immediateness" of the sprite() API.

    In order to transparently recreate the sprite() API with a deferred mesh renderer, you might do the following:

    • Use one mesh containing a pool of vertices (perhaps limiting yourself to 1000 - 10000 quads)
    • Use one texture atlas (image).

    On each frame:

    • Reset all the vertices to (0,0,0)
    • Handle each sprite call as detailed below
    • Render resulting mesh once

    On each call to sprite():

    • Check if the specified texture or sprite exists in the texture atlas.
    • If not, delete the texture atlas and re-allocate it so that it includes the new texture (slow, but happens once per new texture use). Store the texture coordinates for the added texture.
    • If it does then fetch the texture coordinates for the specified texture region
    • Form a valid rectangle for the sprite from unused mesh vertices

    Because sprite() is an immediate API (you call it, it draws, no state is left behind), it's quite hard to re-create in a deferred rendering system, such as mesh. It might be better to consider sprite objects — you create sprite objects, add them to a system, and then render the system. Internally it is entirely one mesh, and one texture, but the API paradigms are far more similar.

  • Posts: 179

    Thanks, @Simeon for that info, it seems that everything you say would work. Create one texture map like a spritesheet and store coordinates, then reuse rects of the mesh, cycling through a large amount so that there are enough to handle all of the sprites in each frame.

    The goal of this was just to transparently recreate the sprite API to make it faster, and I really only wanted to do it when I thought the project was going to take me less than an hour. Now that I have failed at that, I figured I would post the code in case anyone else felt like picking up the project.

    I think I should have realized that if it were that easy to speed up sprites, you would have already done it!

  • SimeonSimeon Admin Mod
    Posts: 4,889

    As @ruilov said, we were thinking about doing it in the background — generating atlases and using a single mesh to handle all sprites.

    However it's far more difficult, because if you suddenly draw an ellipse() between a sequence of sprite() calls, you have just split your mesh into two. Because a texture/shader change occurs in-sequence.

  • Posts: 13

    Is my understanding correct that you have to specify texture coordinates as floating-point fractions of the bitmap size, rather than integral pixel coordinates? I would be worried about rounding error especially in the case of adjacent sprites in an atlas (if I'm following this discussion correctly), perhaps causing the edge of one sprite to bleed into another. Do FP coords work OK in practice or is there some special trickery involved?

  • SimeonSimeon Admin Mod
    Posts: 4,889

    FP coordinates are the standard - you can easily achieve sub-pixel precision even for very large images. Some people will enlarge their sprite regions by 0.5 pixels when looking up texture coordinates.

    Generally you leave 1 or 2 pixels of padding between images packed into a texture atlas. Because if your textures are packed edge-to-edge, the bilinear smoothing algorithm will sample adjacent pixels, and can pick up neighboring colors.

Sign In or Register to comment.