#### Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

# Performance of arc mesh/shader vs ellipse function

edited May 2014 Posts: 381

Hi all,

I read that to get better performance on drawing, you should write the drawing using mesh and shader. I need to draw some arcs, I mean lots of arcs. I used to draw an arc using simple trigonometry and use ellipses as the "pixel". It's fine as long as I draw less than 10 arcs. Above that, it's getting to slow, like 3-5 fps. While I need to draw about 50-60 moving arcs simultaneously. Then, I took a look at the arc shader sample and use the arc function from Codea's wiki. To my surprise, its performance is almost equal to my technique. I knew there must be something wrong, but I couldn't find it.

So, could someone tell me how to draw arc fastly? It'd be great if it's able to draw arc smoothly (anti-aliased) and respect the lineCapMode as well.

Thank you.

Tagged:
«1

• Posts: 2,043

The arc shader works very well. Unfortunately it doesn't respect lineCapMode (afaik, if not feel free to correct me).

Using the code I wrote below, I am able to stamp arcs all over the screen while maintaining 60 FPS.

``````--# Main
-- Arc

-- Use this function to perform your initial setup
function setup()
parameter.watch("1/DeltaTime")
arcs = {}
end

-- This function gets called once every frame
function draw()
-- This sets a dark background color
background(255, 255, 255, 255)

for i, arc in ipairs(arcs) do
arc:draw()
end
end

