[Major Feature] True headless execution without X11/GPU dependencies #157

Open
opened 2025-12-07 04:36:42 +00:00 by john · 1 comment
Owner

Problem Statement

McRogueFace cannot execute on servers without X11, even with --headless mode. The current headless implementation avoids creating a window but still requires the full graphics stack:

HeadlessRenderer → sf::RenderTexture → SFML Graphics → OpenGL → X11

This prevents use cases like:

  • Running simulations on cloud servers (AWS, GCP, etc.)
  • CI/CD pipelines without Xvfb
  • Docker containers without GPU passthrough
  • Headless game servers for multiplayer scenarios
  • Batch processing / overnight simulation runs

Root Cause Analysis

SFML's architecture is the fundamental blocker. sf::RenderTexture::create() unconditionally initializes an OpenGL context, which on Linux requires X11 libraries and a display.

Current Dependencies

From CMakeLists.txt:

  • sfml-graphics (requires OpenGL)
  • sfml-window (requires X11/udev on Linux)
  • sfml-system (no graphics deps - safe)
  • sfml-audio (already disabled in headless)
  • OpenGL::GL (required by ImGui-SFML)

SFML Type Pervasiveness

  • 311 occurrences of sf:: types across codebase
  • Core interface: UIDrawable::render(sf::Vector2f, sf::RenderTarget&)
  • Types everywhere: sf::Vector2f, sf::Color, sf::FloatRect, sf::Font, sf::Texture

Requirements

  1. Must work without X11 - executable runs on headless Linux servers
  2. Must preserve screenshot capability - simulations need visual output
  3. Must not break existing functionality - current graphical mode unchanged
  4. Should minimize API changes - Python scripts shouldn't need modification

Potential Approaches

Option A: Software Rasterizer Backend

Replace OpenGL with a CPU-based renderer (Cairo, Skia, or custom).

Pros:

  • Full rendering capability including screenshots
  • No GPU/X11 required

Cons:

  • Massive undertaking - essentially reimplementing SFML Graphics
  • Performance implications for complex scenes
  • Need to handle fonts, textures, shaders differently

Option B: Conditional Compilation (Dual Binary)

Build two versions: mcrogueface (full) and mcrogueface-headless (minimal).

Pros:

  • Clean separation
  • Headless binary much smaller

Cons:

  • Two binaries to maintain
  • Preprocessor complexity throughout codebase
  • Still need rendering solution for screenshots

Option C: Abstract Renderer Interface + Pluggable Backends

Create RenderBackend abstraction with multiple implementations:

  • SFMLBackend - current behavior (requires X11)
  • SoftwareBackend - CPU rendering (no X11)
  • NullBackend - no rendering (fastest, no screenshots)

Pros:

  • Maximum flexibility
  • Clean architecture
  • Could add other backends later (Vulkan, etc.)

Cons:

  • Largest refactor (~100+ files)
  • Need to abstract all SFML types or keep sfml-system

Option D: Offscreen Mesa/OSMesa

Use Mesa's software OpenGL implementation.

Pros:

  • Minimal code changes
  • Full OpenGL compatibility

Cons:

  • Additional runtime dependency
  • Performance overhead
  • May not work in all environments

Option E: Wait for SFML 3.x

SFML 3.0 may have better headless support.

Pros:

  • Upstream solution

Cons:

  • Unknown timeline
  • Unknown if it actually solves this
  • Would require SFML upgrade anyway

Option C (Abstract Renderer) with Option A (Software Rasterizer) as the headless backend.

This is the most future-proof approach but also the largest undertaking. It would:

  1. Define abstract RenderTarget interface (or wrapper around sf::RenderTarget)
  2. Create SoftwareRenderer using a library like:
    • stb_truetype + stb_image_write (minimal deps, C)
    • Cairo (mature, 2D focused)
    • Blend2D (modern, fast CPU 2D)
  3. Implement font rendering, texture loading, primitive drawing
  4. Wire up to existing HeadlessRenderer pathway

Scope Estimate

This is a Major Feature requiring:

  • Significant architectural changes
  • New dependencies evaluation
  • Extensive testing across both modes
  • Documentation updates

Not suitable for near-term v1.0 timeline. Should be planned as a post-1.0 enhancement or parallel long-term effort.

Workaround (Current)

For immediate needs, use virtual framebuffer:

xvfb-run -a ./mcrogueface --headless --exec script.py

Or in Docker:

RUN apt-get install -y xvfb
CMD ["xvfb-run", "-a", "./mcrogueface", "--headless", "--exec", "simulation.py"]
  • #153 - Simulation/step mode (works but still needs X11)
  • HeadlessRenderer implementation (src/HeadlessRenderer.h/cpp)

References

