【问题标题】:SDL2 Window turns black on resizeSDL2 窗口在调整大小时变黑
【发布时间】:2016-01-23 19:12:12
【问题描述】:

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

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

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;
}

【问题讨论】:

  • 我是pretty sure,您应该在创建窗口之前致电SDL_GL_SetAttribute
  • 是的,没错,但我认为这与问题无关。这是一些测试的剩余代码。但我会再测试一次。
  • 是的,在创建窗口之前移动它并没有改变这种情况。但是感谢您彻底检查代码。

标签: macos opengl sdl-2


【解决方案1】:

好的,在与 SDL2 进行了一番斗争之后,我得到了它在 macOS 10.12 上的工作。

问题来了:

  1. 例如,当您使用 SDL_PollEvent(&amp;event) 轮询事件时,SDL2 会捕获调整大小事件并仅重新发送最后 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,因为这会导致卡住事件的奇怪行为。 (例如,调整大小有时不会停止)

【讨论】:

  • @HolyBlackCat 这确实适用于 Windows。但在 Windows 上,过滤器似乎总是在主线程上运行。
  • 我在任何地方都找不到任何保证在filterEvent 中的代码运行时 SDL_PollEvent() 不会返回的保证。没有同步保证意味着我们将需要使用互斥锁(假设 myDrawFunction 无法处理同时从两个线程调用)。
  • 这个答案有效,但我认为它在技术上不正确。调整大小发送多个SDL_WINDOWEVENTS,最后一个将是SDL_WINDOWEVENT_EXPOSED。如果我们阅读该事件的 SDL 源代码,肯定会说“窗口已暴露,应该重绘”。您添加对myDrawFunction(); 的调用这一事实也恰好起作用,因为SDL_WINDOWEVENT_RESIZED 也是EXPOSED 之前发送的事件之一。
【解决方案2】:

事实证明,这并不是我的代码所特有的,它是 MacOSX 中所有 OpenGL 库的更广泛问题的一部分。尽管 GLFW 中的最新补丁已修复它,并且在 XCode 本身提供的 GLUT 版本中,它要好得多,您只需在调整大小时观察到窗口闪烁。

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

问题在于 OSX 窗口管理器的阻塞特性,它会阻塞所有事件,直到释放鼠标。

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

ofNotifyUpdate();
instance->display();

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

找了2天,因为刚开始做项目,没有太多依赖SDL实现,所以决定改用GLFW,它的resize处理最流畅,没有任何闪烁。

【讨论】:

  • “调整大小处理程序”是什么意思?这两行代码应该放在哪里还不是很清楚。 SDL_PollEvent 仅在调整大小完成后发送一个事件,因此您无法在用户空间中挂钩。
猜你喜欢
  • 1970-01-01
  • 2014-01-11
  • 2014-12-02
  • 1970-01-01
  • 1970-01-01
  • 2019-05-15
  • 1970-01-01
  • 2012-08-02
  • 1970-01-01
相关资源
最近更新 更多