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
Looprepresents 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
BaseLoopand 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_lateris not used.Other loop-backends can use the default implementation, which uses the
asyncadapterwhich 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_taskshould 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()callsuper()._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
CanvasGrouprepresents a group of canvas objects from the same class, that share a loop.The initial/default loop is passed when the
CanvasGroupis instantiated.Backends can subclass
BaseCanvasGroupand set an instance at theirRenderCanvas._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
RenderCanvasrepresents the canvas to render to.Backends must subclass
BaseRenderCanvasand 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
CanvasGroupinstance 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_methodsrepresents 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 thepresent_methodsis supported.With method “screen”, the context will render directly to a (virtual) surface. The dict should have a
windowfield containing the window id. On Linux there should also beplatformfield to distinguish between “wayland” and “x11”, and adisplayfield 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_drawas 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_paintto 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 torequestAnimationFramein JavaScript. In any case, inside_time_to_paint()a call likecontext.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
BaseRenderCanvasimplements theclose()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.