UMG Binding Surgeon rewrites the UMG bindings that cost a Blueprint VM call every frame

Epic’s own Optimization Guidelines for UMG say it plainly. When you bind attributes to fields in your UI they poll the attribute every frame. This is inefficient so you should avoid using bound attributes.

That advice is correct but it’s almost impossible to act on. Bindings accumulate one at a time over years across everyone who ever touched the HUD. By the time somebody profiles the UI there are two hundred of them and there’s no obvious way to tell which ones are expensive or which ones are safe to change.

Here’s the part that’s not in the documentation. Not all bindings cost the same.

Look at UWidgetBlueprintGeneratedClass::BindDynamicDelegates. A binding whose SourcePath is valid gets served by a native property binder and UMG reads the property directly. A binding without one falls through to BindUFunction. That second path is a Blueprint VM call per widget per property per frame. A Bind you made by picking a function in the details panel is that second kind.

This plugin finds every binding of the second kind and rewrites the ones it can prove into the first kind. Same value. No VM call.

The dangerous failure here isn’t a crash. It’s a wrong rewrite where a binding quietly stops producing the value it used to and nobody notices until a build. So every ambiguity resolves toward not rewriting. A rewrite is granted by three positive proofs and all three are required.

One is structural. The bound function graph must contain a function entry and a function result and exactly one variable read and nothing else. Any other node kind refuses the binding and the report names the class it found. A graph that only reads V returns V so a path that reads V returns the same thing.

Two is resolution. The variable must be a real property on the compiled widget class and the widget must carry a bindable Property Delegate. A local variable inside the graph looks identical to a member read at the node level and cannot be reached by a binding path.

Three is the engine’s own verdict. FEditorPropertyPath::Validate runs before anything is written. That’s the same check the UMG compiler runs and it asks FindBinderClassForDestination for the native binder that serves the delegate return type. So a rewrite that clears proof three isn’t merely permitted. The native path is proven to engage.

It’s fail safe even if all three were wrong. The original function name is never cleared. UMG carries it through for property kind bindings too and falls back to it whenever the native binder cannot be generated. So the worst case of a rewrite this tool got wrong is the behaviour you already had rather than a dead binding. Clearing the function name to make the patch look tidy would convert a fail safe into a fail silent.

What it refuses is reported by name with what was actually found in your asset. FunctionNotPureRead names the nodes it could not prove. FunctionReadsMultipleVariables means no single read reproduces the value. SourcePropertyNotFound means the graph reads a local. DrivenByAnimation refuses every binding on a widget any animation touches because a rewrite could fight it for control. PathRejectedByEngine prints UMG’s own error text verbatim.

Everything’s a dry run unless you pass Apply. Apply also writes an undo manifest and then rescans the whole project and fails the run if the second look disagrees with the first. Revert puts every rewrite back exactly including the original MemberGuid.

There’s a headless commandlet so this can gate a build. Budget N exits 1 when more than N bindings still call a Blueprint function every frame. It counts Blueprint call bindings rather than total bindings and that distinction matters. A native property binding is still polled every frame. It just costs a property read instead of a VM entry. If the budget counted the total then a project that ran this tool and accepted every rewrite and became measurably cheaper would score exactly the same.

Built for Unreal Engine 5.8. Editor plugin plus commandlet. It writes no runtime code and adds no runtime cost.

Disclosure. I build these with AI assistance and the code ships raw and readable so you can audit every line of it before you run it against your project.