## Problem Statement McRogueFace cannot execute on servers without X11, even with `--headless` mode. The current headless implementation avoids creating a window but still requires the full graphics stack: ``` HeadlessRenderer → sf::RenderTexture → SFML Graphics → OpenGL → X11 ``` This prevents use cases like: - Running simulations on cloud servers (AWS, GCP, etc.) - CI/CD pipelines without Xvfb - Docker containers without GPU passthrough - Headless game servers for multiplayer scenarios - Batch processing / overnight simulation runs ## Root Cause Analysis **SFML's architecture is the fundamental blocker.** `sf::RenderTexture::create()` unconditionally initializes an OpenGL context, which on Linux requires X11 libraries and a display. ### Current Dependencies From `CMakeLists.txt`: - `sfml-graphics` (requires OpenGL) - `sfml-window` (requires X11/udev on Linux) - `sfml-system` (no graphics deps - safe) - `sfml-audio` (already disabled in headless) - `OpenGL::GL` (required by ImGui-SFML) ### SFML Type Pervasiveness - **311 occurrences** of `sf::` types across codebase - Core interface: `UIDrawable::render(sf::Vector2f, sf::RenderTarget&)` - Types everywhere: `sf::Vector2f`, `sf::Color`, `sf::FloatRect`, `sf::Font`, `sf::Texture` ## Requirements 1. **Must work without X11** - executable runs on headless Linux servers 2. **Must preserve screenshot capability** - simulations need visual output 3. **Must not break existing functionality** - current graphical mode unchanged 4. **Should minimize API changes** - Python scripts shouldn't need modification ## Potential Approaches ### Option A: Software Rasterizer Backend Replace OpenGL with a CPU-based renderer (Cairo, Skia, or custom). **Pros:** - Full rendering capability including screenshots - No GPU/X11 required **Cons:** - Massive undertaking - essentially reimplementing SFML Graphics - Performance implications for complex scenes - Need to handle fonts, textures, shaders differently ### Option B: Conditional Compilation (Dual Binary) Build two versions: `mcrogueface` (full) and `mcrogueface-headless` (minimal). **Pros:** - Clean separation - Headless binary much smaller **Cons:** - Two binaries to maintain - Preprocessor complexity throughout codebase - Still need rendering solution for screenshots ### Option C: Abstract Renderer Interface + Pluggable Backends Create `RenderBackend` abstraction with multiple implementations: - `SFMLBackend` - current behavior (requires X11) - `SoftwareBackend` - CPU rendering (no X11) - `NullBackend` - no rendering (fastest, no screenshots) **Pros:** - Maximum flexibility - Clean architecture - Could add other backends later (Vulkan, etc.) **Cons:** - Largest refactor (~100+ files) - Need to abstract all SFML types or keep sfml-system ### Option D: Offscreen Mesa/OSMesa Use Mesa's software OpenGL implementation. **Pros:** - Minimal code changes - Full OpenGL compatibility **Cons:** - Additional runtime dependency - Performance overhead - May not work in all environments ### Option E: Wait for SFML 3.x SFML 3.0 may have better headless support. **Pros:** - Upstream solution **Cons:** - Unknown timeline - Unknown if it actually solves this - Would require SFML upgrade anyway ## Recommended Path **Option C (Abstract Renderer) with Option A (Software Rasterizer) as the headless backend.** This is the most future-proof approach but also the largest undertaking. It would: 1. Define abstract `RenderTarget` interface (or wrapper around sf::RenderTarget) 2. Create `SoftwareRenderer` using a library like: - **stb_truetype + stb_image_write** (minimal deps, C) - **Cairo** (mature, 2D focused) - **Blend2D** (modern, fast CPU 2D) 3. Implement font rendering, texture loading, primitive drawing 4. Wire up to existing `HeadlessRenderer` pathway ## Scope Estimate This is a **Major Feature** requiring: - Significant architectural changes - New dependencies evaluation - Extensive testing across both modes - Documentation updates Not suitable for near-term v1.0 timeline. Should be planned as a post-1.0 enhancement or parallel long-term effort. ## Workaround (Current) For immediate needs, use virtual framebuffer: ```bash xvfb-run -a ./mcrogueface --headless --exec script.py ``` Or in Docker: ```dockerfile RUN apt-get install -y xvfb CMD ["xvfb-run", "-a", "./mcrogueface", "--headless", "--exec", "simulation.py"] ``` ## Related - #153 - Simulation/step mode (works but still needs X11) - HeadlessRenderer implementation (`src/HeadlessRenderer.h/cpp`) ## References - [SFML and headless rendering discussion](https://en.sfml-dev.org/forums/index.php?topic=27691.0) - [Cairo Graphics](https://www.cairographics.org/) - [Blend2D](https://blend2d.com/) - [OSMesa](https://docs.mesa3d.org/osmesa.html)
john added the
Major Feature
priority:tier3-future
labels 2025-12-07 04:37:43 +00:00
Author
Owner

Note: libtcod-headless Already Exists

The project already maintains a fork of libtcod without SDL dependencies: https://github.com/jmccardle/libtcod-headless

This means libtcod is not a blocker for true headless execution. The dependency situation is simpler than initially assessed:

Dependency Headless Status
libtcod-headless Ready (no SDL)
SFML Needs abstraction
CPython Works as-is
ImGui-SFML Tied to SFML

SFML is the sole blocker. The renderer abstraction work is the critical path for headless support.

This also has implications for future WASM/Emscripten builds — libtcod-headless should cross-compile cleanly without SDL concerns.

## Note: libtcod-headless Already Exists The project already maintains a fork of libtcod without SDL dependencies: https://github.com/jmccardle/libtcod-headless This means libtcod is **not a blocker** for true headless execution. The dependency situation is simpler than initially assessed: | Dependency | Headless Status | |------------|-----------------| | libtcod-headless | ✅ Ready (no SDL) | | SFML | ❌ Needs abstraction | | CPython | ✅ Works as-is | | ImGui-SFML | ❌ Tied to SFML | **SFML is the sole blocker.** The renderer abstraction work is the critical path for headless support. This also has implications for future WASM/Emscripten builds — libtcod-headless should cross-compile cleanly without SDL concerns.
john added the
system:rendering
label 2025-12-07 04:51:24 +00:00
Sign in to join this conversation.
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Reference: john/McRogueFace#157
No description provided.