Roku Developer Program

Developers and content creators—a complete solution for growing an audience directly.
cancel
Showing results for 
Search instead for 
Did you mean: 
oomzay
Level 7

SwapBuffers not blocking if animation longer than 1/60 sec.

I am experimenting with a double buffered roScreen & 2d animation. I think I have misunderstood what SwapBuffers() does...

If my animation takes less than 16.6 mS (60FPS) from the previous SwapBuffers call then the next SwapBuffers call blocks until +16.6 mS, which is what I expected. However, if my animation takes a bit longer than 16.6 mS then the next SwapBuffers returns immediately! I was expecting it to block till the following sync at +33.3mS (30FPS).

Configuration: Model: 2400SK, Firmware Version: 075.05E00430A

I have reduced the original code to a demo that displays the measured frame timings on the screen and allows me to adjust the animation time using LEFT & RIGHT buttons. Its a bit bloaty, the avg filter is not really needed to see the problem, but here it all is:-


Library "v30/bslCore.brs"

sub main ()

' Initialise the usual 2d resources
'
ecodes = bslUniversalControlEventCodes()
clk = CreateObject("roTimespan")
port = CreateObject("roMessagePort")
screen = CreateObject("roScreen", TRUE) ' dbl buffered
screen.SetMessagePort(port)
screen.clear(0)

' Let's have a font to display the timings on screen
'
font = CreateObject("roFontRegistry").GetDefaultFont()
txt_height = font.getOneLineHeight()
txt_color = &h44ff44ff
txt_margin = 20

' Artificial animation time.
' This can be adjusted using LEFT/RIGHT buttons to explore the
' frame sync problem.
'
busy_ms = 8

' Frame timing data.
'
' The filter is just a luxury - but its reassuring to see
' it settle close to 16,666 uSecs in the 60FPS regime!
'
last_frame_start = INVALID ' monitor inter-frame period
frame_avg_filter = jcEWMAFilter(0.99, 1000/60)

' Spin around SwappingBuffers and monitoring
' how long each cycle takes.
'
while TRUE

' Handle async events without blocking:
' left/right adjust update time +/- 1 mSec
'
msg = port.GetMessage()
if type(msg)="roUniversalControlEvent" then
ecode = msg.GetInt()
if ecode = ecodes.BUTTON_RIGHT_PRESSED then
busy_ms = busy_ms + 1
else if ecode = ecodes.BUTTON_LEFT_PRESSED then
busy_ms = busy_ms - 1
end if
end if

' Sync and Swap the screen buffers.
' The timestamp should be just after vsync? - I thought!
'
screen.SwapBuffers()
current_frame_start = clk.TotalMilliseconds()

' Compute & display timings.
'
' The 1ms resolution measurements _should_ dither around
' the vsync time (16.6mSecs) or a multiple thereof.
' I.e. 16-17, 33-34 ...
'
if last_frame_start <> INVALID then

last_frame_ms = current_frame_start - last_frame_start
avg_ms = frame_avg_filter.update(last_frame_ms)
avg_us = int(1000 * avg_ms)

screen.clear(0)
x = txt_margin
y = txt_margin
screen.drawText("Animation: " + busy_ms.toStr() + " mSecs", x, y, txt_color, font)
y = y + txt_height
screen.drawText("Frame period: " + last_frame_ms.toStr() + " mSecs", x, y, txt_color, font)
y = y + txt_height
screen.drawText("Frame average: " + avg_us.toStr() + " uSecs", x, y, txt_color, font)
endif

' Bump the frame start history
'
last_frame_start = current_frame_start

' Simulate lengthy animation
'
while clk.TotalMilliseconds() < current_frame_start + busy_ms
' just spin
end while

end while
end sub


' Exponentially Weighted Moving Average (EWMA) filter
' Yt = Xt + α ( Yt-1 - Xt)
'
function jcEWMAFilter(alpha as Float, avg0 as Float) as Object
this = {}

this.alpha = alpha
this.avg = avg0

' Insert new sample, returns updated average.
'
this.update = function (x as Float) as Float
this = m
this.avg = x + (this.alpha * (this.avg - x))
return this.avg
end function

return this
end function


Have I got the wrong idea with SwapBuffers?
If so, how else can I sync?

Thanks.
0 Kudos
6 Replies
oomzay
Level 7

Re: SwapBuffers not blocking if animation longer than 1/60 s

I found the post below that confirms SwapBuffers should indeed wait for the next vsync, as I assumed:
"RokuMarkn" wrote:
Also note that SwapBuffers waits for the next vertical sync, so it's expected that it could take up to 16 ms. That's not a performance problem, it's the way SwapBuffers has to work.

Could someone with roku animation experience kindly have a quick look at my code to see if I have made an obvious error? This seems like a "show stopper" for animations slower than 60FPS if it really isn't waiting for the next vsync.

