Jitter when creating images in love.load

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
wilco
Prole
Posts: 11
Joined: Sun Jun 21, 2020 8:51 am

Jitter when creating images in love.load

I'm new to löve and was trying to create a simple infinite scrolling background. While the code works, the scrolling was very jittery. After some testing/debugging I found that the problem was that I created the images (newImage()) inside of the love.load() function. When creating the images outside of the love.load() the scrolling is very smooth.

Now I'm trying to understand why this is happening. To my understanding the love.load() runs only once at the start up of the game. So why is there a difference to the 2 approaches?

Also what is the best place/way to instantiate new assets like images?

Code that jitters:
Uses local variables outside of the love.load() and then assigns the images inside the love.load() function to these variables.

Code: Select all

-- globals start
WIDTH = 512
HEIGHT = 288
-- globals end

local background
local ground
local pipe

local PIPE_WIDTH

local BACKGROUND_SCROLL_SPEED = 30
local GROUND_SCROLL_SPEED = 60
local BACKGROUND_LOOPING_POINT = 413

local backgroundX = 0
local groundX = 0
local pipeX = WIDTH

love.graphics.setDefaultFilter('nearest', 'nearest')
love.window.setMode(WIDTH * 2, HEIGHT * 2, {
vsync = 1
})

background = love.graphics.newImage('assets/background.png')
ground = love.graphics.newImage('assets/ground.png')
pipe = love.graphics.newImage('assets/pipe.png')

PIPE_WIDTH = pipe:getWidth()
end

function love.update(dt)
backgroundX = (backgroundX + BACKGROUND_SCROLL_SPEED * dt) % BACKGROUND_LOOPING_POINT
groundX = (groundX + GROUND_SCROLL_SPEED * dt) % WIDTH

pipeX = pipeX - GROUND_SCROLL_SPEED * dt
if (pipeX < -PIPE_WIDTH) then
pipeX = WIDTH
end
end

function love.draw()
love.graphics.push()
love.graphics.scale(2, 2)

love.graphics.draw(background, -backgroundX, 0)
love.graphics.draw(pipe, pipeX, HEIGHT / 2)
love.graphics.draw(ground, -groundX, HEIGHT-16)

love.graphics.pop()
end


Code that runs smooth:
Uses local variables outside of the love.load() and immediately assigns the images to these variables.

Code: Select all

-- globals start
WIDTH = 512
HEIGHT = 288
-- globals end

local background = love.graphics.newImage('assets/background.png')
local ground = love.graphics.newImage('assets/ground.png')
local pipe = love.graphics.newImage('assets/pipe.png')

local PIPE_WIDTH = pipe:getWidth()

local BACKGROUND_SCROLL_SPEED = 30
local GROUND_SCROLL_SPEED = 60
local BACKGROUND_LOOPING_POINT = 413

local backgroundX = 0
local groundX = 0
local pipeX = WIDTH

love.graphics.setDefaultFilter('nearest', 'nearest')
love.window.setMode(WIDTH * 2, HEIGHT * 2, {
vsync = 1
})
end

function love.update(dt)
backgroundX = (backgroundX + BACKGROUND_SCROLL_SPEED * dt) % BACKGROUND_LOOPING_POINT
groundX = (groundX + GROUND_SCROLL_SPEED * dt) % WIDTH

pipeX = pipeX - GROUND_SCROLL_SPEED * dt
if (pipeX < -PIPE_WIDTH) then
pipeX = WIDTH
end
end

function love.draw()
love.graphics.push()
love.graphics.scale(2, 2)

love.graphics.draw(background, -backgroundX, 0)
love.graphics.draw(pipe, pipeX, HEIGHT / 2)
love.graphics.draw(ground, -groundX, HEIGHT-16)

love.graphics.pop()
end

Images I used:
https://github.com/games50/fifty-bird/b ... ground.png
https://github.com/games50/fifty-bird/b ... 2/pipe.png
https://github.com/games50/fifty-bird/b ... ground.png
Last edited by wilco on Sun Jun 21, 2020 3:31 pm, edited 3 times in total.

hoistbypetard
Prole
Posts: 26
Joined: Fri May 22, 2020 7:00 pm

Re: Jitter when creating images in love.load

