oomzay
Visitor
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
10-05-2016
03:26 PM
Surprise: screen.finish() appears to be swapping the screen buffers!
I was interested in the measuring the time taken by the GPU to finish rendering each frame so I added an option to do an explicit screen.finish() to my frame loop (after drawing) and found to my surprise that the double buffered screen started tearing. What's more, adjusting the moment when the screen.finish() is called during each frame moves the "tear line" up and down so it looks very much like finish() is actually triggering an unsynchronised swap!
Configuration: Model: 2400SK (Roku LT) (Firmware Version: 077.00E09593A)
In the demo code below (written to demo a different issue) the OK/SELECT button now enables/disables the call to screen.finish() and LEFT & RIGHT buttons adjust the simulated drawing time, and hence the moment when finish() is called, and hence the tear position.
Configuration: Model: 2400SK (Roku LT) (Firmware Version: 077.00E09593A)
In the demo code below (written to demo a different issue) the OK/SELECT button now enables/disables the call to screen.finish() and LEFT & RIGHT buttons adjust the simulated drawing time, and hence the moment when finish() is called, and hence the tear position.
' ----------------------------------------------------------------------------
' Vertical Synchronisation Glitch Demo
'
' On some platforms there is now (7.00 firmware) an intermittent
' (~1 per second) failure to SwapBuffers at 60FPS, even for animation times
' less than 4mSecs.
'
' This can be observed as tearing and/or juddering of the vertical bars
' and is reflected in the displayed frame times. The average should
' settle to 16,666 uSecs, the instantaneous value should dither between
' 16 and 17 mSecs.
'
' - left/right buttons adjust simulated animation time +/-1 ms
' ----------------------------------------------------------------------------
Library "v30/bslCore.brs"
' ----------------------------------------------------------------------------
' ----------------------------------------------------------------------------
sub main ()
' Log system info so we know when we are comparing chalk and cheese
'
log_system_info()
' Handy Facts
'
VIDEO_FIELD_MS = 1000.0 / 60.0
' Initialise various resources
'
ecodes = bslUniversalControlEventCodes()
clk = CreateObject("roTimespan")
port = CreateObject("roMessagePort")
screen = CreateObject("roScreen", TRUE)
screen.SetMessagePort(port)
' Prepare font to display statistics on screen
'
font = CreateObject("roFontRegistry").GetDefaultFont()
txt_height = font.getOneLineHeight()
txt_color = &h60ff60ff
txt_margin = 100
' Create some animated stripes to illustrate tearing and judders.
'
num_stripes = 10
stripes = []
for x = 0 to screen.getWidth()-1 step screen.getWidth()/num_stripes
stripes.push(VerticalStripe(screen.getWidth(), screen.getHeight(), x))
end for
' Simulated frame animation time (target & moving average)
'
animation_target_ms = 4
animation_avg_filter = jcEWMAFilter(0.995, animation_target_ms * 1000)
last_animation_overrun_ms = animation_target_ms
' Frame timing stats.
'
last_frame_start = INVALID
frame_avg_filter = jcEWMAFilter(0.995, VIDEO_FIELD_MS * 1000)
last_frame_overrun_ms = 17
' Issue Finish() after rendering
'
explicit_finish = FALSE
' Do initial buffer swap before we start timing loop
'
screen.clear(0)
screen.SwapBuffers()
screen.clear(0)
' Spin around SwappingBuffers and monitoring how long field/frame takes.
'
while TRUE
screen.SwapBuffers()
this_frame_start = clk.TotalMilliseconds()
' Handle any events
' - left/right buttons adjust simulated animation time +/-1 ms
'
msg = port.GetMessage()
if type(msg) = "roUniversalControlEvent" then
ecode = msg.GetInt()
if ecode = ecodes.BUTTON_RIGHT_PRESSED then
animation_target_ms = animation_target_ms + 1
else if ecode = ecodes.BUTTON_LEFT_PRESSED then
animation_target_ms = animation_target_ms - 1
else if ecode = ecodes.BUTTON_SELECT_PRESSED then
explicit_finish = not explicit_finish
end if
end if
screen.clear(0)
' Animate & render the vertical stripes
'
for each stripe in stripes
stripe.animate(screen)
end for
' Calculate & render statistics.
'
if last_frame_start <> INVALID then
' Calc
last_frame_ms = this_frame_start - last_frame_start
frame_avg_us = int(frame_avg_filter.update(last_frame_ms * 1000))
animation_avg_us = int(animation_avg_filter.update(last_animation_ms * 1000))
if last_frame_ms > 17 then
last_frame_overrun_ms = last_frame_ms
end if
if last_animation_ms > animation_target_ms then
last_animation_overrun_ms = last_animation_ms
end if
' Render
x = txt_margin
y = txt_margin
screen.drawText("Vertical Synchronisation Glitch Demo", x, y, txt_color, font)
y = y + 2 * txt_height
screen.drawText("Animation target: " + animation_target_ms.toStr() + " ms (L = -1, R = +1)", x, y, txt_color, font)
y = y + txt_height
screen.drawText("Animation actual: " + last_animation_ms.toStr() + " ms (Avg: " + animation_avg_us.toStr() + " us, Last overrun: " + last_animation_overrun_ms.toStr() + " ms)", x, y, txt_color, font)
y = y + txt_height
screen.drawText("Frame actual: " + last_frame_ms.toStr() + "ms (Avg: " + frame_avg_us.toStr() + " us, Last overrun: " + last_frame_overrun_ms.toStr() + " ms)", x, y, txt_color, font)
endif
' Spin until the simulated animation time has elapsed.
'
this_animation_ms = clk.TotalMilliseconds() - this_frame_start
while this_animation_ms < animation_target_ms
this_animation_ms = clk.TotalMilliseconds() - this_frame_start
end while
' Finish to sync with GPU
' *** This seems to cause horrible tearing ***
'
if explicit_finish then
screen.finish()
end if
' Go round again
'
last_frame_start = this_frame_start
last_animation_ms = this_animation_ms
end while
end sub
' ----------------------------------------------------------------------------
' Animated Vertical White Stripe
' ----------------------------------------------------------------------------
function VerticalStripe (cw as Integer, ch as Integer, x0 as Integer) as Object
this = {}
this.cw = cw
this.x = x0
this.w = 50
this.h = ch
this.dx = 4
this.clr = &h808080ff
' Animate and render this stripe to canvas.
'
this.animate = function (canvas as Object) as Void
this = m
this.x = this.x + this.dx
if this.x > this.cw then
this.x = this.x - (this.cw + this.w)
end if
canvas.drawRect(this.x, 0, this.w, this.h, this.clr)
end function
return this
end function
' ----------------------------------------------------------------------------
' 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
' ----------------------------------------------------------------------------
' Log basic system info
' ----------------------------------------------------------------------------
sub log_system_info()
di = CreateObject("roDeviceInfo")
print " Device: "; di.GetDeviceUniqueId()
print " Model: "; di.GetModel(); " ("; di.GetModelDisplayName(); ")"
print "Firmware Version: "; di.GetVersion()
md = di.GetModelDetails()
print " Vendor Name: "; md.VendorName
print " Model Number: "; md.ModelNumber
ai = CreateObject("roAppInfo")
print " App: "; ai.GetTitle(); " ("; ai.GetVersion(); ")"
print " DevID: "; ai.GetDevID()
end sub
4 REPLIES 4
oomzay
Visitor
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
10-08-2016
04:11 AM
Re: Surprise: screen.finish() appears to be swapping the screen buffers!
So how can screen.finish() cause tearing on a double buffered screen? (when draw + finish < 4 mSecs)
Is this a bug or a design feature that we need to understand?
What really happens, in some detail, when you call screen.finish() and screen.swapbuffers()?
Any differences across platforms?
Thanks.
Is this a bug or a design feature that we need to understand?
What really happens, in some detail, when you call screen.finish() and screen.swapbuffers()?
Any differences across platforms?
Thanks.


