Skip to content

Downstream fixes: dynamic-flags ABI semantics, per-object dirty skipping (-79% csmUpdateModel), dirty-detection precision (rebased onto 1.1.0) - #4

Open
970205011 wants to merge 5 commits into
SakuraMotion:masterfrom
970205011:port/downstream-fixes
Open

Downstream fixes: dynamic-flags ABI semantics, per-object dirty skipping (-79% csmUpdateModel), dirty-detection precision (rebased onto 1.1.0)#4
970205011 wants to merge 5 commits into
SakuraMotion:masterfrom
970205011:port/downstream-fixes

Conversation

@970205011

Copy link
Copy Markdown

PurismCore is used as a drop-in Live2DCubismCore.dll replacement under the official CubismFramework (Qt6 desktop app, production model: 3713 drawables / 823 params / 705 parts / 314 mask clips). These five fixes were developed and validated against v1.0.1 and are now rebased onto master (1.1.0) as part of the sync. Details in #3.

1. csmResetDrawableDynamicFlags semantics (ABI fix)

The official Core's reset only re-baselines the internal diff state; it does not clear the change bits in place. The previous in-place clear erased the current frame's csmVertexPositionsDidChange bits before the framework's csmGetDrawableDynamicFlags reads them, silently disabling the VERTEX_CHANGED-driven mask/clipping pipeline. With this fix, flag sampling matches the official DLL frame-for-frame (64/64 first frame, steady state 9/64 on our model).

2. Per-object dirty skipping in csmUpdateModel (+79%)

The official Core skips per-drawable work when inputs are unchanged. Per-object dirty checks (skip deformer/art-mesh/offscreen work when blend-key indices/weights are unchanged; skip disabled nodes and opacity-0 subtrees) bring steady-state csmUpdateModel from 5.88ms to 1.27ms (-79%, 4.9x) on our model, while remaining bit-identical to the non-dirty path when everything changes.

3. Dirty-detection precision

  • VERTEX_CHANGED is set only for meshes actually recomputed this frame (gated on mesh_changed), matching official sparsity and avoiding needless vertex re-uploads/mask passes.
  • Effective parent-part opacity changes invalidate dependent meshes (missing dependency in naive dirty checks).
  • Warp-grid (glue) meshes use two-endpoint fixed-point dirty propagation: if either endpoint is dirty, both recompute, preventing frame-over-frame vertex drift visible as mask sampling errors on animated limbs.

Validation

  • Builds cleanly with MinGW gcc 13.1 -O3 (shared library, PURISM_CORE_DLL), 0 errors.
  • Upstream unit tests: 402 passed, 0 failed.
  • A/B against the official DLL on a 3713-drawable model: steady-state flags/mask behavior indistinguishable; total frame cost ~10.5ms at 30fps with visually identical clipping output.

zhouyang and others added 5 commits September 2, 2026 10:40
…ore semantics

The official core's reset only re-baselines the internal diff state; the
flag array keeps showing the current frame's DidChange bits until the next
csmUpdateModel overwrites them. Host apps (Cubism Framework) call
csmUpdateModel -> csmResetDrawableDynamicFlags back to back and read the
flags later while drawing, so clearing the array in place erased the
current frame's bits before any read (csmVertexPositionsDidChange was
always 0, silently disabling the mask/clipping pipeline and leaving
uninitialized buffer indexes to be read).

Update the regress_flag_reset scenario to assert the aligned semantics.
Skip deformers/art meshes/offscreens/glues whose blend key index/weight are unchanged with no dirty blend-shape target, no enable flip, and no recomputed parent deformer. Hidden nodes (enable=false/opacity=0) and their children are skipped entirely. Steady-state csmUpdateModel drops 5.88ms to 1.27ms (-79%/4.9x, -O3); mask steady state 9/64 unchanged, no crash over 4h+ run.
…lidation

psm__update_flags set csmVertexPositionsDidChange for every enabled
drawable each frame (Normal dirty branch). In steady state with dirty
skipping most enabled meshes are not recomputed, so their vertex buffers
are already current and do not need re-upload / mask re-draw. Gate the
flag on m->mesh_changed[i] (set in psm__process_art_meshes, which runs
before psm__update_flags) so only genuinely recomputed meshes report a
vertex change. This makes the framework skip the mask atlas pass on most
frames (Draw ~17ms -> ~7ms) while keeping clipping correct.
The dirty-update skip rule assumed a mesh's parent-part contribution only
changes when the part chain's enable state flips (caught by en_flip). But
the part's effective opacity also changes at runtime when the user adjusts
a part opacity via the UI (csmSetPartOpacity -> input_opacity), without any
enable flip. Clean meshes then kept their stale final opacity until they
happened to be recomputed for another reason, so part opacity edits did not
take effect.

Track per-part effective-opacity changes: add part_opa_dirty / part_opa_prev
arrays, set part_opa_dirty[i] in psm__apply_part_opacity when the recomputed
effective opacity differs from the previous frame, and treat a changed parent
part as a recompute reason in psm__process_art_meshes.
psm__apply_glues 对 glue 两个端点 mesh 做 stateful 位置混合。旧 psm__glue_mesh_dirty 只在 glue 自身脏时标记两端;若仅一端因动画 self_dirty 而重算,另一端保持 clean,则 clean 端仍保存上一帧 post-glue 的最终位置,重应用 glue 时出现 double-counting,且该端 mesh_changed=0 导致 reverse_y 遗漏、VERTEX_CHANGED 缺失 -> mask atlas 不重绘 -> 手臂闪烁。

新逻辑:先按 self_dirty/blend/enable-flip/parent/part-opa 计算 base_dirty;再对 glue 做固定点传播(glue 自身脏或任一端 base_dirty 则两端都标记 glue_mesh_dirty=1);调用点移到 process_art_meshes 前,确保 enable/deformer/part 状态就绪。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant