Skip to content

en RenderHandle Pipeline

HoCha113 edited this page Jun 22, 2026 · 3 revisions

Documentation moved: This page is archived. See the up-to-date guide at innovault.wiki.

Expanded coverage on innovault.wiki

中文版本

RenderHandle Pipeline

Client render pipeline: maps Terraria draw stages to overridable virtual methods; manages RenderTarget2D and instance ordering.

Client / GPU only: On dedicated server (Main.dedServ), ScreenTargets are not created and UpdateBySystem plus all draw hooks are skipped.


What is it? When to use?

RenderHandle is the abstract base class (VaultType<RenderHandle>) for InnoVault's render pipeline. RenderHandleLoader maps Terraria hooks (On_Main.*, On_FilterManager.EndCapture, etc.) to layered virtual methods; each registered instance is invoked in ascending Weight order.

Good for:

  • Full-screen post-process, color grading, screen distortion (EndCaptureDraw / PostEndCaptureDraw)
  • Custom drawing between tiles / players / entities (DrawBeforeTilesDrawAfterEntities)
  • Built-in subsystems: PRTRender (particles), Model3DRenderer (3D), ActorRender (Actor layered draw)

Not for:

  • Server logic or network sync → ModSystem / SyncVar Network Sync
  • Regular HUD / panels → Basic UI UIHandle (separate pipeline, not RenderHandle)
  • Single projectile / NPC PostDraw sprite → use Main.spriteBatch directly; no RenderHandle needed

Relationship to PRT, Models3D, and UI

Module Relationship to RenderHandle
Basic PRT System PRTRender : RenderHandle, Weight = 1; PRTRenderLayer aligns to draw stages
Models3D System Model3DRenderer : RenderHandle, Weight = 5; Model3DLayer aligns to draw stages
Actor Entity System Internal ActorRender : RenderHandle, draws by ActorDrawLayer
Basic UI No direct link; UIHandle uses its own layer stack and LogicUpdate

5-minute quick start

using InnoVault.RenderHandles;
using Microsoft.Xna.Framework.Graphics;
using Terraria;
using Terraria.ModLoader;

// Autoload: ModType subclasses of RenderHandle are registered by tModLoader
public class MyScreenTint : RenderHandle
{
    public override bool CanLoad() => !Main.dedServ;

    // Higher Weight = later in the chain; built-in PRT=1, Models3D=5
    public override float Weight => 10f;

    public override void EndCaptureDraw(
        SpriteBatch spriteBatch,
        GraphicsDevice graphicsDevice,
        RenderTarget2D screenSwap)
    {
        // During EndCapture you can read filterManager / finalTexture / screenTarget1/2
        // SpriteBatch state is fully managed by the implementer
        spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
        // ... post-process Main.screenTarget or finalTexture ...
        spriteBatch.End();
    }
}

Key points:

  • Override CanLoad() to skip registration on dedicated server (recommended)
  • Each hook has a SpriteBatch active/inactive contract — see API reference below
  • Modify the visible frame via Main.screenTarget; screenSwap is an intermediate buffer

Comparison

RenderHandle Direct Main.spriteBatch ModSystem draw hooks
Hook timing 10+ standard stages pre-mapped Find PostDraw / IL hooks yourself PostDrawTiles, etc. — limited stages
Multi-mod cooperation Instances sorted by Weight Easy to clobber RenderState No unified ordering
RT management Auto ScreenSwap + ScreenTargets[] Manual alloc/dispose Manual
Post-process EndCaptureDraw with FilterManager RTs Must hook EndCapture yourself Usually not possible
Learning curve Must learn SpriteBatch contracts per stage Low, but state bugs common Medium
Typical use Full-screen FX, cross-system layered draw Single entity PostDraw Simple global overlay

Architecture

Registration and frame loop

