@Ignatz it’s your rotation code rotating your mesh and using your texture-drawing code, and it works great, so I’d say you got some things right!
Update: still working on this, making incremental improvements. Thanks to @Ignatz, I am displaying multiple meshes that I can draw on with my finger. Frame rate is really low, but I’m working on it.
I’m surprised your frame rate is low, unless it’s been low all along. Is it related to the number of meshes, or anything you can identify?
This is why I was asking about the number of vertices. You can check it quite easily, just find the part of the code where the vertices table is assigned m.vertices = verts
or whatever and add print("vertices:", #verts)
.
If you have too many, you can lower the poly count in Blender with a decimate modifier, or by removing parts of the mesh that you can’t see (eg sometimes you see character rigs with inner mouths, teeth etc, for doing facial animation, which you probably don’t need)
Or m.size
@Ignatz, @Yojimbo: there has to be something more fundamental I’m doing wrong, because I’m seeing frame rates around 15fps while simply displaying two textured squares. Which is, what, eight vertices total?
I’m doing a bit of ground-up re-inspection of my code to try and find the problem.
I will say this though: math.floor() seems to realllly slow stuff down.
I doubt it’s math.floor. You could try integer divide //1
We need more detail. Can you post some code, or pseudo code, to give us an idea of the work Codea is doing?
PS a square is 6 vertices (2 triangles), so two of them is 12 vertices. You should have no problem drawing thousands at 60 FPS, so something else is causing it (and I doubt it’s math.floor).
The code is pretty much the same as the code I already posted above, but I do want to do a little more digging of my own before I post the current state of it. I think, after all, @Yojimbo2000 may have hit the nail on the head in warning me about mixing up 2-D and 3-D rendering orders
Yes, you should do your 2D drawing before or after 3D
@Ignatz I may be measuring FPS wrong. I grabbed the code I’m using from your tilt-racer project. I may be deploying it wrong.
--globals.FPS defined as 60 in setup
function draw()
backingMode(STANDARD)
background(69, 119, 104, 255)
globals.FPS=globals.FPS*0.9+.1/DeltaTime
text("FPS: "..math.floor(globals.FPS),25,HEIGHT-25)
end
With nothing else going on at all, it shows FPS 15 or 16 most of the time. I must be doing it wrong.
I added this profiler to the drawing to texture code above:
profiler={}
function profiler.init(silent)
profiler.del=0
profiler.c=0
profiler.fps=0
profiler.mem=0
if not silent then
parameter.watch("profiler.fps")
parameter.watch("profiler.mem")
end
end
function profiler.draw()
profiler.del = profiler.del + DeltaTime
profiler.c = profiler.c + 1
if profiler.c==10 then
profiler.fps=profiler.c/profiler.del
profiler.del=0
profiler.c=0
profiler.mem=collectgarbage("count", 2)
end
end
I got 45 fps when not touching, dropping to around 22-25 fps when touching. I’m on an iPad Air. That does seem way, way lower than it should be. You really want this running at 60 fps.
Also, in the code above, it seems as if you have separated the 2d and 3d drawing?
I haven’t fully pulled this apart, but based on what I’ve done previously with drawing to offscreen buffers (eg the Gaussian blur shader I ported), I would make the offscreen buffer image as small as you can make it. Given that the touch event only has an accuracy of 0-255, overlayImage does not need to be 2000x1400 pixels, does it? (remember that Codea automatically converts all pixels to retina coordinates). It should just be 255 by 255. So when you draw ellipses to it, divide the coords of the touch by 4. (Or, given the retina-ization, 128x128, and divide by 8)?
Also, why is the buffer image WIDTH x HEIGHT if you only draw to a 10x10 section of it? It could just be 10x10 pixels.
Is there any need to clip? Just make the image the size you need and translate.
With the Gaussian blur shader, quartering the size of the offscreen buffer made a big difference to the speed
Here’s a version that quarters the size of the two buffers. When not touching, it’s still 45 fps, but it doesn’t drop quite so much when you do touch. Not as big a saving as I’d hoped for. The overlay image can probably go down to /8, although the overlay sprite would look a bit weird.
-- MakePlane
function setup()
textMode(CORNER)
-- noStroke()
setUpTests()
makePlane = MakePlane()
texture = makePlane.plane.mesh.texture
colorCoordinates = {}
overlayImage = image(WIDTH/4, HEIGHT/4)
setUpBufferDisplay()
profiler.init()
end
function setUpBufferDisplay()
bufferImage = image(WIDTH/4, HEIGHT/4)
local bufferFract = 1 / 6
cameoSize = vec2(WIDTH * bufferFract, HEIGHT * bufferFract)
camoPos = vec2((cameoSize.x/2)+20, (cameoSize.y/2)+20)
setContext(bufferImage) --fill image with yellow for visibility
strokeWidth(0)
fill(220, 210, 122, 255)
rect(0,0,WIDTH,HEIGHT)
setContext()
end
function setUpTests()
local parameterTable = {} --series of buttons to confirm visuals
local assignableAction = function() end
local nextParameter = function ()
parameter.clear()
if #parameterTable == 0 then
return
end
local testDefinition = parameterTable[1]
local passAction = function()
print(testDefinition..": PASS")
assignableAction()
end
local failAction = function()
print(testDefinition..": FAIL")
assignableAction()
end
parameter.action("PASS if "..testDefinition, passAction)
parameter.action("FAIL", failAction)
table.remove(parameterTable, 1)
end
assignableAction = nextParameter
parameterTable = {"plane is showing", "touching draws red dots", "dragging leaves trail", "touch activates shader", "buffer image in corner", "clipping drawn in buffer", "mesh clips in buffer", "buffer perspective matches", "buffer coordinates read", "touch draws to texture", "draws in right place", "all touches draw"}
nextParameter()
end
function draw()
backingMode(STANDARD)
background(69, 119, 104, 255)
drawPlane()
draw2D()
profiler.draw()
end
function drawPlane()
pushMatrix()
makePlane:draw()
popMatrix()
end
function draw2D()
ortho()
viewMatrix(matrix())
-- sprite(overlayImage, WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
sprite(bufferImage, camoPos.x, camoPos.y, cameoSize.x, cameoSize.y)
if #colorCoordinates > 0 then
drawTouchesToTexture()
end
end
function drawTouchesToTexture()
local textureX, textureY, baseX, baseY, marker, intCoord, fX, fY
textureX, textureY = 0, 0
for i in ipairs(colorCoordinates) do
coord = colorCoordinates[i] * 0.25
intCoord = vec2(math.floor(coord.x), math.floor(coord.y))
baseX, baseY, marker = bufferImage:get(intCoord.x, intCoord.y)
if marker == 1 then
fX, fY = baseX / 255, baseY / 255
textureX = texture.width * fX
textureY = texture.height * fY
setContext(texture)
fill(0, 43, 255, 255)
ellipse(textureX, textureY, 25)
setContext()
end
end
text("x: "..textureX..", y: "..math.floor(textureY, 10, 10))
colorCoordinates = {}
end
function touched(touch)
setContext(overlayImage)
fill(255, 4, 0, 255)
ellipse(touch.x/4, touch.y/4, 3)
setContext()
if touch.state == MOVING then
toggleShader(true)
drawClipToBufferPosition(touch)
table.insert(colorCoordinates, vec2(touch.x, touch.y))
else
toggleShader(false)
end
end
function drawClipToBufferPosition(touch)
setContext(bufferImage)
pushMatrix()
clip((touch.x*0.25) - 10, (touch.y*0.25) - 10, 20, 20)
makePlane:draw()
popMatrix()
setContext()
end
function toggleShader(onOrOff)
if onOrOff == true then
local planeShader = shader(PlaneShader.v, PlaneShader.f)
makePlane.plane.mesh.shader = planeShader
else
makePlane.plane.mesh.shader = nil
end
end
profiler={}
function profiler.init(silent)
profiler.del=0
profiler.c=0
profiler.fps=0
profiler.mem=0
if not silent then
parameter.watch("profiler.fps")
parameter.watch("profiler.mem")
end
end
function profiler.draw()
profiler.del = profiler.del + DeltaTime
profiler.c = profiler.c + 1
if profiler.c==10 then
profiler.fps=profiler.c/profiler.del
profiler.del=0
profiler.c=0
profiler.mem=collectgarbage("count", 2)
end
end
Ok, I see what you mean about not separating 2d and 3d draws. So when you touch, you actually set up the camera twice, draw the plane twice etc, hence the frame rate dropping. I guess it’s tricky because you need continuous touch events as your finger moves
Bingo, I got it. For some reason, it is backingMode(STANDARD)
in the draw loop. Comment that out, and for some reason, fps jumps up to 60. Here’s a version that tries to do all the 3d drawing in one go. I don’t think it makes much difference though, as you need to re-establish the camera when you setContext to a different image anyway. It also gets rid of the colorCoordinates table loop (as it only ever contains one value? Were you planning on implementing multi-touch later?), to try to speed things up a bit. The fps stays at 60 fps though, regardless of whether you’re touching. So, quarter-size buffers (smaller is possible) and not setting the backing mode each frame triple the running speed, and only allowing for a single touch seems to help too.
And the overlay image is just there for testing isn’t it? So you could get rid of that in the final version.
Edit (tidied up the line drawing). Sorry I left the code in such a messy state. But feel that speed!
--# Main
-- MakePlane
function setup()
textMode(CORNER)
planeShader = shader(PlaneShader.v, PlaneShader.f)
-- noStroke()
setUpTests()
makePlane = MakePlane()
texture = makePlane.plane.mesh.texture
colorCoordinates = {}
overlayImage = image(WIDTH/4, HEIGHT/4)
setUpBufferDisplay()
profiler.init()
end
function setUpBufferDisplay()
bufferImage = image(WIDTH/4, HEIGHT/4)
local bufferFract = 1 / 6
cameoSize = vec2(WIDTH * bufferFract, HEIGHT * bufferFract)
camoPos = vec2((cameoSize.x/2)+20, (cameoSize.y/2)+20)
setContext(bufferImage) --fill image with yellow for visibility
strokeWidth(0)
fill(220, 210, 122, 255)
rect(0,0,WIDTH,HEIGHT)
setContext()
end
function setUpTests()
local parameterTable = {} --series of buttons to confirm visuals
local assignableAction = function() end
local nextParameter = function ()
parameter.clear()
if #parameterTable == 0 then
return
end
local testDefinition = parameterTable[1]
local passAction = function()
print(testDefinition..": PASS")
assignableAction()
end
local failAction = function()
print(testDefinition..": FAIL")
assignableAction()
end
parameter.action("PASS if "..testDefinition, passAction)
parameter.action("FAIL", failAction)
table.remove(parameterTable, 1)
end
assignableAction = nextParameter
parameterTable = {"plane is showing", "touching draws red dots", "dragging leaves trail", "touch activates shader", "buffer image in corner", "clipping drawn in buffer", "mesh clips in buffer", "buffer perspective matches", "buffer coordinates read", "touch draws to texture", "draws in right place", "all touches draw"}
nextParameter()
end
function draw()
-- backingMode(STANDARD)
background(69, 119, 104, 255)
drawPlane()
draw2D()
profiler.draw()
end
function drawPlane()
pushMatrix()
-- makePlane:setupPerspective()
if touching then
toggleShader(true)
drawClipToBufferPosition(touching)
toggleShader(false)
-- touching = nil
end
-- pushMatrix()
makePlane:draw()
popMatrix()
end
function draw2D()
ortho()
viewMatrix(matrix())
sprite(overlayImage, WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
sprite(bufferImage, camoPos.x, camoPos.y, cameoSize.x, cameoSize.y)
-- if #colorCoordinates > 0 then
if touching then
drawTouchesToTexture()
touching = nil
end
end
function drawTouchesToTexture()
local textureX, textureY, baseX, baseY, marker, intCoord, fX, fY
textureX, textureY = 0, 0
-- for i in ipairs(colorCoordinates) do
-- coord = colorCoordinates[i] * 0.25
coord = touching * 0.25
intCoord = vec2(math.floor(coord.x), math.floor(coord.y))
baseX, baseY, marker = bufferImage:get(intCoord.x, intCoord.y)
if marker == 1 then
fX, fY = baseX / 255, baseY / 255
textureX = texture.width * fX
textureY = texture.height * fY
lastTexX = lastTexX or textureX
lastTexY = lastTexY or textureY
setContext(texture)
stroke(0, 43, 255, 255)
strokeWidth(25)
line(textureX, textureY, lastTexX, lastTexY)
-- ellipse(textureX, textureY, 25)
setContext()
lastTexX, lastTexY = textureX, textureY
end
-- end
-- text("x: "..textureX..", y: "..math.floor(textureY, 10, 10))
-- end
-- colorCoordinates = {}
end
function touched(touch)
setContext(overlayImage)
fill(255, 4, 0, 255)
ellipse(touch.x/4, touch.y/4, 3)
setContext()
touching = vec2(touch.x, touch.y)
if touch.state == ENDED then
touching, lastTexX, lastTexY = nil,nil, nil
end
--[[
if touch.state == MOVING then
touching = vec2(touch.x, touch.y)
-- table.insert(colorCoordinates, vec2(touch.x, touch.y))
else
toggleShader(false)
end
]]
end
function drawClipToBufferPosition(touch)
setContext(bufferImage)
pushMatrix()
clip((touch.x*0.25) - 10, (touch.y*0.25) - 10, 20, 20)
makePlane:draw()
clip()
popMatrix()
setContext()
end
function toggleShader(onOrOff)
if onOrOff == true then
makePlane.plane.mesh.shader = planeShader
else
makePlane.plane.mesh.shader = nil
end
end
profiler={}
function profiler.init(silent)
profiler.del=0
profiler.c=0
profiler.fps=0
profiler.mem=0
if not silent then
parameter.watch("profiler.fps")
parameter.watch("profiler.mem")
end
end
function profiler.draw()
profiler.del = profiler.del + DeltaTime
profiler.c = profiler.c + 1
if profiler.c==10 then
profiler.fps=profiler.c/profiler.del
profiler.del=0
profiler.c=0
profiler.mem=collectgarbage("count", 2)
end
end
--# MakePlane
MakePlane = class()
function MakePlane:init()
self:createSimpleMesh()
end
function MakePlane:draw()
self:setupPerspective()
self.plane.mesh:draw()
end
function MakePlane:createSimpleMesh()
--settings for our rectangle (named "plane" below)
self.plane={}
self.plane.centre=vec3(0,0,0)
self.plane.rotate=vec3(45,0,20)
self.plane.size=vec2(500,500,0)
strokeWidth(10)
stroke(255, 0, 0, 255)
--set up mesh, needed for shader
self.plane.mesh=mesh()
self.plane.mesh.texture = readImage("SpaceCute:Background")
local s=self.plane.size
local x1,y1,x2,y2,z=-s.x/2,-s.y/2,s.x/2,s.y/2,s.z
self.plane.mesh.vertices={vec3(x1,y1,z),vec3(x2,y1,z),vec3(x2,y2,z),vec3(x2,y2,z),vec3(x1,y2,z),vec3(x1,y1,z)}
self.plane.mesh.texCoords={vec2(0,0),vec2(1,0),vec2(1,1),vec2(1,1),vec2(0,1),vec2(0,0)}
self.plane.mesh:setColors(color(255,255,0))
self.img=image(WIDTH, HEIGHT)
self.shaderImg=image(WIDTH, HEIGHT)
end
function MakePlane:setupPerspective()
perspective()
camera(0,0,900,0,0,-1)
translate(self.plane.centre.x, self.plane.centre.y, self.plane.centre.z)
rotate(self.plane.rotate.x,1,0,0)
rotate(self.plane.rotate.y,0,1,0)
rotate(self.plane.rotate.z,0,0,1)
end
PlaneShader = {
v = [[
uniform mat4 modelViewProjection;
attribute vec4 position;
attribute vec2 texCoord;
varying highp vec2 vTexCoord;
void main()
{
vTexCoord = texCoord;
gl_Position = modelViewProjection * position;
}
]],
f = [[
precision highp float;
varying highp vec2 vTexCoord;
void main()
{
lowp float marker = 1.0 /255.0;
gl_FragColor = vec4(vTexCoord.x,vTexCoord.y,marker,1.0);
}
]]}