Skip to main content

Module texture_bus

Module texture_bus 

Source
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::Cpu frame — uploaded with queue.write_texture into an Rgba8Unorm texture the pass owns (re-created only when the extent or the device changes) and bound under the registration’s id.
  • a SourcePayload::Native frame — handed to the NativeImporter installed for its NativeHandleKind with set_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_done fence 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§

ImportedTexture
What a NativeImporter produced: a view the engine can sample, and its extent in texels.
NativeHandle
An opaque platform GPU buffer a NativeImporter turns into a texture.
SourceFrame
One frame a source hands the bus.
SourceMeta
The extent and pixel format of a SourceFrame, as returned by SourceFrame::into_parts.
TextureRegistration
A frame source registered with register_texture_source; unregisters on drop.

Enums§

ImportError
Why a NativeImporter could not import a handle. The frame is released and the last good frame stays shown.
NativeHandleKind
Which platform object a NativeHandle points at — the key a NativeImporter is installed under.
SourceFormat
The pixel layout of a SourceFrame.
SourcePayload
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§

NativeImporter
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 kind right now.
register_texture_source
Registers source as 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 importer for every SourcePayload::Native frame of kind, process-wide, returning the importer it replaced.

Type Aliases§

ReleaseCallback
The callback a frame carries to tell its producer the bus is done with it.