SDL2 Window 调整大小时变黑

SDL2 Window turns black on resize

我已经开始使用 SDL2,但对它没有经验。我正在开发 Mac 系统。几乎一切都很好,但我有一个问题,当一个可调整大小的 window 被调整大小时,在拖动手柄时, window 变黑,我只能在释放后重新绘制它。我已经检查过 window 正在调整大小时,没有产生任何事件,我没有办法干扰或检测到这一点,因为事件循环刚刚暂停。有什么可能的解决方案吗?

这是代码(几乎是处理调整大小事件教程的复制品):

SDL_Event event;

SDL_Rect nativeSize;
SDL_Rect newWindowSize;

float scaleRatioW;//This is to change anything that might rely on something like mouse coords
float scaleRatioH; //(such as a button on screen) over to the new coordinate system scaling would create

SDL_Window * window; //Our beautiful window
SDL_Renderer * renderer; //The renderer for our window
SDL_Texture * backBuffer; //The back buffer that we will be rendering everything to before scaling up

SDL_Texture * ballImage; //A nice picture to demonstrate the scaling;

bool resize;

void InitValues(); //Initialize all the variables needed
void InitSDL();     //Initialize the window, renderer, backBuffer, and image;
bool HandleEvents(); //Handle the window changed size event
void Render();            //Switches the render target back to the window and renders the back buffer, then switches back.
void Resize();      //The important part for stretching. Changes the viewPort, changes the scale ratios

void InitValues()
{
    nativeSize.x = 0;
    nativeSize.y = 0;
    nativeSize.w = 256;
    nativeSize.h = 224; //A GameBoy size window width and height

    scaleRatioW = 1.0f;
    scaleRatioH = 1.0f;

    newWindowSize.x = 0;
    newWindowSize.y = 0;
    newWindowSize.w = nativeSize.w;
    newWindowSize.h = nativeSize.h;

    window = NULL;
    renderer = NULL;
    backBuffer = NULL;
    ballImage = NULL;

    resize = false;
}

void InitSDL()
{
    if(SDL_Init(SDL_INIT_EVERYTHING) < 0)
    {
        //cout << "Failed to initialize SDL" << endl;
        printf("%d\r\n", __LINE__);
    }

    //Set the scaling quality to nearest-pixel
    if(SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0") < 0)
    {
        //cout << "Failed to set Render Scale Quality" << endl;
         printf("%d\r\n", __LINE__);
    }

    //Window needs to be resizable
    window = SDL_CreateWindow("Rescaling Windows!",
                                                    SDL_WINDOWPOS_CENTERED,
                                                    SDL_WINDOWPOS_CENTERED,
                                                    256,
                                                    224,
                                                    SDL_WINDOW_RESIZABLE);

    //You must use the SDL_RENDERER_TARGETTEXTURE flag in order to target the backbuffer
    renderer = SDL_CreateRenderer(window,
                                                      -1,
                                                      SDL_RENDERER_ACCELERATED |
                                                      SDL_RENDERER_TARGETTEXTURE);

    //Set to blue so it's noticeable if it doesn't do right.
    SDL_SetRenderDrawColor(renderer, 0, 0, 200, 255);

    //Similarly, you must use SDL_TEXTUREACCESS_TARGET when you create the texture
    backBuffer = SDL_CreateTexture(renderer,
                                                       SDL_GetWindowPixelFormat(window),
                                                       SDL_TEXTUREACCESS_TARGET,
                                                       nativeSize.w,
                                                       nativeSize.h);

    //IMPORTANT Set the back buffer as the target
    SDL_SetRenderTarget(renderer, backBuffer);

    //Load an image yay
    SDL_Surface * image = SDL_LoadBMP("Ball.bmp");

    ballImage = SDL_CreateTextureFromSurface(renderer, image);

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);

    SDL_FreeSurface(image);
}

