diff --git a/.editorconfig b/.editorconfig index 6d104bcadb..374097c7a8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,6 +8,8 @@ indent_style = space indent_size = 4 insert_final_newline = true trim_trailing_whitespace = true +# https://devblogs.microsoft.com/cppblog/doxygen-and-xml-doc-comment-support/ +vc_generate_documentation_comments = doxygen_slash_star [*.json] indent_size = 2 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 534707a441..9cbb3b8001 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,6 +13,7 @@ env: -DMEGAMOL_WARNING_LEVEL="Off" -DMEGAMOL_PLUGIN_MEGAMOL101_GL=ON -DMEGAMOL_USE_CGAL=ON + -DMEGAMOL_USE_POWER=ON -DMEGAMOL_USE_PROFILING=ON -DMEGAMOL_USE_STACKTRACE=ON -DMEGAMOL_USE_TRACY=ON diff --git a/CMakeLists.txt b/CMakeLists.txt index 0812f7d442..f55fb68e36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ # Copyright (c) 2020, MegaMol Dev Team # All rights reserved. # -cmake_minimum_required(VERSION 3.19...3.29 FATAL_ERROR) +cmake_minimum_required(VERSION 3.24...3.29 FATAL_ERROR) # Vcpkg setup set(MEGAMOL_VCPKG_VERSION "2024.07.12") # Update default-registry baseline in vcpkg.json when changing! @@ -27,6 +27,7 @@ megamol_feature_option(VTKM "Enable VTK-m support." OFF) megamol_feature_option(CUESDK "Enable Corsair CUESDK support." OFF "WIN32") megamol_feature_option(OPENGL_DEBUGGROUPS "Inject OpenGL debug groups into calls." OFF "MEGAMOL_USE_OPENGL") megamol_feature_option(POWER_VISA "Enable power measurement VISA sensors." OFF "MEGAMOL_USE_POWER") +megamol_feature_option(TRACY_TIME_PLOT "Enable custom Tracy timed plotting capabilities." OFF "MEGAMOL_USE_TRACY") megamol_feature_option(VR_INTEROP "Enable MegaMol-Unity VR Interop via Spout2." OFF "WIN32;MEGAMOL_USE_OPENGL") # Disable in source build diff --git a/cmake/megamol_config.cmake b/cmake/megamol_config.cmake index 998a6c6d51..b31b15bcaa 100644 --- a/cmake/megamol_config.cmake +++ b/cmake/megamol_config.cmake @@ -27,6 +27,12 @@ endif () add_compile_definitions("$<$:DEBUG>") add_compile_definitions("$<$:_DEBUG>") +# MEMLEAK +cmake_dependent_option(MEGAMOL_DETECT_MEMLEAK "Enable to use Memleak detection (MSVC only)" OFF "MSVC" OFF) +if (MEGAMOL_DETECT_MEMLEAK) + add_compile_definitions("$<$:MEGAMOL_DETECT_MEMLEAK>") +endif() + # Compiler flags # Note: special C++ and C-Compiler flags should be set for each language separately as done below. # Otherwise, a possible compilation with CUDA will propagate those flags to the CUDA-Compiler and lead to a crash. @@ -95,8 +101,10 @@ endif () # CUDA if (MEGAMOL_USE_CUDA) + if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES) + set(CMAKE_CUDA_ARCHITECTURES native) + endif() enable_language(CUDA) - set(CMAKE_CUDA_ARCHITECTURES FALSE) endif () # MPI diff --git a/cmake/vcpkg_ports/dataversepp/portfile.cmake b/cmake/vcpkg_ports/dataversepp/portfile.cmake new file mode 100644 index 0000000000..dbd692e0b8 --- /dev/null +++ b/cmake/vcpkg_ports/dataversepp/portfile.cmake @@ -0,0 +1,30 @@ +vcpkg_check_linkage(ONLY_DYNAMIC_LIBRARY) + +vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO UniStuttgart-VISUS/dataversepp + REF 4857d61a899443ae77641e3df730d2b8c26607dc # master on 2024-07-27 + SHA512 801b2f43ea42698f295ad36069f30495da8891acee5dda23127168b7d989e8d9a67b45f794288ed3747c5333109fbbcb6a78051b46fcf46273de91a46750dd55 + HEAD_REF master +) + +vcpkg_cmake_configure( + SOURCE_PATH "${SOURCE_PATH}" + OPTIONS + -DDATAVERSE_BuildCli=OFF + -DDATAVERSE_DownloadThirdParty=OFF + -DDATAVERSE_BuildTests=OFF + -DDATAVERSE_Unicode=OFF +) + +vcpkg_cmake_install() + +vcpkg_cmake_config_fixup( + PACKAGE_NAME dataversepp + CONFIG_PATH lib/cmake/dataverse +) + +vcpkg_copy_pdbs() + +file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include") +vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENCE") diff --git a/cmake/vcpkg_ports/dataversepp/vcpkg.json b/cmake/vcpkg_ports/dataversepp/vcpkg.json new file mode 100644 index 0000000000..7f485b4692 --- /dev/null +++ b/cmake/vcpkg_ports/dataversepp/vcpkg.json @@ -0,0 +1,32 @@ +{ + "name": "dataversepp", + "version-date": "2024-07-27", + "description": "Framework for dataverse data transfer.", + "homepage": "https://github.com/UniStuttgart-VISUS/dataversepp", + "license": "MIT", + "dependencies": [ + { + "name": "curl", + "features": [ + "ssl" + ] + }, + { + "name": "icu", + "platform": "!windows" + }, + "nlohmann-json", + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + }, + { + "name": "wil", + "platform": "windows" + } + ] +} diff --git a/core/include/mmcore/utility/graphics/ScreenShotComments.h b/core/include/mmcore/utility/graphics/ScreenShotComments.h index ce30f66c20..81cc120a4b 100644 --- a/core/include/mmcore/utility/graphics/ScreenShotComments.h +++ b/core/include/mmcore/utility/graphics/ScreenShotComments.h @@ -14,6 +14,8 @@ #include +#include "RuntimeInfo.h" + namespace megamol::core::utility::graphics { class ScreenShotComments { @@ -26,7 +28,7 @@ class ScreenShotComments { * constructor and later get out the png_text you need for feeding libpng. Note that the returned png_text array * is only valid as long as the ScreenShotComments instance is in scope! */ - ScreenShotComments(std::string const& project_configuration, + ScreenShotComments(std::string const& project_configuration, megamol::frontend_resources::RuntimeInfo const* ri, const std::optional& additional_comments = std::nullopt); png_comments GetComments() const; diff --git a/core/include/mmcore/utility/platform/RuntimeInfo.h b/core/include/mmcore/utility/platform/RuntimeInfo.h index 368d9c8347..a2591c7c7b 100644 --- a/core/include/mmcore/utility/platform/RuntimeInfo.h +++ b/core/include/mmcore/utility/platform/RuntimeInfo.h @@ -5,6 +5,7 @@ */ #pragma once +#include #include #include "WMIUtil.h" @@ -15,6 +16,7 @@ class RuntimeInfo { public: static std::string GetHardwareInfo() { if (m_hardware_info.empty()) { + std::lock_guard lock(write_mtx_); get_hardware_info(); } return m_hardware_info; @@ -22,6 +24,7 @@ class RuntimeInfo { static std::string GetOsInfo() { if (m_os_info.empty()) { + std::lock_guard lock(write_mtx_); get_os_info(); } return m_os_info; @@ -29,11 +32,44 @@ class RuntimeInfo { static std::string GetRuntimeLibraries() { if (m_runtime_libraries.empty()) { + std::lock_guard lock(write_mtx_); get_runtime_libraries(); } return m_runtime_libraries; } + static std::string GetSMBIOSInfo() { + if (smbios_.empty()) { + std::lock_guard lock(write_mtx_); + get_smbios_info(); + } + return smbios_; + } + + static std::string GetCPUInfo() { + if (cpu_.empty()) { + std::lock_guard lock(write_mtx_); + get_cpu_info(); + } + return cpu_; + } + + static std::string GetGPUInfo() { + if (gpu_.empty()) { + std::lock_guard lock(write_mtx_); + get_gpu_info(); + } + return gpu_; + } + + static std::string GetOSInfo() { + if (os_.empty()) { + std::lock_guard lock(write_mtx_); + get_OS_info(); + } + return os_; + } + private: static void get_hardware_info(); @@ -45,6 +81,18 @@ class RuntimeInfo { inline static std::string m_os_info; inline static std::string m_hardware_info; + static void get_smbios_info(bool serial = false); + static void get_cpu_info(); + static void get_gpu_info(); + static void get_OS_info(); + + inline static std::string smbios_; + inline static std::string cpu_; + inline static std::string gpu_; + inline static std::string os_; + + inline static std::mutex write_mtx_; + #ifdef _WIN32 inline static WMIUtil wmi; #endif diff --git a/core/include/mmcore/utility/platform/WMIUtil.h b/core/include/mmcore/utility/platform/WMIUtil.h index 359af1f499..116bde05a0 100644 --- a/core/include/mmcore/utility/platform/WMIUtil.h +++ b/core/include/mmcore/utility/platform/WMIUtil.h @@ -9,6 +9,7 @@ #ifdef _WIN32 #include +#include #define _WIN32_DCOM #include @@ -27,8 +28,10 @@ class WMIUtil { std::string get_value(const std::string& wmi_class, const std::string& attribute); private: - IWbemLocator* locator = nullptr; - IWbemServices* service = nullptr; + //IWbemLocator* locator = nullptr; + //IWbemServices* service = nullptr; + wil::com_ptr service; + //wil::unique_couninitialize_call cleanup; }; } // namespace megamol::core::utility::platform diff --git a/core/src/utility/graphics/ScreenShotComments.cpp b/core/src/utility/graphics/ScreenShotComments.cpp index 127fd2c1a1..2e3d2c2767 100644 --- a/core/src/utility/graphics/ScreenShotComments.cpp +++ b/core/src/utility/graphics/ScreenShotComments.cpp @@ -20,8 +20,9 @@ namespace mcu_graphics = megamol::core::utility::graphics; -mcu_graphics::ScreenShotComments::ScreenShotComments( - std::string const& project_configuration, const std::optional& additional_comments) { +mcu_graphics::ScreenShotComments::ScreenShotComments(std::string const& project_configuration, + megamol::frontend_resources::RuntimeInfo const* ri, + const std::optional& additional_comments) { the_comments["Title"] = "MegaMol Screen Capture " + utility::DateTime::CurrentDateTimeFormatted(); //the_comments["Author"] = ""; @@ -34,11 +35,14 @@ mcu_graphics::ScreenShotComments::ScreenShotComments( the_comments["Remote Branch"] = megamol::core::utility::buildinfo::MEGAMOL_GIT_BRANCH_NAME_FULL(); the_comments["Remote URL"] = megamol::core::utility::buildinfo::MEGAMOL_GIT_REMOTE_URL(); - the_comments["Software Environment"] = platform::RuntimeInfo::GetRuntimeLibraries(); - the_comments["Hardware Environment"] = platform::RuntimeInfo::GetHardwareInfo(); the_comments["CMakeCache"] = megamol::core::utility::buildinfo::MEGAMOL_CMAKE_CACHE(); the_comments["Git Diff"] = megamol::core::utility::buildinfo::MEGAMOL_GIT_DIFF(); - the_comments["Operating System"] = platform::RuntimeInfo::GetOsInfo(); + + if (ri) { + the_comments["Hardware Environment"] = ri->get_hardware_info(); + the_comments["Operating System"] = ri->get_os_info(); + the_comments["Software Environment"] = ri->get_runtime_libraries(); + } //the_comments["Disclaimer"] = ""; //the_comments["Warning"] = ""; diff --git a/core/src/utility/platform/RuntimeInfo.cpp b/core/src/utility/platform/RuntimeInfo.cpp index efc18611c7..f5cbac93d6 100644 --- a/core/src/utility/platform/RuntimeInfo.cpp +++ b/core/src/utility/platform/RuntimeInfo.cpp @@ -117,73 +117,79 @@ std::vector dlinfo_linkmap(void* handle) { } // namespace void megamol::core::utility::platform::RuntimeInfo::get_hardware_info() { + if (m_hardware_info.empty()) { #ifdef _WIN32 - //m_hardware_info = execute("systeminfo"); - std::stringstream s; - s << "{" << std::endl; - s << R"("Processor Name":")" << wmi.get_value("Win32_Processor", "Name") << "\"," << std::endl; - s << R"("Processor Version":")" << wmi.get_value("Win32_Processor", "Version") << "\"," << std::endl; - s << R"("GPU Name":")" << wmi.get_value("Win32_VideoController", "Name") << "\"," << std::endl; - s << R"("OS Name":")" << wmi.get_value("Win32_OperatingSystem", "Name") << "\"," << std::endl; - s << R"("OS Version":")" << wmi.get_value("Win32_OperatingSystem", "Version") << "\"," << std::endl; - s << R"("OS Architecture":")" << wmi.get_value("Win32_OperatingSystem", "OSArchitecture") << "\"," << std::endl; - s << R"("Available Memory":")" << wmi.get_value("Win32_OperatingSystem", "TotalVisibleMemorySize") << "\"" - << std::endl; - s << "}"; - m_hardware_info = s.str(); + //m_hardware_info = execute("systeminfo"); + std::stringstream s; + s << "{" << std::endl; + s << R"("Processor Name":")" << wmi.get_value("Win32_Processor", "Name") << "\"," << std::endl; + s << R"("Processor Version":")" << wmi.get_value("Win32_Processor", "Version") << "\"," << std::endl; + s << R"("GPU Name":")" << wmi.get_value("Win32_VideoController", "Name") << "\"," << std::endl; + s << R"("OS Name":")" << wmi.get_value("Win32_OperatingSystem", "Name") << "\"," << std::endl; + s << R"("OS Version":")" << wmi.get_value("Win32_OperatingSystem", "Version") << "\"," << std::endl; + s << R"("OS Architecture":")" << wmi.get_value("Win32_OperatingSystem", "OSArchitecture") << "\"," << std::endl; + s << R"("Available Memory":")" << wmi.get_value("Win32_OperatingSystem", "TotalVisibleMemorySize") << "\"" + << std::endl; + s << "}"; + m_hardware_info = s.str(); #else - m_hardware_info = execute("cat /proc/cpuinfo /proc/meminfo"); + m_hardware_info = execute("cat /proc/cpuinfo /proc/meminfo"); #endif + } } void megamol::core::utility::platform::RuntimeInfo::get_runtime_libraries() { + if (m_runtime_libraries.empty()) { #ifdef _WIN32 - HANDLE h_mod_snap = INVALID_HANDLE_VALUE; - h_mod_snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId()); - if (h_mod_snap != INVALID_HANDLE_VALUE) { - std::stringstream out; - MODULEENTRY32 me32; - me32.dwSize = sizeof(MODULEENTRY32); - if (Module32First(h_mod_snap, &me32)) { - do { - out << me32.szExePath << " ("; - out << get_file_version(me32.szExePath) << ")" << std::endl; - } while (Module32Next(h_mod_snap, &me32)); + HANDLE h_mod_snap = INVALID_HANDLE_VALUE; + h_mod_snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId()); + if (h_mod_snap != INVALID_HANDLE_VALUE) { + std::stringstream out; + MODULEENTRY32 me32; + me32.dwSize = sizeof(MODULEENTRY32); + if (Module32First(h_mod_snap, &me32)) { + do { + out << me32.szExePath << " ("; + out << get_file_version(me32.szExePath) << ")" << std::endl; + } while (Module32Next(h_mod_snap, &me32)); + } + CloseHandle(h_mod_snap); + m_runtime_libraries = out.str(); + } else { + m_runtime_libraries = ""; } - CloseHandle(h_mod_snap); - m_runtime_libraries = out.str(); - } else { - m_runtime_libraries = ""; - } #else - void* handle = dlopen(nullptr, RTLD_NOW); + void* handle = dlopen(nullptr, RTLD_NOW); - // TODO looks like all library paths are already absolute, do we need search paths here? - // const auto paths = dlinfo_search_path(handle); + // TODO looks like all library paths are already absolute, do we need search paths here? + // const auto paths = dlinfo_search_path(handle); - const auto list = dlinfo_linkmap(handle); + const auto list = dlinfo_linkmap(handle); - std::stringstream out; - for (const auto& lib : list) { - out << lib; - // If the library is a symlink, print link target to get the filename with the full version number. - std::filesystem::path p(lib); - if (std::filesystem::is_symlink(p)) { - p = std::filesystem::canonical(p); - out << " (=> " << p.string() << ")"; + std::stringstream out; + for (const auto& lib : list) { + out << lib; + // If the library is a symlink, print link target to get the filename with the full version number. + std::filesystem::path p(lib); + if (std::filesystem::is_symlink(p)) { + p = std::filesystem::canonical(p); + out << " (=> " << p.string() << ")"; + } + out << std::endl; } - out << std::endl; - } - m_runtime_libraries = out.str(); + m_runtime_libraries = out.str(); #endif + } } void megamol::core::utility::platform::RuntimeInfo::get_os_info() { + if (m_os_info.empty()) { #ifdef _WIN32 - m_os_info = execute("ver"); + m_os_info = execute("ver"); #else - m_os_info = execute("cat /etc/issue"); + m_os_info = execute("cat /etc/issue"); #endif + } } @@ -209,3 +215,86 @@ std::string megamol::core::utility::platform::RuntimeInfo::execute(const std::st return "unable to execute " + cmd; } } + + +void megamol::core::utility::platform::RuntimeInfo::get_smbios_info(bool serial) { + if (smbios_.empty()) { +#ifdef _WIN32 + std::stringstream s; + s << "{" << std::endl; + s << R"("Manufacturer":")" << wmi.get_value("Win32_BaseBoard", "Manufacturer") << "\"," << std::endl; + s << R"("Product":")" << wmi.get_value("Win32_BaseBoard", "Product") << "\"," << std::endl; + s << R"("Version":")" << wmi.get_value("Win32_BaseBoard", "Version") << "\"," << std::endl; + if (serial) + s << R"("SerialNumber":")" << wmi.get_value("Win32_BaseBoard", "SerialNumber") << "\"," << std::endl; + s << "}"; + smbios_ = s.str(); +#else + smbios_ = "SMBIOS info not available"; +#endif + } +} + + +void megamol::core::utility::platform::RuntimeInfo::get_cpu_info() { + if (cpu_.empty()) { +#ifdef _WIN32 + std::stringstream s; + s << "{" << std::endl; + s << R"("Manufacturer":")" << wmi.get_value("Win32_Processor", "Manufacturer") << "\"," << std::endl; + s << R"("Name":")" << wmi.get_value("Win32_Processor", "Name") << "\"," << std::endl; + s << R"("ProcessorId":")" << wmi.get_value("Win32_Processor", "ProcessorId") << "\"," << std::endl; + s << R"("NumberOfCores":")" << wmi.get_value("Win32_Processor", "NumberOfCores") << "\"," << std::endl; + s << R"("NumberOfLogicalProcessors":")" << wmi.get_value("Win32_Processor", "NumberOfLogicalProcessors") + << "\"," << std::endl; + s << R"("AvailableRam":")" << wmi.get_value("Win32_OperatingSystem", "TotalVisibleMemorySize") << "\"," + << std::endl; + s << "}"; + cpu_ = s.str(); +#else + cpu_ = execute("cat /proc/cpuinfo /proc/meminfo"); +#endif + } +} + + +void megamol::core::utility::platform::RuntimeInfo::get_gpu_info() { + if (gpu_.empty()) { +#ifdef _WIN32 + std::stringstream s; + s << "{" << std::endl; + s << R"("Name":")" << wmi.get_value("Win32_VideoController", "Name") << "\"," << std::endl; + s << R"("DriverVersion":")" << wmi.get_value("Win32_VideoController", "DriverVersion") << "\"," << std::endl; + s << R"("AdapterRam":")" << wmi.get_value("Win32_VideoController", "AdapterRam") << "\"," << std::endl; + s << "}"; + gpu_ = s.str(); +#else + gpu_ = "GPU info not available"; +#endif + } +} + + +void megamol::core::utility::platform::RuntimeInfo::get_OS_info() { + if (os_.empty()) { +#ifdef _WIN32 + std::stringstream s; + s << "{" << std::endl; + s << R"("Name":")" << wmi.get_value("Win32_ComputerSystem", "Name") << "\"," << std::endl; + s << R"("OSName":")" << wmi.get_value("Win32_OperatingSystem", "Name") << "\"," << std::endl; + s << R"("Manufacturer":")" << wmi.get_value("Win32_OperatingSystem", "Manufacturer") << "\"," << std::endl; + s << R"("Version":")" << wmi.get_value("Win32_OperatingSystem", "Version") << "\"," << std::endl; + s << R"("OSArchitecture":")" << wmi.get_value("Win32_OperatingSystem", "OSArchitecture") << "\"," << std::endl; + s << "}"; + os_ = s.str(); +#else + std::stringstream s; + s << "{" << std::endl; + s << R"("Name":")" << execute("cat /proc/sys/kernel/hostname") << "\"," << std::endl; + s << R"("OSName":")" << execute("cat /etc/issue") << "\"," << std::endl; + s << R"("Version":")" << execute("cat /proc/sys/kernel/osrelease") << "\"," << std::endl; + s << "}"; + os_ = s.str(); +#endif + } +} diff --git a/core/src/utility/platform/WMIUtil.cpp b/core/src/utility/platform/WMIUtil.cpp index 8d88a7d2a8..88853b8d3c 100644 --- a/core/src/utility/platform/WMIUtil.cpp +++ b/core/src/utility/platform/WMIUtil.cpp @@ -10,17 +10,21 @@ #ifdef _WIN32 +#include +#include + megamol::core::utility::platform::WMIUtil::WMIUtil() { HRESULT hres; // Step 1: -------------------------------------------------- // Initialize COM. ------------------------------------------ - - hres = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + auto cleanup = wil::CoInitializeEx(COINIT_MULTITHREADED); + //hres = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + /*hres = CoInitializeEx(nullptr, COINIT_MULTITHREADED); if (FAILED(hres)) { megamol::core::utility::log::Log::DefaultLog.WriteWarn( "WMIUtil: Failed to initialize COM library. Is COM already initialized? Error code = %#010X", hres); - } + }*/ // Step 2: -------------------------------------------------- // Set general COM security levels -------------------------- @@ -40,22 +44,26 @@ megamol::core::utility::platform::WMIUtil::WMIUtil() { if (FAILED(hres)) { megamol::core::utility::log::Log::DefaultLog.WriteError( "WMIUtil: Failed to initialize security. Error code = %#010X", hres); - CoUninitialize(); + //CoUninitialize(); return; } // Step 3: --------------------------------------------------- // Obtain the initial locator to WMI ------------------------- + auto locator = wil::CoCreateInstance(CLSID_WbemLocator, CLSCTX_INPROC_SERVER); + /*hres = CoCreateInstance( + CLSID_WbemLocator, nullptr, CLSCTX_INPROC_SERVER, IID_IWbemLocator, reinterpret_cast(&locator));*/ - hres = CoCreateInstance( - CLSID_WbemLocator, nullptr, CLSCTX_INPROC_SERVER, IID_IWbemLocator, reinterpret_cast(&locator)); - - if (FAILED(hres)) { + if (!locator) { + megamol::core::utility::log::Log::DefaultLog.WriteError("WMIUtil: Failed to create IWbemLocator object."); + return; + } + /*if (FAILED(hres)) { megamol::core::utility::log::Log::DefaultLog.WriteError( "WMIUtil: Failed to create IWbemLocator object. Error code = %#010X", hres); CoUninitialize(); return; - } + }*/ // Step 4: ----------------------------------------------------- // Connect to WMI through the IWbemLocator::ConnectServer method @@ -76,9 +84,9 @@ megamol::core::utility::platform::WMIUtil::WMIUtil() { if (FAILED(hres)) { megamol::core::utility::log::Log::DefaultLog.WriteError( "WMIUtil: Could not connect. Error code = %#010X", hres); - locator->Release(); - locator = nullptr; - CoUninitialize(); + //locator->Release(); + //locator = nullptr; + //CoUninitialize(); return; } @@ -87,36 +95,39 @@ megamol::core::utility::platform::WMIUtil::WMIUtil() { // Step 5: -------------------------------------------------- // Set security levels on the proxy ------------------------- - hres = CoSetProxyBlanket(service, // Indicates the proxy to set - RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx - RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx - nullptr, // Server principal name - RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx - RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx - nullptr, // client identity - EOAC_NONE // proxy capabilities + hres = CoSetProxyBlanket(service.get(), // Indicates the proxy to set + RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx + RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx + nullptr, // Server principal name + RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx + RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx + nullptr, // client identity + EOAC_NONE // proxy capabilities ); if (FAILED(hres)) { megamol::core::utility::log::Log::DefaultLog.WriteError( "WMIUtil: Could not set proxy blanket. Error code = %#010X", hres); - service->Release(); - service = nullptr; - locator->Release(); - locator = nullptr; - CoUninitialize(); + //service->Release(); + //service = nullptr; + //locator->Release(); + //locator = nullptr; + //CoUninitialize(); return; } + + cleanup.release(); } megamol::core::utility::platform::WMIUtil::~WMIUtil() { // Cleanup // ======== - if (service) - service->Release(); - if (locator) - locator->Release(); + /*if (service) + service->Release();*/ + /*if (locator) + locator->Release();*/ + service.reset(); CoUninitialize(); } @@ -165,13 +176,22 @@ std::string megamol::core::utility::platform::WMIUtil::get_value( // Get the value of the Name property hr = pclsObj->Get(ws_attribute, 0, &vtProp, nullptr, nullptr); - auto wslen = ::SysStringLen(vtProp.bstrVal); - auto len = ::WideCharToMultiByte(CP_ACP, 0, vtProp.bstrVal, wslen, nullptr, 0, nullptr, nullptr); - std::string dblstr(len, '\0'); - len = ::WideCharToMultiByte(CP_ACP, 0 /* no flags */, vtProp.bstrVal, wslen /* not necessary NULL-terminated */, - &dblstr[0], len, nullptr, nullptr /* no default char */); - ret = dblstr; - VariantClear(&vtProp); + + if (SUCCEEDED(hr)) { + if (vtProp.vt == VARENUM::VT_BSTR) { + auto wslen = ::SysStringLen(vtProp.bstrVal); + auto len = ::WideCharToMultiByte(CP_ACP, 0, vtProp.bstrVal, wslen, nullptr, 0, nullptr, nullptr); + std::string dblstr(len, '\0'); + len = ::WideCharToMultiByte(CP_ACP, 0 /* no flags */, vtProp.bstrVal, + wslen /* not necessary NULL-terminated */, &dblstr[0], len, nullptr, nullptr /* no default char */); + ret = dblstr; + } + if (vtProp.vt == VARENUM::VT_I4) { + ret = std::to_string(vtProp.lVal); + } + + VariantClear(&vtProp); + } pclsObj->Release(); } diff --git a/core_gl/src/utility/SSBOStreamer.cpp b/core_gl/src/utility/SSBOStreamer.cpp index eb13e63d7f..47c857000f 100644 --- a/core_gl/src/utility/SSBOStreamer.cpp +++ b/core_gl/src/utility/SSBOStreamer.cpp @@ -13,6 +13,10 @@ #include "vislib/assert.h" +#ifdef MEGAMOL_USE_TRACY +#include +#endif + using namespace megamol::core::utility; SSBOStreamer::SSBOStreamer(const std::string& debugLabel) @@ -132,6 +136,10 @@ void SSBOStreamer::UploadChunk(unsigned int idx, GLuint& numItems, unsigned int& if (theData == nullptr || idx > this->numChunks - 1) return; +#ifdef MEGAMOL_USE_TRACY + ZoneScopedN("SSBOStreamer::UploadChunk"); +#endif + // we did not succeed doing anything yet numItems = sync = 0; diff --git a/frontend/main/extra/megamol_config.lua.in b/frontend/main/extra/megamol_config.lua.in index 68a1562f6b..f49679e2e6 100644 --- a/frontend/main/extra/megamol_config.lua.in +++ b/frontend/main/extra/megamol_config.lua.in @@ -65,6 +65,7 @@ mmSetGlobalValue("MachineName", computer) -- CLI: -g MachineName:WhateverYourMac -- equals ./megamol.exe --fullscreen=true mmSetCliOption("fullscreen", "off") -- CLI: --fullscreen=false mmSetCliOption("vsync", "off") -- CLI: --vsync=false +mmSetCliOption("flushfinish", "off") -- CLI: --flushfinish=false mmSetCliOption("interactive", "off") -- CLI: --interactive=false mmSetCliOption("window", "1920x1080") -- CLI: --window 1920x1080 mmSetCliOption("khrdebug", "off") -- CLI: --khrdebug=false diff --git a/frontend/main/src/CLIConfigParsing.cpp b/frontend/main/src/CLIConfigParsing.cpp index 6c8efdfcfc..242c94299f 100644 --- a/frontend/main/src/CLIConfigParsing.cpp +++ b/frontend/main/src/CLIConfigParsing.cpp @@ -107,6 +107,7 @@ static std::string opengl_context_option = "opengl"; static std::string khrdebug_option = "khrdebug"; static std::string disable_opengl_option = "nogl"; static std::string vsync_option = "vsync"; +static std::string flushfinish_option = "flushfinish"; static std::string window_option = "w,window"; static std::string fullscreen_option = "f,fullscreen"; static std::string force_window_size_option = "force-window-size"; @@ -124,6 +125,10 @@ static std::string profile_log_option = "profiling-log"; static std::string flush_frequency_option = "flush-frequency"; static std::string profile_log_no_autostart_option = "pause-profiling"; static std::string profile_log_include_events_option = "profiling-include-events"; +static std::string power_lpt_option = "power-lpt"; +static std::string power_write_file_option = "power-write-file"; +static std::string power_folder_option = "power-folder"; +static std::string power_tinker_map_filename_option = "power-tinker-map-filename"; static std::string param_option = "param"; static std::string remote_head_option = "headnode"; static std::string remote_render_option = "rendernode"; @@ -199,6 +204,26 @@ static void profile_log_include_events_handler( config.include_graph_events = parsed_options[option_name].as(); } +static void power_lpt_handler( + std::string const& option_name, cxxopts::ParseResult const& parsed_options, RuntimeConfig& config) { + config.power_lpt = parsed_options[option_name].as(); +} + +static void power_write_file_handler( + std::string const& option_name, cxxopts::ParseResult const& parsed_options, RuntimeConfig& config) { + config.power_write_file = parsed_options[option_name].as(); +} + +static void power_folder_handler( + std::string const& option_name, cxxopts::ParseResult const& parsed_options, RuntimeConfig& config) { + config.power_folder = parsed_options[option_name].as(); +} + +static void power_tinker_map_filename_handler( + std::string const& option_name, cxxopts::ParseResult const& parsed_options, RuntimeConfig& config) { + config.tinker_map_filename = parsed_options[option_name].as(); +} + static void remote_head_handler( std::string const& option_name, cxxopts::ParseResult const& parsed_options, RuntimeConfig& config) { config.remote_headnode = parsed_options[option_name].as(); @@ -392,6 +417,11 @@ static void vsync_handler( config.opengl_vsync = parsed_options[option_name].as(); }; +static void flushfinish_handler( + std::string const& option_name, cxxopts::ParseResult const& parsed_options, RuntimeConfig& config) { + config.opengl_flush_finish = parsed_options[option_name].as(); +}; + static void no_opengl_handler( std::string const& option_name, cxxopts::ParseResult const& parsed_options, RuntimeConfig& config) { // User cannot overwrite default value when there is no openGL present @@ -551,6 +581,8 @@ std::vector cli_options_list = cxxopts::value(), opengl_context_handler}, {khrdebug_option, "Enable OpenGL KHR debug messages", cxxopts::value(), khrdebug_handler}, {vsync_option, "Enable VSync in OpenGL window", cxxopts::value(), vsync_handler}, + {flushfinish_option, "Use glFlush and glFinish before buffer swap", cxxopts::value(), + flushfinish_handler}, {disable_opengl_option, "Disable OpenGL. Always TRUE if not built with OpenGL", cxxopts::value(), no_opengl_handler}, {window_option, "Set the window size and position, syntax: --window WIDTHxHEIGHT[+POSX+POSY]", @@ -586,6 +618,16 @@ std::vector cli_options_list = {profile_log_include_events_option, "Include graph events in the profiling log", cxxopts::value(), profile_log_include_events_handler} +#endif +#ifdef MEGAMOL_USE_POWER + , + {power_lpt_option, "Set LPT port for trigger out", cxxopts::value(), power_lpt_handler}, + {power_write_file_option, "Set on if power service should write files to disk", cxxopts::value(), + power_write_file_handler}, + {power_folder_option, "Set output folder for power service", cxxopts::value(), + power_folder_handler}, + {power_tinker_map_filename_option, "Set output folder for power service", cxxopts::value(), + power_tinker_map_filename_handler} #endif , {param_option, "Set MegaMol Graph parameter to value: --param param=value", diff --git a/frontend/main/src/main.cpp b/frontend/main/src/main.cpp index 0384668173..9b8ca9a1e6 100644 --- a/frontend/main/src/main.cpp +++ b/frontend/main/src/main.cpp @@ -21,6 +21,7 @@ #include "ProjectLoader_Service.hpp" #include "Remote_Service.hpp" #include "RuntimeConfig.h" +#include "RuntimeInfo_Service.hpp" #include "Screenshot_Service.hpp" #include "VR_Service.hpp" #include "mmcore/LuaAPI.h" @@ -30,6 +31,11 @@ #ifdef MEGAMOL_USE_TRACY #include +#include +#endif + +#ifdef MEGAMOL_USE_POWER +#include "Power_Service.hpp" #endif using megamol::core::utility::log::Log; @@ -55,6 +61,9 @@ void loadPlugins(megamol::frontend_resources::PluginsResource& pluginsRes); CMRC_DECLARE(megamol_icons); int main(const int argc, const char** argv) { +#if defined(MEGAMOL_DETECT_MEMLEAK) && defined(DEBUG) + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +#endif #ifdef MEGAMOL_USE_TRACY ZoneScoped; #endif @@ -79,6 +88,9 @@ int main(const int argc, const char** argv) { log(config.as_string()); log(global_value_store.as_string()); + megamol::frontend::RuntimeInfo_Service ri_service; + ri_service.setPriority(1); + megamol::frontend::OpenGL_GLFW_Service gl_service; megamol::frontend::OpenGL_GLFW_Service::Config openglConfig; openglConfig.windowTitlePrefix = "MegaMol"; @@ -89,6 +101,7 @@ int main(const int argc, const char** argv) { } openglConfig.enableKHRDebug = config.opengl_khr_debug; openglConfig.enableVsync = config.opengl_vsync; + openglConfig.enableGlFlushFinish = config.opengl_flush_finish; // pass window size and position if (config.window_size.has_value()) { openglConfig.windowPlacement.size = true; @@ -188,6 +201,16 @@ int main(const int argc, const char** argv) { profiling_config.autostart_profiling = config.autostart_profiling; profiling_config.include_graph_events = config.include_graph_events; +#ifdef MEGAMOL_USE_POWER + megamol::frontend::Power_Service power_service; + megamol::frontend::Power_Service::Config power_config; + power_config.lpt = config.power_lpt; + power_config.write_to_files = config.power_write_file; + power_config.folder = config.power_folder; + power_config.tinker_map_filename = config.tinker_map_filename; + power_service.setPriority(1); +#endif + #ifdef MM_CUDA_ENABLED megamol::frontend::CUDA_Service cuda_service; cuda_service.setPriority(24); @@ -208,6 +231,10 @@ int main(const int argc, const char** argv) { // clang-format on bool run_megamol = true; megamol::frontend::FrontendServiceCollection services; + services.add(ri_service, nullptr); +#ifdef MEGAMOL_USE_POWER + services.add(power_service, &power_config); +#endif if (with_gl) { services.add(gl_service, &openglConfig); } diff --git a/frontend/resources/include/PowerCallbacks.h b/frontend/resources/include/PowerCallbacks.h new file mode 100644 index 0000000000..5ca053f949 --- /dev/null +++ b/frontend/resources/include/PowerCallbacks.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +namespace megamol::frontend_resources { + +static std::string PowerCallbacks_Req_Name = "PowerCallbacks"; + +struct PowerCallbacks { + std::function signal_high; + std::function signal_low; + std::function signal_frame; +}; + +} // namespace megamol::frontend_resources diff --git a/frontend/resources/include/RuntimeConfig.h b/frontend/resources/include/RuntimeConfig.h index b29fba5565..0a31c07913 100644 --- a/frontend/resources/include/RuntimeConfig.h +++ b/frontend/resources/include/RuntimeConfig.h @@ -59,6 +59,7 @@ struct RuntimeConfig { #endif bool opengl_khr_debug = false; bool opengl_vsync = false; + bool opengl_flush_finish = false; std::optionalcore, false=>compat*/>> opengl_context_version = {{4, 6, false /*compat*/}}; std::optional window_size = @@ -83,6 +84,11 @@ struct RuntimeConfig { bool autostart_profiling = true; bool include_graph_events = false; + std::string power_lpt = "lpt1"; + bool power_write_file = false; + std::string power_folder = "./"; + std::string tinker_map_filename = "./tinkerforge.json"; + struct Tile { UintPair global_framebuffer_resolution; // e.g. whole powerwall resolution, needed for tiling UintPair tile_start_pixel; diff --git a/frontend/resources/include/RuntimeInfo.h b/frontend/resources/include/RuntimeInfo.h new file mode 100644 index 0000000000..f22528aa4d --- /dev/null +++ b/frontend/resources/include/RuntimeInfo.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +namespace megamol::frontend_resources { +struct RuntimeInfo { + std::function get_hardware_info; + std::function get_os_info; + std::function get_runtime_libraries; + + std::function get_smbios_info; + std::function get_cpu_info; + std::function get_gpu_info; + std::function get_OS_info; +}; +} // namespace megamol::frontend_resources diff --git a/frontend/resources/include/Screenshots.h b/frontend/resources/include/Screenshots.h index 1b22ebce15..48a8c70c1a 100644 --- a/frontend/resources/include/Screenshots.h +++ b/frontend/resources/include/Screenshots.h @@ -57,11 +57,13 @@ class IScreenshotSource { class IImageDataWriter { public: - bool write_screenshot(IScreenshotSource const& image_source, std::filesystem::path const& filename) const { - return this->write_image(image_source.take_screenshot(), filename); + bool write_screenshot(IScreenshotSource const& image_source, std::filesystem::path const& filename, + void const* user_ptr = nullptr) const { + return this->write_image(image_source.take_screenshot(), filename, user_ptr); } - virtual bool write_image(ScreenshotImageData const& image, std::filesystem::path const& filename) const = 0; + virtual bool write_image(ScreenshotImageData const& image, std::filesystem::path const& filename, + void const* user_ptr = nullptr) const = 0; ~IImageDataWriter() = default; }; @@ -99,7 +101,8 @@ class GLScreenshotSource : public IScreenshotSource { class ScreenshotImageDataToPNGWriter : public IImageDataWriter { public: - bool write_image(ScreenshotImageData const& image, std::filesystem::path const& filename) const override; + bool write_image(ScreenshotImageData const& image, std::filesystem::path const& filename, + void const* user_ptr = nullptr) const override; }; diff --git a/frontend/resources/include/WindowManipulation.h b/frontend/resources/include/WindowManipulation.h index 842a1b29c2..d0468bd739 100644 --- a/frontend/resources/include/WindowManipulation.h +++ b/frontend/resources/include/WindowManipulation.h @@ -27,6 +27,11 @@ struct WindowManipulation { void set_fullscreen(const Fullscreen action) const GL_VSTUB(); void* window_ptr = nullptr; + bool enable_gl_flush_finish = false; + +#ifdef MEGAMOL_USE_POWER + void const* power_callbacks = nullptr; +#endif }; } // namespace megamol::frontend_resources diff --git a/frontend/services/CMakeLists.txt b/frontend/services/CMakeLists.txt index 3d314937e1..d2b4b83717 100644 --- a/frontend/services/CMakeLists.txt +++ b/frontend/services/CMakeLists.txt @@ -13,6 +13,7 @@ find_package(imguizmoquat CONFIG REQUIRED) find_package(implot CONFIG REQUIRED) find_package(ZeroMQ REQUIRED) find_package(cppzmq REQUIRED) +find_package(sol2 CONFIG REQUIRED) file(GLOB_RECURSE header_files RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "include/*.h" @@ -32,6 +33,8 @@ file(GLOB_RECURSE header_files RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "remote_service/*.hpp" "profiling_service/*.hpp" "vr_service/*.hpp" + "runtimeinfo_service/*.hpp" + "power_service/*.hpp" # "service_template/*.hpp" ) @@ -48,6 +51,8 @@ file(GLOB_RECURSE source_files RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "remote_service/*.cpp" "profiling_service/*.cpp" "vr_service/*.cpp" + "runtimeinfo_service/*.cpp" + "power_service/*.cpp" # "service_template/*.cpp" ) @@ -109,6 +114,8 @@ target_include_directories(${PROJECT_NAME} PUBLIC "gui/3rd" "gui/src" "vr_service" + "runtimeinfo_service" + "power_service" # "service_template" ) @@ -119,6 +126,10 @@ target_include_directories(abstract_frontend_service INTERFACE ) target_link_libraries(abstract_frontend_service INTERFACE frontend_resources) +# sol2 +target_link_libraries(${PROJECT_NAME} INTERFACE sol2) +target_compile_definitions(${PROJECT_NAME} PUBLIC SOL_ALL_SAFETIES_ON=1) + if (MEGAMOL_USE_CUDA) find_package(CUDAToolkit) target_compile_definitions(${PROJECT_NAME} PUBLIC MM_CUDA_ENABLED) @@ -136,8 +147,15 @@ if (MEGAMOL_USE_OPENGL) endif () if (MEGAMOL_USE_POWER) + find_package(Parquet CONFIG REQUIRED) + target_link_libraries(${PROJECT_NAME} PRIVATE "$,Parquet::parquet_static,Parquet::parquet_shared>") find_package(power_overwhelming CONFIG REQUIRED) - target_link_libraries(${PROJECT_NAME} PUBLIC power_overwhelming::power_overwhelming) + find_package(dataverse CONFIG REQUIRED) + target_link_libraries(${PROJECT_NAME} PUBLIC power_overwhelming::power_overwhelming dataverse::dataverse) + if (WIN32) + find_package(wil REQUIRED) + target_link_libraries(${PROJECT_NAME} PUBLIC WIL::WIL) + endif() endif () if (MEGAMOL_USE_VR_INTEROP) diff --git a/frontend/services/opengl_glfw/OpenGL_GLFW_Service.hpp b/frontend/services/opengl_glfw/OpenGL_GLFW_Service.hpp index b11c0f93f8..48fc9d9bc6 100644 --- a/frontend/services/opengl_glfw/OpenGL_GLFW_Service.hpp +++ b/frontend/services/opengl_glfw/OpenGL_GLFW_Service.hpp @@ -18,6 +18,12 @@ #include "WindowManipulation.h" #include "Window_Events.h" +#ifdef MEGAMOL_USE_POWER +#include "PowerCallbacks.h" +#endif + +#include + namespace megamol::frontend { struct WindowPlacement { @@ -53,6 +59,7 @@ class OpenGL_GLFW_Service final : public AbstractFrontendService { std::vector windowIcons{}; bool enableKHRDebug = true; // max error reporting bool enableVsync = false; // max frame rate + bool enableGlFlushFinish = false; bool glContextCoreProfile = false; bool forceWindowSize = false; }; @@ -131,6 +138,10 @@ class OpenGL_GLFW_Service final : public AbstractFrontendService { frontend_resources::OpenGL_Helper m_opengl_helper; WindowManipulation m_windowManipulation; +#ifdef MEGAMOL_USE_POWER + frontend_resources::PowerCallbacks const* power_callbacks_ = nullptr; +#endif + // this holds references to the event structs we fill. the events are passed to the renderers/views using // const std::vector& getModuleResources() override std::vector m_renderResourceReferences; diff --git a/frontend/services/opengl_glfw/gl/OpenGL_GLFW_Service.cpp b/frontend/services/opengl_glfw/gl/OpenGL_GLFW_Service.cpp index df32f39565..3830137588 100644 --- a/frontend/services/opengl_glfw/gl/OpenGL_GLFW_Service.cpp +++ b/frontend/services/opengl_glfw/gl/OpenGL_GLFW_Service.cpp @@ -44,6 +44,10 @@ #include #endif +#ifdef MEGAMOL_USE_POWER +#include "PowerCallbacks.h" +#endif + static const std::string service_name = "OpenGL_GLFW_Service: "; static void log(std::string const& text) { const std::string msg = service_name + text; @@ -295,11 +299,27 @@ void megamol::frontend_resources::WindowManipulation::set_swap_interval(const un } void megamol::frontend_resources::WindowManipulation::swap_buffers() const { +#ifdef MEGAMOL_USE_TRACY + ZoneScopedN("swap_buffers"); +#endif + if (enable_gl_flush_finish) { + glFlush(); + glFinish(); + } glfwSwapBuffers(reinterpret_cast(window_ptr)); #ifdef MEGAMOL_USE_TRACY - TracyGpuCollect; + static unsigned int collect_cnt = 0; + if (collect_cnt % 10 == 0) { + TracyGpuCollect; + } + ++collect_cnt; FrameMark; #endif +#ifdef MEGAMOL_USE_POWER + /*reinterpret_cast(power_callbacks)->signal_high(); + reinterpret_cast(power_callbacks)->signal_low();*/ + reinterpret_cast(power_callbacks)->signal_frame(); +#endif } void megamol::frontend_resources::WindowManipulation::set_fullscreen(const Fullscreen action) const { @@ -585,6 +605,7 @@ bool OpenGL_GLFW_Service::init(const Config& config) { m_windowEvents.previous_state.time = glfwGetTime(); m_windowManipulation.window_ptr = window_ptr; + m_windowManipulation.enable_gl_flush_finish = m_pimpl->config.enableGlFlushFinish; m_windowManipulation.set_mouse_cursor = [&](const int cursor_id) -> void { update_glfw_mouse_cursors(cursor_id); }; @@ -598,8 +619,12 @@ bool OpenGL_GLFW_Service::init(const Config& config) { {frontend_resources::OpenGL_Helper_Req_Name, m_opengl_helper}}; m_requestedResourcesNames = {"FrameStatistics", "FramebufferEvents", - frontend_resources::MegaMolGraph_SubscriptionRegistry_Req_Name, - frontend_resources::FrameStatsCallbacks_Req_Name}; + frontend_resources::MegaMolGraph_SubscriptionRegistry_Req_Name, frontend_resources::FrameStatsCallbacks_Req_Name +#ifdef MEGAMOL_USE_POWER + , + frontend_resources::PowerCallbacks_Req_Name +#endif + }; m_pimpl->last_time = std::chrono::system_clock::now(); @@ -820,6 +845,11 @@ void OpenGL_GLFW_Service::setRequestedResources(std::vector re megamolgraph_subscription.subscribe(debug_helper_subscription); #endif + +#ifdef MEGAMOL_USE_POWER + power_callbacks_ = &resources[4].getResource(); + m_windowManipulation.power_callbacks = power_callbacks_; +#endif } void OpenGL_GLFW_Service::glfw_onKey_func(const int key, const int scancode, const int action, const int mods) { diff --git a/frontend/services/power_service/Power_Service.cpp b/frontend/services/power_service/Power_Service.cpp new file mode 100644 index 0000000000..db5f43d35f --- /dev/null +++ b/frontend/services/power_service/Power_Service.cpp @@ -0,0 +1,378 @@ +/* + * Power_Service.cpp + * + * Copyright (C) 2021 by MegaMol Team + * Alle Rechte vorbehalten. + */ + +#include "Power_Service.hpp" + +#ifdef MEGAMOL_USE_POWER + +#include + +#include + +#include "LuaApiResource.h" +#include "ModuleGraphSubscription.h" +#include "mmcore/LuaAPI.h" +#include "mmcore/utility/log/Log.h" +#include "power/DataverseWriter.h" +#include "power/Tinkerforge.h" +#include "power/WriterUtility.h" + +#if _WIN32 +#include +#endif + +// local logging wrapper for your convenience until central MegaMol logger established +static const std::string service_name = "[Power_Service] "; +static void log(std::string const& text) { + const std::string msg = service_name + text; + megamol::core::utility::log::Log::DefaultLog.WriteInfo(msg.c_str()); +} + +static void log_error(std::string const& text) { + const std::string msg = service_name + text; + megamol::core::utility::log::Log::DefaultLog.WriteError(msg.c_str()); +} + +static void log_warning(std::string const& text) { + const std::string msg = service_name + text; + megamol::core::utility::log::Log::DefaultLog.WriteWarn(msg.c_str()); +} + + +namespace megamol { +namespace frontend { + +//bool Power_Service::init_sol_commands_ = true; + +Power_Service::Power_Service() /*: sol_state_(nullptr)*/ { + // init members to default states +} + +Power_Service::~Power_Service() { + // clean up raw pointers you allocated with new, which is bad practice and nobody does +} + + +bool Power_Service::init(void* configPtr) { + if (configPtr == nullptr) + return false; + + const auto conf = static_cast(configPtr); + auto const lpt = conf->lpt; + write_to_files_ = conf->write_to_files; + write_folder_ = conf->folder; + + main_trigger_ = std::make_shared(lpt); + main_trigger_->RegisterPreTrigger("Power_Service_sb", std::bind(&Power_Service::sb_pre_trg, this)); + main_trigger_->RegisterInitTrigger("Power_Service_hmc", std::bind(&Power_Service::hmc_init_trg, this)); + main_trigger_->RegisterPostTrigger("Power_Service_sb", std::bind(&Power_Service::sb_post_trg, this)); + main_trigger_->RegisterPostTrigger("Power_Service_seg", std::bind(&Power_Service::seg_post_trg, this)); + main_trigger_->RegisterFinTrigger("Power_Service_hmc", std::bind(&Power_Service::hmc_fin_trg, this)); + main_trigger_->RegisterSignal( + "Power_Service_trg", std::bind(&Power_Service::trigger_ts_signal, this, std::placeholders::_1)); + +#if WIN32 + try { + rtx_ = std::make_unique(main_trigger_); + } catch (std::exception& ex) { + log_warning(std::format("RTX devices not available: {}", ex.what())); + rtx_ = nullptr; + } +#else + rtx_ = nullptr; +#endif + + callbacks_.signal_high = + std::bind(&megamol::power::ParallelPortTrigger::SetBit, main_trigger_->GetHandle(), 7, true); + callbacks_.signal_low = + std::bind(&megamol::power::ParallelPortTrigger::SetBit, main_trigger_->GetHandle(), 7, false); + callbacks_.signal_frame = [&]() -> void { + main_trigger_->GetHandle()->SetBit(7, true); + main_trigger_->GetHandle()->SetBit(7, false); + }; + + m_providedResourceReferences = {{frontend_resources::PowerCallbacks_Req_Name, callbacks_}}; + + m_requestedResourcesNames = { + frontend_resources::LuaAPI_Req_Name, "RuntimeInfo", frontend_resources::MegaMolGraph_Req_Name}; + + using namespace visus::power_overwhelming; + + auto emi_discard_func = [](std::string const& name) { return name.find("RAPL_Package0_PKG") == std::string::npos; }; + + auto msr_discard_func = [](std::string const& name) { return name.find("msr/0/package") == std::string::npos; }; + + auto adl_discard_func = [](std::string const& name) { + static bool first = false; + if (name.find("Radeon") == std::string::npos || first) { + return true; + } else { + first = true; + return false; + } + }; + + auto tinker_config_func = [](tinkerforge_sensor& sensor) { + sensor.reset(); + //sensor.resync_internal_clock_after(20); + sensor.configure( + sample_averaging::average_of_4, conversion_time::milliseconds_1_1, conversion_time::milliseconds_1_1); + }; + + std::function tinker_transform_func = [](std::string const& name) { return name; }; + nlohmann::json json_data; + try { + json_data = power::parse_json_file(conf->tinker_map_filename); + tinker_transform_func = std::bind(&power::transform_tf_name, std::cref(json_data), std::placeholders::_1); + } catch (...) { + core::utility::log::Log::DefaultLog.WriteWarn( + "[Power_Service] Could not parse Tinker json file. Using fallback."); + } + + auto nvml_transform_func = [](std::string const& name) -> std::string { return "NVML[" + name + "]"; }; + + auto adl_transform_func = [](std::string const& name) -> std::string { return "ADL[" + name + "]"; }; + + auto msr_transform_func = [](std::string const& name) -> std::string { return "MSR[" + name + "]"; }; + + std::unique_ptr> nvml_samplers = nullptr; + try { + nvml_samplers = std::make_unique>( + std::chrono::milliseconds(600), std::chrono::milliseconds(10), nullptr, nullptr, nvml_transform_func); + } catch (...) { + nvml_samplers = nullptr; + core::utility::log::Log::DefaultLog.WriteWarn("[Power_Service] No NVML sensors available"); + } + std::unique_ptr> adl_samplers = nullptr; + try { + adl_samplers = std::make_unique>(std::chrono::milliseconds(600), + std::chrono::milliseconds(10), adl_discard_func, nullptr, adl_transform_func); + } catch (...) { + adl_samplers = nullptr; + core::utility::log::Log::DefaultLog.WriteWarn("[Power_Service] No ADL sensors available"); + } + std::unique_ptr> emi_samplers = nullptr; + try { + emi_samplers = std::make_unique>( + std::chrono::milliseconds(600), std::chrono::milliseconds(1), emi_discard_func); + } catch (...) { + emi_samplers = nullptr; + core::utility::log::Log::DefaultLog.WriteWarn("[Power_Service] No EMI sensors available"); + } + std::unique_ptr> msr_samplers = nullptr; + if (!emi_samplers) { + try { + msr_samplers = std::make_unique>(std::chrono::milliseconds(600), + std::chrono::milliseconds(1), msr_discard_func, nullptr, msr_transform_func); + } catch (...) { + msr_samplers = nullptr; + core::utility::log::Log::DefaultLog.WriteWarn("[Power_Service] No MSR sensors available"); + } + } + std::unique_ptr> tinker_samplers = nullptr; + try { + tinker_samplers = std::make_unique>(std::chrono::milliseconds(600), + std::chrono::milliseconds(10), nullptr, tinker_config_func, tinker_transform_func); + } catch (...) { + tinker_samplers = nullptr; + core::utility::log::Log::DefaultLog.WriteWarn("[Power_Service] No Tinkerforge sensors available"); + } + + samplers = std::make_unique(std::move(nvml_samplers), std::move(adl_samplers), + std::move(emi_samplers), std::move(msr_samplers), std::move(tinker_samplers)); + + try { + std::vector hmc_tmp(hmc8015_sensor::for_all(nullptr, 0)); + hmc8015_sensor::for_all(hmc_tmp.data(), hmc_tmp.size()); + hmc_sensors_.reserve(hmc_tmp.size()); + for (auto& s : hmc_tmp) { + s.synchronise_clock(true); + s.voltage_range(instrument_range::explicitly, 300); + s.current_range(instrument_range::explicitly, 5); + s.log_behaviour(std::numeric_limits::lowest(), log_mode::unlimited); + auto const name = power::get_pwrowg_str(s, &hmc8015_sensor::instrument_name); + hmc_sensors_[name] = std::move(s); + } + } catch (...) { + core::utility::log::Log::DefaultLog.WriteInfo("[Power_Service]: No HMC devices found"); + } + + log("initialized successfully"); + //return init(*static_cast(configPtr)); + return true; +} + +void Power_Service::close() { + hmc_sensors_.clear(); + samplers.reset(); +} + +std::vector& Power_Service::getProvidedResources() { + return m_providedResourceReferences; +} + +const std::vector Power_Service::getRequestedResourceNames() const { + return m_requestedResourcesNames; +} + +void Power_Service::setRequestedResources(std::vector resources) { + // maybe we want to keep the list of requested resources + this->m_requestedResourceReferences = resources; + + ri_ = &m_requestedResourceReferences[1].getResource(); + megamolgraph_ptr_ = + const_cast(&m_requestedResourceReferences[2].getResource()); + + meta_.runtime_libs = ri_->get_runtime_libraries(); + + meta_.hardware_software_info.clear(); + meta_.hardware_software_info["OS"] = ri_->get_OS_info(); + meta_.hardware_software_info["SMBIOS"] = ri_->get_smbios_info(); + meta_.hardware_software_info["GPU"] = ri_->get_gpu_info(); + meta_.hardware_software_info["CPU"] = ri_->get_cpu_info(); + + fill_lua_callbacks(); +} + +void Power_Service::updateProvidedResources() {} + +void Power_Service::digestChangedRequestedResources() {} + +void Power_Service::resetProvidedResources() {} + +void Power_Service::preGraphRender() {} + +void Power_Service::postGraphRender() {} + +void Power_Service::fill_lua_callbacks() { + auto luaApi = m_requestedResourceReferences[0].getResource(); + + luaApi->RegisterCallback("mmPowerSetup", "()", [&]() -> void { + //setup_measurement(); + if (rtx_) { + rtx_->ApplyConfigs(&meta_); + } + }); + + luaApi->RegisterCallback("mmPowerMeasure", "(string path)", [&](std::string path) -> void { + //start_measurement(); + reset_measurement(); + write_folder_ = path; + meta_.project_file = megamolgraph_ptr_->Convenience().SerializeGraph(); + if (rtx_) { + if (write_to_files_) { + if (dataverse_key_) { + std::function dataverse_writer = + std::bind(&power::DataverseWriter, dataverse_config_.base_path, dataverse_config_.doi, + std::placeholders::_1, dataverse_key_->GetToken(), std::ref(sbroker_.Get(false))); + power::writer_func_t parquet_dataverse_writer = + std::bind(&power::wf_parquet_dataverse, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4, dataverse_writer); + rtx_->StartMeasurement(path, {parquet_dataverse_writer, &power::wf_tracy_wrapper::wf_tracy}, &meta_, + sbroker_.Get(false)); + } else { + rtx_->StartMeasurement( + path, {&power::wf_parquet, &power::wf_tracy_wrapper::wf_tracy}, &meta_, sbroker_.Get(false)); + } + } else { + rtx_->StartMeasurement(path, {&power::wf_tracy_wrapper::wf_tracy}, &meta_, sbroker_.Get(false)); + } + } + }); + + luaApi->RegisterCallback( + "mmPowerWriteToFile", "(bool flag)", [&](bool const flag) -> void { write_to_files_ = flag; }); + + luaApi->RegisterCallback("mmPowerSetLPTAddress", "(string address)", + [&](std::string const address) -> void { main_trigger_->SetLPTAddress(address); }); + + luaApi->RegisterCallback("mmPowerConfig", "(string path, int points, int count, int range_ms, int timeout_ms)", + [&](std::string path, int points, int count, int range, int timeout) -> void { + if (rtx_) { + rtx_->UpdateConfigs( + path, points, count, std::chrono::milliseconds(range), std::chrono::milliseconds(timeout)); + } + meta_.trigger_ts.reserve(count); + reset_segment_range(std::chrono::milliseconds(range)); + }); + + luaApi->RegisterCallback("mmPowerIsPending", "()", [&]() -> bool { return sbroker_.GetValue(); }); + + luaApi->RegisterCallback("mmPowerSoftwareTrigger", "(bool set)", [&](bool set) -> void { + if (rtx_) { + rtx_->SetSoftwareTrigger(set); + } + }); + + luaApi->RegisterCallback("mmPowerDataverseKey", "(string path_to_key)", [&](std::string path_to_key) -> void { +#if WIN32 + dataverse_key_ = std::make_unique(path_to_key); +#else + core::utility::log::Log::DefaultLog.WriteWarn("[Power_Service] Dataverse access currently Windows only."); +#endif + }); + + luaApi->RegisterCallback("mmPowerDataverseDataset", "(string base_path, string doi)", + [&](std::string base_path, std::string doi) -> void { + dataverse_config_.base_path = base_path; + dataverse_config_.doi = doi; + }); + + luaApi->RegisterCallback( + "mmPowerRecipe", "(string name, string path)", [&](std::string name, std::string path) -> void { + if (std::filesystem::exists(path) && std::filesystem::is_regular_file(path)) { + auto const size = std::filesystem::file_size(path); + std::ifstream f(path); + std::vector data(size); + f.read(data.data(), data.size()); + f.close(); + meta_.analysis_recipes[name] = std::string(data.begin(), data.end()); + } + }); +} + + +void Power_Service::write_sample_buffers(std::size_t seg_cnt) { + auto const nvml_path = std::filesystem::path(write_folder_) / ("nvml_s" + std::to_string(seg_cnt) + ".parquet"); + auto const adl_path = std::filesystem::path(write_folder_) / ("adl_s" + std::to_string(seg_cnt) + ".parquet"); + auto const emi_path = std::filesystem::path(write_folder_) / ("emi_s" + std::to_string(seg_cnt) + ".parquet"); + auto const msr_path = std::filesystem::path(write_folder_) / ("msr_s" + std::to_string(seg_cnt) + ".parquet"); + auto const tinker_path = std::filesystem::path(write_folder_) / ("tinker_s" + std::to_string(seg_cnt) + ".parquet"); + + auto const tpl = std::make_tuple(power::SamplersCollectionWrapper::nvml_path_t{nvml_path}, + power::SamplersCollectionWrapper::adl_path_t{adl_path}, power::SamplersCollectionWrapper::emi_path_t{emi_path}, + power::SamplersCollectionWrapper::msr_path_t{msr_path}, + power::SamplersCollectionWrapper::tinker_path_t{tinker_path}); + + if (dataverse_key_) { + samplers->visit( + &power::ISamplerCollection::WriteBuffers, tpl, &meta_, dataverse_config_.base_path, dataverse_config_.doi, + dataverse_key_->GetToken(), sbroker_.Get(false)); + } else { + samplers->visit(&power::ISamplerCollection::WriteBuffers, tpl, &meta_); + } + + samplers->visit(&power::ISamplerCollection::ResetBuffers); +} + + +void Power_Service::reset_segment_range(std::chrono::milliseconds const& range) { + auto const [trg_prefix, trg_postfix, trg_wait] = power::get_trigger_timings(range); + samplers->visit( + &power::ISamplerCollection::SetSegmentRange, trg_prefix + trg_postfix + std::chrono::seconds(1)); +} + +void Power_Service::reset_measurement() { + sbroker_.Reset(); + seg_cnt_ = 0; + meta_.trigger_ts.clear(); +} + +} // namespace frontend +} // namespace megamol + +#endif diff --git a/frontend/services/power_service/Power_Service.hpp b/frontend/services/power_service/Power_Service.hpp new file mode 100644 index 0000000000..be343426b6 --- /dev/null +++ b/frontend/services/power_service/Power_Service.hpp @@ -0,0 +1,233 @@ +/* + * Power_Service.hpp + * + * Copyright (C) 2021 by MegaMol Team + * Alle Rechte vorbehalten. + */ + +#pragma once + +#ifdef MEGAMOL_USE_POWER + +#include +#include +#include +#include + +#include "mmcore/MegaMolGraph.h" + +#include "AbstractFrontendService.hpp" + +#include "PowerCallbacks.h" +#include "RuntimeInfo.h" + +#include "power/CryptToken.h" +#include "power/MetaData.h" +#include "power/ParquetWriter.h" +#include "power/RTXInstruments.h" +#include "power/SamplerCollection.h" +#include "power/SignalBroker.h" +#include "power/Trigger.h" +#include "power/Utility.h" + +#include +#include + + +namespace megamol { +namespace frontend { + +inline std::string gen_hmc_filename(unsigned int const cnt) { + return std::string("PWR_") + std::to_string(cnt); +} + +inline std::string gen_hmc_filename() { + return std::string("PWR"); +} + +inline std::string gen_hmc_filename(std::string const& fix) { + return fix + std::string(".CSV"); +} + +class Power_Service final : public AbstractFrontendService { +public: + struct Config { + std::string lpt = "lpt1"; + bool write_to_files = false; + std::string folder = "./"; + std::string tinker_map_filename = "./tinkerforge.json"; + }; + + std::string serviceName() const override { + return "Power_Service"; + } + + Power_Service(); + + ~Power_Service(); + + bool init(void* configPtr) override; + + void close() override; + + std::vector& getProvidedResources() override; + + const std::vector getRequestedResourceNames() const override; + + void setRequestedResources(std::vector resources) override; + + void updateProvidedResources() override; + + void digestChangedRequestedResources() override; + + void resetProvidedResources() override; + + void preGraphRender() override; + + void postGraphRender() override; + +private: + std::vector m_providedResourceReferences; + + std::vector m_requestedResourcesNames; + + std::vector m_requestedResourceReferences; + + frontend_resources::PowerCallbacks callbacks_; + + std::vector rtx_instr_; + + std::unique_ptr samplers; + + std::unordered_map hmc_sensors_; + + void fill_lua_callbacks(); + + void reset_segment_range(std::chrono::milliseconds const& range); + + void reset_measurement(); + + void write_sample_buffers(std::size_t seg_cnt); + + void seg_post_trg() { + ++seg_cnt_; + } + + void sb_pre_trg() { + do_buffer_ = true; + + samplers->visit(&power::ISamplerCollection::Reset); + samplers->visit(&power::ISamplerCollection::StartRecording); + } + + void sb_post_trg() { + do_buffer_ = false; + + samplers->visit(&power::ISamplerCollection::StopRecording); + + if (write_to_files_) { + write_sample_buffers(seg_cnt_); + } + } + + void hmc_init_trg() { + for (auto& [n, s] : hmc_sensors_) { + s.log_file(gen_hmc_filename().c_str(), true, false); + auto const path_size = s.log_file((char*) nullptr, 0); + std::string path; + path.resize(path_size); + s.log_file(path.data(), path.size()); + if (!path.empty()) { + path.resize(path.size() - 1); + } + std::regex reg(R"(^\"(\w+)\", INT$)"); + std::smatch match; + if (std::regex_match(path, match, reg)) { + hmc_paths_[n] = gen_hmc_filename(match[1]); + } + } + // TODO: maybe need sleep here + for (auto& [n, s] : hmc_sensors_) { + s.reset_integrator(); + s.start_integrator(); + s.log(true); + } + } + + void hmc_fin_trg() { + for (auto& [n, s] : hmc_sensors_) { + s.log(false); + s.stop_integrator(); + } + std::this_thread::sleep_for(std::chrono::seconds(1)); + if (write_to_files_) { + for (auto& [n, s] : hmc_sensors_) { + try { + //auto blob = s.copy_file_from_instrument(gen_hmc_filename(hmc_m).c_str(), false); + auto blob = s.copy_file_from_instrument(hmc_paths_[n].c_str(), false); + + auto hmc_file = std::string(blob.begin(), blob.end()); + + auto const [meta_str, csv_str, vals] = power::parse_hmc_file(hmc_file); + + auto const csv_path = power::create_full_path(write_folder_, n, ".csv"); + std::ofstream file(csv_path.string(), std::ios::binary); + file.write(csv_str.data(), csv_str.size()); + file.close(); + auto const meta_path = power::create_full_path(write_folder_, n, ".meta.csv"); + file.open(meta_path.string(), std::ios::binary); + file.write(meta_str.data(), meta_str.size()); + file.close(); + + auto const parquet_path = power::create_full_path(write_folder_, n); + power::ParquetWriter(parquet_path, vals, &meta_); + } catch (...) { + core::utility::log::Log::DefaultLog.WriteError( + "HMC: failed to fetch data {}", hmc_paths_[n].back()); + } + } + } + for (auto& [n, s] : hmc_sensors_) { + s.delete_file_from_instrument(hmc_paths_[n].c_str(), false); + hmc_paths_[n].clear(); + } + } + + void trigger_ts_signal(power::filetime_dur_t const& ts) { + meta_.trigger_ts.push_back(ts); + } + + bool write_to_files_ = false; + + std::string write_folder_ = "./"; + + std::unique_ptr rtx_; + + bool do_buffer_ = false; + + std::size_t seg_cnt_ = 0; + + std::shared_ptr main_trigger_; + + frontend_resources::RuntimeInfo const* ri_; + + power::MetaData meta_; + + std::unique_ptr dataverse_key_ = nullptr; + + struct dataverse_config_s { + std::string base_path; + std::string doi; + } dataverse_config_; + + power::SignalBroker sbroker_; + + core::MegaMolGraph* megamolgraph_ptr_; + + std::unordered_map hmc_paths_; +}; + +} // namespace frontend +} // namespace megamol + +#endif diff --git a/frontend/services/power_service/config/HPWR/tinkerforge.json b/frontend/services/power_service/config/HPWR/tinkerforge.json new file mode 100644 index 0000000000..c4bea8342c --- /dev/null +++ b/frontend/services/power_service/config/HPWR/tinkerforge.json @@ -0,0 +1,10 @@ +{ + "12VHPWR": [ "Ugu", "UgH", "Ufm", "UgF", "Uft", "UeW" ], + "12VPEG": [ "U6Q" ], + "3VPEG": [ "UfN" ], + "12VATX": [ "U5T" ], + "5VATX": [ "UgC" ], + "3VATX": [ "244F" ], + "P8": [ "Ugx" ], + "P4": [ "Ugo" ] +} diff --git a/frontend/services/power_service/config/HPWR/tinkerforge_recipe.json b/frontend/services/power_service/config/HPWR/tinkerforge_recipe.json new file mode 100644 index 0000000000..431e60a8ee --- /dev/null +++ b/frontend/services/power_service/config/HPWR/tinkerforge_recipe.json @@ -0,0 +1,18 @@ +{ + "PEGAUX": { + "ADD": [ "12VHPWR0_samples", "12VHPWR1_samples", "12VHPWR2_samples", "12VHPWR3_samples", "12VHPWR4_samples", "12VHPWR5_samples" ] + }, + "12VPEG": { "ADD": [ "12VPEG_samples" ] }, + "3VPEG": { "ADD": [ "3VPEG_samples" ] }, + "12VATX": { + "ADD": [ "12VATX_samples" ], + "SUB": [ "12VPEG_samples" ] + }, + "5VATX": { "ADD": [ "5VATX_samples" ] }, + "3VATX": { + "ADD": [ "3VATX_samples" ], + "SUB": [ "3VPEG_samples" ] + }, + "P8": { "ADD": [ "P8_samples" ] }, + "P4": { "ADD": [ "P4_samples" ] } +} diff --git a/frontend/services/power_service/config/PEG2/tinkerforge.json b/frontend/services/power_service/config/PEG2/tinkerforge.json new file mode 100644 index 0000000000..d119b4e007 --- /dev/null +++ b/frontend/services/power_service/config/PEG2/tinkerforge.json @@ -0,0 +1,10 @@ +{ + "PEG": [ "244E", "244J" ], + "12VPEG": [ "U6Q" ], + "3VPEG": [ "UfN" ], + "12VATX": [ "U5T" ], + "5VATX": [ "UgC" ], + "3VATX": [ "244F" ], + "P8": [ "Ugx" ], + "P4": [ "Ugo" ] +} diff --git a/frontend/services/power_service/config/PEG2/tinkerforge_recipe.json b/frontend/services/power_service/config/PEG2/tinkerforge_recipe.json new file mode 100644 index 0000000000..6cbec0dfb7 --- /dev/null +++ b/frontend/services/power_service/config/PEG2/tinkerforge_recipe.json @@ -0,0 +1,18 @@ +{ + "PEGAUX": { + "ADD": [ "PEG0_samples", "PEG1_samples" ] + }, + "12VPEG": { "ADD": [ "12VPEG_samples" ] }, + "3VPEG": { "ADD": [ "3VPEG_samples" ] }, + "12VATX": { + "ADD": [ "12VATX_samples" ], + "SUB": [ "12VPEG_samples" ] + }, + "5VATX": { "ADD": [ "5VATX_samples" ] }, + "3VATX": { + "ADD": [ "3VATX_samples" ], + "SUB": [ "3VPEG_samples" ] + }, + "P8": { "ADD": [ "P8_samples" ] }, + "P4": { "ADD": [ "P4_samples" ] } +} diff --git a/frontend/services/power_service/config/PEG3/tinkerforge.json b/frontend/services/power_service/config/PEG3/tinkerforge.json new file mode 100644 index 0000000000..108256fbed --- /dev/null +++ b/frontend/services/power_service/config/PEG3/tinkerforge.json @@ -0,0 +1,10 @@ +{ + "PEG": [ "244E", "244J", "244G" ], + "12VPEG": [ "U6Q" ], + "3VPEG": [ "UfN" ], + "12VATX": [ "U5T" ], + "5VATX": [ "UgC" ], + "3VATX": [ "244F" ], + "P8": [ "Ugx" ], + "P4": [ "Ugo" ] +} diff --git a/frontend/services/power_service/config/PEG3/tinkerforge_recipe.json b/frontend/services/power_service/config/PEG3/tinkerforge_recipe.json new file mode 100644 index 0000000000..c6cc825cd8 --- /dev/null +++ b/frontend/services/power_service/config/PEG3/tinkerforge_recipe.json @@ -0,0 +1,18 @@ +{ + "PEGAUX": { + "ADD": [ "PEG0_samples", "PEG1_samples", "PEG2_samples" ] + }, + "12VPEG": { "ADD": [ "12VPEG_samples" ] }, + "3VPEG": { "ADD": [ "3VPEG_samples" ] }, + "12VATX": { + "ADD": [ "12VATX_samples" ], + "SUB": [ "12VPEG_samples" ] + }, + "5VATX": { "ADD": [ "5VATX_samples" ] }, + "3VATX": { + "ADD": [ "3VATX_samples" ], + "SUB": [ "3VPEG_samples" ] + }, + "P8": { "ADD": [ "P8_samples" ] }, + "P4": { "ADD": [ "P4_samples" ] } +} diff --git a/frontend/services/power_service/config/rta01.rtxcfg b/frontend/services/power_service/config/rta01.rtxcfg new file mode 100644 index 0000000000..4d9b0bf407 --- /dev/null +++ b/frontend/services/power_service/config/rta01.rtxcfg @@ -0,0 +1,12 @@ +acq = oscilloscope_single_acquisition:new():points(points):count(count):segmented(true) +trigger = oscilloscope_trigger:new("EXT", "EDGE") +trigger:level(5, oscilloscope_quantity:new(2500.0, "mV")):slope(oscilloscope_trigger_slope.rising):mode(oscilloscope_trigger_mode.normal) +quant = oscilloscope_quantity:new(range, "ms") +rta01 = rtx_instrument_configuration:new(quant, acq, trigger, timeout); +chan_1 = oscilloscope_channel:new(1) +chan_1:state(true):attenuation(oscilloscope_quantity:new(1, "A")):range(oscilloscope_quantity:new(60, "A")):offset(oscilloscope_quantity:new(23, "A")):zero_adjust(0.9):label(oscilloscope_label:new("A_PEGAUX", true)) +chan_2 = oscilloscope_channel:new(2) +chan_2:state(true):range(oscilloscope_quantity:new(1, "V")):offset(oscilloscope_quantity:new(12, "V")):label(oscilloscope_label:new("V_PEGAUX", true)) +rta01:channel(chan_1) +rta01:channel(chan_2) +rta01 = as_slave(rta01, oscilloscope_quantity:new(2500.0, "mV")) diff --git a/frontend/services/power_service/config/rtb01.rtxcfg b/frontend/services/power_service/config/rtb01.rtxcfg new file mode 100644 index 0000000000..d85004a86f --- /dev/null +++ b/frontend/services/power_service/config/rtb01.rtxcfg @@ -0,0 +1,14 @@ +acq = oscilloscope_single_acquisition:new():points(points):count(count):segmented(true) +trigger = oscilloscope_trigger:new("EXT", "EDGE") +trigger:level(5, oscilloscope_quantity:new(2500.0, "mV")):slope(oscilloscope_trigger_slope.rising):mode(oscilloscope_trigger_mode.normal) +quant = oscilloscope_quantity:new(range, "ms") +rtb01 = rtx_instrument_configuration:new(quant, acq, trigger, timeout); +chan_1 = oscilloscope_channel:new(1) +chan_1:state(true):range(oscilloscope_quantity:new(5, "V")):attenuation(oscilloscope_quantity:new(1, "V")):offset(oscilloscope_quantity:new(2, "V")):label(oscilloscope_label:new("frame", true)) +chan_3 = oscilloscope_channel:new(3) +chan_3:state(true):attenuation(oscilloscope_quantity:new(100, "A")):range(oscilloscope_quantity:new(8, "A")):offset(oscilloscope_quantity:new(3.2, "A")):label(oscilloscope_label:new("A_PEG12", true)) +chan_4 = oscilloscope_channel:new(4) +chan_4:state(true):range(oscilloscope_quantity:new(2, "V")):attenuation(oscilloscope_quantity:new(1, "V")):offset(oscilloscope_quantity:new(5, "V")):label(oscilloscope_label:new("V_ATX5", true)) +rtb01:channel(chan_1) +rtb01:channel(chan_3) +rtb01:channel(chan_4) diff --git a/frontend/services/power_service/config/rtb02.rtxcfg b/frontend/services/power_service/config/rtb02.rtxcfg new file mode 100644 index 0000000000..5379e6db9f --- /dev/null +++ b/frontend/services/power_service/config/rtb02.rtxcfg @@ -0,0 +1,18 @@ +acq = oscilloscope_single_acquisition:new():points(points):count(count):segmented(true) +trigger = oscilloscope_trigger:new("EXT", "EDGE") +trigger:level(5, oscilloscope_quantity:new(2500.0, "mV")):slope(oscilloscope_trigger_slope.rising):mode(oscilloscope_trigger_mode.normal) +quant = oscilloscope_quantity:new(range, "ms") +rtb02 = rtx_instrument_configuration:new(quant, acq, trigger, timeout); +chan_1 = oscilloscope_channel:new(1) +chan_1:state(true):attenuation(oscilloscope_quantity:new(10, "V")):range(oscilloscope_quantity:new(2, "V")):offset(oscilloscope_quantity:new(12, "V")):label(oscilloscope_label:new("V_P4", true)) +chan_2 = oscilloscope_channel:new(2) +chan_2:state(true):attenuation(oscilloscope_quantity:new(10, "V")):range(oscilloscope_quantity:new(2, "V")):offset(oscilloscope_quantity:new(12, "V")):label(oscilloscope_label:new("V_PEG12", true)) +chan_3 = oscilloscope_channel:new(3) +chan_3:state(true):attenuation(oscilloscope_quantity:new(10, "V")):range(oscilloscope_quantity:new(2, "V")):offset(oscilloscope_quantity:new(12, "V")):label(oscilloscope_label:new("V_P8", true)) +chan_4 = oscilloscope_channel:new(4) +chan_4:state(true):range(oscilloscope_quantity:new(2, "V")):attenuation(oscilloscope_quantity:new(1, "V")):offset(oscilloscope_quantity:new(12, "V")):label(oscilloscope_label:new("V_ATX12", true)) +rtb02:channel(chan_1) +rtb02:channel(chan_2) +rtb02:channel(chan_3) +rtb02:channel(chan_4) +rtb02 = as_slave(rtb02, oscilloscope_quantity:new(2500.0, "mV")) diff --git a/frontend/services/power_service/config/rtb03.rtxcfg b/frontend/services/power_service/config/rtb03.rtxcfg new file mode 100644 index 0000000000..de5d4ed712 --- /dev/null +++ b/frontend/services/power_service/config/rtb03.rtxcfg @@ -0,0 +1,18 @@ +acq = oscilloscope_single_acquisition:new():points(points):count(count):segmented(true) +trigger = oscilloscope_trigger:new("EXT", "EDGE") +trigger:level(5, oscilloscope_quantity:new(2500.0, "mV")):slope(oscilloscope_trigger_slope.rising):mode(oscilloscope_trigger_mode.normal) +quant = oscilloscope_quantity:new(range, "ms") +rtb03 = rtx_instrument_configuration:new(quant, acq, trigger, timeout); +chan_1 = oscilloscope_channel:new(1) +chan_1:state(true):attenuation(oscilloscope_quantity:new(100, "A")):range(oscilloscope_quantity:new(10, "A")):offset(oscilloscope_quantity:new(4, "A")):label(oscilloscope_label:new("A_P4", true)) +chan_2 = oscilloscope_channel:new(2) +chan_2:state(true):attenuation(oscilloscope_quantity:new(100, "A")):range(oscilloscope_quantity:new(10, "A")):offset(oscilloscope_quantity:new(4, "A")):label(oscilloscope_label:new("A_P8", true)) +chan_3 = oscilloscope_channel:new(3) +chan_3:state(true):attenuation(oscilloscope_quantity:new(100, "A")):range(oscilloscope_quantity:new(10, "A")):offset(oscilloscope_quantity:new(4, "A")):label(oscilloscope_label:new("A_ATX12", true)) +chan_4 = oscilloscope_channel:new(4) +chan_4:state(true):attenuation(oscilloscope_quantity:new(100, "A")):range(oscilloscope_quantity:new(10, "A")):offset(oscilloscope_quantity:new(4, "A")):label(oscilloscope_label:new("A_ATX5", true)) +rtb03:channel(chan_1) +rtb03:channel(chan_2) +rtb03:channel(chan_3) +rtb03:channel(chan_4) +rtb03 = as_slave(rtb03, oscilloscope_quantity:new(2500.0, "mV")) diff --git a/frontend/services/power_service/config/rtx_recipe.json b/frontend/services/power_service/config/rtx_recipe.json new file mode 100644 index 0000000000..9abfd870af --- /dev/null +++ b/frontend/services/power_service/config/rtx_recipe.json @@ -0,0 +1,42 @@ +{ + "PEGAUX": { + "ADD": [ "A_PEGAUX" ], + "SUB": [], + "MUL": [ "V_PEGAUX" ] + }, + "12VPEG": { + "ADD": [ "A_PEG12" ], + "SUB": [], + "MUL": [ "V_PEG12" ] + }, + "3VPEG": { + "ADD": [], + "SUB": [], + "MUL": [] + }, + "12VATX": { + "ADD": [ "A_ATX12" ], + "SUB": [ "A_PEG12" ], + "MUL": [ "V_ATX12" ] + }, + "5VATX": { + "ADD": [ "A_ATX5" ], + "SUB": [], + "MUL": [ "V_ATX5" ] + }, + "3VATX": { + "ADD": [], + "SUB": [], + "MUL": [] + }, + "P8": { + "ADD": [ "A_P8" ], + "SUB": [], + "MUL": [ "V_P8" ] + }, + "P4": { + "ADD": [ "A_P4" ], + "SUB": [], + "MUL": [ "V_P4" ] + } +} diff --git a/frontend/services/power_service/power/ColumnNames.h b/frontend/services/power_service/power/ColumnNames.h new file mode 100644 index 0000000000..3a9550b59e --- /dev/null +++ b/frontend/services/power_service/power/ColumnNames.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +namespace megamol::power { +/// +/// Name for column holding timestamps. +/// +constexpr char const* global_ts_name = "timestamps"; +/// +/// Name for column holding sample values. +/// +constexpr char const* global_samples_name = "samples"; +} // namespace megamol::power diff --git a/frontend/services/power_service/power/CryptToken.cpp b/frontend/services/power_service/power/CryptToken.cpp new file mode 100644 index 0000000000..8a35a6f928 --- /dev/null +++ b/frontend/services/power_service/power/CryptToken.cpp @@ -0,0 +1,70 @@ +#include "CryptToken.h" + +#include +#include +#include +#include +#include + +#if WIN32 +#include +#include +#include +#pragma comment(lib, "Credui.lib") +#pragma comment(lib, "Crypt32.lib") +#endif + +namespace megamol::power { +#if WIN32 +CryptToken::CryptToken(std::string const& filename) : token_(CREDUI_MAX_PASSWORD_LENGTH + 1), token_size_(0) { + //std::filesystem::path filepath(filename); + if (std::filesystem::exists(filename) && std::filesystem::is_regular_file(filename)) { + token_size_ = std::filesystem::file_size(filename); + std::string file_data; + file_data.resize(token_size_); + auto in_file = std::ifstream(filename, std::ios::binary); + in_file.read(file_data.data(), token_size_); + in_file.close(); + DATA_BLOB blob_in; + blob_in.cbData = token_size_; + blob_in.pbData = (BYTE*) (file_data.data()); + DATA_BLOB blob_out; + if (!CryptUnprotectData(&blob_in, nullptr, nullptr, nullptr, nullptr, 0, &blob_out)) { + throw std::runtime_error("[CryptToken] Cannot decrypt data"); + } + token_size_ = blob_out.cbData; + std::copy(blob_out.pbData, blob_out.pbData + blob_out.cbData, token_.GetPtr()); + SecureZeroMemory(blob_out.pbData, blob_out.cbData); + LocalFree(blob_out.pbData); + } else { + BOOL tkSave; + auto res = CredUIPromptForCredentials(nullptr, "DarUSDataverse", nullptr, 0, (PSTR) "", 0, token_.GetPtr(), + CREDUI_MAX_PASSWORD_LENGTH + 1, &tkSave, + CREDUI_FLAGS_DO_NOT_PERSIST | CREDUI_FLAGS_EXCLUDE_CERTIFICATES | CREDUI_FLAGS_GENERIC_CREDENTIALS | + CREDUI_FLAGS_PASSWORD_ONLY_OK | CREDUI_FLAGS_KEEP_USERNAME); + + DATA_BLOB blob_in; + blob_in.cbData = std::strlen(token_.GetPtr()); + blob_in.pbData = (BYTE*) token_.GetPtr(); + DATA_BLOB blob_out; + if (!CryptProtectData(&blob_in, nullptr, nullptr, nullptr, nullptr, 0, &blob_out)) { + throw std::runtime_error("[CryptToken] Cannot encrypt data"); + } + + std::ofstream out_file(filename, std::ios::binary); + out_file.write((char const*) blob_out.pbData, blob_out.cbData); + out_file.close(); + + SecureZeroMemory(blob_out.pbData, blob_out.cbData); + LocalFree(blob_out.pbData); + } +} +#else +CryptToken::CryptToken(std::string const& filename) : token_(0), token_size_(0) {} +#endif + + +char const* CryptToken::GetToken() const { + return token_.GetPtr(); +} +} // namespace megamol::power diff --git a/frontend/services/power_service/power/CryptToken.h b/frontend/services/power_service/power/CryptToken.h new file mode 100644 index 0000000000..9207660e04 --- /dev/null +++ b/frontend/services/power_service/power/CryptToken.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include "ProtectedMemory.h" + +namespace megamol::power { + +/** + * @brief Class for storing the Dataverse API token securely. + */ +class CryptToken { +public: + /** + * @brief Ctor. Stores token in @c ProtectedMemory. + * If file at @c filename exists, token will be read from there. + * If the file does not exists, a prompt will ask for the token + * and writes the file with the token encrypted with user credentials. + * @param filename The filepath to the token file. + */ + CryptToken(std::string const& filename); + + /** + * @brief Dtor. + */ + ~CryptToken() = default; + + /** + * @brief Access the token. + * @return The token. + */ + char const* GetToken() const; + +private: + power::ProtectedMemory token_; + + std::size_t token_size_; +}; +} // namespace megamol::power diff --git a/frontend/services/power_service/power/DataverseWriter.cpp b/frontend/services/power_service/power/DataverseWriter.cpp new file mode 100644 index 0000000000..ad5a1432bc --- /dev/null +++ b/frontend/services/power_service/power/DataverseWriter.cpp @@ -0,0 +1,42 @@ +#include "DataverseWriter.h" + +#ifdef MEGAMOL_USE_POWER + +#include +#include + +#include + +namespace megamol::power { +void DataverseWriter(std::string const& dataverse_path, std::string const& doi, std::string const& filepath, + char const* key, char& signal) { +#if WIN32 + using namespace visus::dataverse; + signal = true; + try { + dataverse_connection dataverse; + dataverse.base_path(make_const_narrow_string(dataverse_path, CP_OEMCP)) + .api_key(make_const_narrow_string(key, CP_OEMCP)) + .upload( + make_const_narrow_string(doi, CP_OEMCP), make_const_narrow_string(filepath, CP_OEMCP), + /*make_const_narrow_string("Test Data", CP_OEMCP), make_const_narrow_string("", CP_OEMCP), nullptr, 0, + false,*/ + [](const blob& result, void* context) { + std::string output(result.as(), result.size()); + std::cout << convert(convert(output, CP_UTF8), CP_OEMCP) << std::endl; + std::cout << std::endl; + *static_cast(context) = false; + }, + [](const int error, const char* msg, const char* cat, narrow_string::code_page_type cp, void* context) { + std::cerr << msg << std::endl << std::endl; + *static_cast(context) = false; + }, + &signal); + } catch (...) { + signal = false; + } +#endif +} +} // namespace megamol::power + +#endif diff --git a/frontend/services/power_service/power/DataverseWriter.h b/frontend/services/power_service/power/DataverseWriter.h new file mode 100644 index 0000000000..989db687dd --- /dev/null +++ b/frontend/services/power_service/power/DataverseWriter.h @@ -0,0 +1,20 @@ +#pragma once + +#ifdef MEGAMOL_USE_POWER + +#include + +namespace megamol::power { +/** + * @brief Writes the specified file into the Dataverse. + * @param dataverse_path API path to the Dataverse. + * @param doi DOI of the dataset to write into in the Dataverse. + * @param filepath The path to the file to be written into the Dataverse. + * @param key API token of the Dataverse. + * @param signal True while the file is being written. + */ +void DataverseWriter(std::string const& dataverse_path, std::string const& doi, std::string const& filepath, + char const* key, char& signal); +} // namespace megamol::power + +#endif diff --git a/frontend/services/power_service/power/MetaData.h b/frontend/services/power_service/power/MetaData.h new file mode 100644 index 0000000000..143c48b6bb --- /dev/null +++ b/frontend/services/power_service/power/MetaData.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include + +#include "Timestamp.h" + +namespace megamol::power { +/** + * @brief Struct to hold the meta data of the measurements. + */ +struct MetaData { + /** + * @brief MegaMol project file while running the measurement. + */ + std::string project_file; + /** + * @brief Power-overwhelming configurations. + */ + std::unordered_map oszi_configs; + /** + * @brief Loaded libraries. + */ + std::string runtime_libs; + /** + * @brief Hardware and software information. + */ + std::unordered_map hardware_software_info; + /** + * @brief The mapping of the different sensors to power rails for later analysis. + */ + std::unordered_map analysis_recipes; + /** + * @brief The timestamps of the trigger signals. + */ + std::vector trigger_ts; +}; +} // namespace megamol::power diff --git a/frontend/services/power_service/power/ParallelPortTrigger.cpp b/frontend/services/power_service/power/ParallelPortTrigger.cpp new file mode 100644 index 0000000000..08edaacc84 --- /dev/null +++ b/frontend/services/power_service/power/ParallelPortTrigger.cpp @@ -0,0 +1,90 @@ +#include "ParallelPortTrigger.h" + +#ifdef MEGAMOL_USE_POWER + +#include +#include +#include + +#include "mmcore/utility/log/Log.h" + +namespace megamol::power { + +ParallelPortTrigger::ParallelPortTrigger(char const* path) { + this->Open(path); +} + + +void ParallelPortTrigger::Open(char const* path) { +#ifdef WIN32 + this->handle_.reset(::CreateFileA( + path, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, NULL)); + if (!this->handle_) { + //throw std::system_error(::GetLastError(), std::system_category()); + core::utility::log::Log::DefaultLog.WriteWarn("[ParallelPortTriger] Could not open parallel port %s", path); + } +#endif +} + + +//DWORD ParallelPortTrigger::Write(const void* data, const DWORD cnt) const { +// DWORD retval = 0; +// +//#ifdef WIN32 +// if (!::WriteFile(this->handle_.get(), data, cnt, &retval, nullptr)) { +// throw std::system_error(::GetLastError(), std::system_category()); +// } +//#endif +// +// return retval; +//} + + +uint32_t ParallelPortTrigger::Write(std::uint8_t data) { +#ifdef WIN32 + DWORD retval = 0; + if (handle_) { + if (!::WriteFile(this->handle_.get(), &data, 1, &retval, nullptr)) { + throw std::system_error(::GetLastError(), std::system_category()); + } + } + return static_cast(retval); +#else + return 0; +#endif +} + + +uint32_t ParallelPortTrigger::WriteHigh() { + static const auto data = (std::numeric_limits::max)(); + //return this->Write(&data, sizeof(data)); + return Write(data); +} + + +uint32_t ParallelPortTrigger::WriteLow() { + static const char data = 0; + //return this->Write(&data, sizeof(data)); + return Write(data); +} + + +std::uint8_t set_bit(std::uint8_t data, unsigned char idx, bool state) { + std::uint8_t val = state ? 1 : 0; + return (data & ~(1UL << idx)) | (val << idx); +} + + +uint32_t ParallelPortTrigger::SetBit(unsigned char idx, bool state) { + if (idx < 8) { + auto data = data_state_.load(); + while (data_state_.compare_exchange_weak(data, set_bit(data, idx, state))) {} + return Write(data); + } + + return 0; +} + +} // namespace megamol::power + +#endif diff --git a/frontend/services/power_service/power/ParallelPortTrigger.h b/frontend/services/power_service/power/ParallelPortTrigger.h new file mode 100644 index 0000000000..549596e9ec --- /dev/null +++ b/frontend/services/power_service/power/ParallelPortTrigger.h @@ -0,0 +1,83 @@ +#pragma once + +#ifdef MEGAMOL_USE_POWER + +#include +#include + +#ifdef WIN32 +#include +#endif + +namespace megamol::power { + +/** + * @brief Class representing a trigger device over the parallel port. + * Produces signals for external hardware sensors. + */ +class ParallelPortTrigger final { +public: + /** + * @brief Ctor. + */ + ParallelPortTrigger() = default; + + /** + * @brief Ctor. Opens the specified parallel port. + * If parallel port cannot be opened, @c handle_ remains nullptr. + * @param path Path of the parallel port. + */ + explicit ParallelPortTrigger(char const* path); + + /** + * @brief Opens the specified parallel port. + * If parallel port cannot be opened, @c handle_ remains nullptr. + * Resets existing handle. + * @param path Path of the parallel port. + */ + void Open(char const* path); + + //DWORD Write(const void* data, const DWORD cnt) const; + + /** + * @brief Writes data on opened parallel port. + * @param data The data to write. + * @return API-specific return code value. + * @throws std::system_error If write fails. + */ + uint32_t Write(std::uint8_t data); + + /** + * @brief Write high on all output bits. + * @return API-specific return code value. + * @throws std::system_error If write fails. + */ + uint32_t WriteHigh(); + + /** + * @brief Write low on all output bits. + * @return API-specific return code value. + * @throws std::system_error If write fails. + */ + uint32_t WriteLow(); + + /** + * @brief Sets specified bit to the value in @c set. + * @param idx The index of the bit to write. + * @param state The value to write on the bit. + * @return API-specific return code value. + * @throws std::system_error If write fails. + */ + uint32_t SetBit(unsigned char idx, bool state); + +private: +#ifdef WIN32 + wil::unique_hfile handle_; +#endif + + std::atomic data_state_; +}; + +} // namespace megamol::power + +#endif diff --git a/frontend/services/power_service/power/ParquetWriter.cpp b/frontend/services/power_service/power/ParquetWriter.cpp new file mode 100644 index 0000000000..3b0295168a --- /dev/null +++ b/frontend/services/power_service/power/ParquetWriter.cpp @@ -0,0 +1,210 @@ +#include "ParquetWriter.h" + +#ifdef MEGAMOL_USE_POWER + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mmcore/utility/log/Log.h" + +#include +#include +#include +#include + +#include "ColumnNames.h" +#include "MetaData.h" +#include "Utility.h" + +namespace megamol::power { +template +void BatchWriter(parquet::RowGroupWriter* writer, std::vector const& data, int c_idx, std::size_t min_field_size) { + if constexpr (std::is_same_v) { + auto float_writer = static_cast(writer->column(c_idx)); + float_writer->WriteBatch(min_field_size, nullptr, nullptr, data.data()); + } else if (std::is_same_v) { + auto int_writer = static_cast(writer->column(c_idx)); + int_writer->WriteBatch(min_field_size, nullptr, nullptr, data.data()); + } else { + throw std::invalid_argument("Unsupported type"); + } +} + +void WriteMetaData(std::unique_ptr& file_writer, MetaData const* meta) { + auto md = std::make_shared(); + //md->Append(std::string("project_file"), meta->project_file); + for (auto const& [key, value] : meta->oszi_configs) { + md->Append(key, value); + } + //md->Append(std::string("runtime_libraries"), meta->runtime_libs); + for (auto const& [key, value] : meta->hardware_software_info) { + md->Append(key, value); + } + for (auto const& [key, value] : meta->analysis_recipes) { + md->Append(key, value); + } + std::stringstream ss; + for (auto const& el : meta->trigger_ts) { + ss << el.count() << ";"; + } + auto str = ss.str(); + if (!str.empty()) + str.pop_back(); + md->Append(std::string("trigger_ts"), str); + file_writer->AddKeyValueMetadata(md); +} + +void ParquetWriter(std::filesystem::path const& file_path, value_map_t const& values_map, MetaData const* meta) { + using namespace parquet; + using namespace parquet::schema; + + try { + std::size_t min_field_size = 0; + bool first_time = true; + + // create scheme + NodeVector fields; + fields.reserve(values_map.size()); + for (auto const& [name, v_values] : values_map) { + if (std::holds_alternative>(v_values)) { + fields.push_back(PrimitiveNode::Make(name, Repetition::REQUIRED, Type::FLOAT, ConvertedType::NONE)); + if (first_time) { + min_field_size = std::get>(v_values).size(); + first_time = false; + } else { + min_field_size = std::min(min_field_size, std::get>(v_values).size()); + } + } else if (std::holds_alternative>(v_values)) { + fields.push_back(PrimitiveNode::Make(name, Repetition::REQUIRED, Type::INT64, ConvertedType::NONE)); + if (first_time) { + min_field_size = std::get>(v_values).size(); + first_time = false; + } else { + min_field_size = std::min(min_field_size, std::get>(v_values).size()); + } + } else { + throw std::runtime_error("Unexpected type"); + } + } + auto schema = std::static_pointer_cast(GroupNode::Make("schema", Repetition::REQUIRED, fields)); + + // open file + std::shared_ptr<::arrow::io::FileOutputStream> file; + PARQUET_ASSIGN_OR_THROW(file, ::arrow::io::FileOutputStream::Open(file_path.string())); + + // configure + WriterProperties::Builder builder; + builder.compression(Compression::BROTLI); + auto props = builder.build(); + + // create instance + auto file_writer = ParquetFileWriter::Open(file, schema, props); + + // write meta data + if (meta) { + WriteMetaData(file_writer, meta); + } + + // write data + auto rg_writer = file_writer->AppendBufferedRowGroup(); + + int c_idx = 0; + for (auto const& [name, v_values] : values_map) { + if (std::holds_alternative>(v_values)) { + auto const& values = std::get>(v_values); + BatchWriter(rg_writer, values, c_idx++, min_field_size); + } else if (std::holds_alternative>(v_values)) { + auto const& values = std::get>(v_values); + BatchWriter(rg_writer, values, c_idx++, min_field_size); + } else { + throw std::runtime_error("Unexpected type"); + } + } + + // close + rg_writer->Close(); + file_writer->Close(); + +#ifdef _DEBUG + std::unique_ptr reader = parquet::ParquetFileReader::OpenFile(file_path.string()); + PrintSchema(reader->metadata()->schema()->schema_root().get(), std::cout); +#endif + } catch (std::exception const& ex) { + core::utility::log::Log::DefaultLog.WriteError("[ParquetWriter]: %s", ex.what()); + } +} + +void ParquetWriter( + std::filesystem::path const& file_path, std::vector const& buffers, MetaData const* meta) { + using namespace parquet; + using namespace parquet::schema; + + if (buffers.empty()) + return; + + try { + std::size_t min_field_size = buffers[0].ReadSamples().size(); + + // create scheme + NodeVector fields; + fields.reserve(buffers.size() * 2); + for (auto const& b : buffers) { + fields.push_back(PrimitiveNode::Make( + b.Name() + "_" + global_samples_name, Repetition::REQUIRED, Type::FLOAT, ConvertedType::NONE)); + fields.push_back(PrimitiveNode::Make( + b.Name() + "_" + global_ts_name, Repetition::REQUIRED, Type::INT64, ConvertedType::NONE)); + min_field_size = std::min(min_field_size, b.ReadSamples().size()); + } + auto schema = std::static_pointer_cast(GroupNode::Make("schema", Repetition::REQUIRED, fields)); + + // open file + std::shared_ptr<::arrow::io::FileOutputStream> file; + PARQUET_ASSIGN_OR_THROW(file, ::arrow::io::FileOutputStream::Open(file_path.string())); + + // configure + WriterProperties::Builder builder; + builder.compression(Compression::BROTLI); + auto props = builder.build(); + + // create instance + auto file_writer = ParquetFileWriter::Open(file, schema, props); + + // write meta data + if (meta) { + WriteMetaData(file_writer, meta); + } + + // write data + auto rg_writer = file_writer->AppendBufferedRowGroup(); + + int c_idx = 0; + for (auto const& b : buffers) { + auto const& s_values = b.ReadSamples(); + BatchWriter(rg_writer, s_values, c_idx++, min_field_size); + auto const& t_values = b.ReadTimestamps(); + BatchWriter(rg_writer, t_values, c_idx++, min_field_size); + } + + // close + rg_writer->Close(); + file_writer->Close(); + +#ifdef _DEBUG + std::unique_ptr reader = parquet::ParquetFileReader::OpenFile(file_path.string()); + PrintSchema(reader->metadata()->schema()->schema_root().get(), std::cout); +#endif + } catch (std::exception const& ex) { + core::utility::log::Log::DefaultLog.WriteError("[ParquetWriter]: %s", ex.what()); + } +} +} // namespace megamol::power + +#endif diff --git a/frontend/services/power_service/power/ParquetWriter.h b/frontend/services/power_service/power/ParquetWriter.h new file mode 100644 index 0000000000..848200559d --- /dev/null +++ b/frontend/services/power_service/power/ParquetWriter.h @@ -0,0 +1,32 @@ +#pragma once + +#ifdef MEGAMOL_USE_POWER + +#include +#include + +#include "MetaData.h" +#include "SampleBuffer.h" +#include "Utility.h" + +namespace megamol::power { +/** + * @brief Writer for @c value_map_t container to parquet file. + * @param file_path The filepath. + * @param values_map The data to write. + * @param meta The MetaData object to embed in the output file. + */ +void ParquetWriter( + std::filesystem::path const& file_path, value_map_t const& values_map, MetaData const* meta = nullptr); + +/** + * @brief Writer a set of @c SampleBuffer to parquet file. + * @param file_path The filepath. + * @param buffers The data to write. + * @param meta The MetaData object to embed in the output file. + */ +void ParquetWriter( + std::filesystem::path const& file_path, std::vector const& buffers, MetaData const* meta = nullptr); +} // namespace megamol::power + +#endif diff --git a/frontend/services/power_service/power/ProtectedMemory.cpp b/frontend/services/power_service/power/ProtectedMemory.cpp new file mode 100644 index 0000000000..baad937f4c --- /dev/null +++ b/frontend/services/power_service/power/ProtectedMemory.cpp @@ -0,0 +1,53 @@ +#include "ProtectedMemory.h" + +#include +#include + +#if WIN32 +#include +#include +#pragma comment(lib, "Crypt32.lib") +#endif + +namespace megamol::power { +ProtectedMemory::ProtectedMemory(std::size_t const req_size) { +#if WIN32 + data_size_ = req_size; + auto cbMod = req_size % CRYPTPROTECTMEMORY_BLOCK_SIZE; + if (cbMod) { + data_size_ += CRYPTPROTECTMEMORY_BLOCK_SIZE - cbMod; + } + data_ = (char*) LocalAlloc(LPTR, data_size_); + if (!data_) { + data_size_ = 0; + throw std::runtime_error("[ProtectedMemory] Cannot allocate data"); + } + if (!CryptProtectMemory(data_, data_size_, CRYPTPROTECTMEMORY_SAME_PROCESS)) { + LocalFree(data_); + data_size_ = 0; + throw std::runtime_error("[ProtectedMemory] Cannot encrypt data"); + } + SecureZeroMemory(data_, data_size_); +#endif +} + +ProtectedMemory::~ProtectedMemory() { +#if WIN32 + if (data_) { + SecureZeroMemory(data_, data_size_); + LocalFree(data_); + } + data_size_ = 0; +#endif +} + +ProtectedMemory::ProtectedMemory(ProtectedMemory&& rhs) noexcept + : data_(std::exchange(rhs.data_, nullptr)) + , data_size_(std::exchange(rhs.data_size_, 0)) {} + +ProtectedMemory& ProtectedMemory::operator=(ProtectedMemory&& rhs) noexcept { + data_ = std::exchange(rhs.data_, nullptr); + data_size_ = std::exchange(rhs.data_size_, 0); + return *this; +} +} // namespace megamol::power diff --git a/frontend/services/power_service/power/ProtectedMemory.h b/frontend/services/power_service/power/ProtectedMemory.h new file mode 100644 index 0000000000..65d587e076 --- /dev/null +++ b/frontend/services/power_service/power/ProtectedMemory.h @@ -0,0 +1,58 @@ +#pragma once + +#include + +namespace megamol::power { +/** + * @brief Class representing an allocation of protected memory. + */ +class ProtectedMemory { +public: + /** + * @brief Allocate @c req_size (+ padding) bytes of protected memory. + * @param req_size Bytes to allocate. + * @throws std::runtime_error If allocation or encryption fails. + */ + ProtectedMemory(std::size_t const req_size); + + /** + * @brief Dtor. + */ + ~ProtectedMemory(); + + ProtectedMemory(ProtectedMemory const&) = delete; + + ProtectedMemory& operator=(ProtectedMemory const&) = delete; + + /** + * @brief Move Ctor. + */ + ProtectedMemory(ProtectedMemory&& rhs) noexcept; + + /** + * @brief Move assignment. + */ + ProtectedMemory& operator=(ProtectedMemory&& rhs) noexcept; + + /** + * @brief Get pointer to begin of allocation. + * @return The pointer. + */ + char* GetPtr() { + return data_; + } + + /** + * @brief Get const pointer to begin of allocation. + * @return The pointer. + */ + char const* GetPtr() const { + return data_; + } + +private: + char* data_ = nullptr; + + std::size_t data_size_ = 0; +}; +} // namespace megamol::power diff --git a/frontend/services/power_service/power/RTXInstruments.cpp b/frontend/services/power_service/power/RTXInstruments.cpp new file mode 100644 index 0000000000..55a2af4def --- /dev/null +++ b/frontend/services/power_service/power/RTXInstruments.cpp @@ -0,0 +1,248 @@ +#include "RTXInstruments.h" + +#ifdef MEGAMOL_USE_POWER + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ColumnNames.h" +#include "Trigger.h" +#include "sol_rtx_instrument.h" + +using namespace visus::power_overwhelming; + +namespace megamol::power { + +RTXInstruments::RTXInstruments(std::shared_ptr trigger) : trigger_(trigger) { + auto num_devices = rtx_instrument::all(nullptr, 0); + if (num_devices == 0) + throw std::runtime_error("No RTX devices attached"); + std::vector rtx_instr(num_devices); + rtx_instrument::all(rtx_instr.data(), rtx_instr.size()); + + rtx_instr_.reserve(rtx_instr.size()); + + std::for_each(rtx_instr.begin(), rtx_instr.end(), [&](rtx_instrument& i) { + auto const name = get_pwrowg_str( + i, &visus::power_overwhelming::rtx_instrument::name); + core::utility::log::Log::DefaultLog.WriteInfo("[RTXInstruments]: Found %s as %s", name, + get_pwrowg_str( + i, &visus::power_overwhelming::rtx_instrument::identify)); + rtx_instr_[name] = std::move(i); + }); + + sol_state_.open_libraries(sol::lib::base); + + sol_register_all(sol_state_); +} + +void RTXInstruments::UpdateConfigs(std::filesystem::path const& config_folder, int points, int count, + std::chrono::milliseconds range, std::chrono::milliseconds timeout) { + if (std::filesystem::is_directory(config_folder)) { + std::for_each(rtx_instr_.begin(), rtx_instr_.end(), [&](auto const& instr) { + auto const& name = instr.first; + auto const fullpath = config_folder / (name + ".rtxcfg"); + if (std::filesystem::exists(fullpath)) { + sol_state_["points"] = points; + sol_state_["count"] = count; + sol_state_["range"] = range.count(); + sol_state_["timeout"] = timeout.count(); + sol_state_.script_file(fullpath.string()); + rtx_config_[name] = sol_state_[name]; + } + }); + config_range_ = range; + } +} + +void RTXInstruments::ApplyConfigs(MetaData* meta) { + try { + if (meta) { + meta->oszi_configs.clear(); + } + std::for_each(rtx_instr_.begin(), rtx_instr_.end(), [&](auto& instr) { + auto const& name = instr.first; + auto& i = instr.second; + auto fit = rtx_config_.find(name); + if (fit == rtx_config_.end()) { + core::utility::log::Log::DefaultLog.WriteWarn("[RTXInstruments]: No config found for device %s", name); + } else { + i.synchronise_clock(true); + i.reset(rtx_instrument_reset::buffers | rtx_instrument_reset::status | rtx_instrument_reset::stop); + // need to stop running measurement otherwise wait on trigger cannot be used to guard start of trigger sequence + fit->second.beep_on_trigger(true).beep_on_apply(true).beep_on_error(true); + if (!fit->second.slave()) { + main_instr_ = &i; + } + fit->second.apply(i); + if (meta) { + auto const cfg_str_size = rtx_instrument_configuration::serialise(nullptr, 0, fit->second); + std::string cfg_str; + cfg_str.resize(cfg_str_size); + rtx_instrument_configuration::serialise(cfg_str.data(), cfg_str.size(), fit->second); + meta->oszi_configs[fit->first] = cfg_str; + } + i.reference_position(oscilloscope_reference_point::left); + i.trigger_position(oscilloscope_quantity(0, "ms")); + i.operation_complete(); + core::utility::log::Log::DefaultLog.WriteInfo("[RTXInstruments]: Configured device %s", name); + } + }); + } catch (std::exception& ex) { + core::utility::log::Log::DefaultLog.WriteError( + "[RTXInstruments]: Failed to apply configurations.\n%s", ex.what()); + } +} + +void RTXInstruments::StartMeasurement(std::filesystem::path const& output_folder, + std::vector const& writer_funcs, power::MetaData const* meta, char& signal) { + if (!std::filesystem::is_directory(output_folder)) { + core::utility::log::Log::DefaultLog.WriteError( + "[RTXInstruments]: Path {} is not a directory", output_folder.string()); + return; + } + + auto t_func = [&](std::filesystem::path const& output_folder, std::vector const& writer_funcs, + power::MetaData const* meta, char& signal) { + try { + signal = true; + + waiting_on_trigger(); + + int global_device_counter = 0; + for (auto& [name, i] : rtx_instr_) { + i.on_operation_complete_ex( + [&global_device_counter, num_dev = rtx_instr_.size(), trigger = trigger_.get()](visa_instrument&) { + ++global_device_counter; + if (global_device_counter >= num_dev) { + trigger->DisarmTrigger(); + } + }); + i.acquisition(oscilloscope_acquisition_state::single); + i.operation_complete_async(); + } + + // magic sleep to wait until devices are ready to recieve other requests + while (!waiting_on_trigger()) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + auto const [trg_prefix, trg_postfix, trg_wait] = get_trigger_timings(config_range_); + trigger_->ArmTrigger(); + auto tp_fut = std::async(std::launch::async, + std::bind(&Trigger::StartTriggerSequence, trigger_.get(), trg_prefix, trg_postfix, trg_wait)); + tp_fut.wait(); + auto const last_trigger_ft = tp_fut.get(); + + core::utility::log::Log::DefaultLog.WriteInfo("[RTXInstruments]: Start fetching data"); + auto const start_fetch = std::chrono::steady_clock::now(); + std::vector> all_wf_fut; + all_wf_fut.reserve(rtx_instr_.size()); + + for (auto& [name, i] : rtx_instr_) { + all_wf_fut.push_back(std::async(std::launch::async, + std::bind(static_cast(&rtx_instrument::data), + std::addressof(i), oscilloscope_waveform_points::maximum, 1000))); + } + + for (auto& f : all_wf_fut) { + f.wait(); + } + auto const stop_fetch = std::chrono::steady_clock::now(); + core::utility::log::Log::DefaultLog.WriteInfo("[RTXInstruments]: Stop fetching data %dms", + std::chrono::duration_cast(stop_fetch - start_fetch).count()); + + std::size_t f_cnt = 0; + std::for_each(rtx_instr_.begin(), rtx_instr_.end(), [&](auto& instr) { + auto const& name = instr.first; + auto& i = instr.second; + + auto fit = rtx_config_.find(name); + if (fit == rtx_config_.end()) { + core::utility::log::Log::DefaultLog.WriteError( + "[RTXInstruments]: Could not find config for device {}", name); + return; + } + + auto const& config = fit->second; + auto const num_channels = config.channels(); + if (num_channels == 0) { + core::utility::log::Log::DefaultLog.WriteError("[RTXInstruments]: No configured channels"); + return; + } + std::vector channels(num_channels); + config.channels(channels.data(), channels.size()); + + core::utility::log::Log::DefaultLog.WriteInfo("[RTXInstruments]: Start reading data"); + auto const start_read = std::chrono::steady_clock::now(); + auto const all_waveforms = all_wf_fut[f_cnt++].get(); + + auto const num_segments = i.history_segments(); + + power::segments_t values_map(num_segments); + + for (std::decay_t s_idx = 0, fetch_idx = num_segments - 1; s_idx < num_segments; + ++s_idx, --fetch_idx) { + auto const& waveform = all_waveforms[fetch_idx * num_channels].waveform(); + auto const sample_times = generate_timeline(waveform); + auto const segment_times = + offset_timeline(sample_times, std::chrono::duration_cast( + std::chrono::duration(waveform.segment_offset()))); + auto const timestamps = offset_timeline(segment_times, last_trigger_ft); + values_map[s_idx][global_ts_name] = timestamps; + + for (unsigned int c_idx = 0; c_idx < num_channels; ++c_idx) { + auto const tpn = channels[c_idx].label().text(); + values_map[s_idx][tpn] = + copy_waveform(all_waveforms[fetch_idx * num_channels + c_idx].waveform()); + } + } + auto const stop_read = std::chrono::steady_clock::now(); + core::utility::log::Log::DefaultLog.WriteInfo("[RTXInstruments]: Finished reading data in %dms", + std::chrono::duration_cast(stop_read - start_read).count()); + core::utility::log::Log::DefaultLog.WriteInfo("[RTXInstruments]: Start writing data"); + auto const start_write = std::chrono::steady_clock::now(); + std::for_each(writer_funcs.begin(), writer_funcs.end(), + [&output_folder, &name, &values_map, &meta]( + auto const& writer_func) { writer_func(output_folder, name, values_map, meta); }); + auto const stop_write = std::chrono::steady_clock::now(); + core::utility::log::Log::DefaultLog.WriteInfo("[RTXInstruments]: Finished writing data in %dms", + std::chrono::duration_cast(stop_write - start_write).count()); + }); + signal = false; + } catch (std::exception& ex) { + core::utility::log::Log::DefaultLog.WriteError( + "[RTXInstruments]: Failed to take measurement.\n%s", ex.what()); + } + }; + + auto t_thread = std::thread(t_func, output_folder, writer_funcs, meta, std::ref(signal)); + t_thread.detach(); +} + +bool RTXInstruments::waiting_on_trigger() const { + for (auto& [name, i] : rtx_instr_) { + auto res = i.query("STAT:OPER:COND?\n"); + *strchr(res.as(), '\n') = 0; + auto const val = std::atoi(res.as()); + if (val & 8) { + return true; + } + } + return false; +} + +} // namespace megamol::power + +#endif diff --git a/frontend/services/power_service/power/RTXInstruments.h b/frontend/services/power_service/power/RTXInstruments.h new file mode 100644 index 0000000000..c851112b4e --- /dev/null +++ b/frontend/services/power_service/power/RTXInstruments.h @@ -0,0 +1,101 @@ +#pragma once + +#ifdef MEGAMOL_USE_POWER + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "MetaData.h" +#include "Trigger.h" +#include "Utility.h" + +#include + +namespace megamol::power { + +/** + * @brief Class representing the interface to the external R&S oscilloscopes. + */ +class RTXInstruments { +public: + /** + * @brief Ctor. Searches for all attached scopes. + * @param trigger Trigger object that will trigger the scopes. + * @throws std::runtime_error No scopes are detected. + */ + RTXInstruments(std::shared_ptr trigger); + + /** + * @brief Loads the scope configs in @c config_folder. + * Expecting the scope name as file name with "rtxcfg" as extension. + * The configs will not be applied immediately. + * @param config_folder Path to the configs. + * @param points Number of samples to acquire per measurment segment. + * @param count Number of segments. + * @param range Time range of measurement in milliseconds. + * @param timeout Timeout for communication with the scopes in milliseconds. + */ + void UpdateConfigs(std::filesystem::path const& config_folder, int points, int count, + std::chrono::milliseconds range, std::chrono::milliseconds timeout); + + /** + * @brief Apply the configs previously read with UpdateConfigs. + * @param meta Pointer to MetaData object to insert the applied configs. + */ + void ApplyConfigs(MetaData* meta = nullptr); + + /** + * @brief Starts the measurement with the attached and configures scopes. + * @param output_folder Path to folder for storing the acquired samples. + * @param writer_funcs Writer functions to push the acquired samples. + * @param meta MetaData object to embed in the output files. + * @param signal Will be set to one while the measurement is running. + */ + void StartMeasurement(std::filesystem::path const& output_folder, + std::vector const& writer_funcs, power::MetaData const* meta, char& signal); + + /** + * @brief Toggles alternative software trigger according to @c set. + * @param set Flag to set the software trigger. + */ + void SetSoftwareTrigger(bool set) { + enforce_software_trigger_ = set; + trigger_->RegisterSubTrigger("RTXInstruments", std::bind(&RTXInstruments::soft_trg, this)); + } + +private: + void soft_trg() { + /*for (auto& [name, i] : rtx_instr_) { + i.trigger_manually(); + }*/ + main_instr_->trigger_manually(); + } + + bool waiting_on_trigger() const; + + std::unordered_map rtx_instr_; + + visus::power_overwhelming::rtx_instrument* main_instr_ = nullptr; + + std::unordered_map rtx_config_; + + sol::state sol_state_; + + std::chrono::milliseconds config_range_; + + bool enforce_software_trigger_ = false; + + std::shared_ptr trigger_ = nullptr; +}; + +} // namespace megamol::power + +#endif diff --git a/frontend/services/power_service/power/SampleBuffer.cpp b/frontend/services/power_service/power/SampleBuffer.cpp new file mode 100644 index 0000000000..7c18cbea84 --- /dev/null +++ b/frontend/services/power_service/power/SampleBuffer.cpp @@ -0,0 +1,39 @@ +#include "SampleBuffer.h" + +#ifdef MEGAMOL_USE_POWER + +namespace megamol::power { + +SampleBuffer::SampleBuffer( + std::string const& name, std::chrono::milliseconds const& sample_range, std::chrono::milliseconds const& sample_dis) + : name_(name) + , sample_dis_(sample_dis) { + SetSampleRange(sample_range); +} + +void SampleBuffer::Add(sample_t const sample, filetime_t const timestamp) { + samples_.push_back(sample); + timestamps_.push_back(timestamp); + if (samples_.size() > 0.95f * samples_.capacity()) { + samples_.reserve(samples_.capacity() + cap_incr_); + timestamps_.reserve(timestamps_.capacity() + cap_incr_); + } +} + +void SampleBuffer::Clear() { + samples_.clear(); + timestamps_.clear(); + samples_.reserve(cap_incr_); + timestamps_.reserve(cap_incr_); +} + +void SampleBuffer::SetSampleRange(std::chrono::milliseconds const& sample_range) { + auto const total_samples = sample_range / sample_dis_; + cap_incr_ = total_samples * 1.1f; + samples_.reserve(cap_incr_); + timestamps_.reserve(cap_incr_); +} + +} // namespace megamol::power + +#endif diff --git a/frontend/services/power_service/power/SampleBuffer.h b/frontend/services/power_service/power/SampleBuffer.h new file mode 100644 index 0000000000..47869215a0 --- /dev/null +++ b/frontend/services/power_service/power/SampleBuffer.h @@ -0,0 +1,90 @@ +#pragma once + +#ifdef MEGAMOL_USE_POWER + +#include +#include +#include + +#include "Timestamp.h" +#include "Utility.h" + +namespace megamol::power { + +/// +/// Container class for the samples obtained with the power-overwhelming library. +/// +class SampleBuffer { +public: + /// + /// Ctor. + /// + SampleBuffer() = default; + + /// + /// + /// + /// + /// + /// + explicit SampleBuffer(std::string const& name, std::chrono::milliseconds const& sample_range, + std::chrono::milliseconds const& sample_dis); + + /// + /// Appends a sample and corresponding timestamp to the buffer. + /// Increases capacity if it is close to be filled. + /// + /// Sample value. + /// Timestamp in FILETIME. + void Add(sample_t const sample, filetime_t const timestamp); + + /// + /// Clears the underlying buffer. + /// + void Clear(); + + /// + /// Get the sample buffer. + /// + /// Sample buffer. + samples_t const& ReadSamples() const { + return samples_; + } + + /// + /// Get the timestamp buffer. + /// + /// Timestamp buffer. + timeline_t const& ReadTimestamps() const { + return timestamps_; + } + + /// + /// Get the name associated with the buffer. + /// + /// + std::string const& Name() const { + return name_; + } + + /// + /// Set the sampling time range. Influences the capacity increase wrt. the sample distance. + /// + /// The time range in milliseconds. + void SetSampleRange(std::chrono::milliseconds const& sample_range); + +private: + std::string name_; + + std::size_t cap_incr_ = 100; + + samples_t samples_; + + timeline_t timestamps_; + + std::chrono::milliseconds sample_dis_; +}; + +} // namespace megamol::power + +#endif diff --git a/frontend/services/power_service/power/SamplerCollection.h b/frontend/services/power_service/power/SamplerCollection.h new file mode 100644 index 0000000000..b21e87b06e --- /dev/null +++ b/frontend/services/power_service/power/SamplerCollection.h @@ -0,0 +1,367 @@ +#pragma once + +#ifdef MEGAMOL_USE_POWER + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DataverseWriter.h" +#include "MetaData.h" +#include "ParquetWriter.h" +#include "SamplerUtility.h" + +#ifdef MEGAMOL_USE_TRACY +#include +#endif + +using namespace visus::power_overwhelming; + +namespace megamol::power { +/// +/// Interface definition for the class. +/// +struct ISamplerCollection { + virtual void SetSegmentRange(std::chrono::milliseconds const& range) = 0; + + virtual void ResetBuffers() = 0; + + virtual void WriteBuffers(std::filesystem::path const& path, MetaData const* meta) const = 0; + + virtual void WriteBuffers(std::filesystem::path const& path, MetaData const* meta, + std::string const& dataverse_path, std::string const& dataverse_doi, char const* dataverse_token, + char& fin_signal) const = 0; + + virtual void Reset() = 0; + + virtual void StartRecording() = 0; + + virtual void StopRecording() = 0; + + virtual ~ISamplerCollection() = default; +}; + +/// +/// Class holding all samplers from a specific power-overwhelming sampler type. +/// Holds the corresponding sample buffers, as well. +/// +/// Power-overwhelming sampler type. +template +class SamplerCollection final : public ISamplerCollection { +public: + /// + /// Opens and configures all sensors of a specific power-overwhelming sampler type. + /// + /// Time range of a measurement segment in milliseconds. + /// Intended time distance between each sample in milliseconds. + /// Power-overwhelming name-based discard function for individual sensors. + /// Config function for each sensor. + /// Transform function that converts the power-overwhelming name of a sensor into a MegaMol name. + SamplerCollection(std::chrono::milliseconds const& sample_range, std::chrono::milliseconds const& sample_dis, + discard_func_t discard = nullptr, config_func_t config = nullptr, transform_func_t transform = nullptr) { + using namespace visus::power_overwhelming; + + auto const sensor_count = T::for_all(nullptr, 0); + if (sensor_count == 0) { + throw std::runtime_error("no sensors found"); + } + + std::vector tmp_sensors(sensor_count); + T::for_all(tmp_sensors.data(), tmp_sensors.size()); + + sensors_.reserve(sensor_count); + buffers_.reserve(sensor_count); + sensor_names_.reserve(sensor_count); + + for (auto& sensor : tmp_sensors) { + auto tmp_name = unmueller_string(sensor.name()); + if (discard) { + if (discard(tmp_name)) + continue; + } + try { + if (transform) { + tmp_name = transform(tmp_name); + } + } catch (...) { + continue; + } + sensor_names_.push_back(tmp_name); + auto const& name = sensor_names_.back(); + +#ifdef MEGAMOL_USE_TRACY + TracyPlotConfig(name.data(), tracy::PlotFormatType::Number, false, true, 0); +#endif + + auto& buffer = buffers_.emplace_back(name, sample_range, sample_dis); + + sensors_[name] = std::move(sensor); + if (config) { + config(sensors_[name]); + } + sensors_[name].sample( + std::move(async_sampling() + .delivers_measurement_data_to(&sample_func) + .stores_and_passes_context(std::make_tuple(name.data(), &buffer, std::cref(do_buffer_))) + .samples_every(std::chrono::duration_cast(sample_dis).count()) + .from_source(tinkerforge_sensor_source::power))); + } + } + + SamplerCollection(SamplerCollection const&) = delete; + SamplerCollection& operator=(SamplerCollection const&) = delete; + + /*SamplerCollection(SamplerCollection&& rhs) noexcept + : do_buffer_{std::exchange(rhs.do_buffer_, false)} + , sensor_names_{std::exchange(rhs.sensor_names_, std::vector{})} + , buffers_{std::exchange(rhs.buffers_, buffers_t{})} + , sensors_{std::exchange(rhs.sensors_, samplers_t{})} {} + + SamplerCollection& operator=(SamplerCollection&& rhs) noexcept { + if (this != std::addressof(rhs)) { + sensors_ = std::exchange(rhs.sensors_, samplers_t{}); + buffers_ = std::exchange(rhs.buffers_, buffers_t{}); + } + return *this; + }*/ + + /// + /// Sets the time range of a measurement segment for potential buffer realloc. + /// + /// Time range in milliseconds. + void SetSegmentRange(std::chrono::milliseconds const& range) override { + for (auto& b : buffers_) { + b.SetSampleRange(range); + } + } + + /// + /// Clears content of the sample buffers. + /// + void ResetBuffers() override { + for (auto& b : buffers_) { + b.Clear(); + } + } + + /// + /// Writes sample buffers as Parquet file. + /// + /// Path to the output file. + /// Meta information that should be embedded into the output file. + void WriteBuffers(std::filesystem::path const& path, MetaData const* meta) const override { + ParquetWriter(path, buffers_, meta); + } + + /// + /// Writes sample buffers as Parquet file and, additionally, sends their content to a Dataverse dataset. + /// + /// Path to the output file. + /// Meta information that should be embedded into the output file. + /// Path of the Dataverse API endpoint. + /// DOI of the dataset in the Dataverse. + /// Dataverse API token. + /// Inout param that will be set true + /// once the data transfer operation is finished (). + void WriteBuffers(std::filesystem::path const& path, MetaData const* meta, std::string const& dataverse_path, + std::string const& dataverse_doi, char const* dataverse_token, char& fin_signal) const override { + WriteBuffers(path, meta); + DataverseWriter(dataverse_path, dataverse_doi, path.string(), dataverse_token, fin_signal); + } + + /// + /// Specific reset for tinkerforge sensors. + /// Resyncs the internal clocks of the sensors with the system clock. + /// + void Reset() override { + if constexpr (std::is_same_v) { + for (auto& [n, s] : sensors_) { + s.resync_internal_clock(); + } + } + } + + /// + /// Starts the recording of samples into the sample buffers. + /// + void StartRecording() override { + do_buffer_ = true; + } + + /// + /// Stops the recording of samples into the sample buffers. + /// + void StopRecording() override { + do_buffer_ = false; + } + +private: + bool do_buffer_ = false; + std::vector sensor_names_; + power::buffers_t buffers_; + power::samplers_t sensors_; +}; + +/// +/// Wrapper for the class. +/// Abstraction over the template type. +/// Holds collections of all power-overwhelming sensor types. +/// +class SamplersCollectionWrapper final { +public: + struct base_path_t { + explicit base_path_t(std::filesystem::path const& path) : path_(path) {} + operator std::filesystem::path const&() const { + return path_; + } + + protected: + ~base_path_t() = default; + + private: + std::filesystem::path const& path_; + }; + + struct nvml_path_t final : public base_path_t { + explicit nvml_path_t(std::filesystem::path const& path) : base_path_t(path) {} + }; + struct adl_path_t final : public base_path_t { + explicit adl_path_t(std::filesystem::path const& path) : base_path_t(path) {} + }; + struct emi_path_t final : public base_path_t { + explicit emi_path_t(std::filesystem::path const& path) : base_path_t(path) {} + }; + struct msr_path_t final : public base_path_t { + explicit msr_path_t(std::filesystem::path const& path) : base_path_t(path) {} + }; + struct tinker_path_t final : public base_path_t { + explicit tinker_path_t(std::filesystem::path const& path) : base_path_t(path) {} + }; + + /// + /// Signature for a function visitor. + /// + /// Types of the parameter set to pass to the function. + template + using to_invoke_f = void (ISamplerCollection::*)(Ts...); + /// + /// Signature for a const function visitor. + /// + /// Types of the parameter set to be pass to the function. + template + using to_invoke_f_c = void (ISamplerCollection::*)(Ts...) const; + + /// + /// Visits all available power-overwhelming sensors with a specified function. + /// + /// Types of the parameter set to be passed to the function. + /// The function to be visited. + /// Parameter set to be passed to the function. + template + void visit(to_invoke_f to_invoke, Ts... args) { + if (nvml_samplers_) + (*nvml_samplers_.*to_invoke)(std::forward(args)...); + if (adl_samplers_) + (*adl_samplers_.*to_invoke)(std::forward(args)...); + if (emi_samplers_) + (*emi_samplers_.*to_invoke)(std::forward(args)...); + if (msr_samplers_) + (*msr_samplers_.*to_invoke)(std::forward(args)...); + if (tinker_samplers_) + (*tinker_samplers_.*to_invoke)(std::forward(args)...); + } + + /// + /// Visits all available power-overwhelming sensors with a specified const function. + /// Specialized for the output function with the file path tuple. + /// + /// Types of the parameter set to be passed to the function. + /// The function to be visited. + /// Set of file paths for the specific power-overwhelming sensor types. + /// Parameter set to be passed to the function. + template + void visit(to_invoke_f_c to_invoke, + std::tuple const& paths, Ts... args) { + if (nvml_samplers_) + (*nvml_samplers_.*to_invoke)(std::get<0>(paths), std::forward(args)...); + if (adl_samplers_) + (*adl_samplers_.*to_invoke)(std::get<1>(paths), std::forward(args)...); + if (emi_samplers_) + (*emi_samplers_.*to_invoke)(std::get<2>(paths), std::forward(args)...); + if (msr_samplers_) + (*msr_samplers_.*to_invoke)(std::get<3>(paths), std::forward(args)...); + if (tinker_samplers_) + (*tinker_samplers_.*to_invoke)(std::get<4>(paths), std::forward(args)...); + } + + /// + /// Ctor. + /// + SamplersCollectionWrapper() = default; + + /// + /// Specialized ctor taking ownership of for all power-overwhelming sensor types. + /// + /// for NVML sensors. + /// for ADL sensors. + /// for EMI sensors. + /// for MSR sensors. + /// for Tinkerforge sensors. + SamplersCollectionWrapper(std::unique_ptr>&& nvml_samplers, + std::unique_ptr>&& adl_samplers, + std::unique_ptr>&& emi_samplers, + std::unique_ptr>&& msr_samplers, + std::unique_ptr>&& tinker_samplers) + : nvml_samplers_{std::move(nvml_samplers)} + , adl_samplers_{std::move(adl_samplers)} + , emi_samplers_{std::move(emi_samplers)} + , msr_samplers_{std::move(msr_samplers)} + , tinker_samplers_{std::move(tinker_samplers)} {} + + /// + /// Move ctor. + /// + SamplersCollectionWrapper(SamplersCollectionWrapper&& rhs) noexcept + : nvml_samplers_{std::exchange(rhs.nvml_samplers_, nullptr)} + , adl_samplers_{std::exchange(rhs.adl_samplers_, nullptr)} + , emi_samplers_{std::exchange(rhs.emi_samplers_, nullptr)} + , msr_samplers_{std::exchange(rhs.msr_samplers_, nullptr)} + , tinker_samplers_{std::exchange(rhs.tinker_samplers_, nullptr)} {} + + /// + /// Move assignment. + /// + SamplersCollectionWrapper& operator=(SamplersCollectionWrapper&& rhs) noexcept { + nvml_samplers_ = std::exchange(rhs.nvml_samplers_, nullptr); + adl_samplers_ = std::exchange(rhs.adl_samplers_, nullptr); + emi_samplers_ = std::exchange(rhs.emi_samplers_, nullptr); + msr_samplers_ = std::exchange(rhs.msr_samplers_, nullptr); + tinker_samplers_ = std::exchange(rhs.tinker_samplers_, nullptr); + + return *this; + } + + /// + /// Dtor. + /// + ~SamplersCollectionWrapper() = default; + +private: + std::unique_ptr> nvml_samplers_ = nullptr; + + std::unique_ptr> adl_samplers_ = nullptr; + + std::unique_ptr> emi_samplers_ = nullptr; + + std::unique_ptr> msr_samplers_ = nullptr; + + std::unique_ptr> tinker_samplers_ = nullptr; +}; +} // namespace megamol::power + +#endif // MEGAMOL_USE_POWER diff --git a/frontend/services/power_service/power/SamplerUtility.h b/frontend/services/power_service/power/SamplerUtility.h new file mode 100644 index 0000000000..93fbfbb78b --- /dev/null +++ b/frontend/services/power_service/power/SamplerUtility.h @@ -0,0 +1,90 @@ +#pragma once + +#if MEGAMOL_USE_POWER + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "SampleBuffer.h" + +#ifdef MEGAMOL_USE_TRACY +#include +#endif + +using namespace visus::power_overwhelming; + +namespace megamol::power { + +/// +/// Container for a set of samplers from the power-overwhelming library. +/// (key = std::string, value = T) +/// +/// Sampler type. +template +using samplers_t = std::unordered_map; + +/// +/// Container definitions for a set of SampleBuffers . +/// +using buffers_t = std::vector; + +/// +/// Type definition for user data of the sampler functions . +/// +using context_t = std::tuple; + +/// +/// Signature for functions that discard power-overwhelming sensors based on their name. +/// +using discard_func_t = std::function; + +/// +/// Signature for functions that configure a power-overwhelming sensor. +/// +/// +template +using config_func_t = std::function; + +/// +/// Signature for functions that transform a power-overwhelming sensor name into a MegaMol sensor name. +/// +using transform_func_t = std::function; + +/// +/// Sampling function for all power-overwhelming samplers. +/// Samples will stored only when a flag passed as user data is true . +/// If tracy is active, all samples are also recorded as tracy plot. +/// +/// Samples from the power-overwhelimg library. +/// Number of samples in m. +/// Passed user data that contains the name of the sensor, the buffer for the samples, +/// and a flag whether samples are currently recorded. +inline void sample_func( + wchar_t const*, visus::power_overwhelming::measurement_data const* m, std::size_t const n, void* usr_ptr) { + auto usr_data = static_cast(usr_ptr); + auto name = std::get<0>(*usr_data); + auto buffer = std::get<1>(*usr_data); + auto const& do_buffer = std::get<2>(*usr_data); +#ifdef MEGAMOL_USE_TRACY + for (std::size_t i = 0; i < n; ++i) { + TracyPlot(name, m[i].power()); + } +#endif + if (do_buffer) { + for (std::size_t i = 0; i < n; ++i) { + buffer->Add(m[i].power(), m[i].timestamp()); + } + } +} +} // namespace megamol::power + +#endif diff --git a/frontend/services/power_service/power/SignalBroker.h b/frontend/services/power_service/power/SignalBroker.h new file mode 100644 index 0000000000..b2324b5d0d --- /dev/null +++ b/frontend/services/power_service/power/SignalBroker.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +namespace megamol::power { +/// +/// Helper class to gather and distribute ready/finished signal from different entities. +/// +class SignalBroker { +public: + /// + /// Created a new signal entry and returns a reference to that entry. + /// + /// Initial value; 0 false; 1 or higher true. + /// Reference to the pushed signal value to set later. + char& Get(bool initial) { + signals_.push_back(initial); + return signals_.back(); + } + + /// + /// Returns true if any of the signals is true. + /// + /// True, if any of signals is true; false if none of the signals is true. + bool GetValue() const { + return std::any_of(signals_.begin(), signals_.end(), [](auto const& val) { return val; }); + } + + /// + /// Clears the list of signals. + /// + void Reset() { + signals_.clear(); + } + +private: + std::list signals_; +}; +} // namespace megamol::power diff --git a/frontend/services/power_service/power/Timestamp.h b/frontend/services/power_service/power/Timestamp.h new file mode 100644 index 0000000000..413f758acd --- /dev/null +++ b/frontend/services/power_service/power/Timestamp.h @@ -0,0 +1,85 @@ +#pragma once + +#include +#include +#include +#include +#include + +#ifdef WIN32 +#include +#endif + +namespace megamol::power { +/// +/// Base type for FILETIME timestamps. +/// +using filetime_t = int64_t; +/// +/// Base container for timestamps. +/// +using timeline_t = std::vector; +/// +/// FILETIME chrono duration for conversion. +/// +using filetime_dur_t = std::chrono::duration>; +/// +/// FILETIME epoch offset. +/// +inline constexpr auto ft_offset = filetime_dur_t(116444736000000000LL); + +/// +/// Get current time as FILETIME. +/// +/// Timestamp as FILETIME. +inline filetime_dur_t get_highres_timer() { +#ifdef WIN32 + FILETIME f; + GetSystemTimePreciseAsFileTime(&f); + ULARGE_INTEGER tv; + tv.HighPart = f.dwHighDateTime; + tv.LowPart = f.dwLowDateTime; + return filetime_dur_t{tv.QuadPart}; + + /*LARGE_INTEGER t; + QueryPerformanceCounter(&t); + return t.QuadPart;*/ +#else + return filetime_dur_t{0}; +#endif +} + +/// +/// Adds offset to a timeline. +/// +/// Timeline to offset. +/// The offset in filetime. +/// Returns new timeline with offset. +inline timeline_t offset_timeline(timeline_t const& timeline, filetime_dur_t const& offset) { + timeline_t ret(timeline.size()); + + std::transform( + timeline.begin(), timeline.end(), ret.begin(), [o = offset.count()](auto const& val) { return val + o; }); + + return ret; +} + +/// +/// Converts time.h time to FILETIME. +/// +/// Input timestamp. +/// Returns timestamp in FILETIME. +inline filetime_dur_t convert_tm2ft(std::tm& t) { + return filetime_dur_t(std::mktime(&t) * 10000000LL) + ft_offset; +} + +/// +/// Converts a system_time time point to FILETIME. +/// +/// Input timestamp. +/// Returns timestamp in FILETIME. +inline filetime_dur_t convert_systemtp2ft(std::chrono::system_clock::time_point const& tp) { + static auto epoch = std::chrono::system_clock::from_time_t(0); + return std::chrono::duration_cast(tp - epoch); +} +} // namespace megamol::power diff --git a/frontend/services/power_service/power/Tinkerforge.h b/frontend/services/power_service/power/Tinkerforge.h new file mode 100644 index 0000000000..9415da0f73 --- /dev/null +++ b/frontend/services/power_service/power/Tinkerforge.h @@ -0,0 +1,83 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace megamol::power { +/// +/// Parses a file into a JSON object. +/// +/// Path to the JSON file. +/// JSON object of the file. +nlohmann::json parse_json_file(std::filesystem::path const& path) { + std::ifstream f(path); + auto json_file = nlohmann::json::parse(f); + f.close(); + return json_file; +} + +/// +/// Look-up of tinkerforge sensor name in the given JSON object. +/// +/// JSON object with tinkerforge sensor names. +/// Tinkerforge UID as search string. +/// Name of the sensor. +/// +std::string get_tf_name(nlohmann::json const& data, std::string const& search) { + for (auto const& el : data.items()) { + // expecting array + if (!el.value().is_array()) { + throw std::runtime_error("expected array"); + } + + int counter = 0; + for (auto const& val : el.value().items()) { + auto const v = val.value().get(); + if (v == search) { + if (el.value().size() > 1) { + return el.key() + std::to_string(counter); + } else { + return el.key(); + } + } + ++counter; + } + } + return ""; +} + +/// +/// Extracts UID from power-overwhelming tinkerforge name. +/// +/// Tinkerforge name. +/// UID of the tinkerforge sensor. +std::string get_search_string(std::string const& name) { + auto const reg = std::regex(R"(^Tinkerforge/[\w|\:]+/(\w+)$)"); + std::smatch match; + if (std::regex_match(name, match, reg)) { + return match[1]; + } + return ""; +} + +/// +/// Get name of the tinkerforge sensor from the JSON object. +/// +/// JSON object. +/// Power-overwhelming name of the tinkerforge sensor. +/// Name of tinkerforge sensor. +/// +std::string transform_tf_name(nlohmann::json const& data, std::string const& name) { + auto const transformed_name = get_tf_name(data, get_search_string(name)); + if (transformed_name.empty()) { + throw std::runtime_error("unable to transform name"); + } + return transformed_name; +} + +} // namespace megamol::power diff --git a/frontend/services/power_service/power/Trigger.cpp b/frontend/services/power_service/power/Trigger.cpp new file mode 100644 index 0000000000..1f63225e94 --- /dev/null +++ b/frontend/services/power_service/power/Trigger.cpp @@ -0,0 +1,5 @@ +#include "Trigger.h" + +#ifdef MEGAMOL_USE_POWER + +#endif diff --git a/frontend/services/power_service/power/Trigger.h b/frontend/services/power_service/power/Trigger.h new file mode 100644 index 0000000000..80ecc293fc --- /dev/null +++ b/frontend/services/power_service/power/Trigger.h @@ -0,0 +1,255 @@ +#pragma once + +#ifdef MEGAMOL_USE_POWER + +#include +#include +#include +#include +#include +#include +#include + +#include "ParallelPortTrigger.h" +#include "Timestamp.h" + +#ifdef MEGAMOL_USE_TRACY +#include +#endif + +namespace megamol::power { +/// +/// Class containing the trigger base functionality to trigger an oscilloscope over the parallel port +/// and synchronize the measurement with other sensors. +/// +class Trigger final { +public: + /// + /// Ctr setting the address of the parallel port to use. + /// Expected format: "lpt1" without prefix "\\.\" + /// + /// LPT address + Trigger(std::string const& address) { + set_lpt(address); + } + + /// + /// Arms the trigger sequence. + /// + void ArmTrigger() { + armed_ = true; + } + + /// + /// Disarms the trigger sequence. + /// Trigger sequence will exit after full cycle. + /// + void DisarmTrigger() { + armed_ = false; + } + + /// + /// Runs the trigger sequence as long as armed_ is true. + /// The sequence fires a pre trigger first, for instance, to start recording non-osci sensors, since an osci records the time window of the past. + /// Then the main trigger fires that will start the measurement at the oscilloscope. + /// Afterwards, a post trigger is fired , for instance, to stop recording of non-osci sensors. + /// After exit of this trigger sequence a final trigger is fired, for instance, to write the recorded buffers of the measurements. + /// This function locks access to the parallel port handle. + /// + /// Runup time for measurement. + /// Time between trigger and post trigger operations. + /// Wait time at the end of trigger sequence. + /// The timestamp of the last main trigger in filetime. + filetime_dur_t StartTriggerSequence(std::chrono::milliseconds const& prefix, + std::chrono::milliseconds const& postfix, std::chrono::milliseconds const& wait) { + filetime_dur_t trg_tp{0}; + std::unique_lock trg_lock(trg_mtx_); + fire_init_trigger(); + while (armed_) { + fire_pre_trigger(); + std::this_thread::sleep_for(prefix + std::chrono::seconds(1)); //< additional second for NVML runup + trg_tp = fire_trigger(); + std::this_thread::sleep_for(postfix); + fire_post_trigger(); + std::this_thread::sleep_for(wait); + } + fire_fin_trigger(); + return trg_tp; + } + + /// + /// Registers a signal. Signals will be notified when trigger is fired. + /// + /// Name of the signal. + /// Function to be called when trigger is fired. + void RegisterSignal(std::string const& name, std::function const& signal) { + signals_[name] = signal; + } + + /// + /// Registers a function that is called before a trigger sequence is called. + /// + /// Name of the function. + /// The callable function. + void RegisterInitTrigger(std::string const& name, std::function const& trigger) { + init_trigger_[name] = trigger; + } + + /// + /// Registers a function that is called with the main trigger. + /// + /// Name of the function. + /// The callable function. + void RegisterSubTrigger(std::string const& name, std::function const& trigger) { + sub_trigger_[name] = trigger; + } + + /// + /// Registers a function that is called at the beggining of the prefix time in the trigger sequence. + /// + /// Name of the function. + /// The callable function. + void RegisterPreTrigger(std::string const& name, std::function const& pre_trigger) { + pre_trigger_[name] = pre_trigger; + } + + /// + /// Registers a function that is called at the end of the postfix time in the trigger sequence. + /// + /// Name of the function. + /// The callable function. + void RegisterPostTrigger(std::string const& name, std::function const& post_trigger) { + post_trigger_[name] = post_trigger; + } + + /// + /// Register a final trigger that is fired after the trigger sequence. + /// + /// Name of the trigger. Should be unique. + /// The instance of the function to register. + void RegisterFinTrigger(std::string const& name, std::function const& fin_trigger) { + fin_trigger_[name] = fin_trigger; + } + + /// + /// Opens the parallel port used for sending the main trigger. + /// + /// Address of the virtual file representing the parallel port. + void SetLPTAddress(std::string const& address) { + set_lpt(address); + } + + /// + /// Return a pointer the parallel port used for sending the main trigger. + /// + /// Pointer to parallel port. + ParallelPortTrigger* GetHandle() { + return trigger_.get(); + } + +private: + void set_lpt(std::string const& address) { + std::regex p("^(lpt|LPT)(\\d)$"); + std::smatch m; + if (!std::regex_search(address, m, p)) { + throw std::runtime_error("LPT address malformed"); + } + + std::unique_lock trg_lock(trg_mtx_); + try { + trigger_ = std::make_unique(("\\\\.\\" + address).c_str()); + } catch (...) { + trigger_ = nullptr; + } + } + + filetime_dur_t fire_trigger() { +#ifdef MEGAMOL_USE_TRACY + ZoneScopedNC("Trigger::trigger", 0xDB0ABF); +#endif + fire_sub_trigger(); + + auto const ts = get_highres_timer(); + if (trigger_) { + trigger_->SetBit(6, true); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + trigger_->SetBit(6, false); + } + + notify_all(ts); + + return ts; + } + + void notify_all(filetime_dur_t const& ts) const { + for (auto const& [n, s] : signals_) { + s(ts); + } + } + + void fire_init_trigger() const { +#ifdef MEGAMOL_USE_TRACY + ZoneScopedN("Trigger::fire_init_trigger"); +#endif + for (auto const& [n, t] : init_trigger_) { + t(); + } + } + + void fire_sub_trigger() const { +#ifdef MEGAMOL_USE_TRACY + ZoneScopedN("Trigger::fire_sub_trigger"); +#endif + for (auto const& [n, t] : sub_trigger_) { + t(); + } + } + + void fire_pre_trigger() const { +#ifdef MEGAMOL_USE_TRACY + ZoneScopedN("Trigger::fire_pre_trigger"); +#endif + for (auto const& [n, p] : pre_trigger_) { + p(); + } + } + + void fire_post_trigger() const { +#ifdef MEGAMOL_USE_TRACY + ZoneScopedN("Trigger::fire_post_trigger"); +#endif + for (auto const& [n, p] : post_trigger_) { + p(); + } + } + + void fire_fin_trigger() const { +#ifdef MEGAMOL_USE_TRACY + ZoneScopedN("Trigger::fire_fin_trigger"); +#endif + for (auto const& [n, p] : fin_trigger_) { + p(); + } + } + + std::unique_ptr trigger_; + + std::unordered_map> signals_; + + std::unordered_map> init_trigger_; + + std::unordered_map> sub_trigger_; + + std::unordered_map> pre_trigger_; + + std::unordered_map> post_trigger_; + + std::unordered_map> fin_trigger_; + + bool armed_ = false; + + std::mutex trg_mtx_; +}; +} // namespace megamol::power + +#endif diff --git a/frontend/services/power_service/power/Utility.cpp b/frontend/services/power_service/power/Utility.cpp new file mode 100644 index 0000000000..d4d193c050 --- /dev/null +++ b/frontend/services/power_service/power/Utility.cpp @@ -0,0 +1,130 @@ +#include "Utility.h" + +#ifdef MEGAMOL_USE_POWER + +#include +#include + +namespace megamol::power { + +std::tuple parse_hmc_file(std::string hmc_file) { + // some lines have a leading '\r' + hmc_file.erase(std::remove_if(std::begin(hmc_file), std::end(hmc_file), [](auto const& c) { return c == '\r'; })); + + std::regex count_reg(R"(#Actual Count;(\d+)\s*)"); + std::regex date_reg(R"(#Date;([\d|-]+)\s*)"); + std::smatch match; + + std::stringstream hmc_stream(hmc_file); + std::stringstream meta_stream; + std::stringstream csv_stream; + + power::value_map_t vals; + + std::string line; + + int num_of_rows = 0; + int line_count = 0; + + std::string date_str; + + std::vector col_names; + + while (std::getline(hmc_stream, line)) { + if (line[0] == '#') { + // meta information + meta_stream << line << '\n'; + if (std::regex_match(line, match, count_reg)) { + num_of_rows = std::stoi(match[1].str()); + } + if (std::regex_match(line, match, date_reg)) { + date_str = match[1]; + } + } else { + if (line[0] != '\n') { + // csv data + if (num_of_rows == 0) + break; + if (line_count > num_of_rows) + break; + if (line_count == 0) { + // title line + std::string val_str; + auto sstream = std::istringstream(line); + while (std::getline(sstream, val_str, ';')) { + col_names.push_back(val_str); + if (val_str.find("Timestamp") != std::string::npos) { + vals[val_str] = power::timeline_t{}; + std::get(vals[val_str]).reserve(num_of_rows); + } else { + vals[val_str] = power::samples_t{}; + std::get(vals[val_str]).reserve(num_of_rows); + } + } + } else { + // data line + std::string val_str; + std::vector val_strs; + val_strs.reserve(col_names.size()); + auto sstream = std::istringstream(line); + while (std::getline(sstream, val_str, ';')) { + val_strs.push_back(val_str); + } + + if (val_strs.size() != col_names.size()) { + throw std::runtime_error("unexpected number of values in line"); + } + + for (std::size_t i = 0; i < val_strs.size(); ++i) { + if (col_names[i].find("Timestamp") != std::string::npos) { + // parse UTC timestamp with fractional seconds + auto const ms_pos = val_str.find('.'); + int64_t t_ms = 0; + std::string time_str; + if (ms_pos == std::string::npos) { + // timestamp without ms part + time_str = val_str; + } else { + time_str = std::string(val_str.begin(), val_str.begin() + ms_pos); + auto const ms_str = std::string(val_str.begin() + ms_pos + 1, val_str.end()); + t_ms = std::stoi(ms_str); + } +#ifdef _WIN32 // TODO this does only work with gcc >= 14 + std::chrono::utc_clock::time_point tp; + std::istringstream time_stream(date_str + "T" + time_str); + if (std::chrono::from_stream(time_stream, "%FT%T", tp)) { + auto const ts = (power::convert_systemtp2ft(std::chrono::utc_clock::to_sys(tp)) + + std::chrono::duration_cast( + std::chrono::milliseconds(t_ms)) + + ft_offset) + .count(); + std::get(vals.at(col_names[i])).push_back(ts); + } else +#else +#warning "Fix me!!! Disable time point parsing on Linux as std::chrono::from_stream is only available with gcc >= 14." +#endif + { + throw std::runtime_error("could not parse UTC time"); + } + } else { + // data + if (!val_strs[i].empty()) + std::get(vals.at(col_names[i])).push_back(std::stof(val_strs[i])); + else + std::get(vals.at(col_names[i])) + .push_back(std::numeric_limits::signaling_NaN()); + } + } + } + csv_stream << line << '\n'; + ++line_count; + } + } + } + + return std::make_tuple(meta_stream.str(), csv_stream.str(), vals); +} + +} // namespace megamol::power + +#endif diff --git a/frontend/services/power_service/power/Utility.h b/frontend/services/power_service/power/Utility.h new file mode 100644 index 0000000000..9a9e5ab647 --- /dev/null +++ b/frontend/services/power_service/power/Utility.h @@ -0,0 +1,176 @@ +#pragma once + +#ifdef MEGAMOL_USE_POWER + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "MetaData.h" +#include "Timestamp.h" + +namespace megamol::power { +/// +/// Type for measurement samples. +/// +using sample_t = float; +/// +/// Container storing a set of samples. +/// +using samples_t = std::vector; +/// +/// Base type for a measurement as key, value map. +/// +using value_map_t = std::unordered_map>; +/// +/// Container storing a set of measurement segments. +/// +using segments_t = std::vector; +/// +/// Function signature for writers (output_path, device_name, value_map, meta). +/// +using writer_func_t = + std::function; + +/// +/// Retrieves a string, such as the name of a device, from the power_overwhelming library. +/// +/// Class type of which the retrieve function is a member. +/// Instance to invoke the retrive function. +/// The retrieve function. +/// The retrieved string. +template +inline std::string get_pwrowg_str(T const& i, std::size_t (T::*func)(char*, std::size_t) const) { + auto const name_size = (i.*func)(static_cast(nullptr), 0); + std::vector name(name_size); + (i.*func)(name.data(), name.size()); + return std::string{name.data()}; +} + +/// +/// Copy a power_overwhelming waveform into vector. +/// +/// The waveform. +/// Vector with the samples from the waveform. +inline std::vector copy_waveform(visus::power_overwhelming::oscilloscope_waveform const& wave) { + return std::vector(wave.begin(), wave.end()); +} + +/// +/// Generates a sequence of timestamps for the given waveform. +/// The timestamps are in filetime. +/// +/// The waveform. +/// Vector containing the timestamps. +inline power::timeline_t generate_timeline(visus::power_overwhelming::oscilloscope_waveform const& waveform) { + + auto const t_begin = waveform.time_begin(); + //auto const t_end = waveform.time_end(); + auto const t_dis = waveform.sample_distance(); + //auto const t_off = waveform.segment_offset(); + auto const r_length = waveform.record_length(); + + using ft_float = std::chrono::duration; + + auto const t_b_ft = + std::chrono::duration_cast(ft_float(t_begin * static_cast(ft_float::period::den))); + auto const t_d_ft = + std::chrono::duration_cast(ft_float(t_dis * static_cast(ft_float::period::den))); + + power::timeline_t ret(r_length, t_b_ft.count()); + + auto const t_d_ft_c = t_d_ft.count(); + + std::inclusive_scan( + ret.begin(), ret.end(), ret.begin(), [&t_d_ft_c](auto const& lhs, auto const& rhs) { return lhs + t_d_ft_c; }); + + return ret; +} + +/// +/// Parses the log file retrieved from an HMC device. +/// +/// The log file as string. +/// A tuple containing the meta info portion as string, the csv portion as string, and the parsed csv content as a value map. +/// +std::tuple parse_hmc_file(std::string hmc_file); + +/// Creates a full file path for a segment file. +/// Base output folder. +/// Name of the device the output data is from. +/// Is used as prefix for the filename. +/// Segment index. Is included in the filename. +/// Extension of the file. (Expects leading '.') +/// Full path to the segment file destination. +inline std::filesystem::path create_full_path(std::filesystem::path const& output_folder, + std::string const& device_name, std::size_t const s_idx, std::string const& ext = ".parquet") { + return output_folder / (device_name + "_s" + std::to_string(s_idx) + ext); +} + +/// +/// Creates a full file path without segment id. +/// +/// Base output folder. +/// Name of the device the output data is from. +/// Is used as prefix for the filename. +/// Extension of the file. (Expects leading '.') +/// Full path to the file destination. +inline std::filesystem::path create_full_path( + std::filesystem::path const& output_folder, std::string const& device_name, std::string const& ext = ".parquet") { + return output_folder / (device_name + ext); +} + +/// +/// Convert a wide char string to std::string. +/// +/// Wide char string. +/// Char string. +inline std::string unmueller_string(wchar_t const* name) { + // https://en.cppreference.com/w/cpp/locale/codecvt/out + + auto const& f = std::use_facet>(std::locale()); + + std::wstring internal(name); + std::mbstate_t mb = std::mbstate_t(); + std::string external(internal.size() * f.max_length(), '\0'); + const wchar_t* from_next; + char* to_next; + + auto const res = f.out( + mb, &internal[0], &internal[internal.size()], from_next, &external[0], &external[external.size()], to_next); + if (res != std::codecvt_base::ok) { + throw std::runtime_error("could not convert string"); + } + external.resize(to_next - &external[0]); + + return external; +} + +/// +/// Computes measurement prefix, postfix, and wait time for a specified time range. +/// +/// Base measurement range in milliseconds. +/// Tuple with prefix, postfix, and wait time in milliseconds. +inline std::tuple get_trigger_timings( + std::chrono::milliseconds range) { + // config_range_ / 12, config_range_ - config_range_ / 12, std::chrono::milliseconds(1000) + config_range_ + auto const lw = std::chrono::milliseconds(200); + auto const prefix = range / 12ll + lw; + auto const postfix = range - (range / 12ll) + lw; + auto const wait = range + std::chrono::milliseconds(1000); + return std::make_tuple(prefix, postfix, wait); +} + +} // namespace megamol::power + +#endif diff --git a/frontend/services/power_service/power/WriterUtility.cpp b/frontend/services/power_service/power/WriterUtility.cpp new file mode 100644 index 0000000000..a85d3e38e4 --- /dev/null +++ b/frontend/services/power_service/power/WriterUtility.cpp @@ -0,0 +1,9 @@ +#include "WriterUtility.h" + +#ifdef MEGAMOL_USE_POWER + +namespace megamol::power { +std::unordered_set wf_tracy_wrapper::name_lib = std::unordered_set{}; +} + +#endif diff --git a/frontend/services/power_service/power/WriterUtility.h b/frontend/services/power_service/power/WriterUtility.h new file mode 100644 index 0000000000..23aebc72e0 --- /dev/null +++ b/frontend/services/power_service/power/WriterUtility.h @@ -0,0 +1,101 @@ +#pragma once + +#ifdef MEGAMOL_USE_POWER + +#include +#include +#include + +#include "ColumnNames.h" +#include "MetaData.h" +#include "ParquetWriter.h" +#include "Utility.h" + +#ifdef MEGAMOL_USE_TRACY +#include +#endif + +namespace megamol::power { + +inline void wf_parquet(std::filesystem::path const& output_folder, std::string const& device_name, + power::segments_t const& values_map, power::MetaData const* meta) { + for (std::size_t s_idx = 0; s_idx < values_map.size(); ++s_idx) { + auto const fullpath = create_full_path(output_folder, device_name, s_idx); + ParquetWriter(fullpath, values_map[s_idx], meta); + } +} + +inline void wf_parquet_dataverse(std::filesystem::path const& output_folder, std::string const& device_name, + power::segments_t const& values_map, power::MetaData const* meta, + std::function const& dataverse_writer) { + for (std::size_t s_idx = 0; s_idx < values_map.size(); ++s_idx) { + auto const fullpath = create_full_path(output_folder, device_name, s_idx); + ParquetWriter(fullpath, values_map[s_idx], meta); + dataverse_writer(fullpath.string()); + } +} + +struct wf_tracy_wrapper { + static std::unordered_set name_lib; + + static void wf_tracy(std::filesystem::path const& output_folder, std::string const&, + power::segments_t const& values_map, power::MetaData const* meta) { +#ifdef MEGAMOL_USE_TRACY +#ifdef MEGAMOL_USE_TRACY_TIME_PLOT + for (auto const& vm : values_map) { + for (auto const& [name, v] : vm) { + name_lib.insert(name); + } + } + + for (std::size_t s_idx = 0; s_idx < values_map.size(); ++s_idx) { + auto const& vm = values_map[s_idx]; + auto const& timestamps = std::get(vm.at(global_ts_name)); + for (auto const& [name, v_values] : vm) { + if (std::holds_alternative(v_values)) { + auto const t_name_it = name_lib.find(name); + if (t_name_it != name_lib.end()) { + auto const& values = std::get(v_values); + TracyPlotConfig(t_name_it->data(), tracy::PlotFormatType::Number, false, true, 0); + for (std::size_t v_idx = 0; v_idx < values.size(); ++v_idx) { + tracy::Profiler::PlotData(t_name_it->data(), values[v_idx], timestamps[v_idx]); + } + } + } + } + } +#endif +#endif + } +}; + +inline void wf_tracy(std::filesystem::path const& output_folder, [[maybe_unused]] std::string const&, + power::segments_t const& values_map, power::MetaData const* meta) { +#ifdef MEGAMOL_USE_TRACY +#ifdef MEGAMOL_USE_TRACY_TIME_PLOT + static std::set tpn_library; + for (std::size_t s_idx = 0; s_idx < values_map.size(); ++s_idx) { + auto const& vm = values_map[s_idx]; + auto const& timestamps = std::get(vm.at("timestamps")); + for (auto const& [name, v_values] : vm) { + if (std::holds_alternative(v_values)) { + auto const c_name = name + "\0"; + tpn_library.insert(c_name); + auto t_name_it = tpn_library.find(name.c_str()); + if (t_name_it != tpn_library.end()) { + auto const& values = std::get(v_values); + TracyPlotConfig(t_name_it->data(), tracy::PlotFormatType::Number, false, true, 0); + for (std::size_t v_idx = 0; v_idx < values.size(); ++v_idx) { + tracy::Profiler::PlotData(t_name_it->data(), values[v_idx], timestamps[v_idx]); + } + } + } + } + } +#endif +#endif +} + +} // namespace megamol::power + +#endif diff --git a/frontend/services/power_service/power/sol_rtx_instrument.cpp b/frontend/services/power_service/power/sol_rtx_instrument.cpp new file mode 100644 index 0000000000..b24b0943d1 --- /dev/null +++ b/frontend/services/power_service/power/sol_rtx_instrument.cpp @@ -0,0 +1,273 @@ +#include "sol_rtx_instrument.h" + +#ifdef MEGAMOL_USE_POWER + +#include + +#include +#include + +namespace megamol::power { + +using namespace visus::power_overwhelming; + +void sol_rtx_instrument(sol::state& lua) { + auto rtx_instrument_table = lua.new_usertype("rtx_instrument", + sol::constructors, const char*, const visa_instrument::timeout_type)>()); +#if 0 + rtx_instrument_table["acquisition"] = + sol::overload(static_cast(&rtx_instrument::acquisition), + static_cast(&rtx_instrument::acquisition)/*, + static_cast(&rtx_instrument::acquisition)*/); +#endif + + rtx_instrument_table["acquisition"] = + static_cast( + &rtx_instrument::acquisition); + + rtx_instrument_table["channel"] = + static_cast(&rtx_instrument::channel); + + rtx_instrument_table["reference_position"] = + static_cast( + &rtx_instrument::reference_position); + + rtx_instrument_table["trigger"] = + static_cast(&rtx_instrument::trigger); + + lua.set_function("find_resources", [](const char*, const char*) { + std::vector ret; + + auto devices = visa_instrument::find_resources("0x0AAD", "0x01D6"); + + for (auto d = devices.as(); (d != nullptr) && (*d != 0); d += strlen(d) + 1) { + ret.emplace_back(d); + } + + return ret; + }); +} + + +void sol_oscilloscope_single_acquisition(sol::state& lua) { + auto acq_table = lua.new_usertype( + "oscilloscope_single_acquisition", sol::constructors()); + + acq_table["count"] = static_cast( + &oscilloscope_acquisition::count); + + acq_table["points"] = static_cast( + &oscilloscope_acquisition::points); + + acq_table["segmented"] = static_cast( + &oscilloscope_acquisition::segmented); +} + + +void sol_oscilloscope_reference_point(sol::state& lua) { + lua.new_enum("oscilloscope_reference_point", + {{"left", oscilloscope_reference_point::left}, {"middle", oscilloscope_reference_point::middle}, + {"right", oscilloscope_reference_point::right}}); +} + + +void sol_oscilloscope_channel(sol::state& lua) { + auto channel_table = lua.new_usertype( + "oscilloscope_channel", sol::constructors()); + + channel_table["attenuation"] = + static_cast( + &oscilloscope_channel::attenuation); + + channel_table["label"] = static_cast( + &oscilloscope_channel::label); + + channel_table["state"] = + static_cast(&oscilloscope_channel::state); + + channel_table["range"] = + static_cast( + &oscilloscope_channel::range); + + channel_table["offset"] = + static_cast( + &oscilloscope_channel::offset); + + channel_table["zero_adjust"] = + static_cast(&oscilloscope_channel::zero_adjust); +} + + +void sol_oscilloscope_edge_trigger(sol::state& lua) { + auto trigger_table = lua.new_usertype( + "oscilloscope_trigger", sol::constructors()); + + trigger_table["level"] = sol::overload( + static_cast(&oscilloscope_trigger::level), + static_cast( + &oscilloscope_trigger::level)); + + trigger_table["slope"] = + static_cast( + &oscilloscope_trigger::slope); + + trigger_table["mode"] = + static_cast( + &oscilloscope_trigger::mode); + + lua.new_enum("oscilloscope_trigger_slope", + {{"both", oscilloscope_trigger_slope::both}, {"rising", oscilloscope_trigger_slope::rising}, + {"falling", oscilloscope_trigger_slope::falling}}); + + lua.new_enum("oscilloscope_trigger_mode", + {{"automatic", oscilloscope_trigger_mode::automatic}, {"normal", oscilloscope_trigger_mode::normal}}); +} + + +void sol_oscilloscope_quantity(sol::state& lua) { + auto quant_table = lua.new_usertype( + "oscilloscope_quantity", sol::constructors()); +} + + +void sol_oscilloscope_label(sol::state& lua) { + auto label_table = lua.new_usertype( + "oscilloscope_label", sol::constructors()); +} + + +void sol_rtx_instrument_configuration(sol::state& lua) { + auto config_table = lua.new_usertype( + "rtx_instrument_configuration", sol::constructors, + std::reference_wrapper, std::uint32_t)>()); + + config_table["channel"] = &rtx_instrument_configuration::channel; + + config_table["trigger_position"] = + static_cast( + &rtx_instrument_configuration::trigger_position); + + //config_table["as_slave"] = &rtx_instrument_configuration::as_slave; + lua.set_function("as_slave", + [](const rtx_instrument_configuration& config, + oscilloscope_quantity const& level) -> rtx_instrument_configuration { return config.as_slave(0, level); }); + + //lua->set_function("get_config", + // [](const oscilloscope_quantity quant, const oscilloscope_acquisition& acq) -> rtx_instrument_configuration { + // /*oscilloscope_edge_trigger trigger = oscilloscope_edge_trigger("EXT"); + // trigger.level(5, oscilloscope_quantity(2000.0f, "mV")) + // .slope(oscilloscope_trigger_slope::rising) + // .mode(oscilloscope_trigger_mode::normal);*/ + + // return rtx_instrument_configuration(quant, acq, + // dynamic_cast(oscilloscope_edge_trigger("EXT") + // .level(5, oscilloscope_quantity(2, "V")) + // .slope(oscilloscope_trigger_slope::rising) + // .mode(oscilloscope_trigger_mode::normal)), + // 10000); + // }); + + //lua->set_function( + // "get_trigger", [](const char* source) -> oscilloscope_edge_trigger { + // /*oscilloscope_edge_trigger trigger = oscilloscope_edge_trigger("EXT"); + // trigger.level(5, oscilloscope_quantity(2000.0f, "mV")) + // .slope(oscilloscope_trigger_slope::rising) + // .mode(oscilloscope_trigger_mode::normal);*/ + + // return oscilloscope_edge_trigger(source); + // }); +} + +void sol_expressions(sol::state& lua, + std::vector, std::vector>>> const& + val_map) { + lua.set_function("rtx_plus", [&val_map](int idx, sol::variadic_args va) -> std::vector { + std::vector ret; + auto const& curr_map = val_map.at(idx); + for (auto v : va) { + std::string name = v; + auto const& v_values = curr_map.at(name); + if (!std::holds_alternative>(v_values)) { + throw std::runtime_error("Unexpected type"); + } + auto const& values = std::get>(v_values); + if (ret.empty()) { + ret = values; + } else { + std::transform(values.begin(), values.end(), ret.begin(), ret.begin(), std::plus()); + } + } + return ret; + }); + auto m_func_1 = [&val_map](int idx, sol::variadic_args va) -> std::vector { + std::vector ret; + auto const& curr_map = val_map.at(idx); + for (auto v : va) { + std::string name = v; + auto const& v_values = curr_map.at(name); + if (!std::holds_alternative>(v_values)) { + throw std::runtime_error("Unexpected type"); + } + auto const& values = std::get>(v_values); + if (ret.empty()) { + ret = values; + } else { + std::transform(values.begin(), values.end(), ret.begin(), ret.begin(), std::multiplies()); + } + } + return ret; + }; + auto m_func_2 = [&val_map](int idx, std::vector const& lhs, std::string rhs) -> std::vector { + auto ret = lhs; + auto const& v_values = val_map.at(idx).at(rhs); + if (!std::holds_alternative>(v_values)) { + throw std::runtime_error("Unexpected type"); + } + auto const& values = std::get>(v_values); + std::transform(values.begin(), values.end(), ret.begin(), ret.begin(), std::multiplies()); + return ret; + }; + auto m_func_3 = [&val_map](int idx, std::string lhs, std::vector const& rhs) -> std::vector { + auto ret = rhs; + auto const& v_values = val_map.at(idx).at(lhs); + if (!std::holds_alternative>(v_values)) { + throw std::runtime_error("Unexpected type"); + } + auto const& values = std::get>(v_values); + std::transform(values.begin(), values.end(), ret.begin(), ret.begin(), std::multiplies()); + return ret; + }; + + lua.set_function("rtx_multiplies", sol::overload(m_func_1, m_func_2, m_func_3)); +} + + +void sol_register_all(sol::state& lua) { + sol_rtx_instrument(lua); + + sol_rtx_instrument_configuration(lua); + + sol_oscilloscope_single_acquisition(lua); + + sol_oscilloscope_reference_point(lua); + + sol_oscilloscope_channel(lua); + + sol_oscilloscope_edge_trigger(lua); + + sol_oscilloscope_quantity(lua); + + sol_oscilloscope_label(lua); +} + +} // namespace megamol::power + +#endif diff --git a/frontend/services/power_service/power/sol_rtx_instrument.h b/frontend/services/power_service/power/sol_rtx_instrument.h new file mode 100644 index 0000000000..0f659b02dc --- /dev/null +++ b/frontend/services/power_service/power/sol_rtx_instrument.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include + +namespace megamol::power { + +void sol_rtx_instrument(sol::state& lua); + +void sol_oscilloscope_single_acquisition(sol::state& lua); + +void sol_oscilloscope_reference_point(sol::state& lua); + +void sol_oscilloscope_channel(sol::state& lua); + +void sol_oscilloscope_edge_trigger(sol::state& lua); + +void sol_oscilloscope_quantity(sol::state& lua); + +void sol_oscilloscope_label(sol::state& lua); + +void sol_rtx_instrument_configuration(sol::state& lua); + +void sol_expressions(sol::state& lua, + std::vector, std::vector>>> const& + val_map); + +void sol_register_all(sol::state& lua); + +} // namespace megamol::power diff --git a/frontend/services/runtimeinfo_service/RuntimeInfo_Service.cpp b/frontend/services/runtimeinfo_service/RuntimeInfo_Service.cpp new file mode 100644 index 0000000000..9df0dc0433 --- /dev/null +++ b/frontend/services/runtimeinfo_service/RuntimeInfo_Service.cpp @@ -0,0 +1,151 @@ +/** + * MegaMol + * Copyright (c) 2021, MegaMol Dev Team + * All rights reserved. + */ + +#include + +// search/replace Template_Service with your class name +// you should also delete the FAQ comments in these template files after you read and understood them +#include "RuntimeInfo_Service.hpp" + + +// local logging wrapper for your convenience until central MegaMol logger established +#include "mmcore/utility/log/Log.h" + +static const std::string service_name = "RuntimeInfo_Service: "; +static void log(std::string const& text) { + const std::string msg = service_name + text; + megamol::core::utility::log::Log::DefaultLog.WriteInfo(msg.c_str()); +} + +static void log_error(std::string const& text) { + const std::string msg = service_name + text; + megamol::core::utility::log::Log::DefaultLog.WriteError(msg.c_str()); +} + +static void log_warning(std::string const& text) { + const std::string msg = service_name + text; + megamol::core::utility::log::Log::DefaultLog.WriteWarn(msg.c_str()); +} + + +namespace megamol { +namespace frontend { + +RuntimeInfo_Service::RuntimeInfo_Service() { + // init members to default states +} + +RuntimeInfo_Service::~RuntimeInfo_Service() { + // clean up raw pointers you allocated with new, which is bad practice and nobody does +} + +bool RuntimeInfo_Service::init(void* configPtr) { + ri_resource_.get_hardware_info = [&]() { return get_hardware_info(); }; + ri_resource_.get_os_info = [&]() { return get_os_info(); }; + ri_resource_.get_runtime_libraries = [&]() { return get_runtime_libraries(); }; + + ri_resource_.get_smbios_info = [&]() { return get_smbios_info(); }; + ri_resource_.get_cpu_info = [&]() { return get_cpu_info(); }; + ri_resource_.get_gpu_info = [&]() { return get_gpu_info(); }; + ri_resource_.get_OS_info = [&]() { return get_OS_info(); }; + + m_providedResourceReferences = {{"RuntimeInfo", ri_resource_}}; + + auto t = std::thread([&]() { + //log("(Async) get WMI stuff"); + get_hardware_info(); + get_os_info(); + get_runtime_libraries(); + get_smbios_info(); + get_cpu_info(); + get_gpu_info(); + get_OS_info(); + //log("(Async) finished getting WMI stuff"); + }); + t.detach(); + + log("initialized successfully"); + return true; +} + +void RuntimeInfo_Service::close() { + // close libraries or APIs you manage + // wrap up resources your service provides, but don not depend on outside resources to be available here + // after this, at some point only the destructor of your service gets called +} + +std::vector& RuntimeInfo_Service::getProvidedResources() { + return m_providedResourceReferences; +} + +const std::vector RuntimeInfo_Service::getRequestedResourceNames() const { + return m_requestedResourcesNames; +} + +void RuntimeInfo_Service::setRequestedResources(std::vector resources) { + // maybe we want to keep the list of requested resources + this->m_requestedResourceReferences = resources; +} + +void RuntimeInfo_Service::updateProvidedResources() {} + +void RuntimeInfo_Service::digestChangedRequestedResources() {} + +void RuntimeInfo_Service::resetProvidedResources() { + // this gets called at the end of the main loop iteration + // since the current resources state should have been handled in this frame already + // you may clean up resources whose state is not needed for the next iteration + // e.g. m_keyboardEvents.clear(); + // network_traffic_buffer.reset_to_empty(); +} + +void RuntimeInfo_Service::preGraphRender() { + // this gets called right before the graph is told to render something + // e.g. you can start a start frame timer here + + // rendering via MegaMol View is called after this function finishes + // in the end this calls the equivalent of ::mmcRenderView(hView, &renderContext) + // which leads to view.Render() +} + +void RuntimeInfo_Service::postGraphRender() { + // the graph finished rendering and you may more stuff here + // e.g. end frame timer + // update window name + // swap buffers, glClear +} + +std::string RuntimeInfo_Service::get_hardware_info() { + return ri_.GetHardwareInfo(); +} + +std::string RuntimeInfo_Service::get_os_info() { + return ri_.GetOsInfo(); +} + +std::string RuntimeInfo_Service::get_runtime_libraries() { + return ri_.GetRuntimeLibraries(); +} + +std::string RuntimeInfo_Service::get_smbios_info() { + return ri_.GetSMBIOSInfo(); +} + +std::string RuntimeInfo_Service::get_cpu_info() { + return ri_.GetCPUInfo(); +} + +std::string RuntimeInfo_Service::get_gpu_info() { + return ri_.GetGPUInfo(); +} + +std::string RuntimeInfo_Service::get_OS_info() { + return ri_.GetOSInfo(); +} + + +} // namespace frontend +} // namespace megamol diff --git a/frontend/services/runtimeinfo_service/RuntimeInfo_Service.hpp b/frontend/services/runtimeinfo_service/RuntimeInfo_Service.hpp new file mode 100644 index 0000000000..df40d8543d --- /dev/null +++ b/frontend/services/runtimeinfo_service/RuntimeInfo_Service.hpp @@ -0,0 +1,186 @@ +/** + * MegaMol + * Copyright (c) 2021, MegaMol Dev Team + * All rights reserved. + */ + +#pragma once + +#include "AbstractFrontendService.hpp" + +#include "mmcore/utility/platform/RuntimeInfo.h" + +#include "RuntimeInfo.h" + +namespace megamol { +namespace frontend { + +// search/replace Template_Service with your class name +// you should also delete the FAQ comments in these template files after you read and understood them +class RuntimeInfo_Service final : public AbstractFrontendService { +public: + // We encourage you to use a configuration struct + // that can be passed to your init() function. + struct Config {}; + + // sometimes somebody wants to know the name of the service + std::string serviceName() const override { + return "RuntimeInfo_Service"; + } + + // constructor should not take arguments, actual object initialization deferred until init() + RuntimeInfo_Service(); + ~RuntimeInfo_Service(); + // your service will be constructed and destructed, but not copy-constructed or move-constructed + // so no need to worry about copy or move constructors. + + // implement the following functions/callbacks to get your service hooked into the frontend + + // init service with input config data, e.g. init GLFW with OpenGL and open window with certain decorations/hints + // if init() fails return false (this will terminate program execution), on success return true + bool init(void* configPtr) override; + void close() override; + + // expose the resources or input events this service provides via getProvidedResources(): e.g. Keyboard inputs, Controller inputs, GLFW Window events + // the FrontendResource is a named wrapper that wraps some type (struct) in an std::any and casts its content to a requested type + // each service may provide a set of resources for other services or graph modules to use + // usually resources shared among services and modules are read only, in the sense that the FrontendResource wrapper only returns const& to held resources + // if you need to manipulate a resource that you do not own, make sure you know what you're doing before using const_cast<> + // keep in mind that the FrontendResource is just a wrapper that holds a void* to an object that you provided + // thus, keep objects that you broadcast as resources alive until your close() gets called! + // if lifetime of one of your resources ends before your close() gets called you produce dangling references in other services! + // if you need to re-initialize or swap resource contents in a way that needs an objects lifetime to end, consider wrapping that behaviour in a way that is user friendly + std::vector& getProvidedResources() override; + + // a service may request a set of resources that are provided by other services or the system + // this works in two steps: the service tells the system which services it requests using the service names (usually the type name of the structs) + // the frontend system makes sure to provide the service with the requested resources, or else program execution is terminated with an error message + // note that the list of resources given to the service is in order of the initial resource requests, + // thus you don't need to search for resources by name but can rather access the set resource vector directly at the expected index + // the idea of behind resources is that when your service gets its list of requested resources, + // the contract is that there will be an actual resource available and not some null pointer. + // if somebody does not play along and provides fake resources with wrong wrapped types to the system, thus giving your code garbage to work with, + // we can not really stop him from doing so, but there should occur an unhandled exception thrown by std::any for a bad type cast + // the gist is that you should expect to get the correct resources you requested from the system + // and you can work on those resources without tedious error and type checking + // if you want to see how resources are distributed among the services, look into FrontendServiceCollection.cpp::assignRequestedResources() + // the lifetime of the resources you get starts when your setRequestedResources() gets called and ends before your close() gets called + // dont depend on requested resources being available in your close(). you yourself sould destroy or close the resources you provide in close(). + // init() gets called in order of service priority (see below) and close() gets called in reverse order, + // so if you really need access to some resource in your close() make sure + // the priority order of your service is _after_ the service that provides your critical resources (i.e. your set priority number should be higher) + const std::vector getRequestedResourceNames() const override; + void setRequestedResources(std::vector resources) override; + + // the following resource update and graph render callbacks get called in each iteration of the main loop + // this is probably where most work of your service is done + // the service callbacks get called in the main loop in the following order: + // + // auto services = {lua_service, opengl_service, gui_service}; // wrapper that loops over all services in service priority order + // services.init(); + // services.assignResourcesAmongServices(); + // + // while (true) { + // services.updateProvidedResources(); + // services.digestChangedRequestedResources(); + // + // if (services.shouldShutdown()) + // break; + // + // {// render step + // services.preGraphRender(); + // megamol_graph.RenderNextFrame(); + // services.postGraphRender(); // calls service callbacks in reverse order + // } + // + // services.resetProvidedResources(); + // } + // services.close(); // calls service callbacks in reverse order + + // called first in main loop, each service updates its shared resources to some new state here (e.g. receive keyboard inputs, network traffic) + void updateProvidedResources() override; + + // after each service updates its provided resources, services may check for updates in their requested resources + // for example, a GUI may check for user inputs placed in keyboard or mouse input resources that are provided by some other service + // usually working with resources should not modify them, + // but if you are really sure that it is ok for you to change resources, you may cast the away the const from the resource reference and manipulate the resource + // for example, you may cast away the const from the MegaMolGraph to issue creation/deletion of modules and calls + // or you may delete keyboard and mouse inputs from corresponding resources if you are sure they only affect your service + // this callback is also a good place to verify if your service received a shutdown request and propagate it to the system via setShutdown() + void digestChangedRequestedResources() override; + + // after rendering of a frame finished and before the next iteration of the main loop, services may want to reset resource state to some value + // e.g. after user inputs (keyboard, mouse) or window resize evets got handled by the relevant services or modules, + // the service providing and managing those resource structs may want to clear those inputs before the next frame starts + // (in some cases the distinction between updateProvidedResources() at the beginning of a main loop iteration + // and a resetProvidedResources() at the end of that iteration seems to be necessary) + void resetProvidedResources() override; + + // gets called before graph rendering, you may prepare rendering with some API, e.g. set frame-timers, etc + void preGraphRender() override; + // clean up after rendering, e.g. render gui over graph rendering, stop and show frame-timers in GLFW window, swap buffers, glClear for next framebuffer + void postGraphRender() override; + + // from AbstractFrontendService + // you inherit the following functions that manage priority of your service and shutdown requests to terminate the program + // you and others may use those functions, but you will not override them + // priority indicates the order in which services get their callbacks called, i.e. this is the sorting of the vector that holds all services + // lower priority numbers get called before the bigger ones. for close() and postGraphRender() services get called in the reverse order, + // i.e. this works like construction and destruction order of objects in a c++ + // + // int setPriority(const int p) // priority initially 0 + // int getPriority() const; + + // your service can signal to the program that a shutdown request has been received. + // call setShutdown() to set your shutdown status to true, this is best done in your updateProvidedResources() or digestChangedRequestedResources(). + // if a servie signals a shutdown the system calls close() on all services in reverse priority order, then program execution terminates. + // + // bool shouldShutdown() const; // shutdown initially false + // void setShutdown(const bool s = true); + +private: + std::string get_hardware_info(); + + std::string get_os_info(); + + std::string get_runtime_libraries(); + + std::string get_smbios_info(); + + std::string get_cpu_info(); + + std::string get_gpu_info(); + + std::string get_OS_info(); + + // this can hold references to the resources (i.e. structs) we provide to others, e.g. you may fill this and return it in getProvidedResources() + // provided resources will be queried by the system only once, + // there is no requirement to store the resources in a vector the whole time, you just need to return such a vector in getProvidedResources() + // but you need to store the actual resource objects you provide and manage + // note that FrontendResource wraps a void* to the objects you provide, thus your resource objects will not be copied, but they will be referenced + // (however the FrontendResource objects themselves will be copied) + std::vector m_providedResourceReferences; + + // names of resources you request for your service can go here + // requested resource names will be queried by the system only once, + // there is no requirement to store the names in a vector the whole time, you just need to return such a vector in getRequestedResourceNames() + std::vector m_requestedResourcesNames; + + // you may store the resources you requested in this vector by filling it when your setRequestedResources() gets called + // the resources provided to you by the system match the names you requested in getRequestedResourceNames() and are expected to reference actual existing objects + // the sorting of resources matches the order of your requested resources names, you can use this to directly index into the vector provided by setRequestedResources() + // if every service follows the rules the provided resources should be valid existing objects, thus you can use them directly without error or nullptr checking, + // but we in the end we must blindly rely on the std::any in FrontendResource to hold the struct or type you expect it to hold + // (or else std::any will throw a bad type cast exception that should terminate program execution. + // you do NOT catch or check for that exception or need to care for it in any way!) + std::vector m_requestedResourceReferences; + + bool initialized_ = false; + + megamol::core::utility::platform::RuntimeInfo ri_; + + megamol::frontend_resources::RuntimeInfo ri_resource_; +}; + +} // namespace frontend +} // namespace megamol diff --git a/frontend/services/screenshot_service/Screenshot_Service.cpp b/frontend/services/screenshot_service/Screenshot_Service.cpp index 2d40b466da..fc4868a794 100644 --- a/frontend/services/screenshot_service/Screenshot_Service.cpp +++ b/frontend/services/screenshot_service/Screenshot_Service.cpp @@ -70,8 +70,8 @@ static void PNGAPI pngFlushFileFunc(png_structp pngPtr) { f->Flush(); } -static bool write_png_to_file( - megamol::frontend_resources::ScreenshotImageData const& image, std::filesystem::path const& filename) { +static bool write_png_to_file(megamol::frontend_resources::ScreenshotImageData const& image, + std::filesystem::path const& filename, megamol::frontend_resources::RuntimeInfo const* ri) { vislib::sys::FastFile file; try { // open final image file @@ -108,7 +108,7 @@ static bool write_png_to_file( } auto additional = megamol::core::utility::graphics::ScreenShotComments::comments_storage_map(); additional["Frame ID"] = std::to_string(frame_stats_ptr->rendered_frames_count - 1); - megamol::core::utility::graphics::ScreenShotComments ssc(project, additional); + megamol::core::utility::graphics::ScreenShotComments ssc(project, ri, additional); png_set_text(pngPtr, pngInfoPtr, ssc.GetComments().data(), ssc.GetComments().size()); png_set_IHDR(pngPtr, pngInfoPtr, image.width, image.height, 8, PNG_COLOR_TYPE_RGB_ALPHA /* PNG_COLOR_TYPE_RGB */, @@ -175,8 +175,9 @@ megamol::frontend_resources::ImageWrapperScreenshotSource::take_screenshot() con } bool megamol::frontend_resources::ScreenshotImageDataToPNGWriter::write_image( - ScreenshotImageData const& image, std::filesystem::path const& filename) const { - return write_png_to_file(image, filename); + ScreenshotImageData const& image, std::filesystem::path const& filename, void const* ri_ptr) const { + return write_png_to_file( + image, filename, reinterpret_cast(ri_ptr)); } namespace megamol::frontend { @@ -198,7 +199,7 @@ bool Screenshot_Service::init(const Config& config) { m_requestedResourcesNames = {"optional", // TODO: for GLScreenshoSource. how to kill? frontend_resources::MegaMolGraph_Req_Name, "optional", "RuntimeConfig", "optional", - frontend_resources::FrameStatistics_Req_Name}; + frontend_resources::FrameStatistics_Req_Name, "RuntimeInfo"}; this->m_frontbufferToPNG_trigger = [&](std::filesystem::path const& filename) -> bool { log("write screenshot to " + filename.generic_string()); @@ -221,6 +222,18 @@ bool Screenshot_Service::init(const Config& config) { void Screenshot_Service::close() {} std::vector& Screenshot_Service::getProvidedResources() { + this->m_frontbufferToPNG_trigger = [&](std::filesystem::path const& filename) -> bool { + log("write screenshot to " + filename.generic_string()); + return m_toFileWriter_resource.write_screenshot(m_frontbufferSource_resource, filename, ri_); + }; + + this->m_imagewrapperToPNG_trigger = [&](megamol::frontend_resources::ImageWrapper const& image, + std::filesystem::path const& filename) -> bool { + log("write screenshot to " + filename.generic_string()); + return m_toFileWriter_resource.write_screenshot( + megamol::frontend_resources::ImageWrapperScreenshotSource(image), filename, ri_); + }; + this->m_providedResourceReferences = {{"GLScreenshotSource", m_frontbufferSource_resource}, {"ImageDataToPNGWriter", m_toFileWriter_resource}, {"GLFrontbufferToPNG_ScreenshotTrigger", m_frontbufferToPNG_trigger}, @@ -252,6 +265,8 @@ void Screenshot_Service::setRequestedResources(std::vector res } frame_stats_ptr = const_cast( &resources[5].getResource()); + + ri_ = &resources[6].getResource(); } void Screenshot_Service::updateProvidedResources() {} diff --git a/frontend/services/screenshot_service/Screenshot_Service.hpp b/frontend/services/screenshot_service/Screenshot_Service.hpp index 296c0eb45b..3183ee6400 100644 --- a/frontend/services/screenshot_service/Screenshot_Service.hpp +++ b/frontend/services/screenshot_service/Screenshot_Service.hpp @@ -13,6 +13,8 @@ // ImageData struct and interfaces for screenshot sources/writers #include "Screenshots.h" +#include "RuntimeInfo.h" + namespace megamol::frontend { class Screenshot_Service final : public AbstractFrontendService { @@ -68,6 +70,8 @@ class Screenshot_Service final : public AbstractFrontendService { std::vector m_providedResourceReferences; std::vector m_requestedResourcesNames; std::vector m_requestedResourceReferences; + + megamol::frontend_resources::RuntimeInfo const* ri_; }; } // namespace megamol::frontend diff --git a/plugins/cinematic_gl/src/CinematicView.cpp b/plugins/cinematic_gl/src/CinematicView.cpp index e0f4347389..4b78bbf25f 100644 --- a/plugins/cinematic_gl/src/CinematicView.cpp +++ b/plugins/cinematic_gl/src/CinematicView.cpp @@ -531,6 +531,13 @@ ImageWrapper CinematicView::Render(double time, double instanceTime) { } +bool megamol::cinematic_gl::CinematicView::create() { + ri_ = &frontend_resources.get(); + + return View3DGL::create(); +} + + bool CinematicView::render_to_file_setup() { auto ccc = this->keyframeKeeperSlot.CallAs(); @@ -685,7 +692,7 @@ bool CinematicView::render_to_file_write() { auto& megamolgraph = frontend_resources.get(); project = const_cast(megamolgraph).Convenience().SerializeGraph(); - megamol::core::utility::graphics::ScreenShotComments ssc(project); + megamol::core::utility::graphics::ScreenShotComments ssc(project, ri_); png_set_text( this->png_data.structptr, this->png_data.infoptr, ssc.GetComments().data(), ssc.GetComments().size()); png_set_IHDR(this->png_data.structptr, this->png_data.infoptr, this->png_data.width, this->png_data.height, 8, diff --git a/plugins/cinematic_gl/src/CinematicView.h b/plugins/cinematic_gl/src/CinematicView.h index 11c452f344..b6dc1f7eb2 100644 --- a/plugins/cinematic_gl/src/CinematicView.h +++ b/plugins/cinematic_gl/src/CinematicView.h @@ -12,6 +12,7 @@ #include #include "ModuleGraphSubscription.h" +#include "RuntimeInfo.h" #include "cinematic/Keyframe.h" #include "cinematic_gl/CinematicUtils.h" #include "mmcore/CallerSlot.h" @@ -35,6 +36,7 @@ class CinematicView : public mmstd_gl::view::View3DGL { Base::requested_lifetime_resources(req); req.require(); req.require(); + req.require(); } /** @@ -67,6 +69,8 @@ class CinematicView : public mmstd_gl::view::View3DGL { */ ImageWrapper Render(double time, double instanceTime) override; + bool create() override; + private: typedef std::chrono::system_clock::time_point TimePoint_t; @@ -113,6 +117,7 @@ class CinematicView : public mmstd_gl::view::View3DGL { unsigned int fps; bool skyboxCubeMode; std::shared_ptr cinematicFbo; + megamol::frontend_resources::RuntimeInfo const* ri_ = nullptr; /********************************************************************** * functions diff --git a/plugins/mmstd_gl/include/mmstd_gl/special/ScreenShooter.h b/plugins/mmstd_gl/include/mmstd_gl/special/ScreenShooter.h index 5cb072c0fb..34e945f829 100644 --- a/plugins/mmstd_gl/include/mmstd_gl/special/ScreenShooter.h +++ b/plugins/mmstd_gl/include/mmstd_gl/special/ScreenShooter.h @@ -10,6 +10,7 @@ #include +#include "RuntimeInfo.h" #include "mmcore/MegaMolGraph.h" #include "mmcore/Module.h" #include "mmcore/job/AbstractJob.h" @@ -26,6 +27,7 @@ class ScreenShooter : public core::job::AbstractJob, public core::Module, public static void requested_lifetime_resources(frontend_resources::ResourceRequest& req) { Module::requested_lifetime_resources(req); req.require(); + req.require(); } /** @@ -166,6 +168,8 @@ class ScreenShooter : public core::job::AbstractJob, public core::Module, public bool running; std::shared_ptr currentFbo; + + frontend_resources::RuntimeInfo const* ri_ = nullptr; }; } // namespace megamol::mmstd_gl::special diff --git a/plugins/mmstd_gl/src/special/ScreenShooter.cpp b/plugins/mmstd_gl/src/special/ScreenShooter.cpp index c792135ba2..094f8f857e 100644 --- a/plugins/mmstd_gl/src/special/ScreenShooter.cpp +++ b/plugins/mmstd_gl/src/special/ScreenShooter.cpp @@ -328,6 +328,7 @@ bool special::ScreenShooter::Terminate() { */ bool special::ScreenShooter::create() { currentFbo = std::make_shared(1, 1); + ri_ = &frontend_resources.get(); return true; } @@ -468,7 +469,7 @@ void special::ScreenShooter::BeforeRender(core::view::AbstractView* view) { std::string project; auto& megamolgraph = frontend_resources.get(); project = const_cast(megamolgraph).Convenience().SerializeGraph(); - megamol::core::utility::graphics::ScreenShotComments ssc(project); + megamol::core::utility::graphics::ScreenShotComments ssc(project, ri_); png_set_text(data.pngPtr, data.pngInfoPtr, ssc.GetComments().data(), ssc.GetComments().size()); diff --git a/plugins/molecularmaps/CMakeLists.txt b/plugins/molecularmaps/CMakeLists.txt index ae4777a3b4..7948bc3041 100644 --- a/plugins/molecularmaps/CMakeLists.txt +++ b/plugins/molecularmaps/CMakeLists.txt @@ -21,8 +21,8 @@ if (molecularmaps_PLUGIN_ENABLED) message(FATAL_ERROR "Molecularmaps currently only supports CUDA Version 11 or above. The version found was CUDA ${CUDA_VERSION_MAJOR} (${CMAKE_CUDA_COMPILER_VERSION}). Please turn off the Molecularmaps plugin or install a correct version of CUDA." ) endif() - set(CMAKE_CUDA_ARCHITECTURES 52) - set(CMAKE_CUDA_FLAGS_RELEASE "${CMAKE_CUDA_FLAGS} -O3") + set_target_properties(molecularmaps PROPERTIES CUDA_ARCHITECTURES 52) + target_compile_options(molecularmaps PRIVATE $<$,$>:-O3>) file(GLOB_RECURSE cuda_source_files RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "src/*.cu") file(GLOB_RECURSE cuda_header_files RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "src/*.cuh") diff --git a/plugins/optix_hpg/CMakeLists.txt b/plugins/optix_hpg/CMakeLists.txt index 13ba9bf9ae..ada3beb257 100644 --- a/plugins/optix_hpg/CMakeLists.txt +++ b/plugins/optix_hpg/CMakeLists.txt @@ -9,7 +9,6 @@ megamol_plugin(optix_hpg cuda DEPENDS_PLUGINS mmstd - mmstd_gl mesh geometry_calls) @@ -21,36 +20,93 @@ if (optix_hpg_PLUGIN_ENABLED) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/") - set(OPTIX_HPG_CUDA_ARCH "75" CACHE STRING "CUDA Arch for Optix HPG") - set_property(CACHE OPTIX_HPG_CUDA_ARCH PROPERTY STRINGS 61 75 80) - - if (OPTIX_HPG_CUDA_ARCH STREQUAL "61") - set(CMAKE_CUDA_ARCHITECTURES 61) - elseif (OPTIX_HPG_CUDA_ARCH STREQUAL "75") - set(CMAKE_CUDA_ARCHITECTURES 75) - elseif (OPTIX_HPG_CUDA_ARCH STREQUAL "80") - set(CMAKE_CUDA_ARCHITECTURES 80) - endif () - - include(configure_optix) find_package(CUDAToolkit) + include(configure_optix) - get_target_property(GLM_INCLUDES glm::glm INTERFACE_INCLUDE_DIRECTORIES) - - # Collect source files - file(GLOB_RECURSE cuda_resource_include_files RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "cuda_resources/*.h" "cuda_resources/*.cuh") - - include_directories(${GLM_INCLUDES} "${CMAKE_CURRENT_SOURCE_DIR}/src") - cuda_compile_and_embed(embedded_raygen_programs cuda_resources/raygen.cu) - cuda_compile_and_embed(embedded_miss_programs cuda_resources/miss.cu) - cuda_compile_and_embed(embedded_miss_occlusion_programs cuda_resources/miss_occlusion.cu) - cuda_compile_and_embed(embedded_sphere_programs cuda_resources/sphere.cu) - cuda_compile_and_embed(embedded_sphere_occlusion_programs cuda_resources/sphere_occlusion.cu) - cuda_compile_and_embed(embedded_mesh_programs cuda_resources/mesh.cu) - cuda_compile_and_embed(embedded_transitioncalculator_programs cuda_resources/transitioncalculator.cu) + set(ptx_include_dirs "${CMAKE_CURRENT_SOURCE_DIR}/src" "${CMAKE_CURRENT_SOURCE_DIR}/cuda_resources") + embed_ptx( + OUTPUT_TARGET + embedded_raygen_programs + PTX_LINK_LIBRARIES + glm::glm + OptiX::OptiX + PTX_INCLUDE_DIRECTORIES + ${ptx_include_dirs} + SOURCES + cuda_resources/raygen.cu) + embed_ptx( + OUTPUT_TARGET + embedded_miss_programs + PTX_LINK_LIBRARIES + glm::glm + OptiX::OptiX + PTX_INCLUDE_DIRECTORIES + ${ptx_include_dirs} + SOURCES + cuda_resources/miss.cu) + embed_ptx( + OUTPUT_TARGET + embedded_miss_occlusion_programs + PTX_LINK_LIBRARIES + glm::glm + OptiX::OptiX + PTX_INCLUDE_DIRECTORIES + ${ptx_include_dirs} + SOURCES + cuda_resources/miss_occlusion.cu) + embed_ptx( + OUTPUT_TARGET + embedded_sphere_programs + PTX_LINK_LIBRARIES + glm::glm + OptiX::OptiX + PTX_INCLUDE_DIRECTORIES + ${ptx_include_dirs} + SOURCES + cuda_resources/sphere.cu) + #embed_ptx( + # OUTPUT_TARGET + # embedded_sphere_occlusion_programs + # PTX_LINK_LIBRARIES + # glm::glm + # OptiX::OptiX + # PTX_INCLUDE_DIRECTORIES + # ${ptx_include_dirs} + # SOURCES + # cuda_resources/sphere_occlusion.cu) + embed_ptx( + OUTPUT_TARGET + embedded_mesh_programs + PTX_LINK_LIBRARIES + glm::glm + OptiX::OptiX + PTX_INCLUDE_DIRECTORIES + ${ptx_include_dirs} + SOURCES + cuda_resources/mesh.cu) + embed_ptx( + OUTPUT_TARGET + embedded_transitioncalculator_programs + PTX_LINK_LIBRARIES + glm::glm + OptiX::OptiX + PTX_INCLUDE_DIRECTORIES + ${ptx_include_dirs} + SOURCES + cuda_resources/transitioncalculator.cu) - target_sources(optix_hpg PRIVATE ${cuda_resource_include_files} ${embedded_raygen_programs} ${embedded_miss_programs} ${embedded_miss_occlusion_programs} ${embedded_sphere_programs} ${embedded_sphere_occlusion_programs} ${embedded_mesh_programs} ${embedded_transitioncalculator_programs}) + target_link_libraries(optix_hpg PRIVATE + $ + $ + $ + $ + #$ + $ + $) target_include_directories(optix_hpg PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/cuda_resources/") - target_link_libraries(optix_hpg PRIVATE CUDA::cuda_driver) + target_link_libraries(optix_hpg PRIVATE CUDA::cuda_driver OptiX::OptiX) + if (MEGAMOL_USE_OPENGL) + target_link_libraries(optix_hpg PRIVATE mmstd_gl) + endif () endif () diff --git a/plugins/optix_hpg/cmake/FindOptiX.cmake b/plugins/optix_hpg/cmake/FindOptiX.cmake index 61f358056b..ee4daf82ba 100644 --- a/plugins/optix_hpg/cmake/FindOptiX.cmake +++ b/plugins/optix_hpg/cmake/FindOptiX.cmake @@ -1,3 +1,5 @@ +# https://github.com/owl-project/owl/blob/master/owl/cmake/FindOptiX.cmake + # # Copyright (c) 2018 NVIDIA CORPORATION. All rights reserved. # @@ -26,156 +28,38 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # -# Locate the OptiX distribution. Search relative to the SDK first, then look in the system. - -# Our initial guess will be within the SDK. -if (NOT DEFINED OptiX_INSTALL_DIR) - if (WIN32) - if ($ENV{OptiX_INSTALL_DIR}) - set(OptiX_INSTALL_DIR $ENV{OptiX_INSTALL_DIR}) - else() - set(OptiX_INSTALL_DIR "c:/ProgramData/NVIDIA Corporation/OptiX SDK 7.2.0") - endif() - else() - set(OptiX_INSTALL_DIR $ENV{OptiX_INSTALL_DIR}) - endif() +if (TARGET OptiX::OptiX) + return() endif() -#set(OptiX_INSTALL_DIR "${CMAKE_SOURCE_DIR}/../" CACHE PATH "Path to OptiX installed location.") -# The distribution contains both 32 and 64 bit libraries. Adjust the library -# search path based on the bit-ness of the build. (i.e. 64: bin64, lib64; 32: -# bin, lib). Note that on Mac, the OptiX library is a universal binary, so we -# only need to look in lib and not lib64 for 64 bit builds. -if(CMAKE_SIZEOF_VOID_P EQUAL 8 AND NOT APPLE) - set(bit_dest "64") +if (OptiX_INSTALL_DIR) + message(STATUS "Detected the OptiX_INSTALL_DIR variable (pointing to ${OptiX_INSTALL_DIR}; going to use this for finding optix.h") + find_path(OptiX_ROOT_DIR NAMES include/optix.h PATHS ${OptiX_INSTALL_DIR}) +elseif (DEFINED ENV{OptiX_INSTALL_DIR}) + message(STATUS "Detected the OptiX_INSTALL_DIR env variable (pointing to $ENV{OptiX_INSTALL_DIR}; going to use this for finding optix.h") + find_path(OptiX_ROOT_DIR NAMES include/optix.h PATHS $ENV{OptiX_INSTALL_DIR}) else() - set(bit_dest "") + find_path(OptiX_ROOT_DIR NAMES include/optix.h) endif() -macro(OPTIX_find_api_library name version) - find_library(${name}_LIBRARY - NAMES ${name}.${version} ${name} - PATHS "${OptiX_INSTALL_DIR}/lib${bit_dest}" - NO_DEFAULT_PATH - ) - find_library(${name}_LIBRARY - NAMES ${name}.${version} ${name} - ) - if(WIN32) - find_file(${name}_DLL - NAMES ${name}.${version}.dll - PATHS "${OptiX_INSTALL_DIR}/bin${bit_dest}" - NO_DEFAULT_PATH - ) - find_file(${name}_DLL - NAMES ${name}.${version}.dll - ) - endif() -endmacro() - -#OPTIX_find_api_library(optix 70) -#OPTIX_find_api_library(optixu 1) -#OPTIX_find_api_library(optix_prime 1) - -# Include -find_path(OptiX_INCLUDE - NAMES optix.h - PATHS "${OptiX_INSTALL_DIR}/include" - NO_DEFAULT_PATH +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(OptiX + FOUND_VAR OptiX_FOUND + REQUIRED_VARS + OptiX_ROOT_DIR ) -find_path(OptiX_INCLUDE - NAMES optix.h - ) - -# Check to make sure we found what we were looking for -function(OptiX_report_error error_message required) - if(OptiX_FIND_REQUIRED AND required) - message(FATAL_ERROR "${error_message}") - else() - if(NOT OptiX_FIND_QUIETLY) - message(STATUS "${error_message}") - endif(NOT OptiX_FIND_QUIETLY) - endif() -endfunction() -#if(NOT optix_LIBRARY) -# OptiX_report_error("optix library not found. Please locate before proceeding." TRUE) -#endif() -if(NOT OptiX_INCLUDE) - OptiX_report_error("OptiX headers (optix.h and friends) not found. Please locate before proceeding." TRUE) +if (OptiX_FOUND) + message("OptiX found all right.") +else() + message("could not find optix, through none of the supported ways.") + message("If you haven't yet done so, please install OptiX, then point OWL to where you have it installed. There are two different ways of doing that (pick which one you prefer):") + message("Option 1: cmake's OPTIX_ROOT variable. E.g.") + message(" OptiX_ROOT=/NVIDIA-OptiX-SDK-7.6.0-linux64-x86_64/ cmake .") + message("Option 1: defining a `OptiX_INSTALL_DIR` environment variable") + message(" export OptiX_INSTALL_DIR=/NVIDIA-OptiX-SDK-7.6.0-linux64-x86_64/") + message(" cmake .") endif() -#if(NOT optix_prime_LIBRARY) -# OptiX_report_error("optix Prime library not found. Please locate before proceeding." FALSE) -#endif() - -# Macro for setting up dummy targets -function(OptiX_add_imported_library name lib_location dll_lib dependent_libs) - set(CMAKE_IMPORT_FILE_VERSION 1) - # Create imported target -# add_library(${name} SHARED IMPORTED) - - # Import target "optix" for configuration "Debug" - # if(WIN32) - # set_target_properties(${name} PROPERTIES - # IMPORTED_IMPLIB "${lib_location}" - # #IMPORTED_LINK_INTERFACE_LIBRARIES "glu32;opengl32" - # IMPORTED_LOCATION "${dll_lib}" - # IMPORTED_LINK_INTERFACE_LIBRARIES "${dependent_libs}" - # ) - # elseif(UNIX) - # set_target_properties(${name} PROPERTIES - # #IMPORTED_LINK_INTERFACE_LIBRARIES "glu32;opengl32" - # IMPORTED_LOCATION "${lib_location}" - # # We don't have versioned filenames for now, and it may not even matter. - # #IMPORTED_SONAME "${optix_soname}" - # IMPORTED_LINK_INTERFACE_LIBRARIES "${dependent_libs}" - # ) - # else() - # # Unknown system, but at least try and provide the minimum required - # # information. - # set_target_properties(${name} PROPERTIES - # IMPORTED_LOCATION "${lib_location}" - # IMPORTED_LINK_INTERFACE_LIBRARIES "${dependent_libs}" - # ) - # endif() - - # Commands beyond this point should not need to know the version. - set(CMAKE_IMPORT_FILE_VERSION) -endfunction() - -# Sets up a dummy target -OptiX_add_imported_library(optix "${optix_LIBRARY}" "${optix_DLL}" "${OPENGL_LIBRARIES}") -#OptiX_add_imported_library(optixu "${optixu_LIBRARY}" "${optixu_DLL}" "") -#OptiX_add_imported_library(optix_prime "${optix_prime_LIBRARY}" "${optix_prime_DLL}" "") - -macro(OptiX_check_same_path libA libB) - if(_optix_path_to_${libA}) - if(NOT _optix_path_to_${libA} STREQUAL _optix_path_to_${libB}) - # ${libA} and ${libB} are in different paths. Make sure there isn't a ${libA} next - # to the ${libB}. - get_filename_component(_optix_name_of_${libA} "${${libA}_LIBRARY}" NAME) - if(EXISTS "${_optix_path_to_${libB}}/${_optix_name_of_${libA}}") - message(WARNING " ${libA} library found next to ${libB} library that is not being used. Due to the way we are using rpath, the copy of ${libA} next to ${libB} will be used during loading instead of the one you intended. Consider putting the libraries in the same directory or moving ${_optix_path_to_${libB}}/${_optix_name_of_${libA}} out of the way.") - endif() - endif() - set( _${libA}_rpath "-Wl,-rpath,${_optix_path_to_${libA}}" ) - endif() -endmacro() - -# Since liboptix.1.dylib is built with an install name of @rpath, we need to -# compile our samples with the rpath set to where optix exists. -if(APPLE) - get_filename_component(_optix_path_to_optix "${optix_LIBRARY}" PATH) - if(_optix_path_to_optix) - set( _optix_rpath "-Wl,-rpath,${_optix_path_to_optix}" ) - endif() - get_filename_component(_optix_path_to_optixu "${optixu_LIBRARY}" PATH) - OptiX_check_same_path(optixu optix) - get_filename_component(_optix_path_to_optix_prime "${optix_prime_LIBRARY}" PATH) - OptiX_check_same_path(optix_prime optix) - OptiX_check_same_path(optix_prime optixu) - - set( optix_rpath ${_optix_rpath} ${_optixu_rpath} ${_optix_prime_rpath} ) - list(REMOVE_DUPLICATES optix_rpath) -endif() +add_library(OptiX::OptiX INTERFACE IMPORTED) +target_include_directories(OptiX::OptiX INTERFACE ${OptiX_ROOT_DIR}/include) diff --git a/plugins/optix_hpg/cmake/configure_cuda.cmake b/plugins/optix_hpg/cmake/configure_cuda.cmake deleted file mode 100644 index 27d119ea8a..0000000000 --- a/plugins/optix_hpg/cmake/configure_cuda.cmake +++ /dev/null @@ -1,29 +0,0 @@ -## ======================================================================== ## -## Copyright 2018-2019 Ingo Wald ## -## ## -## 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. ## -## ======================================================================== ## - -if (POLICY CMP0077) - cmake_policy(SET CMP0077 NEW) -endif() - -set(CUDA_USE_STATIC_CUDA_RUNTIME ON) -#find_package(CUDA REQUIRED) -find_package(CUDA) - -if (CUDA_FOUND) - include_directories(${CUDA_TOOLKIT_INCLUDE}) - - set(CUDA_SEPARABLE_COMPILATION ON) -endif() diff --git a/plugins/optix_hpg/cmake/configure_optix.cmake b/plugins/optix_hpg/cmake/configure_optix.cmake index 234046687a..448be001f4 100644 --- a/plugins/optix_hpg/cmake/configure_optix.cmake +++ b/plugins/optix_hpg/cmake/configure_optix.cmake @@ -1,70 +1,42 @@ -# ======================================================================== # -# Copyright 2018-2020 Ingo Wald # -# # -# 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. # -# ======================================================================== # - -#set(CMAKE_MODULE_PATH -# "${CMAKE_CURRENT_SOURCE_DIR}/../cmake" -# ${CMAKE_MODULE_PATH} -# ) - -include(configure_cuda) -#find_package(CUDA REQUIRED) -find_package(OptiX REQUIRED VERSION 7) - -include_directories(${CUDA_TOOLKIT_INCLUDE}) -include_directories(${OptiX_INCLUDE}) - -if (WIN32) - add_definitions(-DNOMINMAX) -endif() - +find_package(OptiX REQUIRED) find_program(BIN2C bin2c DOC "Path to the cuda-sdk bin2c executable.") -# this macro defines cmake rules that execute the following four steps: -# 1) compile the given cuda file ${cuda_file} to an intermediary PTX file -# 2) use the 'bin2c' tool (that comes with CUDA) to -# create a second intermediary (.c-)file which defines a const string variable -# (named '${c_var_name}') whose (constant) value is the PTX output -# from the previous step. -# 3) compile the given .c file to an intermediary object file (why thus has -# that PTX string 'embedded' as a global constant. -# 4) assign the name of the intermediary .o file to the cmake variable -# 'output_var', which can then be added to cmake targets. -macro(cuda_compile_and_embed output_var cuda_file) - set(c_var_name ${output_var}) - if(${CMAKE_BUILD_TYPE} MATCHES "Release") - cuda_compile_ptx(ptx_files - ${cuda_file} - OPTIONS -O3 -DNDEBUG=1 --use_fast_math -arch=compute_${CMAKE_CUDA_ARCHITECTURES} - ) - else() - cuda_compile_ptx(ptx_files - ${cuda_file} - OPTIONS -arch=compute_${CMAKE_CUDA_ARCHITECTURES} - ) +# adapted from https://github.com/owl-project/owl/blob/master/owl/cmake/embed_ptx.cmake +## Copyright 2021 Jefferson Amstutz +## SPDX-License-Identifier: Apache-2.0 +function(embed_ptx) + set(oneArgs OUTPUT_TARGET) + set(multiArgs PTX_LINK_LIBRARIES PTX_INCLUDE_DIRECTORIES SOURCES) + cmake_parse_arguments(EMBED_PTX "" "${oneArgs}" "${multiArgs}" ${ARGN}) + + if (NOT ${NUM_SOURCES} EQUAL 1) + message(FATAL_ERROR + "embed_ptx() can only compile and embed one file at a time.") endif() - list(GET ptx_files 0 ptx_file) - set(embedded_file ${ptx_file}_embedded.c) -# message("adding rule to compile and embed ${cuda_file} to \"const char ${var_name}[];\"") + + set(PTX_TARGET ${EMBED_PTX_OUTPUT_TARGET}_ptx) + + add_library(${PTX_TARGET} OBJECT) + target_sources(${PTX_TARGET} PRIVATE ${EMBED_PTX_SOURCES}) + target_link_libraries(${PTX_TARGET} PRIVATE ${EMBED_PTX_PTX_LINK_LIBRARIES}) + target_include_directories(${PTX_TARGET} PRIVATE ${EMBED_PTX_PTX_INCLUDE_DIRECTORIES}) + set_property(TARGET ${PTX_TARGET} PROPERTY CUDA_PTX_COMPILATION ON) + set_property(TARGET ${PTX_TARGET} PROPERTY CUDA_ARCHITECTURES native) + set_property(TARGET ${PTX_TARGET} PROPERTY CXX_STANDARD 17) + target_compile_options(${PTX_TARGET} PRIVATE $<$:-lineinfo> -diag-suppress 20012) # warning suppressed due to GLM + + set(EMBED_PTX_C_FILE ${CMAKE_CURRENT_BINARY_DIR}/${EMBED_PTX_OUTPUT_TARGET}.c) + get_filename_component(OUTPUT_FILE_NAME ${EMBED_PTX_C_FILE} NAME) add_custom_command( - OUTPUT ${embedded_file} - COMMAND ${BIN2C} -c --padd 0 --type char --name ${c_var_name} ${ptx_file} > ${embedded_file} - DEPENDS ${ptx_file} - COMMENT "compiling (and embedding ptx from) ${cuda_file}" - ) - set(${output_var} ${embedded_file}) -endmacro() + OUTPUT ${EMBED_PTX_C_FILE} + COMMAND ${BIN2C} -c --padd 0 --type char --name ${EMBED_PTX_OUTPUT_TARGET} $ > ${EMBED_PTX_C_FILE} + VERBATIM + DEPENDS $ ${PTX_TARGET} + COMMENT "Generating embedded PTX file: ${OUTPUT_FILE_NAME}" + ) + + add_library(${EMBED_PTX_OUTPUT_TARGET} OBJECT) + target_sources(${EMBED_PTX_OUTPUT_TARGET} PRIVATE ${EMBED_PTX_C_FILE}) +endfunction() diff --git a/plugins/optix_hpg/cuda_resources/mesh.cu b/plugins/optix_hpg/cuda_resources/mesh.cu index 6a91127320..0545bcb379 100644 --- a/plugins/optix_hpg/cuda_resources/mesh.cu +++ b/plugins/optix_hpg/cuda_resources/mesh.cu @@ -50,6 +50,7 @@ namespace optix_hpg { PerRayData& prd = getPerRayData(); const auto& self = getProgramData(); + #if 0 const Ray ray(optixGetWorldRayOrigin(), optixGetWorldRayDirection(), optixGetRayTmin(), optixGetRayTmax()); @@ -78,6 +79,7 @@ namespace optix_hpg { set_depth(prd, ray.tmax); lighting(prd, geo_col, P, ffN); + #endif } MM_OPTIX_CLOSESTHIT_KERNEL(mesh_closesthit_occlusion)() { diff --git a/plugins/optix_hpg/cuda_resources/miss.cu b/plugins/optix_hpg/cuda_resources/miss.cu index eeae439f7d..0e92f5c692 100644 --- a/plugins/optix_hpg/cuda_resources/miss.cu +++ b/plugins/optix_hpg/cuda_resources/miss.cu @@ -10,8 +10,9 @@ namespace optix_hpg { PerRayData& prd = getPerRayData(); const auto& self = getProgramData(); - prd.radiance = glm::vec3(self.bg); - prd.done = true; + /*prd.radiance = glm::vec3(self.bg); + prd.done = true;*/ + prd.albedo = glm::vec3(self.bg); } } // namespace device } // namespace optix_hpg diff --git a/plugins/optix_hpg/cuda_resources/perraydata.h b/plugins/optix_hpg/cuda_resources/perraydata.h index db25b743fa..d4cd661045 100644 --- a/plugins/optix_hpg/cuda_resources/perraydata.h +++ b/plugins/optix_hpg/cuda_resources/perraydata.h @@ -7,6 +7,7 @@ namespace megamol { namespace optix_hpg { namespace device { struct PerRayData { +#if 0 int depth; glm::vec3 radiance; @@ -34,6 +35,14 @@ struct PerRayData { float intensity; OptixTraversableHandle world; +#else + int particleID; + float t; + glm::vec3 pos; + glm::vec3 albedo; + bool countDepth; + float ray_depth; +#endif }; } // namespace device } // namespace optix_hpg diff --git a/plugins/optix_hpg/cuda_resources/raygen.cu b/plugins/optix_hpg/cuda_resources/raygen.cu index 3b127deb51..d3e66e140e 100644 --- a/plugins/optix_hpg/cuda_resources/raygen.cu +++ b/plugins/optix_hpg/cuda_resources/raygen.cu @@ -42,15 +42,51 @@ namespace optix_hpg { // Modified 2021 MegaMol Dev Team // + // code partially from: https://github.com/UniStuttgart-VISUS/rtxpkd_ldav2020 + // ======================================================================== // + // Copyright 2018-2019 Ingo Wald // + // // + // 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. // + // ======================================================================== // + + // ======================================================================== // + // Modified 2019-2020 VISUS - University of Stuttgart // + // ======================================================================== // + + + +//#define RANDVEC3F glm::vec3(rnd(42), rnd(42), rnd(42)) +#define RANDVEC3F glm::vec3(rnd(seed), rnd(seed), rnd(seed)) + + inline __device__ glm::vec3 random_in_unit_sphere(unsigned int& seed) { + glm::vec3 p; + do { + p = 2.0f * RANDVEC3F - glm::vec3(1, 1, 1); + } while (glm::dot(p, p) >= 1.0f); + return p; + } + inline __device__ glm::vec4 traceRay( - const RayGenData& self, Ray& ray /*, Random& rnd*/, PerRayData& prd, glm::vec4& bg, int maxBounces) { + const RayGenData& self, Ray& ray, unsigned int& seed /*, Random& rnd*/, PerRayData& prd, glm::vec4& bg, int maxBounces) { unsigned int p0 = 0; unsigned int p1 = 0; packPointer(&prd, p0, p1); - glm::vec3 col(0.f); + glm::vec3 col(1.f); + + #if 0 for (;;) { prd.wo = -ray.direction; optixTrace(self.world, (const float3&) ray.origin, (const float3&) ray.direction, ray.tmin, ray.tmax, 0, @@ -82,6 +118,42 @@ namespace optix_hpg { return glm::vec4(col.x, col.y, col.z, 1.0f); //return glm::vec4(1, 1, 1, 1.0f); // return glm::vec4(prd.radiance, 1.0f); + #else + for (int depth = 0;true;++depth) { + prd.particleID = -1; + + optixTrace(self.world, (const float3&)ray.origin, (const float3&)ray.direction, ray.tmin, ray.tmax, 0, + (OptixVisibilityMask)-1, + /*rayFlags */ OPTIX_RAY_FLAG_DISABLE_ANYHIT, + /*SBToffset */ 0, + /*SBTstride */ 2, + /*missSBTIndex */ 0, p0, p1); + if (prd.particleID == -1) { + return glm::vec4(col * glm::vec3(0.8f), 1.0f); + } + + glm::vec3 N = (ray.origin + prd.t * ray.direction) - prd.pos; + if (glm::dot(N, ray.direction) > 0.f) + N = -N; + N = glm::normalize(N); + + if (maxBounces == 0) { + return glm::vec4(prd.albedo * (.2f + .6f * fabsf(glm::dot(N, ray.direction))), 1.0f); + } + + col *= prd.albedo; + + if (depth >= maxBounces) + return glm::vec4(0.1f, 0.1f, 0.1f, 1.0f); + + auto scattered_origin = ray.origin + prd.t * ray.direction; + auto scattered_direction = N + random_in_unit_sphere(seed); + ray = Ray(/* origin : */ scattered_origin, + /* direction: */ glm::normalize(scattered_direction), + /* tmin : */ 1e-3f, + /* tmax : */ 1e+8f); + } + #endif } MM_OPTIX_RAYGEN_KERNEL(raygen_program)() { @@ -94,7 +166,7 @@ namespace optix_hpg { return; if (pixelID.y >= self.fbSize.y) return; - const int pixelIdx = pixelID.x + self.fbSize.x * pixelID.y; + //const int pixelIdx = pixelID.x + self.fbSize.x * pixelID.y; const FrameState* fs = &self.frameStateBuffer[0]; @@ -119,6 +191,7 @@ namespace optix_hpg { float depth = FLT_MAX; + #if 0 do { PerRayData prd; @@ -160,6 +233,20 @@ namespace optix_hpg { depth = fminf(depth, prd.ray_depth); } while (--i); + #else + PerRayData prd; + do { + prd.countDepth = true; + prd.ray_depth = FLT_MAX; + float u = -fs->rw + (fs->rw + fs->rw) * float(pixelID.x + rnd(seed)) / self.fbSize.x; + float v = -(fs->th + (-fs->th - fs->th) * float(pixelID.y + rnd(seed)) / self.fbSize.y); + /*float u = -fs->rw + (fs->rw + fs->rw) * float(pixelID.x) / self.fbSize.x; + float v = -(fs->th + (-fs->th - fs->th) * float(pixelID.y) / self.fbSize.y);*/ + auto ray = generateRay(*fs, u, v); + col += traceRay(self, ray, seed /*, rnd*/, prd, bg, fs->maxBounces); + depth = fminf(depth, prd.ray_depth); + } while (--i); + #endif col /= (float) fs->samplesPerPixel; // col.w = frame_idx + 1; //++col.w; @@ -172,18 +259,21 @@ namespace optix_hpg { // col.w = frame_idx + 1; } - surf2Dwrite(make_float4(col.r, col.g, col.b, col.a), self.col_surf, pixelID.x * sizeof(float4), pixelID.y, - cudaBoundaryModeZero); - /*surf2Dwrite(make_float4(1, 1, 1, 1), self.col_surf, pixelID.x * sizeof(float4), pixelID.y, - cudaBoundaryModeZero);*/ - if (depth < FLT_MAX) { depth = (fs->depth_params.z / depth) - (fs->depth_params.x); depth = 0.5f * (depth + 1.0f); } else { depth = 1.f; + col = bg; } surf2Dwrite(depth, self.depth_surf, pixelID.x * sizeof(float), pixelID.y, cudaBoundaryModeZero); + + surf2Dwrite(make_float4(col.r, col.g, col.b, col.a), self.col_surf, pixelID.x * sizeof(float4), pixelID.y, + cudaBoundaryModeZero); + /*surf2Dwrite(make_float4(1, 1, 1, 1), self.col_surf, pixelID.x * sizeof(float4), pixelID.y, + cudaBoundaryModeZero);*/ + + //surf2Dwrite(1, self.depth_surf, pixelID.x * sizeof(float), pixelID.y, cudaBoundaryModeZero); } } // namespace device diff --git a/plugins/optix_hpg/cuda_resources/sphere.cuh b/plugins/optix_hpg/cuda_resources/sphere.cuh index 222ca746cf..0663eda575 100644 --- a/plugins/optix_hpg/cuda_resources/sphere.cuh +++ b/plugins/optix_hpg/cuda_resources/sphere.cuh @@ -1,5 +1,7 @@ #pragma once +#include "sphere.h" + #include #include @@ -94,9 +96,10 @@ namespace optix_hpg { PerRayData& prd = getPerRayData(); /*prd.primID = primID; prd.t = optixGetRayTmax();*/ - const auto& self = getProgramData(); +#if 0 + Ray ray(optixGetWorldRayOrigin(), optixGetWorldRayDirection(), optixGetRayTmin(), optixGetRayTmax()); @@ -113,6 +116,18 @@ namespace optix_hpg { set_depth(prd, ray.tmax); lighting(prd, geo_col, P, ffN); +#else + prd.particleID = primID; + const Particle& particle = self.particleBufferPtr[primID]; + prd.pos = glm::vec3(particle.pos); + glm::vec3 geo_col = glm::vec3(self.globalColor); + if (self.hasColorData) { + geo_col = glm::vec3(self.colorBufferPtr[primID]); + } + prd.albedo = geo_col; + prd.t = optixGetRayTmax(); + set_depth(prd, optixGetRayTmax()); +#endif } inline __device__ void kernel_bounds(const void* geomData, box3f& primBounds, const unsigned int primID) { diff --git a/plugins/optix_hpg/src/CUDAToGL.h b/plugins/optix_hpg/src/CUDAToGL.h index 47356510ec..70df5bd6a4 100644 --- a/plugins/optix_hpg/src/CUDAToGL.h +++ b/plugins/optix_hpg/src/CUDAToGL.h @@ -1,5 +1,7 @@ #pragma once +#ifdef MEGAMOL_USE_OPENGL + #include #include @@ -13,6 +15,11 @@ #include "mmstd_gl/renderer/ContextToGL.h" #include "optix/Utils.h" +#ifdef MEGAMOL_USE_TRACY +#include +#include +#endif + namespace megamol::optix_hpg { inline constexpr char cudatogl_name[] = "CUDAToGL"; @@ -88,6 +95,10 @@ inline constexpr auto cuda_to_gl_init_func = [](std::shared_ptr& shader, std::shared_ptr& lhs_fbo, std::shared_ptr& fbo, int width, int height) -> void { +#ifdef MEGAMOL_USE_TRACY + ZoneScopedN("CUDAToGL::Blit"); + TracyGpuZone("CUDAToGL::Blit"); +#endif CUDA_CHECK_ERROR(cuGraphicsUnmapResources(1, &fbo->data.col_tex_ref, fbo->data.exec_stream)); CUDA_CHECK_ERROR(cuGraphicsUnmapResources(1, &fbo->data.depth_tex_ref, fbo->data.exec_stream)); @@ -100,4 +111,7 @@ inline constexpr auto cuda_to_gl_ren_func = [](std::shared_ptr; + } // namespace megamol::optix_hpg + +#endif diff --git a/plugins/optix_hpg/src/optix/AbstractRenderer.cpp b/plugins/optix_hpg/src/optix/AbstractRenderer.cpp index d3e1829ab9..e8fcf5ed61 100644 --- a/plugins/optix_hpg/src/optix/AbstractRenderer.cpp +++ b/plugins/optix_hpg/src/optix/AbstractRenderer.cpp @@ -2,6 +2,10 @@ #include "optix/CallGeometry.h" +#ifdef MEGAMOL_USE_TRACY +#include +#endif + megamol::optix_hpg::AbstractRenderer::AbstractRenderer() : in_geo_slot_("inGeo", "") { in_geo_slot_.SetCompatibleCall(); @@ -21,6 +25,9 @@ megamol::optix_hpg::AbstractRenderer::~AbstractRenderer() { bool megamol::optix_hpg::AbstractRenderer::Render(CallRender3DCUDA& call) { +#ifdef MEGAMOL_USE_TRACY + ZoneScopedN("AbstractRenderer::Render"); +#endif auto const viewport = glm::uvec2(call.GetFramebuffer()->width, call.GetFramebuffer()->height); call.GetFramebuffer()->data.exec_stream = optix_ctx_->GetExecStream(); diff --git a/plugins/optix_hpg/src/optix/AbstractRenderer.h b/plugins/optix_hpg/src/optix/AbstractRenderer.h index 75cfa49c6c..dcb54825a2 100644 --- a/plugins/optix_hpg/src/optix/AbstractRenderer.h +++ b/plugins/optix_hpg/src/optix/AbstractRenderer.h @@ -86,7 +86,7 @@ class AbstractRenderer : public core::view::RendererModule> const& names) { simple_log<2048> log; - +#if OPTIX_VERSION < 80000 OPTIX_CHECK_ERROR(optixModuleCreateFromPTX( ctx, module_options, pipeline_options, ptx_code, std::strlen(ptx_code), log, log, &module_)); +#else + OPTIX_CHECK_ERROR( + optixModuleCreate(ctx, module_options, pipeline_options, ptx_code, std::strlen(ptx_code), log, log, &module_)); +#endif #if DEBUG if (log.get_log_size() > 1) { core::utility::log::Log::DefaultLog.WriteInfo("[MMOptixModule] Optix Module creation info: %s", log.read()); @@ -196,8 +200,13 @@ megamol::optix_hpg::MMOptixModule::MMOptixModule(const char* ptx_code, OptixDevi std::vector> const& names) { simple_log<2048> log; +#if OPTIX_VERSION < 80000 OPTIX_CHECK_ERROR(optixModuleCreateFromPTX( ctx, module_options, pipeline_options, ptx_code, std::strlen(ptx_code), log, log, &module_)); +#else + OPTIX_CHECK_ERROR( + optixModuleCreate(ctx, module_options, pipeline_options, ptx_code, std::strlen(ptx_code), log, log, &module_)); +#endif #if DEBUG if (log.get_log_size() > 1) { core::utility::log::Log::DefaultLog.WriteInfo("[MMOptixModule] Optix Module creation info: %s", log.read()); @@ -253,10 +262,15 @@ void megamol::optix_hpg::MMOptixModule::ComputeBounds( glm::vec3 blockDims(32, 32, 1); uint32_t threadsPerBlock = blockDims.x * blockDims.y * blockDims.z; - uint32_t numBlocks = (num_elements + threadsPerBlock - 1) / threadsPerBlock; + /*uint32_t numBlocks = (num_elements + threadsPerBlock - 1) / threadsPerBlock; + uint32_t numBlocks_x = 1 + uint32_t(powf((float)numBlocks, 1.f / 3.f)); + uint32_t numBlocks_y = 1 + uint32_t(sqrtf((float)(numBlocks / numBlocks_x))); + uint32_t numBlocks_z = (numBlocks + numBlocks_x * numBlocks_y - 1) / numBlocks_x * numBlocks_y;*/ + + uint32_t numBlocks = std::ceilf(static_cast(num_elements) / static_cast(threadsPerBlock)); uint32_t numBlocks_x = 1 + uint32_t(powf((float) numBlocks, 1.f / 3.f)); uint32_t numBlocks_y = 1 + uint32_t(sqrtf((float) (numBlocks / numBlocks_x))); - uint32_t numBlocks_z = (numBlocks + numBlocks_x * numBlocks_y - 1) / numBlocks_x * numBlocks_y; + uint32_t numBlocks_z = std::ceilf(static_cast(numBlocks) / static_cast(numBlocks_x * numBlocks_y)); glm::uvec3 gridDims(numBlocks_x, numBlocks_y, numBlocks_z); diff --git a/plugins/optix_hpg/src/optix/Renderer.cpp b/plugins/optix_hpg/src/optix/Renderer.cpp index 92cb7570a5..1d8a09b1dc 100644 --- a/plugins/optix_hpg/src/optix/Renderer.cpp +++ b/plugins/optix_hpg/src/optix/Renderer.cpp @@ -143,9 +143,15 @@ void megamol::optix_hpg::Renderer::on_change_programs(std::tupleGetExecStream()); + if (pipeline) { + OPTIX_CHECK_ERROR(optixPipelineDestroy(pipeline)); + } OPTIX_CHECK_ERROR(optixPipelineCreate(optix_ctx->GetOptiXContext(), &optix_ctx->GetPipelineCompileOptions(), &optix_ctx->GetPipelineLinkOptions(), groups.data(), groups.size(), log.data(), &log_size, &pipeline)); + + core::utility::log::Log::DefaultLog.WriteInfo("[OptiX]: {}", log); } diff --git a/plugins/optix_hpg/src/optix/Renderer.h b/plugins/optix_hpg/src/optix/Renderer.h index 43a376b892..bace7fcf6f 100644 --- a/plugins/optix_hpg/src/optix/Renderer.h +++ b/plugins/optix_hpg/src/optix/Renderer.h @@ -5,7 +5,7 @@ #include #include -#include +//#include #include "AbstractRenderer.h" #include "CUDA_Context.h" diff --git a/plugins/optix_hpg/src/optix/SphereGeometry.cpp b/plugins/optix_hpg/src/optix/SphereGeometry.cpp index a865383dea..23bb795fbf 100644 --- a/plugins/optix_hpg/src/optix/SphereGeometry.cpp +++ b/plugins/optix_hpg/src/optix/SphereGeometry.cpp @@ -12,7 +12,7 @@ namespace megamol::optix_hpg { extern "C" const char embedded_sphere_programs[]; -extern "C" const char embedded_sphere_occlusion_programs[]; +//extern "C" const char embedded_sphere_occlusion_programs[]; } // namespace megamol::optix_hpg @@ -62,12 +62,12 @@ void megamol::optix_hpg::SphereGeometry::init(Context const& ctx) { {{MMOptixModule::MMOptixNameKind::MMOPTIX_NAME_INTERSECTION, "sphere_intersect"}, {MMOptixModule::MMOptixNameKind::MMOPTIX_NAME_CLOSESTHIT, "sphere_closesthit"}, {MMOptixModule::MMOptixNameKind::MMOPTIX_NAME_BOUNDS, "sphere_bounds"}}); - sphere_occlusion_module_ = MMOptixModule(embedded_sphere_occlusion_programs, ctx.GetOptiXContext(), + /*sphere_occlusion_module_ = MMOptixModule(embedded_sphere_occlusion_programs, ctx.GetOptiXContext(), &ctx.GetModuleCompileOptions(), &ctx.GetPipelineCompileOptions(), MMOptixModule::MMOptixProgramGroupKind::MMOPTIX_PROGRAM_GROUP_KIND_HITGROUP, {{MMOptixModule::MMOptixNameKind::MMOPTIX_NAME_INTERSECTION, "sphere_intersect"}, {MMOptixModule::MMOptixNameKind::MMOPTIX_NAME_CLOSESTHIT, "sphere_closesthit_occlusion"}, - {MMOptixModule::MMOptixNameKind::MMOPTIX_NAME_BOUNDS, "sphere_bounds_occlusion"}}); + {MMOptixModule::MMOptixNameKind::MMOPTIX_NAME_BOUNDS, "sphere_bounds_occlusion"}});*/ ++program_version; @@ -79,10 +79,10 @@ bool megamol::optix_hpg::SphereGeometry::assertData(geocalls::MultiParticleDataC auto const pl_count = call.GetParticleListCount(); for (auto const& el : particle_data_) { - CUDA_CHECK_ERROR(cuMemFree(el)); + CUDA_CHECK_ERROR(cuMemFreeAsync(el, ctx.GetExecStream())); } for (auto const& el : color_data_) { - CUDA_CHECK_ERROR(cuMemFree(el)); + CUDA_CHECK_ERROR(cuMemFreeAsync(el, ctx.GetExecStream())); } particle_data_.resize(pl_count, 0); @@ -135,16 +135,17 @@ bool megamol::optix_hpg::SphereGeometry::assertData(geocalls::MultiParticleDataC color_data[p_idx].a = ca_acc->Get_f(p_idx); } // CUDA_CHECK_ERROR(cuMemFree(color_data_)); - CUDA_CHECK_ERROR(cuMemAlloc(&color_data_[pl_idx], col_count * sizeof(glm::vec4))); + CUDA_CHECK_ERROR(cuMemAllocAsync(&color_data_[pl_idx], col_count * sizeof(glm::vec4), ctx.GetExecStream())); CUDA_CHECK_ERROR(cuMemcpyHtoDAsync( color_data_[pl_idx], color_data.data(), col_count * sizeof(glm::vec4), ctx.GetExecStream())); } // CUDA_CHECK_ERROR(cuMemFree(_particle_data)); - CUDA_CHECK_ERROR(cuMemAlloc(&particle_data_[pl_idx], p_count * sizeof(device::Particle))); + CUDA_CHECK_ERROR( + cuMemAllocAsync(&particle_data_[pl_idx], p_count * sizeof(device::Particle), ctx.GetExecStream())); CUDA_CHECK_ERROR(cuMemcpyHtoDAsync( particle_data_[pl_idx], data.data(), p_count * sizeof(device::Particle), ctx.GetExecStream())); - CUDA_CHECK_ERROR(cuMemAlloc(&bounds_data[pl_idx], p_count * sizeof(box3f))); + CUDA_CHECK_ERROR(cuMemAllocAsync(&bounds_data[pl_idx], p_count * sizeof(box3f), ctx.GetExecStream())); sphere_module_.ComputeBounds(particle_data_[pl_idx], bounds_data[pl_idx], p_count, ctx.GetExecStream()); @@ -185,10 +186,10 @@ bool megamol::optix_hpg::SphereGeometry::assertData(geocalls::MultiParticleDataC sbt_records_.push_back(sbt_record); // occlusion stuff - SBTRecord sbt_record_occlusion; + /*SBTRecord sbt_record_occlusion; OPTIX_CHECK_ERROR(optixSbtRecordPackHeader(sphere_occlusion_module_, &sbt_record_occlusion)); sbt_record_occlusion.data = sbt_record.data; - sbt_records_.push_back(sbt_record_occlusion); + sbt_records_.push_back(sbt_record_occlusion);*/ ++sbt_version; } @@ -203,19 +204,19 @@ bool megamol::optix_hpg::SphereGeometry::assertData(geocalls::MultiParticleDataC ctx.GetOptiXContext(), &accelOptions, build_inputs.data(), build_inputs.size(), &bufferSizes)); CUdeviceptr geo_temp; - CUDA_CHECK_ERROR(cuMemFree(_geo_buffer)); - CUDA_CHECK_ERROR(cuMemAlloc(&_geo_buffer, bufferSizes.outputSizeInBytes)); - CUDA_CHECK_ERROR(cuMemAlloc(&geo_temp, bufferSizes.tempSizeInBytes)); + CUDA_CHECK_ERROR(cuMemFreeAsync(_geo_buffer, ctx.GetExecStream())); + CUDA_CHECK_ERROR(cuMemAllocAsync(&_geo_buffer, bufferSizes.outputSizeInBytes, ctx.GetExecStream())); + CUDA_CHECK_ERROR(cuMemAllocAsync(&geo_temp, bufferSizes.tempSizeInBytes, ctx.GetExecStream())); OptixTraversableHandle geo_handle = 0; OPTIX_CHECK_ERROR(optixAccelBuild(ctx.GetOptiXContext(), ctx.GetExecStream(), &accelOptions, build_inputs.data(), build_inputs.size(), geo_temp, bufferSizes.tempSizeInBytes, _geo_buffer, bufferSizes.outputSizeInBytes, &_geo_handle, nullptr, 0)); - CUDA_CHECK_ERROR(cuMemFree(geo_temp)); + CUDA_CHECK_ERROR(cuMemFreeAsync(geo_temp, ctx.GetExecStream())); // CUDA_CHECK_ERROR(cuMemFree(bounds_data)); for (auto const& el : bounds_data) { - CUDA_CHECK_ERROR(cuMemFree(el)); + CUDA_CHECK_ERROR(cuMemFreeAsync(el, ctx.GetExecStream())); } ////////////////////////////////////// @@ -256,7 +257,7 @@ bool megamol::optix_hpg::SphereGeometry::get_data_cb(core::Call& c) { } program_groups_[0] = sphere_module_; - program_groups_[1] = sphere_occlusion_module_; + //program_groups_[1] = sphere_occlusion_module_; out_geo->set_handle(&_geo_handle); out_geo->set_program_groups(program_groups_.data(), program_groups_.size(), program_version); diff --git a/plugins/optix_hpg/src/optix/SphereGeometry.h b/plugins/optix_hpg/src/optix/SphereGeometry.h index 4bc606ea38..30186e131e 100644 --- a/plugins/optix_hpg/src/optix/SphereGeometry.h +++ b/plugins/optix_hpg/src/optix/SphereGeometry.h @@ -57,11 +57,11 @@ class SphereGeometry : public core::Module { MMOptixModule sphere_module_; - MMOptixModule sphere_occlusion_module_; + //MMOptixModule sphere_occlusion_module_; std::vector> sbt_records_; - std::array program_groups_; + std::array program_groups_; CUdeviceptr _geo_buffer = 0; diff --git a/plugins/optix_hpg/src/optix/utils_device.h b/plugins/optix_hpg/src/optix/utils_device.h index 9e404a85e7..468aee2687 100644 --- a/plugins/optix_hpg/src/optix/utils_device.h +++ b/plugins/optix_hpg/src/optix/utils_device.h @@ -215,10 +215,14 @@ inline __device__ float CosineHemispherePdf(float cosTheta) { inline __device__ void set_depth(PerRayData& prd, float depth) { +#if 1 if (prd.countDepth) { prd.ray_depth = depth; prd.countDepth = false; } +#else + prd.ray_depth = depth; +#endif } // OptiX SDK @@ -255,7 +259,7 @@ inline __device__ void set_depth(PerRayData& prd, float depth) { // // Modified 2021 MegaMol Dev Team // - +#if 0 inline __device__ void lighting(PerRayData& prd, glm::vec3 const& geo_col, glm::vec3 const& P, glm::vec3 const& ffN) { if (prd.countEmitted) prd.emitted = geo_col * 0.2f; @@ -304,12 +308,13 @@ inline __device__ void lighting(PerRayData& prd, glm::vec3 const& geo_col, glm:: OPTIX_RAY_FLAG_TERMINATE_ON_FIRST_HIT, 1, 2, 1, occluded); if (!occluded) { - weight = nDl /* LnDl*/ / (MMO_PI * Ldist * Ldist); + weight = nDl /* LnDl*/; // / (MMO_PI * Ldist * Ldist); } } prd.radiance += glm::vec3(prd.intensity) * weight; } +#endif } // namespace device diff --git a/plugins/optix_hpg/src/optix/utils_host.h b/plugins/optix_hpg/src/optix/utils_host.h index b9ae63c19b..55ddcd7b8e 100644 --- a/plugins/optix_hpg/src/optix/utils_host.h +++ b/plugins/optix_hpg/src/optix/utils_host.h @@ -56,6 +56,6 @@ typedef struct RayH { glm::vec3 direction; float tMin; float tMax; -}; +} RayH; } // namespace optix_hpg } // namespace megamol diff --git a/plugins/optix_hpg/src/optix_hpg.cpp b/plugins/optix_hpg/src/optix_hpg.cpp index c1480aa86d..0869287400 100644 --- a/plugins/optix_hpg/src/optix_hpg.cpp +++ b/plugins/optix_hpg/src/optix_hpg.cpp @@ -34,7 +34,9 @@ class OptixHpgPluginInstance : public megamol::core::factories::AbstractPluginIn this->module_descriptions.RegisterAutoDescription(); this->module_descriptions.RegisterAutoDescription(); this->module_descriptions.RegisterAutoDescription(); +#ifdef MEGAMOL_USE_OPENGL this->module_descriptions.RegisterAutoDescription(); +#endif // register calls this->call_descriptions.RegisterAutoDescription(); diff --git a/plugins/protein_cuda/CMakeLists.txt b/plugins/protein_cuda/CMakeLists.txt index fa047f6ef2..89e03ef358 100644 --- a/plugins/protein_cuda/CMakeLists.txt +++ b/plugins/protein_cuda/CMakeLists.txt @@ -21,15 +21,15 @@ if (protein_cuda_PLUGIN_ENABLED) message(FATAL_ERROR "Protein CUDA currently only supports CUDA Version 11 or above. The version found was CUDA ${CMAKE_CUDA_COMPILER_VERSION}. Please turn off the Protein CUDA plugin or install a correct version of CUDA.") endif () - set(CMAKE_CUDA_ARCHITECTURES 52) - set(CMAKE_CUDA_FLAGS_RELEASE "${CMAKE_CUDA_FLAGS} -O3") + set_target_properties(protein_cuda PROPERTIES CUDA_ARCHITECTURES 52) + target_compile_options(protein_cuda PRIVATE $<$,$>:-O3>) get_filename_component(CUDA_COMPILER_DIRECTORY "${CMAKE_CUDA_COMPILER}" DIRECTORY) get_filename_component(CUDA_BIN_DIR ${CMAKE_CUDA_COMPILER} DIRECTORY) # Suppress CUDA warnings if (SUPPRESS_CUDA_WARNINGS) - set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -Xcompiler \"-w\"") + target_compile_options(protein_cuda PRIVATE $<$,$>:-Xcompiler -w>) endif () # Collect source files diff --git a/vcpkg.json b/vcpkg.json index 3efa7fdaf5..a5e5d7bce5 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -61,6 +61,10 @@ "tinygltf", "tinyobjloader", "tinyply", + { + "name": "wil", + "platform": "windows" + }, "zeromq", "zfp", "zlib" @@ -153,6 +157,7 @@ "parquet" ] }, + "dataversepp", "power-overwhelming" ] }, @@ -192,6 +197,9 @@ } ] }, + "tracy-time-plot": { + "description": "Custom Tracy timed plotting capabilities" + }, "vr-interop": { "description": "Use VR Interop", "dependencies": [