Skip to content

Commit

Permalink
Finish Work on renderCallbackDuringResize
Browse files Browse the repository at this point in the history
  • Loading branch information
pthom committed Jun 2, 2024
1 parent e7602c2 commit 3ff11ce
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 40 deletions.
91 changes: 68 additions & 23 deletions src/hello_imgui/internal/backend_impls/abstract_runner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ void AbstractRunner::ChangeWindowSize(HelloImGui::ScreenSize windowSize)
{
auto bounds = mBackendWindowHelper->GetWindowBounds(mWindow);
bounds.size = windowSize;
this->setWasWindowResizedByCodeDuringThisFrame();
mBackendWindowHelper->SetWindowBounds(mWindow, bounds);
}

Expand Down Expand Up @@ -437,6 +438,7 @@ void AbstractRunner::MakeWindowSizeRelativeTo96Ppi_IfRequired()
ForDim2(dim)
bounds.position[dim] = (int)((float)bounds.position[dim] * scaleFactor);
}
setWasWindowResizedByCodeDuringThisFrame();
mBackendWindowHelper->SetWindowBounds(mWindow, bounds);
}
}
Expand Down Expand Up @@ -700,8 +702,12 @@ void AbstractRunner::Setup()

PrepareWindowGeometry();

auto renderCallbackDuringResize = [this]() { CreateFramesAndRender(true); };
Impl_CreateWindow(renderCallbackDuringResize);
auto fnRenderCallbackDuringResize = [this]()
{
if (! mWasWindowResizedByCodeDuringThisFrame)
CreateFramesAndRender(true);
};
Impl_CreateWindow(fnRenderCallbackDuringResize);

#ifdef HELLOIMGUI_HAS_OPENGL
if (params.rendererBackendType == RendererBackendType::OpenGL3)
Expand Down Expand Up @@ -805,7 +811,17 @@ void AbstractRunner::RenderGui()
if (params.appWindowParams.borderless) // Need to add params.appWindowParams.borderlessResizable
{
#if !defined(HELLOIMGUI_MOBILEDEVICE) && !defined(__EMSCRIPTEN__)
bool shouldClose = HandleBorderlessMovable(mWindow, mBackendWindowHelper.get(), params);
mWasWindowResizedByCodeDuringThisFrame = false;
auto fnGetWindowBounds = [this]() -> ScreenBounds
{
return mBackendWindowHelper->GetWindowBounds(mWindow);
};
auto fnSetWindowBounds = [this](const ScreenBounds& bounds)
{
setWasWindowResizedByCodeDuringThisFrame();
mBackendWindowHelper->SetWindowBounds(mWindow, bounds);
};
bool shouldClose = HandleBorderlessMovable(fnGetWindowBounds, fnSetWindowBounds, params);
if (shouldClose)
params.appShallExit = true;
#endif
Expand All @@ -824,8 +840,9 @@ void AbstractRunner::RenderGui()
{
ImGui::EndGroup();
ImVec2 userWidgetsSize = ImGui::GetItemRectSize();
mGeometryHelper->TrySetWindowSize(mBackendWindowHelper.get(), mWindow, userWidgetsSize);
mWasWindowAutoResizedOnPreviousFrame = true;
mGeometryHelper->TrySetWindowSize(
mBackendWindowHelper.get(), mWindow, userWidgetsSize,
this->setWasWindowResizedByCodeDuringThisFrame);
}
}

Expand Down Expand Up @@ -931,14 +948,14 @@ void AbstractRunner::CreateFramesAndRender(bool skipPollEvents)
{
// The window was resized on last frame
// We should now recenter the window if needed and ensure it fits on the monitor
mGeometryHelper->EnsureWindowFitsMonitor(mBackendWindowHelper.get(), mWindow);
mGeometryHelper->EnsureWindowFitsMonitor(mBackendWindowHelper.get(), mWindow, this->setWasWindowResizedByCodeDuringThisFrame);

// if this is the third frame, and the user wanted a centered window, let's recenter it
// we do this on the third frame (mIdxFrame == 2), since the initial autosize happens on the second
// (see WantAutoSize())
if (params.appWindowParams.windowGeometry.positionMode == HelloImGui::WindowPositionMode::MonitorCenter &&
(mIdxFrame == 2))
mGeometryHelper->CenterWindowOnMonitor(mBackendWindowHelper.get(), mWindow);
mGeometryHelper->CenterWindowOnMonitor(mBackendWindowHelper.get(), mWindow, this->setWasWindowResizedByCodeDuringThisFrame);

mWasWindowAutoResizedOnPreviousFrame = false;
params.appWindowParams.windowGeometry.resizeAppWindowAtNextFrame = false;
Expand Down Expand Up @@ -1115,14 +1132,33 @@ void AbstractRunner::CreateFramesAndRender(bool skipPollEvents)
RenderGui();
};

