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

Halved fps on mouse move in fullscreen mode issue #372

Open
qrp73 opened this issue Feb 13, 2025 · 12 comments
Open

Halved fps on mouse move in fullscreen mode issue #372

qrp73 opened this issue Feb 13, 2025 · 12 comments

Comments

@qrp73
Copy link

qrp73 commented Feb 13, 2025

There is an issue which leads to drop down fps to a half value of display refresh rate, it happens when you move mouse in fullscreen mode.

First I reported this issue about 2 years ago. It was analyzed and the patch was made, but not published and didn't applied to the code, then the issue was deleted and completely disappears.

Then I opened the issue again and it was deleted with no response.

About a year ago I reported it again. And again the patch was made for private testing in some separate branch and it was possible to install it for test. But now I cannot see this issue. It looks that again it was deleted and all info disappears.

But the bug is still there and still not fixed in Raspberry Pi OS Bookworm with all latest updates. And it is very annoying as it leads to a low fps in games which happens when you move mouse in fullscreen mode.

Please fix this halved fps issue.

Steps to reproduce:

  1. Create test-vblank4.c file and copy/paste code (see below)
  2. Compile with gcc -o test-vblank4 test-vblank4.c -Wall -lGL -lSDL2 -lSDL2_ttf -lSDL2_image
  3. Run with ./test-vblank4. This test app shows realtime graph with time interval between buffer swaps. It should be stable and equals to display refresh rate.
  4. Press F11 to switch into fullscreen mode
  5. Move mouse

Expected result: buffer swap time interval is stable

Actual result: buffer swap time interval raise up and leads to drop down application frames per second update. As result app fps drops down to a half value of display refresh rate and app/game become laggy and slow.

Image

The issue happens only when app running in fullscreen mode. Which makes it even more annoying, because fullscreen mode is intended to use display exclusive for the game to get better fps, but on the contrary it leads to a halved fps when you move mouse...

Since the patch is already available in private branches and tested, please just apply that patch to fix the issue.

Test code

test-vblank4.c
// sudo apt install libsdl2-dev libsdl2-ttf-dev libsdl2-image-dev
// sudo pacman -S sdl2 sdl2_ttf sdl2_image
// gcc -o test-vblank4 test-vblank4.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);            
}


typedef struct {
    GLuint texture;
    GLuint textureOutline;
    int w;
    int h;
} GlyphEntry;

GlyphEntry* _glyphs[0x100];

void textureARGB2LUMA(SDL_Surface* surface, GLubyte* dst_pixels) {
    // ARGB to LUMINANCE_ALPHA
    uint32_t* src_pixels = (uint32_t*)surface->pixels;
    int pitch32 = surface->pitch / SDL_BYTESPERPIXEL(surface->format->format);
    for (int y = 0; y < surface->h; y++) {
        for (int x = 0; x < surface->w; x++) {
            uint32_t pixel = src_pixels[y * pitch32 + x];
            uint8_t a = (pixel & surface->format->Amask) >> surface->format->Ashift;
            uint8_t r = (pixel & surface->format->Rmask) >> surface->format->Rshift;
            uint8_t g = (pixel & surface->format->Gmask) >> surface->format->Gshift;
            uint8_t b = (pixel & surface->format->Bmask) >> surface->format->Bshift;
            uint8_t l = (r + g + b) / 3;
            *dst_pixels++ = l;      // Luminance
            *dst_pixels++ = a;      // Alpha
        }
    }    
}

