Howdy, Stranger!

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

drawing performance

edited May 2012 in Questions Posts: 15

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

Comments

  • Posts: 398

    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?

  • edited May 2012 Posts: 273

    @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/

  • Posts: 15

    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 ;)

    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..

  • BortelsBortels Mod
    Posts: 1,557

    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.

  • SimeonSimeon Admin Mod
    Posts: 5,199

    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).

  • Posts: 15

    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

  • SimeonSimeon Admin Mod
    Posts: 5,199

    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.

  • Posts: 15

    your welcome @Simeon ;) no problem!

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

  • BortelsBortels Mod
    edited May 2012 Posts: 1,557

    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.

  • Posts: 398

    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. :-)

    Thanks for the "under the bonnet" insight into how Codea draws stuff.. Very useful.

  • edited May 2012 Posts: 15

    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 ;)

    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
    
    
  • SimeonSimeon Admin Mod
    Posts: 5,199

    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
    
  • Posts: 15

    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 ;)

    I didn't have that in mind until now..

    lupo

  • SimeonSimeon Admin Mod
    Posts: 5,199

    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.)

  • edited May 2012 Posts: 15

    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
Sign In or Register to comment.