npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@oxy.so/bloom-avatar-native

v0.1.2

Published

Shared native GPU presentation for Bloom's existing avatar engine

Readme

Bloom native avatar GPU adapter

This optional Android adapter is intentionally separate from Bloom: installing ordinary Bloom must not autolink a native graphics dependency. The adapter uses the existing avatar engine, assets, shaders and motion controllers. It does not stream screenshots or substitute images for live animation.

The Android adapter creates one headless Expo GL context. Each independently laid-out native TextureView owns an EGL surface in that context. The existing engine renders its atlas, including 4× MSAA and its final presentation pass; native GPU framebuffer blits present the corresponding cell into each view. Native composition owns clipping, transforms, scrolling and input. No atlas pixels cross the JavaScript bridge.

The stock Expo GL 58.0.3 implementation lacks renderbufferStorageMultisample. The adapter supplies that native operation on the same GL command queue after a JS flush; this preserves the renderer's requested samples instead of reducing quality. Of the 79 GL methods observed in the startup/idle/work/dispose trace, this was the only method listed as unimplemented in that installed release. This audit is not a claim that every GPU or texture extension is supported.

node scripts/build-avatar-native-runtime.mjs compiles the same TypeScript engine into a static CommonJS factory. It accepts the absolute asset-directory URL. Shader strings are compiled into the bundle, while all 433 generated external assets matched the web runtime's names and SHA-256 bytes in the initial build. It does not evaluate remote code, use import.meta, inline binary assets or create global DOM replacements.

The initial physical Pixel 8a proof rendered twelve independently composited native views from one context with MSAA 4×, transparent output, rotated cards, native press handling and unmount/remount. That first proof used animated test geometry; the subsequent integration described below uses the actual shared avatar engine. An iOS presenter is not implemented yet.

Choice of backend

  • Expo GL retains the WebGL2 API and therefore the existing custom GLSL material equations. Its headless context alone has no native presentation; this companion supplies the GPU surfaces.
  • React Native WebGPU can configure multiple native canvases with one device; its default Android TextureView supports ordinary alpha composition. This is a viable alternative backend, but Three RawShaderMaterial supports only WebGLRenderer. Adopting it would also require a shader/render-pass port with visual parity gates, not merely replacing the canvas component.
  • Skia's WebGPU interoperability shares Dawn devices/textures without CPU copies. Matching Dawn releases and a new rendering backend remain prerequisites; adding Skia does not translate our GLSL.
  • React Native Filament supplies a native physically based renderer and GLB loading. Replacing the present material equations, authored asset loading and motion binding would be a larger port; visual equivalence is not established by using a physically based renderer.

A single WebView is not itself a texture-export API. Android virtual displays can target a Surface, but routing an entire browser compositor through that path adds a separate native presentation system. WKWebView's public snapshot API produces an image, not a reusable GPU texture. The chosen adapter avoids depending on browser compositor internals on either platform. A native EAGL presentation adapter could preserve GLSL on iOS, but OpenGL ES is deprecated there and requires explicit device validation; it must not be described as implemented based on the Android proof.

Integration contract

Install this companion and its declared peers in the native application, then rebuild that application's native binary. Use the companion version whose exported runtime revision is hosted by your provider. Ordinary Bloom remains usable without it. Bloom accepts companion ABI version 1; the companion also requires native module ABI 1. Installing new JavaScript without the matching native rebuild cannot silently use an incompatible binary. Bloom's optional literal require selects the adapter only on Android and only after the hosted manifest.json matches the compiled adapter's complete expected manifest, including runtime.mjs, every code chunk, and every binary asset. A missing module, another runtime revision, or an unavailable manifest retains the existing WebView route. Concurrent checks share one request. Failed checks expire after five seconds; successful checks expire after one minute for later mounts. A running adapter remains on its bundled code revision. Production hosting must use immutable versioned directories or retain the pinned files for the lifetime of released native applications.

The native build first compiles a fresh browser runtime from the same checkout into .local/avatar-native-compatible-web, verifies native asset hashes against it, and writes its complete manifest into the companion. The regular tracked web runtime must be regenerated from those same inputs before deployment. A code-only shader/controller change invalidates compatibility even when asset hashes are unchanged. The companion pins Expo GL 58.0.3 because its context registry and native GLContext integration are internal APIs. Updating that peer requires another API, pixel and lifecycle audit.

Use the neutral public revision subpath to select immutable hosting without copying a hash into application code. It loads no React or native modules and works in Node CommonJS/ESM, web bundlers and Metro:

import { AVATAR_RUNTIME_REVISION } from '@oxy.so/bloom-avatar-native/runtime-revision';

const characterRuntimeUrl =
  `https://alia.onl/bloom-character/releases/${AVATAR_RUNTIME_REVISION}/runtime.mjs`;

The revision is SHA-256 of the exact UTF-8 bytes of the generated complete asset-manifest.json, including its final newline. The hosted manifest.json must have those same bytes; do not reserialize it. Upload the complete matching browser directory into that revision directory once and retain it for installed native clients. Runtime compatibility still compares every manifest file hash.

Pools are shared across providers for one runtime URL directory and initial size bucket (64, 128 or 320 logical pixels). Equal-size instances share one context, renderer, asset cache and atlas. Each pool contains at most sixteen slots, reduced further when the device texture/renderbuffer/viewport limits require it. Additional slots open another pool. The limit reserves room for every existing slot to grow to the unchanged 512px maximum backing without an unbounded atlas; allocation failure is reported, never compensated by reducing quality. Different initial buckets use separate contexts to avoid drawing a small icon at a hero's resolution. Resizing an existing group changes its engine size in place and may promote that group to its largest member; it preserves motion state, clock positions and consumed action keys. This trade-off avoids silently resetting a working avatar or transferring private state into another renderer.

Every destination is an ordinary transparent TextureView. Native composition retains its clipping, scrolling, transforms and stacking; JavaScript does not stream positions. Native pre-draw visibility observations send only boolean transitions. Offscreen views and background applications hold their existing pose. Surfaces becoming available or being recreated invalidate a fresh frame, including a paused avatar. onReady follows a successful native swap, rather than merely issuing draw commands. One avatar's snapshot reads its retained native output on explicit request and encodes PNG off the GL thread; animation never performs image readback or encoding.

The presenter submits synchronously into Expo's GL queue and acknowledges completion by ticket. At most two presentation batches may be outstanding; this permits CPU/GPU overlap without an unbounded frame queue. Context destruction follows queued batches and surface destruction. Native EGL failures reach Bloom's existing unavailable state. This adapter does not yet recover a lost native GL context automatically. It restores EGL draw/read surfaces and framebuffer/scissor state after every batch.

The physical integration now renders twelve real avatars through the shared slot and latest-intent controllers, rather than test geometry. Android debug observations on a Pixel 8a found approximately 292–295 actual TextureView updates per five seconds with two batches outstanding; a single batch serialized work at approximately 186–188. These are development-build observations, not a cross-device performance guarantee. All twelve paused views held their counters unchanged across four five-second samples. Formal comparison and remaining lifecycle evidence belong in the repository benchmark report. iOS continues through the existing WebView renderer; this package currently contains no iOS presenter.