Skip to content

Commit 3dba30f

Browse files
andy31415andreilitvin
authored andcommitted
Place IMGUI UIs into a separate common module (#25131)
* Modularize imgui - split windows and ready to add into any app * Comment update * Added extra assert * Switch to using a work scheduler instead of a stack lock for state updates * Restyle * typo fix * Update to sem_wait * Restyle * Generate shutdown event before stoppign the event loop task * Restyle * Update the code to not reference Server (would not compile otherwise) * Use a semaphore to wait for UI to send the shutdown. Also update comments * Restyle --------- Co-authored-by: Andrei Litvin <andreilitvin@google.com>
1 parent 7cdf887 commit 3dba30f

File tree

12 files changed

+844
-558
lines changed

12 files changed

+844
-558
lines changed

examples/common/imgui_ui/BUILD.gn

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Copyright (c) 2023 Project CHIP Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
import("//build_overrides/chip.gni")
15+
16+
import("${chip_root}/build/chip/tools.gni")
17+
import("${chip_root}/third_party/imgui/imgui.gni")
18+
19+
config("imgui_ui_config") {
20+
# allow including via 'imgui_ui/....'
21+
include_dirs = [ "../" ]
22+
}
23+
24+
static_library("imgui_ui") {
25+
sources = [
26+
"ui.cpp",
27+
"ui.h",
28+
]
29+
30+
deps = [
31+
"${chip_root}/examples/common/imgui_ui/windows",
32+
"${chip_root}/examples/platform/linux:app-main",
33+
"${chip_root}/src/lib/support",
34+
"${chip_root}/third_party/imgui",
35+
]
36+
37+
public_configs = [
38+
":imgui_ui_config",
39+
"${chip_root}/third_party/imgui:imgui_config", # propagate enabling flag
40+
]
41+
}

examples/common/imgui_ui/ui.cpp

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
/*
2+
*
3+
* Copyright (c) 2023 Project CHIP Authors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
#include "ui.h"
18+
19+
#include <SDL.h>
20+
#include <SDL_opengl.h>
21+
#include <imgui.h>
22+
#include <imgui_impl_opengl3.h>
23+
#include <imgui_impl_sdl2.h>
24+
25+
#include <lib/support/logging/CHIPLogging.h>
26+
27+
#include <atomic>
28+
#include <thread>
29+
30+
namespace example {
31+
namespace Ui {
32+
namespace {
33+
34+
// Controls running the UI event loop
35+
std::atomic<bool> gUiRunning{ false };
36+
37+
void UiInit(SDL_GLContext * gl_context, SDL_Window ** window)
38+
{
39+
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0)
40+
{
41+
ChipLogError(AppServer, "SDL Init Error: %s\n", SDL_GetError());
42+
return;
43+
}
44+
45+
#if defined(__APPLE__)
46+
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac
47+
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
48+
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
49+
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
50+
#else
51+
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
52+
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
53+
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
54+
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
55+
#endif
56+
57+
#ifdef SDL_HINT_IME_SHOW_UI
58+
SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
59+
#endif
60+
61+
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
62+
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
63+
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
64+
SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
65+
*window = SDL_CreateWindow("Light UI", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, window_flags);
66+
*gl_context = SDL_GL_CreateContext(*window);
67+
SDL_GL_MakeCurrent(*window, *gl_context);
68+
SDL_GL_SetSwapInterval(1); // Enable vsync
69+
70+
// Setup Dear ImGui context
71+
IMGUI_CHECKVERSION();
72+
ImGui::CreateContext();
73+
ImGuiIO & io = ImGui::GetIO();
74+
(void) io;
75+
// io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
76+
// io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
77+
78+
ImGui::StyleColorsDark();
79+
80+
// Setup Platform/Renderer backends
81+
ImGui_ImplSDL2_InitForOpenGL(*window, *gl_context);
82+
ImGui_ImplOpenGL3_Init();
83+
}
84+
85+
void UiShutdown(SDL_GLContext * gl_context, SDL_Window ** window)
86+
{
87+
ImGui_ImplOpenGL3_Shutdown();
88+
ImGui_ImplSDL2_Shutdown();
89+
ImGui::DestroyContext();
90+
91+
SDL_GL_DeleteContext(*gl_context);
92+
SDL_DestroyWindow(*window);
93+
SDL_Quit();
94+
}
95+
96+
void EventLoop(ImguiUi * ui)
97+
{
98+
gUiRunning = true;
99+
SDL_GLContext gl_context;
100+
SDL_Window * window = nullptr;
101+
102+
UiInit(&gl_context, &window);
103+
104+
ImGuiIO & io = ImGui::GetIO();
105+
106+
while (gUiRunning.load())
107+
{
108+
SDL_Event event;
109+
while (SDL_PollEvent(&event))
110+
{
111+
ImGui_ImplSDL2_ProcessEvent(&event);
112+
if ((event.type == SDL_QUIT) ||
113+
(event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE &&
114+
event.window.windowID == SDL_GetWindowID(window)))
115+
{
116+
gUiRunning = false;
117+
}
118+
}
119+
120+
ImGui_ImplOpenGL3_NewFrame();
121+
ImGui_ImplSDL2_NewFrame();
122+
ImGui::NewFrame();
123+
124+
ui->UpdateState();
125+
ui->Render();
126+
127+
// rendering
128+
ImGui::Render();
129+
glViewport(0, 0, (int) io.DisplaySize.x, (int) io.DisplaySize.y);
130+
glClearColor(0, 0, 0, 0);
131+
glClear(GL_COLOR_BUFFER_BIT);
132+
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
133+
SDL_GL_SwapWindow(window);
134+
}
135+
136+
UiShutdown(&gl_context, &window);
137+
138+
ChipLogProgress(AppServer, "UI thread Stopped...");
139+
}
140+
141+
} // namespace
142+
143+
void ImguiUi::RunMainLoop()
144+
{
145+
// Guaranteed to be on the main task (no chip event loop started yet)
146+
ChipLoopLoadInitialState();
147+
148+
// Platform event loop will be on a separate thread,
149+
// while the event UI loop will be on the main thread.
150+
chip::DeviceLayer::PlatformMgr().StartEventLoopTask();
151+
152+
// SignalSafeStopMainLoop will stop this loop below
153+
// or the loop exits by itself when processing a SDL
154+
// exit (generally by clicking the window close icon).
155+
EventLoop(this);
156+
157+
// ensure shutdown events are generated (generally basic cluster
158+
// will send a shutdown event to subscribers).
159+
//
160+
// We attempt to wait for finish as the event will be sent sync.
161+
// Since the Main loop is stopped, there will be no MRP, however at least
162+
// one event is attempted to be sent.
163+
chip::DeviceLayer::PlatformMgr().ScheduleWork(
164+
[](intptr_t arg) {
165+
chip::DeviceLayer::PlatformMgr().HandleServerShuttingDown();
166+
sem_t * semaphore = reinterpret_cast<sem_t *>(arg);
167+
sem_post(semaphore); // notify complete
168+
},
169+
reinterpret_cast<intptr_t>(&mChipLoopWaitSemaphore));
170+
sem_wait(&mChipLoopWaitSemaphore);
171+
172+
// Stop the chip main loop as well. This is expected to
173+
// wait for the task to finish.
174+
chip::DeviceLayer::PlatformMgr().StopEventLoopTask();
175+
}
176+
177+
void ImguiUi::SignalSafeStopMainLoop()
178+
{
179+
gUiRunning = false;
180+
}
181+
182+
void ImguiUi::ChipLoopStateUpdate()
183+
{
184+
assertChipStackLockedByCurrentThread();
185+
for (auto it = mWindows.begin(); it != mWindows.end(); it++)
186+
{
187+
(*it)->UpdateState();
188+
}
189+
}
190+
191+
void ImguiUi::ChipLoopLoadInitialState()
192+
{
193+
assertChipStackLockedByCurrentThread();
194+
for (auto it = mWindows.begin(); it != mWindows.end(); it++)
195+
{
196+
(*it)->LoadInitialState();
197+
}
198+
}
199+
200+
void ImguiUi::Render()
201+
{
202+
for (auto it = mWindows.begin(); it != mWindows.end(); it++)
203+
{
204+
(*it)->Render();
205+
}
206+
}
207+
208+
void ImguiUi::ChipLoopUpdateCallback(intptr_t self)
209+
{
210+
ImguiUi * _this = reinterpret_cast<ImguiUi *>(self);
211+
_this->ChipLoopStateUpdate();
212+
sem_post(&_this->mChipLoopWaitSemaphore); // notify complete
213+
}
214+
215+
void ImguiUi::UpdateState()
216+
{
217+
CHIP_ERROR err = chip::DeviceLayer::PlatformMgr().ScheduleWork(&ChipLoopUpdateCallback, reinterpret_cast<intptr_t>(this));
218+
219+
if (err != CHIP_NO_ERROR)
220+
{
221+
ChipLogError(AppServer, "Failed to schedule state update: %" CHIP_ERROR_FORMAT, err.Format());
222+
return;
223+
}
224+
225+
// ensure update is done when exiting
226+
sem_wait(&mChipLoopWaitSemaphore);
227+
}
228+
229+
} // namespace Ui
230+
} // namespace example

examples/common/imgui_ui/ui.h

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
*
3+
* Copyright (c) 2023 Project CHIP Authors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
#pragma once
18+
19+
#include <AppMain.h>
20+
#include <imgui_ui/windows/window.h>
21+
22+
#include <semaphore.h>
23+
24+
#include <list>
25+
#include <memory>
26+
27+
namespace example {
28+
namespace Ui {
29+
30+
/**
31+
* Supports showing a UI using ImGUI
32+
*
33+
* The UI supports several windows, such as QR codes or device control.
34+
*/
35+
class ImguiUi : public AppMainLoopImplementation
36+
{
37+
public:
38+
ImguiUi() { sem_init(&mChipLoopWaitSemaphore, 0 /* shared */, 0); }
39+
virtual ~ImguiUi() { sem_destroy(&mChipLoopWaitSemaphore); }
40+
41+
void AddWindow(std::unique_ptr<Window> window) { mWindows.push_back(std::move(window)); }
42+
43+
void UpdateState(); // runs a state update from ember/app
44+
void Render(); // render windows to screen
45+
46+
// AppMainLoopImplementation
47+
void RunMainLoop() override;
48+
void SignalSafeStopMainLoop() override;
49+
50+
private:
51+
// First initial state load
52+
void ChipLoopLoadInitialState();
53+
54+
// Updates the window states. Run in the CHIP main loop (has access
55+
// to CHIP API calls)
56+
void ChipLoopStateUpdate();
57+
58+
sem_t mChipLoopWaitSemaphore;
59+
std::list<std::unique_ptr<Window>> mWindows;
60+
61+
static void ChipLoopUpdateCallback(intptr_t self);
62+
};
63+
64+
} // namespace Ui
65+
} // namespace example
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Copyright (c) 2023 Project CHIP Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
import("//build_overrides/chip.gni")
15+
16+
import("${chip_root}/build/chip/tools.gni")
17+
import("${chip_root}/third_party/imgui/imgui.gni")
18+
19+
source_set("windows") {
20+
sources = [ "window.h" ]
21+
}
22+
23+
static_library("qrcode") {
24+
sources = [
25+
"qrcode.cpp",
26+
"qrcode.h",
27+
]
28+
29+
deps = [
30+
":windows",
31+
"${chip_root}/examples/common/QRCode",
32+
"${chip_root}/examples/platform/linux:app-main",
33+
"${chip_root}/src/app/server",
34+
"${chip_root}/src/setup_payload",
35+
"${chip_root}/third_party/imgui",
36+
]
37+
38+
public_configs =
39+
[ "${chip_root}/examples/common/QRCode:qrcode-common_config" ]
40+
}
41+
42+
static_library("light") {
43+
sources = [
44+
"light.cpp",
45+
"light.h",
46+
]
47+
48+
deps = [
49+
":windows",
50+
"${chip_root}/src/app/common:cluster-objects",
51+
"${chip_root}/third_party/imgui",
52+
]
53+
54+
public_configs = [ "${chip_root}/src:includes" ]
55+
}

0 commit comments

Comments
 (0)