flowchart TB
    subgraph load [Load phase]
        ModType[ModType subclass autoload] --> VR[VaultRegister]
        VR --> Inst[RenderHandle.Instances.Add]
        Inst --> Sort[Sort by Weight ascending]
        RHL[RenderHandleLoader IVaultLoader] --> Hooks[Attach Terraria On_ hooks]
    end

    subgraph frame [Each frame]
        RHS[RenderHandleSystem.PostUpdateEverything] --> Upd[UpdateBySystem per instance]
        Hooks --> Stage[DrawBatch per stage]
        Stage --> H1[Instances Weight low to high]
        H1 --> H2[HandleRenderAction error isolation]
    end

    subgraph unload [Unload]
        RHL --> Dispose[DisposeScreenTargets + Instances.Clear]
    end
Loading

Draw stage order (in-world, Main.gameMenu == false)

flowchart TD
    A[DrawBeforeTiles] --> B[DrawNPCsOverTiles]
    B --> C[DrawAfterTiles]
    C --> D[DrawBeforePlayers]
    D --> E[Player draw orig]
    E --> F[DrawAfterPlayers]
    F --> G[DrawBeforeInfernoRings]
    G --> H[Entities / particles etc.]
    H --> I[EndEntityDraw / DrawAfterEntities]
    I --> J[EndCaptureDraw]
    J --> K[PostEndCaptureDraw]
    K --> L[FilterManager.orig_EndCapture]
Loading

EndCaptureDraw is skipped when Main.gameMenu == true; PostEndCaptureDraw still runs on the main menu.

Built-in instance weights

Type Weight Notes
PRTRender 1 Particle layered draw, runs first
ActorRender (internal) default 1 Actor layered draw
Model3DRenderer 5 3D models, after PRT to avoid clobbering particle canvas
Custom suggest > 5 or < 1 Insert where needed

Level 1 — Hello World

Draw a simple overlay in DrawAfterEntities (SpriteBatch inactive on entry; must End before return):

public class DebugOverlay : RenderHandle
{
    public override bool CanLoad() => !Main.dedServ;

    public override void DrawAfterEntities(
        SpriteBatch spriteBatch,
        GraphicsDevice graphicsDevice,
        RenderTarget2D screenSwap)
    {
        spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend,
            Main.DefaultSamplerState, DepthStencilState.None,
            Main.Rasterizer, null, Main.GameViewMatrix.TransformationMatrix);

        Utils.DrawBorderString(spriteBatch, "RenderHandle OK",
            Main.LocalPlayer.Center - Main.screenPosition, Color.Lime);

        spriteBatch.End();
    }
}

Level 2 — Common patterns

Full-screen post-process (EndCapture)

public class VignettePass : RenderHandle
{
    public override bool CanLoad() => !Main.dedServ;
    public override float Weight => 20f;

    public override void EndCaptureDraw(
        SpriteBatch spriteBatch,
        GraphicsDevice graphicsDevice,
        RenderTarget2D screenSwap)
    {
        // Readable during this stage:
        // filterManager, finalTexture, screenTarget1, screenTarget2
        // Target Main.screenTarget for actual frame modifications
        spriteBatch.Begin(/* ... */);
        // Draw vignette texture to screenTarget
        spriteBatch.End();
    }
}

Private RTs (ScreenSlot)

public class BlurPass : RenderHandle
{
    public override bool CanLoad() => !Main.dedServ;
    public override int ScreenSlot => 2; // Two full-screen RTs

    public override void DrawAfterPlayers(
        SpriteBatch spriteBatch,
        GraphicsDevice graphicsDevice,
        RenderTarget2D screenSwap)
    {
        RenderTarget2D temp = ScreenTargets[0];
        graphicsDevice.SetRenderTarget(temp);
        graphicsDevice.Clear(Color.Transparent);
        // ... render to temp ...
        graphicsDevice.SetRenderTarget(null);
        // Composite back to main frame
    }
}

