diff --git a/tsd/CMakeLists.txt b/tsd/CMakeLists.txt index 05af06bc6..16b14c04c 100644 --- a/tsd/CMakeLists.txt +++ b/tsd/CMakeLists.txt @@ -78,11 +78,7 @@ project(tsd LANGUAGES C CXX) ## Major dependencies ## -if (TSD_BUILD_INTERACTIVE_APPS) - find_package(anari ${ANARI_REQUIRED_VERSION} REQUIRED COMPONENTS viewer) -else() - find_package(anari ${ANARI_REQUIRED_VERSION} REQUIRED) -endif() +find_package(anari ${ANARI_REQUIRED_VERSION} REQUIRED) if (TSD_USE_CUDA) enable_language(CUDA) diff --git a/tsd/apps/interactive/dataTreeEditor/tsdDataTreeEditor.cpp b/tsd/apps/interactive/dataTreeEditor/tsdDataTreeEditor.cpp index 9ba0341eb..6e4ba5872 100644 --- a/tsd/apps/interactive/dataTreeEditor/tsdDataTreeEditor.cpp +++ b/tsd/apps/interactive/dataTreeEditor/tsdDataTreeEditor.cpp @@ -33,7 +33,7 @@ class Application : public TSDApplication ~Application() override = default; - anari_viewer::WindowArray setupWindows() override + tsd::ui::imgui::WindowArray setupWindows() override { auto windows = TSDApplication::setupWindows(); diff --git a/tsd/apps/interactive/demos/animatedParticles/tsdDemoAnimatedParticles.cpp b/tsd/apps/interactive/demos/animatedParticles/tsdDemoAnimatedParticles.cpp index 972dbb49f..23f8ed9d3 100644 --- a/tsd/apps/interactive/demos/animatedParticles/tsdDemoAnimatedParticles.cpp +++ b/tsd/apps/interactive/demos/animatedParticles/tsdDemoAnimatedParticles.cpp @@ -26,7 +26,7 @@ class Application : public TSDApplication Application(int argc, const char *argv[]) : TSDApplication(argc, argv) {} ~Application() override = default; - anari_viewer::WindowArray setupWindows() override + tsd::ui::imgui::WindowArray setupWindows() override { auto windows = TSDApplication::setupWindows(); diff --git a/tsd/apps/interactive/demos/animatedVolume/tsdDemoAnimatedVolume.cpp b/tsd/apps/interactive/demos/animatedVolume/tsdDemoAnimatedVolume.cpp index 722834953..0f5dcfeb5 100644 --- a/tsd/apps/interactive/demos/animatedVolume/tsdDemoAnimatedVolume.cpp +++ b/tsd/apps/interactive/demos/animatedVolume/tsdDemoAnimatedVolume.cpp @@ -26,7 +26,7 @@ class Application : public TSDApplication Application(int argc, const char *argv[]) : TSDApplication(argc, argv) {} ~Application() override = default; - anari_viewer::WindowArray setupWindows() override + tsd::ui::imgui::WindowArray setupWindows() override { auto windows = TSDApplication::setupWindows(); diff --git a/tsd/apps/interactive/demos/arrayInstancing/tsdDemoArrayInstancing.cpp b/tsd/apps/interactive/demos/arrayInstancing/tsdDemoArrayInstancing.cpp index a88604884..84fadc60e 100644 --- a/tsd/apps/interactive/demos/arrayInstancing/tsdDemoArrayInstancing.cpp +++ b/tsd/apps/interactive/demos/arrayInstancing/tsdDemoArrayInstancing.cpp @@ -21,7 +21,7 @@ class Application : public TSDApplication Application() = default; ~Application() override = default; - anari_viewer::WindowArray setupWindows() override + tsd::ui::imgui::WindowArray setupWindows() override { auto windows = TSDApplication::setupWindows(); diff --git a/tsd/apps/interactive/demos/customField/tsdDemoCustomField.cpp b/tsd/apps/interactive/demos/customField/tsdDemoCustomField.cpp index 79e52ead5..3c9fcde05 100644 --- a/tsd/apps/interactive/demos/customField/tsdDemoCustomField.cpp +++ b/tsd/apps/interactive/demos/customField/tsdDemoCustomField.cpp @@ -31,7 +31,7 @@ class Application : public TSDApplication ~Application() override = default; - anari_viewer::WindowArray setupWindows() override + tsd::ui::imgui::WindowArray setupWindows() override { auto windows = TSDApplication::setupWindows(); diff --git a/tsd/apps/interactive/demos/manualAccumulationReset/tsdDemoAccumulationReset.cpp b/tsd/apps/interactive/demos/manualAccumulationReset/tsdDemoAccumulationReset.cpp index 31803b49f..9fd868dbe 100644 --- a/tsd/apps/interactive/demos/manualAccumulationReset/tsdDemoAccumulationReset.cpp +++ b/tsd/apps/interactive/demos/manualAccumulationReset/tsdDemoAccumulationReset.cpp @@ -21,7 +21,7 @@ class Application : public TSDApplication Application() = default; ~Application() override = default; - anari_viewer::WindowArray setupWindows() override + tsd::ui::imgui::WindowArray setupWindows() override { auto windows = TSDApplication::setupWindows(); diff --git a/tsd/apps/interactive/demos/viskores/tsdDemoViskores.cpp b/tsd/apps/interactive/demos/viskores/tsdDemoViskores.cpp index 68c99e4bf..11ca2399d 100644 --- a/tsd/apps/interactive/demos/viskores/tsdDemoViskores.cpp +++ b/tsd/apps/interactive/demos/viskores/tsdDemoViskores.cpp @@ -32,7 +32,7 @@ class Application : public TSDApplication Application(int argc, const char *argv[]); ~Application() override; - anari_viewer::WindowArray setupWindows() override; + tsd::ui::imgui::WindowArray setupWindows() override; void uiFrameEnd() override; void teardown() override; const char *getDefaultLayout() const override; @@ -56,7 +56,7 @@ Application::Application(int argc, const char *argv[]) Application::~Application() = default; -anari_viewer::WindowArray Application::setupWindows() +tsd::ui::imgui::WindowArray Application::setupWindows() { ImNodes::CreateContext(); diff --git a/tsd/apps/interactive/mpiViewer/DistributedViewport.h b/tsd/apps/interactive/mpiViewer/DistributedViewport.h index db5c3caab..4e00a4f37 100644 --- a/tsd/apps/interactive/mpiViewer/DistributedViewport.h +++ b/tsd/apps/interactive/mpiViewer/DistributedViewport.h @@ -3,7 +3,6 @@ #pragma once -#include "anari_viewer/ui_anari.h" // SDL #include // std @@ -47,7 +46,7 @@ struct DistributedViewport : public tsd::ui::imgui::Window void ui_overlay(); void ui_timeControls(); - int windowFlags() const override; // anari_viewer::Window + int windowFlags() const override; // Data ///////////////////////////////////////////////////////////////////// diff --git a/tsd/apps/interactive/mpiViewer/mpiViewer.cpp b/tsd/apps/interactive/mpiViewer/mpiViewer.cpp index 84f789619..66192d38a 100644 --- a/tsd/apps/interactive/mpiViewer/mpiViewer.cpp +++ b/tsd/apps/interactive/mpiViewer/mpiViewer.cpp @@ -32,7 +32,7 @@ class Application : public TSDApplication {} ~Application() override = default; - anari_viewer::WindowArray setupWindows() override + tsd::ui::imgui::WindowArray setupWindows() override { auto *log = new tsd::ui::imgui::Log(this, m_controller->appContext()); @@ -44,7 +44,7 @@ class Application : public TSDApplication auto *viewport = new tsd::mpi_viewer::DistributedViewport( this, m_controller, "Viewport"); - anari_viewer::WindowArray windows = TSDApplication::setupWindows(); + tsd::ui::imgui::WindowArray windows = TSDApplication::setupWindows(); windows.emplace_back(viewport); windows.emplace_back(log); diff --git a/tsd/apps/interactive/multiDeviceViewer/multiDeviceViewer.cpp b/tsd/apps/interactive/multiDeviceViewer/multiDeviceViewer.cpp index 33af16e21..56abd1a6a 100644 --- a/tsd/apps/interactive/multiDeviceViewer/multiDeviceViewer.cpp +++ b/tsd/apps/interactive/multiDeviceViewer/multiDeviceViewer.cpp @@ -42,7 +42,7 @@ class Application : public TSDApplication ~Application() override = default; - anari_viewer::WindowArray setupWindows() override + tsd::ui::imgui::WindowArray setupWindows() override { auto windows = TSDApplication::setupWindows(); diff --git a/tsd/apps/interactive/network/client/tsdRemoteViewer.cpp b/tsd/apps/interactive/network/client/tsdRemoteViewer.cpp index c8dc30943..adb17f265 100644 --- a/tsd/apps/interactive/network/client/tsdRemoteViewer.cpp +++ b/tsd/apps/interactive/network/client/tsdRemoteViewer.cpp @@ -26,7 +26,7 @@ struct Application : public TSDApplication Application(); ~Application() override; - anari_viewer::WindowArray setupWindows() override; + tsd::ui::imgui::WindowArray setupWindows() override; void uiMainMenuBar() override; void teardown() override; const char *getDefaultLayout() const override; @@ -110,7 +110,7 @@ Application::~Application() appContext()->tsd.scene.updateDelegate().erase(m_updateDelegate); } -anari_viewer::WindowArray Application::setupWindows() +tsd::ui::imgui::WindowArray Application::setupWindows() { auto windows = TSDApplication::setupWindows(); diff --git a/tsd/apps/interactive/viewer/tsdViewer.cpp b/tsd/apps/interactive/viewer/tsdViewer.cpp index 13d44cd6a..41e06b2da 100644 --- a/tsd/apps/interactive/viewer/tsdViewer.cpp +++ b/tsd/apps/interactive/viewer/tsdViewer.cpp @@ -31,7 +31,7 @@ class Application : public TSDApplication Application(int argc, const char *argv[]) : TSDApplication(argc, argv) {} ~Application() override = default; - anari_viewer::WindowArray setupWindows() override + tsd::ui::imgui::WindowArray setupWindows() override { auto windows = TSDApplication::setupWindows(); diff --git a/tsd/external/CMakeLists.txt b/tsd/external/CMakeLists.txt index 7ba084651..9465a0bfc 100644 --- a/tsd/external/CMakeLists.txt +++ b/tsd/external/CMakeLists.txt @@ -20,6 +20,7 @@ add_subdirectory( ) if(TSD_BUILD_UI_LIBRARY) + add_subdirectory(tsd_imgui_sdl) add_subdirectory(tsd_imguizmo) endif() diff --git a/tsd/external/tsd_imgui_sdl/CMakeLists.txt b/tsd/external/tsd_imgui_sdl/CMakeLists.txt new file mode 100644 index 000000000..b1b75d2b0 --- /dev/null +++ b/tsd/external/tsd_imgui_sdl/CMakeLists.txt @@ -0,0 +1,50 @@ +## Copyright 2026 NVIDIA Corporation +## SPDX-License-Identifier: Apache-2.0 + +project(tsd_ext_imgui_sdl LANGUAGES CXX) + +anari_sdk_fetch_project( + NAME ${PROJECT_NAME} + URL https://github.com/ocornut/imgui/archive/refs/tags/v1.91.7-docking.zip + MD5 2cfbf7b7790076d6debe5060ec1fb47f +) + +find_package(SDL3 QUIET) +if(NOT TARGET SDL3::SDL3) + mark_as_advanced(SDL3_DIR) + anari_sdk_fetch_project( + NAME tsd_ext_sdl3 + URL https://github.com/libsdl-org/SDL/archive/refs/tags/release-3.2.12.zip + MD5 62da6ae22e546cdacdb6dfef520391bf + ) + add_subdirectory( + ${tsd_ext_sdl3_LOCATION} + ${CMAKE_CURRENT_BINARY_DIR}/tsd_ext_sdl3 + EXCLUDE_FROM_ALL + ) +else() + mark_as_advanced(CLEAR SDL3_DIR) +endif() + +project_add_library(STATIC) + +project_sources( +PRIVATE + ${tsd_ext_imgui_sdl_LOCATION}/imgui.cpp + ${tsd_ext_imgui_sdl_LOCATION}/imgui_draw.cpp + ${tsd_ext_imgui_sdl_LOCATION}/imgui_demo.cpp + ${tsd_ext_imgui_sdl_LOCATION}/imgui_tables.cpp + ${tsd_ext_imgui_sdl_LOCATION}/imgui_widgets.cpp + ${tsd_ext_imgui_sdl_LOCATION}/backends/imgui_impl_sdl3.cpp + ${tsd_ext_imgui_sdl_LOCATION}/backends/imgui_impl_sdlrenderer3.cpp + ${tsd_ext_imgui_sdl_LOCATION}/misc/cpp/imgui_stdlib.cpp +) + +project_link_libraries(PUBLIC SDL3::SDL3) + +project_include_directories( +PUBLIC + ${tsd_ext_imgui_sdl_LOCATION} + ${tsd_ext_imgui_sdl_LOCATION}/backends + ${tsd_ext_imgui_sdl_LOCATION}/misc/cpp +) diff --git a/tsd/external/tsd_imguizmo/CMakeLists.txt b/tsd/external/tsd_imguizmo/CMakeLists.txt index bc98e2e08..4aca63d94 100644 --- a/tsd/external/tsd_imguizmo/CMakeLists.txt +++ b/tsd/external/tsd_imguizmo/CMakeLists.txt @@ -24,5 +24,5 @@ PUBLIC project_link_libraries( PRIVATE anari::helium - anari::anari_viewer -) \ No newline at end of file + tsd_ext_imgui_sdl +) diff --git a/tsd/src/tsd/ui/imgui/Application.cpp b/tsd/src/tsd/ui/imgui/Application.cpp index 6835c21e8..ab0bd77a2 100644 --- a/tsd/src/tsd/ui/imgui/Application.cpp +++ b/tsd/src/tsd/ui/imgui/Application.cpp @@ -12,19 +12,44 @@ #include "tsd/ui/imgui/Application.h" #include "tsd/ui/imgui/tsd_font.h" #include "tsd/ui/imgui/windows/Window.h" -// anari_viewer -#include "anari_viewer/ui_anari.h" // SDL +#include #include -#include // std +#include #include +#include +// imgui +#define IMGUI_DISABLE_INCLUDE_IMCONFIG_H +#include "imgui_impl_sdl3.h" +#include "imgui_impl_sdlrenderer3.h" namespace tsd::ui::imgui { -Application::Application(int argc, const char **argv) +struct Application::AppImpl { - std::vector args(argv, argv + argc); + SDL_Window *sdlWindow{nullptr}; + SDL_Renderer *sdlRenderer{nullptr}; + int width{0}; + int height{0}; + bool windowResized{true}; + std::string name; + + std::chrono::time_point frameEndTime; + std::chrono::time_point frameStartTime; + + WindowArray windows; + + void init(Uint32 windowFlags); + void renderWindows(); + void cleanup(); +}; + +Application::Application(int argc, const char **argv) : m_impl(new AppImpl) +{ + std::vector args; + if (argv != nullptr) + args.assign(argv, argv + argc); auto *ctx = appContext(); parseCommandLine(args); ctx->parseCommandLine(args); @@ -34,6 +59,29 @@ Application::Application(int argc, const char **argv) Application::~Application() = default; +SDL_Renderer *Application::sdlRenderer() +{ + return m_impl->sdlRenderer; +} + +SDL_Window *Application::sdlWindow() +{ + return m_impl->sdlWindow; +} + +void Application::run(int width, int height, const char *name) +{ + m_impl->width = width; + m_impl->height = height; + m_impl->name = name; + + m_impl->init(sdlWindowFlags()); + m_impl->windows = setupWindows(); + mainLoop(); + teardown(); + m_impl->cleanup(); +} + tsd::app::Context *Application::appContext() { return &m_ctx; @@ -49,13 +97,6 @@ CommandLineOptions *Application::commandLineOptions() return &m_commandLine; } -#ifdef TSD_USE_LUA -ExtensionManager *Application::extensionManager() const -{ - return m_extensionManager.get(); -} -#endif - void Application::getFilenameFromDialog(std::string &filenameOut, bool save) { auto fileDialogCb = @@ -99,6 +140,13 @@ void Application::saveDefaultApplicationSettings() saveGlobalApplicationSettings(); } +#ifdef TSD_USE_LUA +ExtensionManager *Application::extensionManager() const +{ + return m_extensionManager.get(); +} +#endif + void Application::parseCommandLine(std::vector &args) { for (int i = 1; i < args.size(); i++) { @@ -116,130 +164,17 @@ void Application::parseCommandLine(std::vector &args) } } -anari_viewer::WindowArray Application::setupWindows() -{ - anari_viewer::ui::init(); - - ImGuiIO &io = ImGui::GetIO(); - io.IniFilename = nullptr; - auto *font = io.Fonts->AddFontFromMemoryCompressedTTF( - tsd_font_compressed_data, tsd_font_compressed_size, 20.f); - io.Fonts->ConfigData[0].FontDataOwnedByAtlas = false; - io.FontDefault = font; - - auto *window = sdlWindow(); - SDL_MaximizeWindow(window); - m_uiConfig.fontScale = SDL_GetWindowDisplayScale(window); - - setupImGuiStyle(); - - if (commandLineOptions()->useDefaultLayout) - ImGui::LoadIniSettingsFromMemory(getDefaultLayout()); - - m_appSettingsDialog = std::make_unique(this); - m_taskModal = std::make_unique(this); - m_offlineRenderModal = std::make_unique(this); - m_fileDialog = std::make_unique(this); - m_exportNanoVDBFileDialog = std::make_unique(this); - m_vorticityDialog = std::make_unique(this); - m_cuttingPlaneDialog = std::make_unique(this); - - m_applicationName = SDL_GetWindowTitle(sdlWindow()); - updateWindowTitle(); - - loadGlobalApplicationSettings(); - m_appSettingsDialog->applySettings(); - - SDL_SetRenderVSync(sdlRenderer(), 1); - - m_extensionManager = std::make_unique(); - m_extensionManager->initialize(appContext()); - - return {}; -} - -void Application::uiFrameStart() +bool Application::getWindowSize(int &width, int &height) const { - const ImGuiIO &io = ImGui::GetIO(); - - m_ctx.tsd.animationMgr.tick(ImGui::GetIO().DeltaTime); - - if (!m_filenameToSaveNextFrame.empty()) { - saveApplicationState(m_filenameToSaveNextFrame.c_str()); - m_filenameToSaveNextFrame.clear(); - } else if (!m_filenameToLoadNextFrame.empty()) { - loadStateForNextFrame(); - } - - // Main Menu // - - if (ImGui::BeginMainMenuBar()) { - uiMainMenuBar(); - ImGui::EndMainMenuBar(); - } - - // Modals // - - bool modalActive = false; - if (m_appSettingsDialog->visible()) { - m_appSettingsDialog->renderUI(); - modalActive = true; - } - - if (m_taskModal->visible()) { - m_taskModal->renderUI(); - modalActive = true; - } - - if (m_offlineRenderModal->visible()) { - m_offlineRenderModal->renderUI(); - modalActive = true; - } - - if (m_fileDialog->visible()) { - m_fileDialog->renderUI(); - modalActive = true; - } - - // Handle app shortcuts // - if (m_exportNanoVDBFileDialog->visible()) { - m_exportNanoVDBFileDialog->renderUI(); - modalActive = true; - } - - if (m_vorticityDialog->visible()) { - m_vorticityDialog->renderUI(); - modalActive = true; - } - - if (m_cuttingPlaneDialog->visible()) { - m_cuttingPlaneDialog->renderUI(); - modalActive = true; - } - - if (!io.WantTextInput && ImGui::IsKeyPressed(ImGuiKey_Space)) - m_ctx.tsd.animationMgr.togglePlay(); - - if (ImGui::IsKeyChordPressed(ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_S)) - this->getFilenameFromDialog(m_filenameToSaveNextFrame, true); - else if (ImGui::IsKeyChordPressed(ImGuiMod_Ctrl | ImGuiMod_Alt | ImGuiKey_S)) - doSave("state.tsd"); - else if (ImGui::IsKeyChordPressed(ImGuiMod_Ctrl | ImGuiKey_S)) - doSave(); - - if (ImGui::IsKeyPressed(ImGuiKey_F1, false)) - printf("%s\n", ImGui::SaveIniSettingsToMemory()); - - if (!modalActive && ImGui::IsKeyChordPressed(ImGuiKey_Escape)) - m_ctx.clearSelected(); + width = m_impl->width; + height = m_impl->height; + return m_impl->windowResized; } -void Application::teardown() +float Application::getLastFrameLatency() const { - teardownUsdDevice(); - teardownTsdDevice(); - appContext()->anari.releaseAllDevices(); - anari_viewer::ui::shutdown(); + auto diff = m_impl->frameEndTime - m_impl->frameStartTime; + return std::chrono::duration(diff).count(); } void Application::setupImGuiStyle() @@ -372,6 +307,160 @@ void Application::setupImGuiStyle() style.Colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.8f, 0.8f, 0.8f, 0.35f); } +Uint32 Application::sdlWindowFlags() const +{ + return SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN + | SDL_WINDOW_HIGH_PIXEL_DENSITY; +} + +WindowArray Application::setupWindows() +{ + ImGuiIO &io = ImGui::GetIO(); + io.IniFilename = nullptr; + auto *font = io.Fonts->AddFontFromMemoryCompressedTTF( + tsd_font_compressed_data, tsd_font_compressed_size, 20.f); + io.Fonts->ConfigData[0].FontDataOwnedByAtlas = false; + io.FontDefault = font; + + auto *window = sdlWindow(); + SDL_MaximizeWindow(window); + m_uiConfig.fontScale = SDL_GetWindowDisplayScale(window); + + setupImGuiStyle(); + + if (commandLineOptions()->useDefaultLayout) + ImGui::LoadIniSettingsFromMemory(getDefaultLayout()); + + m_appSettingsDialog = std::make_unique(this); + m_taskModal = std::make_unique(this); + m_offlineRenderModal = std::make_unique(this); + m_fileDialog = std::make_unique(this); + m_exportNanoVDBFileDialog = std::make_unique(this); + m_vorticityDialog = std::make_unique(this); + m_cuttingPlaneDialog = std::make_unique(this); + + m_applicationName = SDL_GetWindowTitle(sdlWindow()); + updateWindowTitle(); + + loadGlobalApplicationSettings(); + m_appSettingsDialog->applySettings(); + + SDL_SetRenderVSync(sdlRenderer(), 1); + + m_extensionManager = std::make_unique(); + m_extensionManager->initialize(appContext()); + + return {}; +} + +void Application::mainLoopStart() +{ + // no-op +} + +void Application::mainLoopEnd() +{ + // no-op +} + +void Application::teardown() +{ + teardownUsdDevice(); + teardownTsdDevice(); + appContext()->anari.releaseAllDevices(); +} + +void Application::uiFrameStart() +{ + const ImGuiIO &io = ImGui::GetIO(); + + m_ctx.tsd.animationMgr.tick(ImGui::GetIO().DeltaTime); + + if (!m_filenameToSaveNextFrame.empty()) { + saveApplicationState(m_filenameToSaveNextFrame.c_str()); + m_filenameToSaveNextFrame.clear(); + } else if (!m_filenameToLoadNextFrame.empty()) { + loadStateForNextFrame(); + } + + // Main Menu // + + if (ImGui::BeginMainMenuBar()) { + uiMainMenuBar(); + ImGui::EndMainMenuBar(); + } + + // Modals // + + bool modalActive = false; + if (m_appSettingsDialog->visible()) { + m_appSettingsDialog->renderUI(); + modalActive = true; + } + + if (m_taskModal->visible()) { + m_taskModal->renderUI(); + modalActive = true; + } + + if (m_offlineRenderModal->visible()) { + m_offlineRenderModal->renderUI(); + modalActive = true; + } + + if (m_fileDialog->visible()) { + m_fileDialog->renderUI(); + modalActive = true; + } + + // Handle app shortcuts // + if (m_exportNanoVDBFileDialog->visible()) { + m_exportNanoVDBFileDialog->renderUI(); + modalActive = true; + } + + if (m_vorticityDialog->visible()) { + m_vorticityDialog->renderUI(); + modalActive = true; + } + + if (m_cuttingPlaneDialog->visible()) { + m_cuttingPlaneDialog->renderUI(); + modalActive = true; + } + + if (!io.WantTextInput && ImGui::IsKeyPressed(ImGuiKey_Space)) + m_ctx.tsd.animationMgr.togglePlay(); + + if (ImGui::IsKeyChordPressed(ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_S)) + this->getFilenameFromDialog(m_filenameToSaveNextFrame, true); + else if (ImGui::IsKeyChordPressed(ImGuiMod_Ctrl | ImGuiMod_Alt | ImGuiKey_S)) + doSave("state.tsd"); + else if (ImGui::IsKeyChordPressed(ImGuiMod_Ctrl | ImGuiKey_S)) + doSave(); + + if (ImGui::IsKeyPressed(ImGuiKey_F1, false)) + printf("%s\n", ImGui::SaveIniSettingsToMemory()); + + if (!modalActive && ImGui::IsKeyChordPressed(ImGuiKey_Escape)) + m_ctx.clearSelected(); +} + +void Application::uiRenderStart() +{ + // no-op +} + +void Application::uiRenderEnd() +{ + // no-op +} + +void Application::uiFrameEnd() +{ + // no-op +} + void Application::uiMainMenuBar() { uiMainMenuBar_File(); @@ -529,6 +618,7 @@ void Application::uiMainMenuBar_Tools() ImGui::EndMenu(); } } + void Application::uiMainMenuBar_Lua() { #ifdef TSD_USE_LUA @@ -928,12 +1018,85 @@ void Application::teardownTsdDevice() m_tsdDevice.renderIndex = nullptr; } -void Application::setWindowArray(const anari_viewer::WindowArray &wa) +void Application::setWindowArray(const WindowArray &wa) { for (auto &w : wa) m_windows.push_back((Window *)w.get()); } +void Application::mainLoop() +{ + auto window = sdlWindow(); + + bool open = true; + while (open) { + m_impl->frameStartTime = m_impl->frameEndTime; + m_impl->frameEndTime = std::chrono::steady_clock::now(); + mainLoopStart(); + SDL_Event event; + while (SDL_PollEvent(&event)) { + ImGui_ImplSDL3_ProcessEvent(&event); + if (event.type == SDL_EVENT_QUIT) + open = false; + if (event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED + && event.window.windowID == SDL_GetWindowID(window)) + open = false; + } + + ImGui_ImplSDLRenderer3_NewFrame(); + ImGui_ImplSDL3_NewFrame(); + + ImGui::NewFrame(); + + if (ImGui::IsKeyChordPressed(ImGuiKey_Q | ImGuiMod_Ctrl)) + open = false; + + uiFrameStart(); + + ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoDocking + | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse + | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove + | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; + + ImGuiViewport *viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(viewport->WorkPos); + ImGui::SetNextWindowSize(viewport->WorkSize); + ImGui::SetNextWindowViewport(viewport->ID); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + + ImGui::Begin("MainDockSpace", nullptr, windowFlags); + ImGui::PopStyleVar(3); + + ImGuiID dockspaceId = ImGui::GetID("MainDockSpaceID"); + ImGui::DockSpace(dockspaceId, ImVec2(0.0f, 0.0f), ImGuiDockNodeFlags_None); + + m_impl->renderWindows(); + + ImGui::End(); + + ImGui::Render(); + + uiRenderStart(); + ImGuiIO &io = ImGui::GetIO(); + m_impl->width = io.DisplaySize.x; + m_impl->height = io.DisplaySize.y; + auto sdlRenderer = m_impl->sdlRenderer; + SDL_SetRenderDrawColorFloat(sdlRenderer, 0.1f, 0.1f, 0.1f, 1.f); + SDL_RenderClear(sdlRenderer); + ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), sdlRenderer); + SDL_RenderPresent(sdlRenderer); + uiRenderEnd(); + + m_impl->windowResized = false; + + uiFrameEnd(); + mainLoopEnd(); + } +} + void Application::updateWindowTitle() { auto *w = this->sdlWindow(); @@ -948,4 +1111,72 @@ void Application::updateWindowTitle() SDL_SetWindowTitle(w, title.c_str()); } +void Application::AppImpl::init(Uint32 windowFlags) +{ + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD)) + throw std::runtime_error("failed to initialize SDL"); + + sdlWindow = SDL_CreateWindow(name.c_str(), width, height, windowFlags); + if (sdlWindow == nullptr) + throw std::runtime_error("failed to create SDL window"); + + sdlRenderer = SDL_CreateRenderer(sdlWindow, nullptr); + SDL_SetWindowPosition( + sdlWindow, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); + if (sdlRenderer == nullptr) { + SDL_DestroyWindow(sdlWindow); + SDL_Quit(); + throw std::runtime_error("failed to create SDL renderer"); + } + + SDL_ShowWindow(sdlWindow); + + const float scale = SDL_GetWindowPixelDensity(sdlWindow); + SDL_SetRenderScale(sdlRenderer, scale, scale); + + ImGui::CreateContext(); + ImGui::StyleColorsDark(); + + ImGui_ImplSDL3_InitForSDLRenderer(sdlWindow, sdlRenderer); + ImGui_ImplSDLRenderer3_Init(sdlRenderer); + + ImGuiIO &io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + + ImGuiStyle &style = ImGui::GetStyle(); + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + style.Colors[ImGuiCol_WindowBg].w = 1.0f; + + style.WindowRounding = 0.0f; + style.ChildRounding = 0.f; + style.FrameRounding = 0.f; + style.PopupRounding = 0.f; + style.ScrollbarRounding = 0.f; + style.GrabRounding = 0.f; + style.TabRounding = 0.f; +} + +void Application::AppImpl::renderWindows() +{ + for (auto &w : windows) + w->renderUI(); +} + +void Application::AppImpl::cleanup() +{ + windows.clear(); + + ImGui_ImplSDLRenderer3_Shutdown(); + ImGui_ImplSDL3_Shutdown(); + ImGui::DestroyContext(); + + SDL_DestroyRenderer(sdlRenderer); + SDL_DestroyWindow(sdlWindow); + SDL_Quit(); + + sdlRenderer = nullptr; + sdlWindow = nullptr; +} + } // namespace tsd::ui::imgui diff --git a/tsd/src/tsd/ui/imgui/Application.h b/tsd/src/tsd/ui/imgui/Application.h index 037b6c1f4..eb3897980 100644 --- a/tsd/src/tsd/ui/imgui/Application.h +++ b/tsd/src/tsd/ui/imgui/Application.h @@ -17,14 +17,18 @@ // tsd_core #include "tsd/core/Logging.hpp" #include "tsd/core/TaskQueue.hpp" -// anari_viewer -#include +// SDL +#include // std #include +#include +#include +#include namespace tsd::ui::imgui { struct Window; +using WindowArray = std::vector>; struct UIConfig { @@ -39,11 +43,17 @@ struct CommandLineOptions std::string secondaryViewportLibrary; }; -class Application : public anari_viewer::Application +class Application { public: Application(int argc = 0, const char **argv = nullptr); - ~Application() override; + virtual ~Application(); + + SDL_Renderer *sdlRenderer(); + SDL_Window *sdlWindow(); + + // Start the application run loop + void run(int width, int height, const char *name); tsd::app::Context *appContext(); UIConfig *uiConfig(); @@ -70,19 +80,26 @@ class Application : public anari_viewer::Application protected: void parseCommandLine(std::vector &args); - - // Things from anari_viewer::Application to override // - - virtual anari_viewer::WindowArray setupWindows() override; - virtual void uiFrameStart() override; - virtual void teardown() override; + bool getWindowSize(int &width, int &height) const; + float getLastFrameLatency() const; // Internal API // virtual void setupImGuiStyle(); - virtual void uiMainMenuBar(); + virtual Uint32 sdlWindowFlags() const; + virtual WindowArray setupWindows(); + virtual void mainLoopStart(); + virtual void mainLoopEnd(); + virtual void teardown(); + + virtual void uiFrameStart(); + virtual void uiRenderStart(); + virtual void uiRenderEnd(); + virtual void uiFrameEnd(); + + virtual void uiMainMenuBar(); void uiMainMenuBar_File(); void uiMainMenuBar_Edit(); void uiMainMenuBar_Tools(); @@ -113,7 +130,7 @@ class Application : public anari_viewer::Application void syncTsdScene(); void teardownTsdDevice(); - void setWindowArray(const anari_viewer::WindowArray &wa); + void setWindowArray(const WindowArray &wa); virtual const char *getDefaultLayout() const = 0; // Data // @@ -133,12 +150,16 @@ class Application : public anari_viewer::Application CommandLineOptions m_commandLine; private: + void mainLoop(); void updateWindowTitle(); // Data // tsd::app::Context m_ctx; + struct AppImpl; + std::unique_ptr m_impl; + tsd::core::TaskQueue m_jobs{10}; std::string m_applicationName = "TSD"; diff --git a/tsd/src/tsd/ui/imgui/CMakeLists.txt b/tsd/src/tsd/ui/imgui/CMakeLists.txt index eebfc66b5..b074682be 100644 --- a/tsd/src/tsd/ui/imgui/CMakeLists.txt +++ b/tsd/src/tsd/ui/imgui/CMakeLists.txt @@ -3,8 +3,6 @@ project(tsd_ui_imgui) -find_package(anari REQUIRED COMPONENTS viewer) - project_add_library(OBJECT) project_sources(PRIVATE @@ -48,45 +46,12 @@ PUBLIC tsd_rendering tsd_ext_imguizmo tsd_ext_imoguizmo + tsd_ext_imgui_sdl tsd_ext_tinyexr stb_image -PRIVATE - anari::anari_viewer -) - -## -## Gather anari::anari_viewer include paths for apps to have when needed -## - -# Direct include dirs -get_target_property( - ANARI_VIEWER_INCLUDE_DIRS - anari::anari_viewer - INTERFACE_INCLUDE_DIRECTORIES ) -# Get list of incoming dependent targets to anari::anari_viewer -get_target_property( - ANARI_VIEWER_LINK_LIBRARIES - anari::anari_viewer - INTERFACE_LINK_LIBRARIES -) - -# Append the list of include paths from each dependent target -foreach(LINK_TARGET ${ANARI_VIEWER_LINK_LIBRARIES}) - get_target_property( - LINK_TARGET_INCLUDE_DIRS - ${LINK_TARGET} - INTERFACE_INCLUDE_DIRECTORIES - ) - if (LINK_TARGET_INCLUDE_DIRS) - list(APPEND ANARI_VIEWER_INCLUDE_DIRS ${LINK_TARGET_INCLUDE_DIRS}) - endif() -endforeach() - project_include_directories( PUBLIC ${CMAKE_CURRENT_LIST_DIR} - INTERFACE - ${ANARI_VIEWER_INCLUDE_DIRS} ) diff --git a/tsd/src/tsd/ui/imgui/windows/BaseViewport.h b/tsd/src/tsd/ui/imgui/windows/BaseViewport.h index 2989db2fa..22257e54e 100644 --- a/tsd/src/tsd/ui/imgui/windows/BaseViewport.h +++ b/tsd/src/tsd/ui/imgui/windows/BaseViewport.h @@ -6,6 +6,8 @@ #include "Window.h" // tsd_rendering #include "tsd/rendering/pipeline/ImagePipeline.h" +// imgui +#include // ImGuizmo #include // imoguizmo @@ -95,7 +97,7 @@ struct BaseViewport : public Window bool m_showAnimationSlider{true}; private: - int windowFlags() const override; // anari_viewer::Window + int windowFlags() const override; void applyViewMatrixToArcball(const float *viewMat); tsd::rendering::ImagePipeline m_pipeline; diff --git a/tsd/src/tsd/ui/imgui/windows/MultiDeviceViewport.h b/tsd/src/tsd/ui/imgui/windows/MultiDeviceViewport.h index d3b35736a..83423b49a 100644 --- a/tsd/src/tsd/ui/imgui/windows/MultiDeviceViewport.h +++ b/tsd/src/tsd/ui/imgui/windows/MultiDeviceViewport.h @@ -43,7 +43,7 @@ struct MultiDeviceViewport : public Window void ui_menubar(); void ui_handleInput(); - int windowFlags() const override; // anari_viewer::Window + int windowFlags() const override; // ImGui input state // diff --git a/tsd/src/tsd/ui/imgui/windows/Window.cpp b/tsd/src/tsd/ui/imgui/windows/Window.cpp index e379df57b..8c827b20f 100644 --- a/tsd/src/tsd/ui/imgui/windows/Window.cpp +++ b/tsd/src/tsd/ui/imgui/windows/Window.cpp @@ -3,15 +3,54 @@ #include "Window.h" #include "tsd/ui/imgui/Application.h" +// imgui +#define IMGUI_DISABLE_INCLUDE_IMCONFIG_H +#include namespace tsd::ui::imgui { -Window::~Window() = default; - Window::Window(Application *app, const char *name) - : anari_viewer::windows::Window(app, name, true), m_app(app) + : m_app(app), m_name(name) {} +Window::~Window() = default; + +void Window::renderUI() +{ + if (!m_visible) + return; + + ImGui::SetNextWindowSize(ImVec2(550, 680), ImGuiCond_FirstUseEver); + ImGui::Begin(m_name.c_str(), &m_visible, windowFlags()); + buildUI(); + ImGui::End(); +} + +void Window::show() +{ + m_visible = true; +} + +void Window::hide() +{ + m_visible = false; +} + +void Window::toggleShown() +{ + m_visible = !m_visible; +} + +bool *Window::visiblePtr() +{ + return &m_visible; +} + +const char *Window::name() +{ + return m_name.c_str(); +} + void Window::saveSettings(tsd::core::DataNode &thisWindowRoot) { thisWindowRoot["visible"] = *visiblePtr(); @@ -22,6 +61,11 @@ void Window::loadSettings(tsd::core::DataNode &thisWindowRoot) thisWindowRoot["visible"].getValue(ANARI_BOOL, visiblePtr()); } +ImGuiWindowFlags Window::windowFlags() const +{ + return 0; +} + tsd::app::Context *Window::appContext() const { return m_app ? m_app->appContext() : nullptr; diff --git a/tsd/src/tsd/ui/imgui/windows/Window.h b/tsd/src/tsd/ui/imgui/windows/Window.h index 7587f58b5..977033910 100644 --- a/tsd/src/tsd/ui/imgui/windows/Window.h +++ b/tsd/src/tsd/ui/imgui/windows/Window.h @@ -3,12 +3,14 @@ #pragma once -// anari_viewer -#include "anari_viewer/windows/Window.h" // tsd_core #include "tsd/core/DataTree.hpp" // tsd_app #include "tsd/app/Context.h" +// imgui +#include +// std +#include namespace tsd::ui::imgui { @@ -16,19 +18,33 @@ class Application; constexpr float INDENT_AMOUNT = 20.f; -struct Window : public anari_viewer::windows::Window +struct Window { - Window(Application *app, const char *name = "Window"); - virtual ~Window() override; + Window(Application *app, const char *name); + virtual ~Window(); - virtual void buildUI() override = 0; + void renderUI(); + + void show(); + void hide(); + void toggleShown(); + + bool *visiblePtr(); + const char *name(); + + // Interface to override for custom windows // + + virtual void buildUI() = 0; virtual void saveSettings(tsd::core::DataNode &thisWindowRoot); virtual void loadSettings(tsd::core::DataNode &thisWindowRoot); protected: + virtual int windowFlags() const; tsd::app::Context *appContext() const; Application *m_app{nullptr}; + std::string m_name; + bool m_visible{true}; }; } // namespace tsd::ui::imgui