backingmode(RETAINED) bug

There is a a small annoying bug with draw() function: when a project is run, there is a small glitch after 1/5 second, which is ok in backingmode STANDARD, but resets your picture in RETAINED mode. Its seems that the backingmode() function is really applied 1/5s after the beginning of the project start.

@Simeon I can easily manage that by delaying the computations start with a tween.delay(), but since it is a bug, maybe you want to solve it? Or will you let it ‘as is’? I would like to know your choice on that, to adapt my code to this choice. Thanks.

Below is some code to reproduce the bug:
[edit] changed a couple colors to make it even more obvious.


-- test backing

function setup()
    ball = {x=0, y=HEIGHT/2, r=50}
    tween(1, ball, {x=WIDTH}, {loop=tween.loop.pingpong} )
    stroke(0)
    strokeWidth(2)
    fill(255)
    -- choose one of these by commenting one of the lines
    draw = draw1 -- STANDARD backing mode
    draw = draw2 -- or RETAINED
    -- this is to init backingmode during setup (doesnt help)
    --draw()
end

function draw1()
    if backingMode() ~= STANDARD then 
        backingMode(STANDARD)
        return
    end
    background(255, 0, 20, 255)
    ellipse(ball.x,ball.y,ball.r)
end
function draw2()
    if backingMode() ~= RETAINED then 
        backingMode(RETAINED)
        background(255, 0, 20, 255)
        return
    end
    ellipse(ball.x,ball.y,ball.r)
end

-- this is to init backingmode before setup (doesnt help either)
-- draw2() 


Thanks @jmv38. I’d like to fix this if possible.

thanks for your answer.
Then i’ll wait for it to be solved, and just make a simple workaround meanwhile.

@Jmv38 so this is actually a tricky thing to guarantee under a multithreaded model, but I have implemented a fix that you can use to guarantee a retained backing on frame 1.

In the fixed version, calling backingMode(RETAINED) in the global scope (e.g., outside of setup()) will ensure that you get a retained backing mode by the time setup() is called. This will be in the next beta build.

However, calling backingMode(RETAINED) from inside draw() or setup() will not happen immediately. This is because of the teardown and setup required to set (or unset) retained backing mode.

Basically what is happening is your draw() function is happening on a render thread, and it is possible to “queue up” multiple draw() functions for performance reasons. However, the backingMode function needs to completely reset OpenGL in order to work, so it also needs to “queue up” after the last scheduled draw function.

This means that often there will be 1 or 2 frames before Codea gets around to evaluating your backingMode call.

Here’s a diagram:

Lua                      Renderer

draw()                   Frame #1
  backingMode()---|
  background() ---|-----> glClear 
                  | 
                  |       Finish Frame #1
                  |
draw()            |      Frame #2 (already queued by the time backingMode is called!)
                  |       Finish Frame #2
                  | 
                  |-----> TEARDOWN
                          SETUP NEW BACKING

draw()                   Frame #3
                          Finish Frame #3

Because of this new asynchronous behaviour, it might be better to update the API to backingMode to accept an optional callback when the backing mode is set, in case you need to do your initial drawing, e.g.,

function setup()
    backingMode( RETAINED, function()
        -- Backing mode has been set, do some drawing
        background(255,0,0)
        
        -- Some static content
    end)
end

Alternatively we could have a global function that Codea calls when the mode is changed:

function backingModeChanged( newMode )
    if newMode == RETAINED then
        -- Do some initial drawing 
    end
end

(Similar to orientationChanged global callback)

wow, there is so much under the hood, good thing you manage all that for us and we have only to understand lua…
I would go for the the backingMode( mode, optionalCallback, …) calling optionnalCallback(…), because this increase complexity only for those who want to go that far, and keep things unchanged for others. And it does not introduce another function. And this callback mechanism is very standard in Codea now.
Thanks.

Btw, is there any chance to get access to the frame grabber some day? Today the only way to save currently displayed image is to draw() into an image with setContext(img), but this does not fit well with backingmode(retained).
Another usage would be to capture some pictures from the internal web browser, this is not possible today. This is not very useful for making games, this is more for the fun of using Codea as an allmighty tool for ipad.
Thanks.

