libsdl-org / SDL

Simple Directmedia Layer
https://libsdl.org
zlib License
10.23k stars 1.87k forks source link

Android opengles2: First `SDL_RenderPresent()` broken after resize #11324

Open AntTheAlchemist opened 1 month ago

AntTheAlchemist commented 1 month ago

This appears to affect resizing a window on Android, as well as orientation change.

I discovered this when implementing an "only redraw if something changed" logic. So an app that redraws every frame regardless, will not see this problem.

Is this maybe what they call a race condition? SDL tries to present before Java's resized surface is ready?

I can supply a small repro if needed.

slouken commented 1 month ago

Sure, that would be helpful.

AntTheAlchemist commented 1 month ago

[EDIT] I've inserted a much better repro.

Instructions: Do anything to change the size of the renderer, such as rotate the device, or enter split screen or freform mode. You can see the first frame after a resize is not drawn correctly (should be green box around the edge of the window). Touch the screen to force a 2nd redraw, and it will draw correctly.

#define SDL_MAIN_USE_CALLBACKS 1
#include <SDL3/SDL_main.h>
#include <SDL3/SDL.h>

SDL_Window* win = nullptr;
SDL_Renderer* ren = nullptr;
bool redraw = true;

SDL_AppResult SDL_AppInit(void**, int, char**) {
    if(!SDL_Init(SDL_INIT_VIDEO))
        return SDL_APP_FAILURE;
    if(!(win = SDL_CreateWindow(nullptr, 500, 500, SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY)))
        return SDL_APP_FAILURE;
    if(!(ren = SDL_CreateRenderer(win, "opengles2")))
        return SDL_APP_FAILURE;
    return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppIterate(void*) {
    if(redraw) {
        SDL_SetRenderDrawColorFloat(ren, 0.0f, 0.5f, 0.0f, SDL_ALPHA_OPAQUE_FLOAT);
        SDL_RenderClear(ren);
        int w, h;
        SDL_GetRenderOutputSize(ren, &w, &h);
        SDL_SetRenderDrawColorFloat(ren, 0.0f, 0.00f, 0.5f, SDL_ALPHA_OPAQUE_FLOAT);
        SDL_FRect box{50, 50, (float)w - 100, (float)h - 100};
        SDL_RenderFillRect(ren, &box);
        SDL_RenderPresent(ren);
        redraw = false;
    }
    return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppEvent(void*, SDL_Event* event) {
    switch (event->type) {
        case SDL_EVENT_FINGER_DOWN:
        case SDL_EVENT_KEY_DOWN:
        case SDL_EVENT_WINDOW_RESTORED:
        case SDL_EVENT_WINDOW_EXPOSED:
        case SDL_EVENT_DID_ENTER_FOREGROUND:
        case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
            redraw = true;
            break;
        case SDL_EVENT_QUIT:
            return SDL_APP_SUCCESS;
    }
    return SDL_APP_CONTINUE;
}

void SDL_AppQuit(void*, SDL_AppResult result) {
    if(result == SDL_APP_FAILURE)
        SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, nullptr, SDL_GetError(), win);
}
slime73 commented 1 month ago

Does it make a difference if you redraw on SDL_EVENT_WINDOW_EXPOSED events instead of size change ones?

AntTheAlchemist commented 1 month ago

No, SDL_EVENT_WINDOW_EXPOSED comes through next to resize events, so it makes no difference. The redraw still happens at the same step.

There's a problem with the renderer on the first present, which corrects itself the following frames. You wouldn't see this if an app redraws constantly, because it'll only affect the 1st frame drawn.

AntTheAlchemist commented 2 weeks ago

I've updated this issue to make it clearer, an included better repro code https://github.com/libsdl-org/SDL/issues/11324#issuecomment-2436283594

Would be good to get this fixed, as it's a breaking bug for me.