Increased gaussian blur causes increased CPU load

Well, here is some code which, as far as I can tell, does not require any extra CPU/GPU load to perform the blur because it’s already been calculated. I think. This code, however, starts to run extremely slowly if the spot count goes above 10 or the max blur value is increased. As it is, it runs almost just fine on an iPhone5s - though the device noticeably heats up.

Download: https://dl.dropboxusercontent.com/u/10254959/SnapshotBlurTest/SnapshotBlurTest.zip

main.lua

-- snapshot blur test require("rgb") display.setStatusBar( display.HiddenStatusBar ) display.setDefault( "background", 1, 1, 1 ) math.randomseed( os.time() ) stage = display.getCurrentStage() pWidth, pHeight = display.contentWidth, display.contentHeight local colours = { "red", "green", "blue", "purple", "yellow", "dodgerblue", "lightseagreen", "gold", "maroon", } local spotcount = 10 local group = display.newGroup() group.x, group.y = pWidth\*.5, pHeight\*.5 local xSpan, ySpan = pWidth\*.5, pHeight\*.5 local function newEndpoint( isStart ) local x = math.random( 0, pWidth ) - pWidth\*.5 local y = pHeight if (not isStart) then y = -y end return x, y end function newDot( p, x, y, r, c, a, b ) print( p, x, y, r, c, a, b ) local circleSnapshot = display.newSnapshot( p, 600, 600 ) circleSnapshot.circle = display.newCircle( 0, 0, r ) circleSnapshot.circle:setFillColor(c[1],c[2],c[3],a) circleSnapshot.group:insert( circleSnapshot.circle ) circleSnapshot.fill.effect = "filter.blurGaussian" circleSnapshot.fill.effect.horizontal.blurSize = b circleSnapshot.fill.effect.vertical.blurSize = b circleSnapshot.x, circleSnapshot.y = x, y return circleSnapshot end local function launch(dot) if (dot.name == "timer") then local colour = RGB[colours[ math.random(1,#colours)] ] dot = newDot( group, 0, 0, math.random(10, 40), colour, 1/(100/math.random(20,70)), math.random(10, 30) ) end local sx, sy = newEndpoint( true ) local ex, ey = newEndpoint( false ) dot.x, dot.y = sx, sy transition.to( dot, { time=math.random(4000,15000), x=ex, y=ey, onComplete=launch } ) end for i=1, spotcount do timer.performWithDelay( math.random(1000,7000), launch, 1 ) end

rgb.lua

-- rgb --Colors referenced from http://www.tayloredmktg.com/rgb/ RGB = { neoncyan = {16, 174, 239}, -- Neon Cyan neonyellow = {231, 228, 37}, -- Neon Yellow neonpink = {231, 83, 177}, -- Neon Pink neongreen = {4, 228, 37}, -- Neon Green iosblue = {0, 122, 255}, -- iOS Blue iosgrey = {146, 146, 146}, -- iOS Grey iosgreen = {50, 155, 43}, -- iOS Green iosdarkgreen = {35, 105, 28}, -- iOS Dark Green white = {255, 255, 255}, --White orange = {255, 132, 66}, --Orange pink = {255, 192, 203}, --Pink red = {255, 0, 0}, --Red green = {0, 255, 0}, --Green blue = {0, 0, 255}, --Blue purple = {160, 32, 240}, --Purple black = {0, 0, 0}, --Black yellow = {255, 255, 0}, --Yellow grey = {150, 150, 150}, --Grey whitesmoke = {245, 245, 245}, --White Smoke skyblue = {135, 206, 250}, --Sky Blue deepskyblue = {0, 191, 255}, --Deep sky blue dodgerblue = {30, 144, 255}, --Dodger Blue navy = {0, 0, 128}, --Navy lightskyblue = {135, 206, 250}, --Light Sky Blue lightcyan = {224, 255, 255}, --Light Cyan powderblue = {176, 224, 230}, --Powder Blue darkgreen = {0, 100, 0}, --Dark Green darkolivegreen = {85, 107, 47}, -- Dark Olive Green yellowgreen = {154, 205, 50}, --Yellow Green khaki = {240, 230, 140}, --Khaki kellygreen = {76, 187, 23}, --Kelly Green northtexasgreen = {5, 144, 51}, --North Texas Green mediumspringgreen = {0, 250, 154}, -- Medium Spring Green honeydew = {240, 255, 240}, --Honeydew lightseagreen = {32, 178, 170}, --Light Sea Green palegreen = {152, 251, 152}, --Pale Green limegreen = {50, 205, 50}, --Lime Green forestgreen = {34, 139, 34}, --Forest Green gold = {255, 215, 0}, --Gold mustard = {255, 192, 3}, --Mustard lemonchiffon = {255, 250, 205}, --Lemon Chiffon lightgoldenrodyellow = {250, 250, 210}, --Light Goldenrod Yellow darkred = {160, 0, 0}, --Dark Red peach = {255, 185, 143}, --Peach hotpink = {255, 105, 180}, --Hot Pink deeppink = {255, 20, 147}, --Deep Pink turquoise = {64, 224, 208}, --Turquoise kcred = {207, 0, 0}, --KC Red petal = {173, 90, 255}, --Petal indianred = {205, 92, 92}, --Indian Red violetred = {208, 32, 144}, --Violet Red darksalmon = {233, 150, 122}, --Dark Salmon lavenderblush = {255, 240, 245}, --Lavender Blush wheat = {245, 222, 179}, --Wheat thistle = {216, 191, 216}, --Thistle maroon = {176, 48, 96}, --Maroon bamboo = {216, 199, 169}, --Bamboo greyflannel = {195, 196, 192}, -- Grey Flannel oldlace = {249, 240, 226}, --Old Lace brownbag = {192, 137, 103}, -- Brown Bag mediumgrey = {175, 175, 175}, -- Med Grey darkwood = {133, 94, 66}, -- Dark Wood woolgrey = {143, 148, 152}, -- Wool Grey tan = {139, 90, 43}, --Tan darkgrey = {100, 100, 100}, -- Dark Grey darkbrown = {95, 59, 24}, --Dark Brown } -- added since v.1225 local function convertForGfx2() for k,v in pairs(RGB) do for i=1, #v do v[i] = v[i] / 255 end end end convertForGfx2() local colours = { primary = { "red", "blue", "kellygreen", }, moderate = { "red", "blue", "kellygreen", "deeppink", "deepskyblue", "gold", }, advanced = { "red", "blue", "kellygreen", "deeppink", "deepskyblue", "gold", "darkbrown", "lightseagreen", "mediumspringgreen", }, } function getColourBars() local group = display.newGroup() local scrollview = widget.newScrollView { top = 0, left = 0, width = pWidth, height = pHeight, scrollWidth = pWidth, scrollHeight = pHeight, } group:insert(scrollview) local text = display.newText( group, "", 0,50, nil, 48 ) function touch(e) text.text = e.target.text text.x = pWidth/2 return true end local y = 0 if (true) then for k,v in pairs(RGB) do local rect = display.newRect( 0, 0, pWidth, 50 ) scrollview:insert(rect) rect.y=25+y rect.text = k rect:setFillColor( v[1], v[2], v[3] ) rect:addEventListener("tap",touch) y=y+50 end else for k,v in pairs(colours) do for i=1, #v do local rect = display.newRect( 0, 0, pWidth, 50 ) scrollview:insert(rect) rect.y=25+y rect.text = v[i] local col = RGB[v[i]] rect:setFillColor( col[1], col[2], col[3] ) rect:addEventListener("tap",touch) y=y+50 end y=y+150 end end return scrollView end

The reason things start to go slow is because the blur effect is never rendered to the texture. The blur is being calculated for every object on every frame.

If you want the blur to only be rendered once, then you must have a second snapshot which contains the first snapshot (with the blur effect) as I described above.

I modified your newDot function as follows.

After doing this I was able to increase the spotCount to 50 and the blur up to 100 and still see good performance. The more spots and the wider blur you set, there may be an initial stuttering as the rendering is being done for the first time, but after that the animation is smooth.

I used the snapshot canvas here. I’m not sure if it’s necessary, but I think that less resources will be used by doing so.

function newDot( p, x, y, r, c, a, b ) print( p, x, y, r, c, a, b ) local circle = display.newCircle( 0, 0, r ) circle:setFillColor(c[1],c[2],c[3],a) local circleSnapshot = display.newSnapshot(r\*2+b\*2, r\*2+b\*2 ) circleSnapshot.canvas:insert( circle ) circleSnapshot.canvasMode = "discard"; circleSnapshot.fill.effect = "filter.blurGaussian" circleSnapshot.fill.effect.horizontal.blurSize = b circleSnapshot.fill.effect.vertical.blurSize = b circleSnapshot:invalidate("canvas"); local outerSnapshot = display.newSnapshot(p, circleSnapshot.width, circleSnapshot.height) outerSnapshot.canvas:insert(circleSnapshot); outerSnapshot.canvasMode = "discard"; outerSnapshot:invalidate("canvas"); outerSnapshot.x, outerSnapshot.y = x, y return outerSnapshot; end

Awesome, thank you. I’m just not clear on why two snapshots are required. Can the snapshot be told simply to render once? If so, I would have thought that only one is required. If snapshots are redrawing surely that means that both are redrawing? (Yes, there is obviously something I have not grasped here.) However, your code works really well, so I’ve updated my snippet to include advice from Danny, too…

https://dl.dropboxusercontent.com/u/10254959/SnapshotBlurTest/SnapshotBlurTest.2.zip

The whole idea with snapshots is that the children of the snapshot are rendered to texture only once (or until invalidate() is called).

However, it’s only the children that are rendered to texture. Any effects on the snapshot are *not* included when rendering to the texture. 

That’s why a second snapshot is needed. The second snapshot will have the first snapshot as its child, and since children get rendered to texture (effects and all), the blur effect on the first snapshot will ultimately be rendered to the texture of the second snapshot only once.

So it performs better with two because the outer snapshot is not applying any filters to it’s own content?

Precisely.

I’m not certain if using the snapshot canvas in this case has any advantage over the group though. I’m just assuming that rendering from the canvas in “discard” mode will have a smaller memory footprint than if you’d use the group. I may be wrong about that though…

@corona staff

This should be mentioned on the filter guide site. I would have had a problem without that advices.

http://docs.coronalabs.com/guide/graphics/effects.html#performance

Thanks!

PS. hope my grammar fits, cause I’m german.

@corona staff

This should be mentioned on the filter guide site. I would have had a problem without that advices.

http://docs.coronalabs.com/guide/graphics/effects.html#performance

Thanks!

PS. hope my grammar fits, cause I’m german.