void loadGlyph(TTF_Font* font, const uint8_t symbol, GlyphEntry* glyph) {
    glyph->texture = 0;
    glyph->textureOutline = 0;
    glyph->w = 0;
    glyph->h = 0;
    
    SDL_Color color = {255, 255, 255, 255};
    GLuint texture = 0;

    char text[2] = { symbol, 0 };
    TTF_SetFontOutline(font, 0);
    SDL_Surface* surface = TTF_RenderText_Blended(font, text, color);
    if (surface == NULL) {
        glPopAttrib();    
        printf("TTF_RenderText_Blended() failed: %s\n", TTF_GetError());
        return;
    }
    glyph->w = surface->w;
    glyph->h = surface->h;
    GLubyte* texture_pixels = (GLubyte*)malloc(surface->w * surface->h * 2); // LUMINANCE ALPHA
    if (texture_pixels == NULL) {
        printf("malloc() failed!\n");
        SDL_FreeSurface(surface);
        glPopAttrib();
        return;
    }
    textureARGB2LUMA(surface, texture_pixels);
    glEnable(GL_TEXTURE_2D);
    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);
    glPixelStorei(GL_UNPACK_ROW_LENGTH, surface->w);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surface->w, surface->h, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, texture_pixels);    
    glyph->texture = texture;
    free(texture_pixels);
    SDL_FreeSurface(surface);
    surface = NULL;
    texture = 0;
    
    TTF_SetFontOutline(font, 1);
    surface = TTF_RenderText_Blended(font, text, color);
    if (surface == NULL) {
        glPopAttrib();    
        printf("TTF_RenderText_Blended() failed: %s\n", TTF_GetError());
        return;
    }
    texture_pixels = (GLubyte*)malloc(surface->w * surface->h * 2); // LUMINANCE ALPHA
    if (texture_pixels == NULL) {
        printf("malloc() failed!\n");
        SDL_FreeSurface(surface);
        glPopAttrib();
        return;
    }
    textureARGB2LUMA(surface, texture_pixels);    
    glEnable(GL_TEXTURE_2D);
    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);
    glPixelStorei(GL_UNPACK_ROW_LENGTH, surface->w);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surface->w, surface->h, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, texture_pixels);    
    glyph->textureOutline = texture;
    free(texture_pixels);
    SDL_FreeSurface(surface);
    surface = NULL;
    texture = 0;    
}
void freeGlyphs() {
    for (size_t i=0; i < sizeof(_glyphs)/sizeof(GlyphEntry*); i++) {
        GlyphEntry* glyph = _glyphs[i];
        if (glyph == NULL) {
            continue;
        }
        if (glyph->texture != 0) {
            glDeleteTextures(1, &(glyph->texture));
            glyph->texture = 0;
        }
        if (glyph->textureOutline != 0) {
            glDeleteTextures(1, &(glyph->textureOutline));
            glyph->textureOutline = 0;
        }
        free(glyph);
        _glyphs[i] = NULL;
    }
}


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);
    int cx = x, cy = y;
    for (int i = 0; text[i] != '\0'; i++) {
        uint8_t symbol = text[i] & 0xff;
        GlyphEntry* glyph = _glyphs[symbol];
        if (glyph == NULL) {
            glyph = (GlyphEntry*)malloc(sizeof(GlyphEntry));
            loadGlyph(font, symbol, glyph);
            _glyphs[symbol] = glyph;            
        }
        //if (glyph->textureOutline != 0) {
        //    glBindTexture(GL_TEXTURE_2D, glyph->textureOutline);
        //    glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
        //    fillTexturedRect(cx, cy, glyph->w, glyph->h);            
        //}
        if (glyph->texture != 0) {
            glBindTexture(GL_TEXTURE_2D, glyph->texture);
            glColor4f(color.r/255.0f, color.g/255.0f, color.b/255.0f, 1.0f);            
            fillTexturedRect(cx, cy, glyph->w, glyph->h);            
        }
        cx += glyph->w;
    }
    glBindTexture(GL_TEXTURE_2D, 0);
    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);
    freeGlyphs();

    IMG_Quit();
    TTF_CloseFont(font);
    TTF_Quit();
    SDL_GL_DeleteContext(glContext);
    SDL_DestroyWindow(window);
    SDL_Quit();
    return 0;
}
@popcornmix
Copy link

First I reported this issue about 2 years ago. It was analyzed and the patch was made, but not published and didn't applied to the code, then the issue was deleted and completely disappears.

Since the patch is already available in private branches and tested, please just apply that patch to fix the issue.

I don't believe we've ever deleted a github issue.
I don't remember the details of a two year old issue.
Do you remember if the patch was to the kernel/firmware/mesa/something else?

@qrp73
Copy link
Author

