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

OpenGL: Set swapInterval > 1 leads to incorrect update rate for x11 backend and ignored for wayland and KMSDRM backend #320

Open
qrp73 opened this issue Nov 16, 2024 · 0 comments

Comments

@qrp73
Copy link

qrp73 commented Nov 16, 2024

Attempting to set swapInterval greater than 1 for the X11 backend results in an incorrect buffer swap rate, which also varies each time.

For the Wayland and KMSDRM backends, setting swapInterval greater than 1 leads to actual set swapInterval = 1.

For some unknown reason, SDL_GL_GetSwapInterval() returns negative values with x11 and wayland backend if actual swapInterval > 0.

Steps to reproduce

  1. sudo apt install libsdl2-dev libsdl2-ttf-dev libsdl2-image-dev
  2. Create test-vblank3.c and copy/paste code (see below)
  3. Compile it with gcc -o test-vblank3 test-vblank3.c -Wall -lGL -lSDL2 -lSDL2_ttf -lSDL2_image
  4. Run it with ./test-vblank3 --driver x11
  5. Press F1 to toggle swapInterval between 0,1,2,3

Expected result: setting swapInterval > 1 is accepted and properly processed. Fps should correspond to requested swapInterval value. For swapInterval=1 it should be equals to display refresh rate, for swapInterval=2 it should be half of the display refresh rate, etc.

Actual result: For x11 backend setting swapInterval > 1 leads to some weird and incorrect fps which randomly varies from time to time on every swapInterval change.

  1. Repeat steps 4 and 5 with ./test-vblank3 --driver wayland

Actual result: For wayland backend setting swapInterval > 1 leads to actual set swapInterval = 1, so there is no way to set swapInterval > 1.

  1. Switch to a virtual console and repeat steps 4 and 5 with ./test-vblank3 --driver KMSDRM

Actual result: For KMSDRM backend setting swapInterval > 1 leads to actual set swapInterval = 1, the same issue as for wayland backend.

  1. Press F11 in a loop for x11 backend and notice that each time you get the same swapInterval 2 or 3, fps is different.

Expected result: each time you set swapInterval=2, fps should be the same

Actual result: each time you set swapInterval=2, fps is different: sometimes it is as expected 37.5 fps, sometimes 50 fps, sometimes 45 fps, etc.

Screenshots

  1. x11 backend: swapInterval=2, expected 37.5 fps, actual value is 45.3 and varies and unstable...
    screenshot_7

  2. x11 backend: swapInterval=3, expected 25.0 fps, actual value is 27.5 fps and unstable...
    screenshot_8

  3. KMSDRM backend: swapInterval=3, expected 25.0 fps, actual swapInterval=1 and 75.0 fps...
    screenshot_9

Test code

test-vblank3.c
// sudo apt install libsdl2-dev libsdl2-ttf-dev libsdl2-image-dev
// sudo pacman -S sdl2 sdl2_ttf sdl2_image
// gcc -o test-vblank3 test-vblank3.c -Wall -lGL -lSDL2 -lSDL2_ttf -lSDL2_image
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <limits.h>
#include <GL/gl.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_opengl.h>
#include <SDL2/SDL_ttf.h>
#include <SDL2/SDL_image.h>

#ifdef _WIN32
#include <limits.h>
#define MAX_PATH_LENGTH MAX_PATH
#else
#include <limits.h>
#define MAX_PATH_LENGTH PATH_MAX
#endif


