Benchmarks & Optimisations

Here's the source for my benchmarking app: bench.zip (8.3 KB)

runs at 50fps by default

edit: 2020-12-19, added

  • fSpriteSetVisible,

edit: 2020-10-27, added

  • fSpriteSetImage,
  • fSpriteSetCenterStatic,
  • fSpriteSetCenterToggle,
  • fSpriteSetCenterRandom,

Code improvements accepted!

import "CoreLibs/graphics"
import "CoreLibs/sprites"
import "CoreLibs/timer"

local gfx <const> = playdate.graphics
local format <const> = string.format
local wait <const> = playdate.wait
local update <const> = playdate.graphics.sprite.update
local updateTimers <const> = playdate.timer.updateTimers
local cos <const> = math.cos
local sin <const> = math.sin
local floor <const> = math.floor
local random <const> = math.random
local drawLine <const> = playdate.graphics.drawLine
local wait <const> = playdate.wait
local drawText <const> = playdate.graphics.drawText
local drawTextInRect <const> = playdate.graphics.drawTextInRect
local drawRect <const> = playdate.graphics.drawRect
local fillRect <const> = playdate.graphics.fillRect
local drawCircleAtPoint <const> = playdate.graphics.drawCircleAtPoint
local fillCircleAtPoint <const> = playdate.graphics.fillCircleAtPoint
local drawCircleInRect <const> = playdate.graphics.drawCircleInRect
local fillCircleInRect <const> = playdate.graphics.fillCircleInRect
local lockFocus <const> = playdate.graphics.lockFocus
local unlockFocus <const> = playdate.graphics.unlockFocus
local pushContext <const> = playdate.graphics.pushContext
local popContext <const> = playdate.graphics.popContext

local now = playdate.getCurrentTimeMilliseconds

math.randomseed(0)    -- same random numbers every time
local function rnd(x)
    return random(0,x)
end

local playerSprite = nil
local FPS = 50
local frameMS = 1000/FPS
playdate.display.setRefreshRate(FPS)

if playdate.isSimulator then
    location = "SIMULATOR"
else
    location = "DEVICE"
end

local W <const> = playdate.display.getWidth()
local H <const> = playdate.display.getHeight()
local CW <const> = W/2
local CH <const> = H/2

local start = 0
local count = 0
local cmd = 1
local runs = 5
local cmdName = ""
local done = false
local testImage <const> = gfx.image.new( "Images/background" )
local testBack = gfx.image.new( "Images/background" )
local testSprite <const> = gfx.image.new("Images/playerImage")

local function fNil() end
local function fDrawLineDiagonal() drawLine(0, 0, W, H) return "drawLine - Diagonal" end
local function fDrawLineHorizontal() drawLine(0, CH, W, CH) return "drawLine - Horizontal" end
local function fDrawLineVertical() drawLine(CW, 0, CW, H) return "drawLine - Vertical" end
local function fDrawLineRandomDiagonal() drawLine(0, rnd(H), W, rnd(H)) return "drawLine - Random Diagonal" end
local function fDrawLineFillRect() fillRect(0, CH, W, 1) return "drawLine - fillRect" end
local function fDrawLineDrawRect() drawRect(0, CH, W, 1) return "drawLine - drawRect" end
local function fMathRandom() math.random(0,999) return "math.random" end
local function fMathRandomLocal() random(0,999) return "math.random - local" end
local function fMathSin() sin(90) return "math.sin" end
local function fMathSinRandom() sin(rnd(359)) return "math.sin - random" end
local function fMathCos() cos(90) return "math.cos" end
local function fMathCosRandom() cos(rnd(359)) return "math.cos - random" end
local function fMathFloor() floor(1.23) return "math.floor - local" end
local function fImageSample() testImage:sample(0, 0) return "image:sample" end
local function fDrawText() drawText("TEST!", CW-22, CH+50) return "drawText - local" end
local function fDrawTextInRect() drawTextInRect("TEST!", CW-25, CH+50, 50, 50, nil, nil, kTextAlignment.center) return "drawTextInRect" end
local function fDrawRect() drawRect(CW-50, CH-50, 100, 100) return "drawRect" end
local function fFillRect() fillRect(CW-50, CH-50, 100, 100) return "fillRect" end
local function fDrawCircleAtPoint() drawCircleAtPoint(CW, CH, 100) return "drawCircleAtPoint" end
local function fFillCircleAtPoint() fillCircleAtPoint(CW, CH, 100) return "fillCircleAtPoint" end
local function fDrawCircleInRect() drawCircleInRect(CW-50, CH-50, 100, 100) return "drawCircleInRect" end
local function fFillCircleInRect() fillCircleInRect(CW-50, CH-50, 100, 100) return "fillCircleInRect" end
local function fSpriteMoveToStatic() playerSprite:setVisible(true) playerSprite:moveTo(CW, CH) return "sprite:moveTo - static" end
local function fSpriteMoveToRandom() playerSprite:setVisible(true) playerSprite:moveTo(rnd(W), rnd(H)) return "sprite:moveTo - random" end
local function fSpriteSetImage() playerSprite:setImage( testSprite ) return "sprite:setImage" end
local function fSpriteSetCenterStatic() playerSprite:setCenter(0, 0) return "sprite:setCenter - static" end
local function fSpriteSetCenterToggle() playerSprite:setCenter(playerSprite.x and 400 or 0, 0) return "sprite:setCenter - toggle" end
local function fSpriteSetCenterRandom() playerSprite:setCenter(rnd(399), 0) return "sprite:setCenter - random" end
local function fSpriteSetZIndex() playerSprite:setZIndex(1) return "sprite:setZIndex" end
local function fDraw() testSprite:draw(0,0) return "image:draw" end
local function fDrawLocked() gfx.lockFocus(testBack) testSprite:draw(0,0) gfx.unlockFocus() return "image:draw - locked" end
local function fDrawLockedLocal() lockFocus(testBack) testSprite:draw(0,0) unlockFocus() return "image:draw - locked local" end
local function fDrawPushContext() pushContext(testBack) testSprite:draw(0,0) popContext() return "image:draw - pushcontext local" end
-- local function () return "" end

