drawing performance

Hi,

before doing a real project, im trying some stuff.

While playing with a touch-gesture to zoom objects, i experienced a noteable drop in performance as the objects get bigger.
Im just drawing a Square and an Ellipse… how can that be sooo expensive?

Any Ideas what I’m doing wrong?

Thanks in advance,
lupo

Here’s the full Code:

--# Main
function setup()
    displayMode(FULLSCREEN)
    size = 200
    touches = {}
    touchcount = 0
    x = WIDTH / 2
    y = HEIGHT / 2
    objects = {Rect(),Ellipse()}
end

function draw()
    background(0, 0, 0, 255)
    for k,obj in ipairs(objects) do
        obj:draw()
    end
end

function updateObjects()
    for i,obj in ipairs(objects) do
        obj.x = x
        obj.y = y
        obj.size = size
    end
end

function touched(touch)
    if touch.state == ENDED then
        touches[touch.id] = nil
        touchcount = touchcount - 1
    else
        touches[touch.id] = touch
        if touch.state == BEGAN then
            touchcount = touchcount + 1
        end
    end
    
    if touchcount == 1 then
        if not (touch.state == ENDED) then
            x = touch.x
            y = touch.y
        end
    elseif touchcount == 2 then
        for k,othertouch in pairs(touches) do
            if not (touch.id == othertouch.id) then
                x = math.min(touch.x, othertouch.x) + math.abs( touch.x - othertouch.x ) / 2
                y = math.min(touch.y, othertouch.y) + math.abs( touch.y - othertouch.y ) / 2
                size = math.sqrt( math.pow((touch.x - othertouch.x),2) + math.pow((touch.y - othertouch.y),2) )
            end
        end
    end
    updateObjects()
end

--# Ellipse
Ellipse = class()

function Ellipse:init()
    self.x = WIDTH / 2
    self.y = HEIGHT / 2
    self.size = 200
end

function Ellipse:draw()
    pushMatrix()
    strokeWidth(5)
    stroke(190, 151, 59, 59)
    ellipse(self.x, self.y, self.size)
    fill(203, 64, 64, 255)
    popMatrix()
end

--# Rect
Rect = class()

function Rect:init()
    self.x = WIDTH / 2
    self.y = HEIGHT / 2
    self.size = 200
end

function Rect:draw()
    strokeWidth(5)
    stroke(155, 48, 48, 70)
    rect(self.x - self.size / 2, self.y - self.size / 2, self.size, self.size)
    fill(72, 63, 203, 255)
end

I did a similar thing for my first mini-project with Codea - simple circular ripples emanating from a set of multi-touch events. Performance degraded considerably with anything above 10-15 ellipses being drawn and getting larger over time. Things seemed to improve slightly with thinner stroke widths.

Like you, I probably would have thought Codea would eat this sort of thing for breakfast - but i also wondered if I was doing something wrong (highly likely!)

Anyone else experienced this sort of thing?

@lupo and @andymac3d… I’m not sure if it is applicable to your cases but there was a discussion some months ago about ellipses rendering slowly. Can’t say that it will help but @Simeon offered a few tips on how to speed things up:

http://twolivesleft.com/Codea/Talk/discussion/646/large-circle-ellipse-renders-slowly/

thanks for the link @Blanchot . I replaced the ellipse with another rect, but still got the same problems.

increasing size means slower rendering. allthough im just painting two primitive shapes.

maybe the problem is that im using the ipad1 :wink:

i also tried to use an image object as buffer: painting everything into the image, then draw the image onto the screen… this approach made things even worse.

I think using a static image for each shape like in the linked thread will do the thing. I’ll also have a look at those mesh thingeys…

The only other thing I can see that might be expensive is the square root - and I don’t see a way around that in this context.

lupo: iPad 1’s main limitation is fill rate. So when you draw lots of ellipses, or even smooth rects (which each render blended shader-computed pixels) it rapidly adds up. At the moment there is no way to render non-blended pixels (we felt it would be too confusing to add this much state information).