// Simple queue implementation
typedef struct {
    uint64_t* data;
    size_t front;
    size_t rear;
    size_t size;
    size_t capacity;
} Queue64;
void queue_init(Queue64* q, size_t capacity) {
    q->capacity = capacity;
    q->size = 0;
    q->front = 0;
    q->rear = 0;
    q->data = (uint64_t*)malloc(capacity * sizeof(uint64_t));    
}
void queue_free(Queue64* q) {
    free(q->data);
}
void queue_resize(Queue64* q, size_t new_capacity) {
    if (new_capacity < q->size) {
        printf("warn: queue_resize(): new capacity %zu is smaller than current size %zu. The last %zu elements will be discarded.\n", new_capacity, q->size, q->size - new_capacity);
        q->size = new_capacity;  // Truncate elements if the new size is smaller than the current size
    }
    uint64_t* new_data = (uint64_t*)malloc(new_capacity * sizeof(uint64_t));
    // copy elements to a new buffer
    for (size_t i = 0; i < q->size; i++) {
        new_data[i] = q->data[(q->front + i) % q->capacity];
    }
    // Free the old buffer and update pointers
    free(q->data);
    q->data = new_data;
    q->front = 0;
    q->rear = q->size;
    q->capacity = new_capacity;
}
void queue_enqueue(Queue64* q, uint64_t value) {
    // If the queue is full, resize it
    if (q->size == q->capacity) {
        queue_resize(q, q->capacity * 2);  // Double the capacity
    }
    // Add the element to the queue
    q->data[q->rear] = value;
    q->rear = (q->rear + 1) % q->capacity;  // Circular move
    q->size++;
}
uint64_t queue_dequeue(Queue64* q) {
    if (q->size == 0) {
        printf("error: queue_dequeue() failed: queue is empty\n");
        return 0;
    }
    uint64_t value = q->data[q->front];
    q->front = (q->front + 1) % q->capacity;
    q->size--;
    return value;
}
// Function to remove N oldest elements from the front of the queue
void queue_trimFront(Queue64* q, size_t n) {
    if (n >= q->size) {
        printf("warn: queue_trimFront(): trying to remove more elements (%zu) than the queue contains (%zu). Clearing the entire queue.\n", n, q->size);
        q->front = 0;  // Reset the front pointer
        q->size = 0;   // Reset the size
    } else {
        // Directly update the front pointer to remove N elements
        q->front = (q->front + n) % q->capacity;
        q->size -= n;
    }
}
size_t queue_getSize(Queue64* q) {
    return q->size;
}
uint64_t queue_getElement(Queue64* queue, int index) {
    if (index < 0 || index >= queue->size) {
        printf("error: queue_getElement() failed: index %d is out of bounds\n", index);
        return 0;  // or some default value for error
    }
    // Calculate the actual index in the circular queue
    size_t actual_index = (queue->front + index) % queue->capacity;
    return queue->data[actual_index];
}

Queue64 _queue;
uint64_t _frameCounter;




void fillRect(float x, float y, float w, float h) {
    GLfloat vertices[] = { x, y + h, x + w, y + h, x + w, y, x, y };
    glEnableClientState(GL_VERTEX_ARRAY);
    glVertexPointer(2, GL_FLOAT, 0, vertices);
    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
    //glDisableClientState(GL_VERTEX_ARRAY);    
}

void fillTexturedRect(float x, float y, float w, float h) {
    GLfloat vertices[]  = { x, y + h, x + w, y + h, x + w, y, x, y };
    GLfloat texCoords[] = { 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f };
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    glVertexPointer(2, GL_FLOAT, 0, vertices);
    glTexCoordPointer(2, GL_FLOAT, 0, texCoords);
    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
    //glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    //glDisableClientState(GL_VERTEX_ARRAY);
}

void drawLine(float x1, float y1, float x2, float y2) {
    GLfloat vertices[2 * 2] = { x1,y1, x2,y2 };
    glEnableClientState(GL_VERTEX_ARRAY);
    glVertexPointer(2, GL_FLOAT, 0, vertices);
    glDrawArrays(GL_LINE_STRIP, 0, 2);
    //glDisableClientState(GL_VERTEX_ARRAY);            
}

void drawText(TTF_Font* font, const char* text, int x, int y, SDL_Color color) {
    glPushAttrib(GL_CURRENT_BIT | GL_DEPTH_BUFFER_BIT | GL_ENABLE_BIT);
    glDisable(GL_DEPTH_TEST);
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    //TTF_SetFontOutline(font, outlineWidth);
    SDL_Surface* surface = TTF_RenderText_Blended(font, text, color);
    if (surface == NULL) {
        glPopAttrib();    
        printf("TTF_RenderText_Blended() failed: %s\n", TTF_GetError());
        return;
    }
    
    GLuint texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glPixelStorei(GL_UNPACK_ROW_LENGTH, surface->pitch / SDL_BYTESPERPIXEL(surface->format->format));
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surface->w, surface->h, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, surface->pixels);

    // shadow imitation
    //glColor4f(0.0f, 0.0f, 0.0f, 1.0f);    
    //fillTexturedRect(x+1, y+1, surface->w, surface->h);
    
    glColor4f(1.0f, 1.0f, 1.0f, 1.0f);    
    fillTexturedRect(x, y, surface->w, surface->h);

    SDL_FreeSurface(surface);
    glDeleteTextures(1, &texture);
    glPopAttrib();    
}

