Skip to content

Texture Pinning (API)

Roblox texture assets are loaded lazily. The first time an asset id appears on-screen, Roblox fetches the bytes, decodes the image, uploads it to the GPU, then renders. That round-trip can introduce a single-frame stutter on the first emission of an effect with a fresh texture.

A subtler problem follows. Even after Roblox decodes the bytes, it can later evict them under memory pressure if no visible instance is still referencing the asset id. So a once-decoded texture can stutter again on its second appearance, hours later, after a busy stretch.

The plugin’s texture-pinning system handles both: an eager preload, plus a persistent reference that prevents eviction. The trick is a hidden, near-invisible UI reference to each pinned asset id — the renderer still counts it as a live consumer of the asset, so the bytes stay resident.

Walks every transformed item plus every raw ParticleEmitter and Trail under root, collects their texture asset ids, and pins each one. Returns immediately; decode kicks off in a background task.spawn (so a long preload doesn’t block your script).

local root = workspace.SwordEffects
Part_Icles:Preload(root, true)
-- now any emit under SwordEffects is texture-warm

The force parameter controls which items under root are eligible:

  • force = false (default) — only items with the PreloadTexture attribute set to true are pinned. Pairs with the per-emitter PreloadTexture toggle: each effect opts itself in at authoring time, then a single tree-wide :Preload call warms exactly the opted-in set.
  • force = true — every transformed / PE / Trail under root is pinned, regardless of the attribute. Use when you want bulk warming without authoring per-item flags.

If neither PreloadTexture is set anywhere under root and you don’t pass force = true, nothing gets pinned and the call is a silent no-op.

Inverse of :Preload. Walks the same hierarchy and releases this root’s claim on every pinned asset under it. The force parameter behaves identically to :Preload’s — false respects PreloadTexture, true sweeps every transformed / PE / Trail.

Part_Icles:Deload(root, true)

Pins are reference-counted per asset id. If two roots both pinned the same texture, deloading one root keeps the texture pinned via the other root’s claim. The pin only releases (and the texture becomes evictable again) when the last owner deloads.

Use after a level transition, or when an effect set won’t appear again this session. On memory-constrained devices this lets you reclaim the budget for the next set.

The per-emitter PreloadTexture attribute (toggle in the Advanced section of Part / Beam / ImageLabel / Model) is the opt-in flag. The script API drives the pinning sweep. Models gained the PreloadTexture toggle as of v33; earlier versions only exposed it on Part / Beam / ImageLabel.

A typical pattern:

-- Game start: warm everything tagged for this session.
Part_Icles:Preload(ReplicatedStorage.Effects)
-- Player joins arena 1: warm arena-specific effects.
Part_Icles:Preload(ReplicatedStorage.Arenas.Castle.Effects)
-- Player leaves arena 1, enters arena 2: release arena 1, warm arena 2.
Part_Icles:Deload(ReplicatedStorage.Arenas.Castle.Effects)
Part_Icles:Preload(ReplicatedStorage.Arenas.Forest.Effects)

These calls all default to force = false, so each warms exactly the items the artist opted in via PreloadTexture.

Pinning is per-asset-id, not per-instance — pinning the same texture id in two folders shares the warming. Decoding happens once.

Pin lifetimes are owner-tracked. In Studio, the plugin watches the common parents (Workspace, ServerStorage, ReplicatedStorage, StarterPack, ReplicatedFirst) and auto-releases a pin’s claim when its owning instance gets deleted — so authoring-time deletes don’t leak texture references. The shipped runtime that lives in your published game does not wire that auto-release; in a shipped game, call :Deload(root) explicitly when an emitter set is retired, or call :Deactivate to clear every pin in one pass.

With the API documented, the last chapter — Examples — walks through four common patterns: sword-slash spark, fireball clone-from-storage, on-screen-only emission for performance, and recursive :AbsoluteEmit for nested hierarchies.