Quick answer: Set render_target_update_mode to UPDATE_ALWAYS for continuous, or UPDATE_ONCE with manual re-triggering for on-demand updates. Default UPDATE_WHEN_VISIBLE may not trigger if the texture isn’t actively displayed.
You set up a SubViewport to render a 3D character for a portrait preview in the UI. The first frame shows the character; subsequent frames show the same first frame even when the character moves. The viewport isn’t re-rendering.
Five Update Modes
SubViewport’s update mode controls when the GPU re-renders into its texture:
- UPDATE_DISABLED: never render. Useful to freeze a snapshot.
- UPDATE_ONCE: render exactly one frame then auto-revert to DISABLED. Used for capture-then-stop.
- UPDATE_WHEN_VISIBLE: default. Renders only while the viewport’s texture is sampled by a visible CanvasItem.
- UPDATE_WHEN_PARENT_VISIBLE: similar but tied to the SubViewportContainer’s visibility.
- UPDATE_ALWAYS: render every frame regardless of visibility.
The Fix
@onready var portrait: SubViewport = $PortraitViewport
func _ready():
portrait.render_target_update_mode = SubViewport.UPDATE_ALWAYS
The portrait viewport now re-renders every frame — movement is visible immediately. Cost is one extra render pass per frame; usually fine for small viewports.
On-Demand Updates for Performance
If the portrait should only refresh when the character changes (e.g., armor swap):
portrait.render_target_update_mode = SubViewport.UPDATE_DISABLED
func refresh_portrait():
portrait.render_target_update_mode = SubViewport.UPDATE_ONCE
Each refresh call renders one frame. Other frames cost nothing. Ideal for static UI previews.
Diagnosing Default Mode Confusion
UPDATE_WHEN_VISIBLE is sensible but tricky: the viewport renders only if a visible CanvasItem (TextureRect, Sprite2D) is currently sampling the viewport’s texture. If your UI hides the texture momentarily, the viewport pauses. Switch to UPDATE_ALWAYS if you don’t need the optimization.
Use SubViewportContainer for Display
The cleanest way to display a SubViewport in UI is to wrap it in a SubViewportContainer:
SubViewportContainer
SubViewport
Camera3D
Character (PackedScene)
The container automatically scales the viewport’s texture and binds it. Update modes still apply to the SubViewport.
Verifying
Move the character (or whatever is inside the viewport). The portrait should update visibly each frame with UPDATE_ALWAYS, or update only on refresh calls with the on-demand pattern. Use the Debugger’s Monitor panel to confirm frame rendering counts.
“SubViewport doesn’t render constantly by default — it tries to be efficient. If you need live updates, switch to UPDATE_ALWAYS.”
Use UPDATE_ONCE for inventory previews — massive perf win over UPDATE_ALWAYS when items don’t change often.