#define max(a, b) ((a) > (b) ? (a) : (b))


int _swapInterval = 1;
int _fullscreen = 0;
SDL_DisplayMode _displayMode;


void window_onRender(SDL_Window* window, TTF_Font* font, int width, int height) {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    char buf[128];
    SDL_Color textColor = {255, 255, 0}; // Green
    int x = 10, y = 10, step=24;
    if (SDL_GetDisplayMode(0, 0, &_displayMode) != 0) {
        snprintf(buf, sizeof(buf), "SDL_GetDisplayMode() failed: %s\n", SDL_GetError());
        drawText(font, buf, x, y, textColor);
        y+=step;
        //return;
    }
    int swapInterval = SDL_GL_GetSwapInterval();
    
    double fps = 0.0;
    uint64_t tmax = 0;
    uint64_t tmin = -1;
    uint64_t tavg = 0;
    size_t length = queue_getSize(&_queue);    
    if (length > 0) {
        // find tavg for the last second (for fps)
        size_t counter = 0;
        for (size_t i=max(length-_displayMode.refresh_rate, 0); i < length; i++) {
            uint64_t v = queue_getElement(&_queue, i);
            tavg += v;
            counter++;
        }
        if (counter > 0) {
            tavg /= counter;
        }
        fps = (double)1000000000UL / tavg;
        // find statistics
        tmax = 0;
        tmin = -1;
        tavg = 0;
        for (size_t i=0; i < length; i++) {
            uint64_t v = queue_getElement(&_queue, i);
            if (v > tmax) tmax = v;
            if (v < tmin) tmin = v;
            tavg += v;
        }
        tavg /= length;
    }

    float targetRate = 0.0;
    float targetTime = 0.0;
    float viewMax = 0.0;
    if (abs(swapInterval) == 0 || _displayMode.refresh_rate == 0) {
        targetRate = INFINITY;
        targetTime = 0;
        viewMax = 1000000000.0 / max(_displayMode.refresh_rate, 30);
    } else {
        targetRate = (double)_displayMode.refresh_rate / abs(swapInterval);
        targetTime = 1000000000.0 / targetRate;
        viewMax = targetTime;
    }
    viewMax *= 1.2;
    if (tmax > viewMax) viewMax = tmax;
    float scale = viewMax!=0 ? height / viewMax : 0;

    glEnable(GL_BLEND);                
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    // Draw grid and limit lines    
    glColor4f(1.0f, 1.0f, 1.0f, 0.2f);  // White    
    // horizontal grid    
    for (uint64_t t=0; t < viewMax; t+=1000000.0) {
        drawLine(0, height - scale * t, width-1, height - scale * t);
    }
    // vertical grid
    for (int x=_frameCounter%_displayMode.refresh_rate; x < width; x+=_displayMode.refresh_rate) {
        drawLine(width-x, 0, width-x, height);
    }
    // target time line
    //glColor4f(1.0f, 0.0f, 1.0f, 0.6f);  // Magenta
    //drawLine(0, height - scale * targetTime, width-1, height - scale * targetTime);    
    
    float limitMax;
    float limitMin;
    if (abs(swapInterval) == 0) {
        limitMin = 0.0;
        limitMax = 0.0;
    } else {
        limitMin = 1000000000.0 / (targetRate+1);
        limitMax = 1000000000.0 / (targetRate-1);
    }
    float limitMinY = height - scale * limitMin;
    float limitMaxY = height - scale * limitMax;
    //glColor4f(1.0f, 0.0f, 0.0f, 0.6f);
    //drawLine(0, limitMinY, width-1, limitMinY);
    //drawLine(0, limitMinY, width-1, limitMaxY);
    
    
    // time graph
    int startIndex = width - length;
    GLfloat vertices[width * 2]; // (x, y)
    for (int i = 0; i < width; ++i) {
        uint64_t v = 0;
        if (i >= startIndex) {
            v = queue_getElement(&_queue, i-startIndex);
        }
        vertices[i * 2] = i;                        // x
        vertices[i * 2 + 1] = height - scale * v;   // y (reversed)
    }
    glEnableClientState(GL_VERTEX_ARRAY);
    glVertexPointer(2, GL_FLOAT, 0, vertices);
    glColor4f(0.0f, 1.0f, 0.0f, 0.8f);            // Green
    glDrawArrays(GL_LINE_STRIP, 0, sizeof(vertices)/(2*sizeof(GLfloat)));
    glDisableClientState(GL_VERTEX_ARRAY);
    
    // +-1 fps area
    glColor4f(1.0f, 1.0f, 0.0f, 0.3f);      // Yellow
    fillRect(0, limitMinY, width-1, limitMaxY-limitMinY);
    
    glDisable(GL_BLEND);        
    

    snprintf(buf, sizeof(buf), "real: %7.3f fps", fps);
    drawText(font, buf, x, y, textColor);
    y+=step;
    snprintf(buf, sizeof(buf), "need: %7.3f fps", targetRate);
    drawText(font, buf, x, y, textColor);
    y+=step;
    y+=step;    
    snprintf(buf, sizeof(buf), "tmin: %7.3f ms", tmin/1000000.0);
    drawText(font, buf, x, y, textColor);
    y+=step;
    snprintf(buf, sizeof(buf), "tmax: %7.3f ms", tmax/1000000.0);
    drawText(font, buf, x, y, textColor);
    y+=step;
    snprintf(buf, sizeof(buf), "tavg: %7.3f ms", tavg/1000000.0);
    drawText(font, buf, x, y, textColor);
    y+=step;
    y+=step;
    if (swapInterval < 0)
        snprintf(buf, sizeof(buf), "swap: %d, \"%s\"", swapInterval, SDL_GetError());
    else
        snprintf(buf, sizeof(buf), "swap: %d", swapInterval);
    drawText(font, buf, x, y, textColor);
    y+=step;
    snprintf(buf, sizeof(buf), "need: %d", _swapInterval);
    drawText(font, buf, x, y, textColor);    
    y+=step;
    y+=step;            
    snprintf(buf, sizeof(buf), "drv:  %s", (const char*)SDL_GetCurrentVideoDriver());
    drawText(font, buf, x, y, textColor);    
    y+=step;    
    snprintf(buf, sizeof(buf), "mode: %dx%d@%d", _displayMode.w, _displayMode.h, _displayMode.refresh_rate);
    drawText(font, buf, x, y, textColor);
    y+=step;
    snprintf(buf, sizeof(buf), "size: %dx%d", width, height);
    drawText(font, buf, x, y, textColor);
}

