I've been using the fixed-timestep loop (as described in "Fix your timestep") for a while now but hadn't actually noticed that the movements in my game are not smooth. It's a bit harder to notice when things are accelerating, deaccelerating, stopping etc. but it does become very apparent if there's something that moves at constant speed across the screen.
I've now been trying to fix this problem for days but I have no clue where to look next or how to find a solution. I'm actually convinced that what I'm doing must be conceptually wrong instead of it being a bug that I can spot myself in my code. So I'm turning to the forums for help. I'll first try to describe what I'm doing and then post some code.
So here's what I'm doing:
I measure time between game loops right before I would call update and add this value to an accumulator. Then while the accumulator is greater equals the timestep, I update while decreasing the accumulator. I then divide the now remaining value by the timestep to obtain the alpha. The alpha is then passed to the rendering where it's used for linear interpolation. After I have obtained the interpolated position, the coordinates of an object are used to create a quad which stores them as GLdouble. All the quads are put into a buffer with glBufferData and then at the end of the rendering step I call glfwSwapBuffers.
Here's the code, I changed a lot of things to obtain a minimal example but this is what I am running and it's not working:
Loop:
int64_t timestep = 1000000 / 60;
int64_t accumulator = 0;
using clock = std::chrono::high_resolution_clock;
using unit = std::chrono::microseconds;
clock::time_point now = clock::now();
while (running)
{
clock::time_point before = now;
now = clock::now();
unit duration = std::chrono::duration_cast<unit>(now - before);
int64_t elapsed = duration.count();
accumulator += elapsed;
for(; accumulator >= timestep; accumulator -= timestep)
{
update();
}
double alpha = static_cast<double>(accumulator) / timestep;
draw(alpha);
}
In this example I'm trying to move a rectangle across the screen.
Update:
x_before = x_now;
x_now = x_now < 0.0 ?
800.0 :
x_now - 1.0;
Draw:
double x_inter = (1.0 - alpha) * x_before + alpha * x_now;
// draw_rectangle(double x, double y, width, height...)
graphics.draw_rectangle(x_inter, 0.0, 100, 100, Color::White);
// Buffers data
graphics.buffer_data();
// calls glfwSwapBuffers
window.render();
Methods of Graphics component:
void Graphics::draw_rectangle(double x, double y, int16_t w, int16_t h, const Color& colour)
{
// this creates a quad and puts it into a vector of quads.
// the other parameters just mean that it will be an empty rectangle filled with a colour
quads.emplace_back(x, x + w, y, y + h, Atlas::NULLOFFSET, colour, 0.0);
}
// Here's Quad and it's constructor
class Graphics::Quad
{
public:
static const size_t NUM_VERTICES = 6;
struct Vertex
{
GLdouble x;
GLdouble y;
GLdouble s;
GLdouble t;
Color c;
};
Quad(GLdouble l, GLdouble r, GLdouble t, GLdouble b,
const Offset& o, const Color& colour, GLdouble rot);
private:
Vertex vertices[NUM_VERTICES];
};
// Constructor of quad
{
vertices[0] = { l, b, o.l, o.b, colour };
vertices[1] = { l, t, o.l, o.t, colour };
vertices[2] = { r, b, o.r, o.b, colour };
vertices[3] = { r, b, o.r, o.b, colour };
vertices[4] = { r, t, o.r, o.t, colour };
vertices[5] = { l, t, o.l, o.t, colour };
}
// Putting quads into the buffer
void Graphics::buffer_data()
{
glClearColor(1.0, 1.0, 1.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
GLsizei csize = static_cast<GLsizei>(quads.size() * sizeof(Quad));
GLsizei fsize = static_cast<GLsizei>(quads.size() * Quad::NUM_VERTICES);
glEnableVertexAttribArray(attribute_coord);
glEnableVertexAttribArray(attribute_color);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, csize, quads.data(), GL_STREAM_DRAW);
glDrawArrays(GL_TRIANGLES, 0, fsize);
glDisableVertexAttribArray(attribute_coord);
glDisableVertexAttribArray(attribute_color);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
// Here's how I create the atlas, etc.
{
glBindTexture(GL_TEXTURE_2D, texture);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, Atlas::WIDTH, Atlas::HEIGHT, 0,
GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glVertexAttribPointer(attribute_coord, 4, GL_DOUBLE, GL_FALSE, sizeof(Quad::Vertex), 0);
glVertexAttribPointer(attribute_color, 4, GL_DOUBLE, GL_FALSE, sizeof(Quad::Vertex), (const void*)32);
}
I feel like I've tried pretty much everything I could to fix it. If I do not use the fixed-timestep loop at all, but simply update once before doing a draw, the movement look's fine. I can only imagine that this is because I'm my rectangle now always moves exactly one pixel between each draw. But this of course doesn't work for me because then my simulation is no longer synchronized to the real time, which I want to be the case.
To describe the jittery movement some more: It seems to randomly skip ahead, as in, it sometimes moves one pixel and sometimes two. I have also used the console to print this to the screen. But as far as I understand the idea of the fixed-timestep loop, this shouldn't be a problem right? The whole idea is that we sometimes update once, sometimes twice or not at all and it still works.
All help and advice is appreciated, I've been working on this for a while now but I definitely see myself as a beginner still and I just can't figure this one out myself.