Skip to content

1.1.2-rc

Pre-release
Pre-release

Choose a tag to compare

@arkavo-com arkavo-com released this 05 Sep 02:04
· 22 commits to main since this release
dbea992

Pre-release on 1.1.1. Ships the SpringBone warmup fix, the per-frame joint palette ring, and the outline hull rebuild. Tagged as a pre-release because #427 moves settled SpringBone poses on real avatars — it is promoted to 1.1.2 once Muse validates its assets. SPM stable resolvers skip pre-releases, so from: "1.1.0" stays on 1.1.1 until then.

Behaviour change — SpringBone (#427)

AvatarSample A in VRM 0.0 rendered with hair flared above the forehead after loading. Its hair explicitly authors gravityPower = 0; the loader's 30-step warmup suppressed authored stiffness throughout those steps and then froze the displaced pose.

  • Warmup uses authored stiffness and drag from its first step. The interactive startup ramp (settlingFrames, 1 - smoothstep(0, 60, frames)) is no longer applied during warmup — warmup is exactly when settling should finish, so the counter is zeroed on entry instead of being drained per step. Removes the VMK#240/#292 patch pair, which only masked the drain arithmetic.
  • Collision responses are published even when every chain authors zero gravity. The previous anyGravity gate let an unrelated chain's gravity decide whether these nodes received their settled pose.
  • VRM 0.0 sphere collider offsets get the missing handedness conversion. Offsets now reflect Z into the glTF node's local frame before the Ry180 facing rotation, matching three-vrm's VRM0 loader: (x, y, z) → (-x, y, z), previously (-x, y, -z). VRM 1.0 assets are unaffected.

What moves: four trajectory baselines regenerated. 21 of 46 bones change per quality tier; maximum mean-axis shift is 8.65 mm (ultra), 11.36 mm (high), 16.86 mm (medium), 36.78 mm (low). The 1 mm regression tolerance is unchanged. Authored gravity values, warmup step count, and GPU dispatch count are all unchanged; no per-frame work is added.

Renderer (#426)

  • Per-frame joint palette ring. With multiple frames in flight the renderer overwrote one CPU-written joint palette while earlier GPU frames could still be reading it. Each existing in-flight slot now owns a persistent palette, selected by the uniform-buffer slot; compositor views share that selection until frame completion.
  • Per-skin pose versions. An older slot copies the latest idle pose once instead of re-evaluating matrices, which also prevents a stale pose when animation stops. Slots already holding the current pose skip the copy. The identity-palette debug override invalidates the cached pose when toggled.
  • MToon material uniforms are prepared once per render pass and reused across primitives and dedicated outlines. Per-draw values and expression overrides apply to copies; the cache resets each pass, so runtime material edits still take effect.
  • Cost: two additional padded palette buffers per renderer — 2 × (totalJointCount + 1024) × 64 bytes. No added per-frame GPU wait. Measured on M4 Max (animated AvatarSample_A, 1024×1024, MSAA 1, physics off, 200 warmup + 5,000 frames): CPU median 0.109–0.110 ms → 0.111 ms, GPU p95 0.203–0.205 ms → 0.204–0.205 ms. Timing is essentially unchanged; no speedup is claimed — this is a correctness fix.

Outlines and shaders (#425)

  • skinned_mtoon_outline_vertex now reads morphed positions, first-person hidden flags, and the outline width multiply texture, and extrudes through the shared applyMToonOutlineExtrusion instead of a hand-inlined copy. mtoon_outline_vertex reads morphed positions too, and renderMToonOutlines binds all three with the depth-prepass fallbacks.
  • First-person cull now runs after hull extrusion in both shaders. The world-coordinates branch rebuilt clipPos and resurrected hidden vertices as outline hulls, which also affected the merged path.
  • skinned_vertex (debug-only) skins through vrm_skin rather than a third inline copy.
  • OutlineFirstPersonHiddenTests asserts on rendered pixels; verified to fail with the outline cull removed.
  • Shader slices rebuilt — macOS, iOS, iOS Simulator, visionOS, visionOS Simulator. The MToon known-good hash is re-baselined. Consumers pick these up only via this tag.

Closes #149, #150, #154, #156, #361, #363.

Docs (#425)

The performance guide, the DocC LoadingVRMModels article, and the API doc comments are resynced with actual behaviour:

  • A cancel inside a primitive decode throws Swift's CancellationError, not GLTFError.loadingCancelled, on both the serial and parallel mesh paths and regardless of enableCancellation. Callers that special-case cancellation must match both error types.
  • Three VRMLoadingOptimization flags are marked no-effect (below).
  • Corrected: skinned cull bounds (fixed 50% inflation, then hips translation), decode-limiter scope (every load, not only .parallelMeshLoading), the SpringBone sleep snippet, depth-prepass coverage (opaque, non-face), and the outline lever (vertex-side, not fragment-side).
  • Documents the runtime frame-rate levers: pipeline binary archive, frustum culling, morph and sleep gates, sprite cache, depth prepass.
  • CLAUDE.md's stale ">8 active morph targets switches to GPU" claim is replaced; morph compute always runs, capped at 8 active targets per primitive dispatch.

API

Source-compatible with 1.1.1. Nothing added, nothing removed. The only API-surface changes are doc comments that correct what the existing symbols do:

  • VRMLoadingOptimization.skipSecondaryUVs — reserved, no effect. Only TEXCOORD_0 is ever read, so secondary UVs are already skipped on every load.
  • VRMLoadingOptimization.parallelTextureDecoding — reserved, no effect. Use parallelTextureLoading for concurrent texture work. Note this flag is in VRMLoadingOptimization.default, so that set does no parallel work.
  • VRMLoadingOptimization.lazyTextureLoading — reserved, no effect. Textures always load eagerly.
  • VRMConstants.Limits.morphComputeThreshold — unused. Morph accumulation always runs on the GPU compute path; there is no CPU evaluation to switch away from. Kept for source compatibility.
  • RendererConfig.enableDepthPrepass — covers opaque, non-face geometry; face materials keep their current depth state.

Default-path rendering changes only through the SpringBone warmup fix above and the outline first-person cull.

Verification

  • Full suite on dbea992: 2080 tests enumerated, exit 0, zero failures (swift test --parallel --num-workers 14 -j 16 --disable-sandbox).
  • Xcode Cloud green on dbea992: Build - visionOS, Test - iOS, Test - macOS. CodeQL, Lint, and Shaders green.
  • Release VRMRender build with fresh renders of AvatarSample A 0.0, A 1.0, and U 1.0 on #427 — the upward hair flare is gone.

Not yet validated: Muse asset pass over the new SpringBone settled poses. That is the gate for promoting this to 1.1.2.

Full diff: 1.1.1...1.1.2-rc