13

At first my code set up the SDL environment, and proceeded to update the OpenGL context, without performing any SDL_Event processing whatsoever. This causes the window, as long as it was open, to appear to Windows to be unresponsive. The window flickers a bit. The titlebar would get "(Not Responding)" appended to it, and upon clicking inside the window it becomes grayed out, as Windows does this by default on non responsive windows. However in this state (even as and after it becomes grayed out), the OpenGL display continues to update and animate, and here's the kicker, it even does so while the window is being dragged. Clearly in this case the application isn't handling events from windows correctly, causing windows to think that it is in a hanged state. But there is clear evidence that the opengl continues to render.

Now I make one single modification to the code, which is these three lines placed in an appropriate spot inside the loop (which also does the OpenGL draw):

SDL_Event event;
if (SDL_PollEvent(&event) && event.type == SDL_QUIT)
    break;

All this is doing is flushing the message queue using SDL.

Now the behavior is that Windows no longer thinks it is "Not Responding" and it does not get grayed out. No flicker. Everything seems to run swimmingly. But once I click and drag the title bar to drag the window, rendering gets blocked. I haven't debugged it to be sure, but I suspect that SDL_PollEvent blocks for the duration of the window drag.

Is there a way around this? This is interesting because part of the behavior exhibited by failing to handle events is proof that what I want is possible in theory.

Update: I found this thread: http://www.gamedev.net/topic/488074-win32-message-pump-and-opengl---rendering-pauses-while-draggingresizing/

The verdict seems to be that it comes down to certain choices that Microsoft made for us... It basically gets stuck in DefWindowProc() till the mouse is released. It would get very messy to hack a fix for this and I might be able to do a work around by rendering in another thread. But I don't even want to begin to think about juggling an OpenGL context from multiple threads, if that's even something that's possible.

Steven Lu
  • 41,389
  • 58
  • 210
  • 364

5 Answers5

7

Some workaround that works for me - add event filter for SDL_WINDOWEVENT_SIZE_CHANGED event and do additional SetViewport and draw frame.

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);
inf581
  • 612
  • 1
  • 7
  • 9
5

This question is old, but the solution I'm using doesn't seem to be mentioned anywhere else, so here it is.

I got my inspiration from this answer, and it doesn't use additional threads.

#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();
    }
}

Of course, if your rendering function needs to keep additional state (e.g. if you're using OOP), use the void* parameter of eventWatch(void*, SDL_Event*) to pass the state.

Bernard
  • 5,209
  • 1
  • 34
  • 64
  • It should be noted that EventWatch handlers run on the thread which invokes the event, rather than the main thread. This could lead to issues because SDL expects rendering to performed on the main thread, so your call to `render` might be invalid. – springogeek Dec 23 '20 at 13:41
1

I had a similar problem in which it would freeze video playback when the window was dragged or resized. The solution I found was to spawn a separate thread for rendering and use the main thread for input.

Example:

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

Keep in mind that you MUST create the window in the thread that does event handling. If not it won't work. You can create the window in your event handling thread then pass that window pointer to your rendering thread.

0

Many windows procedures run a separate message loop until a certain event occurs, so you shouldn't rely on your main loop to do the drawing. If possible, application logic and rendering should always be handled in a separate thread.

Your main thread (that only handles message processing) doesn't need GL context at all, so you wouldn't need to worry about sharing.

riv
  • 6,846
  • 2
  • 34
  • 63
0

I propose you created 2 threads:

  • Thread 1: loops calling SDL_PollEvent() (without rendering anything)
  • Thread 2: does OpenGL rendering (without calling SDL_PollEvent())

This way, your OpenGL context would be manipulated from a single thread. The whole solution has a minimum impact the architecture of your application.

user1202136
  • 11,171
  • 4
  • 41
  • 62
  • Thanks for your answer, I will look into doing this. – Steven Lu Mar 30 '12 at 16:05
  • 1
    FYI be careful--OpenGL is not thread safe. I don't know how SDL handles events well enough to say whether events are tied closely enough with the OpenGL context to cause problems. – geometrian Jul 30 '12 at 23:43
  • 2
    This is not a good solution because the rendering depends on the outcome of the event handling. Having two threads of non parallelizable tasks doesn't help. – felipecrv Jan 06 '15 at 21:18
  • @imallett You don't need OpenGL to be thread-safe, since you always do OpenGL calls from a single thread. – user1202136 Jan 07 '15 at 12:31
  • @philix Of course, you would need to properly synchronize the two threads. You could use a read-write lock, the read lock being mostly held by your OpenGL thread, while the write lock is only sometimes held by the event loop. If I have some time, I could write code to demonstrate this. – user1202136 Jan 07 '15 at 12:34
  • @user1202136 the locking would mostly serialize the two tasks. A cleaner idea would be to asynchronously handle the events. – felipecrv Jan 07 '15 at 16:52
  • @user1202136 I didn't say it was _impossible_, just that one should _be careful_. Having written my own windowing layer, I can tell you that there are subtle issues here. For example, is the wgl/glx API for binding a context part of OpenGL, or is it part of the windowing system? Is the windowing system input callback per-frame or per-context (keeping in mind that device contexts != render contexts != hardware contexts)? [continued] – geometrian Jan 07 '15 at 18:03
  • The OP's underlying issue is that on Windows, dragging frames counts as sizing the window and blocks other input events. To maintain rendering AND responsivity, one needs to redraw the frame while it's being dragged (which can be achieved by directly calling from the input callback or by using a thread).¶ Your solution will work (so have a +1 to bring you back to 0, to match the other threading answer), but I think the issue raised by @philix is important. Still, almost every other windowing system does something like this internally (gah), using a timer+thread to invoke rendering. – geometrian Jan 07 '15 at 18:04