Actually @Jmv38 it’s possible to write the backingMode + callback option directly in Codea now, so I won’t implement the callback option as an official API.

(EDIT: However the next version does implement backingMode() support for the global scope, which is good for anyone that just needs to set backingMode once at the start of their project.)

Here is a version of your example which should work:

function backingModeDraw( mode, callback )
    backingMode( mode )
    
    local oldDraw = draw
    draw = function()
        callback()        
        oldDraw()
        draw = oldDraw
    end
end

function setup()
    ball = {x=0, y=HEIGHT/2, r=50}
    tween(1, ball, {x=WIDTH}, {loop=tween.loop.pingpong} )
    stroke(0)
    strokeWidth(2)
    fill(255)
end

function draw()
    if backingMode() ~= RETAINED then 
        backingModeDraw(RETAINED, function()
            background(255,0,20,255)
        end)
        
        return
    end
    
    ellipse(ball.x,ball.y,ball.r)
end

(This basically “hijacks” the draw function for one frame after it is called.)

nice idea, but unforunately it doesnt work: the background color is called before the baking mode is actually changed, so when i run your code i get a black background. Did it work for you?

@Jmv38 actually I think that is due to a separate bug I just fixed — the layout system was queuing up a layer backing change as well as the backingMode call. It no longer does that.

My fix above should work in the next beta version.

however this works (edit: simultaneous posts, i couldnt read your above comment when i posted that)

function backingModeDraw( mode, callback )
    backingMode( mode )

    local oldDraw = draw
    local countdown = 15
    draw = function()
        if countdown == 0 then
            callback()        
            draw = oldDraw
        end
        countdown = countdown - 1
    end
end

function setup()
    ball = {x=0, y=HEIGHT/2, r=50}
    tween(1, ball, {x=WIDTH}, {loop=tween.loop.pingpong} )
    stroke(0)
    strokeWidth(2)
    fill(255)
end

function draw()
    if backingMode() ~= RETAINED then 
        backingModeDraw(RETAINED, function()
            background(255,0,20,255)
        end)

        return
    end

    ellipse(ball.x,ball.y,ball.r)
end

i was surprised i had to set the countdown as high as 15. the value of 10 sometimes works, sometimes not.

next beta: i am still under ios 7.x, sorry for that :-"

@Jmv38 yeah, that’s not due to the renderer queuing up frames — it can only queue up a maximum of 2 frames (otherwise touches would potentially lag).

What is happening is another layout is being triggered after displaying the viewer (after about 10 frames it seems), which is causing the OpenGL layer to be set again, which queues up a teardown / setup. When it tears down the backing mode is still retained, but because you only ever set the background colour once it never gets set back to red after the layout causes the reset. This extra layout has been eliminated.

The next build should be out shortly, in which you do not need the countdown and can use my solution. You will also be able to set the backing mode from global scope (much simpler for most uses).

Btw, one more thing: this forum and your reactivity are really a great part of Codea experience. Some months ago i tried to switch to pythonista, because my son was starting python courses, but after a couple months i kind of stopped: it is a great environement, but the forum is 10x less active and enthousistic than this one, so after a while programming felt like… work, not fun. While writing programs under Codea and sharing them is just so much fun!
Thank you for that!

Thanks! I had a lot of fun trying to solve this bug as well.

I’ll put the next beta on the old TestFlight as well so you can try it. (It shuts down on the 26th of February, so there is still some time to use it.)

@Jmv38 let me know if you are able to use the beta and if it solves the issue.

i will

@simeon i’ve installed the lastest beta, and it works without the countdown now. Great job!

@Jmv38 the following should also work now

backingMode(RETAINED)

function setup()
    background(255,0,20,255)
    ball = {x=0, y=HEIGHT/2, r=50}
    tween(1, ball, {x=WIDTH}, {loop=tween.loop.pingpong} )
    stroke(0)
    strokeWidth(2)
    fill(255)
end

function draw()
    ellipse(ball.x,ball.y,ball.r)
end

yes it does.