Skip to content

Rendering overview

Tick UI ships three render-pipeline integration paths: Universal Render Pipeline (URP), Built-in (BIRP), and a pipeline-agnostic RenderTexture path for in-world UI. All three forward draw recording through an TickRecorder (typically BaseGameUI) into the same IDrawer, so most code stays pipeline-agnostic. This page describes the recorder pattern that unifies them and the points where the paths differ.

The three paths

URP lives in Source/Integration/URP/. TickURPInjector hooks RenderPipelineManager.beginCameraRendering, and an TickRenderPass enqueued onto the camera's renderer writes into resourceData.activeColorTexture. Compiled under TICK_URP.

BIRP lives in Source/Integration/BIRP/. TickBIRPInjector adds a CommandBuffer to the camera at a configurable CameraEvent. The buffer writes into the camera's currently bound target.

RenderTexture lives in Source/Integration/RenderTexture/. TickRenderTextureInjector is a pipeline-agnostic MonoBehaviour that records the recorder's commands directly into a RenderTexture each LateUpdate via Graphics.ExecuteCommandBuffer. Use it to display Tick UI on an in-world surface. Assign the texture to a material on a quad or screen mesh, and the UI appears on it.

The URP and BIRP injectors find the recorder on the same GameObject via GetComponent<TickRecorder>(). The RenderTexture injector takes the recorder as an explicit Inspector field, so it can drive a recorder that lives elsewhere in the scene.

For per-pipeline configuration, refer to Configure URP, Configure BIRP, and Configure RenderTexture.

The recorder pattern

TickRecorder is the abstract base that all three injector paths talk to. It exposes two virtual methods:

public abstract void RecordInto(CommandBuffer target);
public virtual void SetRenderTarget(RenderTargetIdentifier target, Vector2Int size, bool isOffscreen);

BaseGameUI is the standard subclass. It owns the UI instance and forwards both calls to the underlying Drawer. Custom integrations subclass TickRecorder directly when BaseGameUI's MonoBehaviour-driven setup does not fit; refer to Build a custom integration.

Each injector calls SetRenderTarget(target, size, isOffscreen) every frame before RecordInto. The drawer needs all three: the target identifier so it can re-bind it after the frosted-glass pipeline's grab-blit, the pixel size so its shape shaders' pixel-to-NDC conversion matches the actual target, and the off-screen flag so it can cancel the shader Y-flip on D3D-like APIs when drawing into a non-backbuffer target. The URP path forwards resourceData.activeColorTexture and the camera's pixel dimensions; the BIRP path forwards BuiltinRenderTextureType.CameraTarget and the camera's pixel dimensions; the RenderTexture path forwards the destination texture and its pixel dimensions.

Pixel coordinates and the target size

Tick UI's shape shaders convert pixel positions to NDC against a _ScreenSize uniform. The drawer sets that uniform from the size forwarded through SetRenderTarget, so render scale ≠ 1, viewport-rect cameras, off-screen RenderTexture targets, and dynamic resolution all position the UI correctly as long as the injector forwards the active target's pixel dimensions each frame. The shipped injectors do.

If no size has been forwarded, the drawer falls back to Screen.width × Screen.height. This happens when a custom integration skips the call. Custom integrations need to pass the actual target size to avoid that fallback. Refer to Build a custom integration.

For the math behind the conversion, refer to Coordinate spaces. For the drawer's command-list model that drives the conversion, refer to Drawer and batching.

Frosted glass and the temp RT

HandleDrawFrosted allocates a temporary RenderTexture sized to the forwarded target size so the shader can grab the current target, blur it, and composite it back. The grab-and-blit changes the render target mid-frame. Drawer keeps the RenderTargetIdentifier forwarded through SetRenderTarget and uses it to restore the bound target after the blit.

A custom integration must call recorder.SetRenderTarget(target, size, isOffscreen) each frame so the drawer knows both which target to restore to and which pixel dimensions to size its conversion against.

UI Toolkit

Tick UI ships no UI Toolkit integration path. UI Toolkit hosting requires a custom integration through TickRecorder.

Composite with uGUI

A scene that uses both Tick UI and Unity's uGUI (a Canvas in Screen Space - Overlay mode) composites them in a fixed order: uGUI draws on top of Tick UI, regardless of which RenderPassEvent (URP) or CameraEvent (BIRP) Tick UI is configured to use. Screen Space - Overlay canvases run after every camera has finished rendering, which is later than any pipeline event Tick UI can hook.

Practical consequences:

  • A uGUI Canvas covers Tick UI panels even when Interlude UI is configured for AfterRendering (URP) or AfterEverything (BIRP).
  • Switching the canvas to Screen Space - Camera or World Space brings it into the same render-pass timeline as Tick UI, so the configured pipeline event controls draw order.
  • Tick UI supports running both UI stacks together. Its API covers HUDs, menus, and debug overlays. Use uGUI when a project already depends on it.

Pitfalls

UI scales wrong under render scale ≠ 1.0 or on a non-display target. : Almost always a custom integration that forgot to call recorder.SetRenderTarget(target, size, isOffscreen) each frame, so the drawer fell back to Screen.*. The shipped URP, BIRP, and RenderTexture injectors handle this for you; check there only if you wrote your own integration.

The frosted blit corrupts the render target. : The custom integration's bind lifecycle does not match the drawer's expectations. Confirm SetRenderTarget(target, size, isOffscreen) runs each frame so the drawer knows which target to restore to after the blit.

Multiple cameras render the UI more than once. : TickURPInjector hooks every Game-type camera. Multiple cameras with the injector enqueued enqueue the pass on each. Restrict the injector to a single camera, or filter inside OnBeginCameraRendering.

The integration runs before camera rendering completes. : The chosen RenderPassEvent (URP) or CameraEvent (BIRP) schedules Tick UI too early. Default to the after-everything events for HUD usage. Refer to Configure URP and Configure BIRP.

A uGUI Canvas covers Tick UI panels. : Screen Space - Overlay canvases composite after every camera has finished, so they land on top of Tick UI regardless of the chosen pipeline event. Switch the canvas to Screen Space - Camera or World Space to share Tick UI's pipeline timeline. Refer to the Composite with uGUI section above.

Additional resources