diff --git a/project/src/backend/sdl/SDLApplication.cpp b/project/src/backend/sdl/SDLApplication.cpp index 4609c53e4c..77b8a98afd 100644 --- a/project/src/backend/sdl/SDLApplication.cpp +++ b/project/src/backend/sdl/SDLApplication.cpp @@ -26,6 +26,10 @@ namespace lime static Uint32 s_lastSleepCalibration = 0; static bool s_busyWaitOnly = false; static bool firstTime = true; + static SDL_atomic_t s_waitEventBlocking; +#if defined(HX_WINDOWS) && !defined(HX_WINRT) + static SDL_atomic_t s_inModalEventWatch; +#endif static inline void CalibrateSleepGuard(bool force = false) { @@ -119,6 +123,12 @@ namespace lime SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_WARN); currentApplication = this; +#if defined(HX_WINDOWS) && !defined(HX_WINRT) + modalWatchInstalled = false; + mainThreadID = SDL_ThreadID(); + pendingResizeDispatchSkips = 0; + pendingWatchRenderSkips = 0; +#endif framePeriod = 1000.0 / 60.0; currentUpdate = 0; @@ -142,6 +152,12 @@ namespace lime SDL_EventState(SDL_DROPFILE, SDL_ENABLE); SDLJoystick::Init(); +#if defined(HX_WINDOWS) && !defined(HX_WINRT) + SDL_AtomicSet(&s_inModalEventWatch, 0); + SDL_AtomicSet(&s_waitEventBlocking, 0); + SDL_AddEventWatch(ModalEventWatch, this); + modalWatchInstalled = true; +#endif #ifdef HX_MACOS CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(CFBundleGetMainBundle()); @@ -159,8 +175,107 @@ namespace lime SDLApplication::~SDLApplication() { +#if defined(HX_WINDOWS) && !defined(HX_WINRT) + if (modalWatchInstalled && SDL_WasInit(0)) + { + SDL_DelEventWatch(ModalEventWatch, this); + modalWatchInstalled = false; + } +#endif + } + +#if defined(HX_WINDOWS) && !defined(HX_WINRT) + int SDLApplication::ModalEventWatch(void *userdata, SDL_Event *event) + { + + if (!event || event->type != SDL_WINDOWEVENT) + return 0; + + const Uint8 windowEvent = event->window.event; + if (windowEvent != SDL_WINDOWEVENT_EXPOSED && windowEvent != SDL_WINDOWEVENT_SIZE_CHANGED && windowEvent != SDL_WINDOWEVENT_RESIZED && windowEvent != SDL_WINDOWEVENT_MOVED) + return 0; + + SDLApplication *application = (SDLApplication *)userdata; + if (!application || !application->active || inBackground) + return 0; + if (SDL_AtomicGet(&s_waitEventBlocking) == 0) + return 0; + + if (SDL_ThreadID() != application->mainThreadID) + return 0; + + // Prevent re-entry if rendering queues another window event. + if (!SDL_AtomicCAS(&s_inModalEventWatch, 0, 1)) + return 0; + + application->PumpOneFrameFromWatch(event); + SDL_AtomicSet(&s_inModalEventWatch, 0); + return 0; } + void SDLApplication::PumpOneFrameFromWatch(SDL_Event *watchEvent) + { + + if (!active || inBackground) + return; + + bool isResizeEvent = false; + bool isExposeEvent = false; + if (watchEvent && watchEvent->type == SDL_WINDOWEVENT) + { + isResizeEvent = (watchEvent->window.event == SDL_WINDOWEVENT_SIZE_CHANGED || watchEvent->window.event == SDL_WINDOWEVENT_RESIZED); + isExposeEvent = (watchEvent->window.event == SDL_WINDOWEVENT_EXPOSED); + } + + currentUpdate = SDL_GetTicks(); + bool frameDue = ((Sint32)(currentUpdate - nextUpdate) >= 0); + bool isResizeOrExposeEvent = (isResizeEvent || isExposeEvent); + if (!frameDue && !isResizeEvent) + return; + + bool exitedBlocking = false; + if (SDL_AtomicGet(&s_waitEventBlocking)) + { + System::GCExitBlocking(); + SDL_AtomicSet(&s_waitEventBlocking, 0); + exitedBlocking = true; + } + + if (isResizeEvent) + { + ProcessWindowEvent(watchEvent); + pendingResizeDispatchSkips++; + } + + if (frameDue) + { + applicationEvent.type = UPDATE; + applicationEvent.deltaTime = currentUpdate - lastUpdate; + lastUpdate = currentUpdate; + + nextUpdate += NextFrameStep(framePeriod); + while (nextUpdate <= currentUpdate) + { + nextUpdate += NextFrameStep(framePeriod); + } + + ApplicationEvent::Dispatch(&applicationEvent); + RenderEvent::Dispatch(&renderEvent); + + if (watchEvent && isResizeOrExposeEvent) + { + pendingWatchRenderSkips++; + } + } + + if (exitedBlocking) + { + System::GCEnterBlocking(); + SDL_AtomicSet(&s_waitEventBlocking, 1); + } + } +#endif + int SDLApplication::Exec() { @@ -373,23 +488,106 @@ namespace lime if (!inBackground) { + bool skipImmediateRender = false; +#if defined(HX_WINDOWS) && !defined(HX_WINRT) + if (pendingWatchRenderSkips > 0) + { + pendingWatchRenderSkips--; + skipImmediateRender = true; + } - RenderEvent::Dispatch(&renderEvent); + if (SDL_AtomicGet(&s_waitEventBlocking)) + { + PumpOneFrameFromWatch(); + } + else +#endif + { + currentUpdate = SDL_GetTicks(); + bool frameDue = ((Sint32)(currentUpdate - nextUpdate) >= 0); + if (frameDue) + { + applicationEvent.type = UPDATE; + applicationEvent.deltaTime = currentUpdate - lastUpdate; + lastUpdate = currentUpdate; + + nextUpdate += NextFrameStep(framePeriod); + while (nextUpdate <= currentUpdate) + { + nextUpdate += NextFrameStep(framePeriod); + } + + ApplicationEvent::Dispatch(&applicationEvent); + } + + if (frameDue && !skipImmediateRender) + { + RenderEvent::Dispatch(&renderEvent); + } + } } break; case SDL_WINDOWEVENT_SIZE_CHANGED: + case SDL_WINDOWEVENT_RESIZED: + { - ProcessWindowEvent(event); + bool skipResizeDispatch = false; + bool skipImmediateRender = false; +#if defined(HX_WINDOWS) && !defined(HX_WINRT) + if (pendingResizeDispatchSkips > 0) + { + pendingResizeDispatchSkips--; + skipResizeDispatch = true; + } + if (pendingWatchRenderSkips > 0) + { + pendingWatchRenderSkips--; + skipImmediateRender = true; + } +#endif + if (!skipResizeDispatch) + { + ProcessWindowEvent(event); + } if (!inBackground) { +#if defined(HX_WINDOWS) && !defined(HX_WINRT) + if (SDL_AtomicGet(&s_waitEventBlocking)) + { + PumpOneFrameFromWatch(); + } + else +#endif + { + currentUpdate = SDL_GetTicks(); + bool frameDue = ((Sint32)(currentUpdate - nextUpdate) >= 0); + if (frameDue) + { + applicationEvent.type = UPDATE; + applicationEvent.deltaTime = currentUpdate - lastUpdate; + lastUpdate = currentUpdate; - RenderEvent::Dispatch(&renderEvent); + nextUpdate += NextFrameStep(framePeriod); + while (nextUpdate <= currentUpdate) + { + nextUpdate += NextFrameStep(framePeriod); + } + + ApplicationEvent::Dispatch(&applicationEvent); + } + + if (frameDue && !skipImmediateRender) + { + RenderEvent::Dispatch(&renderEvent); + } + } } break; + } case SDL_WINDOWEVENT_CLOSE: @@ -425,7 +623,12 @@ namespace lime active = true; lastUpdate = SDL_GetTicks(); nextUpdate = lastUpdate; + nextFrac = 0.0; firstTime = true; +#if defined(HX_WINDOWS) && !defined(HX_WINRT) + pendingResizeDispatchSkips = 0; + pendingWatchRenderSkips = 0; +#endif CalibrateSleepGuard(true); } @@ -902,6 +1105,7 @@ namespace lime break; case SDL_WINDOWEVENT_SIZE_CHANGED: + case SDL_WINDOWEVENT_RESIZED: windowEvent.type = WINDOW_RESIZE; windowEvent.width = event->window.data1; @@ -923,6 +1127,13 @@ namespace lime applicationEvent.type = EXIT; ApplicationEvent::Dispatch(&applicationEvent); +#if defined(HX_WINDOWS) && !defined(HX_WINRT) + if (modalWatchInstalled) + { + SDL_DelEventWatch(ModalEventWatch, this); + modalWatchInstalled = false; + } +#endif SDL_QuitSubSystem(initFlags); @@ -956,6 +1167,7 @@ namespace lime framePeriod = 1000.0; } + nextFrac = 0.0; s_busyWaitOnly = (framePeriod <= 2.0); } @@ -1116,6 +1328,7 @@ namespace lime #else bool isBlocking = false; + SDL_AtomicSet(&s_waitEventBlocking, 0); for (;;) { @@ -1128,19 +1341,28 @@ namespace lime case -1: if (isBlocking) + { + SDL_AtomicSet(&s_waitEventBlocking, 0); System::GCExitBlocking(); + } return 0; case 1: if (isBlocking) + { + SDL_AtomicSet(&s_waitEventBlocking, 0); System::GCExitBlocking(); + } return 1; default: if (!isBlocking) + { System::GCEnterBlocking(); + SDL_AtomicSet(&s_waitEventBlocking, 1); + } isBlocking = true; Uint32 now = SDL_GetTicks(); @@ -1153,7 +1375,10 @@ namespace lime event->type = SDL_USEREVENT; if (isBlocking) + { + SDL_AtomicSet(&s_waitEventBlocking, 0); System::GCExitBlocking(); + } return 1; } @@ -1179,14 +1404,20 @@ namespace lime { if (isBlocking) + { + SDL_AtomicSet(&s_waitEventBlocking, 0); System::GCExitBlocking(); + } return 1; } else if (waitResult == -1) { if (isBlocking) + { + SDL_AtomicSet(&s_waitEventBlocking, 0); System::GCExitBlocking(); + } return 0; } @@ -1207,13 +1438,19 @@ namespace lime case -1: if (isBlocking) + { + SDL_AtomicSet(&s_waitEventBlocking, 0); System::GCExitBlocking(); + } return 0; case 1: if (isBlocking) + { + SDL_AtomicSet(&s_waitEventBlocking, 0); System::GCExitBlocking(); + } return 1; default: @@ -1225,7 +1462,10 @@ namespace lime event->type = SDL_USEREVENT; if (isBlocking) + { + SDL_AtomicSet(&s_waitEventBlocking, 0); System::GCExitBlocking(); + } return 1; } diff --git a/project/src/backend/sdl/SDLApplication.h b/project/src/backend/sdl/SDLApplication.h index 679de5e932..3da9da7fd8 100644 --- a/project/src/backend/sdl/SDLApplication.h +++ b/project/src/backend/sdl/SDLApplication.h @@ -41,6 +41,10 @@ namespace lime { private: void HandleEvent (SDL_Event* event); +#if defined(HX_WINDOWS) && !defined(HX_WINRT) + void PumpOneFrameFromWatch (SDL_Event* watchEvent = 0); + static int ModalEventWatch (void* userdata, SDL_Event* event); +#endif void ProcessClipboardEvent (SDL_Event* event); void ProcessDropEvent (SDL_Event* event); void ProcessGamepadEvent (SDL_Event* event); @@ -51,6 +55,7 @@ namespace lime { void ProcessTextEvent (SDL_Event* event); void ProcessTouchEvent (SDL_Event* event); void ProcessWindowEvent (SDL_Event* event); + //void Tick(); int WaitEvent (SDL_Event* event); static void UpdateFrame (); @@ -77,6 +82,12 @@ namespace lime { TextEvent textEvent; TouchEvent touchEvent; WindowEvent windowEvent; +#if defined(HX_WINDOWS) && !defined(HX_WINRT) + bool modalWatchInstalled; + Uint32 mainThreadID; + int pendingResizeDispatchSkips; + int pendingWatchRenderSkips; +#endif }; @@ -84,4 +95,4 @@ namespace lime { } -#endif \ No newline at end of file +#endif diff --git a/project/src/backend/sdl/SDLWindow.cpp b/project/src/backend/sdl/SDLWindow.cpp index 6e6459f54f..a6bbda3eeb 100644 --- a/project/src/backend/sdl/SDLWindow.cpp +++ b/project/src/backend/sdl/SDLWindow.cpp @@ -30,6 +30,159 @@ namespace lime { static bool displayModeSet = false; +#if defined (HX_WINDOWS) && !defined (HX_WINRT) + static const wchar_t* LIME_SDL_OLD_RESIZE_WNDPROC_PROP = L"LimeSDL.OldResizeWndProc"; + static const wchar_t* LIME_SDL_WINDOW_ID_PROP = L"LimeSDL.WindowID"; + static const wchar_t* LIME_SDL_LAST_RESIZE_WIDTH_PROP = L"LimeSDL.LastResizeWidth"; + static const wchar_t* LIME_SDL_LAST_RESIZE_HEIGHT_PROP = L"LimeSDL.LastResizeHeight"; + static const wchar_t* LIME_SDL_LAST_RESIZE_TICK_PROP = L"LimeSDL.LastResizeTick"; + static const Uint32 LIME_SDL_MIN_RESIZE_PUSH_INTERVAL_MS = 8; + + static bool ShouldQueueLiveResizeEvent (HWND hwnd, int width, int height, bool throttled) { + + if (width < 1 || height < 1) return false; + + int lastWidth = (int)(INT_PTR)GetPropW (hwnd, LIME_SDL_LAST_RESIZE_WIDTH_PROP); + int lastHeight = (int)(INT_PTR)GetPropW (hwnd, LIME_SDL_LAST_RESIZE_HEIGHT_PROP); + if (width == lastWidth && height == lastHeight) return false; + + Uint32 now = SDL_GetTicks (); + if (throttled) { + + Uint32 lastTick = (Uint32)(UINT_PTR)GetPropW (hwnd, LIME_SDL_LAST_RESIZE_TICK_PROP); + if (lastTick != 0 && (Uint32)(now - lastTick) < LIME_SDL_MIN_RESIZE_PUSH_INTERVAL_MS) { + + return false; + + } + + } + + SetPropW (hwnd, LIME_SDL_LAST_RESIZE_WIDTH_PROP, (HANDLE)(INT_PTR)width); + SetPropW (hwnd, LIME_SDL_LAST_RESIZE_HEIGHT_PROP, (HANDLE)(INT_PTR)height); + SetPropW (hwnd, LIME_SDL_LAST_RESIZE_TICK_PROP, (HANDLE)(UINT_PTR)now); + return true; + + } + + static void PushLiveResizeEvent (HWND hwnd, int width, int height, bool throttled) { + + if (!ShouldQueueLiveResizeEvent (hwnd, width, height, throttled)) return; + + Uint32 windowID = (Uint32)(UINT_PTR)GetPropW (hwnd, LIME_SDL_WINDOW_ID_PROP); + if (!windowID) return; + + SDL_Event event; + SDL_zero (event); + event.type = SDL_WINDOWEVENT; + event.window.event = SDL_WINDOWEVENT_SIZE_CHANGED; + event.window.windowID = windowID; + event.window.data1 = width; + event.window.data2 = height; + SDL_PushEvent (&event); + + } + + static void PushLiveResizeEventFromRect (HWND hwnd, const RECT* windowRect) { + + if (!windowRect) return; + + RECT currentWindowRect; + RECT currentClientRect; + if (!GetWindowRect (hwnd, ¤tWindowRect)) return; + if (!GetClientRect (hwnd, ¤tClientRect)) return; + + POINT currentClientTopLeft = { currentClientRect.left, currentClientRect.top }; + POINT currentClientBottomRight = { currentClientRect.right, currentClientRect.bottom }; + if (!ClientToScreen (hwnd, ¤tClientTopLeft) || !ClientToScreen (hwnd, ¤tClientBottomRight)) return; + + int nonClientWidth = (currentWindowRect.right - currentWindowRect.left) - (currentClientBottomRight.x - currentClientTopLeft.x); + int nonClientHeight = (currentWindowRect.bottom - currentWindowRect.top) - (currentClientBottomRight.y - currentClientTopLeft.y); + if (nonClientWidth < 0) nonClientWidth = 0; + if (nonClientHeight < 0) nonClientHeight = 0; + + int width = (windowRect->right - windowRect->left) - nonClientWidth; + int height = (windowRect->bottom - windowRect->top) - nonClientHeight; + PushLiveResizeEvent (hwnd, width, height, true); + + } + + static LRESULT CALLBACK LimeResizeWndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { + + if (message == WM_SIZING) { + + PushLiveResizeEventFromRect (hwnd, (const RECT*)lParam); + + } else if (message == WM_SIZE) { + + if (wParam != SIZE_MINIMIZED) { + + // Always send WM_SIZE updates (especially final size) without throttling. + PushLiveResizeEvent (hwnd, LOWORD (lParam), HIWORD (lParam), false); + + } + + } + + WNDPROC oldWndProc = (WNDPROC)GetPropW (hwnd, LIME_SDL_OLD_RESIZE_WNDPROC_PROP); + if (oldWndProc) { + + return CallWindowProc (oldWndProc, hwnd, message, wParam, lParam); + + } + + return DefWindowProc (hwnd, message, wParam, lParam); + + } + + static void InstallResizeEventHook (SDL_Window* sdlWindow) { + + if (!sdlWindow) return; + + SDL_SysWMinfo wminfo; + SDL_VERSION (&wminfo.version); + if (SDL_GetWindowWMInfo (sdlWindow, &wminfo) != 1) return; + + HWND hwnd = wminfo.info.win.window; + if (!hwnd) return; + if (GetPropW (hwnd, LIME_SDL_OLD_RESIZE_WNDPROC_PROP)) return; + + SetLastError (0); + LONG_PTR previous = SetWindowLongPtr (hwnd, GWLP_WNDPROC, (LONG_PTR)LimeResizeWndProc); + if (previous == 0 && GetLastError () != 0) return; + + SetPropW (hwnd, LIME_SDL_OLD_RESIZE_WNDPROC_PROP, (HANDLE)previous); + SetPropW (hwnd, LIME_SDL_WINDOW_ID_PROP, (HANDLE)(UINT_PTR)SDL_GetWindowID (sdlWindow)); + + } + + static void RestoreResizeEventHook (SDL_Window* sdlWindow) { + + if (!sdlWindow) return; + + SDL_SysWMinfo wminfo; + SDL_VERSION (&wminfo.version); + if (SDL_GetWindowWMInfo (sdlWindow, &wminfo) != 1) return; + + HWND hwnd = wminfo.info.win.window; + if (!hwnd) return; + + WNDPROC oldWndProc = (WNDPROC)GetPropW (hwnd, LIME_SDL_OLD_RESIZE_WNDPROC_PROP); + if (oldWndProc) { + + SetWindowLongPtr (hwnd, GWLP_WNDPROC, (LONG_PTR)oldWndProc); + + } + + RemovePropW (hwnd, LIME_SDL_WINDOW_ID_PROP); + RemovePropW (hwnd, LIME_SDL_OLD_RESIZE_WNDPROC_PROP); + RemovePropW (hwnd, LIME_SDL_LAST_RESIZE_WIDTH_PROP); + RemovePropW (hwnd, LIME_SDL_LAST_RESIZE_HEIGHT_PROP); + RemovePropW (hwnd, LIME_SDL_LAST_RESIZE_TICK_PROP); + + } +#endif + SDLWindow::SDLWindow (Application* application, int width, int height, int flags, const char* title) { @@ -167,6 +320,10 @@ namespace lime { } + #if defined (HX_WINDOWS) && !defined (HX_WINRT) + InstallResizeEventHook (sdlWindow); + #endif + #if defined (HX_WINDOWS) && !defined (HX_WINRT) HINSTANCE handle = ::GetModuleHandle (nullptr); @@ -299,6 +456,10 @@ namespace lime { if (sdlWindow) { + #if defined (HX_WINDOWS) && !defined (HX_WINRT) + RestoreResizeEventHook (sdlWindow); + #endif + SDL_DestroyWindow (sdlWindow); sdlWindow = 0; @@ -352,6 +513,10 @@ namespace lime { if (sdlWindow) { + #if defined (HX_WINDOWS) && !defined (HX_WINRT) + RestoreResizeEventHook (sdlWindow); + #endif + SDL_DestroyWindow (sdlWindow); sdlWindow = 0;