Держите окно активным при перетаскивании (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*)
для передачи состояния.