For some applications, it may be that we want to run Arcade, but not open up a window. We might want to draw to a buffer and save an image to be used in a server or data science visualization. In remote cloud operations, we might not even have a monitor for the computer. Running Arcade this way is called headless mode.
Arcade can render in headless mode on Linux servers with EGL installed. This should work both in a desktop environment and on servers and even in virtual machines. Both software and hardware rendering should be acceptable depending on your use case.
We are leveraging the headless mode in pyglet. If you are seeking knowledege about the inner workings of headless, that’s the right place to look.
Enabling headless mode#
Headless mode needs to be configured before arcade is imported. This can be done in the following ways:
# Before arcade is imported import os os.environ["ARCADE_HEADLESS"] = "True" # The above is a shortcut for import pyglet pyglet.options["headless"] = True
This of course also means you can configure headless externally.
$ export ARCADE_HEADLESS=True
To quickly check the enviroment such as renderer and versions:
$ python -m arcade Arcade 2.6.12 ------------- vendor: AMD renderer: AMD Radeon(TM) Vega 11 Graphics (RAVEN, DRM 3.41.0, 5.13.0-37-generic, LLVM 12.0.0) version: (4, 6) python: 3.9.9 (main, Dec 20 2021, 08:19:16) [GCC 9.3.0] platform: linux
How is this affecting my code?#
In headless mode we don’t have any window events or inputs events.
This means events like
will never be called. A project not created for a headless setting
will need some tweaking.
Note that the window itself still has a framebuffer you can render
to and read pixels from. The size of this framebuffer is the size
you specify when creating the window. More framebuffers can be
created through the
ArcadeContext if needed.
If you are creating and destroying a lot of arcade objects
you might want to look into
In Arcade we normally do garbage collection of OpenGL objects
once per frame by calling
Simple headless mode#
For simpler applications we don’t need to subclass the window.
# Configure headless before importing arcade import os os.environ["ARCADE_HEADLESS"] = "true" import arcade # Create a 100 x 100 headless window window = arcade.open_window(100, 100) # Draw a quick rectangle arcade.draw_rectangle_filled(50, 50, 50, 50, color=arcade.color.AMAZON) # Dump the framebuffer to a png image = arcade.get_image(0, 0, *window.get_size()) image.save(f"framebuffer.png")
You are free to
clear() the window and render
new contents at any time.
Headless mode while extending the Arcade Window#
For Arcade users extending the window, this method makes more sense.
run() method supports headless
mode and will emulate Pyglet’s event loop by calling
flip() (swap buffers)
in a loop until you close the window.
import os os.environ["ARCADE_HEADLESS"] = "true" import arcade class App(arcade.Window): def __init__(self): super().__init__(200, 200) self.frame = 0 self.sprite = arcade.Sprite( ":resources:images/animated_characters/female_adventurer/femaleAdventurer_idle.png", center_x=self.width / 2, center_y=self.height / 2, ) def on_draw(self): self.clear() self.sprite.draw() # Dump the window framebuffer to disk image = arcade.get_image(0, 0, *self.get_size()) image.save("framebuffer.png") def on_update(self, delta_time: float): # Close the window on the second frame if self.frame == 2: self.close() self.frame += 1 App().run()
You can also split your code into
if needed. Doing it this way might make it simpler to work
with headless and non-headless mode during development. You just
need to programmatically close the window and switch views.
We can easily separate logic with the
flag. When calling
run() we also garbage collect OpenGL
resources every frame.
The lower level rendering API is of course still available
arcade.Window.ctx. It exposes methods
to create framebuffers, textures, shaders (including compute shaders)
and other higher level wrappers over OpenGL types.
When working in a multi-gpu environment you can also select a specific device id. This is 0 by default and must be set before the window is created. These device ids usually refers to a physical device (graphics card) or a virtual card/device.
# Default setting pyglet.options['headless_device'] = 0 # Use the second gpu/device pyglet.options['headless_device'] = 1
If you run into issues or have questions please create an issue on github or join our discord server.