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().

  • Make sure its ok if this is called a second time, after a run.

  • 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 this loop.

True async loop-backends (like asyncio and trio) should implement this. When they do, _rc_call_later is not used.

Other loop-backends can use the default implementation, which uses the asyncadapter which runs coroutines using _rc_call_later.

  • If you implement this, make _rc_call_later() raise an exception.

  • 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.

Backends that implement _rc_add_task should not implement this. Other backends can use the default implementation, which uses a scheduler thread and _rc_call_soon_threadsafe. But they can also implement this using the loop-backend’s own mechanics.

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

  • Take into account that on Windows, timers are usually inaccurate.

  • 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.

_rc_call_soon_threadsafe(callback)

Method to schedule a callback in the loop’s thread.

Must be thread-safe; this may be called from a different thread.

class rendercanvas.stub.StubCanvasGroup(default_loop: BaseLoop)

The CanvasGroup represents 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: Literal['bitmap', 'screen', None] = None, **kwargs)

The RenderCanvas represents the canvas to render to.

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

Backends must call self._final_canvas_init() at the end of its __init__(). This will set the canvas’ logical size and title.

Backends must call self._size_info.set_physical_size(width, height, native_pixel_ratio), whenever the size or pixel ratio changes. It must be called when the actual viewport has changed, so typically not in _rc_set_logical_size(), but e.g. when the underlying GUI layer fires a resize event. Setting the size implicitly requests a new draw.

Backends must also call self.submit_event(), if applicable, to produce events for mouse and keyboard. Backends must not submit a “resize” event; the base class takes care of that. See the event spec for details.

_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_info(present_methods)

Select a present method and return corresponding info dict.

This method is only called once, when the context is created. The subclass can use this moment to setup the internal state for the selected presentation method.

The present_methods represents the supported methods of the canvas-context, in order of context-preference, possibly filtered by a user-specified method. A canvas backend must implement at least the “screen” or “bitmap” method.

The returned dict must contain at least the key ‘method’, which must match one of the present_methods. The remaining values represent information required by the canvas-context to perform the presentation, and optionally some (debug) meta data. The backend may optionally return None to indicate that none of the present_methods is supported.

With method “screen”, the context will render directly to a (virtual) surface. The 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. For Pyodide the ‘window’ field should be the <canvas> object.

With method “bitmap”, the context will present the result as an image bitmap. For the WgpuContext, the result will first be rendered to a texture, and then downloaded to RAM. The 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 backend to call _time_to_draw().

The backend must call _time_to_draw as soon as it’s ready for the next frame. It is allowed to call it directly (rather than scheduling it). It can also be called later, but it must be called eventually, otherwise it will halt the rendering.

This functionality allows the backend to throttle the frame rate. For instance, backends that implement ‘remote’ rendering can allow new frames based on the number of in-flight frames and downstream throughput.

_rc_request_paint()

Request the backend to do a paint, and call _time_to_paint().

The backend must schedule _time_to_paint to be called as soon as possible, but (if applicable) it must be in the native animation frame, a.k.a. draw event. This function is analog to requestAnimationFrame in JavaScript. In any case, inside _time_to_paint() a call like context.get_current_texture() should be allowed.

When the present-method is ‘screen’, this method is called to initiate a draw. When the present-method is ‘bitmap’, it is called when the draw (and present) is completed, so the native system can repaint with the latest rendered frame.

If the implementation of this method does nothing, it is equivalent to waiting for a forced draw or a draw invoked by the GUI system.

_rc_force_paint()

Perform a synchronous paint.

The backend should, if possible, invoke its native paint event right now (synchronously). The default implementation just calls _time_to_paint().

_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: Literal['bitmap', 'screen', 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.