arkavo-org / arkavo-org/VRMMetalKit

GLTFMetalKit: persistent triple-buffered morph vertex buffer pool

Open
#247 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Swift
Stars
6
Forks
2
Avg merge
18h 51m
Merged PRs (30d)
26

Description

## Context

Reviewer feedback on PR #241 (Major #1):

> Each call to `drawCalls(animationIndex:time:)` that touches a morphed primitive allocates a fresh `MTLBuffer` via `device.makeBuffer(bytes:length:options:[])`. For canonical Khronos `AnimatedMorphCube` (24 vertices) this is a non-issue. For a typical face blendshape rig (8–30k vertices, 30–80 targets, 60 fps), this becomes a significant per-frame allocation and copy that will fragment the Metal heap and stall on the autorelease pool.

Cheap intermediate fix before the GPU compute kernel ports (issue #244). Tracked here so it's not lost.

## What's needed

- Cache `MTLBuffer`s per (primitiveIndex, frame-in-flight) so the rebuild path reuses storage instead of allocating each call.
- Triple-buffer to allow the GPU to consume frame N's morphed vertices while the CPU builds frame N+2's. Standard Metal in-flight pattern.
- Add a "weights unchanged since last frame" guard so static rest-pose morphed primitives skip the blend + upload entirely.
- Storage location: probably a new `GLTFMorphBufferPool` class owned by the asset (mutable state), or threaded explicitly through the `drawCalls(...)` call.

## Acceptance

- A synthetic stress test (e.g. 30k verts × 16 morph targets at 60 fps for 5 seconds) doesn't show heap-fragmentation warnings or autorelease-pool stalls in Instruments.
- Memory footprint is bounded (~3× per-primitive vertex buffer size, not unbounded growth).
- `AnimatedMorphCube` continues to render identically.

## Relationship to #244

Independent. The GPU compute kernel (#244) is a separate axis: it moves the *math* to GPU. This issue is about *buffer lifecycle* on the CPU/upload side. Doing both is the canonical face-blendshape path; either alone is an improvement.

## Estimate

~3 hours. The triple-buffer pattern is well-established in Metal sample code; the bulk is wiring the lifecycle correctly without breaking the value-type-ish `GLTFAsset` API.

Contributor guide

Open the contributing guide

Research direction

Start at GLTFAsset's drawCalls(animationIndex:time:) morphed-primitive rebuild path and review PR #241's allocation behavior. Trace how asset state can own or receive a GLTFMorphBufferPool, then use the AnimatedMorphCube and the synthetic 30k-vertex stress case to verify bounded triple-buffered storage, skipped unchanged weights, and identical rendering.

Written by the indexing model from the issue text.

Assessment

Tech stack
swift
Domain
computer-graphics, performance
Issue type
Refactor
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
50/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.