qrp73 commented Feb 13, 2025

The original issue was disappears for some unknown reason so I cannot find it in history. May be it was linked with some repository which was deleted, but it is completely disappears.

As I remember, the patch was for Mesa. According to the discussion, the issue occurs because some resource is reserved to support two 4K display config. However, as a side effect, this leads to issues with any other display configuration. The patch was implemented in such a way that it reserves this resource only when the system actually uses two 4K displays. In other cases, it avoids slowing down the display update. Something like that.

I tried to install the patch and it worked good.

If I remember correctly it was discussed with @ghollingworth

@qrp73
Copy link
Author

qrp73 commented Feb 13, 2025

I found last discussion, here is the patch: labwc/labwc#2290 (comment)

Could you please apply it to the main branch?

@popcornmix
Copy link

There is a bump to mesa 24.2.4 in testing in the beta forums.
It may be worth joining that and checking if the fix is present.

@Consolatis
Copy link

Consolatis commented Feb 13, 2025

First I reported this issue about 2 years ago. It was analyzed and the patch was made, but not published and didn't applied to the code, then the issue was deleted and completely disappears.
Then I opened the issue again and it was deleted with no response.
About a year ago I reported it again. And again the patch was made for private testing in some separate branch and it was possible to install it for test. But now I cannot see this issue. It looks that again it was deleted and all info disappears.
The original issue was disappears for some unknown reason so I cannot find it in history.

https://github.com/issues should show all of the issues you created.

@qrp73
Copy link
Author

qrp73 commented Feb 13, 2025

https://github.com/issues should show all of the issues you created.

I know, but original issue (the first one) is missing from issues. I carefully check full list of my issues, both -open and closed, it is just disappears, I have no idea why. The same thing happens with second issue. The third one is still there, it just has collapsed block and this is why I didn't find it.

@qrp73
Copy link
Author

qrp73 commented Feb 13, 2025

There is a bump to mesa 24.2.4 in testing in the beta forums. It may be worth joining that and checking if the fix is present.

I know this forum and was participated in this beta. But there was some moderator on this forum who decided to joke on me and unfortunately administrators didn't responded on my messages for a long time. After all they responded, but it was too late, I decided to leave it. I promised myself to not return. It was before the administrators contacted me. Later they contacted with me and the issue was settled, so to speak, but since I promised to myself to not return, now this forum is taboo for me. I won't respect myself if I go back there.

This is issue with halved fps is very old, it is in test stage more than one year. It seems too much, isn't it? Hope it will be rolled out with update soon...

@popcornmix
Copy link

When I get a chance, I'll try your test program with the beta mesa version.

@popcornmix
Copy link

Cannot reproduce with my system using beta repo:

$ apt policy mesa-common-dev
mesa-common-dev:
  Installed: (none)
  Candidate: 24.2.4-1~bpo12+1~rpt1
  Version table:
     24.2.8-1~bpo12+1 100
        100 http://deb.debian.org/debian bookworm-backports/main arm64 Packages
     24.2.4-1~bpo12+1~rpt1 500
        500 http://buildbot.pitowers.org:3143 bookworm/untested arm64 Packages
     23.2.1-1~bpo12+rpt3 500
        500 http://buildbot.pitowers.org:3143 bookworm/main arm64 Packages
     22.3.6-1+deb12u1 500
        500 http://deb.debian.org/debian bookworm/main arm64 Packages

I only see (narrow) spikes in the test app when pressing F11. No effect from moving mouse in either windowed or fullscreen mode.

@qrp73
Copy link
Author

qrp73 commented Feb 14, 2025

Cannot reproduce with my system using beta repo

When this version will be rolled out to the main branch?

@popcornmix
Copy link

The usual answer is "when it's ready".
I'll ask when I see someone who may know, but I suspect that's the response I'll get.

@popcornmix
Copy link

popcornmix commented Feb 17, 2025

As expected, it's "when it's ready".
There are some positive reports of bugs fixes and (significantly) higher performance achieved, but there is one outstanding regression that affects performance in full screen mode. It's being investigated but no fix yet.

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

3 participants