Expand description
The consumer half of the external-texture frame bus: a frame source — any closure that hands over the newest frame it has — registered here becomes a scene texture the display list draws by id.
§Shape
register_texture_source takes a closure returning
Option<SourceFrame> and registers an
ExternalPass for it under an id of its own (the
reserved texture-source namespace, SceneTextureId::for_texture_source).
Once per engine frame, ahead of the scene, the pass polls the closure:
None— nothing new; whatever was shown last stays shown.- a
SourcePayload::Cpuframe — uploaded withqueue.write_textureinto anRgba8Unormtexture the pass owns (re-created only when the extent or the device changes) and bound under the registration’s id. - a
SourcePayload::Nativeframe — handed to theNativeImporterinstalled for itsNativeHandleKindwithset_native_importer; with none installed it is refused, warned about once per registration and released.
Every frame’s SourceFrame::release callback runs exactly once, on every
path — including a panic unwinding through the pass (a SourceFrame
dropped still holding its callback runs it) and a device replaced by a
newer one (the bus polls the old device until its retire fences fired
before it lets go of it). A retire fence’s callback holds its queue alive
until it fires, so a device that is never polled again keeps its retiring
frames’ releases pending; a device lost outright releases them on its
next poll. When depends on what the bus did with the payload:
- a CPU frame — right after the upload is enqueued (the queue copies the pixels into staging synchronously, so nothing reads the payload again);
- a refused frame (malformed, no importer, import error, or the retire backlog full) — right away, since nothing ever sampled it;
- an imported native frame — not when the importer returns. The release is
the producer’s signal that it may reuse the platform buffer (a camera
or decoder ring recycles on it), and the GPU samples the imported
texture until the frame is replaced and the submission that last drew it
has finished. So the release is held until both hold: the imported
texture was retired (a newer frame replaced it, or the registration
was dropped / the device changed) and a
Queue::on_submitted_work_donefence taken at that moment has fired (two chained fences for a dropped registration — what those cover, and what they do not, is under “A registration dropped mid-drain” below). It runs on whichever thread polls the device, and the imported texture’s view is dropped just before it.
An importer that needs the platform buffer alive for longer than its own call keeps its own reference (in the imported texture’s lifetime, typically); the bus never releases that one.
The returned TextureRegistration reports the id to draw by
(TextureRegistration::scene_texture_id, what a widget’s
draw_scene_texture names) and unregisters on drop.
§Bounded retire backlog
At most MAX_RETIRING (4) retired native textures per registration wait
on their fence at once, so a producer with a 3-deep buffer ring never sees
more than its ring in flight. When a native frame arrives with the backlog
full (after a non-blocking Device::poll gave the fences a chance), the
newest frame is dropped: its release runs at once, nothing was sampled,
and the last good frame stays shown.
§A registration dropped mid-drain
A registration dropped (or a device changed) while its imported texture is
still shown retires it through two chained fences: the second
on_submitted_work_done is taken when the first fires. What the two hops
guarantee is narrower than “the frame that sampled it finished”: a fence
fires once every submission made before it was taken has completed, and
one taken with no work in flight fires at the next device poll. So the
release waits for every submission made before the drop, plus a second
completion point after that, and nothing more. A registration dropped
mid-drain — between a frame’s record, which bound the view, and that
frame’s submit — may release its last native frame while the frame that
sampled it is still executing (or not yet submitted): an interim submit
(atlas growth, another registration’s backlog poll) can satisfy both hops
first. The producer may then overwrite its buffer and that one frame
tear. It is not a memory-safety hole — the importer holds its own
reference to the platform buffer for the imported texture’s lifetime, and
wgpu keeps the texture alive for every submission that uses it — only a
possible visual glitch on the last frame of a dropped registration. Tying
the release to the dropping frame’s own submission is follow-up work.
§Why a closure, not a plugin type
The renderer never depends on the plugin substrate, and the substrate never names the renderer, so neither can hand the other its frame type. The types here are this crate’s own plain-data twins of the substrate’s producer-side ones, with matching constructors and accessors; the bridge is a closure written by code that depends on both (a plugin’s facade-side half, or the app):
let registration = register_texture_source(move || {
let frame = sink.take_latest()?;
// Check what can be refused while the frame still guards its release.
if frame.meta().format != TextureFormat::Rgba8 {
frame.release();
return None;
}
// SAFETY: the callback is moved straight into the `SourceFrame` built
// below, with the handle it protects, and nothing fallible runs in
// between.
let (meta, payload, release) = unsafe { frame.into_parts() };
let payload = match payload {
FramePayload::Cpu(pixels) => SourcePayload::Cpu(pixels),
// SAFETY: forwards the producer's own `NativeHandle::new`
// contract. The producer-side handle was consumed by value, so
// this one is the only handle for the buffer, and it travels
// with the release callback attached below.
FramePayload::Native(h) => SourcePayload::Native(unsafe {
NativeHandle::new(/* the matching NativeHandleKind */, h.ptr())
}),
};
let mut out = SourceFrame::new(meta.width, meta.height, SourceFormat::Rgba8, payload);
if let Some(release) = release {
out = out.on_release(release);
}
Some(out)
});Both frame types release on drop, so the bridge consumes the producer
frame by value and moves its payload and callback across together. The
handle is move-only, so it cannot be copied, and a frame’s payload and
release are private, so outside unsafe a handle cannot leave the frame
that carries its release: the bridge’s into_parts is the unsafe step
that promises the callback is carried on.
§Pixels and compositing
A CPU frame is tightly packed premultiplied-alpha RGBA8, width * 4 bytes
per row, top row first, sampled as Rgba8Unorm (bytes pass through
unconverted). The engine composites the bound texture blended, never as
opaque — it cannot read the texels to know (docs/LIMITATIONS.md’s
engine-scene-texture-always-blended), and the bus inherits that.
§Where it runs
Exactly where external passes run: every engine-tier SurfaceRenderer
frame drains them, so a registration shows on the desktop, Android, iOS
and web shells alike, and nowhere a surface is not presenting. A shell that
rebuilds its engine on the same device (a fresh surface) gets the current
view bound again on its first frame; a device change drops what was
shown until the source yields its next frame.
Structs§
- Imported
Texture - What a
NativeImporterproduced: a view the engine can sample, and its extent in texels. - Native
Handle - An opaque platform GPU buffer a
NativeImporterturns into a texture. - Source
Frame - One frame a source hands the bus.
- Source
Meta - The extent and pixel format of a
SourceFrame, as returned bySourceFrame::into_parts. - Texture
Registration - A frame source registered with
register_texture_source; unregisters on drop.
Enums§
- Import
Error - Why a
NativeImportercould not import a handle. The frame is released and the last good frame stays shown. - Native
Handle Kind - Which platform object a
NativeHandlepoints at — the key aNativeImporteris installed under. - Source
Format - The pixel layout of a
SourceFrame. - Source
Payload - Where a
SourceFrame’s pixels live.
Constants§
- MAX_
RETIRING - How many retired native textures one registration lets wait on their fence at once (see the module docs).
Traits§
- Native
Importer - Turns a platform GPU buffer into a texture on the frame’s device, without a CPU copy.
Functions§
- clear_
native_ importer - Removes the importer installed for
kind, returning it. - native_
importer_ installed - Whether an importer is installed for
kindright now. - register_
texture_ source - Registers
sourceas a scene texture, polled once per engine frame for its newest frame (see the module docs), and returns the registration that names it and keeps it alive. - set_
native_ importer - Installs
importerfor everySourcePayload::Nativeframe ofkind, process-wide, returning the importer it replaced.
Type Aliases§
- Release
Callback - The callback a frame carries to tell its producer the bus is done with it.