Expo config plugin#
{
"expo": {
"plugins": ["@apricot/react"]
}
}It applies:
| Setting | Why |
|---|---|
The react-native-webgpu plugin |
The GPU canvas |
Android minSdkVersion at least 26 (keeps a higher value) |
react-native-webgpu uses Android 8.0 hardware-buffer APIs |
iOS CADisableMinimumFrameDurationOnPhone |
Lets the frame loop run at 120 Hz on ProMotion iPhones |
Run npx expo prebuild (or a new dev build) after adding it.
Metro: withApricot#
const { getDefaultConfig } = require("expo/metro-config");
const { withApricot } = require("@apricot/metro");
module.exports = withApricot(getDefaultConfig(__dirname));It:
- adds
glbandgltfto the asset extensions, sorequire("./car.glb")works (exported asASSET_EXTENSIONS); - resolves every
threeandthree/webgpuimport to three's WebGPU build, so the bundle holds one copy of three. Keep any other resolver you had:withApricotcalls it for everything else.
A .gltf must be self-contained (buffers and images embedded); a separate .bin cannot be found at runtime. Convert to .glb with apricot-models.
apricot-models CLI#
From @apricot/metro. Install it as a dev dependency and run it with npx.
Optimise#
npx apricot-models <file-or-folder>... [-o <out-folder>]Each .glb or .gltf input (folders: every model inside) becomes <name>.glb in the output folder.
What it does:
- removes duplicate and unused data, welds vertices;
- quantizes meshes (
KHR_mesh_quantization): smaller files with no runtime decoder; - decodes Draco and meshopt input on your computer (Hermes has no WebAssembly, so the phone cannot);
- rejects KTX2 / Basis textures with a clear message (use PNG or JPEG);
- converts
.gltfwith a separate.bininto one.glb; - prints the size before and after, and budget warnings.
Inspect#
npx apricot-models inspect <file> [--json]Prints triangles, parts (name, triangles, materials; heaviest first), materials with how many parts use them, textures (size, type, bytes), animation clip names and budget warnings. These are the names <Model materials>, joints, animations and model kits select by. --json prints a ModelSummary plus warnings.
Budgets#
For one hero model on a mid-range phone:
| Budget | Value |
|---|---|
| Triangles | 150,000 |
| Texture side | 2048 px |
| Texture memory | 8 MB total |
Beyond these the CLI warns; the model still works but may cost frame rate or memory. Each separate mesh also costs about 80 µs per frame (measured on a Galaxy A55): merge parts in Blender where you can, and use <Instances> for repeats.
Node API: @apricot/metro/models#
import { BUDGET, budgetWarnings, createModelIO, optimizeModel, summarize } from "@apricot/metro/models";| Export | Description |
|---|---|
optimizeModel(input, { quantize? }) |
Promise<{ bytes, report: { before, after, byteLength, warnings } }>. input is .glb bytes or a gltf-transform Document |
summarize(document) |
A ModelSummary: meshes, materials, textures, vertices, triangles, parts, material names, texture list, animations, extensions |
createModelIO() |
A gltf-transform NodeIO with every extension and the Draco and meshopt decoders |
BUDGET |
{ triangles: 150000, textureSide: 2048, textureBytes: 8388608 } |
budgetWarnings(summary) |
Warning strings for a summary over budget |