funcs = {
    fNil,
    fDrawLineDiagonal,
    fDrawLineHorizontal,
    fDrawLineVertical,
    fDrawLineRandomDiagonal,
    fDrawLineFillRect,
    fDrawLineDrawRect,
    fMathRandom,
    fMathRandomLocal,
    fMathSin,
    fMathSinRandom,
    fMathCos,
    fMathCosRandom,
    fMathFloor,
    fImageSample,
    fDrawText,
    fDrawTextInRect,
    fDrawRect,
    fFillRect,
    fDrawCircleAtPoint,
    fFillCircleAtPoint,
    fDrawCircleInRect,
    fFillCircleInRect,
    fSpriteMoveToStatic,
    fSpriteMoveToRandom,
    fSpriteSetImage,
    fSpriteSetCenterStatic,
    fSpriteSetCenterToggle,
    fSpriteSetCenterRandom,
    fSpriteSetZIndex,
    fDraw,
    fDrawLocked,
    fDrawLockedLocal,
    fDrawPushContext,
}
local max = #funcs

function myGameSetUp()

    local playerImage = gfx.image.new("Images/playerImage")
    assert( playerImage ) -- make sure the image was where we thought

    playerSprite = gfx.sprite.new()
    playerSprite:setImage( playerImage )
    playerSprite:setCenter( 0.5, 0.5 )
    playerSprite:moveTo( CW, CH )
    playerSprite:setVisible(false)
    playerSprite:add() -- This is critical!

    local backgroundImage = gfx.image.new( "Images/background" )
    assert( backgroundImage )
    lockFocus(backgroundImage)
    playdate.graphics.drawTextInRect("*BENCH*", CW-50, CH-7, 100, 50, nil, nil, kTextAlignment.center)
    unlockFocus(backgroundImage)

    gfx.sprite.setBackgroundDrawingCallback(
        function( x, y, width, height )
            gfx.setClipRect( x, y, width, height ) -- just draw what we need
            backgroundImage:draw( 0, 0 )
            gfx.clearClipRect()
        end
    )
end

myGameSetUp()

function playdate.gameWillResume()
    cmd = 1
    start = 0
    wait(300)
end

function playdate.update()
    -- playdate.graphics.sprite.update()
    -- playdate.timer.updateTimers()
    update()
    updateTimers()

    if start == 0 then
        start = now()
    end

    if cmd == 1 then
        print(location.." ("..runs.." RUN AVE)\n")
        print("#,"," BENCH,","CALL")
    end

    -- run command for a frame worth of milliseconds
    if start > 0 and cmd > 0 and cmd <= max then -- approx calls: 104000 sim, 3000 device
        for i = 1,runs do
            while now() < start+frameMS do
                cmdName = funcs[cmd]()

                count = count + 1
            end
            start = now()
        end
        done = true
    end

    if done == true then
        print(format("%02d,\t%6d,", cmd, count//runs), cmdName)

        done = false
        count = 0
        start = 0
        cmd = cmd + 1
    end

    if cmd == max+1 then
        print("\nEND")
        cmd = cmd + 1
        playerSprite:setVisible(false)
        playdate.display.setRefreshRate(30)
    end
end
5 Likes