Skip to content

Rendering, Text, And Graphics

RoyalTerminal splits rendering into five layers: backend-neutral contracts, text shaping and fallback, the default Skia renderer, the managed framebuffer shader pipeline, and optional Ghostty renderer interop. The default Skia renderer also owns row-local regex text highlighting because highlighting is a rendering concern rather than VT state. That separation keeps the default path simple while still giving advanced hosts access to GPU-level integration.

The default render path

The normal path for an Avalonia application is:

  1. a VT processor updates TerminalScreen
  2. shaping and font fallback resolve how text should be drawn
  3. SkiaTerminalRenderer resolves search, selection, regex text highlights, and cell colors
  4. optional framebuffer shaders post-process the completed terminal frame
  5. the Avalonia control presents the result

For most applications, this is the only rendering path you need.

Default renderer types

TypePurpose
GlyphCacheGlyph and font resource cache for the renderer.
SkiaTerminalRendererHigh-performance CPU renderer for the terminal screen, including regex text highlighting.
TerminalTextHighlightRuleRuntime regex highlight rule with optional foreground/background color overrides.
CursorStyleRenderer cursor style enum.

Regex text highlighting uses compiled row-local regexes, span-based matching, reusable row buffers, and a static row cache mode for unchanged text. See Regex Text Highlighting for the host API and performance details.

Framebuffer shaders

The managed shader path runs after the terminal has been rendered into a Skia surface. It treats the completed terminal frame as a texture and applies one or more TerminalShaderSource entries through TerminalShaderPostProcessor.

The reusable shader model and compatibility translation live in RoyalApps.RoyalTerminal.Shaders, which has no Avalonia, SkiaSharp, terminal, or native interop dependency. RoyalApps.RoyalTerminal.Rendering.Skia owns the Skia-specific TerminalShaderPostProcessor and TerminalShaderFrameContext adapter types.

The main host-facing properties live on TerminalControl:

MemberPurpose
ShaderSourcesOrdered shader chain applied to the completed frame.
ShaderAnimationEnabledAllows animated shaders to keep requesting frames while terminal output is idle.

Supported source families:

Source familyArticle
Direct Skia Runtime Effect sourceSkia Runtime Effect Shaders
Ghostty/Shadertoy mainImage GLSLGhostty/Shadertoy Shader Compatibility
Windows Terminal-style HLSL pixel shadersWindows Terminal HLSL Shader Compatibility

Start with Shader Support for the architecture and Applying Shaders for the host API.

Text shaping and font fallback

Terminal rendering is not just "draw a codepoint". Ligatures, bidirectional text, fallback fonts, emoji presentation, and diagnostics all matter if the renderer is going to remain believable across languages and shells.

Text stack

TypePurpose
TextDirectionModeShaping direction mode.
TextShapingOptionsShaping options payload.
ShapedGlyphOne shaped glyph with cluster and positional data.
ShapedTextRunImmutable shaped glyph run.
ITextShaperShaping contract.
HarfBuzzTextShaperHarfBuzz-based shaping implementation.
HarfBuzzTypefaceCacheCache for HarfBuzz typeface resources.
HarfBuzzTypefaceEntryOne cached HarfBuzz typeface resource.
TerminalFontResolutionResult of resolving a primary or fallback typeface.
TerminalFontResolverFallback typeface resolver.
TextRenderDiagnosticsDiagnostics snapshot for shaping and fallback behavior.

The shaping layer is deliberately terminal-focused. It exists to preserve the cell grid while still handling the text cases that real terminal applications produce.

Backend-neutral render contracts

When you need to render outside the default Avalonia control, the public contract surface lives in RoyalApps.RoyalTerminal.Rendering.Contracts.

TypePurpose
IRenderBackendCapability contract for a backend implementation.
IRenderSurfaceSurface contract for rendering into external targets.
RenderBackendKindBackend kind enum.
RenderTargetKindRender target kind enum.
RenderPixelFormatPixel-format enum.
RenderFeatureFlagsOptional backend feature flags.
RenderBackendCapabilitiesCapability snapshot for a backend or surface.
RenderTargetDescriptorExternal target descriptor passed into validation or rendering.
RenderValidationResultValidation result model.
RenderFrameResultRender result model.
RenderTargetDescriptorValidatorStatic validator for render target invariants.

This contract layer is what makes the renderer interop packages possible without baking one graphics stack into the rest of the terminal model.

Ghostty renderer interop

The Ghostty renderer is optional. RoyalTerminal keeps it in separate packages so hosts can opt in only when they actually want GPU-level interop.

Core interop types

TypePurpose
GhosttyRenderContextOwner of the native Ghostty renderer context.
GhosttyRenderSurfaceManaged render surface over the native Ghostty renderer surface.
GhosttyRenderInteropExceptionInterop-specific exception type.
GhosttyRenderInteropResultNative result code enum.
GhosttyRenderInteropResultMapperStatic mapper from native result codes to readable messages.
RenderThemeNeutral render theme structure for Ghostty surfaces.
GhosttyRendererNativeLibraryLoaderNative loader for ghostty-renderer-capi.

Skia bridge types

TypePurpose
ISkiaRgbaFallbackRendererContract for CPU fallback drawing into Skia.
GhosttyRenderSurfaceRgbaFallbackRendererDefault RGBA fallback implementation.
SkiaInteropRenderRequestRequest passed into the Skia bridge.
SkiaInteropRenderResultResult returned by the Skia bridge.
SkiaInteropRendererBridge that renders Ghostty surfaces through GPU interop or CPU fallback.

If you are using the Avalonia package, the host-specific handle acquisition layer is documented separately in Embedding In Avalonia.

Choosing the right rendering level

If you needStart with
A normal Avalonia terminalSkiaTerminalRenderer through TerminalControl
Terminal post-process effectsTerminalControl.ShaderSources and TerminalShaderSource
Font shaping and fallback insightHarfBuzzTextShaper and TerminalFontResolver
A custom render hostRenderTargetDescriptor, IRenderSurface, and the contracts package
Ghostty renderer integrationGhosttyRenderContext, GhosttyRenderSurface, and SkiaInteropRenderer

The rule of thumb is simple: stay on the default Skia path until your host architecture gives you a concrete reason to do otherwise.

MIT Licensed