The gaussian blur filter is known to be a resource intense shader.
You can file a bug report on it, but I’m not sure there is much we can do about it.
Rob
The gaussian blur filter is known to be a resource intense shader.
You can file a bug report on it, but I’m not sure there is much we can do about it.
Rob
Thanks for the input jerejigga, I had considered canvas textures (and may have to go down that route with a static blur if it comes down to it) but was hoping that I’d overlooked something simple that would solve this.
Rob I can file a bug report if needed, but I’m curious as to how anyone can currently use that filter given that the sample I linked to has hardly anything in it, no updating and ran so slow. I would assume that even on a newer/faster device, if you used that filter in a real game which has it’s own logic and updating objects it would cripple the frame rate there too.
Have you tried making snapshots and not be constantly calling the blur function?
I’ve never actually tried to use it a project where frame rate mattered, so I’m speculating here.
You’ve lost me slightly, as I’m already using snapshots and I’m not constantly calling the blur function. I create the blur once, and the only time it would be updated at the moment is during a 800ms transition on a button touch (though even this is removed in my sample).
In my sample I’m literally creating the blurred rects, then putting them into a snapshot and never changing them after that. This is the entirety of the sample:
local minX = display.screenOriginX local minY = display.screenOriginY local maxX = display.viewableContentWidth + -1\* display.screenOriginX local maxY = display.viewableContentHeight + -1\* display.screenOriginY local \_W = maxX - minX local \_H = maxY - minY local centerX = display.contentCenterX local centerY = display.contentCenterY local allowBlurFilter = true local rectW, rectH = \_W \* 0.4, \_W \* 0.2 --make a background local bg = display.newRect(minX + \_W \* 0.5, minY + \_H \* 0.5, \_W, \_H) bg:setFillColor( 1, 1, 0.8 ) --draw a large image on screen local img = display.newImageRect("images/coronaicon.png", \_W, \_W) img.x, img.y = centerX, minY + (\_W \* 0.5) --draw 4 rects, with blurred drop shadow for i = 1, 4 do local x, y = 0, 0 if i \<= 2 then x = centerX - rectW \* 0.6 else x = centerX + rectW \* 0.6 end y = maxY - 300 - ((2-i) % 2) \* (rectH \* 1.5) --create a shadow and insert it into a snapshot which is twice as large, so there is room for blurring local shadowRect = display.newRect( 0, 0, rectW, rectH ) shadowRect:setFillColor( 0 ) local shadow = display.newSnapshot( rectW \* 2, rectH \* 2 ) shadow.x = 10 shadow.y = 10 shadow.alpha = 0.5 shadow.group:insert(shadowRect) if allowBlurFilter then shadow.fill.effect = "filter.blurGaussian" --[[shadow.fill.effect.horizontal.blurSize = 4 shadow.fill.effect.horizontal.sigma = 128 shadow.fill.effect.vertical.blurSize = 4 shadow.fill.effect.vertical.sigma = 128--]] shadow:invalidate() end --create the button local button = display.newRect( 0, 0, rectW, rectH) button:setFillColor( 0.04, 0.25, 0.55 ) --add some text to it local buttonText = display.newText({ text = "Button "..i, x = button.x, y = button.y, width = button.width, height = 0, font = native.systemFont, align = "center", fontSize = 90, }) --insert into a group local group = display.newGroup( ) group.x, group.y = x, y group:insert(shadow) group:insert(button) group:insert(buttonText) end --draw frame rate on screen local fps = require("memory\_and\_FPS") local performance = fps.PerformanceOutput.new() performance.group.x, performance.group.y = centerX, minY
Have you tested on more modern devices? I just ran your sample on an iPhone 6S, and it’s super fast. I understand that’s the fastest iPhone, but I’m surprised that an iPad 3 would be so utterly slow. Basically, I want to confirm this is a hardware issue more than a filter issue…
Brent
We tried it on an iPad Air and this sample ran ok, however in our real app it still ran slowly if we tried to transition the filter values on a button press.
I say our real app, in actual fact it’s also a prototype app which is basically the sample posted above with button presses + transitions enabled. Still no enterFrame listeners or anything like that, just transition + perform with delay calls on the touch began/ended phases:
local minX = display.screenOriginX local minY = display.screenOriginY local maxX = display.viewableContentWidth + -1\* display.screenOriginX local maxY = display.viewableContentHeight + -1\* display.screenOriginY local \_W = maxX - minX local \_H = maxY - minY local centerX = display.contentCenterX local centerY = display.contentCenterY local allowBlurFilter = true local enableTouch = true local rectW, rectH = \_W \* 0.4, \_W \* 0.2 --make a background local bg = display.newRect(minX + \_W \* 0.5, minY + \_H \* 0.5, \_W, \_H) bg:setFillColor( 1, 1, 0.8 ) --draw a large image on screen local img = display.newImageRect("images/coronaicon.png", \_W, \_W) img.x, img.y = centerX, minY + (\_W \* 0.5) local function filterTransition(obj, alpha, blur, sigma, time, ease, xScale, yScale ) if obj.\_colorTransition ~= nil then timer.cancel(obj.\_colorTransition) obj.\_colorTransition = nil end if obj.\_valueTransition ~= nil then transition.cancel(obj.\_valueTransition) obj.\_valueTransition = nil end obj.\_valueTransition = transition.to(obj, {time = time, xScale = xScale, yScale = yScale, \_a=alpha, \_hb=blur, \_hs=sigma, \_vb=blur, \_vs=sigma, transition = ease}) local f = function() if obj then if obj.\_hb then --count = count + 1 obj.alpha = obj.\_a obj.fill.effect.horizontal.blurSize = obj.\_hb obj.fill.effect.horizontal.sigma = obj.\_hs obj.fill.effect.vertical.blurSize = obj.\_vb obj.fill.effect.vertical.sigma = obj.\_vs end end end obj.\_colorTransition = timer.performWithDelay(5, f, math.ceil(time/5)) end local function cancelTransitions(obj, shadow) if obj and obj.trans then transition.cancel(obj) obj.trans = nil end if shadow then if shadow.\_colorTransition then timer.cancel( shadow.\_colorTransition ) end if shadow.\_valueTransition then transition.cancel( shadow.\_valueTransition ) end end end --draw 4 rects, with blurred drop shadow for i = 1, 4 do local x, y = 0, 0 if i \<= 2 then x = centerX - rectW \* 0.6 else x = centerX + rectW \* 0.6 end y = maxY - 300 - ((2-i) % 2) \* (rectH \* 1.5) --create a shadow and insert it into a snapshot which is twice as large, so there is room for blurring local shadowRect = display.newRect( 0, 0, rectW, rectH ) shadowRect:setFillColor( 0 ) local shadow = display.newSnapshot( rectW \* 2, rectH \* 2 ) shadow.x = 10 shadow.y = 10 shadow.alpha = 0.5 shadow.group:insert(shadowRect) if allowBlurFilter then shadow.fill.effect = "filter.blurGaussian" shadow.fill.effect.horizontal.blurSize = 4 shadow.fill.effect.horizontal.sigma = 128 shadow.fill.effect.vertical.blurSize = 4 shadow.fill.effect.vertical.sigma = 128 shadow:invalidate() end --used in the transition function above if shadow.fill.effect then shadow.\_a = shadow.alpha shadow.\_hb = shadow.fill.effect.horizontal.blurSize shadow.\_hs = shadow.fill.effect.horizontal.sigma shadow.\_vb = shadow.fill.effect.vertical.blurSize shadow.\_vs = shadow.fill.effect.vertical.sigma end --create the button local button = display.newRect( 0, 0, rectW, rectH) button:setFillColor( 0.04, 0.25, 0.55 ) --add some text to it local buttonText = display.newText({ text = "Button "..i, x = button.x, y = button.y, width = button.width, height = 0, font = native.systemFont, align = "center", fontSize = 90, }) --insert into a group local group = display.newGroup( ) group.x, group.y = x, y group:insert(shadow) group:insert(button) group:insert(buttonText) local function onTouch(e) if e.phase == "began" then display.getCurrentStage():setFocus(button) cancelTransitions(group, shadow) group.trans = transition.to(group, {xScale = 1.1, yScale = 1.1, time = transitionTimeUp, transition = easing.outElastic, onComplete = function() cancelTransitions(group, shadow) end }) filterTransition(shadow, 0.7, 32, 64, 800, easing.outElastic, 1.1, 1.1) elseif e.phase == "ended" or e.phase == "cancelled" then display.getCurrentStage():setFocus(nil) cancelTransitions(group, shadow) group.trans = transition.to(group, {xScale = 1, yScale = 1, time = transitionTimeDown, transition = easing.outBack, onComplete = function() cancelTransitions(group, shadow) end}) filterTransition(shadow, 0.5, 4, 128, 400, easing.outBack, 1, 1) end return true end if enableTouch then button:addEventListener( "touch", onTouch ) end end ---[[--draw frame rate on screen local fps = require("memory\_and\_FPS") local performance = fps.PerformanceOutput.new() performance.group.x, performance.group.y = centerX, minY --]]
(I’ve also updated the repo on bitbucket if you’d rather grab it from there)
So on the iPad 3 just using the filter kills the framerate, on the iPad Air it’s fine until you try to perform any re-rendering of the filter, and then the frame rate takes a hit again. It’s not as bad, but given that this is a super basic sample and running on a reasonably recent device it’s still a bit concerning.
I’ve taken a screenshot on the iPad Air that shows the hit the frame rate takes when press one of the buttons:

