Rendering
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:
- a shader-keyword change (brush kind / shadow / text),
- a stencil-ref change (
PushClip/PopClip), - an opacity-layer boundary (
PushOpacity/PopOpacityallocates an offscreen RT).
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):
- Cascade
ComputeAll: ~8–9 ms at 1000 elements (cold). - A
:hoverflip re-cascades in ~0.08 ms (per-element state digest; was 7.5 ms before the v0.5 cache-key fix — a 90× win). - Layout: ~4–11 ms at 1000 elements depending on shape.
- Paint convert: ~1 ms at 500–1000 boxes; gradient/shadow-heavy pages cost more
(multi-shadow boxes and
filter: blur()/text-shadoware the most expensive painters). - End-to-end hover toggle at 1000 elements: ~2.7 ms median.
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.
Weva