How backends work

This page documents what’s needed to implement a backend for rendercanvas. The purpose of this documentation is to help maintain current and new backends. Making this internal API clear helps understanding how the backend-system works. Also see https://github.com/pygfx/rendercanvas/blob/main/rendercanvas/stub.py.

Note

It is possible to create a custom backend (outside of the rendercanvas package). However, we consider this API an internal detail that may change with each version without warning.

class rendercanvas.stub.StubLoop

The Loop represents the event-loop that drives the rendering and events.

Some backends will provide a corresponding loop (like qt and ws). Other backends may use existing loops (like glfw and jupyter). And then there are loop-backends that only implement a loop (e.g. asyncio or trio).

Backends must subclass BaseLoop and implement a set of methods prefixed with _rc_.

_rc_init()

Put the loop in a ready state.

Called when the first canvas is created to run in this loop. This is when we know pretty sure that this loop is going to be used, so time to start the engines. Note that in interactive settings, this method can be called again, after the loop has stopped, to restart it.

  • Import any dependencies.

  • If this loop supports some kind of interactive mode, activate it!

  • Optionally call _mark_as_interactive().

  • Return None.

_rc_run()

Start running the event-loop.

  • Start the event-loop.

  • The loop object must also work when the native loop is started in the GUI-native way (i.e. this method may not be called).

  • If the backend is in interactive mode (i.e. there already is an active native loop) this may return directly.

async _rc_run_async()

Run async.

_rc_stop()

Clean up the loop, going to the off-state.

  • Cancel any remaining tasks.

  • Stop the running event-loop, if applicable.

  • Be ready for another call to _rc_init() in case the loop is reused.

  • Return None.

_rc_add_task(async_func, name)

Add an async task to the running loop.

This method is optional. A subclass must either implement _rc_add_task or _rc_call_later.

  • Schedule running the task defined by the given co-routine function.

  • The name is for debugging purposes only.

  • The subclass is responsible for cancelling remaining tasks in _rc_stop.

  • Return None.

_rc_call_later(delay, callback)

Method to call a callback in delay number of seconds.

This method is optional. A subclass must either implement _rc_add_task or _rc_call_later.

  • If you implememt this, make _rc_add_task() call super()._rc_add_task().

  • If delay is zero, this should behave like “call_soon”.

  • No need to catch errors from the callback; that’s dealt with internally.

  • Return None.

class rendercanvas.stub.StubCanvasGroup(default_loop: BaseLoop)

The CanvasGroup representss a group of canvas objects from the same class, that share a loop.

The initial/default loop is passed when the CanvasGroup is instantiated.

Backends can subclass BaseCanvasGroup and set an instance at their RenderCanvas._rc_canvas_group. It can also be omitted for canvases that don’t need to run in a loop. Note that this class is only for internal use, mainly to connect canvases to a loop; it is not public API.

The subclassing is only really done so the group has a distinguishable name. Though we may add _rc_ methods to this class in the future.

class rendercanvas.stub.StubRenderCanvas(*args, size: Tuple[float, float] | None = (640, 480), title: str | None = '$backend', update_mode: UpdateModeEnum = 'ondemand', min_fps: float = 0.0, max_fps: float = 30.0, vsync: bool = True, present_method: str | None = None, **kwargs)

The RenderCanvas represents the canvas to render to.

Backends must subclass BaseRenderCanvas and implement a set of methods prefixed with _rc_. This class also shows a few other private methods of the base canvas class, that a backend must be aware of.

_final_canvas_init()

Must be called by the subclasses at the end of their __init__.

This sets the canvas logical size and title, which must happen after the widget itself is initialized. (Doing this automatically can be done with a metaclass, but let’s keep it simple.)

_set_size_info(physical_size, pixel_ratio)

Must be called by subclasses when their size changes.

Backends must not submit a “resize” event; the base class takes care of that, because it requires some more attention than the other events.

The subclass must call this when the actual viewport has changed. So not in _rc_set_logical_size(), but e.g. when the underlying GUI layer fires a resize event, and maybe on init.

_process_events()

Process events and animations.

Called from the scheduler.

_draw_frame_and_present()

Draw the frame and present the result.

Errors are logged to the “rendercanvas” logger. Should be called by the subclass at its draw event.

_rc_canvas_group = <rendercanvas.stub.StubCanvasGroup object>

Class attribute that refers to the CanvasGroup instance to use for canvases of this class. It specifies what loop is used, and enables users to changing the used loop.

_rc_gui_poll()

Process native events.

_rc_get_present_methods()

Get info on the present methods supported by this canvas.

Must return a small dict, used by the canvas-context to determine how the rendered result will be presented to the canvas. This method is only called once, when the context is created.

Each supported method is represented by a field in the dict. The value is another dict with information specific to that present method. A canvas backend must implement at least either “screen” or “bitmap”.

With method “screen”, the context will render directly to a surface representing the region on the screen. The sub-dict should have a window field containing the window id. On Linux there should also be platform field to distinguish between “wayland” and “x11”, and a display field for the display id. This information is used by wgpu to obtain the required surface id.

With method “bitmap”, the context will present the result as an image bitmap. On GPU-based contexts, the result will first be rendered to an offscreen texture, and then downloaded to RAM. The sub-dict must have a field ‘formats’: a list of supported image formats. Examples are “rgba-u8” and “i-u8”. A canvas must support at least “rgba-u8”. Note that srgb mapping is assumed to be handled by the canvas.

_rc_request_draw()

Request the GUI layer to perform a draw.

Like requestAnimationFrame in JS. The draw must be performed by calling _draw_frame_and_present(). It’s the responsibility for the canvas subclass to make sure that a draw is made as soon as possible.

The default implementation does nothing, which is equivalent to waiting for a forced draw or a draw invoked by the GUI system.

_rc_force_draw()

Perform a synchronous draw.

When it returns, the draw must have been done. The default implementation just calls _draw_frame_and_present().

_rc_present_bitmap(*, data, format, **kwargs)

Present the given image bitmap. Only used with present_method ‘bitmap’.

If a canvas supports special present methods, it will need to implement corresponding _rc_present_xx() methods.

_rc_set_logical_size(width, height)

Set the logical size. May be ignored when it makes no sense.

The default implementation does nothing.

_rc_close()

Close the canvas.

Note that BaseRenderCanvas implements the close() method, which is a rather common name; it may be necessary to re-implement that too.

Backends should probably not mark the canvas as closed yet, but wait until the underlying system really closes the canvas. Otherwise the loop may end before a canvas gets properly cleaned up.

Backends can emit a closed event, either in this method, or when the real close happens, but this is optional, since the loop detects canvases getting closed and sends the close event if this has not happened yet.

_rc_get_closed()

Get whether the canvas is closed.

_rc_set_title(title)

Set the canvas title. May be ignored when it makes no sense.

The default implementation does nothing.

_rc_set_cursor(cursor)

Set the cursor shape. May be ignored.

The default implementation does nothing.

class rendercanvas.base.WrapperRenderCanvas(*args, size: Tuple[float, float] | None = (640, 480), title: str | None = '$backend', update_mode: UpdateModeEnum = 'ondemand', min_fps: float = 0.0, max_fps: float = 30.0, vsync: bool = True, present_method: str | None = None, **kwargs)

A base render canvas for top-level windows that wrap a widget, as used in e.g. Qt and wx.

This base class implements all the re-direction logic, so that the subclass does not have to. Subclasses should not implement any of the _rc_ methods. Subclasses must instantiate the wrapped canvas and set it as _subwidget.