LavaUI docs GitHub

protocol GPUResourceHost

Swift

protocol GPUResourceHost : AnyObject, Sendable

Who names the things that live on a GPU.

A GlyphInstance carries a font id and an image command carries a texture id, and both are only meaningful to the process that owns the atlas they index. In a normal app that process is this one, so the ids come from the Editor and nobody has to think about it. Under a shared renderer they come from the compositor, and a locally-invented number would draw the wrong face or the wrong picture — silently, because a stale id is still a valid index.

So this is the seam between the two modes, and it is deliberately the whole seam: a client differs from a windowed app in who answers these five questions and in nothing else. Shaping, measurement, layout and emit are unchanged, because none of them ever needed a device — only naming does.

Note what is not here: pixels. A remote host is asked to register a file it can open itself, never handed a decoded bitmap, which is the same rule the control plane follows for draw lists. Whoever owns the GPU does the decode, because they are about to own the result.

Sources/LavaUI/ResourceHost.swift:24

Methods

func registerFont(path: String, pixelSize26_6: UInt32, faceIndex: UInt32, rasterFlags: UInt32) -> UInt32?

Registers a face and returns the id to stamp into GlyphInstance.

Idempotent per face, which is a stronger claim than per path: the host opens the file and keys on what it contained, so two names for one file are one id, and a file whose bytes changed is correctly a new one. Nil if the file will not load.

The size is 26.6 fixed point — pixels times 64 — because a Float is not something two processes can compare for equality and agree on, and FreeType wants it quantised anyway. rasterFlags is a FontRasterFlags.raw; unknown bits are refused, not ignored.

Sources/LavaUI/ResourceHost.swift:36

func registerFont(path: String, pixelSize: Float) -> UInt32?

Face 0 of path at pixelSize, hinted the renderer's default way — what a caller that just wants a font file at a size should use.

Rounds the size to 26.6 here rather than making every caller do it, which is the point: there is exactly one conversion from "pixels a human typed" to "the number the renderer keys on", and it lives here.

Sources/LavaUI/ResourceHost.swift:135

func registerImage(path: String, maxPixelSize: UInt32, turn: ImageTurn) -> UIImage?

Registers an image, decoded from path, capped to maxPixelSize (0 = native) and quarter-turned by turn, returning a handle with the id and the decoded size.

Blocks. For anything an app needs before its first frame — an icon, a brand mark — where a placeholder would be worse than a stall.

The turn is asked of the host rather than done by the caller because one of the two hosts is a compositor the caller cannot decode for — see ImageTurn. Most callers want none and should use the two-argument convenience below.

Sources/LavaUI/ResourceHost.swift:52

func registerImage(data: [UInt8], maxPixelSize: UInt32) -> UIImage?

Registers an image from encoded bytes (PNG, JPEG, …) rather than a path, for one that was downloaded, generated, or unpacked and never touched the filesystem.

Identity is the content, so registering the same bytes twice is one texture without the caller keeping a key. Prefer registerImage(path:) whenever there is a path: under a compositor this one sends the file through shared memory, where a path sends a path.

Blocks, like its sibling.

Sources/LavaUI/ResourceHost.swift:77

func registerImage(path: String, maxPixelSize: UInt32) -> UIImage?

The overwhelmingly common case: no turn.

A convenience rather than a defaulted argument because a protocol requirement cannot carry one, and every caller but a viewer's rotate button wants a picture the way it is stored.

Sources/LavaUI/ResourceHost.swift:113

func registerImageAsync(path: String, maxPixelSize: UInt32, turn: ImageTurn, completion: @escaping @Sendable (UIImage?) -> Void)

The same registration off the calling thread, with completion run on the main queue.

A protocol requirement rather than a helper because the two hosts split the work differently and only they know where. Locally, the decode belongs on a worker and the upload must be back on the main thread — it touches the device. Remotely the whole thing is one call, and there is no main-thread half at all.

Sources/LavaUI/ResourceHost.swift:62

func registerImageAsync(path: String, maxPixelSize: UInt32, completion: @escaping @Sendable (UIImage?) -> Void)

Registers an image off the calling thread with no turn; completion runs on the main queue. See registerImageAsync(path:maxPixelSize:turn:completion:).

Sources/LavaUI/ResourceHost.swift:119

func registerImageAsync(path: String, maxPixelSize: UInt32, turn: ImageTurn, completion: @escaping @Sendable (UIImage?) -> Void)

Off-thread registration for a host that has no main-thread half — a remote one, where the call is already a round trip and touches nothing local. Editor overrides this, because it does.

Sources/LavaUI/ResourceHost.swift:147

func releaseImage(key: String)

Drops one reference to a registered image. key is the cacheKey from the handle, not the bare path — the same file at two decode sizes is two textures.

Sources/LavaUI/ResourceHost.swift:104

func uploadPixels(_ pixels: UnsafeRawBufferPointer, width: UInt32, height: UInt32, stride: UInt32, format: PixelFormat) -> UIImage?

Uploads pixels the caller rasterised itself — a PDF page, a chart — as a new texture.

pixels holds height rows stride bytes apart, the last of which needs only width * 4. The bytes are read before this returns, so the caller may reuse its buffer at once. Nil if the buffer is shorter than that, or the host could not take it.

Every call is a new texture, under a key from ImageStore.pixelKey(), and releaseImage(key:) frees it rather than keeping it dormant: nothing could ever ask for the same pixels again. Not cached by ImageStore; the caller owns the handle and releases it.

Blocks. Under a compositor this is a copy into shared memory and one round trip, from any thread. The built-in renderer uploads to its own device, which is the main thread's alone, so on Editor call it from there.

Sources/LavaUI/ResourceHost.swift:96