blob: c0c04ffb4a9efb9a293f00a7f7d23eb4f0dd0f4f [file] [log] [blame]
/*
*
* 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