auto fnCallTestEngineCallbackPostSwap = [this]()
{
#ifdef HELLOIMGUI_WITH_TEST_ENGINE
// TestEngineCallbacks::PostSwap() handles the GIL in its own way,
// it can not be called inside SCOPED_RELEASE_GIL_ON_MAIN_THREAD
if (params.useImGuiTestEngine)
{
TestEngineCallbacks::PostSwap();
}
#endif
};

// ======================================================================================
// Real work - Lambdas calls
//
// Below, we call the lambdas defined above
//
// ======================================================================================
//
// Gotcha due to the integration of ImGui Test Engine in Python:
// They are two important gotchas to know here
// 1. ImGui test engine & coroutine threading
// 2. Possible reentrance into this function when resizing the window
// (see fnHandleIdlingAndPollEvents_MayReRenderDuringResize_GotchaReentrant)
//
//
// 1. Gotcha due to the integration of ImGui Test Engine in Python:
// ----------------------------------------------------------------
// Within ImGui test engine there are two threads!
// One thread is the main thread, and the second is the "coroutine" thread.
// They run like a coroutine and actually will never run in parallel!
Expand All @@ -1140,13 +1176,30 @@ void AbstractRunner::CreateFramesAndRender(bool skipPollEvents)
// Inside these blocks, it is strictly forbidden to call any user callback
// (since they might run Python code on the main thread)
//
// // For more details, see
// // external/imgui_test_engine/imgui_test_engine/imgui_test_engine/imgui_te_python_gil.jpg
//
// Also, two ImGui methods handle the test engine and its coroutine + thread switches:
// => They should not be in a block SCOPED_RELEASE_GIL_ON_MAIN_THREAD
// ImGui::NewFrame();
// TestEngineCallbacks::PostSwap
// And fnHandleIdlingAndPollEvents_MayReRenderDuringResize_GotchaReentrant
// shall also not be in a block SCOPED_RELEASE_GIL_ON_MAIN_THREAD
// (see second gotcha below)
//
//
// 2. Gotcha / possible re-entrance into this function when resizing the window
// -----------------------------------------------------------------------------
// There is a severe gotcha inside GLFW and SDL: PollEvent is supposed to
// return immediately, but it doesn't when resizing the window!
// If you do nothing, the window content is "stretching" during the resize
// but not update is done.
// Instead, you have to subscribe to a kind of special "mid-resize" event,
// and then call the render function yourself.
//
// For more details, see
// external/imgui_test_engine/imgui_test_engine/imgui_test_engine/imgui_te_python_gil.jpg
// See IBackendWindowHelper::CreateWindow(AppWindowParams &info, const BackendOptions& backendOptions,
// std::function<void()> renderCallbackDuringResize) = 0;
// Where renderCallbackDuringResize is set to CreateFramesAndRender(skipPollEvents=true)

// Will display on remote server if needed
mRemoteDisplayHandler.Heartbeat_PreImGuiNewFrame();
Expand All @@ -1170,13 +1223,10 @@ void AbstractRunner::CreateFramesAndRender(bool skipPollEvents)
// return immediately, but it doesn't when resizing the window!
// Instead, you have to subscribe to a kind of special "mid-resize" event,
// and then call the render function yourself.
{
//SCOPED_RELEASE_GIL_ON_MAIN_THREAD; ??
if (!skipPollEvents)
fnHandleIdlingAndPollEvents_MayReRenderDuringResize_GotchaReentrant(); // GOTCHA!!!
}
if (!skipPollEvents)
fnHandleIdlingAndPollEvents_MayReRenderDuringResize_GotchaReentrant();

_UpdateFrameRateStats();
_UpdateFrameRateStats(); // not in a SCOPED_RELEASE_GIL_ON_MAIN_THREAD, because it is very fast

fnLoadAdditionalFontDuringExecution_UserCallback();

Expand All @@ -1192,7 +1242,7 @@ void AbstractRunner::CreateFramesAndRender(bool skipPollEvents)
// so that it can *NOT* be called inside SCOPED_RELEASE_GIL_ON_MAIN_THREAD
ImGui::NewFrame();

fnCheckOpenGlErrorOnFirstFrame_WarnPotentialFontError();
fnCheckOpenGlErrorOnFirstFrame_WarnPotentialFontError(); // not in a SCOPED_RELEASE_GIL_ON_MAIN_THREAD, because it is very fast and rare

