diff --git a/conanfile.py b/conanfile.py index 48b1695e9..5daeb1dcb 100644 --- a/conanfile.py +++ b/conanfile.py @@ -79,6 +79,7 @@ class Morpheus(ConanFile): "unordered_dense/4.8.1", "boost/1.90.0", "ctre/3.10.0", + "ftxui/6.0.2", "magic_enum/0.9.7", "ms-gsl/4.2.0", "rapidjson/cci.20250205", diff --git a/examples/gfx/render_triangle/CMakeLists.txt b/examples/gfx/render_triangle/CMakeLists.txt index 629ba15d6..d18b109d2 100644 --- a/examples/gfx/render_triangle/CMakeLists.txt +++ b/examples/gfx/render_triangle/CMakeLists.txt @@ -1,10 +1,13 @@ add_executable(RenderTriangle main.cpp) +find_package(ftxui) target_link_libraries(RenderTriangle PRIVATE + ftxui::ftxui morpheus::application morpheus::gfx::platform morpheus::gfx::gl4 + morpheus::vis ) set_target_properties(RenderTriangle diff --git a/examples/gfx/render_triangle/main.cpp b/examples/gfx/render_triangle/main.cpp index 1b311a9dd..d62eb9abb 100644 --- a/examples/gfx/render_triangle/main.cpp +++ b/examples/gfx/render_triangle/main.cpp @@ -1,8 +1,11 @@ #include #include #include -// #include #include +#include + +#include +#include using namespace morpheus::application; using namespace morpheus::gfx; @@ -45,13 +48,158 @@ class RenderTriange : public Application int main(int argc, char* argv[]) { + // const std::vector renders = { + // "Direct X 12", + // "OpenGL 4", + // "Vulkan" + // }; - tryCatch( - [&] - { - RenderTriange example(argc, argv); - example.Run(); - }); + // int selectedRenders = 0; + // ftxui::Component compiler = ftxui::Radiobox(&renders, &selectedRenders); + + // auto screen = ftxui::ScreenInteractive::Fullscreen(); + ////auto testComponent = ftxui::Renderer([]() { return ftxui::text("test Component"); }); + // auto testComponent = ftxui::Renderer(compiler, [&]() { + // auto rendererWin = ftxui::window(ftxui::text("Renderer"), compiler->Render() | ftxui::vscroll_indicator | ftxui::frame); + // return rendererWin; + // }); + // screen.Loop(testComponent); + + morpheus::vis::RenderSystemFactory factory; + factory.runTuiConfiguration(); + + // tryCatch( + // [&] + // { + // RenderTriange example(argc, argv); + // example.Run(); + // }); // render_system_factory renderer_factory; } + +/* +#include +#include +#include +#include +#include +#include +#include + +using namespace ftxui; + +// Enum to track the current screen state +enum class ScreenStage { + SelectRenderer, + ConfigureRenderer, +}; + +int main() { + ScreenInteractive screen = ScreenInteractive::TerminalOutput(); + + // State + ScreenStage current_stage = ScreenStage::SelectRenderer; + + std::vector renderer_options = { + "DirectX 12", "Vulkan", "OpenGL" + }; + int selected_renderer = 0; + auto renderer_selector = Radiobox(&renderer_options, &selected_renderer); + + // Settings per renderer + std::map> renderer_settings = { + {"DirectX 12", {"Use DXIL", "Enable Tearing"}}, + {"Vulkan", {"Validation Layers", "Prefer Integrated GPU"}}, + {"OpenGL", {"Core Profile", "Enable Debug Output"}} + }; + + // Dynamic state for second screen + std::vector setting_labels; + std::vector setting_states; // Real bools for safety + std::vector setting_checkboxes; + Component settings_container = Container::Vertical({}); + + // Component placeholders + Component next_button, back_button, finish_button; + + // Container that will switch content + Component main_container = Container::Vertical({}); + + // Renderer root + Component root = Renderer(main_container, [&] { + if (current_stage == ScreenStage::SelectRenderer) { + return vbox({ + text("Select Render System:") | bold, + renderer_selector->Render(), + separator(), + next_button->Render(), + }) | border | center; + } + else { + Elements checkbox_elements; + for (auto& cb : setting_checkboxes) { + checkbox_elements.push_back(cb->Render()); + } + + return vbox({ + text("Configure Settings for: ") | bold, + text(renderer_options[selected_renderer]), + separator(), + vbox(std::move(checkbox_elements)), + separator(), + hbox({ + back_button->Render(), + finish_button->Render(), + }) | center + }) | border | center; + } + }); + + // Buttons — defined after root to capture state + next_button = Button("Next", [&] { + current_stage = ScreenStage::ConfigureRenderer; + + // Populate setting labels + bools + std::string selected_name = renderer_options[selected_renderer]; + setting_labels = renderer_settings[selected_name]; + setting_states = std::vector(setting_labels.size(), false); + setting_checkboxes.clear(); + + for (size_t i = 0; i < setting_labels.size(); ++i) { + bool local = setting_states[i]; + setting_checkboxes.push_back(Checkbox(setting_labels[i], &local)); + } + + settings_container = Container::Vertical(setting_checkboxes); + + // Reconfigure main container + main_container->DetachAllChildren(); + //main_container->Add(renderer_selector); + main_container->Add(settings_container); + main_container->Add(back_button); + main_container->Add(finish_button); + + screen.PostEvent(Event::Custom); // force re-render + }); + + back_button = Button("Back", [&] { + current_stage = ScreenStage::SelectRenderer; + main_container->DetachAllChildren(); + main_container->Add(renderer_selector); + main_container->Add(next_button); + screen.PostEvent(Event::Custom); // force re-render + }); + + finish_button = Button("Finish", [&] { + screen.Exit(); + }); + + // Initial container setup + main_container->Add(renderer_selector); + main_container->Add(next_button); + + screen.Loop(root); + return 0; +} +*/ diff --git a/libraries/gfx/gl4/tests/wgl/adapter.tests.cpp b/libraries/gfx/gl4/tests/wgl/adapter.tests.cpp index f45a71648..b43c561fc 100644 --- a/libraries/gfx/gl4/tests/wgl/adapter.tests.cpp +++ b/libraries/gfx/gl4/tests/wgl/adapter.tests.cpp @@ -36,43 +36,123 @@ */ #ifdef _WIN32 #include -extern "C" __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; -extern "C" __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001; +// extern "C" __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; +// extern "C" __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001; +extern "C" __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000000; +extern "C" __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x00000000; #endif namespace morpheus::gfx::gl4::wgl { +namespace +{ using PathInfoArray = std::vector; using ModeInfoArray = std::vector; using DisplayConfig = std::pair; -conf::exp::expected getCurrentDisplayConfig() +/// Path mapping an output monitor to the graphic adapter mapped to it. +using Path = std::pair; + +auto getDisplayConfigBufferSizes() -> conf::exp::expected, std::string> +{ + UINT32 pathCount = 0, modeCount = 0; + auto const result = GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &pathCount, &modeCount); + if (result != ERROR_SUCCESS) + { + return conf::exp::unexpected(win32::GetLastErrorString(result)); + } + return { + std::pair{pathCount, modeCount} + }; +} + +auto queryDisplayConfig(DisplayConfig config) -> conf::exp::expected +{ + auto& [pathInfo, modeInfo] = config; + UINT32 pathSize = static_cast(pathInfo.size()); + UINT32 modeSize = static_cast(modeInfo.size()); + ULONG const result = QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &pathSize, pathInfo.data(), &modeSize, modeInfo.data(), NULL); + if (result != ERROR_SUCCESS) + return conf::exp::unexpected(win32::GetLastErrorString(result)); + return config; +} + +auto targetDeviceName(DISPLAYCONFIG_PATH_INFO const& path) -> conf::exp::expected { - PathInfoArray pathInfo; - ModeInfoArray modeInfo; + DISPLAYCONFIG_TARGET_DEVICE_NAME targetName = {}; + targetName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME; + targetName.header.size = sizeof(targetName); + targetName.header.adapterId = path.targetInfo.adapterId; + targetName.header.id = path.targetInfo.id; + auto const result = DisplayConfigGetDeviceInfo(&targetName.header); + if (result != ERROR_SUCCESS) + { + return conf::exp::unexpected(win32::GetLastErrorString(result)); + } + return targetName; +} + +auto adapterName(DISPLAYCONFIG_PATH_INFO const& path) -> conf::exp::expected +{ + DISPLAYCONFIG_ADAPTER_NAME adapterName = {}; + adapterName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_ADAPTER_NAME; + adapterName.header.size = sizeof(adapterName); + adapterName.header.adapterId = path.sourceInfo.adapterId; + adapterName.header.id = 0; + auto const result = DisplayConfigGetDeviceInfo(&adapterName.header); + if (result != ERROR_SUCCESS) + { + return conf::exp::unexpected(win32::GetLastErrorString(result)); + } + return adapterName; +} - // First, grab the system's current configuration - for (UINT32 trySize = 32; trySize < 1024; trySize *= 2) +} // namespace + +conf::exp::expected getCurrentDisplayConfig() +{ + auto const unpackCountsToDisplayConfig = [](auto counts) -> conf::exp::expected { - pathInfo.resize(trySize); - modeInfo.resize(trySize); - UINT32 pathSize = static_cast(pathInfo.size()); - UINT32 modeSize = static_cast(modeInfo.size()); + auto const unpackCounts = [](auto&&... args) { return DisplayConfig{std::forward(args)...}; }; + return std::apply(unpackCounts, std::move(counts)); + }; + + return getDisplayConfigBufferSizes().and_then(unpackCountsToDisplayConfig).and_then(queryDisplayConfig); +} + +/// Provides all active display paths mapping a monitors to the graphic adapter which +/// \param config +/// @return +auto getDevicesPaths(DisplayConfig const& config) -> conf::exp::expected, std::string> +{ + auto& [paths, _] = config; + auto const toMonitorToAdapterMapping = [](DISPLAYCONFIG_PATH_INFO const& path) { return std::pair{targetDeviceName(path), adapterName(path)}; }; - ULONG const rc = QueryDisplayConfig(QDC_ALL_PATHS, &pathSize, pathInfo.data(), &modeSize, modeInfo.data(), NULL); + auto r = paths | std::ranges::views::transform(toMonitorToAdapterMapping); - if (rc == ERROR_SUCCESS) + // clang-format off + auto const result = std::ranges::fold_left(std::move(r), conf::exp::expected, std::string>{}, + [](auto&& result, auto&& element) -> conf::exp::expected, std::string> { - pathInfo.resize(pathSize); - modeInfo.resize(modeSize); - break; - } + if (!result) { + return conf::exp::unexpected(result.error()); + } - if (rc != ERROR_INSUFFICIENT_BUFFER) - return conf::exp::unexpected(rc); - } - return DisplayConfig{std::move(pathInfo), std::move(modeInfo)}; + if (!element.first) { + return conf::exp::unexpected(element.first.error()); + } + + if (!element.second) { + return conf::exp::unexpected(element.second.error()); + } + + result.value().push_back(Path{std::move(element).first.value(), std::move(element).second.value()}); + return std::move(result); + }); + // clang-format on + + return result; } TEST_CASE("Create an adapter mode list", "[morpheus.core.gfx.gl.wgl.adapter_list]") @@ -115,10 +195,12 @@ TEST_CASE("Create an adapter mode list", "[morpheus.core.gfx.gl.wgl.adapter_list */ DISPLAYCONFIG_PATH_SOURCE_INFO* pSource = NULL; // will contain the answer - auto result = getCurrentDisplayConfig(); - REQUIRE(result); + auto result = getCurrentDisplayConfig().and_then([](DisplayConfig const& config) { return getDevicesPaths(config); }); + + auto result4 = getCurrentDisplayConfig(); + REQUIRE(result4); - auto& [pathInfo, modeInfo] = result.value(); + auto& [pathInfo, modeInfo] = result4.value(); for (UINT32 tryEnable = 0;; ++tryEnable) { diff --git a/libraries/gfx/platform/lib/morpheus/gfx/platform/concepts/render_system.hpp b/libraries/gfx/platform/lib/morpheus/gfx/platform/concepts/render_system.hpp index 0238995c3..0e5c69667 100644 --- a/libraries/gfx/platform/lib/morpheus/gfx/platform/concepts/render_system.hpp +++ b/libraries/gfx/platform/lib/morpheus/gfx/platform/concepts/render_system.hpp @@ -17,9 +17,10 @@ template concept RenderSystem = requires(T t) { // typename T::Window; + /// requires !meta::Copyable; + { T::getGraphicsApi() } -> std::same_as; - // requires !meta::Copyable; // { t.adapters() } -> AdapterRange; // { t.beginFrame() } -> std::same_as; diff --git a/libraries/gfx/platform/lib/morpheus/gfx/platform/win32/error_codes.cpp b/libraries/gfx/platform/lib/morpheus/gfx/platform/win32/error_codes.cpp index 1a6929e36..cadc01138 100644 --- a/libraries/gfx/platform/lib/morpheus/gfx/platform/win32/error_codes.cpp +++ b/libraries/gfx/platform/lib/morpheus/gfx/platform/win32/error_codes.cpp @@ -1,52 +1,52 @@ -#include -#include - -#include - -namespace morpheus::gfx::win32 -{ - -namespace details -{ -/*! \class vulkan_error_category - Defines an error code category for Vulkan return codes to plug them into the std::error_code framework. - */ -class ErrorCategory : public std::error_category -{ -public: - /*! Queries a short form descriptive name for the category. - \return Short form name of the category. - */ - char const* name() const noexcept override final { return "Win32::ErrorCategory"; } - - /*! Queries an error string describing the error. - \return The error message. - */ - std::string message(int c) const override final { return GetLastErrorString(static_cast(c)); } - - static ErrorCategory const& getSingleInstance() noexcept { return mSingleInstance; } - -private: - static ErrorCategory mSingleInstance; -}; - -ErrorCategory ErrorCategory::mSingleInstance; - -} // namespace details - -details::ErrorCategory const& ErrorCategory() noexcept -{ - return details::ErrorCategory::getSingleInstance(); -} - -} // namespace morpheus::gfx::win32 - -std::error_code make_error_code(morpheus::gfx::win32::ErrorCode const& e) -{ - return std::error_code(static_cast(e.error), morpheus::gfx::win32::ErrorCategory()); -} - -std::error_code getLastErrorCode() -{ - return make_error_code(morpheus::gfx::win32::ErrorCode{::GetLastError()}); -} +#include +#include + +#include + +namespace morpheus::gfx::win32 +{ + +namespace details +{ +/*! \class vulkan_error_category + Defines an error code category for Vulkan return codes to plug them into the std::error_code framework. + */ +class ErrorCategory : public std::error_category +{ +public: + /*! Queries a short form descriptive name for the category. + \return Short form name of the category. + */ + char const* name() const noexcept override final { return "Win32::ErrorCategory"; } + + /*! Queries an error string describing the error. + \return The error message. + */ + std::string message(int c) const override final { return GetLastErrorString(static_cast(c)); } + + static ErrorCategory const& getSingleInstance() noexcept { return mSingleInstance; } + +private: + static ErrorCategory mSingleInstance; +}; + +ErrorCategory ErrorCategory::mSingleInstance; + +} // namespace details + +details::ErrorCategory const& ErrorCategory() noexcept +{ + return details::ErrorCategory::getSingleInstance(); +} + +} // namespace morpheus::gfx::win32 + +std::error_code make_error_code(morpheus::gfx::win32::ErrorCode const& e) +{ + return std::error_code(static_cast(e.error), morpheus::gfx::win32::ErrorCategory()); +} + +std::error_code getLastErrorCode() +{ + return make_error_code(morpheus::gfx::win32::ErrorCode{::GetLastError()}); +} diff --git a/libraries/gfx/platform/lib/morpheus/gfx/platform/win32/error_codes.hpp b/libraries/gfx/platform/lib/morpheus/gfx/platform/win32/error_codes.hpp index a9c8a10d0..e45d012c8 100644 --- a/libraries/gfx/platform/lib/morpheus/gfx/platform/win32/error_codes.hpp +++ b/libraries/gfx/platform/lib/morpheus/gfx/platform/win32/error_codes.hpp @@ -1,54 +1,54 @@ -#pragma once - -#include - -#include - -namespace morpheus::gfx::win32 -{ - -struct [[nodiscard]] ErrorCode -{ - constexpr explicit ErrorCode(DWORD const e) noexcept - : error(e) - {} - DWORD error; -}; - -} // namespace morpheus::gfx::win32 - -namespace std -{ -template <> -struct is_error_code_enum : true_type -{}; -} // namespace std - -namespace morpheus::gfx::win32 -{ - -namespace details -{ -class ErrorCategory; -} - -/// Retrieve the one instance of the Win32 error category -/// \return The one instance of the Win32 error category. -details::ErrorCategory const& ErrorCategory() noexcept; - -} // namespace morpheus::gfx::win32 - -/// Overload the global make_error_code() free function with a ErrorCode. It will be found via ADL by the compiler if needed. -/// \param[in] error The error raised by the underlying Win32 API. -/// \return A std::error_code with Win32 error encoded into it. -/// \note Because windows error codes are just DWORD values, and thus not strongly typed, you must explicitly wrap them in an ErrorCode constructed from a -/// DWORD to pass to the make_error_code overload or call the helper overload getLastErrorCode() which initialised an error code from GetLastError(). -/// \code -/// std::error_code ec = make_error_code(morpheus::gfx::win32::ErrorCode{ERROR_FILE_NOT_FOUND}); -/// \endcode -std::error_code make_error_code(morpheus::gfx::win32::ErrorCode const& e); - -/// Overload the getLastErrorCode which is initialised from GetLastError(). -/// \param[in] error The error raised by the underlying Win32 API. -/// \return A std::error_code with Win32 error encoded into it. -std::error_code getLastErrorCode(); +#pragma once + +#include + +#include + +namespace morpheus::gfx::win32 +{ + +struct [[nodiscard]] ErrorCode +{ + constexpr explicit ErrorCode(DWORD const e) noexcept + : error(e) + {} + DWORD error; +}; + +} // namespace morpheus::gfx::win32 + +namespace std +{ +template <> +struct is_error_code_enum : true_type +{}; +} // namespace std + +namespace morpheus::gfx::win32 +{ + +namespace details +{ +class ErrorCategory; +} + +/// Retrieve the one instance of the Win32 error category +/// \return The one instance of the Win32 error category. +details::ErrorCategory const& ErrorCategory() noexcept; + +} // namespace morpheus::gfx::win32 + +/// Overload the global make_error_code() free function with a ErrorCode. It will be found via ADL by the compiler if needed. +/// \param[in] error The error raised by the underlying Win32 API. +/// \return A std::error_code with Win32 error encoded into it. +/// \note Because windows error codes are just DWORD values, and thus not strongly typed, you must explicitly wrap them in an ErrorCode constructed from a +/// DWORD to pass to the make_error_code overload or call the helper overload getLastErrorCode() which initialised an error code from GetLastError(). +/// \code +/// std::error_code ec = make_error_code(morpheus::gfx::win32::ErrorCode{ERROR_FILE_NOT_FOUND}); +/// \endcode +std::error_code make_error_code(morpheus::gfx::win32::ErrorCode const& e); + +/// Overload the getLastErrorCode which is initialised from GetLastError(). +/// \param[in] error The error raised by the underlying Win32 API. +/// \return A std::error_code with Win32 error encoded into it. +std::error_code getLastErrorCode(); diff --git a/libraries/gfx/platform/lib/morpheus/gfx/platform/win32/render_window.cpp b/libraries/gfx/platform/lib/morpheus/gfx/platform/win32/render_window.cpp index ab1d68414..db7f23fb6 100644 --- a/libraries/gfx/platform/lib/morpheus/gfx/platform/win32/render_window.cpp +++ b/libraries/gfx/platform/lib/morpheus/gfx/platform/win32/render_window.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -53,7 +54,8 @@ LRESULT WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) if (message != WM_CREATE) { // Get the pointer to the active window specified by hWnd - thisWindow = reinterpret_cast(GetWindowLongPtr(hWnd, 0)); + thisWindow = conf::bit::bit_cast(GetWindowLongPtr(hWnd, 0)); + MORPHEUS_ASSERT_MSG(thisWindow, "Window32::WndProc() - Failed to get active window from GetWindowLongPtr"); } // Handle possible windows messages @@ -62,13 +64,13 @@ LRESULT WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) /// Handle the initial case of creating the window case WM_CREATE: { - LPCREATESTRUCT pCreateStruct = reinterpret_cast(lParam); - thisWindow = reinterpret_cast(pCreateStruct->lpCreateParams); + LPCREATESTRUCT pCreateStruct = conf::bit::bit_cast(lParam); + thisWindow = conf::bit::bit_cast(pCreateStruct->lpCreateParams); MORPHEUS_ASSERT_MSG(thisWindow, "Window32::WndProc() - Failed to create window"); // Store pointer in window user data area - ::SetWindowLongPtr(hWnd, 0, reinterpret_cast(thisWindow)); + ::SetWindowLongPtr(hWnd, 0, conf::bit::bit_cast(thisWindow)); // thisWindow->SetActive( true ); } break; @@ -76,7 +78,6 @@ LRESULT WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) /// Handle activation or deactivation of the window case WM_ACTIVATE: { - MORPHEUS_ASSERT_MSG(thisWindow, "Window32::WndProc() - Failed to get active window - WM_ACTIVATE"); // Check if the window is active // thisWindow->SetActive( WA_ACTIVE == LOWORD(wParam) ); @@ -85,7 +86,6 @@ LRESULT WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) case WM_PAINT: { - MORPHEUS_ASSERT_MSG(thisWindow, "Window32::WndProc() - Failed to get active window - WM_PAINT"); // if ( pThisWindow->IsActive() ) { // thisWindow->PresentBackBuffer(); @@ -95,7 +95,6 @@ LRESULT WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) case WM_SIZE: { - MORPHEUS_VERIFY_MSG(thisWindow, "morpheus::gfx::win32::WndProc() - Failed to get active window - WM_SIZE"); thisWindow->resize(); } break; @@ -124,14 +123,12 @@ LRESULT WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) case WM_ENTERSIZEMOVE: { - MORPHEUS_ASSERT_MSG(thisWindow, "Window32::WndProc() - Failed to get active window - WM_ENTERSIZEMOVE"); // thisWindow->SetActive( false ); } break; case WM_EXITSIZEMOVE: { - MORPHEUS_ASSERT_MSG(thisWindow, "Window32::WndProc() - Failed to get active window - WM_EXITSIZEMOVE"); // pThisWindow->SetActive( true ); //! @todo Temp measure, find a better solution @@ -141,7 +138,6 @@ LRESULT WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) case WM_CLOSE: { - MORPHEUS_ASSERT_MSG(thisWindow, "Window32::WndProc() - Failed to get active window - WM_CLOSE"); // Shut down the relevant window // pThisWindow->Destroy(); } diff --git a/libraries/gfx/vulkan/lib/morpheus/gfx/vulkan/render_system.cpp b/libraries/gfx/vulkan/lib/morpheus/gfx/vulkan/render_system.cpp index 015056f78..ec5be0406 100644 --- a/libraries/gfx/vulkan/lib/morpheus/gfx/vulkan/render_system.cpp +++ b/libraries/gfx/vulkan/lib/morpheus/gfx/vulkan/render_system.cpp @@ -36,9 +36,8 @@ RenderSystem::RenderSystem(std::string_view const appName, std::string_view cons , mAvailableLayers(mContext.enumerateInstanceLayerProperties()) , mAdapters(enumerateAdapters(mInstance)) {} -// LCOV_EXCL_STOP -//--------------------------------------------------------------------------------------------------------------------- +// LCOV_EXCL_STOP //--------------------------------------------------------------------------------------------------------------------- } // namespace morpheus::gfx::vulkan diff --git a/libraries/visualisation/lib/morpheus/vis/CMakeLists.txt b/libraries/visualisation/lib/morpheus/vis/CMakeLists.txt index e0dd95d94..9a2416481 100644 --- a/libraries/visualisation/lib/morpheus/vis/CMakeLists.txt +++ b/libraries/visualisation/lib/morpheus/vis/CMakeLists.txt @@ -1,3 +1,5 @@ +find_package(ftxui) + target_sources(MorpheusVisualisation PUBLIC FILE_SET HEADERS @@ -8,3 +10,8 @@ target_sources(MorpheusVisualisation render_system_factory.cpp visualisation.cpp ) + +target_link_libraries(MorpheusVisualisation + PRIVATE + ftxui::ftxui +) diff --git a/libraries/visualisation/lib/morpheus/vis/render_system_factory.cpp b/libraries/visualisation/lib/morpheus/vis/render_system_factory.cpp index e5c45accd..d5b793dda 100644 --- a/libraries/visualisation/lib/morpheus/vis/render_system_factory.cpp +++ b/libraries/visualisation/lib/morpheus/vis/render_system_factory.cpp @@ -4,6 +4,11 @@ #include +#include +#include + +namespace hana = boost::hana; + namespace morpheus::vis { @@ -16,6 +21,71 @@ void RenderSystemFactory::addOptions(boost::program_options::options_description //--------------------------------------------------------------------------------------------------------------------- -//--------------------------------------------------------------------------------------------------------------------- +// boost::program_options::options_description& RenderSystemFactory::runTuiConfiguration() + +// LCOV_EXCL_START +void RenderSystemFactory::runTuiConfiguration() +{ + auto const renderSystems = []() + { + std::vector results; + boost::hana::for_each(availableAPIs(), + [&](auto const& pair) + { + // constexpr auto key = hana::first(pair); + auto value = hana::second(pair); + using RenderSystem = typename decltype(value)::type; + results.push_back(std::string(RenderSystem::getGraphicsApi())); + }); + return results; + }(); + + using namespace ftxui; + + enum class Pages + { + SelectRenderSystem, + RenderSystemSettings, + }; + + Pages current = Pages::SelectRenderSystem; + auto screen = ftxui::ScreenInteractive::Fullscreen(); + + // -- 1. Render System selection + + int selectedRenders = 0; + auto renderSystemRadioBox = ftxui::Radiobox(&renderSystems, &selectedRenders); + // auto nextButton = ftxui::Button("Next", [&] { current = Pages::SelectRenderSystem; screen.PostEvent(ftxui::Event::Custom); }); + auto nextButton = ftxui::Button("Next", + [&] + { + current = Pages::SelectRenderSystem; + screen.Exit(); + }); + auto selectRenderSystemPage = ftxui::Container::Vertical({renderSystemRadioBox, nextButton}); + auto selectRSRenderer = Renderer(selectRenderSystemPage, + [&] + { + return vbox({ + text("Select Render System:") | bold, + renderSystemRadioBox->Render(), + separator(), + nextButton->Render(), + }) | + border | center; + }); + + // -- 2. Render System Settings selection + screen.Loop(selectRSRenderer); + + // auto testComponent = ftxui::Renderer(selectRSRenderer, + // [&]() + // { + // auto rendererWin = ftxui::window(ftxui::text("Render System"), render->Render() | ftxui::vscroll_indicator | ftxui::frame); + // return rendererWin; + // }); + // screen.Loop(testComponent); +} +// LCOV_EXCL_STOP } // namespace morpheus::vis diff --git a/libraries/visualisation/lib/morpheus/vis/render_system_factory.hpp b/libraries/visualisation/lib/morpheus/vis/render_system_factory.hpp index 22ee2a8f1..6a96f9dfe 100644 --- a/libraries/visualisation/lib/morpheus/vis/render_system_factory.hpp +++ b/libraries/visualisation/lib/morpheus/vis/render_system_factory.hpp @@ -1,6 +1,5 @@ #pragma once -#include "morpheus/application/po/adapters/enum.hpp" #include #if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_APPLE) @@ -63,9 +62,10 @@ class RenderSystemFactory /// Register program options void addOptions(boost::program_options::options_description& options); + // boost::program_options::options_description& runTuiConfiguration(); + void runTuiConfiguration(); auto getActiveAPI() const -> API { return mActiveApi; } -private: /// Returns a map of the available rendering systems to their respective underlying graphics APIs. /// static constexpr auto availableAPIs() @@ -81,6 +81,7 @@ class RenderSystemFactory hana::make_pair(RenderSystemType, hana::type_c)); } +private: // using APIMap = std::invoke_result_t; // constexpr static auto renderSystemAPIs = availableAPIs(); diff --git a/libraries/visualisation/tests/render_system_factory.tests.cpp b/libraries/visualisation/tests/render_system_factory.tests.cpp index 4f875524a..300ad6073 100644 --- a/libraries/visualisation/tests/render_system_factory.tests.cpp +++ b/libraries/visualisation/tests/render_system_factory.tests.cpp @@ -18,7 +18,7 @@ TEST_CASE("Test RenderSystem Factory command line options", "[morpheus.vis.rende using namespace morpheus::application::po; RenderSystemFactory renderSystemFactory; std::array cliOptions = {"dummyProgram.exe", "--render-system", param.data()}; - auto const result = application::po::parseProgramOptions(cliOptions, application::po::HelpDocumentation(), renderSystemFactory); + auto const result = parseProgramOptions(cliOptions, HelpDocumentation(), renderSystemFactory); REQUIRE(!result); return renderSystemFactory.getActiveAPI(); }; @@ -30,4 +30,27 @@ TEST_CASE("Test RenderSystem Factory command line options", "[morpheus.vis.rende } } +TEST_CASE("A place holder test", "[morpheus.vis.render_system_factory.available_apis]") +{ + namespace hana = boost::hana; + static constinit auto apis = RenderSystemFactory::availableAPIs(); + +#if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_APPLE) + STATIC_REQUIRE(!hana::contains(apis, RenderSystemType)); + STATIC_REQUIRE(hana::contains(apis, RenderSystemType)); + STATIC_REQUIRE(hana::contains(apis, RenderSystemType)); + STATIC_REQUIRE(hana::contains(apis, RenderSystemType)); +#elif (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_PC_WINDOWS) + STATIC_REQUIRE(hana::contains(apis, RenderSystemType)); + STATIC_REQUIRE(!hana::contains(apis, RenderSystemType)); + STATIC_REQUIRE(hana::contains(apis, RenderSystemType)); + STATIC_REQUIRE(hana::contains(apis, RenderSystemType)); +#elif (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_LINUX) + STATIC_REQUIRE(!hana::contains(apis, RenderSystemType)); + STATIC_REQUIRE(!hana::contains(apis, RenderSystemType)); + STATIC_REQUIRE(hana::contains(apis, RenderSystemType)); + STATIC_REQUIRE(hana::contains(apis, RenderSystemType)); +#endif // #if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_APPLE) +} + } // namespace morpheus::vis