function touched(touch)
if touch.state == ENDED then
table.insert(arcs, Arc(touch.x, touch.y))
arcs[#arcs]:start()
end
end

--# Timer
Arc = class()

function Arc:init(x, y, s, col, t)
self.x = x or WIDTH / 2
self.y = y or HEIGHT / 2
self.size = s or WIDTH / 10
self.time = t or 10

self.color = col or color(245, 3, 120, 255)

self.paused = false
self.amnt = 0

self.tMesh = mesh()
self.tMesh.vertices = triangulate({vec2(-self.size / 2, -self.size / 2),
vec2(-self.size / 2, self.size / 2),
vec2(self.size / 2, self.size / 2),
vec2(self.size / 2, -self.size / 2)})
self.tMesh.texCoords = triangulate({vec2(0,0),vec2(0,1),vec2(1,1),vec2(1,0)})
end

function Arc:start()
self.amnt = 0
if self.timing == nil then
self.timing = tween(self.time, self, { amnt = 1 }, tween.easing.linear)
end
end

function Arc:pause()
if self.timing ~= nil then
tween.stop(self.timing)
end

self.paused = true
end

function Arc:resume()
if self.timing ~= nil then
tween.play(self.timing)
end

self.paused = false
end

function Arc:stop()
if self.timing ~= nil then
tween.stop(self.timing)
self.timing = nil
end
end

function Arc:restart()
self:stop()
self:start()
end

function Arc:draw()
-- Update timer
-- self.tMesh.shader.color = vec4(1 * self.amnt, 1 - (1 * self.amnt), 0, 1)
self.tMesh.shader.a2 = -self.amnt * (math.pi * 2) + math.pi

-- Draw timer
pushMatrix()

translate(self.x, self.y)

rotate(270.1)

self.tMesh:draw()

popMatrix()
end

``````
• Posts: 381

Thanks for the code, @jakattak. I forgot to tell in the first post that I need to draw big and thick arcs. As I modify your code to draw big and thick arcs (mesh.size = WIDTH * 0.75; shader.size = 0.25), unfortunately, your code doesn't do any better than mine. Once there are more than 3 arcs, the drawing speed drops rapidly, like 5-7 fps or lower. Everything goes fast if arcs are small and thin, my code also got no problem with that. Oh, one more thing... I'm using iPad 1 on iOS 5. • Posts: 2,161

@bee Can you give a sample of how many, how thick, and how big your arcs are for testing? I have some code for drawing curves that I could dig out, but I don't want to bother posting it if it isn't any improvement.

As far as I'm aware, the inbuilt arc shader is basically a rectangle with stuff chopped out. This makes it expensive as the rectangles can be very big. My curve drawing makes the curve itself into a mesh which is actually cheaper even though it uses more vertices in the mesh.

• edited May 2014 Posts: 381

@andrew_stacey: You could use @jakattak's code above. Just change these lines:

in Arc:init function `self.size = s or WIDTH / 10` to `self.size = s or (WIDTH * 0.75)` and `self.tMesh.shader.size = .45` to `self.tMesh.shader.size = .25`

Run the program. Then tap to create about 50 arcs. The given arc dimension is about what I need. By the time you're adding more arcs, you should see the drawing performance goes down considerably, long before you reach 50. At least that's what I see on my iPad1.

I'm sorry I still can't provide my own code because I'm still working on it. But basically, it's the same code from the arc shader sample and wiki. Just a tiny bit of modification that I'm sure wouldn't give noticeable affect to the performance.

• edited May 2014 Posts: 381

If you need to be more precise... The diameter of arc range from 200 to 500 pixel. The thickness of arc range from 20 to 100 pixel. And the amount of arc to be drawn on the screen is about 40-60 arcs with variable sizes.

Actually, I also need to draw some sectors and segments (you know, part of circle), with stroke and fill color. If you have the code how to draw them fastly, I hope you can share it with us here, if you don't mind. Thank you. • Posts: 3,297

@Bee since you want to make 'thick' objects, you will probably be slow. Can you accept a sharpness loss? If yes, you could sprite your circles into an image 4 times smaller than the screen. This will be 16x faster. Then you sprite this image once with scale(4). The edges will be a bit smooth, but the fps might be good enough.

• Posts: 381

The arc object isn't static. It should change (recalculated) the angle almost 30 times per second. Do you think using sprite technique will boost the performance?

• edited May 2014 Posts: 3,297

@bee if you post your code i'll check if i can make it faster with the sprite thing.
Using the sprite will drop around 30Hz because of setContext, but maybe it will stay at 25-30 because of the x16 gain...
The advantage is that it is simple to implement.
But @Andew_stacey's solution is probably the best solution for thin arcs, but needs some work.

• edited May 2014 Posts: 381

My code is now so messy with some (failed) optimization attempts. I need some time to clean it up, if really needed. But basically it's taken from the given arc shader sample in Codea's shader lab. So, you -or anyone else interested with this problem- could start from there. Or you can also use the @jakattak's code above. Mine is similar to that, minus the class thingy.

The old version using simple trigonometry, taken from my analog clock program (available on CC).

I haven't tried the sprite technique, yet. Because I thought it wouldn't be any different. I think shader and mesh would be the best technique.

Or, should I make a contest for this? • Posts: 3,297

@bee i have checkd the sprite with jakattack code (modified for big thick circles) :it does work! Results:
-- normal: #arcs: 40 => fps: 19.
-- sprite: #arcs: 90 => fps: 56.
check this:

``````--# Main
-- Arc
-- normal: #arcs: 40 => fps: 19
-- sprite: #arcs: 90 => fps: 56

-- Use this function to perform your initial setup
function setup()
fps = 60
parameter.watch("math.floor(fps)")
parameter.watch("#arcs")
parameter.action("print",status)
arcs = {}
img = image(WIDTH/4,HEIGHT/4)
end
function status()
print("#arcs: "..tostring(#arcs) .." => fps: "..tostring(math.floor(fps)))
end
-- This function gets called once every frame
function draw()
k = 0.01
fps = fps*(1-k) + k/DeltaTime
-- This sets a dark background color

setContext(img)
background(255, 255, 255, 255)
scale(1/4)
for i, arc in ipairs(arcs) do
arc:draw()
end
setContext()
resetMatrix()
background(255, 255, 255, 255)
spriteMode(CORNER)
sprite(img,0,0,WIDTH,HEIGHT)
end

function touched(touch)
if touch.state == ENDED then
table.insert(arcs, Arc(touch.x, touch.y,WIDTH))
arcs[#arcs]:start()
end
end

--# Timer
Arc = class()

function Arc:init(x, y, s, thick, col, t)
self.x = x or WIDTH / 2
self.y = y or HEIGHT / 2
self.size = s or WIDTH / 10
self.thick = thick or WIDTH / 10
self.time = t or 10
local ran = function() return math.random(255) end
self.color = col or color(ran(), ran(), ran(), 255)

self.paused = false
self.amnt = 0

self.tMesh = mesh()
self.tMesh.vertices = triangulate({vec2(-self.size / 2, -self.size / 2),
vec2(-self.size / 2, self.size / 2),
vec2(self.size / 2, self.size / 2),
vec2(self.size / 2, -self.size / 2)})
self.tMesh.texCoords = triangulate({vec2(0,0),vec2(0,1),vec2(1,1),vec2(1,0)})
end

function Arc:start()
self.amnt = 0
if self.timing == nil then
self.timing = tween(self.time, self, { amnt = 1 }, tween.easing.linear)
end
end

function Arc:pause()
if self.timing ~= nil then
tween.stop(self.timing)
end

self.paused = true
end

function Arc:resume()
if self.timing ~= nil then
tween.play(self.timing)
end

self.paused = false
end

function Arc:stop()
if self.timing ~= nil then
tween.stop(self.timing)
self.timing = nil
end
end

function Arc:restart()
self:stop()
self:start()
end

function Arc:draw()
-- Update timer
-- self.tMesh.shader.color = vec4(1 * self.amnt, 1 - (1 * self.amnt), 0, 1)
self.tMesh.shader.a2 = -self.amnt * (math.pi * 2) + math.pi

-- Draw timer
pushMatrix()

translate(self.x, self.y)

rotate(270.1)

self.tMesh:draw()

popMatrix()
end

``````
• Posts: 2,161

Hmm, I'm not sure if this is actually much of an improvement. But here it is.

There's an `arc` function and an `Arc` class. Parameters are centre, xaxis (can be vec2 or number), yaxis (ditto), start angle (radians), delta angle (radians).

``````-- Arc path drawing

Arc = class()

local __makeArc = function(nsteps)
-- nsteps doesn't make a huge difference in the range 50,300
nsteps = nsteps or 50
local m = mesh()
//
//

//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;
uniform lowp vec4 scolour;
uniform lowp vec4 ecolour;
lowp vec4 mcolour = ecolour - scolour;
//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;

varying highp vec2 vTexCoord;
varying lowp vec4 vColour;
varying highp float vWidth;
varying highp float vCore;

uniform float width;
uniform float taper;
uniform float blur;
uniform float cap;
uniform float scap;
uniform float ecap;
float swidth = width + blur;
float ewidth = taper*width - width;
float ecapsw = clamp(cap,0.,1.)*ecap;
float scapsw = clamp(cap,0.,1.)*scap;
uniform vec2 centre;
uniform vec2 xaxis;
uniform vec2 yaxis;
uniform float startAngle;
uniform float deltaAngle;

void main()
{
highp float t = clamp(position.y,0.,1.);
vCore = t;
highp float w = smoothstep(0.,1.,t);
vWidth = w*ewidth + swidth;
highp vec2 bpos = centre + cos(t*deltaAngle + startAngle) * xaxis + sin(t*deltaAngle + startAngle) * yaxis;
highp vec2 bdir = -sin(t*deltaAngle + startAngle) * xaxis + cos(t*deltaAngle + startAngle) * yaxis;
bdir = vec2(bdir.y,-bdir.x);
bdir = vWidth*normalize(bdir);
bpos = bpos + position.x*bdir;
highp vec4 bzpos = vec4(bpos.x,bpos.y,0.,1.);
bzpos.xy += (ecapsw*max(position.y-1.,0.)
+scapsw*min(position.y,0.))*vec2(-bdir.y,bdir.x);
highp float s = clamp(position.y,
scapsw*position.y,1.+ecapsw*(position.y-1.));
vTexCoord = vec2(texCoord.x,s);
vColour = t*mcolour + scolour;
//Multiply the vertex position by our combined transform
gl_Position = modelViewProjection * bzpos;
}
]],[[
//
//

uniform highp float blur;
uniform highp float cap;

varying highp vec2 vTexCoord;
varying highp float vWidth;
varying lowp vec4 vColour;
varying highp float vCore;

void main()
{
lowp vec4 col = vColour;
highp float edge = blur/(vWidth+blur);
col.a = mix( 0., col.a,
(2.-cap)*smoothstep( 0., edge,
min(vTexCoord.x,1. - vTexCoord.x) )
* smoothstep( 0., edge,
min(1.5-vTexCoord.y, .5+vTexCoord.y) )
+ (cap - 1.)*smoothstep( 0., edge,
.5-length(vTexCoord - vec2(.5,vCore)))
);

gl_FragColor = col;
}
]])

for n=1,nsteps do
end
return m
end

local m = __makeArc()

-- centre, xaxis, yaxis, startAngle, deltaAngle, taper
function arc(a,b,c,d,e,f)
if type(a) == "table" then
f = b
a,b,c,d,e = unpack(a)
end
if type(b) ~= "userdata" then
b = b*vec2(1,0)
end
if type(c) ~= "userdata" then
c = c*vec2(0,1)
end
m:draw()
end

function Arc:init(...)
self:setParams(...)
end

function Arc:clone()
return Arc(self.params)
end

function Arc:makeDrawable(t)
t = t or {}
local nsteps = t.steps or self.steps
local m = __makeArc(nsteps)
m.shader.taper = t.taper or self.taper or 1
m.shader.blur = t.blur or self.blur or 2
m.shader.cap = t.cap or self.cap or (lineCapMode()-1)%3
m.shader.scap = t.scap or self.scap or 1
m.shader.ecap = t.ecap or self.ecap or 1
m.shader.width = t.width or self.width or strokeWidth()
m.shader.scolour = t.scolour or self.scolour or t.colour or color(stroke())
m.shader.ecolour = t.ecolour or self.ecolour or t.colour or color(stroke())
local a,b,c,d,e = unpack(self.params)
self.curve = m
self.draw = function(self) self.curve:draw() end
end

function Arc:draw(t)
self:makeDrawable(t)
self.curve:draw()
end

function Arc:setParams(a,b,c,d,e)
if type(a) == "table" then
a,b,c,d,e = unpack(a)
end
if type(b) ~= "userdata" then
b = b*vec2(1,0)
end
if type(c) ~= "userdata" then
c = c*vec2(0,1)
end
self.params = {a,b,c,d,e}
if self.curve then
end
end

function Arc:setStyle(t)
self.scolour = t.scolour or t.colour or self.scolour
self.ecolour = t.ecolour or t.colour or self.ecolour
self.width = t.width or self.width
self.taper = t.taper or self.taper
self.blur = t.blur or self.blur
self.cap = t.cap or self.cap
self.scap = t.scap or self.scap
self.ecap = t.ecap or self.ecap
if not self.curve then
return
end
t = t or {}
if t.colour then
end
if t.scolour then
end
if t.ecolour then
end
if t.width then
end
if t.taper then
end
if t.blur then
end
if t.cap then
end
if t.scap then
end
if t.ecap then
end
end

function Arc:point(t)
local a,b,c,d,e = unpack(self.params)
return a + math.cos(t*e + d)*b + math.sin(t*e + d)*c
end

function Arc:tangent(t)
local a,b,c,d,e = unpack(self.params)
return -math.sin(t*e + d)*b + math.cos(t*e + d)*c
end

function Arc:normal(t)
return self:tangent(t):rotate90()
end

function Arc:unitNormal(t)
local pt = self:normal(t)
local l = pt:len()
if l == 0 then
return vec2(0,0)
else
return pt/l
end
end

function Arc:unitTangent(t)
local pt = self:tangent(t)
local l = pt:len()
if l == 0 then
return vec2(0,0)
else
return pt/l
end
end
``````
• Posts: 3,297

@bee have you tried my code? What perf do you get?

• edited May 2014 Posts: 381

@jmv38: I've tried your code. Thank you. So far, yours is the fastest. It could maintain 50-55 fps even when drawing over 50 arcs. But, a very big but, and I'm sorry for saying this, it looks ugly. The pixel is very big, making the arc/circle very pixelated. I haven't yet modifying your code though. I've been busy with something else. Any hint to make it looks (a lot) better?

@andrew_stacey: Thank you. Your code is a lot better than mine or @jakattak's. While our code can't go over 5 arcs without noticable lag, yours is able to get over 10. It's about double of our code. The algorithm is also good, producing nice and sharp circle/arc. The tapper effect is quite nice. Unfortunately, the performance is still below my expectation. Mr Andrew, if you don't mind, would you please explain your code to us here? Especially the shader and mesh part. (I'm still trying to understand the magic behind shader.) I want to enhance your code for drawing other parts of circle, like sector and segment. I need it to have fill color. Any hints? • Posts: 381

Btw, I still don't understand the "magic" constants for shader program. I see some unreasonable value being used here and there like 0.5, 1.0, and 2.0. What are these numbers? I've been digging the Codea's reference with no luck. • Posts: 3,297

@bee i agree the weak part is pixellization (i warned you about this...). It is possible to trade the 'pixellized edge' against 'smooth edge'. Would it be acceptable? Otherwise no solution.
If you are ready to accept smooth edge, edit the shader to make smooth edge over 1 or 2 pixels. After x4 spriting, it will still look smooth (but 4 times bigger edge, of course).
Good luck!

• Posts: 3,297

Actually there might be a solution with one shader, but very complex (i mean simultate vector graphics?). Beyond me, though...

• Posts: 3,297

A possible trade off: instead of x4, try x3 or x2: the edges will be better, and the fps maybe still acceptable (30Hz).

• Posts: 381

It is possible to trade look with speed. As long as the look is quite acceptable. I'll study your code and we'll see what can I do to make it produces better arcs. Thank you. • Posts: 381

Actually, I think @andrew_stacey's code looks promising. Though I don't really understand the shader part. Waiting for Mr Andrew explanation. Perhaps there is still room for further improvements.

• Posts: 2,161

This creates the arc "object": a mesh with a shader attached. The `arc` function calls this with different parameters passed to the shader. The `Arc` class uses this function to create a new mesh for every instance.

So using the `arc` function means one mesh, but every invocation requires fresh data passed to the GPU. Using the `Arc` class means many meshes, but each is hard-coded with its data (though it can be changed).

``````local __makeArc = function(nsteps)
-- nsteps doesn't make a huge difference in the range 50,300
nsteps = nsteps or 50
local m = mesh()
``````

``````//
//

//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;
``````

The uniforms `scolour` and `ecolour` hold the start and end colours of the arc. From these, we compute the difference "colour" (it may not be a valid colour, but that's not important).

``````uniform lowp vec4 scolour;
uniform lowp vec4 ecolour;
lowp vec4 mcolour = ecolour - scolour;
``````

Standard mesh attributes and varyings.

``````//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;

varying highp vec2 vTexCoord;
varying lowp vec4 vColour;
varying highp float vWidth;
``````

The varying `vCore` holds the parameter along the curve clamped to `[0,1]` which will be used in the fragment shader to deal with the ends. The body of the curve corresponds to `y` positions in `[0,1]` and the caps extend beyond, so if `y == vCore` then we're in the body of the curve and if `y ~= vCore` then we're in one of the caps.

``````varying highp float vCore;
``````

These constants control the appearance of the curve. `width` is hopefully obvious, `taper` is how much the width varies, `blur` is how much the edges are blurred. So the actual starting width, `swidth`, is `width + blur` and the actual ending width, `ewidth`, is `taper * width - width` (hmm, now that I look at it maybe there should be a `+ blur` here!). We can set a global `cap` type for both ends and `scap` and `ecap` are used to turn it on or off for start and finish separately. The variables `ecapsw` and `scapsw` contain how far the caps extend from the curve.

``````uniform float width;
uniform float taper;
uniform float blur;
uniform float cap;
uniform float scap;
uniform float ecap;
float swidth = width + blur;
float ewidth = taper*width - width;
float ecapsw = clamp(cap,0.,1.)*ecap;
float scapsw = clamp(cap,0.,1.)*scap;
``````

This set of parameters control the actual arc parameters. The angles are in radians. The axes are vectors as they needn't be the x-axis and y-axis.

``````uniform vec2 centre;
uniform vec2 xaxis;
uniform vec2 yaxis;
uniform float startAngle;
uniform float deltaAngle;
``````

Now we enter the main function. The idea of the shader is that the mesh is a series of rectangles running from `(-.5,0)` to `(.5,1)` (well, it extends slightly above and below for the line caps). The `y`-coordinate is used to parametrise the arc and the `x`-coordinate goes across the curve. So the vertex shader needs to move these vertices into the right places and scale them accordingly.

``````void main()
{
``````

As said above, the `y` value is in the interval `[0,1]` except for the caps which extend slightly out, so `t` is the actual parameter along the curve.

``````    highp float t = clamp(position.y,0.,1.);
vCore = t;
``````

To compute the width, we need to interpolate between the starting and ending widths. Rather than a linear interpolation, we use `smoothstep` so that the width doesn't change much at the start and end of the arc. This means that if we put two arcs next to each other with matching widths then there won't be a noticeable change in how much the widths change.

``````    highp float w = smoothstep(0.,1.,t);
vWidth = w*ewidth + swidth;
``````

This contains the position along the curve.

``````    highp vec2 bpos = centre + cos(t*deltaAngle + startAngle) * xaxis + sin(t*deltaAngle + startAngle) * yaxis;
``````

To work out the thickness, we need to know the normal vector at this point on the curve. We start with the tangent vector.

``````    highp vec2 bdir = -sin(t*deltaAngle + startAngle) * xaxis + cos(t*deltaAngle + startAngle) * yaxis;
``````

Rotate it to get the normal vector.

``````    bdir = vec2(bdir.y,-bdir.x);
``````

Normalise, and multiply by the width and the `x`-coordinate of the vertex.

``````    bdir = vWidth*normalize(bdir);
bpos = bpos + position.x*bdir;
``````

Convert this to a `vec4` since that's what the shader needs to produce at the end.

``````    highp vec4 bzpos = vec4(bpos.x,bpos.y,0.,1.);
``````

The caps need special handling. This ensures that they poke out a bit from the ends of the arcs in the right directions.

``````    bzpos.xy += (ecapsw*max(position.y-1.,0.)
+scapsw*min(position.y,0.))*vec2(-bdir.y,bdir.x);
highp float s = clamp(position.y,
scapsw*position.y,1.+ecapsw*(position.y-1.));
vTexCoord = vec2(texCoord.x,s);
``````

This blends the colours.

``````    vColour = t*mcolour + scolour;
//Multiply the vertex position by our combined transform
gl_Position = modelViewProjection * bzpos;
}
]],[[
//
//
``````

Now the fragment shader, we need to know the `blur` and `cap` type.

``````uniform highp float blur;
uniform highp float cap;
``````

All the varyings we want from the vertex fragment.

``````varying highp vec2 vTexCoord;
varying highp float vWidth;
varying lowp vec4 vColour;
varying highp float vCore;

void main()
{
``````

``````    lowp vec4 col = vColour;
``````

This tells us where the edge should be.

``````    highp float edge = blur/(vWidth+blur);
``````

The alpha is modified to take into account the edge and the cap type.

``````    col.a = mix( 0., col.a,
(2.-cap)*smoothstep( 0., edge,
min(vTexCoord.x,1. - vTexCoord.x) )
* smoothstep( 0., edge,
min(1.5-vTexCoord.y, .5+vTexCoord.y) )
+ (cap - 1.)*smoothstep( 0., edge,
.5-length(vTexCoord - vec2(.5,vCore)))
);

gl_FragColor = col;
}
]])
``````