wilco wrote:
Sun Jun 21, 2020 11:04 am
Also what is the best place/way to instantiate new assets like images?
I have no idea why those two work differently. (I didn't test your code, just taking your word for it.) I hope someone smarter weighs in on that.

That said, my approach is usually most similar to the one that works without jitter for you. I tend to have some resource manager *thing* (not always a class but generally its own file) and load it at the top of my main.lua file using require().

Usually i have it populate any background images i need, a table of sprite atlases, quads for those, and a table of sound effects/music. It does all that work during the require() call.

pgimeno
Party member
Posts: 2211
Joined: Sun Oct 18, 2015 2:58 pm

Re: Jitter when creating images in love.load

Can you really reproduce the issue with these code snippets? Both work the same for me.

wilco
Prole
Posts: 11
Joined: Sun Jun 21, 2020 8:51 am

Re: Jitter when creating images in love.load

pgimeno wrote:
Sun Jun 21, 2020 3:00 pm
Can you really reproduce the issue with these code snippets? Both work the same for me.
Yes tried it at least 20 times and it consistently jittered in the "jitter example". It can take some time for the jitter to appear and it can be very subtle and hard to spot. Other times it shows immediately or is clearly visible, but this is usually not the case. It jitters for a few seconds and then runs fine again for some time. Usually it happens at least once within 4 loops of the pipe.

The easiest way to spot it is with the "floor" and the "pipe". They should travel at the same speed, but at certain points the floor or the pipe seems to jitter horizontally while the other does not.

It could maybe also be a combination of operating system, löve version and my code?
I'm using Macos 10.14.6 with löve 11.3.
Needed to updated to Macos 10.14.6 because I was having some problems with löve 11.3, like unlimited framerate.

Images I used:
https://github.com/games50/fifty-bird/b ... ground.png
https://github.com/games50/fifty-bird/b ... 2/pipe.png
https://github.com/games50/fifty-bird/b ... ground.png
Last edited by wilco on Sun Jun 21, 2020 3:30 pm, edited 3 times in total.

wilco
Prole
Posts: 11
Joined: Sun Jun 21, 2020 8:51 am

Re: Jitter when creating images in love.load

hoistbypetard wrote:
Sun Jun 21, 2020 12:59 pm
Usually i have it populate any background images i need, a table of sprite atlases, quads for those, and a table of sound effects/music. It does all that work during the require() call.
Thanks, this seems to be working fine for me as well.

wilco
Prole
Posts: 11
Joined: Sun Jun 21, 2020 8:51 am

Re: Jitter when creating images in love.load

Ok... kinda feeling stupid right now

It of course has nothing to do with the love.load() function and everything to do with the love.graphics.setDefaultFilter('nearest', 'nearest') which is not applied retroactively to loaded images. So the jittery example has it's image filtering set to 'nearest' while the smooth example has it's image filtering set to the default value...

So the problem is with the image filtering being set to 'nearest' and probably rounding errors...

nameless tee
Prole
Posts: 12
Joined: Mon Apr 22, 2019 8:16 am

Re: Jitter when creating images in love.load

The "real" problem with your code is that the movement of the ground and the pipes isn't precisely tied together. The nearest filter just makes it more visible.

For updating of pipeX you use:

Code: Select all

pipeX = pipeX - GROUND_SCROLL_SPEED * dt
if (pipeX < -PIPE_WIDTH) then
pipeX = WIDTH
end

But pipeX is -PIPE_WIDTH-some_fraction where some_fraction depends on the somewhat random size of the time deltas, dt. This causes pipeX to shift by some_fraction relative to the ground every time it loops around.

You can fix that by instead using:

Code: Select all

pipeX = pipeX - GROUND_SCROLL_SPEED * dt
if (pipeX < -PIPE_WIDTH) then
pipeX = pipeX + PIPE_WIDTH + WIDTH
end

Now the pipes and the background should appear to be moving in sync. Theoretically they may still diverge because of numerical errors adding up over time, but it practice I'd expect it to run smoothly for days.

Edit: If I do 1e9 iterations (about 192 days) with somewhat random time steps...

Code: Select all

for i = 1, 1e9 do
love.update(1/60*(1+.1-math.random()*.2))
end

...the divergence (print("offset", ((pipeX + groundX + .5) % 1) - .5)) adds up to 2e-10. So after about 200 days the chance of "jitter" per frame is about one in five billion (in this particular experiment, real results may vary).

If you were really serious about jitter, because you'd like your game to run absolutely jitter-free until the heat death of the universe, I'd suggest you add this to your main.lua

Code: Select all

local timeBuf = 0
function love.update(dt)
-- Quantify time steps to avoid numerical errors
timeBuf = timeBuf + dt
local f = 0x400
dt = math.floor(timeBuf*f)/f
timeBuf = timeBuf - dt

-- ...rest of your update function...
end

Edit 2: Fixed theoretical-ish issue with code added in the other edit.

pgimeno
Party member
Posts: 2211
Joined: Sun Oct 18, 2015 2:58 pm

Re: Jitter when creating images in love.load

dup
Last edited by pgimeno on Sun Jun 21, 2020 7:03 pm, edited 1 time in total.

pgimeno
Party member
Posts: 2211
Joined: Sun Oct 18, 2015 2:58 pm

Re: Jitter when creating images in love.load

wilco wrote:
Sun Jun 21, 2020 5:39 pm
It of course has nothing to do with the love.load() function and everything to do with the love.graphics.setDefaultFilter('nearest', 'nearest') which is not applied retroactively to loaded images. So the jittery example has it's image filtering set to 'nearest' while the smooth example has it's image filtering set to the default value...
Heh yeah, I missed that. If you want perfect scrolling with a nearest filter, you have no choice but to scroll at a speed which is an exact multiple of the monitor's refresh rate, and ignore dt. Otherwise there will inevitably be jitter.

wilco
Prole
Posts: 11
Joined: Sun Jun 21, 2020 8:51 am

Re: Jitter when creating images in love.load

nameless tee wrote:
Sun Jun 21, 2020 6:22 pm
You can fix that by instead using:

Code: Select all

pipeX = pipeX - GROUND_SCROLL_SPEED * dt
if (pipeX < -PIPE_WIDTH) then
pipeX = pipeX + PIPE_WIDTH + WIDTH
end

Yes, off course, this makes complete sense now that you say it.
nameless tee wrote:
Sun Jun 21, 2020 6:22 pm
If you were really serious about jitter, because you'd like your game to run absolutely jitter-free until the heat death of the universe, I'd suggest you add this to your main.lua

Code: Select all

local timeBuf = 0
function love.update(dt)
-- Quantify time steps to avoid numerical errors
timeBuf = timeBuf + dt
local f = 0x400
dt = math.floor(timeBuf*f)/f
timeBuf = timeBuf - dt

-- ...rest of your update function...
end

Thank you for the suggestion, I will definitely try this one out.

Edit, added question: This is like a sort of semi fixed time step?
Last edited by wilco on Mon Jun 22, 2020 5:06 pm, edited 1 time in total.

Who is online

Users browsing this forum: No registered users and 31 guests