Держите окно активным при перетаскивании (SDL на Win32)

Сначала мой код настроил среду SDL и приступил к обновлению контекста OpenGL, без какой-либо обработки SDL_Event. Это приводит к тому, что окно, пока оно открыто, должно появиться в Windows, чтобы быть невосприимчивым. Окно мерцает. В заголовке будет добавлено "(Не реагировать)", а при нажатии внутри окна оно становится серым, так как Windows делает это по умолчанию в не реагирующих окнах. Однако в этом состоянии (даже после того, как оно становится серым), дисплей OpenGL продолжает обновляться и анимироваться, а вот кикер, он даже делает это, пока окно перетаскивается. Очевидно, что в этом случае приложение не обрабатывает события из окон правильно, заставляя окна думать, что они находятся в повешенном состоянии. Но есть очевидные доказательства того, что opengl продолжает оказывать.

Теперь я делаю одну модификацию кода, которая представляет собой эти три строки, помещенные в соответствующее место внутри цикла (что также делает DrawGL draw):

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 из нескольких потоков, если это даже возможно.

Ответ 1

Некоторое обходное решение, которое работает для меня - добавляет фильтр событий для события 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);

Ответ 2

Я предлагаю вам создать 2 потока:

  • Тема 1: циклы, вызывающие SDL_PollEvent() (без отображения чего-либо)
  • Тема 2: рендеринг OpenGL (без вызова SDL_PollEvent())

Таким образом, ваш контекст OpenGL будет управляться из одного потока. Все решение имеет минимальное влияние на архитектуру вашего приложения.

Ответ 3

Многие процедуры Windows запускают отдельный цикл сообщения до тех пор, пока не произойдет определенное событие, поэтому вы не должны полагаться на свой основной цикл для рисования. Если возможно, логика приложения и рендеринг всегда должны обрабатываться в отдельном потоке.

Ваш основной поток (который обрабатывает обработку сообщений) вообще не нуждается в контексте GL, поэтому вам не нужно беспокоиться о совместном использовании.

Ответ 4

У меня была аналогичная проблема, в которой он мог бы заморозить воспроизведение видео при перетаскивании или изменении размера окна. Решение, которое я нашел, это создать отдельный поток для рендеринга и использовать основной поток для ввода.

Пример:

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

Имейте в виду, что вы ДОЛЖНЫ создать окно в потоке, который обрабатывает события. Если нет, это не сработает. Вы можете создать окно в потоке обработки событий, а затем передать этот указатель окна на поток рендеринга.

Ответ 5

Этот вопрос старый, но решение, которое я использую, похоже, нигде не упоминается, так что вот оно.

Я получил вдохновение от этого ответа, и он не использует дополнительные потоки.

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

Конечно, если ваша функция рендеринга должна поддерживать дополнительное состояние (например, если вы используете ООП), используйте параметр void* eventWatch(void*, SDL_Event*) для передачи состояния.