| /* |
| * |
| * Copyright (c) 2023 Project CHIP Authors |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| #include "ui.h" |
| |
| #include <SDL.h> |
| #include <SDL_opengl.h> |
| #include <imgui.h> |
| #include <imgui_impl_opengl3.h> |
| #include <imgui_impl_sdl2.h> |
| |
| #include <lib/support/logging/CHIPLogging.h> |
| |
| #include <atomic> |
| #include <thread> |
| |
| namespace example { |
| namespace Ui { |
| namespace { |
| |
| // Controls running the UI event loop |
| std::atomic<bool> gUiRunning{ false }; |
| |
| void UiInit(SDL_GLContext * gl_context, SDL_Window ** window) |
| { |
| if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0) |
| { |
| ChipLogError(AppServer, "SDL Init Error: %s\n", SDL_GetError()); |
| return; |
| } |
| |
| #if defined(__APPLE__) |
| SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac |
| SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); |
| SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); |
| SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); |
| #else |
| SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); |
| SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); |
| SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); |
| SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); |
| #endif |
| |
| #ifdef SDL_HINT_IME_SHOW_UI |
| SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1"); |
| #endif |
| |
| SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); |
| SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); |
| SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); |
| SDL_WindowFlags window_flags = (SDL_WindowFlags) (SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); |
| *window = SDL_CreateWindow("Light UI", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, window_flags); |
| *gl_context = SDL_GL_CreateContext(*window); |
| SDL_GL_MakeCurrent(*window, *gl_context); |
| SDL_GL_SetSwapInterval(1); // Enable vsync |
| |
| // Setup Dear ImGui context |
| IMGUI_CHECKVERSION(); |
| ImGui::CreateContext(); |
| ImGuiIO & io = ImGui::GetIO(); |
| (void) io; |
| // io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls |
| // io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls |
| |
| ImGui::StyleColorsDark(); |
| |
| // Setup Platform/Renderer backends |
| ImGui_ImplSDL2_InitForOpenGL(*window, *gl_context); |
| ImGui_ImplOpenGL3_Init(); |
| } |
| |
| void UiShutdown(SDL_GLContext * gl_context, SDL_Window ** window) |
| { |
| ImGui_ImplOpenGL3_Shutdown(); |
| ImGui_ImplSDL2_Shutdown(); |
| ImGui::DestroyContext(); |
| |
| SDL_GL_DeleteContext(*gl_context); |
| SDL_DestroyWindow(*window); |
| SDL_Quit(); |
| } |
| |
| void EventLoop(ImguiUi * ui) |
| { |
| gUiRunning = true; |
| SDL_GLContext gl_context; |
| SDL_Window * window = nullptr; |
| |
| UiInit(&gl_context, &window); |
| |
| ImGuiIO & io = ImGui::GetIO(); |
| |
| while (gUiRunning.load()) |
| { |
| SDL_Event event; |
| while (SDL_PollEvent(&event)) |
| { |
| ImGui_ImplSDL2_ProcessEvent(&event); |
| if ((event.type == SDL_QUIT) || |
| (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && |
| event.window.windowID == SDL_GetWindowID(window))) |
| { |
| gUiRunning = false; |
| } |
| } |
| |
| ImGui_ImplOpenGL3_NewFrame(); |
| ImGui_ImplSDL2_NewFrame(); |
| ImGui::NewFrame(); |
| |
| ui->UpdateState(); |
| ui->Render(); |
| |
| // rendering |
| ImGui::Render(); |
| glViewport(0, 0, (int) io.DisplaySize.x, (int) io.DisplaySize.y); |
| glClearColor(0, 0, 0, 0); |
| glClear(GL_COLOR_BUFFER_BIT); |
| ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); |
| SDL_GL_SwapWindow(window); |
| } |
| |
| UiShutdown(&gl_context, &window); |
| |
| ChipLogProgress(AppServer, "UI thread Stopped..."); |
| } |
| |
| } // namespace |
| |
| void ImguiUi::RunMainLoop() |
| { |
| // Guaranteed to be on the main task (no chip event loop started yet) |
| ChipLoopLoadInitialState(); |
| |
| // Platform event loop will be on a separate thread, |
| // while the event UI loop will be on the main thread. |
| chip::DeviceLayer::PlatformMgr().StartEventLoopTask(); |
| |
| // SignalSafeStopMainLoop will stop this loop below |
| // or the loop exits by itself when processing a SDL |
| // exit (generally by clicking the window close icon). |
| EventLoop(this); |
| |
| // ensure shutdown events are generated (generally basic cluster |
| // will send a shutdown event to subscribers). |
| // |
| // We attempt to wait for finish as the event will be sent sync. |
| // Since the Main loop is stopped, there will be no MRP, however at least |
| // one event is attempted to be sent. |
| chip::DeviceLayer::PlatformMgr().ScheduleWork( |
| [](intptr_t arg) { |
| chip::DeviceLayer::PlatformMgr().HandleServerShuttingDown(); |
| sem_t * semaphore = reinterpret_cast<sem_t *>(arg); |
| sem_post(semaphore); // notify complete |
| }, |
| reinterpret_cast<intptr_t>(&mChipLoopWaitSemaphore)); |
| sem_wait(&mChipLoopWaitSemaphore); |
| |
| // Stop the chip main loop as well. This is expected to |
| // wait for the task to finish. |
| chip::DeviceLayer::PlatformMgr().StopEventLoopTask(); |
| } |
| |
| void ImguiUi::SignalSafeStopMainLoop() |
| { |
| gUiRunning = false; |
| } |
| |
| void ImguiUi::ChipLoopStateUpdate() |
| { |
| assertChipStackLockedByCurrentThread(); |
| for (auto it = mWindows.begin(); it != mWindows.end(); it++) |
| { |
| (*it)->UpdateState(); |
| } |
| } |
| |
| void ImguiUi::ChipLoopLoadInitialState() |
| { |
| assertChipStackLockedByCurrentThread(); |
| for (auto it = mWindows.begin(); it != mWindows.end(); it++) |
| { |
| (*it)->LoadInitialState(); |
| } |
| } |
| |
| void ImguiUi::Render() |
| { |
| for (auto it = mWindows.begin(); it != mWindows.end(); it++) |
| { |
| (*it)->Render(); |
| } |
| } |
| |
| void ImguiUi::ChipLoopUpdateCallback(intptr_t self) |
| { |
| ImguiUi * _this = reinterpret_cast<ImguiUi *>(self); |
| _this->ChipLoopStateUpdate(); |
| sem_post(&_this->mChipLoopWaitSemaphore); // notify complete |
| } |
| |
| void ImguiUi::UpdateState() |
| { |
| CHIP_ERROR err = chip::DeviceLayer::PlatformMgr().ScheduleWork(&ChipLoopUpdateCallback, reinterpret_cast<intptr_t>(this)); |
| |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(AppServer, "Failed to schedule state update: %" CHIP_ERROR_FORMAT, err.Format()); |
| return; |
| } |
| |
| // ensure update is done when exiting |
| sem_wait(&mChipLoopWaitSemaphore); |
| } |
| |
| } // namespace Ui |
| } // namespace example |