Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't get above 30FPS in full screen with GLFW #262

Open
bill-connelly opened this issue Jun 13, 2024 · 10 comments
Open

Can't get above 30FPS in full screen with GLFW #262

bill-connelly opened this issue Jun 13, 2024 · 10 comments

Comments

@bill-connelly
Copy link

bill-connelly commented Jun 13, 2024

Describe the bug
GLFW is a popular openGL library. However, it seems to misbehave when running at full screen on a raspberry pi 4 or 5. EVEN WHEN NOT RENDERING. Specifically it drops to 30FPS as soon as the window is full screen. Rendering in a windowed mode, a few pixels short of full screen allows performance in the 100s or 1000s of frames per second.

What makes it stranger is that for the first few 1000 frame updates or so, the inter-frame time is very low. equivalent to 3000 frames per second. But then it starts to chug at 30FPS.

GLFW developers don't think it can't be their fault. To quote them

"GLFW itself isn’t doing anything special here, as the abstraction of glfwSwapBuffers is pretty thin in your case (pretty much just calling eglSwapBuffers), so the behaviour is almost certainly down to the OS + window manager + OpenGL + driver behaviour. There could be something which GLFW is doing to cause this, but it seems unlikely.

The first few long frames could be due to the switch from windowed mode to fullscreen, then the rest due to vsync being forced on when fullscreen, perhaps even forced to 2."

To reproduce
This code shows to issue:

#include <stdio.h>
#include <GLFW/glfw3.h>
#include <sys/time.h>

struct timeval tv;
unsigned long
  frameCount = 0,
  time_in_micros,
  oldTime;

int main(void) {

    glfwInit();

    GLFWmonitor* primaryMonitor = glfwGetPrimaryMonitor();

    const GLFWvidmode* mode = glfwGetVideoMode(primaryMonitor);
  
    //Swapping between these two versions are what causes problems.
    GLFWwindow* window = glfwCreateWindow(1920, 1180, "My Title", NULL, NULL);
    //GLFWwindow* window = glfwCreateWindow(mode->width, mode->height, "My Title", primaryMonitor, NULL);

    glfwMakeContextCurrent(window);
    glfwSwapInterval(0);

    while (!glfwWindowShouldClose(window)) {
        frameCount++;
        glfwSwapBuffers(window);
        if (frameCount % 10 == 0) {
            oldTime = time_in_micros;
            gettimeofday(&tv,NULL);
            time_in_micros = 1000000 * tv.tv_sec + tv.tv_usec;
            printf("Interframe time is %f\n", (float)(time_in_micros-oldTime)/10000);
        }
    }

    glfwTerminate();
    return 0;
}

Compile with: gcc glfw5.c -o glfw5 -lglfw

Expected behaviour
I expect 60FPS or better. Given that the first 400 frames/buffer swaps all happen in less than 1 millisecond, I would expect this to continue, but after around 400 buffer swaps, we get to 30 FPS

Actual behaviour
A print out from the above looks like this:
Interframe time is 0.377200
Interframe time is 0.335000
Interframe time is 0.336000
Interframe time is 1.431100
Interframe time is 2.262200
Interframe time is 0.314800
Interframe time is 0.292700
Interframe time is 0.305800
Interframe time is 0.294900
Interframe time is 0.299900
Interframe time is 0.952400
Interframe time is 3.718800
Interframe time is 0.668100
Interframe time is 3.280600
Interframe time is 0.359700
Interframe time is 0.383500
Interframe time is 0.375100
Interframe time is 1.101400
Interframe time is 4.069400
Interframe time is 4.337800
Interframe time is 0.282200
Interframe time is 0.290200
Interframe time is 0.659600
Interframe time is 2.090900
Interframe time is 0.230600
Interframe time is 0.223700
Interframe time is 0.213000
Interframe time is 0.539900
Interframe time is 2.131800
Interframe time is 0.206400
Interframe time is 0.276300
Interframe time is 0.196400
Interframe time is 0.716800
Interframe time is 0.546900
Interframe time is 0.445100
Interframe time is 2.523900
Interframe time is 2.230000
Interframe time is 0.207300
Interframe time is 10.192000
Interframe time is 33.365799
Interframe time is 33.335899
Interframe time is 33.357399

System
Copy and paste the results of the raspinfo command in to this section. Alternatively, copy and paste a pastebin link, or add answers to the following questions:

System Information

Raspberry Pi 4 Model B Rev 1.1
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
NAME="Debian GNU/Linux"
VERSION_ID="12"
VERSION="12 (bookworm)"

Raspberry Pi reference 2024-03-15
Generated using pi-gen, https://github.com/RPi-Distro/pi-gen, f19ee211ddafcae300827f953d143de92a5c6624, stage5

