Rendering

← Back to index

Weva's render backend is the custom CommandBuffer path locked in PLAN §9 — not UI Toolkit, not UGUI. Two backends share one IRenderBackend interface (Runtime/Rendering/Backend/): a production URP path and a debug IMGUI fallback. The paint converter and everything above it are backend-agnostic.

The paint command stream

BoxToPaintConverter turns the laid-out Box tree into a flat ordered PaintCommand list (fills, borders, text, gradients, images, shadows, plus PushClip/PopClip, PushOpacity/PopOpacity, PushFilter/PopFilter, transform pushes). The converter caches per-box command slices keyed on box + style version, so clean boxes reuse their commands unchanged. Commands are pooled and returned each frame for zero steady-state churn.

URP path

The production renderer is a ScriptableRendererFeature (UIBatchedRendererFeature / UIRendererFeature) you add to your URP Renderer asset. All URP code is gated behind the UNITYUI_URP versionDefines token so the package compiles without URP installed.

The render-graph pass (UIRenderGraphPass, Runtime/Rendering/URP/) injects after post-processing (RenderPassEvent.AfterRendering / AfterRenderingPostProcessing) and draws directly into the camera color target — zero intermediate blit for the common case. Offscreen surfaces are allocated only when an effect needs them (filters, masks, opacity-layer compositing, backdrop-filter).

Batching

UIBatcher folds the command stream into UIQuadInstance arrays — one per batch. A batch is a maximal run of quads sharing the same shader keyword + stencil ref. Batches break on:

Instance data uploads via a StructuredBuffer, lifting the old 64 KB constant-buffer cap; up to 1024 instances per draw (DrawMeshInstanced), sized so typical text + UI batches stay single-draw. The batcher is pure C# (no Mesh/Material/GraphicsBuffer dependency) so it is exhaustively unit-testable headlessly.

Shaders & clipping

The URP backend ships dedicated shaders for the box SDF (rounded rects, per-axis elliptical corners), gradients (linear/radial/conic), text, shadows, filters, and Weva_StencilWrite. Clipping is done with stencil writes (Comp Equal / IncrSat / DecrSat) via StencilClipManager, not scissor rects, so arbitrary rounded/nested clip regions work.

IMGUI fallback

IMGUIDocumentRenderer (Runtime/Rendering/) is a debug-grade backend that draws the same command stream via IMGUI. It auto-attaches when not on URP (set RendererBackend = URP to suppress it). Use it for editor testing; it is not a shipping path. The editor preview window uses a separate SoftwarePainter CPU rasterizer.

Idle, scroll & repaint caching

The render pass pulls paint through IUIPaintSource.NeedsRepaint. A document returns NeedsRepaint = false when nothing is dirty, the document has already emitted at least one frame, and the image-registry version is unchanged — so the pass skips BeginFrame/EmitPaint/EndFrame entirely and the previous frame's batches feed the GPU verbatim. Paint conversion and batching are wasted work when nothing changed.

This is what makes a static UI cost ~1 ms per idle frame. position: sticky offsets and scroll position recompute on the paint-only path without a full relayout, so scrolling stays cheap.

Performance characteristics

From Tools/PerfBench/ (dev-machine baseline; see PLAN §13 and the package README for the full table):

Steady-state allocations are above the PLAN goal (paint ~1.1 MB/call at 500 boxes; layout ~1.4 MB/call at 1000 elements) but inside frame budgets at typical UI sizes; closing the gap is on the roadmap.

DevTools

Press F12 in Play mode for the IMGUI DevToolsOverlay (it never goes through the main paint pipeline): box outlines (Chrome DevTools palette), a dirty-tracking highlighter, a hover inspector with computed styles, and a per-frame cascade/layout/paint ms + GC + paint-cache-hit readout. There is also a Window → Weva → DevTools editor window for inspection without entering Play mode.


Next: Samples · Testing