1
1
Fork 0
mirror of https://github.com/QB64-Phoenix-Edition/QB64pe.git synced 2024-05-12 12:00:13 +00:00
QB64-PE/internal/c/libqb/src/glut-main-thread.cpp
Matthew Kilgore cdbfb94c61 Fix GLUT redraw timing
Currently the GLUT thread draws the screen too slowly, Ex. The default
is supposed to be 60 FPS, but it will always draw a bit slower than 60
FPS. This is because the current logic simply inserts delays for the
entire length of a frame, not taking into account how long it took us to
render the last frame.

The new code uses GetTicks() to measure how much time has passed since
the last render, which then lets us calculate the exact amount of delay
until the next frame. We additionally then measure how long the delay
lasted vs. what we asked for (since any delay we do only has a minimum
guarentee, it will ocassionally last a bit longer) and adjust based on
that as well. The result is a perfect 60 FPS as long as rendering is
quick enough.

If the rendering falls behind (Ex. a slow _GL SUB is in the program)
then we'll start skipping frames to get back on track.

Fixes: #408
2024-04-17 00:58:30 -04:00

181 lines
4.9 KiB
C++

#include "libqb-common.h"
#include <GL/glew.h>
#include <list>
#include <queue>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <unordered_map>
// note: MacOSX uses Apple's GLUT not FreeGLUT
#ifdef QB64_MACOSX
# include <GLUT/glut.h>
#else
# define CORE_FREEGLUT
# include <GL/freeglut.h>
#endif
#include "completion.h"
#include "glut-thread.h"
#include "gui.h"
#include "mac-key-monitor.h"
#include "mac-mouse-support.h"
#include "mutex.h"
#include "thread.h"
// FIXME: These extern variable and function definitions should probably go
// somewhere more global so that they can be referenced by libqb.cpp
extern uint8_t *window_title;
extern int32_t framebufferobjects_supported;
extern int32_t screen_hide;
void MAIN_LOOP(void *);
void GLUT_KEYBOARD_FUNC(unsigned char key, int x, int y);
void GLUT_DISPLAY_REQUEST();
void GLUT_KEYBOARDUP_FUNC(unsigned char key, int x, int y);
void GLUT_SPECIAL_FUNC(int key, int x, int y);
void GLUT_SPECIALUP_FUNC(int key, int x, int y);
void GLUT_MOUSE_FUNC(int glut_button, int state, int x, int y);
void GLUT_MOTION_FUNC(int x, int y);
void GLUT_PASSIVEMOTION_FUNC(int x, int y);
void GLUT_RESHAPE_FUNC(int width, int height);
void GLUT_IDLEFUNC();
#ifdef CORE_FREEGLUT
void GLUT_MOUSEWHEEL_FUNC(int wheel, int direction, int x, int y);
#endif
static void glutWarning(const char *fmt, va_list lst) {
// This keeps FreeGlut from dumping warnings to console
}
// Performs all of the FreeGLUT initialization except for calling glutMainLoop()
static void initialize_glut(int argc, char **argv) {
#ifdef CORE_FREEGLUT
glutInitWarningFunc(glutWarning);
glutInitErrorFunc(glutWarning);
#endif
glutInit(&argc, argv);
mac_register_key_handler();
#ifdef QB64_WINDOWS
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH | GLUT_MULTISAMPLE);
#else
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
#endif
glutInitWindowSize(640, 400); // cannot be changed unless display_x(etc) are modified
if (!glutGet(GLUT_DISPLAY_MODE_POSSIBLE)) // must be called on Linux or GLUT crashes
{
exit(1);
}
if (!window_title) {
glutCreateWindow("Untitled");
} else {
glutCreateWindow((char *)window_title);
}
GLenum err = glewInit();
if (GLEW_OK != err) {
gui_alert((char *)glewGetErrorString(err));
}
if (glewIsSupported("GL_EXT_framebuffer_object"))
framebufferobjects_supported = 1;
glutDisplayFunc(GLUT_DISPLAY_REQUEST);
glutIdleFunc(GLUT_IDLEFUNC);
glutKeyboardFunc(GLUT_KEYBOARD_FUNC);
glutKeyboardUpFunc(GLUT_KEYBOARDUP_FUNC);
glutSpecialFunc(GLUT_SPECIAL_FUNC);
glutSpecialUpFunc(GLUT_SPECIALUP_FUNC);
glutMouseFunc(GLUT_MOUSE_FUNC);
glutMotionFunc(GLUT_MOTION_FUNC);
glutPassiveMotionFunc(GLUT_PASSIVEMOTION_FUNC);
glutReshapeFunc(GLUT_RESHAPE_FUNC);
#ifdef CORE_FREEGLUT
glutMouseWheelFunc(GLUT_MOUSEWHEEL_FUNC);
#endif
macMouseInit();
}
static bool glut_is_started;
static struct completion glut_thread_starter;
static struct completion *glut_thread_initialized;
void libqb_start_glut_thread() {
if (glut_is_started)
return;
struct completion init;
completion_init(&init);
glut_thread_initialized = &init;
completion_finish(&glut_thread_starter);
completion_wait(&init);
completion_clear(&init);
}
// Checks whether the GLUT thread is running
bool libqb_is_glut_up() { return glut_is_started; }
void libqb_glut_presetup(int argc, char **argv) {
if (!screen_hide) {
initialize_glut(argc, argv); // Initialize GLUT if the screen isn't hidden
glut_is_started = true;
} else {
completion_init(&glut_thread_starter);
}
}
void libqb_start_main_thread(int argc, char **argv) {
// Start the 'MAIN_LOOP' in a separate thread, as GLUT has to run on the
// initial thread.
struct libqb_thread *main_loop = libqb_thread_new();
libqb_thread_start(main_loop, MAIN_LOOP, NULL);
// This happens for $SCREENHIDE programs. This thread waits on the
// `glut_thread_starter` completion, which will get completed if a
// _ScreenShow is used.
if (!glut_is_started) {
completion_wait(&glut_thread_starter);
initialize_glut(argc, argv);
glut_is_started = true;
if (glut_thread_initialized)
completion_finish(glut_thread_initialized);
}
glutMainLoop();
}
// Due to GLUT making use of cleanup via atexit, we have to call exit() from
// the same thread handling the GLUT logic so that the atexit handler also runs
// from that thread (not doing that can result in a segfault due to using GLUT
// from two threads at the same time).
//
// This is accomplished by simply queuing a GLUT message that calls exit() for us.
void libqb_exit(int exitcode) {
// If GLUT isn't running then we're free to do the exit() call from here
if (!libqb_is_glut_up())
exit(exitcode);
libqb_glut_exit_program(exitcode);
}