fnDrawCustomBackgroundOrClearColor_UserCallback();

Expand All @@ -1214,14 +1264,9 @@ void AbstractRunner::CreateFramesAndRender(bool skipPollEvents)
if (params.callbacks.AfterSwap)
params.callbacks.AfterSwap();

#ifdef HELLOIMGUI_WITH_TEST_ENGINE
// TestEngineCallbacks::PostSwap() handles the GIL in its own way,
// it can not be called inside SCOPED_RELEASE_GIL_ON_MAIN_THREAD
if (params.useImGuiTestEngine)
{
TestEngineCallbacks::PostSwap();
}
#endif
fnCallTestEngineCallbackPostSwap();

if (!mRemoteDisplayHandler.CanQuitApp())
params.appShallExit = false;
Expand Down
8 changes: 8 additions & 0 deletions src/hello_imgui/internal/backend_impls/abstract_runner.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,14 @@ class AbstractRunner
int mIdxFrame = 0;
bool mWasWindowAutoResizedOnPreviousFrame = false;

// Differentiate between cases where the window was resized by code
// and cases where the window was resized by the user
// (in which we have a gotcha, because PollEvents() will *block*
// until the user releases the mouse button)
bool mWasWindowResizedByCodeDuringThisFrame = false;
std::function<void()> setWasWindowResizedByCodeDuringThisFrame =
[&]() { mWasWindowResizedByCodeDuringThisFrame = true; };

// Callbacks related to the rendering backend (OpenGL, ...)
RenderingCallbacksPtr mRenderingBackendCallbacks;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,9 @@ namespace HelloImGui
///////////////////////////////


void WindowGeometryHelper::TrySetWindowSize(BackendApi::IBackendWindowHelper *backendWindowHelper, BackendApi::WindowPointer window, ImVec2 userWidgetsSize)
void WindowGeometryHelper::TrySetWindowSize(
BackendApi::IBackendWindowHelper *backendWindowHelper, BackendApi::WindowPointer window, ImVec2 userWidgetsSize,
std::function<void()> fnBeforeChangingWindowBounds)
{
int widgetsMargin = 6;

Expand All @@ -162,6 +164,7 @@ namespace HelloImGui
auto windowBounds = backendWindowHelper->GetWindowBounds(window);
windowBounds.size = computedSize;

fnBeforeChangingWindowBounds();
backendWindowHelper->SetWindowBounds(window, windowBounds);
}

Expand Down Expand Up @@ -206,7 +209,9 @@ namespace HelloImGui
return bestMonitorIdx;
}

void WindowGeometryHelper::EnsureWindowFitsMonitor(BackendApi::IBackendWindowHelper *backendWindowHelper, BackendApi::WindowPointer window)
void WindowGeometryHelper::EnsureWindowFitsMonitor(
BackendApi::IBackendWindowHelper *backendWindowHelper, BackendApi::WindowPointer window,
std::function<void()> fnBeforeChangingWindowBounds)
{
auto currentMonitorWorkArea = GetCurrentMonitorWorkArea(backendWindowHelper, window);

Expand All @@ -219,17 +224,20 @@ namespace HelloImGui
auto currentWindowBoundsNew = currentMonitorWorkArea.EnsureWindowFitsThisMonitor(currentWindowBounds);
if ( !(currentWindowBoundsNew == currentWindowBounds))
{
fnBeforeChangingWindowBounds();
backendWindowHelper->SetWindowBounds(window, currentWindowBoundsNew);
}
}