Do not set ScreenSlot too high — each RT costs screenWidth × screenHeight VRAM.

Resolution changes

public override void OnResolutionChanged(Vector2 screenSize) {
    // ScreenTargets are rebuilt by the framework; drop cached textures or reset layout here
}

Level 3 — Advanced

Reuse Models3D atomic API

Custom RenderHandle subclasses can call Model3DRenderer.DrawInstance, BuildWorldMatrix, etc. to reuse OBJ/glTF loading and matrix helpers. See Models3D System Level 3.

Manual PRT draw cooperation

For special cases (mind SpriteBatch contracts):

// Entry: Active; exit: still Active
PRTRender.DrawAll(spriteBatch);

// Entry: inactive; exit: still inactive
PRTRender.DrawLayer(spriteBatch, PRTRenderLayer.AfterTiles);

Error isolation

When RenderHandleLoader.HandleRenderAction catches an exception:

  • Sets ignoreBug = 60 (~1 second skip for that instance)
  • Increments errorCount and logs / on-screen red text

The instance resumes automatically after the cooldown once the bug is fixed.


API reference

Properties

Member Type Default Description
Weight float 1 Sort weight; higher = later
ScreenSlot int 0 Private RT count; creates ScreenTargets when > 0
ScreenTargets RenderTarget2D[] Paired with ScreenSlot
filterManager FilterManager Valid only during EndCapture*
finalTexture RenderTarget2D Valid only during EndCapture*
screenTarget1/2 RenderTarget2D Valid only during EndCapture*
Instances List<RenderHandle> All registered instances (Weight-sorted)

Lifecycle

Method When
VaultRegister() Added to Instances; optional ScreenTargets creation
VaultSetup() / SetStaticDefaults() Content setup
UpdateBySystem(int index) Each frame in PostUpdateEverything (not on server)
OnResolutionChanged(Vector2) Window resolution changed
CreateScreenTargets() / DisposeScreenTargets() Manual RT rebuild / release

Draw stages and SpriteBatch contracts

Method SpriteBatch on entry Before return
DrawBeforeTiles Active Keep Active
DrawNPCsOverTiles Caller-dependent Must End
DrawAfterTiles Inactive Must End
DrawBeforePlayers Inactive Must End
DrawAfterPlayers Inactive Must End
DrawBeforeInfernoRings Active Keep Active
DrawAfterEntities Inactive Must End
EndEntityDraw(sb, main) Inactive Must End
EndEntityDraw(sb, main, gd, swap) Inactive Must End
EndCaptureDraw Self-managed Self-managed
PostEndCaptureDraw Self-managed Self-managed

Breaking the contract corrupts SpriteBatch state for later instances or vanilla draw — follow PRTRender source for End / Begin pairing.

Framework types

Type Description
RenderHandleLoader IVaultLoader: Terraria hooks, maintains ScreenSwap
RenderHandleSystem ModSystem: drives UpdateBySystem
RenderHandleLoader.ScreenSwap Global intermediate RT, similar to Main.screenTargetSwap

Common mistakes / FAQ

Issue Cause / fix
Black / garbled frame after draw Wrong Begin/End contract; see table and PRTRender
Dedicated server crash Override CanLoad() => !Main.dedServ
Order conflict with PRT / 3D Adjust Weight; Models3D defaults to 5 after PRT
ScreenTargets is null ScreenSlot == 0 allocates none; or not yet created on main thread
Main-menu post-process missing EndCaptureDraw skipped on menu; use PostEndCaptureDraw
Instance suddenly stops drawing Draw threw → ignoreBug cooldown; check [RenderHandleLoader:...] logs
Draw UI from RenderHandle? Not recommended; use UIHandle — different timing
Need more Terraria stages? Pipeline is fixed; open an issue or IL hook (not recommended alongside framework)

Related modules


Navigation

Previous Home Next
Basic PRT System Home Basic TP Entity

Clone this wiki locally