The fastest primitives should be: noSmooth rect, sprite, mesh, noSmooth lines
The most expensive would be: smooth rects, ellipses, smooth lines (large width). More expensive when you add stroked outlines as well.

Painting into an image buffer every frame will only add overhead to the rendering process. However if you render into the image once, and then draw that, it should be faster than an ellipse (less calculations per pixel).

thanks @Simeon ! since i am using just one rect and one ellipse ( not “lots of”), i assume that drawing (big) shapes is not a viable approach for rendering my screen. at least on the ipad 1…

I will have a look at the usage of images/sprites and meshes, and play around with those.

thanks again,
lupo

Sorry. I read @andymac3d’s post and thought it was yours (10-15 ellipses). That said, it’s the size of the ellipses and rects that matter, not the number of them.

your welcome @Simeon :wink: no problem!

now i know that drawing (and moving) big filled shapes is not a good thing to do :wink:

More to the point - render the ellipse to an image once (make an image, setcontext(image), draw the ellipse, setcontext() )- then use scale() to resize and sprite() the image, is what I think @Simeon is suggesting.

Drawing a smoothed ellipse is “expensive”, relatively - a big filled shape is fine. Blitting the sprite out with an alpha-blend is fairly cheap. Cheapest, presumably, would be setting up a mesh with the ellipse images.

Thanks @Simeon and @Bortels. Yes, my “plan b” was to pre-render this out as a sprite and do pretty much as suggested rather than my er…brute force method. :slight_smile:

Thanks for the “under the bonnet” insight into how Codea draws stuff… Very useful.

yeah, thanks to all of you.

I changed my code to use pre-rendered images, and it goes really fast now, even with upscaled images…

However, While trying to change my code i ran into another problem. I think its simple, but i just can’t do the math atm:

I changed my Ellipse class to create an image on its init(), and then just scale and draw it.
However, after scaling, the draw position is always off the position it should be.

I tried several things with translate() and the draw positions, but i just don’t get it.

everything seems fine until i scale up or down. Once again, any help would be great :wink:

have a nice day,
lupo

My code:

--# Ellipse
Ellipse = class()

function Ellipse:init()
    self.x = WIDTH / 2
    self.y = HEIGHT / 2
    self.size = 200.0
    self.initialSize = self.size
    self.image = image(self.size, self.size)
    setContext(self.image)
    strokeWidth(5)
    stroke(190, 151, 59, 59)
    ellipse(self.size / 2, self.size / 2, self.size)
    setContext()
end

function Ellipse:draw()
    pushMatrix()
    pushStyle()
    
    
    spriteMode(CENTER)
    local currentScale = self.size / self.initialSize
    scale(currentScale)
    --translate(???,???)
    --sprite(self.image, ???, ???)
    sprite(self.image, self.x, self.y)
    
    popStyle()
    popMatrix()
end

--# Main
function setup()
    --displayMode(FULLSCREEN)
    size = 200
    touches = {}
    touchcount = 0
    x = WIDTH / 2
    y = HEIGHT / 2
    objects = {Ellipse()}
end

function draw()
    background(0,0,0)
    for k,obj in ipairs(objects) do
        obj:draw()
    end
end

function updateObjects()
    for i,obj in ipairs(objects) do
        obj.x = x
        obj.y = y
        obj.size = size
    end
end

function touched(touch)
    if touch.state == ENDED then
        touches[touch.id] = nil
        touchcount = touchcount - 1
    else
        touches[touch.id] = touch
        if touch.state == BEGAN then
            touchcount = touchcount + 1
        end
    end
    
    if touchcount == 1 then
        if not (touch.state == ENDED) then
            x = touch.x
            y = touch.y
        end
    elseif touchcount == 2 then
        for k,othertouch in pairs(touches) do
            if not (touch.id == othertouch.id) then
                x = math.min(touch.x, othertouch.x) + math.abs( touch.x - othertouch.x ) / 2
                y = math.min(touch.y, othertouch.y) + math.abs( touch.y - othertouch.y ) / 2
                size = math.sqrt( math.pow((touch.x - othertouch.x),2) + math.pow((touch.y - othertouch.y),2) )
            end
        end
    end
    updateObjects()