void window_onResize(int width, int height) {
    glViewport(0, 0, width, height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0.0, width, height, 0.0, -1.0, 1.0); // coordinate setup: (0,0)=(left,top), (width,height)=(bottom,right)
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

int findFont(const char* font_name, char *buffer, size_t buffer_size) {
    char command[MAX_PATH_LENGTH];
    snprintf(command, sizeof(command), "fc-match -f '%%{file}' %s", font_name);
    FILE *fp = popen(command, "r");
    if (fp == NULL) {
        return 0;
    }
    if (fgets(buffer, buffer_size, fp) == NULL) {
        pclose(fp);
        return 0;
    }
    buffer[strcspn(buffer, "\n")] = '\0';
    pclose(fp);
    return 1;
}

void saveScreenshot(SDL_Window* window) {
    int width, height;
    SDL_GetWindowSize(window, &width, &height);
    SDL_Surface* surface = SDL_CreateRGBSurfaceWithFormat(0, width, height, 32, SDL_PIXELFORMAT_RGBA32);
    if (!surface) {
        printf("SDL_CreateRGBSurfaceWithFormat failed: %s\n", SDL_GetError());
        return;
    }
    glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, surface->pixels);
    if (glGetError() != GL_NO_ERROR) {
        printf("glReadPixels failed\n");
        SDL_FreeSurface(surface);
        return;
    }
    Uint32* pixels = (Uint32*)surface->pixels;
    for (int y = 0; y < height / 2; ++y) {
        for (int x = 0; x < width; ++x) {
            Uint32 temp = pixels[y * width + x];
            pixels[y * width + x] = pixels[(height - y - 1) * width + x];
            pixels[(height - y - 1) * width + x] = temp;
        }
    }
    // Create unique filename
    char filename[128];
    int counter = 1;
    do {
        snprintf(filename, sizeof(filename), "screenshot_%d.png", counter++);
    } while (SDL_RWFromFile(filename, "rb") != NULL);
    // Save
    if (IMG_SavePNG(surface, filename) != 0) {
        printf("IMG_SavePNG() failed: %s\n", IMG_GetError());
    } else {
        printf("Screenshot saved to %s\n", filename);
    }
    SDL_FreeSurface(surface);
}


