From 471f8a679dd1ed22e069b116a231522fbb111ba2 Mon Sep 17 00:00:00 2001 From: Piotr Rybicki Date: Fri, 2 Jun 2023 14:11:55 +0200 Subject: [PATCH] Add an API call to get extension info (#151) * Add API Call * Implement a tool to inspect rgl version and extensions. * Enable tools builing on Windows * Add hint to error 126 * Fix ros2 standalone to be interpreted as ros2 extension * Apply review changes --------- Co-authored-by: Mateusz Szczygielski --- CMakeLists.txt | 12 +++- README.md | 1 + include/rgl/api/core.h | 23 +++++- setup.py | 3 - src/RGLFields.hpp | 2 +- src/Tape.cpp | 5 +- src/Tape.hpp | 6 +- src/Time.hpp | 4 +- src/api/apiCommon.hpp | 4 +- src/api/apiCore.cpp | 27 +++++++ test/src/graphTest.cpp | 16 ++--- test/src/tapeSurfaceTest.cpp | 8 +-- tools/CMakeLists.txt | 17 +++-- tools/inspectLibRGL.cpp | 132 +++++++++++++++++++++++++++++++++++ 14 files changed, 226 insertions(+), 34 deletions(-) create mode 100644 tools/inspectLibRGL.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c7f0d9b9..2ec2bccd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,10 @@ set(RGL_BUILD_ROS2_EXTENSION OFF CACHE BOOL set(RGL_BUILD_ROS2_EXTENSION_STANDALONE OFF CACHE BOOL "Enables building ROS2 extension in standalone mode. It requires installed and sourced ROS2.") +if (RGL_BUILD_ROS2_EXTENSION_STANDALONE) + set(RGL_BUILD_ROS2_EXTENSION ON) +endif() + # Hide automatically generated CTest targets set_property(GLOBAL PROPERTY CTEST_TARGETS_ADDED 1) @@ -99,6 +103,10 @@ add_library(RobotecGPULidar SHARED set_property(TARGET RobotecGPULidar PROPERTY POSITION_INDEPENDENT_CODE ON) +target_compile_definitions(RobotecGPULidar PUBLIC RGL_BUILD_PCL_EXTENSION=$) +target_compile_definitions(RobotecGPULidar PUBLIC RGL_BUILD_ROS2_EXTENSION=$) +target_compile_definitions(RobotecGPULidar PUBLIC RGL_BUILD_UDP_EXTENSION=0) + if (RGL_BUILD_PCL_EXTENSION) find_package(PCL 1.12 CONFIG REQUIRED COMPONENTS common io filters visualization) target_sources(RobotecGPULidar PRIVATE @@ -109,10 +117,9 @@ if (RGL_BUILD_PCL_EXTENSION) target_include_directories(RobotecGPULidar PUBLIC ${PCL_INCLUDE_DIRS}) target_link_directories(RobotecGPULidar PRIVATE ${PCL_LIBRARY_DIRS}) target_link_libraries(RobotecGPULidar PRIVATE ${PCL_LIBRARIES}) - target_compile_definitions(RobotecGPULidar PUBLIC RGL_BUILD_PCL_EXTENSION=$ ${PCL_DEFINITIONS}) endif() -if (RGL_BUILD_ROS2_EXTENSION OR RGL_BUILD_ROS2_EXTENSION_STANDALONE) +if (RGL_BUILD_ROS2_EXTENSION) if (NOT $ENV{ROS_DISTRO} STREQUAL "humble") message(FATAL_ERROR "ROS $ENV{ROS_DISTRO} not supported. Only humble is available.") endif() @@ -121,7 +128,6 @@ if (RGL_BUILD_ROS2_EXTENSION OR RGL_BUILD_ROS2_EXTENSION_STANDALONE) target_sources(RobotecGPULidar PRIVATE src/api/apiRos2.cpp src/graph/Ros2PublishPointsNode.cpp) target_include_directories(RobotecGPULidar PUBLIC ${rclcpp_INCLUDE_DIRS} ${sensor_msgs_INCLUDE_DIRS}) target_link_libraries(RobotecGPULidar PRIVATE ${rclcpp_LIBRARIES} ${sensor_msgs_LIBRARIES}) - target_compile_definitions(RobotecGPULidar PUBLIC RGL_BUILD_ROS2_EXTENSION=$) endif() target_include_directories(RobotecGPULidar diff --git a/README.md b/README.md index 6e78ce4a..7474e4ca 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ An introduction to the RGL API along with an example can be found [here](docs/Us `RobotecGPULidar` library can be built with extensions enhancing RGL with additional functions: - `PCL` - adds nodes and functions for point cloud processing that uses [Point Cloud Library](https://pointclouds.org/). See [documentation](docs/PclExtension.md). - `ROS2` - adds a node to publish point cloud messages to [ROS2](https://www.ros.org/). Check [ROS2 extension doc](docs/Ros2Extension.md) for more information, build instructions, and usage. +- `UDP` - adds a node to publish raw lidar packets, as emitted by physical lidar. Only available in the closed-source version. ## Building in Docker (Linux) diff --git a/include/rgl/api/core.h b/include/rgl/api/core.h index 710c6fec..cdc53928 100644 --- a/include/rgl/api/core.h +++ b/include/rgl/api/core.h @@ -97,6 +97,19 @@ typedef struct Node *rgl_node_t; */ typedef struct Scene *rgl_scene_t; +/** + * Enumerates available extensions in RGL which can be queried using `rgl_get_extension_info`. + * For more details, see the chapter on extensions in the README. + * The order of constants must not be changed. + */ +typedef enum : int32_t +{ + RGL_EXTENSION_PCL = 0, + RGL_EXTENSION_ROS2 = 1, + RGL_EXTENSION_UDP = 2, + RGL_EXTENSION_COUNT +} rgl_extension_t; + /** * Status (error) codes returned by all RGL API functions. * Unrecoverable errors require reloading the library (restarting the application). @@ -242,6 +255,15 @@ typedef enum RGL_API rgl_status_t rgl_get_version_info(int32_t *out_major, int32_t *out_minor, int32_t *out_patch); +/** + * As stated in README, some RGL features (extensions) are opt-in in compile-time. + * This call can be used to query in runtime if specific extensions were compiled in the binary. + * @param extension Extension to query. + * @param out_available Pointer to the result. Pointee is set to non-zero value if the extension is available. + */ +RGL_API rgl_status_t +rgl_get_extension_info(rgl_extension_t extension, int32_t* out_available); + /** * Optionally (re)configures internal logging. This feature may be useful for debugging / issue reporting. * By default (i.e. not calling `rgl_configure_logging`) is equivalent to the following call: @@ -271,7 +293,6 @@ rgl_get_last_error_string(const char **out_error_string); RGL_API rgl_status_t rgl_cleanup(void); - /******************************** MESH ********************************/ /** diff --git a/setup.py b/setup.py index f8399bcf..e6c372da 100755 --- a/setup.py +++ b/setup.py @@ -132,9 +132,6 @@ def is_cuda_version_ok(): linker_rpath_flags.append(f"-Wl,-rpath={rpath}") cmake_args.append(f"-DCMAKE_SHARED_LINKER_FLAGS=\"{' '.join(linker_rpath_flags)}\"") - if on_windows(): - cmake_args.append("-DRGL_BUILD_TOOLS=OFF") # Tools are not available on Windows - cmake_args.append(f"-DCMAKE_INSTALL_PREFIX={os.path.join(os.getcwd(), args.build_dir)}") # Append user args, possibly overwriting diff --git a/src/RGLFields.hpp b/src/RGLFields.hpp index 467b0930..883790d1 100644 --- a/src/RGLFields.hpp +++ b/src/RGLFields.hpp @@ -141,7 +141,7 @@ inline std::string toString(rgl_field_t type) } } -#ifdef RGL_BUILD_ROS2_EXTENSION +#if RGL_BUILD_ROS2_EXTENSION #include inline std::vector toRos2Fields(rgl_field_t type) diff --git a/src/Tape.cpp b/src/Tape.cpp index 56f9f4e0..b5454ed6 100644 --- a/src/Tape.cpp +++ b/src/Tape.cpp @@ -65,6 +65,7 @@ TapePlayer::TapePlayer(const char* path) { tapeFunctions = { { "rgl_get_version_info", std::bind(&TapePlayer::tape_get_version_info, this, _1) }, + { "rgl_get_extension_info", std::bind(&TapePlayer::tape_get_extension_info, this, _1) }, { "rgl_configure_logging", std::bind(&TapePlayer::tape_configure_logging, this, _1) }, { "rgl_cleanup", std::bind(&TapePlayer::tape_cleanup, this, _1) }, { "rgl_mesh_create", std::bind(&TapePlayer::tape_mesh_create, this, _1) }, @@ -95,13 +96,13 @@ TapePlayer::TapePlayer(const char* path) { "rgl_node_gaussian_noise_angular_hitpoint", std::bind(&TapePlayer::tape_node_gaussian_noise_angular_hitpoint, this, _1) }, { "rgl_node_gaussian_noise_distance", std::bind(&TapePlayer::tape_node_gaussian_noise_distance, this, _1) }, - #ifdef RGL_BUILD_PCL_EXTENSION + #if RGL_BUILD_PCL_EXTENSION { "rgl_graph_write_pcd_file", std::bind(&TapePlayer::tape_graph_write_pcd_file, this, _1) }, { "rgl_node_points_downsample", std::bind(&TapePlayer::tape_node_points_downsample, this, _1) }, { "rgl_node_points_visualize", std::bind(&TapePlayer::tape_node_points_visualize, this, _1) }, #endif - #ifdef RGL_BUILD_ROS2_EXTENSION + #if RGL_BUILD_ROS2_EXTENSION { "rgl_node_points_ros2_publish", std::bind(&TapePlayer::tape_node_points_ros2_publish, this, _1) }, { "rgl_node_points_ros2_publish_with_qos", std::bind(&TapePlayer::tape_node_points_ros2_publish_with_qos, this, _1) }, #endif diff --git a/src/Tape.hpp b/src/Tape.hpp index 2b84ba6f..4309c313 100644 --- a/src/Tape.hpp +++ b/src/Tape.hpp @@ -129,6 +129,7 @@ class TapeRecorder int valueToYaml(rgl_field_t value) { return (int) value; } int valueToYaml(rgl_log_level_t value) { return (int) value; } int valueToYaml(rgl_axis_t value) { return (int) value; } + int32_t valueToYaml(rgl_extension_t value) { return static_cast(value); } int valueToYaml(rgl_qos_policy_reliability_t value) { return (int) value; } int valueToYaml(rgl_qos_policy_durability_t value) { return (int) value; } int valueToYaml(rgl_qos_policy_history_t value) { return (int) value; } @@ -188,6 +189,7 @@ struct TapePlayer void playUnchecked(YAML::iterator); void tape_get_version_info(const YAML::Node& yamlNode); + void tape_get_extension_info(const YAML::Node& yamlNode); void tape_configure_logging(const YAML::Node& yamlNode); void tape_cleanup(const YAML::Node& yamlNode); void tape_mesh_create(const YAML::Node& yamlNode); @@ -218,13 +220,13 @@ struct TapePlayer void tape_node_gaussian_noise_angular_hitpoint(const YAML::Node& yamlNode); void tape_node_gaussian_noise_distance(const YAML::Node& yamlNode); - #ifdef RGL_BUILD_PCL_EXTENSION + #if RGL_BUILD_PCL_EXTENSION void tape_graph_write_pcd_file(const YAML::Node& yamlNode); void tape_node_points_downsample(const YAML::Node& yamlNode); void tape_node_points_visualize(const YAML::Node& yamlNode); #endif - #ifdef RGL_BUILD_ROS2_EXTENSION + #if RGL_BUILD_ROS2_EXTENSION void tape_node_points_ros2_publish(const YAML::Node& yamlNode); void tape_node_points_ros2_publish_with_qos(const YAML::Node& yamlNode); #endif diff --git a/src/Time.hpp b/src/Time.hpp index 630ea461..3c554d00 100644 --- a/src/Time.hpp +++ b/src/Time.hpp @@ -14,7 +14,7 @@ #pragma once -#ifdef RGL_BUILD_ROS2_EXTENSION +#if RGL_BUILD_ROS2_EXTENSION #include #endif @@ -27,7 +27,7 @@ struct Time double asSeconds() { return static_cast(timeNs) * 1.0e-9; }; uint64_t asNanoseconds() { return timeNs; } - #ifdef RGL_BUILD_ROS2_EXTENSION + #if RGL_BUILD_ROS2_EXTENSION builtin_interfaces::msg::Time asRos2Msg() { auto msg = builtin_interfaces::msg::Time(); diff --git a/src/api/apiCommon.hpp b/src/api/apiCommon.hpp index d12a809d..8a769c93 100644 --- a/src/api/apiCommon.hpp +++ b/src/api/apiCommon.hpp @@ -15,7 +15,7 @@ #include #include -#ifdef RGL_BUILD_ROS2_EXTENSION +#if RGL_BUILD_ROS2_EXTENSION #include #endif @@ -62,7 +62,7 @@ rgl_status_t rglSafeCall(Fn fn) try { std::invoke(fn); } - #ifdef RGL_BUILD_ROS2_EXTENSION + #if RGL_BUILD_ROS2_EXTENSION catch (rclcpp::exceptions::RCLErrorBase& e) { return updateAPIState(RGL_ROS2_ERROR, e.message.c_str()); } diff --git a/src/api/apiCore.cpp b/src/api/apiCore.cpp index d6ca80c4..d47f9d1e 100644 --- a/src/api/apiCore.cpp +++ b/src/api/apiCore.cpp @@ -76,6 +76,33 @@ void TapePlayer::tape_get_version_info(const YAML::Node& yamlNode) } } +RGL_API rgl_status_t +rgl_get_extension_info(rgl_extension_t extension, int32_t* out_available) +{ + auto status = rglSafeCall([&]() { + CHECK_ARG(0 <= extension && extension < RGL_EXTENSION_COUNT); + CHECK_ARG(out_available != nullptr); + switch (extension) { + case RGL_EXTENSION_PCL: *out_available = RGL_BUILD_PCL_EXTENSION; break; + case RGL_EXTENSION_ROS2: *out_available = RGL_BUILD_ROS2_EXTENSION; break; + case RGL_EXTENSION_UDP: *out_available = RGL_BUILD_UDP_EXTENSION; break; + default: throw std::invalid_argument(fmt::format("queried unknown RGL extension: {}", extension)); + } + }); + TAPE_HOOK(extension, out_available); + return status; +} + +void TapePlayer::tape_get_extension_info(const YAML::Node &yamlNode) +{ + int32_t out_available; + int32_t recorded_available = yamlNode[1].as(); + rgl_get_extension_info(static_cast(yamlNode[0].as()), &out_available); + if (out_available != recorded_available) { + RGL_WARN(fmt::format("tape_get_extension_info: result mismatch, recorded={}, actual={}", recorded_available, out_available)); + } +} + RGL_API rgl_status_t rgl_configure_logging(rgl_log_level_t log_level, const char* log_file_path, bool use_stdout) { diff --git a/test/src/graphTest.cpp b/test/src/graphTest.cpp index 14dab1c9..f4fe18a9 100644 --- a/test/src/graphTest.cpp +++ b/test/src/graphTest.cpp @@ -9,7 +9,7 @@ class GraphCase : public RGLTest {}; -#ifdef RGL_BUILD_PCL_EXTENSION +#if RGL_BUILD_PCL_EXTENSION #include #endif @@ -29,7 +29,7 @@ TEST_F(GraphCase, FullLinear) EXPECT_RGL_SUCCESS(rgl_node_points_compact(&compact)); EXPECT_RGL_SUCCESS(rgl_node_points_transform(&shear, &shearTf)); -#ifdef RGL_BUILD_PCL_EXTENSION +#if RGL_BUILD_PCL_EXTENSION EXPECT_RGL_SUCCESS(rgl_node_points_downsample(&downsample, 0.1f, 0.1f, 0.1f)); #endif @@ -38,12 +38,12 @@ TEST_F(GraphCase, FullLinear) EXPECT_RGL_SUCCESS(rgl_graph_node_add_child(raytrace, compact)); EXPECT_RGL_SUCCESS(rgl_graph_node_add_child(compact, shear)); -#ifdef RGL_BUILD_PCL_EXTENSION +#if RGL_BUILD_PCL_EXTENSION EXPECT_RGL_SUCCESS(rgl_graph_node_add_child(shear, downsample)); #endif EXPECT_RGL_SUCCESS(rgl_graph_run(raytrace)); -#ifdef RGL_BUILD_PCL_EXTENSION +#if RGL_BUILD_PCL_EXTENSION EXPECT_RGL_SUCCESS(rgl_graph_write_pcd_file(downsample, "minimal.pcd")); #else RGL_WARN("RGL compiled without PCL extension. Tests will not save PCD!"); @@ -94,7 +94,7 @@ TEST_F(GraphCase, NodeRemoval) EXPECT_RGL_SUCCESS(rgl_graph_run(raytrace)); // Output pointcloud should contain two boxes -#ifdef RGL_BUILD_PCL_EXTENSION +#if RGL_BUILD_PCL_EXTENSION EXPECT_RGL_SUCCESS(rgl_graph_write_pcd_file(temporalMerge, "two_boxes_removal.pcd")); #else RGL_WARN("RGL compiled without PCL extension. Tests will not save PCD!"); @@ -137,7 +137,7 @@ TEST_F(GraphCase, SpatialMergeFromTransforms) EXPECT_RGL_SUCCESS(rgl_graph_node_add_child(transformPtsY, spatialMerge)); EXPECT_RGL_SUCCESS(rgl_graph_run(raytrace)); -#ifdef RGL_BUILD_PCL_EXTENSION +#if RGL_BUILD_PCL_EXTENSION EXPECT_RGL_SUCCESS(rgl_graph_write_pcd_file(spatialMerge, "two_boxes_spatial_merge.pcd")); #else RGL_WARN("RGL compiled without PCL extension. Tests will not save PCD!"); @@ -182,7 +182,7 @@ TEST_F(GraphCase, SpatialMergeFromRaytraces) } EXPECT_RGL_SUCCESS(rgl_graph_run(spatialMerge)); -#ifdef RGL_BUILD_PCL_EXTENSION +#if RGL_BUILD_PCL_EXTENSION EXPECT_RGL_SUCCESS(rgl_graph_write_pcd_file(spatialMerge, "cube_spatial_merge.pcd")); #else RGL_WARN("RGL compiled without PCL extension. Tests will not save PCD!"); @@ -225,7 +225,7 @@ TEST_F(GraphCase, TemporalMerge) EXPECT_RGL_SUCCESS(rgl_node_points_transform(&transformPts, &translateYTf)); EXPECT_RGL_SUCCESS(rgl_graph_run(raytrace)); -#ifdef RGL_BUILD_PCL_EXTENSION +#if RGL_BUILD_PCL_EXTENSION EXPECT_RGL_SUCCESS(rgl_graph_write_pcd_file(temporalMerge, "two_boxes_temporal_merge.pcd")); #else RGL_WARN("RGL compiled without PCL extension. Tests will not save PCD!"); diff --git a/test/src/tapeSurfaceTest.cpp b/test/src/tapeSurfaceTest.cpp index ee576060..c31e306c 100644 --- a/test/src/tapeSurfaceTest.cpp +++ b/test/src/tapeSurfaceTest.cpp @@ -3,11 +3,11 @@ #include #include -#ifdef RGL_BUILD_PCL_EXTENSION +#if RGL_BUILD_PCL_EXTENSION #include #endif -#ifdef RGL_BUILD_ROS2_EXTENSION +#if RGL_BUILD_ROS2_EXTENSION #include #endif @@ -80,7 +80,7 @@ TEST_F(TapeCase, RecordPlayAllCalls) std::vector<::Field::type> usePointsData = {{1, 2, 3}, {4, 5, 6}}; EXPECT_RGL_SUCCESS(rgl_node_points_from_array(&usePoints, usePointsData.data(), usePointsData.size(), usePointsFields.data(), usePointsFields.size())); - #ifdef RGL_BUILD_ROS2_EXTENSION + #if RGL_BUILD_ROS2_EXTENSION rgl_node_t ros2pub = nullptr; EXPECT_RGL_SUCCESS(rgl_node_points_ros2_publish(&ros2pub, "pointcloud", "rgl")); @@ -108,7 +108,7 @@ TEST_F(TapeCase, RecordPlayAllCalls) EXPECT_RGL_SUCCESS(rgl_graph_run(raytrace)); - #ifdef RGL_BUILD_PCL_EXTENSION + #if RGL_BUILD_PCL_EXTENSION rgl_node_t downsample = nullptr; EXPECT_RGL_SUCCESS(rgl_node_points_downsample(&downsample, 1.0f, 1.0f, 1.0f)); diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index ac108932..d17c90d6 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -1,8 +1,13 @@ -add_executable(tapePlayer tapePlayer.cpp) -target_link_libraries(tapePlayer RobotecGPULidar spdlog) +add_executable(inspectLibRGL inspectLibRGL.cpp) -if (RGL_BUILD_PCL_EXTENSION) - add_executable(tapeVisualizer tapeVisualizer.cpp) - target_link_libraries(tapeVisualizer RobotecGPULidar spdlog yaml-cpp) - target_include_directories(tapeVisualizer PRIVATE ${CMAKE_SOURCE_DIR}/src) +# Linux only - tape related tools +if ((NOT WIN32)) + if (RGL_BUILD_PCL_EXTENSION) + add_executable(tapeVisualizer tapeVisualizer.cpp) + target_link_libraries(tapeVisualizer RobotecGPULidar spdlog yaml-cpp) + target_include_directories(tapeVisualizer PRIVATE ${CMAKE_SOURCE_DIR}/src) + endif() + + add_executable(tapePlayer tapePlayer.cpp) + target_link_libraries(tapePlayer RobotecGPULidar spdlog) endif() diff --git a/tools/inspectLibRGL.cpp b/tools/inspectLibRGL.cpp new file mode 100644 index 00000000..25744768 --- /dev/null +++ b/tools/inspectLibRGL.cpp @@ -0,0 +1,132 @@ +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +typedef rgl_status_t (*rgl_get_version_t)(int32_t* out_major, int32_t* out_minor, int32_t* out_patch); +typedef rgl_status_t (*rgl_get_extension_info_t)(rgl_extension_t extension, int32_t* out_available); + + +// RAII Wrapper for dl-opening RGL to get version and extensions. +#ifdef _WIN32 +struct MicroRGL +{ + MicroRGL(const char* path) + { + handle = LoadLibraryA(path); + if (handle == NULL) { + std::string hint; + if (GetLastError() == 126) { + hint = "Make sure that all dependencies of the library can be found."; + } + auto msg = fmt::format("LoadLibrary error: {} {}\n", GetLastError(), hint); + throw std::runtime_error(msg); + } + } + + ~MicroRGL() + { + if (handle != NULL) { + FreeLibrary(handle); + } + } + + rgl_get_version_t get_rgl_get_version() + { + auto dyn_rgl_get_version = reinterpret_cast(GetProcAddress(handle, "rgl_get_version_info")); + if (dyn_rgl_get_version == NULL) { + auto msg = fmt::format("GetProcAddress error: {}\n", GetLastError()); + throw std::runtime_error(msg); + } + return dyn_rgl_get_version; + } + + rgl_get_extension_info_t get_rgl_get_extension_infos() + { + auto dyn_rgl_get_extension_info = reinterpret_cast(GetProcAddress(handle, "rgl_get_extension_info")); + if (dyn_rgl_get_extension_info == NULL) { + auto msg = fmt::format("GetProcAddress error: {}\n", GetLastError()); + throw std::runtime_error(msg); + } + return dyn_rgl_get_extension_info; + } + +private: + HMODULE handle = NULL; +}; +#else +struct MicroRGL +{ + MicroRGL(const char* path) + { + handle = dlopen(path, RTLD_LAZY); + if (handle == nullptr) { + auto msg = fmt::format("dlopen error: {}\n", dlerror()); + throw std::runtime_error(msg); + } + } + + ~MicroRGL() + { + if (handle != nullptr) { + dlclose(handle); + } + } + + rgl_get_version_t get_rgl_get_version() + { + auto dyn_rgl_get_version = reinterpret_cast(dlsym(handle, "rgl_get_version_info")); + if (dyn_rgl_get_version == nullptr) { + auto msg = fmt::format("dlsym error: {}\n", dlerror()); + throw std::runtime_error(msg); + } + return dyn_rgl_get_version; + } + + rgl_get_extension_info_t get_rgl_get_extension_infos() + { + auto dyn_rgl_get_extension_info = reinterpret_cast(dlsym(handle, "rgl_get_extension_info")); + if (dyn_rgl_get_extension_info == nullptr) { + auto msg = fmt::format("dlsym error: {}\n", dlerror()); + throw std::runtime_error(msg); + } + return dyn_rgl_get_extension_info; + }; + + +private: + void* handle = nullptr; +}; +#endif + + +int main(int argc, char** argv) try +{ + if (argc != 2) { + fmt::print(stderr, "Usage: {} \n", argv[0]); + return 1; + } + + MicroRGL lib {argv[1]}; + + int32_t major = 0, minor = 0, patch = 0; + rgl_status_t status = lib.get_rgl_get_version()(&major, &minor, &patch); + fmt::print("rgl_get_version(...) -> {}.{}.{} [status={}]\n", major, minor, patch, status); + + int32_t extensionID = 0; + while (status == RGL_SUCCESS) { + int32_t available = -1; + status = lib.get_rgl_get_extension_infos()(static_cast(extensionID), &available); + fmt::print("rgl_get_extension_info({}, ...) -> {} [status={}]\n", extensionID, available, status); + extensionID += 1; + } + return 0; +} +catch (std::runtime_error& e) +{ + fmt::print(stderr, "{}\n", e.what()); +}