end

Try the following:

function Ellipse:draw()
    pushMatrix()
    pushStyle()


    spriteMode(CENTER)
    local currentScale = self.size / self.initialSize

    translate(self.x,self.y)
    scale(currentScale)

    sprite(self.image, 0, 0)

    popStyle()
    popMatrix()
end

wow thanks a lot, that was fast, and really helpful.

So, if i understand correctly translate() works on the scaled environment. So the order of scale() and translate() calls does really matter :wink:

I didn’t have that in mind until now…

lupo

It’s the reverse of what you’d expect — due to the matrix multiplication ordering. (You can almost read the transform operations prior to drawing backwards in order to get a sense of how the operations will actually play out.)

Hi,

its me again. Maybe other Codea starters may find this helpful. To solve problem above with many different shapes, I created a base class called BufferedImage. I can extend this class and only code the painting stuff, the remaining stuff is done in the base class.

Heres my code:


--# BufferedImage
BufferedImage = class()

-- this function should be overridden by subclasses
-- everything painted in here, will get painted into
-- an internal Buffer ONCE, when creating an instance.
-- 
-- The draw() then draws it onto the screen scaled and translated
--
function BufferedImage:paint() end

function BufferedImage:init(xPos, yPos, imageSize)
    self.x = xPos
    self.y = yPos
    self.size = imageSize
    self.initialSize = self.size
    self.image = self:createImage()
end

function BufferedImage:createImage()
    local img = image(self.size, self.size)
    
    setContext(img)
    pushMatrix()
    pushStyle()
    
    self:paint()
    
    popStyle()
    popMatrix()
    setContext()
    return img
end

function BufferedImage:draw()
    pushMatrix()
    pushStyle()
    
    spriteMode(CENTER)
    
    local currentScale = self.size / self.initialSize
    
    translate(self.x, self.y)
    scale(currentScale)
    sprite(self.image, 0, 0)
    
    popStyle()
    popMatrix()
end



--# Circle
-- simple Circle implementation of the BufferedImage base class
Circle = class(BufferedImage)

function Circle:paint()
    fill(245, 7, 60, 255)
    strokeWidth(5)
    stroke(68, 109, 217, 59)
    ellipse(self.size / 2, self.size / 2, self.size)
end


--# Rect
--simple rect implementation of the BufferedImage base class
Rect = class(BufferedImage)

function Rect:paint()
    fill(255, 255, 255, 255)
    strokeWidth(5)
    stroke(118, 213, 41, 59)
    rect(0, 0, self.size, self.size)
end



--# Main
function setup()
    --displayMode(FULLSCREEN)
    size = 200
    touches = {}
    touchcount = 0
    x = WIDTH / 2
    y = HEIGHT / 2
    objects = {Rect(x,y,200),Circle(x,y,200)}
end

function draw()
    background(0,0,0)
    for k,obj in ipairs(objects) do
        obj:draw()
    end
end

function updateObjects()
    for i,obj in ipairs(objects) do
        obj.x = x
        obj.y = y
        obj.size = size
    end
end

function touched(touch)
    if touch.state == ENDED then
        touches[touch.id] = nil
        touchcount = touchcount - 1
    else
        touches[touch.id] = touch
        if touch.state == BEGAN then
            touchcount = touchcount + 1
        end
    end
    
    if touchcount == 1 then
        if not (touch.state == ENDED) then
            x = touch.x
            y = touch.y
        end
    elseif touchcount == 2 then
        for k,othertouch in pairs(touches) do
            if not (touch.id == othertouch.id) then
                x = math.min(touch.x, othertouch.x) + math.abs( touch.x - othertouch.x ) / 2
                y = math.min(touch.y, othertouch.y) + math.abs( touch.y - othertouch.y ) / 2
                size = math.sqrt( math.pow((touch.x - othertouch.x),2) + math.pow((touch.y - othertouch.y),2) )
            end
        end
    end
    updateObjects()
end