Back to the lua code, as said in the shader the arc is a mesh filling in the rectangle from `(-.5,0)` to `(.5,1)`. We use lots of rectangles to make the arc smooth.

``````    for n=1,nsteps do
end
return m
end
``````

There's a lot of flexibility in there which could be removed to speed it up a bit. One thing it doesn't do is fill in the arc: it is definitely an arc and not a segment of a circle.

• Posts: 381

@andrew_stacey: Thank you so much for the explanation. I'll play around with your code. Yes, I knew it draws an arc. That's why I asked for a guide how to make it into drawing a sector and segment. I still don't have a clear understanding about how to fill an area using shader. • Posts: 3,297

@bee the problem is that when you'll want to fill the area, you'll be back to your fps problem...

• Posts: 425

If you don't need to change the angle of your arc you could just draw it once with setContext()

• edited May 2014 Posts: 381

@jvm38: Of course. But I think it should be possible using OpenGL (as Codea use it as backend). Some games are able to draw thousands of meshes with acceptable speed, even on iPad1.

@coder: If I want to draw static arc, it wouldn't be this difficult. • Posts: 3,297

@bee the problem is not the number of sprites but the total number of screen pixels that have to be redrawn over and over again when you superpose many very large circles, as you want to do (if i've understood you correctly). Unless you use some additionnal tricks to skip drawing the hidden circle parts, you've already exausted the ipad gpu and you wont succed to be faster with 'brute force'. As you've said, codea mesh uses OpenGL, very directly i assume, so there not much to hope from a direct drawing approch. I say all this just because this is my current understanding of OpenGL, but i am not an expert, so i may be wrong.

• edited May 2014 Posts: 381

Then how those OpenGL-based games are able to draw hundreds even thousands of meshes so fast? Maybe we should summon the experts, @simeon and @john, to join our discussion and give us some enlightments. • Posts: 3,297

Can you tell which games draws hundred of full-screen-size meshes simultaneously on the screen? I am really puzzled. (i have drawn myself meshes with 60 000 vertex on the ipad1 with very good speed, but in practice each pixel of the screen would correspond to only 2 triangles max. Your case is very different, since the cicles do overlap and each covers a large fraction of the screen..).

• Posts: 2,161

@bee What I meant was that my mesh/shader approach draws the arc and only the arc. It doesn't cover the segment of the circle. The arc shader that comes with Codea, on the other hand, does draw the segment of the circle (though possibly with transparent pixels, which is one reason that it is so slow). On the other hand, it shouldn't be difficult to adapt my shader to draw the whole segment. Basically, the left-hand side of the curve should go to the centre point. That ought to do it.

• Posts: 1,976

@bee Most OpenGL-based games are made in Objective-C and OpenGL. Codea is much slower, probably mainly because Lua is a very slow language in general. It's not the standard, built-in programming language, it has a bunch of other complications. Objective-C is native, so it is much faster.

(Correct me if I'm wrong.)

• Posts: 381

@jmv38: Infinity Blade? EA Games' games? CMIIW.

@andrew_stacey: I can understand how to make a segment from an arc. What I don't quite understand is how to fill it. Especially to have an acceptable speed.

@skythecoder: Yes, I thought about it as well. I also read that trigonometry functions is very expensive. It's suggested to use lookup table instead of calculate it during runtime.

• Posts: 2,161

Hmm, it would be interesting to compare the arc code above (with sines and cosines) with a bezier approximation (since that only involves polynomials). I have code for both so I'll give it a go and report back.

With regard to filling the segment, that's what I was talking about. Simply send one edge of the rectangle to the centre and you're done.

• Posts: 3,297

@bee i have added a taper to smooth the circle edges for you. The visual result is no longer pixellized (but a bit fuzzy, of course!). Let me know how you like that one. Here is the code.

``````--# Main
-- Arc
-- normal: #arcs: 40 => fps: 19
-- sprite: #arcs: 90 => fps: 56

-- Use this function to perform your initial setup
function setup()
fps = 60
parameter.watch("math.floor(fps)")
parameter.watch("#arcs")
parameter.action("print",status)
arcs = {}
img = image(WIDTH/4,HEIGHT/4)
end
function status()
print("#arcs: "..tostring(#arcs) .." => fps: "..tostring(math.floor(fps)))
end
-- This function gets called once every frame
function draw()
k = 0.01
fps = fps*(1-k) + k/DeltaTime
-- This sets a dark background color

setContext(img)
background(255, 255, 255, 255)
scale(1/4)
for i, arc in ipairs(arcs) do
arc:draw()
end
setContext()
resetMatrix()
background(255, 255, 255, 255)
spriteMode(CORNER)
sprite(img,0,0,WIDTH,HEIGHT)
end

function touched(touch)
if touch.state == ENDED then
table.insert(arcs, Arc(touch.x, touch.y,WIDTH))
arcs[#arcs]:start()
end
end

vprog = [[
//
//

uniform mat4 modelViewProjection;

attribute vec4 position;
attribute vec2 texCoord;

varying highp vec2 vTexCoord;

void main() {
vTexCoord = texCoord;
gl_Position = modelViewProjection * position;
}

]],
fprog = [[
//
//

precision highp float;

uniform float size;
uniform float a1;
uniform float a2;
uniform float pixelSize;
uniform vec4 color;
const float da = 0.01;
varying vec2 vTexCoord;

void main() {
vec4 col = vec4(0.0);
vec2 r = vTexCoord - vec2(0.5);
float d = length(r);
float taper = 1.0;
if (d > size && d < 0.5) {
taper = 1.0 - smoothstep(0.5-pixelSize*4.0, 0.5, d);
taper = taper * smoothstep(size, size +pixelSize*4.0,d);
float a = atan(r.y, r.x);
if (a2 > a1) {
if (a > a1 && a < a2) {
col = color;
}
} else {
if (a > a1 || a < a2) {
col = color;
if (a > a1) {
taper = taper * smoothstep(a1, a1+0.01, a);
} else {
taper = taper * (1.0 - smoothstep(a2-0.01, a2, a));
}
}
}
}
gl_FragColor = col * vec4(1.0,1.0,1.0,taper);
}

]]
}

--# Timer
Arc = class()

function Arc:init(x, y, s, thick, col, t)
self.x = x or WIDTH / 2
self.y = y or HEIGHT / 2
self.size = s or WIDTH / 10
self.thick = thick or WIDTH / 10
self.time = t or 10
local ran = function() return math.random(255) end
self.color = col or color(ran(), ran(), ran(), 255)

self.paused = false
self.amnt = 0

self.tMesh = mesh()
self.tMesh.vertices = triangulate({vec2(-self.size / 2, -self.size / 2),
vec2(-self.size / 2, self.size / 2),
vec2(self.size / 2, self.size / 2),
vec2(self.size / 2, -self.size / 2)})

self.tMesh.texCoords = triangulate({vec2(0,0),vec2(0,1),vec2(1,1),vec2(1,0)})
end

function Arc:start()
self.amnt = 0
if self.timing == nil then
self.timing = tween(self.time, self, { amnt = 1 }, tween.easing.linear)
end
end

function Arc:pause()
if self.timing ~= nil then
tween.stop(self.timing)
end

self.paused = true
end

function Arc:resume()
if self.timing ~= nil then
tween.play(self.timing)
end

self.paused = false
end

function Arc:stop()
if self.timing ~= nil then
tween.stop(self.timing)
self.timing = nil
end
end

function Arc:restart()
self:stop()
self:start()
end

function Arc:draw()
-- Update timer
-- self.tMesh.shader.color = vec4(1 * self.amnt, 1 - (1 * self.amnt), 0, 1)
self.tMesh.shader.a2 = -self.amnt * (math.pi * 2) + math.pi

-- Draw timer
pushMatrix()

translate(self.x, self.y)

rotate(270.1)

self.tMesh:draw()

popMatrix()
end

``````
• Posts: 3,297 • edited May 2014 Posts: 3,297

There are still a couple defects in the 'almost horizontals' or verticals, to be removed with a larger angle deviation than 0.01 (or a better formula, because tha angle deviation does not correspond to a fixed distance).
.
the program below shows more clearly the weak region: actually, it is only for a1=pi that there is a problem. Just avoid this value.

• edited May 2014 Posts: 3,297
``````--# Main
-- Arc
-- normal: #arcs: 40 => fps: 19
-- sprite: #arcs: 90 => fps: 56

-- Use this function to perform your initial setup
function setup()
fps = 60
parameter.watch("math.floor(fps)")
parameter.watch("#arcs")
parameter.action("print",status)
arcs = {}
img = image(WIDTH/4,HEIGHT/4)
end
function status()
print("#arcs: "..tostring(#arcs) .." => fps: "..tostring(math.floor(fps)))
end
-- This function gets called once every frame
function draw()
k = 0.01
fps = fps*(1-k) + k/DeltaTime
-- This sets a dark background color

setContext(img)
background(255, 255, 255, 255)
scale(1/4)
for i, arc in ipairs(arcs) do
arc:draw()
end
setContext()
resetMatrix()
background(255, 255, 255, 255)
spriteMode(CORNER)
sprite(img,0,0,WIDTH,HEIGHT)
end

function touched(touch)
if touch.state == ENDED then
table.insert(arcs, Arc(touch.x, touch.y,WIDTH))
arcs[#arcs]:start()
end
end

vprog = [[
//
//

uniform mat4 modelViewProjection;

attribute vec4 position;
attribute vec2 texCoord;

varying highp vec2 vTexCoord;

void main() {
vTexCoord = texCoord;
gl_Position = modelViewProjection * position;
}

]],
fprog = [[
//
//

precision highp float;

uniform float size;
uniform float a1;
uniform float a2;
uniform float pixelSize;
uniform vec4 color;
const float da = 0.02;
varying vec2 vTexCoord;

void main() {
vec4 col = vec4(0.0);
vec2 r = vTexCoord - vec2(0.5);
float d = length(r);
float taper = 1.0;
if (d > size && d < 0.5) {
taper = 1.0 - smoothstep(0.5-pixelSize*4.0, 0.5, d);
taper = taper * smoothstep(size, size +pixelSize*4.0,d);
float a = atan(r.y, r.x);
if (a2 > a1) {
if (a > a1 && a < a2) {
col = color;
taper = taper * smoothstep(a1, a1+da, a);
taper = taper * (1.0 - smoothstep(a2-da, a2, a));
}
} else {
if (a > a1 || a < a2) {
col = color;
if (a > a1) {
taper = taper * smoothstep(a1, a1+da, a);
} else {
taper = taper * (1.0 - smoothstep(a2-da, a2, a));
// taper = 0.0;
}
}
}
}
gl_FragColor = col * vec4(1.0,1.0,1.0,taper);
}

]]
}

--# Timer
Arc = class()

function Arc:init(x, y, s, thick, col, t)
self.x = x or WIDTH / 2
self.y = y or HEIGHT / 2
self.size = s or WIDTH / 10
self.thick = thick or WIDTH / 10
self.time = t or 10
local ran = function() return math.random(255) end
self.color = col or color(ran(), ran(), ran(), 255)

self.paused = false
self.amnt = 0

self.tMesh = mesh()
self.tMesh.vertices = triangulate({vec2(-self.size / 2, -self.size / 2),
vec2(-self.size / 2, self.size / 2),
vec2(self.size / 2, self.size / 2),
vec2(self.size / 2, -self.size / 2)})

self.tMesh.texCoords = triangulate({vec2(0,0),vec2(0,1),vec2(1,1),vec2(1,0)})
end

function Arc:start()
self.amnt = 0
if self.timing == nil then
self.timing = tween(self.time, self, { amnt = 1 }, tween.easing.linear)
end
end

function Arc:pause()
if self.timing ~= nil then
tween.stop(self.timing)
end

self.paused = true
end

function Arc:resume()
if self.timing ~= nil then
tween.play(self.timing)
end

self.paused = false
end

function Arc:stop()
if self.timing ~= nil then
tween.stop(self.timing)
self.timing = nil
end
end

function Arc:restart()
self:stop()
self:start()
end

function Arc:draw()
-- Update timer
-- self.tMesh.shader.color = vec4(1 * self.amnt, 1 - (1 * self.amnt), 0, 1)
self.tMesh.shader.a2 = -self.amnt * (math.pi * 2) + math.pi

-- Draw timer
pushMatrix()

translate(self.x, self.y)

--   rotate(270.1)

self.tMesh:draw()

popMatrix()
end

``````
• Posts: 2,043

@Jmv38, that picture looks very nice, but on my iPad Mini (no retina display) it is still pixelated

• edited May 2014 Posts: 3,297

this is weird... Can you change the line

`````` self.tMesh.shader.pixelSize = 1/s
``````

by

`````` self.tMesh.shader.pixelSize = 1/s * 2
``````

to increase border by x2? Or try x3,x4 etc... it should look smooth with higher values.
If nothing changes, then that means the function `smoothstep` is not supported on ipad mini?

• edited May 2014 Posts: 381

@jmv38: I can confirm that your code is still produces pixelated arc on my iPad 1, as it's not retina as well. Increasing pixelSize makes the circle more blur, but sacrificing its edge sharpness. And the radius line is still pixelated, pixelSize doesn't help it.

I think this blurness and pixelation effect is because the image scaling. That's why I hesitated to use this technique since the beginning. Unless someone could show me a technique to minimize it.

• edited May 2014 Posts: 381

Btw, I'm still waiting for polinomial approach from @andrew_stacey. As it doesn't use expensive trigonometry functions, I hope it would be fast and still maintain beautiful look. *finger crossed* • Posts: 3,297

@bee some new info:
1/ i have checked on my ipad1: i cobfirm the image is pixellized.
2/ settling the pixelsize x3 gives a non-pixellized result, but smooth edge (not sharp).
3/ the straight lines are still pixellized, but this could be solved at the same cost as for circle: fuzziness of the border. Is it acceptable? (i wont do the work if you consider that x3 is too fuzzy).
4/ i think there is a solution for speed and sharpness: associate my method and andrew's. What i mean is:
4.a/ draw the surface with my method (and forget the sahder, just make pixellized edges). This will fill the surfaces very quickly.
4.b/ on top of that, draw the edges with andrew segment shader. This will be perfectly sharp and very quick because only a few pixels will be updated.
But this is much work. Is it worth it? It depends on why you want these circles for (you've not explained it in details).

• edited May 2014 Posts: 381

@andrew_stacey: I just noticed that your code also doesn't anti-aliased the radius line (the straight line). I didn't notice it before because my test program use ROUND linecap as the default. Pardon my lack of understanding of shader, but how did you draw the round cap? Which line is it in the code? Any hint how to anti-alias the line as well?

What do you mean by…

The idea of the shader is that the mesh is a series of rectangles running from (-.5,0) to (.5,1)

Why .5? I still don't get it. It seems that each mesh has its own coordinate within it. Am I right? Do you have any reference to study about this openGL shader things? Codea's doc about shader doesn't give me enough informations. Or perhaps I didn't read enough.

@jmv38: I don't really like the idea of image scaling. Yes, it boosts the performance, but I don't quite like the result. It's too blurry. CMIIW. If you think the work is too much, then don't bother to write code sample for me. I just need hints and direction how to do it, let me write the code myself, if you mind to provide code sample. Your explanation above is quite clear to me. Thank you.

My biggest problem here –as I said since the beginning– is with the shader. I still don't really understand how does the shader work with mesh and how to draw using shader. What's uniform? What's varying? Etc. Some of its functions are a bit different from Lua's counter part. It's like a whole different alien world separated from Codea.

• Posts: 3,297

@bee thanks for the feedback. I'll see if i can do something to get really sharp edges, but no garantee.

• Posts: 3,297

i have tried 4.a + 4.b but it doesnt work: the speed is ok, but the edges are not correctly masked.. (

• Posts: 3,297

just because it is nice visually, here is what happens: • edited May 2014 Posts: 381

I found link to @ignatz's shader tutorial ebook by searching this forum. Unfortunately, the link that refers to the file in Ignatz's dropbox is no longer active. I can read some articles about shaders in Ignatz's blog. But I want the ebook so I can read it offline. If someone here got the ebook, would you please reshare it for us here? Thank you.

• edited May 2014 Posts: 5,396

@bee - Oops, I moved some files and broke the links

https://www.dropbox.com/sh/mr2yzp07vffskxt/AACqVnmzpAKOkNDWENPmN4psa

• edited May 2014 Posts: 381

Thank you, @ignatz. Btw, can you help me with this arc problem? • Posts: 5,396

@bee - sorry, I'm not very active in Codea any more, so I can't help with the arc problem (also, I suspect it involves a lot of math!)

• Posts: 2,161

@Ignatz

sorry, I'm not very active in Codea any more

Only temporarily, I hope.

• Posts: 3,297

@ignatz have you another hobby now?