/* * * 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 #include #include #include #include #include #include #include namespace example { namespace Ui { namespace { // Controls running the UI event loop std::atomic 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(arg); sem_post(semaphore); // notify complete }, reinterpret_cast(&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(self); _this->ChipLoopStateUpdate(); sem_post(&_this->mChipLoopWaitSemaphore); // notify complete } void ImguiUi::UpdateState() { CHIP_ERROR err = chip::DeviceLayer::PlatformMgr().ScheduleWork(&ChipLoopUpdateCallback, reinterpret_cast(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