Performance Profiling & Debugging
Texturge is a CPU-driven animation evaluation system. The 21-channel per-glyph per-layer curve evaluation constitutes the main cost. Understanding the hot paths and the performance model helps maintain frame rate under high-load scenarios.
Performance Model
Curve Evaluation Cost Estimate
The per-frame animation evaluation cost is approximately:
Total evaluations ≈ glyph count × layer count × non-empty channel count (≤21)
Typical scenario examples:
| Scenario | Glyphs | Layers | Non-empty channel evaluations per frame (est.) |
|---|---|---|---|
| Simple typewriter | 50 | 1 | ~1,050 |
| Multi-layer title | 30 | 3 | ~3,000 |
| Rich text dialog | 100 | 3 | ~10,000 |
Actual cost is far below the formula’s upper bound because:
- Empty channels skipped —
FPerLayerCurves::Evaluateonly evaluates channels withNumKeys > 0 - Static glyphs skipped —
bIsStaticglyphs directly returnDefaultState, zero curve evaluation - Not-yet-started / finished glyphs —
HasStarted/HasFinishedearly exits
Hot Paths
FBakedGlyph::Evaluate is the core hot spot of animation evaluation:
bIsStatic→ returnsDefaultState(zero cost)LocalTime < 0(not started) → returns the identity state (transparent only withbHideFirstFrame)- Iterates
LayerCurves, callingFPerLayerCurves::Evaluate FPerLayerCurves::Evaluatefor non-empty channels: temporarily builds anFRichCurve(BuildRichCurveFromKeys) →Eval(LocalTime)→ combines by BlendMode
Key performance points:
- Curve keys are pre-baked into
TArray<FRichCurveKey>at compile time — no curve parsing at runtime BuildRichCurveFromKeysbuilds a temporaryFRichCurveon every evaluation — this can accumulate with large text + many layers (known technical limitation; future optimization: caching with dirty flags or binary search over sorted keys)- Compilation happens on demand; zero bake overhead at runtime
General Unreal Insights Analysis
Texturge registers no custom Trace channels or console statistics commands; use Unreal Insights’ generic CPU sampling:
- Start Trace recording (console
Trace.Start, or start from an Unreal Insights session) - Run the animation scenario for at least 5 seconds to collect samples
Trace.Stopto stop, then open the.utracein Insights- Zoom into the animation playback window on the
CPUtimeline and locate the call-stack share ofUTextAnimationStageController::TickAnimation,FPerLayerCurves::Evaluate, and SlateOnPaint
Analysis priority:
- Check the total time share of
TickAnimation; over 2ms/frame warrants deeper investigation - Expand the evaluation call stack and confirm whether the glyph count × layer count is too large
- If
OnPainttime is high, check whether manyUAnimatedTextBlockwidgets are rendering simultaneously (batching issues, see”Performance Best Practices”)
Fingerprint Caching
Mechanism
FAnimationCompiler computes dual fingerprints (uint64) with CityHash64:
| Fingerprint | Computed from | Cache hit condition |
|---|---|---|
TextFingerprint | CityHash64(SourceText) | text unchanged |
BlueprintFingerprint | path + all GlyphAnimation asset signatures (MovieScene + Track/Section GetSignature()) + BlueprintVisible CDO property values + StageTransitionConfig + CompileCount + GeneratedClass address | Blueprint not recompiled, parameters unchanged |
Cache Invalidation Triggers
- Text content changes → text fingerprint changes → re-bake triggered
- Editing
UGlyphAnimationcurves in Sequencer → Section signatures change → Blueprint fingerprint changes - Blueprint recompile (including variable changes) →
CompileCountincrements → fingerprint changes SetBlueprintVariableparameter changes → CDO property values change → fingerprint changes
Re-bake Cooldown
MinFramesBetweenRebakes = 3 — during parameter sweeps (e.g. per-frame variable changes), changes take effect with at most 3 frames of delay, preventing flood re-baking. Cache invalidation decisions live at the widget layer / editor preview layer (FTexturgePreviewBakeCache); the compile time does not manage refresh policy.
Data Scale Recommendations
| Scale | Recommendation |
|---|---|
| Glyphs per widget | ≤ 500 (regular single-line text); split very long text across multiple widgets |
| Layers per glyph | 2–4 layers suffice for most effects (preset library defaults to ≤ 3) |
| Concurrent animated widgets on screen | ≤ 8 rich-text widgets (multi-entry simultaneous playback is expensive) |
| Looping animations | use layer NumberOfLoops or controller looping instead of per-frame reconstruction |
TIP
Performance optimization priority: reduce glyph count (
SkipAtto skip whitespace) → reduce layer count → reduce non-empty channel count (delete unused tracks) → batch widgets.