bool HandleEvents()
{
    while(SDL_PollEvent(&event) )
    {
       printf("%d\r\n", __LINE__);
       if(event.type == SDL_QUIT)
       {
             printf("%d\r\n", __LINE__);
            return true;
       }
        else if(event.type == SDL_WINDOWEVENT)
        {
            if(event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
            {
                resize = true;
                printf("%d\r\n", __LINE__);
            }
        }

        return false;
    }
    return false;
}

void Render()
{
    SDL_RenderCopy(renderer, ballImage, NULL, NULL); //Render the entire ballImage to the backBuffer at (0, 0)
    printf("%d\r\n", __LINE__);

    SDL_SetRenderTarget(renderer, NULL); //Set the target back to the window

    if(resize)
    {
        Resize();
        resize = false;
    }
    printf("%d\r\n", __LINE__);

    SDL_RenderCopy(renderer, backBuffer, &nativeSize, &newWindowSize); //Render the backBuffer onto the screen at (0,0)
    SDL_RenderPresent(renderer);
    SDL_RenderClear(renderer); //Clear the window buffer

    SDL_SetRenderTarget(renderer, backBuffer); //Set the target back to the back buffer
    SDL_RenderClear(renderer); //Clear the back buffer
    printf("%d\r\n", __LINE__);

}

void Resize()
{
    int w, h;
    printf("%d\r\n", __LINE__);

    SDL_GetWindowSize(window, &w, &h);

    scaleRatioW = w / nativeSize.w;
    scaleRatioH = h / nativeSize.h;  //The ratio from the native size to the new size

    newWindowSize.w = w;
    newWindowSize.h = h;

    //In order to do a resize, you must destroy the back buffer. Try without it, it doesn't work
    SDL_DestroyTexture(backBuffer);
    backBuffer = SDL_CreateTexture(renderer,
                                   SDL_GetWindowPixelFormat(window),
                                   SDL_TEXTUREACCESS_TARGET, //Again, must be created using this
                                   nativeSize.w,
                                   nativeSize.h);

    SDL_Rect viewPort;
    SDL_RenderGetViewport(renderer, &viewPort);

    if(viewPort.w != newWindowSize.w || viewPort.h != newWindowSize.h)
    {
        //VERY IMPORTANT - Change the viewport over to the new size. It doesn't do this for you.
        SDL_RenderSetViewport(renderer, &newWindowSize);
    }
}

int main(int argc, char * argv[])
{
    InitValues();
    InitSDL();

    bool quit = false;
    printf("%d\r\n", __LINE__);

    while(!quit)
    {
        printf("%d\r\n", __LINE__);
        quit = HandleEvents();
        Render();
    }

    return 0;
}

原来这不是我的代码特有的,它是 Mac 中所有 OpenGL 库的一个更广泛问题的一部分OSX。尽管 GLFW 中最近的补丁已经修复了它,并且在 XCode 本身提供的 GLUT 版本中,它要好得多,你只是在调整大小时观察到 window 中的闪烁。

https://github.com/openframeworks/openFrameworks/issues/2800 https://github.com/openframeworks/openFrameworks/issues/2456

问题是因为 OSX window 管理器的阻塞特性,它会阻塞所有事件,直到鼠标被释放。

要解决这个问题,您应该操作您正在使用的库并重新编译它。您应该将这些或类似的东西(取决于您的开发环境)添加到调整大小处理程序以绕过块:

ofNotifyUpdate();
instance->display();

如果您是新手,并且希望能够轻松使用库更新,那将是灾难性的。另一种解决方案是通过编写另一个执行该操作的事件处理程序来覆盖 SDL 行为。它更好,因为它不需要编辑 SDL 代码,但添加了一堆我个人不喜欢的平台特定代码,并且会导致很多我不想花时间修复的问题。

经过2天的搜索,因为我刚刚开始这个项目,并没有太多依赖SDL,所以我决定切换到GLFW,它的调整大小处理最流畅,我没有观察到闪烁。

好的,在与 SDL2 进行了一番较量之后,我让它在 macOS 10.12 上运行。

这是问题所在:

  1. SDL2 捕获调整大小事件并仅在您使用 SDL_PollEvent(&event) 轮询事件时重新发送最后 3 个事件。
  2. 在此期间(您按下鼠标左键单击调整大小区域并按住鼠标)SDL_PollEvent 处于阻塞状态。

解决方法如下:

幸运的是,您可以使用 SDL_SetEventFilter 连接到事件处理程序。每次收到事件时都会触发。因此对于所有发生的调整大小事件。

所以你可以做的是注册你自己的事件过滤器回调,基本上允许每个事件(通过返回 1),监听调整大小事件,并将它们发送到你的绘制循环。

示例:

//register this somewhere
int filterEvent(void *userdata, SDL_Event * event) {
    if (event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_RESIZED) {
        //convert userdata pointer to yours and trigger your own draw function
        //this is called very often now
        //IMPORTANT: Might be called from a different thread, see SDL_SetEventFilter docs
        ((MyApplicationClass *)userdata)->myDrawFunction(); 

        //return 0 if you don't want to handle this event twice
        return 0;
    }

    //important to allow all events, or your SDL_PollEvent doesn't get any event
    return 1;
}


///after SDL_Init
SDL_SetEventFilter(filterEvent, this) //this is instance of MyApplicationClass for example

重要提示:不要在 filterEvent 回调中调用 SDL_PollEvent,因为这会导致卡住事件的奇怪行为。 (例如,调整大小有时不会停止)