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:

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.