(If you side load the code the animation time can be adjusted up/down with the left/right buttons - below 16mSecs everything seems fine, i.e. SwapBuffers blocks till +16.6ms, but above that SwapBuffers just does not seem to block at all - ever!)
0 Kudos
oomzay
Level 7

Re: SwapBuffers not blocking if animation longer than 1/60 s

...oh. hang on... the manual doesn't actually state it will wait for the next vsync:

This call will not return until the back buffer is ready to be drawn on to. Depending on the implementation, it may take up to a single video frame period for the new front buffer to become visible.


Does this mean that, depending on the implementation, there might be a 1 frame pipeline and SwapBuffers is just waiting till it can put something in the pipeline? So for animations longer than 16ms the pipeline is always clear when we next call SwapBuffers? So no wait is ever needed?

That would explain the symptoms.

So how do people reliably lock slower animations to vsync? (e.g. 30FPS)
0 Kudos
Roku Employee
Roku Employee

Re: SwapBuffers not blocking if animation longer than 1/60 s

The issue you are seeing is as I understand it a platform dependent issue (affecting our "Giga" series of hardware, the Roku 2 XS/XD, LT2400, 2400SK, MHL Streaming Stick and possibly the USB stick). Essentially SwapBuffers will de-sync from the vertical blanking interval if you take more than 16 ms before calling it. This was a hack to improve performance of our home screen on these hardware platforms.

If you want to do slower animation, then use a timer to trigger your draw commands, but still call swap buffers every 16ms. Or use a single buffered screen and make all drawing dependent on timers.

Also, depending on the type of animation you are doing, you could use the built in animation functionality for roAnimatedSprite, where you can set the animation time between frames, see ifCompositor

- Joel
0 Kudos
oomzay
Level 7

Re: SwapBuffers not blocking if animation longer than 1/60 s

"RokuJoel" wrote:
The issue you are seeing is as I understand it a platform dependent issue (affecting our "Giga" series of hardware, the Roku 2 XS/XD, LT2400, 2400SK, MHL Streaming Stick and possibly the USB stick). Essentially SwapBuffers will de-sync from the vertical blanking interval if you take more than 16 ms before calling it. This was a hack to improve performance of our home screen on these hardware platforms.

Thanks, that's very useful info. Is it reasonable to assume that SwapBuffers will always wait for the next VSYNC on all other models?

"RokuJoel" wrote:
If you want to do slower animation, then use a timer to trigger your draw commands, but still call swap buffers every 16ms.

That is more or less how I have worked around it. It costs 2 full screen bitmaps: one for the frame under construction and one for the previous frame so it can be copied to the screen two fields in a row, but the result is a rock-solid 30FPS which I wanted.

"RokuJoel" wrote:
Also, depending on the type of animation you are doing, you could use the built in animation functionality for roAnimatedSprite, where you can set the animation time between frames, see ifCompositor

I, and I think some others, are a bit confused by the documentation of this class:

1) What exactly does it mean "set the animation time between frames"? Are we telling it how long has passed snce last animation or how long we want between renderings?
2) How exactly is this number used to decide which animated sprite frame to display?
3) What is the intended use of ChangeMatchingRegions?

Anyway, thankyou very much for your response, it makes all the difference to get this sort of background and help when learning a new platform!
0 Kudos
NewManLiving
Level 7

Re: SwapBuffers not blocking if animation longer than 1/60 s

An animated sprite takes an array of regions. Each region has a SetTime member ( see roRegion ). The SetTime member is defined as
Set the "frame hold time" in milliseconds. This is the duration of each frame of any animated sprite which uses this region. As we are dealing with
a single-threaded model AnimationTick must be called explicitly and frequently for animation to take place. Its parameter consists of : "the number of ms since the last call." So it can calculate movement based upon those factors. Having used this briefly in the past to do things like animated cursors and text effects, I found it more economical and practical to just create my own array of regions and animate them myself. Or rather use the rotation and scaling functions to do it with less overhead.
However, if you are in the game business ( which I believe this API was intended ) you may find it very beneficial for what you are doing.
My Channels: 2D API Framework Presentation: https://owner.roku.com/add/2M9LCVC
Updated: 11-11-2015 - Completed Keyboard interface
The Joel Channel ( Final Beta )
0 Kudos
oomzay
Level 7

Re: SwapBuffers not blocking if animation longer than 1/60 s

Thanks, I think I now grok this roCompositor.

"NewManLiving" wrote:
I found it more economical and practical to just create my own array of regions and animate them myself.

Having now had a closer look I too will stick to using my own compositors, they are simpler in the common case, and more flexible where I need it.

In any case I don't believe that roCompositor can make any difference at all to the loss of VSYNC on these "Giga" platforms, that problem is fixed by contriving to call SwapBuffers every 16mS regardless of animation rate & compositor implementation.
0 Kudos