Advanced GLFW: Multi-Monitor, Contexts, and High‑DPI Handling
Multi‑Monitor
- Detect monitors: Use glfwGetMonitors() to list monitors and glfwGetPrimaryMonitor() for the primary one.
- Monitor info: Query glfwGetMonitorName(), glfwGetVideoMode(), and glfwGetVideoModes() for resolution, refresh rate, and color depth. Use glfwGetMonitorPhysicalSize() for physical dimensions (mm) and compute DPI.
- Window placement: Create windows on a chosen monitor by setting their position with glfwSetWindowPos() using the monitor’s video mode and monitor work area (compute from monitor coordinates provided by glfwGetMonitorWorkarea if available in your GLFW version) or by creating a full‑screen window with glfwCreateWindow(width, height, title, monitor, NULL).
- Full‑screen & borderless: For exclusive full‑screen, pass the monitor to glfwCreateWindow. For borderless windowed fullscreen, create a window sized to the monitor’s resolution and place it at the monitor’s position with no decorations (glfwWindowHint(GLFW_DECORATED, GLFW_FALSE)).
- Monitor connection events: Register a monitor callback with glfwSetMonitorCallback() to handle hotplugging (reposition windows, adjust video modes, or recreate fullscreen windows).
Contexts
- Creating contexts: Set context hints before window creation (glfwWindowHint) for client API (GLFW_CLIENT_API), version, profile, forward compatibility, and robustness. Then create a window which provides an OpenGL (or OpenGL ES) context.
- Making current & sharing: Use glfwMakeContextCurrent(window) to make a context current on a thread. Share resources (textures, buffers, shaders) between contexts by passing a shared context when creating a new window on platforms that support it (glfwCreateWindow(…, share)).
- Context per thread: A context may only be current on a single thread at a time. If you need rendering on multiple threads, create separate contexts and share resources; call glfwMakeContextCurrent(NULL) before making a context current on another thread.
- Context lifecycle: Destroy contexts by destroying their windows (glfwDestroyWindow). Be cautious to delete GL resources in a context where they are current or rely on shared contexts to manage lifetime.
- Swap interval & vsync: Control buffer swap behavior with glfwSwapInterval(n). Set it after making the context current. Note platform differences and driver support.
- Extensions & function loading: GLFW does not load GL functions; use an extension loader (glew, glad) after creating and making a context current.
High‑DPI Handling
- Framebuffer vs window size: On HiDPI displays, logical window size (glfwGetWindowSize) differs from framebuffer size (glfwGetFramebufferSize). Use framebuffer size for glViewport and framebuffer-related operations; use window size for UI layout measured in screen coordinates.
- Content scale: Query glfwGetWindowContentScale() or glfwGetMonitorContentScale() to get the scaling factor (e.g., 2.0 on Retina). Use this to scale UI assets, fonts, and input coordinates.
- Framebuffer-aware rendering: Always call glfwGetFramebufferSize(window) each frame (or on resize callback) and set glViewport(0,0,width,height). For pixel-perfect UI use framebuffer size; for DPI-independent UI use content scale to convert logical units to pixels.
- Scaling input and cursor: Convert cursor positions from glfwGetCursorPos (window coordinates) to framebuffer coordinates by multiplying by content scale or by using the ratio framebufferSize / windowSize. Ensure UI hit tests use the same coordinate space as rendering.
- Window hints: Use glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFWTRUE) on platforms where supported to allow the window to automatically scale when moved between monitors with different scales.
- Font & asset strategies: Provide multiple raster font sizes or use vector fonts scaled by content scale. Supply image assets at multiple scales (1x, 2x, 3x) and select based on content scale to avoid blurriness.
- Callbacks for scale changes: Register a window content scale callback with glfwSetWindowContentScaleCallback() to adjust UI and reload appropriate assets when the window moves between monitors or when scale changes.
Best Practices & Pitfalls
- Always query sizes each frame rather than caching indefinitely—monitor configurations and scale can change at runtime.
- Use shared contexts carefully to avoid synchronization issues; consider explicit fences or only sharing immutable resources when possible.
- Handle monitor hotplugging gracefully: save/restore windowed positions, recreate fullscreen windows, and re-evaluate content scale.
- Test on real hardware with different DPIs, multi‑monitor setups, and platform combinations (Windows, macOS, Linux) because behavior and available features differ.
- Prefer logical coordinates for UI layout and convert to framebuffer pixels only at the final rendering step to keep layouts consistent across DPIs.
Quick code snippets
- Making context current and setting vsync:
c
glfwMakeContextCurrent(window); glfwSwapInterval(1); // enable vsync
- Correct viewport with HiDPI:
c
int fbW, fbH; glfwGetFramebufferSize(window, &fbW, &fbH); glViewport(0, 0, fbW, fbH);
- Create fullscreen on a monitor:
c
const GLFWvidmode mode = glfwGetVideoMode(monitor); GLFWwindow w = glfwCreateWindow(mode->width, mode->height, “App”, monitor, NULL);
If you want, I can provide a compact example showing multi‑monitor window placement with content‑scale handling and context sharing.
Leave a Reply