void WindowGeometryHelper::CenterWindowOnMonitor(BackendApi::IBackendWindowHelper* backendWindowHelper, BackendApi::WindowPointer window)
void WindowGeometryHelper::CenterWindowOnMonitor(
BackendApi::IBackendWindowHelper* backendWindowHelper, BackendApi::WindowPointer window,
std::function<void()> fnBeforeChangingWindowBounds)
{
ScreenBounds windowBounds = backendWindowHelper->GetWindowBounds(window);
ScreenBounds currentMonitorWorkArea = GetCurrentMonitorWorkArea(backendWindowHelper, window);
ScreenPosition newWindowPosition = currentMonitorWorkArea.WinPositionCentered(windowBounds.size);
windowBounds.position = newWindowPosition;
fnBeforeChangingWindowBounds();
backendWindowHelper->SetWindowBounds(window, windowBounds);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ namespace HelloImGui
bool HasInitialWindowSizeInfo() const;
ScreenBounds AppWindowBoundsInitial(const std::vector<ScreenBounds>& allMonitorsWorkAreas);

void EnsureWindowFitsMonitor(BackendApi::IBackendWindowHelper* helper, BackendApi::WindowPointer window);
void CenterWindowOnMonitor(BackendApi::IBackendWindowHelper* helper, BackendApi::WindowPointer window);
void EnsureWindowFitsMonitor(BackendApi::IBackendWindowHelper* helper, BackendApi::WindowPointer window, std::function<void()> fnBeforeChangingWindowBounds);
void CenterWindowOnMonitor(BackendApi::IBackendWindowHelper* helper, BackendApi::WindowPointer window, std::function<void()> fnBeforeChangingWindowBounds);
ScreenBounds GetCurrentMonitorWorkArea(BackendApi::IBackendWindowHelper* backendWindowHelper,
BackendApi::WindowPointer window);

// Will set the window size, after making sure it fits on the current screen
void TrySetWindowSize(BackendApi::IBackendWindowHelper *backendWindowHelper, BackendApi::WindowPointer window, ImVec2 userWidgetsSize);
void TrySetWindowSize(BackendApi::IBackendWindowHelper *backendWindowHelper, BackendApi::WindowPointer window, ImVec2 userWidgetsSize, std::function<void()> fnBeforeChangingWindowBounds);

private:
int GetMonitorIndexFromWindowPosition(BackendApi::IBackendWindowHelper *backendWindowHelper, const ScreenPosition& windowPosition) const;
Expand Down
16 changes: 8 additions & 8 deletions src/hello_imgui/internal/borderless_movable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
namespace HelloImGui
{
bool HandleBorderlessMovable(
BackendApi::WindowPointer window,
BackendApi::IBackendWindowHelper * backendWindowHelper,
std::function<ScreenBounds()> fnGetWindowBounds,
std::function<void(ScreenBounds)> fnSetWindowBounds,
const HelloImGui::RunnerParams& runnerParams
)
{
Expand Down Expand Up @@ -78,10 +78,10 @@ namespace HelloImGui
mouseDragLastPos = mousePos - dragDelta;
}

auto windowBounds = backendWindowHelper->GetWindowBounds(window);
auto windowBounds = fnGetWindowBounds();
windowBounds.position[0] += (int)(dragDelta.x);
windowBounds.position[1] += (int)(dragDelta.y);
backendWindowHelper->SetWindowBounds(window, windowBounds);
fnSetWindowBounds(windowBounds);
}

// Set mouse cursor: probably not visible for moving (the cursor will be the classic arrow)
Expand Down Expand Up @@ -181,7 +181,7 @@ namespace HelloImGui
{
ImVec2 dragDelta = ImGui::GetMouseDragDelta(0);
ImGui::ResetMouseDragDelta(0);
auto windowBounds = backendWindowHelper->GetWindowBounds(window);
auto windowBounds = fnGetWindowBounds();
windowBounds.size[0] += (int)dragDelta.x;
windowBounds.size[1] += (int)dragDelta.y;
if (windowBounds.size[0] < minAcceptableWindowSize)
Expand All @@ -194,15 +194,15 @@ namespace HelloImGui
windowBounds.size[1] = minAcceptableWindowSize;
isDragging = false;
}
backendWindowHelper->SetWindowBounds(window, windowBounds);
fnSetWindowBounds(windowBounds);
ImGui::ResetMouseDragDelta(ImGuiMouseButton_Left);
}

// With borderless windows, SDL is able to let window resize themselves to 0,
// and they are then impossible to resize! So we force a minimum size here:
{
bool tooSmall = false;
auto windowBounds = backendWindowHelper->GetWindowBounds(window);
auto windowBounds = fnGetWindowBounds();
if (windowBounds.size[0] < minAcceptableWindowSize)
{
windowBounds.size[0] = minAcceptableWindowSize;
Expand All @@ -214,7 +214,7 @@ namespace HelloImGui
tooSmall = true;
}
if (tooSmall)
backendWindowHelper->SetWindowBounds(window, windowBounds);
fnSetWindowBounds(windowBounds);
}

// Set mouse cursor: (not visible under glfw < 3.4, which does not implement this cursor)
Expand Down
4 changes: 2 additions & 2 deletions src/hello_imgui/internal/borderless_movable.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ namespace HelloImGui
// This function is called by the backend to handle borderless movable windows
// it returns true if the window should be closed
bool HandleBorderlessMovable(
BackendApi::WindowPointer window,
BackendApi::IBackendWindowHelper * backendWindowHelper,
std::function<ScreenBounds()> fnGetWindowBounds,
std::function<void(ScreenBounds)> fnSetWindowBounds,
const HelloImGui::RunnerParams& runnerParams
);
}

0 comments on commit 3ff11ce

Please sign in to comment.