Linux raspberrypi 6.6.28+rpt-rpi-v8 raspberrypi/firmware#1 SMP PREEMPT Debian 1:6.6.28-1+rpt1 (2024-04-22) aarch64 GNU/Linux
Revision : c03111
Serial : 10000000dc24ffb0
Model : Raspberry Pi 4 Model B Rev 1.1
Throttled flag : throttled=0x0
Camera : supported=0 detected=0, libcamera interfaces=0

Videocore information

Apr 17 2024 17:27:09
Copyright (c) 2012 Broadcom
version 86ccc427f35fdc604edc511881cdf579df945fb4 (clean) (release) (start)

alloc failures: 0
compactions: 0
legacy block fails: 0

"

@timg236 timg236 transferred this issue from raspberrypi/firmware Jun 13, 2024
@timg236
Copy link

timg236 commented Jun 13, 2024

Moved to bookworm-feedback because all OpenGL and display related code was moved out of the firmware on Raspberry Pi 4.

Most likely an interaction with Mesa or Compositor

@timg236
Copy link

timg236 commented Jun 13, 2024

This is probably compositor related. It might be worth trying the labwc compositor which is better able to spot fast-paths and eliminate round trips to memory

sudo apt install labwc
then run raspi-config to enable it
https://forums.raspberrypi.com/viewtopic.php?p=2214879&hilit=labwc#p2214879

@qrp73
Copy link

qrp73 commented Jun 20, 2024

It looks like known and old issue which appears from first Bookworm beta version, I already reported it, but it still not fixed and it appears that the issue with my report disappears for some unknown reason. I don't see it in my github history. Probably project where I opened issue was removed from github.

I remember that @ghollingworth tested it, found what is wrong (as I remember he said it creates not enough buffer for fullscreen rendering) and he promised to fix it. But for some unknown reason it still not fixed, I still needs to set SwapInterval(0) and then SwapInterval(1) manually in my OpenGL engine console. I'm already used to it.

You can workaround it with set SwapInterval=0 and then SwapInterval=1 with call glXSwapIntervalEXT (GLX_EXT_swap_control extension).

If I remember correctly it even enough to just set SwapInterval=1, but it should be called when window and context is created and rendering is already in progress. If you call it before that it wont fix it.

When using GLFW you can call it with glfwSwapInterval(1);

@qrp73
Copy link

qrp73 commented Jul 4, 2024

There is also another workaround for this issue which works ok, you can call glfwSwapInterval(1) on each render frame loop. It seems that this additional call don't reduce performance significantly.

@bill-connelly
Copy link
Author

bill-connelly commented Jul 4, 2024

Doesn't seem to work for me. Still getting 30FPS

compiled with gcc glfw5.c -o glfw5 -lglfw

#include <stdio.h>
#include <GLFW/glfw3.h>
#include <sys/time.h>


struct timeval tv;
unsigned long
  frameCount = 0,
  time_in_micros,
  oldTime;

int main(void) {

    glfwInit();

    GLFWmonitor* primaryMonitor = glfwGetPrimaryMonitor();

    const GLFWvidmode* mode = glfwGetVideoMode(primaryMonitor);

    GLFWwindow* window = glfwCreateWindow(mode->width, mode->height, "My Title", primaryMonitor, NULL);

    glfwMakeContextCurrent(window);

    glfwSwapInterval(1);

    while (!glfwWindowShouldClose(window)) {
        frameCount++;
        glfwSwapInterval(1);

        glfwSwapBuffers(window);

        if (frameCount % 10 == 0) {
            oldTime = time_in_micros;
            gettimeofday(&tv,NULL);
            time_in_micros = 1000000 * tv.tv_sec + tv.tv_usec;
            printf("Interframe time is %f\n", (float)(time_in_micros-oldTime)/10000);
        }
    }

    glfwTerminate();
    return 0;
}

@ghollingworth
Copy link
Contributor

Works fine with SDL

/* Compile with: gcc -o gltest gltest.c -lSDL2 -lGL */

#include <stdio.h>
#include <stdint.h>
#include <assert.h>
#include <stdbool.h>
#include <time.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_opengl.h>
#include <GL/gl.h>

int WIDTH = 0, HEIGHT = 0;
SDL_Window *window = NULL;
int flags = SDL_WINDOW_OPENGL;

static void
render_setup()
{
   glViewport(0, 0, WIDTH, HEIGHT);
   glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
}

static void
render_frame()
{
   glClear(GL_COLOR_BUFFER_BIT);
   SDL_GL_SwapWindow(window);
}

