Takes packed frames off the renderer and puts them on the panel.
BB.NSK.Display.Viewport renders into this process, which receives
{:emerge_skia_frame, %VideoInterop.Frame{}} messages carrying a frame
already packed to the panel's format. Nothing here converts pixels.
A refresh takes seconds and the renderer can produce frames faster than that, so drawing happens in a spawned process and only the latest frame waits: a burst of renders costs one refresh of the last one rather than a queue of stale ones. That matters because the robot's safety state can change several times inside a single refresh.
Emerge sends nothing at all when a screen renders to an unchanged tree, which is most of the deduplication and it happens before any of this. What is left here catches the rarer case of two different trees packing to the same bytes, such as a change that lands where the layout clips it.
That upstream silence is also a trap, and configure/3 works around it. A
full refresh is asked for when the panel needs clearing rather than when the
picture changed — it is what removes the ghosting partial refreshes leave —
so it is exactly the case where the tree is most likely to be identical and
no frame will arrive. Asking for one therefore arms a timer, and if nothing
has been rendered by the time it fires the retained frame is redrawn instead.
Without that, a panel showing an unchanging screen would never be cleaned up,
and BB.NSK.Display.Controller.refresh/1 would silently do nothing.
On the host there is no panel, so the draw function is a no-op and the sink is a place to read the last frame from — which is how a screen is previewed without hardware.
Summary
Functions
Returns a specification to start this module under a supervisor.
Blank the panel.
Set the mode the next frames will arrive in, and how the panel should refresh.
The last frame the renderer produced, packed exactly as the panel receives it.
How many frames the renderer has delivered.
Redraw the frame already on the panel.
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec clear(GenServer.server()) :: :ok | {:error, term()}
Blank the panel.
Waits for any refresh already under way, so that this is the last thing the panel is told rather than a command it drops on its way out.
@spec configure(GenServer.server(), :bw1 | :gray2, :full | :partial) :: :ok
Set the mode the next frames will arrive in, and how the panel should refresh.
The renderer is told the mode separately, when the viewport mounts, so this is what the sink validates arriving frames against.
@spec current_frame(GenServer.server()) :: binary() | nil
The last frame the renderer produced, packed exactly as the panel receives it.
@spec frames(GenServer.server()) :: non_neg_integer()
How many frames the renderer has delivered.
Counts frames arriving, not refreshes: an unchanged screen still renders, and the panel is spared the draw rather than the renderer being spared the work. A count that stops climbing while the screen is being changed means the renderer has stopped, which is otherwise invisible from this end.
@spec redraw(GenServer.server(), :full | :partial) :: :ok
Redraw the frame already on the panel.
Nothing is rendered — these are the bytes the panel was last sent — so this is how a screen that has not changed still gets its ghosting cleared.
@spec start_link(keyword()) :: GenServer.on_start()