int main(int argc, char *argv[]) {
    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
        printf("Failed to initialize SDL: %s\n", SDL_GetError());
        return -1;
    }
    //printf("SDL_GetCurrentVideoDriver(): %s\n", (const char*)SDL_GetCurrentVideoDriver());
    
    int width  = 800;
    int height = 600;
    int doublebuffer = 1;
    const char *drivername = NULL;

    // parse command line
    for (int i = 1; i < argc; ++i) {
        if (strcmp(argv[i], "--help") == 0) {
            printf("USAGE: %s [options]\n", argv[0]);
            printf("  F1  - toggle swapInterval\n");
            printf("  F11 - toggle fullscreen mode\n");
            printf("  F12 - save screenshot\n");
            printf("  ESC - exit\n");
            printf("options:\n");
            printf("  --list                  list available SDL video drivers\n");
            printf("  --driver <drivername>   set SDL video driver (equals to SDL_VIDEODRIVER env.variable)\n");
            printf("  --size <width,height>   set initial window size\n");
            printf("  --doublebuffer <1|0>    set SDL_GL_DOUBLEBUFFER attribute value\n");
            SDL_Quit();
            return 0;
        } else if (strcmp(argv[i], "--list") == 0) {
            int num_drivers = SDL_GetNumVideoDrivers();
            printf("\nAvailable --driver values:\n");
            for (int j = 0; j < num_drivers; ++j) {
                printf("  \"%s\"\n", SDL_GetVideoDriver(j));
            }
            SDL_Quit();
            return 0;
        } else if (strcmp(argv[i], "--driver") == 0 && i + 1 < argc) {
            drivername = argv[i + 1];
            printf("--driver %s\n", drivername);
            i++;
        } else if (strcmp(argv[i], "--size") == 0 && i + 1 < argc) {
            if (sscanf(argv[i + 1], "%d,%d", &width, &height) != 2) {
                printf("Invalid --size format. Expected --size <width,height>\n");
                SDL_Quit();
                return -1;
            }
            printf("--size %d,%d\n", width, height);
            i++;
        } else if (strcmp(argv[i], "--doublebuffer") == 0 && i + 1 < argc) {
            if (sscanf(argv[i + 1], "%d", &doublebuffer) != 1) {
                printf("Invalid --doublebuffer format. Expected --doublebuffer <0|1>\n");
                SDL_Quit();
                return -1;
            }
            doublebuffer = doublebuffer ? 1 : 0;
            printf("--doublebuffer %d\n", doublebuffer);
            i++;
        } else {
            printf("error: unknown command line option \"%s\"\n", argv[i]);
            SDL_Quit();
            return -1;            
        } 
    }
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, doublebuffer);    
    if (SDL_VideoInit(drivername) < 0) {
        printf("SDL_VideoInit(\"%s\") failed: %s\n", drivername, SDL_GetError());
        SDL_Quit();
        return -1;
    }
    printf("SDL_GetCurrentVideoDriver(): %s\n", (const char*)SDL_GetCurrentVideoDriver());
    
    if (SDL_GetDisplayMode(0, 0, &_displayMode) != 0) {
        printf("SDL_GetDisplayMode() failed: %s\n", SDL_GetError());
        SDL_Quit();
        return -1;
    }    
    printf("SDL_GetDisplayMode(): %d x %d @ %d, format=0x%08x\n", _displayMode.w, _displayMode.h, _displayMode.refresh_rate, _displayMode.format);

    if (TTF_Init() < 0) {
        printf("TTF_Init() failed: %s\n", TTF_GetError());
        SDL_Quit();
        return -1;
    }
    // find path: $ fc-match -f '%{file}\n' Monospace
    char fontPath[MAX_PATH_LENGTH];
    if (!findFont("Monospace", fontPath, sizeof(fontPath))) {
        printf("findFont(\"Monospace\") failed\n");
        return -1;
    }
    TTF_Font* font = TTF_OpenFont(fontPath, 20);
    if (font == NULL) {
        printf("TTF_OpenFont() failed: %s\n", TTF_GetError());
        TTF_Quit();
        SDL_Quit();
        return -1;
    }
    if (IMG_Init(IMG_INIT_PNG) == 0) {
        printf("IMG_Init failed: %s\n", IMG_GetError());
        SDL_Quit();
        return -1;
    }

    SDL_Window* window = SDL_CreateWindow(
        "test-vblank3", 
        SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
        width, height,
        SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
    if (window == NULL) {
        printf("SDL_CreateWindow() failed: %s\n", SDL_GetError());
        TTF_CloseFont(font);        
        TTF_Quit();
        SDL_Quit();
        return -1;
    }
    if (_fullscreen)
    {
        SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP);
    }
    SDL_GLContext glContext = SDL_GL_CreateContext(window);
    SDL_GL_SetSwapInterval(_swapInterval);
    printf("SDL_GL_SetSwapInterval(%d) => SDL_GL_GetSwapInterval() = %d\n", _swapInterval, SDL_GL_GetSwapInterval());
        

    const int   printAttrs[] = { SDL_GL_DOUBLEBUFFER, SDL_GL_MULTISAMPLEBUFFERS, SDL_GL_MULTISAMPLESAMPLES, 0 };
    const char* printNames[] = { "SDL_GL_DOUBLEBUFFER", "SDL_GL_MULTISAMPLEBUFFERS", "SDL_GL_MULTISAMPLESAMPLES", 0 };
    for (int i=0; printAttrs[i] != 0; i++) {
        int value;
        if (SDL_GL_GetAttribute(printAttrs[i], &value)) {
            printf("SDL_GL_GetAttribute(%s) failed: %s\n", printNames[i], SDL_GetError());
            continue;
        }
        printf("%s = %d\n", printNames[i], value);
    }

    SDL_GetWindowSize(window, &width, &height);
    printf("SDL_GetWindowSize(): %d, %d\n", width, height);
    window_onResize(width, height);

    int running = 1;
    struct timespec ts1, ts2;
    clock_gettime(CLOCK_MONOTONIC_RAW, &ts1);
    
    queue_init(&_queue, 16384);

    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

    while (running) {
        SDL_Event event;
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) { 
                running = 0;
            }
            if (event.type == SDL_KEYDOWN) {
                if (event.key.keysym.sym == SDLK_ESCAPE) { 
                    running = 0;
                }
                if (event.key.keysym.sym == SDLK_F11) {
                    _fullscreen = _fullscreen ? 0:1;
                    printf("SDL_SetWindowFullscreen(%d)\n", _fullscreen);
                    SDL_SetWindowFullscreen(window, _fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
                }
                if (event.key.keysym.sym == SDLK_F1) {
                    _swapInterval = (_swapInterval+1) & 3;
                    SDL_GL_SetSwapInterval(_swapInterval);
                    printf("SDL_GL_SetSwapInterval(%d) => SDL_GL_GetSwapInterval() = %d\n", _swapInterval, SDL_GL_GetSwapInterval());
                }
                if (event.key.keysym.sym == SDLK_F12) {
                    saveScreenshot(window);
                }
            }
            if (event.type == SDL_WINDOWEVENT) {
                if (event.window.event == SDL_WINDOWEVENT_RESIZED || 
                    event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
                    width = event.window.data1;
                    height = event.window.data2;
                    window_onResize(width, height);
                }
            }
        }

        window_onRender(window, font, width, height);

        SDL_GL_SwapWindow(window);
        clock_gettime(CLOCK_MONOTONIC_RAW, &ts2);
        uint64_t dt_ns = ((uint64_t)ts2.tv_sec - (uint64_t)ts1.tv_sec) * 1000000000UL + (ts2.tv_nsec - ts1.tv_nsec);
        ts1 = ts2;
        _frameCounter++;

        queue_enqueue(&_queue, dt_ns);
        SDL_GetWindowSize(window, &width, &height);
        ssize_t excess_elements = (ssize_t)queue_getSize(&_queue) - width;
        if (excess_elements > 0) {
            queue_trimFront(&_queue, excess_elements);
        }
    }

    queue_free(&_queue);

    IMG_Quit();
    TTF_CloseFont(font);
    TTF_Quit();
    SDL_GL_DeleteContext(glContext);
    SDL_DestroyWindow(window);
    SDL_Quit();
    return 0;
}
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

1 participant