From bc5ef7e81a0734b8ead8b3ccbb4bdf88e3bb19a7 Mon Sep 17 00:00:00 2001 From: SoftFever Date: Tue, 5 Aug 2025 00:01:34 +0800 Subject: [PATCH] buildin opengl info detection --- doc/graphics-backend-automation.md | 66 +++-- src/slic3r/GUI/GraphicsBackendManager.cpp | 291 +++++++++++++++++++++- src/slic3r/GUI/GraphicsBackendManager.hpp | 49 +++- 3 files changed, 373 insertions(+), 33 deletions(-) diff --git a/doc/graphics-backend-automation.md b/doc/graphics-backend-automation.md index 2fd30f081c..a781567c4e 100644 --- a/doc/graphics-backend-automation.md +++ b/doc/graphics-backend-automation.md @@ -9,11 +9,21 @@ OrcaSlicer now includes an automatic graphics backend detection and configuratio ## Problem Solved -Previously, users had to manually set environment variables like `GBM_BACKEND=dri` to make the 3D view work properly on systems like Kubuntu 25.04. This was especially problematic for: +Previously, users had to manually set environment variables like `GBM_BACKEND=dri` to make the 3D view work properly on systems like Kubuntu 25.04. Additionally, the system relied on external command-line tools for graphics detection. This was especially problematic for: - **Wayland sessions** with NVIDIA drivers - **Newer graphics drivers** that require specific configurations - **Different graphics hardware** (NVIDIA, AMD, Intel) requiring different settings +- **Containerized environments** (Docker, Flatpak, AppImage) where external tools might not be available +- **Systems without mesa-utils packages** installed + +## Improvements in Current Version + +### Direct OpenGL Detection +- **No External Dependencies**: Graphics detection now uses direct OpenGL API calls instead of relying on `glxinfo`/`eglinfo` commands +- **Container-Friendly**: Works reliably in Docker, Flatpak, and AppImage environments +- **Better Performance**: Eliminates subprocess overhead from command execution +- **Enhanced Reliability**: Direct API calls are more dependable than parsing command output ## How It Works @@ -25,6 +35,26 @@ The system automatically detects: - **Graphics Driver**: NVIDIA, AMD, Intel, or Mesa - **Driver Version**: For NVIDIA drivers, detects version to apply appropriate settings +#### Detection Methodology + +The graphics detection uses a multi-tier approach for maximum reliability: + +1. **Direct OpenGL API Calls (Primary)** + - Creates minimal OpenGL contexts using GLX (X11) or EGL (Wayland/headless) + - Uses `glGetString(GL_VENDOR)` and `glGetString(GL_RENDERER)` directly + - Works reliably in containers and restricted environments + - No dependency on external command-line tools + +2. **Command-Line Tools (Fallback)** + - Uses `glxinfo` and `eglinfo` commands only if direct detection fails + - Provides compatibility with older detection methods + - Tools are no longer required to be installed + +3. **Hardware-Based Detection (Final Fallback)** + - Reads PCI vendor IDs from `/sys/class/drm/` + - Uses `nvidia-smi` for NVIDIA-specific detection + - Filesystem-based detection methods + ### 2. Smart Configuration Based on the detected environment, the system applies optimal settings: @@ -112,13 +142,16 @@ Common log messages: The system now includes improved error handling and validation: -#### Command Execution Errors -- `GraphicsBackendManager: Failed to execute command: glxinfo` +#### Graphics Detection Errors +- `GraphicsBackendManager: Direct OpenGL detection failed, falling back to command-line tools` +- `GraphicsBackendManager: Failed to create or make current OpenGL context` - `GraphicsBackendManager: Command failed with status 127: nvidia-smi` -- `GraphicsBackendManager: Command returned no output: eglinfo` +- `GraphicsBackendManager: glGetString returned null pointers` -#### Driver Detection Fallbacks -- `GraphicsBackendManager: GLX/EGL detection failed, trying glxinfo fallback` +#### Driver Detection Methods +- `GraphicsBackendManager: Successfully retrieved OpenGL info directly` +- `GraphicsBackendManager: Direct OpenGL and GLX/EGL detection failed, trying glxinfo fallback` +- `GraphicsBackendManager: Detected NVIDIA driver via glxinfo final fallback` - `GraphicsBackendManager: Detected NVIDIA driver via nvidia-smi fallback` - `GraphicsBackendManager: Failed to detect graphics driver, using Mesa as fallback` @@ -134,16 +167,20 @@ The system now includes improved error handling and validation: ### Troubleshooting Common Issues -#### 1. Missing System Tools -If you see "Failed to execute command" messages, install missing tools: -```bash -# For Ubuntu/Debian -sudo apt install mesa-utils glxinfo +#### 1. Graphics Detection Issues +The system now uses direct OpenGL API calls as the primary detection method, eliminating the need for external tools in most cases. -# For Arch Linux -sudo pacman -S mesa-utils glxinfo +If direct detection fails and you see "glxinfo fallback" messages, you can optionally install tools for debugging: +```bash +# For Ubuntu/Debian (optional, for debugging only) +sudo apt install mesa-utils + +# For Arch Linux (optional, for debugging only) +sudo pacman -S mesa-utils ``` +**Note**: These tools are no longer required for normal operation as the system uses direct OpenGL API calls. + #### 2. Driver Detection Failures If driver detection fails, check: ```bash @@ -153,7 +190,8 @@ nvidia-smi # Check graphics hardware lspci | grep -i vga -# Check OpenGL information +# Check OpenGL information (the system now does this automatically via API calls) +# For manual checking, you can still use: glxinfo | grep "OpenGL vendor" ``` diff --git a/src/slic3r/GUI/GraphicsBackendManager.cpp b/src/slic3r/GUI/GraphicsBackendManager.cpp index d8eceee107..a69456b976 100644 --- a/src/slic3r/GUI/GraphicsBackendManager.cpp +++ b/src/slic3r/GUI/GraphicsBackendManager.cpp @@ -12,11 +12,19 @@ #include #include #include +#include +#include +#include +#include +#include #endif namespace Slic3r { namespace GUI { +// Thread safety for OpenGL detection operations +std::mutex GraphicsBackendManager::opengl_detection_mutex_; + GraphicsBackendManager& GraphicsBackendManager::get_instance() { static GraphicsBackendManager instance; @@ -79,7 +87,8 @@ GraphicsBackendManager::GraphicsDriver GraphicsBackendManager::detect_graphics_d return container_driver; } - // Try to get OpenGL vendor and renderer info + // Try to get OpenGL vendor and renderer info using direct OpenGL API calls + // This creates minimal OpenGL contexts (GLX/EGL) and uses glGetString() directly std::string glx_info = get_glx_info(); std::string egl_info = get_egl_info(); @@ -124,29 +133,29 @@ GraphicsBackendManager::GraphicsDriver GraphicsBackendManager::detect_graphics_d return GraphicsDriver::Mesa; } - // Try to run glxinfo as fallback (may not work in containers) - BOOST_LOG_TRIVIAL(debug) << "GraphicsBackendManager: GLX/EGL detection failed, trying glxinfo fallback"; + // Try to run glxinfo as final fallback (may not work in containers) + BOOST_LOG_TRIVIAL(debug) << "GraphicsBackendManager: Direct OpenGL and GLX/EGL detection failed, trying glxinfo fallback"; std::string glx_output = execute_command("glxinfo 2>/dev/null | grep 'OpenGL vendor\\|OpenGL renderer'"); if (!glx_output.empty()) { - BOOST_LOG_TRIVIAL(debug) << "GraphicsBackendManager: glxinfo output: " << glx_output.substr(0, 200); + BOOST_LOG_TRIVIAL(debug) << "GraphicsBackendManager: glxinfo fallback output: " << glx_output.substr(0, 200); if (boost::icontains(glx_output, "nvidia")) { - BOOST_LOG_TRIVIAL(info) << "GraphicsBackendManager: Detected NVIDIA driver via glxinfo fallback"; + BOOST_LOG_TRIVIAL(info) << "GraphicsBackendManager: Detected NVIDIA driver via glxinfo final fallback"; return GraphicsDriver::NVIDIA; } else if (boost::icontains(glx_output, "amd") || boost::icontains(glx_output, "radeon")) { - BOOST_LOG_TRIVIAL(info) << "GraphicsBackendManager: Detected AMD driver via glxinfo fallback"; + BOOST_LOG_TRIVIAL(info) << "GraphicsBackendManager: Detected AMD driver via glxinfo final fallback"; return GraphicsDriver::AMD; } else if (boost::icontains(glx_output, "intel")) { - BOOST_LOG_TRIVIAL(info) << "GraphicsBackendManager: Detected Intel driver via glxinfo fallback"; + BOOST_LOG_TRIVIAL(info) << "GraphicsBackendManager: Detected Intel driver via glxinfo final fallback"; return GraphicsDriver::Intel; } else if (boost::icontains(glx_output, "mesa")) { - BOOST_LOG_TRIVIAL(info) << "GraphicsBackendManager: Detected Mesa driver via glxinfo fallback"; + BOOST_LOG_TRIVIAL(info) << "GraphicsBackendManager: Detected Mesa driver via glxinfo final fallback"; return GraphicsDriver::Mesa; } } // Try additional fallback methods - BOOST_LOG_TRIVIAL(debug) << "GraphicsBackendManager: glxinfo fallback failed, trying additional methods"; + BOOST_LOG_TRIVIAL(debug) << "GraphicsBackendManager: All OpenGL detection methods failed, trying hardware-based fallbacks"; // Check for NVIDIA using nvidia-smi std::string nvidia_check = execute_command("nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | head -n1"); @@ -366,10 +375,259 @@ bool GraphicsBackendManager::is_nvidia_driver_newer_than(int major_version) } } +/** + * Helper function to query OpenGL strings and check for errors. + * + * @param context_type String describing the context type for logging (e.g., "GLX", "EGL") + * @return String containing OpenGL vendor, renderer, and version information, + * or empty string if detection fails or errors occur + */ +std::string GraphicsBackendManager::query_opengl_strings(const std::string& context_type) +{ +#ifdef __linux__ + // Query OpenGL information + const GLubyte* vendor = glGetString(GL_VENDOR); + GLenum error = glGetError(); + if (error != GL_NO_ERROR) { + BOOST_LOG_TRIVIAL(warning) << "GraphicsBackendManager: OpenGL error after glGetString(GL_VENDOR): " << error; + return ""; + } + + const GLubyte* renderer = glGetString(GL_RENDERER); + error = glGetError(); + if (error != GL_NO_ERROR) { + BOOST_LOG_TRIVIAL(warning) << "GraphicsBackendManager: OpenGL error after glGetString(GL_RENDERER): " << error; + return ""; + } + + const GLubyte* version = glGetString(GL_VERSION); + error = glGetError(); + if (error != GL_NO_ERROR) { + BOOST_LOG_TRIVIAL(warning) << "GraphicsBackendManager: OpenGL error after glGetString(GL_VERSION): " << error; + return ""; + } + + if (vendor && renderer && version) { + std::string result = "OpenGL vendor string: " + std::string(reinterpret_cast(vendor)) + "\n"; + result += "OpenGL renderer string: " + std::string(reinterpret_cast(renderer)) + "\n"; + result += "OpenGL version string: " + std::string(reinterpret_cast(version)); + + BOOST_LOG_TRIVIAL(info) << "GraphicsBackendManager: Successfully retrieved OpenGL info via " << context_type; + BOOST_LOG_TRIVIAL(debug) << "GraphicsBackendManager: Vendor: " << reinterpret_cast(vendor); + BOOST_LOG_TRIVIAL(debug) << "GraphicsBackendManager: Renderer: " << reinterpret_cast(renderer); + return result; + } + + BOOST_LOG_TRIVIAL(warning) << "GraphicsBackendManager: glGetString returned null pointers via " << context_type; + return ""; +#else + return ""; +#endif +} + +/** + * Direct OpenGL graphics detection using native API calls. + * + * This function creates minimal OpenGL contexts to query graphics information directly + * via OpenGL API calls, eliminating dependency on external tools like glxinfo/eglinfo. + * + * Detection process: + * 1. Try GLX context creation (for X11 environments) + * 2. Try EGL context creation (for Wayland/headless environments) + * 3. Query glGetString(GL_VENDOR/GL_RENDERER/GL_VERSION) + * 4. Clean up all created resources + * + * Benefits: + * - Works reliably in containers (Docker, Flatpak, AppImage) + * - No external command dependencies + * - Better error handling and logging + * - Supports both X11 and Wayland + * + * @return String containing OpenGL vendor, renderer, and version information, + * or empty string if detection fails + */ +std::string GraphicsBackendManager::get_opengl_info_direct() +{ +#ifdef __linux__ + // Thread safety: X11 and EGL operations are not thread-safe + std::lock_guard lock(opengl_detection_mutex_); + + BOOST_LOG_TRIVIAL(debug) << "GraphicsBackendManager: Attempting direct OpenGL detection"; + + std::string result; + Display* display = nullptr; + GLXContext context = nullptr; + Window window = 0; + Colormap colormap = 0; + XVisualInfo* visual = nullptr; + + try { + // Try X11 first + display = XOpenDisplay(nullptr); + if (display) { + BOOST_LOG_TRIVIAL(debug) << "GraphicsBackendManager: X11 display opened successfully"; + + // Get a visual + int screen = DefaultScreen(display); + int attribs[] = { + GLX_RGBA, + GLX_RED_SIZE, 1, + GLX_GREEN_SIZE, 1, + GLX_BLUE_SIZE, 1, + GLX_DEPTH_SIZE, 12, + GLX_DOUBLEBUFFER, + None + }; + + visual = glXChooseVisual(display, screen, attribs); + if (visual) { + BOOST_LOG_TRIVIAL(debug) << "GraphicsBackendManager: GLX visual chosen successfully"; + + // Create a minimal window + Window root = RootWindow(display, screen); + colormap = XCreateColormap(display, root, visual->visual, AllocNone); + + XSetWindowAttributes attrs; + attrs.colormap = colormap; + attrs.event_mask = 0; + + window = XCreateWindow(display, root, 0, 0, 1, 1, 0, + visual->depth, InputOutput, visual->visual, + CWColormap | CWEventMask, &attrs); + + if (window) { + BOOST_LOG_TRIVIAL(debug) << "GraphicsBackendManager: X11 window created successfully"; + + // Create OpenGL context + context = glXCreateContext(display, visual, nullptr, GL_TRUE); + if (context && glXMakeCurrent(display, window, context)) { + BOOST_LOG_TRIVIAL(debug) << "GraphicsBackendManager: OpenGL context created and made current"; + + // Query OpenGL information using helper function + result = query_opengl_strings("GLX"); + + glXMakeCurrent(display, None, nullptr); + } else { + BOOST_LOG_TRIVIAL(warning) << "GraphicsBackendManager: Failed to create or make current OpenGL context"; + } + } else { + BOOST_LOG_TRIVIAL(warning) << "GraphicsBackendManager: Failed to create X11 window"; + } + } else { + BOOST_LOG_TRIVIAL(warning) << "GraphicsBackendManager: Failed to choose GLX visual"; + } + } else { + BOOST_LOG_TRIVIAL(debug) << "GraphicsBackendManager: X11 display not available, trying EGL"; + + // Try EGL as fallback (for Wayland or headless) + EGLDisplay egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (egl_display != EGL_NO_DISPLAY) { + EGLint major, minor; + if (eglInitialize(egl_display, &major, &minor)) { + BOOST_LOG_TRIVIAL(debug) << "GraphicsBackendManager: EGL initialized successfully"; + + EGLint config_attribs[] = { + EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, + EGL_BLUE_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_RED_SIZE, 8, + EGL_DEPTH_SIZE, 8, + EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, + EGL_NONE + }; + + EGLConfig config; + EGLint num_configs; + if (eglChooseConfig(egl_display, config_attribs, &config, 1, &num_configs) && num_configs > 0) { + BOOST_LOG_TRIVIAL(debug) << "GraphicsBackendManager: EGL config chosen successfully"; + + // Create a pbuffer surface + EGLint pbuffer_attribs[] = { + EGL_WIDTH, 1, + EGL_HEIGHT, 1, + EGL_NONE + }; + + EGLSurface surface = eglCreatePbufferSurface(egl_display, config, pbuffer_attribs); + if (surface != EGL_NO_SURFACE) { + BOOST_LOG_TRIVIAL(debug) << "GraphicsBackendManager: EGL pbuffer surface created successfully"; + + eglBindAPI(EGL_OPENGL_API); + EGLContext egl_context = eglCreateContext(egl_display, config, EGL_NO_CONTEXT, nullptr); + + if (egl_context != EGL_NO_CONTEXT && eglMakeCurrent(egl_display, surface, surface, egl_context)) { + BOOST_LOG_TRIVIAL(debug) << "GraphicsBackendManager: EGL context created and made current"; + + // Query OpenGL information using helper function + result = query_opengl_strings("EGL"); + + eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroyContext(egl_display, egl_context); + } else { + BOOST_LOG_TRIVIAL(warning) << "GraphicsBackendManager: Failed to create or make current EGL context"; + } + + eglDestroySurface(egl_display, surface); + } else { + BOOST_LOG_TRIVIAL(warning) << "GraphicsBackendManager: Failed to create EGL pbuffer surface"; + } + } else { + BOOST_LOG_TRIVIAL(warning) << "GraphicsBackendManager: Failed to choose EGL config"; + } + + eglTerminate(egl_display); + } else { + BOOST_LOG_TRIVIAL(warning) << "GraphicsBackendManager: Failed to initialize EGL"; + } + } else { + BOOST_LOG_TRIVIAL(warning) << "GraphicsBackendManager: EGL display not available"; + } + } + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "GraphicsBackendManager: Exception during direct OpenGL detection: " << e.what(); + } catch (...) { + BOOST_LOG_TRIVIAL(error) << "GraphicsBackendManager: Unknown exception during direct OpenGL detection"; + } + + // Cleanup X11 resources (fixing resource leaks) + if (context) { + glXDestroyContext(display, context); + } + if (window && display) { + XDestroyWindow(display, window); + } + if (colormap && display) { + XFreeColormap(display, colormap); + } + if (visual) { + XFree(visual); + } + if (display) { + XCloseDisplay(display); + } + + if (result.empty()) { + BOOST_LOG_TRIVIAL(warning) << "GraphicsBackendManager: Direct OpenGL detection failed, falling back to command-line tools"; + } + + return result; +#else + BOOST_LOG_TRIVIAL(debug) << "GraphicsBackendManager: Direct OpenGL detection not available on this platform"; + return ""; +#endif +} + std::string GraphicsBackendManager::get_glx_info() { - // In containers, this might not work or might interfere with initialization - // Use safer detection methods first + // Primary method: Use direct OpenGL API calls via GLX/EGL contexts + // This avoids dependency on external glxinfo command and works better in containers + std::string direct_info = get_opengl_info_direct(); + if (!direct_info.empty()) { + return direct_info; + } + + // Final fallback: Use glxinfo command if direct detection failed + // This may not work in containers or restricted environments if (is_running_in_container()) { return ""; } @@ -378,8 +636,15 @@ std::string GraphicsBackendManager::get_glx_info() std::string GraphicsBackendManager::get_egl_info() { - // In containers, this might not work or might interfere with initialization - // Use safer detection methods first + // Primary method: Use direct OpenGL API calls via EGL contexts + // This avoids dependency on external eglinfo command and works better in containers + std::string direct_info = get_opengl_info_direct(); + if (!direct_info.empty()) { + return direct_info; + } + + // Final fallback: Use eglinfo command if direct detection failed + // This may not work in containers or restricted environments if (is_running_in_container()) { return ""; } diff --git a/src/slic3r/GUI/GraphicsBackendManager.hpp b/src/slic3r/GUI/GraphicsBackendManager.hpp index 3338e8b664..c1af7cb87a 100644 --- a/src/slic3r/GUI/GraphicsBackendManager.hpp +++ b/src/slic3r/GUI/GraphicsBackendManager.hpp @@ -2,10 +2,30 @@ #define slic3r_GraphicsBackendManager_hpp_ #include +#include namespace Slic3r { namespace GUI { +/** + * GraphicsBackendManager - Automatic Graphics Backend Detection and Configuration + * + * This class provides automatic detection of graphics hardware and session types, + * and applies optimal OpenGL/graphics configurations for Linux systems. + * + * Key Features: + * - Direct OpenGL API-based detection (no external tool dependencies) + * - Support for X11 and Wayland sessions + * - NVIDIA, AMD, Intel, and Mesa driver detection + * - Container-aware detection (Docker, Flatpak, AppImage) + * - Automatic environment variable configuration + * + * Detection Methods (in order of preference): + * 1. Direct OpenGL context creation and glGetString() API calls + * 2. Container-aware filesystem detection + * 3. Command-line tools (glxinfo/eglinfo) as fallback + * 4. Hardware-based detection (PCI vendor IDs, nvidia-smi) + */ class GraphicsBackendManager { public: @@ -41,13 +61,23 @@ public: static GraphicsBackendManager& get_instance(); - // Detect the current graphics environment + /** + * Detect the current graphics environment using direct OpenGL API calls. + * Primary detection method uses glGetString() via minimal GLX/EGL contexts. + * Falls back to filesystem and command-line detection if needed. + */ GraphicsConfig detect_graphics_environment(); - // Apply graphics configuration + /** + * Apply graphics configuration by setting appropriate environment variables. + * Validates configuration before applying and logs the applied settings. + */ void apply_graphics_config(const GraphicsConfig& config); - // Get recommended configuration for current system + /** + * Get recommended configuration for current system based on detected hardware + * and session type. Uses direct OpenGL detection for optimal compatibility. + */ GraphicsConfig get_recommended_config(); // Check if current configuration is optimal @@ -65,7 +95,7 @@ private: GraphicsBackendManager(const GraphicsBackendManager&) = delete; GraphicsBackendManager& operator=(const GraphicsBackendManager&) = delete; - // Helper methods + // Detection helper methods SessionType detect_session_type(); GraphicsDriver detect_graphics_driver(); GraphicsDriver detect_graphics_driver_container_aware(); @@ -73,12 +103,19 @@ private: std::string read_file_content(const std::string& filepath); std::string get_nvidia_driver_version(); bool is_nvidia_driver_newer_than(int major_version); - std::string get_glx_info(); - std::string get_egl_info(); + + // Graphics information retrieval (now uses direct OpenGL API calls as primary method) + std::string get_glx_info(); // GLX/X11 graphics info (direct OpenGL + fallback) + std::string get_egl_info(); // EGL graphics info (direct OpenGL + fallback) + std::string get_opengl_info_direct(); // Direct OpenGL detection via GLX/EGL contexts + std::string query_opengl_strings(const std::string& context_type); // Helper to query OpenGL info strings void set_environment_variable(const std::string& name, const std::string& value); void unset_environment_variable(const std::string& name); std::string execute_command(const std::string& command); + // Thread safety for OpenGL operations + static std::mutex opengl_detection_mutex_; + // Configuration templates GraphicsConfig get_nvidia_wayland_config(); GraphicsConfig get_nvidia_x11_config();