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 85d53c7582..478a19c471 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..c0e202ef84 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,12 +176,20 @@ 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; + + 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; + } else if (vtProp.vt == VARENUM::VT_I4) { + ret = std::to_string(vtProp.lVal); + } else { + throw std::runtime_error("WMIUtil: Unsupported ret type"); + } + VariantClear(&vtProp); pclsObj->Release(); diff --git a/frontend/main/src/main.cpp b/frontend/main/src/main.cpp index 7f2eaba399..abefae8dcb 100644 --- a/frontend/main/src/main.cpp +++ b/frontend/main/src/main.cpp @@ -19,6 +19,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" @@ -69,6 +70,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"; @@ -193,6 +197,7 @@ int main(const int argc, const char** argv) { // clang-format on bool run_megamol = true; megamol::frontend::FrontendServiceCollection services; + services.add(ri_service, nullptr); if (with_gl) { services.add(gl_service, &openglConfig); } 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/services/CMakeLists.txt b/frontend/services/CMakeLists.txt index 3d314937e1..05fab7d89d 100644 --- a/frontend/services/CMakeLists.txt +++ b/frontend/services/CMakeLists.txt @@ -32,6 +32,7 @@ file(GLOB_RECURSE header_files RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "remote_service/*.hpp" "profiling_service/*.hpp" "vr_service/*.hpp" + "runtimeinfo_service/*.hpp" # "service_template/*.hpp" ) @@ -48,6 +49,7 @@ file(GLOB_RECURSE source_files RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "remote_service/*.cpp" "profiling_service/*.cpp" "vr_service/*.cpp" + "runtimeinfo_service/*.cpp" # "service_template/*.cpp" ) @@ -109,6 +111,7 @@ target_include_directories(${PROJECT_NAME} PUBLIC "gui/3rd" "gui/src" "vr_service" + "runtimeinfo_service" # "service_template" ) 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 b7aebf8699..166c6bad30 100644 --- a/frontend/services/screenshot_service/Screenshot_Service.cpp +++ b/frontend/services/screenshot_service/Screenshot_Service.cpp @@ -69,8 +69,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 @@ -105,7 +105,7 @@ static bool write_png_to_file( if (guistate_resources_ptr) { project.append(guistate_resources_ptr->request_gui_state(true)); } - megamol::core::utility::graphics::ScreenShotComments ssc(project); + megamol::core::utility::graphics::ScreenShotComments ssc(project, ri); 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 */, @@ -172,8 +172,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 { @@ -194,30 +195,30 @@ bool Screenshot_Service::init(void* configPtr) { 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::MegaMolGraph_Req_Name, "optional", "RuntimeConfig", "optional", + "RuntimeInfo"}; + screenshot_show_privacy_note = config.show_privacy_note; + + log("initialized successfully"); + return true; +} + +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_u8string()); - return m_toFileWriter_resource.write_screenshot(m_frontbufferSource_resource, filename); + return m_toFileWriter_resource.write_screenshot(m_frontbufferSource_resource, filename, ri_); }; - screenshot_show_privacy_note = config.show_privacy_note; - this->m_imagewrapperToPNG_trigger = [&](megamol::frontend_resources::ImageWrapper const& image, std::filesystem::path const& filename) -> bool { log("write screenshot to " + filename.generic_u8string()); return m_toFileWriter_resource.write_screenshot( - megamol::frontend_resources::ImageWrapperScreenshotSource(image), filename); + megamol::frontend_resources::ImageWrapperScreenshotSource(image), filename, ri_); }; - log("initialized successfully"); - return true; -} - -void Screenshot_Service::close() {} - -std::vector& Screenshot_Service::getProvidedResources() { this->m_providedResourceReferences = {{"GLScreenshotSource", m_frontbufferSource_resource}, {"ImageDataToPNGWriter", m_toFileWriter_resource}, {"GLFrontbufferToPNG_ScreenshotTrigger", m_frontbufferToPNG_trigger}, @@ -247,6 +248,8 @@ void Screenshot_Service::setRequestedResources(std::vector res gui_window_request_resource.register_notification( "Screenshot", std::weak_ptr(service_open_popup), privacy_note); } + + ri_ = &resources[5].getResource(); } void Screenshot_Service::updateProvidedResources() {} diff --git a/frontend/services/screenshot_service/Screenshot_Service.hpp b/frontend/services/screenshot_service/Screenshot_Service.hpp index 33fe58197c..c81b1d22a8 100644 --- a/frontend/services/screenshot_service/Screenshot_Service.hpp +++ b/frontend/services/screenshot_service/Screenshot_Service.hpp @@ -11,6 +11,8 @@ // ImageData struct and interfaces for screenshot sources/writers #include "Screenshots.h" +#include "RuntimeInfo.h" + namespace megamol::frontend { class Screenshot_Service final : public AbstractFrontendService { @@ -66,6 +68,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 64239ac0c5..e840b323ec 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 73415b317f..40048fb93b 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/vcpkg.json b/vcpkg.json index bb25792267..8630951085 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -57,6 +57,10 @@ "tinygltf", "tinyobjloader", "tinyply", + { + "name": "wil", + "platform": "windows" + }, "zeromq", "zfp", "zlib"