Roku Employee
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
10-10-2016
10:29 AM
Re: Surprise: screen.finish() appears to be swapping the screen buffers!
"oomzay" wrote:
I was interested in the measuring the time taken by the GPU to finish rendering each frame so I added an option to do an explicit screen.finish() to my frame loop (after drawing) and found to my surprise that the double buffered screen started tearing. What's more, adjusting the moment when the screen.finish() is called during each frame moves the "tear line" up and down so it looks very much like finish() is actually triggering an unsynchronised swap!
For double-buffered roScreen, I believe you only want to call screen.SwapBuffers(). screen.Finish() should not be used in that case.
oomzay
Visitor
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
10-10-2016
01:19 PM
Re: Surprise: screen.finish() appears to be swapping the screen buffers!
Thank you for your response KC.
1. Please could you arrange for the ifdraw2D.Finish() documentation to be elaborated to clarify this because the ifdraw2D.finish() page simply states it "Realizes the bitmap by finishing all queued draw calls." without mention of side effects or advice not to use it with a double buffered roScreen.
2 Are you able to find answers to any of my more specific questions?
These are not idle questions. I, and I can infer several others, are trying very hard to squeeze maximum graphics performance out of the Roku platforms. The details of the Roku display pipeline implementation are currently opaque but the side effects are not invisible and keep biting us in unexpected ways. An clear explanation of how the API calls map to underlying GL (if indeed they do!) would help to reduce the significant barrier that the proprietory language and APIs impose on potential developers and would thus help people who are trying hard to add value to your platform ...to everyone's advantage.
1. Please could you arrange for the ifdraw2D.Finish() documentation to be elaborated to clarify this because the ifdraw2D.finish() page simply states it "Realizes the bitmap by finishing all queued draw calls." without mention of side effects or advice not to use it with a double buffered roScreen.
2 Are you able to find answers to any of my more specific questions?
These are not idle questions. I, and I can infer several others, are trying very hard to squeeze maximum graphics performance out of the Roku platforms. The details of the Roku display pipeline implementation are currently opaque but the side effects are not invisible and keep biting us in unexpected ways. An clear explanation of how the API calls map to underlying GL (if indeed they do!) would help to reduce the significant barrier that the proprietory language and APIs impose on potential developers and would thus help people who are trying hard to add value to your platform ...to everyone's advantage.


Roku Employee
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
10-11-2016
09:51 AM
Re: Surprise: screen.finish() appears to be swapping the screen buffers!
"oomzay" wrote:
1. Please could you arrange for the ifdraw2D.Finish() documentation to be elaborated to clarify this because the ifdraw2D.finish() page simply states it "Realizes the bitmap by finishing all queued draw calls." without mention of side effects or advice not to use it with a double buffered roScreen.
A note has been added to the ifdraw2D.Finish() documentation, FWIW.
"oomzay" wrote:
2 Are you able to find answers to any of my more specific questions?
...
I sympathize and agree with your points wholeheartedly. It looks like you "filed a ticket" on the other thread you made for this issue, so hopefully that will generate a response through the proper channels. (I am only a casual commenter, and not part of developer support).