static void
run_event_loop()
{
   struct timespec t0, t1;
   int frame_count = 0;

   render_setup();

   clock_gettime(CLOCK_MONOTONIC, &t0);

   bool fullscreen = false;
   while (true) {
      SDL_Event event;
      while (SDL_PollEvent(&event)) {
         if (event.type == SDL_KEYDOWN) {
            switch (event.key.keysym.sym) {
            case SDLK_ESCAPE:
               goto done;
            case 'f':
               fullscreen = !fullscreen;
               if (fullscreen)
                 SDL_SetWindowFullscreen(window, flags | SDL_WINDOW_FULLSCREEN_DESKTOP);
               else
                 SDL_SetWindowFullscreen(window, flags);
               break;
            default:
               break;
            }
         } else if (event.type == SDL_QUIT) {
            goto done;
         }
      }

      render_frame();
      frame_count++;
   }

done:
   clock_gettime(CLOCK_MONOTONIC, &t1);
   float t1_ms = t1.tv_sec * 1000.0f + t1.tv_nsec / 1000000.0f;
   float t0_ms = t0.tv_sec * 1000.0f + t0.tv_nsec / 1000000.0f;
   float t_s = (t1_ms - t0_ms) / 1000.0f;
   float fps = frame_count / t_s;
   printf("Rendered %u frames in %.2f ms (%.2f fps)\n", frame_count, t_s, fps);
}

int main(int argc, char **argv)
{
   if (argc != 4) {
      printf("\nUsage: gltest width height vsync(1|0)\n\n");
      return 1;
   }

   WIDTH = atoi(argv[1]);
   HEIGHT = atoi(argv[2]);
   if (WIDTH <= 0 || HEIGHT <= 0) {
      fprintf(stderr, "Invalid width or height\n");
      return 1;
   } else {
      printf("Rendering at %dx%d\n", WIDTH, HEIGHT);
   }

   bool vsync = atoi(argv[3]);
   printf("Vsync %s\n", vsync ? "enabled" : "disabled");

   printf("Available SDL drivers:\n");
   int ndri = SDL_GetNumVideoDrivers();
   for (int i = 0; i < ndri; i++) {
      printf("   %s\n", SDL_GetVideoDriver(i));
   }

   window = SDL_CreateWindow("OpenGL Test", 0, 0, WIDTH, HEIGHT, flags);
   assert(window);
   SDL_SetWindowPosition(window,0,0);

   SDL_GLContext ctx = SDL_GL_CreateContext(window);
   assert(ctx);

   SDL_GL_SetSwapInterval(vsync ? 1 : 0);

   run_event_loop();

   return 0;
}

@qrp73
Copy link

qrp73 commented Jul 19, 2024

@ghollingworth

Works fine with SDL

No! It don't works fine. With SDL there is the same issue with half frame rate update.

Note: the issue happens in fullscreen mode, not window mode!

Just replace this line in your code:
int flags = SDL_WINDOW_OPENGL;
with this one:
int flags = SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN | SDL_WINDOW_ALLOW_HIGHDPI;

and try it...

Here is result when running it on display with 75 Hz frame rate:

./gltest 640 480 1
Rendering at 640x480
Vsync enabled
Available SDL drivers:
   x11
   wayland
   KMSDRM
   offscreen
   dummy
   evdev
Rendered 461 frames in 12.33 ms (37.38 fps)

@ghollingworth
Copy link
Contributor

Still works for me, using labwc rather than wayfire though

image

@qrp73
Copy link

qrp73 commented Jul 19, 2024

with default raspi os setup (wayfire) it don't works, here is result on 75 Hz display:

$ ./gltest 640 480 1
Rendering at 640x480
Vsync enabled
Available SDL drivers:
   x11
   wayland
   KMSDRM
   offscreen
   dummy
   evdev
Rendered 366 frames in 9.79 ms (37.38 fps)

But when I set wayland driver it looks like works:

$ SDL_VIDEODRIVER="wayland" ./gltest 640 480 1
Rendering at 640x480
Vsync enabled
Available SDL drivers:
   x11
   wayland
   KMSDRM
   offscreen
   dummy
   evdev
Rendered 854 frames in 11.74 ms (72.76 fps)

So, it seems like issue in X11 driver, which is used by default:

$ SDL_VIDEODRIVER="x11" ./gltest 640 480 1
Rendering at 640x480
Vsync enabled
Available SDL drivers:
   x11
   wayland
   KMSDRM
   offscreen
   dummy
   evdev
Rendered 483 frames in 12.92 ms (37.39 fps)

As I understand labwc is a new compositor? What is the difference with wayfire?

@qrp73
Copy link

qrp73 commented Jul 19, 2024

It looks that it affects 0ad game, because it shows 37 fps on 75 Hz display.
So it is critical to fix that issue to fix low fps in games and opengl applciations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants