The native track of eQuantic.UI
Your C# draws its own pixels.
Photon is a from-scratch GPU renderer for the same components that serve your web pages. Metal on Apple, Vulkan elsewhere, straight to the drawable. No Skia tier. No WebView. A CPU reference backend decides what correct means, and the GPU has to agree with it.
4 platformsmacOS, iOS, Android, Windows
±1 LSBGPU against the CPU reference
91committed golden images
0third-party packages
PhotonDesktop
$ dotnet run --project samples/PhotonDesktop[photon] backend: Metal (BGRA8_sRGB)[photon] text: CoreText · icons: CoreGraphics[photon] render scale 2.0, layout in dp
a real window, presenting GPU frames
$ dotnet run -- --Photon:ScreenshotPath out.png# one settled frame, headless, same metrics
WHY OWN THE RENDERERA UI needs a handful of primitives,not a general 2D library.Wrapping Skia would mean inheriting a whole graphics library to draw rounded rectangles and text. Owning the tier means the math is ours, and it is testable to the pixel.
Predictable, by constructionShaders are compiled offline from one Slang source into committed Metal and SPIR-V artifacts, and the pipeline registry is fixed. Nothing compiles a shader while your user waits, and the pipeline caches survive between launches. That jank has a known cause, and this is the cure.
A tiny, honest surfaceClear, fill and stroke a per-corner rounded rect, a two-stop gradient, a texture, a clip, a layer, a baked transform. Interfaces are made of those. The display list is flat and heap-free, so a frame is a buffer of records rather than a graph of objects.
One normative modelA single file of signed-distance math is the specification. The CPU rasterizer and every GPU fragment shader implement exactly those formulas, so "which one is right" is never a debate between platforms.
Predictable, by constructionShaders are compiled offline from one Slang source into committed Metal and SPIR-V artifacts, and the pipeline registry is fixed. Nothing compiles a shader while your user waits, and the pipeline caches survive between launches. That jank has a known cause, and this is the cure.
A tiny, honest surfaceClear, fill and stroke a per-corner rounded rect, a two-stop gradient, a texture, a clip, a layer, a baked transform. Interfaces are made of those. The display list is flat and heap-free, so a frame is a buffer of records rather than a graph of objects.
One normative modelA single file of signed-distance math is the specification. The CPU rasterizer and every GPU fragment shader implement exactly those formulas, so "which one is right" is never a debate between platforms.
Predictable, by constructionShaders are compiled offline from one Slang source into committed Metal and SPIR-V artifacts, and the pipeline registry is fixed. Nothing compiles a shader while your user waits, and the pipeline caches survive between launches. That jank has a known cause, and this is the cure.
A tiny, honest surfaceClear, fill and stroke a per-corner rounded rect, a two-stop gradient, a texture, a clip, a layer, a baked transform. Interfaces are made of those. The display list is flat and heap-free, so a frame is a buffer of records rather than a graph of objects.
One normative modelA single file of signed-distance math is the specification. The CPU rasterizer and every GPU fragment shader implement exactly those formulas, so "which one is right" is never a debate between platforms.
THE PIPELINEFrom a C# expression to a GPU frameThe tree is re-expanded from a retained root every frame, which is also why hot reload works: a patched method body simply takes effect on the next one.
01
Your components
The same write-once vocabulary the web target renders. Build(ComponentContext) is pure and token-based.
02
Layout, in C#
Our own flex engine, judged against the browser: fill inherits indeterminacy, shrink stops at a min-content floor, clamps make the child re-measure.
03
A display list
The realizer turns the laid-out tree into flat draw commands. The builder is reused across frames, so steady state stops allocating.
04
A backend presents it
One seam, three implementations. The GPU ones encode into the window's own drawable; the CPU one is the ground truth they are measured against.
IRenderBackend
Reference
NORMATIVE
A scalar CPU rasterizer. Never shipped in an app, and it pins every golden image.
Metal
macOS and iOS, encoding into a CAMetalLayer drawable.
Vulkan
Android, and Windows wherever a driver exposes a surface.
Both GPU backends run the shared encode loop over one RHI, so a new platform is a swapchain and a window, not a new renderer.
PLATFORMSFour real windows, no wrappers underneathEvery shell talks to the operating system through slim typed bindings we wrote: no interop framework, no C shims, no third-party package. Text, icons and images come from the platform's own frameworks, because a system that already has a text engine should be the one measuring text.
PLATFORM
BACKEND
TEXT & IMAGES
THE WINDOW
macOS
Metal
CoreText · ImageIO
NSWindow over a CAMetalLayer, retina raster with layout in dp, OS mouse, scroll and keyboard
iOS
Metal
CoreText · ImageIO
The same Apple services as macOS, written once; only the window and the event loop differ
Android
Vulkan
Platform text services
The app supplies a factory instead of a Main; frames present on the platform's vsync callback
Windows
NEW
Vulkan, or Reference
DirectWrite · Direct2D · WIC
A real Win32 window with per-monitor DPI and dark-mode follow. Where no Vulkan driver exists, it falls back to the reference rasterizer and still draws
PLATFORM
BACKEND
TEXT & IMAGES
THE WINDOW
macOS
Metal
CoreText · ImageIO
NSWindow over a CAMetalLayer, retina raster with layout in dp, OS mouse, scroll and keyboard
iOS
Metal
CoreText · ImageIO
The same Apple services as macOS, written once; only the window and the event loop differ
Android
Vulkan
Platform text services
The app supplies a factory instead of a Main; frames present on the platform's vsync callback
Windows
NEW
Vulkan, or Reference
DirectWrite · Direct2D · WIC
A real Win32 window with per-monitor DPI and dark-mode follow. Where no Vulkan driver exists, it falls back to the reference rasterizer and still draws
That fallback is the reason the normative backend is not just a test fixture. A virtual machine or a Remote Desktop session often has no Vulkan driver at all, and the app still opens a window and renders correctly, just on the CPU.
PARITY"Looks right on my machine" is not a measurementEvery scene renders through a backend, the pixels are read back, and they are compared against a committed PNG. The CPU reference pins those images; the GPU backends run the same catalog to prove they agree. A drift of more than a bit or two is a bug with a name, not a platform difference to shrug at.
91 golden images in the repository: component pairs in light and dark, mid-drag sheet states, gradients, focus rings, texture coverage.
A dependency-free PNG codec, so the harness has nothing to install, and a failure writes out an amplified diff you can look at.
One shader source in Slang, transliterating the C# math, compiled offline into the artifacts both GPUs consume. You never run a shader toolchain.
THE SAME SCENE, THREE RASTERIZERS
Reference
Metal
Vulkan
diff vs golden ... 0 px above tolerancegpu ↔ cpu ........ within ±1 LSB
An illustration of the contract, not a screenshot of the harness. The real catalog lives in the repository and runs in CI.
PERFORMANCEThe promises come with numbers, and a ratchetMeasured on a dashboard-shaped scene, twenty-four cards plus a strip of looping motion, on an M-series baseline. Ceilings are pinned a margin above these, so a regression fails the build instead of being noticed six months later.
0.23 msrealize, medianp95 0.32 ms, against an 8.33 ms budget
67.5 KBallocated per framesteady state, with frame recycling on
146draw commandsfor the whole dashboard scene
2 Hza blinking caretscheduled frames, not a hot render loop
0.23 msrealize, medianp95 0.32 ms, against an 8.33 ms budget
67.5 KBallocated per framesteady state, with frame recycling on
146draw commandsfor the whole dashboard scene
2 Hza blinking caretscheduled frames, not a hot render loop
0.23 msrealize, medianp95 0.32 ms, against an 8.33 ms budget
67.5 KBallocated per framesteady state, with frame recycling on
146draw commandsfor the whole dashboard scene
2 Hza blinking caretscheduled frames, not a hot render loop
0.23 msrealize, medianp95 0.32 ms, against an 8.33 ms budget
67.5 KBallocated per framesteady state, with frame recycling on
146draw commandsfor the whole dashboard scene
2 Hza blinking caretscheduled frames, not a hot render loop
A path is identity, so paths are cached across frames
The list builder keeps its capacity
Replaced frames feed a node pool
WHAT IS REAL TODAYAn engine that draws its own pixels owes you everythingThe operating system sees one opaque view, so nothing comes for free. Every item here had to be built rather than inherited.
Real text, not a texture atlas
One engine measures and rasterizes, so line breaks agree by construction. Bitmaps are sized from the line's ink box, which is why a deep descender is never clipped.
Icons and images
A pure C# SVG path parser feeds the platform rasterizer, and photographs now decode through the system's own image services on Apple and Windows.
Input the platform respects
A single focus order, dead keys and CJK composition rendered inline at the caret, per-path gestures with press slop, cursors and clipboard.
A semantics tree
Reading order derived from the realized layout, including content scrolled out of view, driving VoiceOver on macOS. A gate keeps it from drifting from the keyboard walk.
Hot reload, state intact
Run under dotnet watch run, edit a Build body, save. Three clicks of counter state survive, because state lives in a path-keyed store.
Motion, scroll and overlays
Loop motion, value transitions, enter and exit presence, sticky headers, drag-to-dismiss with a glide, and menus anchored to real bounds.
Real text, not a texture atlas
One engine measures and rasterizes, so line breaks agree by construction. Bitmaps are sized from the line's ink box, which is why a deep descender is never clipped.
Icons and images
A pure C# SVG path parser feeds the platform rasterizer, and photographs now decode through the system's own image services on Apple and Windows.
Input the platform respects
A single focus order, dead keys and CJK composition rendered inline at the caret, per-path gestures with press slop, cursors and clipboard.
A semantics tree
Reading order derived from the realized layout, including content scrolled out of view, driving VoiceOver on macOS. A gate keeps it from drifting from the keyboard walk.
Hot reload, state intact
Run under dotnet watch run, edit a Build body, save. Three clicks of counter state survive, because state lives in a path-keyed store.
Motion, scroll and overlays
Loop motion, value transitions, enter and exit presence, sticky headers, drag-to-dismiss with a glide, and menus anchored to real bounds.
Real text, not a texture atlas
One engine measures and rasterizes, so line breaks agree by construction. Bitmaps are sized from the line's ink box, which is why a deep descender is never clipped.
Icons and images
A pure C# SVG path parser feeds the platform rasterizer, and photographs now decode through the system's own image services on Apple and Windows.
Input the platform respects
A single focus order, dead keys and CJK composition rendered inline at the caret, per-path gestures with press slop, cursors and clipboard.
A semantics tree
Reading order derived from the realized layout, including content scrolled out of view, driving VoiceOver on macOS. A gate keeps it from drifting from the keyboard walk.
Hot reload, state intact
Run under dotnet watch run, edit a Build body, save. Three clicks of counter state survive, because state lives in a path-keyed store.
Motion, scroll and overlays
Loop motion, value transitions, enter and exit presence, sticky headers, drag-to-dismiss with a glide, and menus anchored to real bounds.
Where the fence is. The accessibility bridge ships for macOS; iOS and Android consume the same tree and are not wired yet. Code surfaces commit a composition but do not render the inline marked run. We would rather name these than let you find them.
STARTA GPU window in three commands
The prerequisite is the .NET SDK. Photon needs no graphics SDK, no shader toolchain and no native build step, because the shaders are already compiled and the bindings are plain C#.
$ dotnet new install eQuantic.UI.Templates$ dotnet new equantic-native -n MyNativeApp$ cd MyNativeApp && dotnet run→ a real window on the GPU. Swap the template for equantic-app and the same components serve a web page instead.
0.2.0-preview.63
development preview on NuGet, MIT licensed
eQuantic.UIeQuantic.UI
Photon
The native GPU track of eQuantic.UI. Metal and Vulkan, held to a CPU reference.MIT © eQuantic.