It was a bit hard to capture it at the exact time, but you can see on the graph that it drops from 55-60ish down to under 25fps, and then back up again when you release the button.
On a related note, you can see that even when the frame rate is high, it seems to be jumping constantly between 60 and 30. We’ve always seen this happening in all of our apps and never been sure why. I’m aware it could be the fps tracker itself that’s causing it, but wondered if there was anything else that could be causing it or if anyone else had seen similar fps rates?
Hi Alan,
Now I’m confused… somewhere above/earlier you said performance was being killed with nothing happening (no re-rendering). Now you say it’s fine (no performance impact) when nothing is happening. What is the actual case here for clarity?
Of course the performance will be killed if you try to transition gaussian blur values. A transition (or movement) means it has to re-render the filter every single frame as it transitions, and performance impacts of doing so are well known.
Best regards,
Brent
To clarify:
iPad 3: nothing happening - slow (7fps) transitioning - super slow (3 fps) iPad Air: nothing happening - fine - 50+ fps transitioning - ok - drops to ~23fps
We knew that transitioning would have some impact, we just didn’t realise it would be so drastic.
Very odd… and on the iPad 3, this is a continual ongoing 3-7 fps? Or just a momentary drop?
fwiw: a snapshot can only prerender its contents onto its canvas, it can’t prerender it’s own effects into itself, so the blur (on the snapshot) is being rendered each frame – its contents may be prerendered and static, but the blur remains dynamic – you’d need to put the snapshot inside a parent that could ALSO be prerendered to make the blur static.
@Brent Yes it’s a continuous 7 when nothing is happening, and then momentarily drops down to 3 or 4 fps when transitioning.
@davebollinger - thanks for the info, I’ll try that out and see what difference it makes.
also try checking that your snapshot is no larger than actually necessary to account for the radius of the blur - these are per-pixel effects, so extra transparent area will still consume gpu power.
otoh, if you ever decide you don’t truly need to be able to configure the blur at run-time then a pre-rendered 9-slice of the blur fringe would be best - no filter performance hit, and photoshop’s gaussian looks better anyway. 
Putting a snapshot in another snapshot seems to have done the trick for static objects as seen here:
https://forums.coronalabs.com/topic/42000-effect-aplied-to-the-entire-group/
so we just won’t have any transitions to the filter which isn’t a big deal.
However I’ve ran into another issue when testing on devices. It seems to be fine on our iOS devices, and on Android even my old Nexus 7 runs at around 20fps. On a Fire Stick though, I get this error:
ERROR: A shader failed to compile. To see errors, add the following to the top of your main.lua: display.setDefault( 'isShaderCompilerVerbose', true )
So I added that to my main.lua, which leads to this error:
ERROR:OPTIMIZER-3 (line 93) Support for for loops is restricted : unable to determine the number of iterations at compile time
I figured that ultimately this all means that the Fire Stick is not powerful enough to handle this filter, so I added a catch before adding the filter:
if ( system.getInfo( "gpuSupportsHighPrecisionFragmentShaders" ) ) then object.fill.effect = "filter.blurGaussian" --etc
but “gpuSupportsHighPrecisionFragmentShaders” returns true.
The errors mentioned above clearly have a knock on effect of some kind, because my objects are not correctly positioned (the positioning happens after the filter is applied). Is there any solution to this? I’m using Corona SDK 2016.2886 if that’s any use.
I do not have a Fire Stick so I cannot speak from hands-on experience. However, I have looked at the specs and the Fire Stick is clearly not designed to handle “high performance” games. Amazon even says so on it’s web site. If there is a way to release your game to Amazon TV but explicitly exclude the Stick, I would do that.
https://www.amazon.com/Amazon-Fire-TV-Stick-Streaming-Media-Player/dp/B00GDQ0RMG#compare
Adding to my previous post, here is a link to a comparison of the technical specs of the three Fire TV devices including the Stick. Clearly, the Stick has inferior OpenGL support. Perhaps it simply can’t support Corona’s OpenGL implementation.
Upon even further review… Fire Stick includes OpenGL ES 2.0 whereas Corona requires OpenGL 2.1. I know there are some subtle differences between OpenGL and OpenGL ES, but perhaps this version mismatch is the reason why the shaders are not working on the Stick.
https://docs.coronalabs.com/guide/start/systemReqs/index.html
It’s certainly a less powerful device, but prior to adding the filter it did at least run our games. My main issue is not that I can’t use the filter, but more that the system.getInfo( “gpuSupportsHighPrecisionFragmentShaders” ) function doesn’t seem to prevent it being used. Would there be a reliable way to catch devices that cannot using the filters using some of the other getInfo calls, such as “GL_VERSION” or “GL_SHADING_LANGUAGE_VERSION”?