拖动窗口时保持活动状态 (SDL在Win32上)

14
一开始我的代码建立了SDL环境,并更新OpenGL上下文,但没有执行任何SDL事件处理。这会导致窗口在打开的情况下对Windows来说似乎没有响应。窗口会闪烁一下。标题栏会附加“(未响应)”字样,在窗口内部单击后,窗口变为灰色,因为在Windows中默认情况下非响应窗口是这样处理的。但是在这种状态下(即使在窗口变灰之后),OpenGL显示仍然继续更新和动画,关键是,它甚至在拖动窗口时也可以这样做。显然,在这种情况下,应用程序没有正确地处理窗口事件,导致Windows认为它处于挂起状态。但有明显的证据表明OpenGL继续渲染。 现在,我只对代码进行了一个单一的修改,就是将这三行代码放置在循环中的适当位置(同时也进行了OpenGL绘制)。
SDL_Event event;
if (SDL_PollEvent(&event) && event.type == SDL_QUIT)
    break;
所有这个代码段所做的就是使用SDL刷新消息队列。
现在的行为是,Windows不再认为它是“无响应”的,窗口也不会变灰。没有闪烁,一切似乎都很顺利。但一旦我点击并拖动标题栏以拖动窗口,渲染就被阻塞了。我没有进行调试以确保,但我怀疑SDL_PollEvent在整个窗口拖动期间都会被阻止。
有什么解决办法吗?这很有趣,因为未能处理事件的部分行为证明了理论上我想要的是可能的。
更新:我找到了这个线程:http://www.gamedev.net/topic/488074-win32-message-pump-and-opengl---rendering-pauses-while-draggingresizing/ 结论似乎是这取决于Microsoft为我们作出的某些选择……它基本上会陷入DefWindowProc()直到鼠标释放。通过黑客方式修复这个问题非常麻烦,我可能可以通过在另一个线程中进行渲染来解决问题。但我甚至不想考虑从多个线程中操纵OpenGL上下文,如果这可能是可行的话。

可能是SDL中的一个错误:https://bugzilla.libsdl.org/show_bug.cgi?id=2077 - LB--
5个回答

7

以下是我使用的一些解决方法:添加SDL_WINDOWEVENT_SIZE_CHANGED事件过滤器,并执行额外的SetViewport和绘制帧。

int SDLApp::eventFilter(void* pthis, const SDL_Event *event)
{
    if (event->type == SDL_WINDOWEVENT &&
        event->window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
    {
        SDLApp* app = (SDLApp*)pthis;
        // Note: NULL rectangle is the entire window
        SDL_RenderSetViewport(app->renderer_, NULL);
        app->DrawFrame();
    }
    return 1;
}

...
SDL_SetEventFilter((SDL_EventFilter)SDLApp::eventFilter, this);

6
这个问题很老,但我使用的解决方案似乎没有在其他地方提到,所以在这里分享一下。我从这个答案中得到了灵感,而且它不需要额外的线程。
#include <SDL.h>
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
#include <SDL_syswm.h>

#define SIZE_MOVE_TIMER_ID 1

bool sizeMoveTimerRunning = false;

int eventWatch(void*, SDL_Event* event) {
    if (event->type == SDL_SYSWMEVENT) {
        const auto& winMessage = event->syswm.msg->msg.win;
        if (winMessage.msg == WM_ENTERSIZEMOVE) {
            // the user started dragging, so create the timer (with the minimum timeout)
            // if you have vsync enabled, then this shouldn't render unnecessarily
            sizeMoveTimerRunning = SetTimer(GetActiveWindow(), SIZE_MOVE_TIMER_ID, USER_TIMER_MINIMUM, nullptr);
        }
        else if (winMessage.msg == WM_TIMER) {
            if (winMessage.wParam == SIZE_MOVE_TIMER_ID) {
                // call your render function
                render();
            }
        }
    }
    return 0;
}

// rendering function
void render() {
    /* do your rendering here */
}

// event loop - call this function after setting up your window to start the event loop
void eventLoop() {
    SDL_AddEventWatch(eventWatch, nullptr); // register the event watch function
    SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); // we need the native Windows events, so we can listen to WM_ENTERSIZEMOVE and WM_TIMER
    while (true) {
        SDL_Event event;
        while (SDL_PollEvent(&event)) {
            if (sizeMoveTimerRunning) {
                // modal drag/size loop ended, so kill the timer
                KillTimer(GetActiveWindow(), SIZE_MOVE_TIMER_ID);
                sizeMoveTimerRunning = false;
            }
            /* handle the events here */
        }
        render();
    }
}

当然,如果您的渲染函数需要保持额外的状态(例如,如果您正在使用面向对象编程),请使用eventWatch(void*,SDL_Event*) void * 参数来传递状态。

需要注意的是,EventWatch处理程序在调用事件的线程上运行,而不是主线程上运行。这可能会导致问题,因为SDL希望在主线程上执行渲染,所以您对render的调用可能无效。 - springogeek

1

我曾遇到过类似的问题,当窗口被拖动或调整大小时,视频播放会冻结。我找到的解决方法是为渲染生成一个单独的线程,并使用主线程进行输入。

示例:

DWORD RenderThread(SDL_Window* window)
{
    //Rendering stuff here...
}

int main()
{
    SDL_Init(SDL_INIT_EVERYTHING);

    SDL_Window* window = SDL_CreateWindow("Title Here",
        SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, h, w, SDL_WINDOW_RESIZABLE);

    HANDLE hRenderThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)RenderThread, window, 0, NULL);

    SDL_Event event;

    while (1)
    {
        SDL_PollEvent(&event);

        switch (event.type)
        {
            //Event handling here...
        }
    }
}

请记住,您必须在处理事件的线程中创建窗口。否则它将无法工作。您可以在事件处理线程中创建窗口,然后将该窗口指针传递给渲染线程。


0

我建议您创建2个线程:

  • 线程1:循环调用SDL_PollEvent()(不渲染任何内容)
  • 线程2:进行OpenGL渲染(不调用SDL_PollEvent())

这样,您的OpenGL上下文将从单个线程中进行操作。整个解决方案对您的应用程序架构影响最小。


谢谢你的回答,我会研究一下如何做到这一点。 - Steven Lu
1
FYI 要小心——OpenGL 不是线程安全的。我不知道 SDL 如何处理事件足够好,能否说事件与 OpenGL 上下文紧密关联,会导致问题。 - geometrian
2
这不是一个好的解决方案,因为渲染取决于事件处理的结果。拥有两个无法并行化任务的线程并没有帮助。 - felipecrv
@imallett,你不需要OpenGL是线程安全的,因为你总是从单个线程中进行OpenGL调用。 - user1202136
@philix 当然,你需要正确同步这两个线程。你可以使用读写锁,读锁大部分时间由OpenGL线程持有,而写锁只有事件循环时才会持有。如果我有时间,我可以编写代码来演示这一点。 - user1202136
显示剩余3条评论

0
许多窗口过程会运行一个单独的消息循环,直到发生某个事件,因此您不应该依赖于主循环进行绘图。如果可能的话,应用程序逻辑和渲染应始终在单独的线程中处理。
您的主线程(仅处理消息处理)根本不需要GL上下文,因此您不需要担心共享。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接