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:

ScenarioGlyphsLayersNon-empty channel evaluations per frame (est.)
Simple typewriter501~1,050
Multi-layer title303~3,000
Rich text dialog1003~10,000

Actual cost is far below the formula’s upper bound because:

  1. Empty channels skippedFPerLayerCurves::Evaluate only evaluates channels with NumKeys > 0
  2. Static glyphs skippedbIsStatic glyphs directly return DefaultState, zero curve evaluation
  3. Not-yet-started / finished glyphsHasStarted / HasFinished early exits

Hot Paths

FBakedGlyph::Evaluate is the core hot spot of animation evaluation:

  1. bIsStatic → returns DefaultState (zero cost)
  2. LocalTime < 0 (not started) → returns the identity state (transparent only with bHideFirstFrame)
  3. Iterates LayerCurves, calling FPerLayerCurves::Evaluate
  4. FPerLayerCurves::Evaluate for non-empty channels: temporarily builds an FRichCurve (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
  • BuildRichCurveFromKeys builds a temporary FRichCurve on 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:

  1. Start Trace recording (console Trace.Start, or start from an Unreal Insights session)
  2. Run the animation scenario for at least 5 seconds to collect samples
  3. Trace.Stop to stop, then open the .utrace in Insights
  4. Zoom into the animation playback window on the CPU timeline and locate the call-stack share of UTextAnimationStageController::TickAnimation, FPerLayerCurves::Evaluate, and Slate OnPaint

Analysis priority:

  1. Check the total time share of TickAnimation; over 2ms/frame warrants deeper investigation
  2. Expand the evaluation call stack and confirm whether the glyph count × layer count is too large
  3. If OnPaint time is high, check whether many UAnimatedTextBlock widgets are rendering simultaneously (batching issues, see”Performance Best Practices”)

Fingerprint Caching

Mechanism

FAnimationCompiler computes dual fingerprints (uint64) with CityHash64:

FingerprintComputed fromCache hit condition
TextFingerprintCityHash64(SourceText)text unchanged
BlueprintFingerprintpath + all GlyphAnimation asset signatures (MovieScene + Track/Section GetSignature()) + BlueprintVisible CDO property values + StageTransitionConfig + CompileCount + GeneratedClass addressBlueprint not recompiled, parameters unchanged

Cache Invalidation Triggers

  • Text content changes → text fingerprint changes → re-bake triggered
  • Editing UGlyphAnimation curves in Sequencer → Section signatures change → Blueprint fingerprint changes
  • Blueprint recompile (including variable changes) → CompileCount increments → fingerprint changes
  • SetBlueprintVariable parameter 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

ScaleRecommendation
Glyphs per widget≤ 500 (regular single-line text); split very long text across multiple widgets
Layers per glyph2–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 animationsuse layer NumberOfLoops or controller looping instead of per-frame reconstruction

TIP

Performance optimization priority: reduce glyph count (SkipAt to skip whitespace) → reduce layer count → reduce non-empty channel count (delete unused tracks) → batch widgets.

images/insights-profiling.png — Unreal Insights profiling screenshot: CPU timeline locating UTextAnimationStageController::TickAnimation and Slate OnPaint call stacks, frame time histogram below marking the peak during animation playback