RenderInfo
public sealed record class RenderInfoWhat the control is actually doing, polled off the render thread.
Worth surfacing rather than hiding, because the honest answer varies by platform: the same code renders on the GPU on most targets and on the CPU on a stock macOS app, and an application that cares can tell the difference here instead of by looking at the frame rate.
Properties
| Member | Description |
|---|---|
| Which renderer drew the last frame. |
| Every renderer, with whether it can be selected and why not. This is what a settings UI binds to; it is populated from the graphics context the platform actually returned, so it tells the truth on each device rather than listing what the library was compiled with. |
| Actual frame colour pipeline and any format fallback. |
| Active camera-depth resolution and contact sampling quality, or the fallback reason. |
| What is doing the work — "OpenGL 4.6", "OpenGL ES 3.0", "Metal", "Vulkan", "CPU", "CPU+GPU". On the GPU backends this is the graphics API and its version, the half of the answer that It also says when the API named is not the one underneath. Vulkan on a Mac reads "Vulkan via MoltenVK", because the frame was translated onto Metal on its way out — which is what accounts for the cost and the two sampling features that path does not have, and is worth knowing when a log from a Mac is being read beside one from anywhere else. On the Skia fallback there is no API to name, so it says which processors the frame passed through. That is two on most machines and worth distinguishing: the fallback draws onto the canvas Avalonia leased it, so the transforms, lighting and clipping run on the CPU while the fills go to the card like any other Skia drawing. "CPU" on its own means a host with no graphics context at all, and it is the one configuration where the frame rate is the CPU's alone. |
| What the culling left out, as a sentence, or null when it left out nothing. Null rather than "0 culled" so that a scene where every node is on screen says nothing at all — the panel then reads as a list of what happened rather than a list of what did not. |
| Material diagnostic currently selected on the scene. |
| Resident shadow and contact-depth texture allocations. Metal only; null elsewhere. |
| The graphics card, as its own driver names it — "NVIDIA GeForce RTX A2000", "Apple M3 Max", "Intel Iris Plus Graphics 640", "Mali-G715". A GPU integrated into the processor is named the same way as a discrete one. Answered once and kept, including when the CPU fallback takes over: the card does not stop existing because a renderer was switched, and the fallback is still compositing through it. Null when there is nothing to name — a host with no graphics context, or a browser, which answers "WebKit WebGL" on purpose because a page that could read the graphics card has been handed a fingerprint, and a placeholder is not worth showing anybody. |
| Draw calls in the last frame — one per visible mesh node, per lighting pass. |
| Environment atlas size, probe count and most recent preparation latency. |
| What stopped the renderer, when something did — a device that could not be made, a device lost and not yet rebuilt, a shader that would not compile — in the renderer's own words. Null while frames are being drawn. |
| Resources released from the device since its registry was created, by grace or by budget. |
| What the active renderer does and does not do — the same rows on every backend, each with a reason. This is the honest answer to "will my material look the same everywhere", and it is a list rather than a sentence because a settings panel can show a list. Empty before the first frame, when there is no active renderer to ask. |
| Time between frames, exponentially smoothed. Startup frames are excluded. |
| Frames drawn per second, averaged over the last half-second. |
| Total frames rendered since the control was attached — the scene drawn, not shown again. |
| How many repaints were served from the last rendered frame instead of rendering the scene again. A compositor repaints a visual when the window around it changes — a host's overlay updating, a panel over the viewport — and not only when this control asks. Each of those used to be a full render outside |
| Most recently completed GPU frame duration, excluding presentation. Reported by the Metal renderer only; null elsewhere. |
| Actual internal render-target height, after scale and allocation limits. |
| Actual internal render-target width, after scale and allocation limits. |
| How many waits have exceeded a tenth of a second. See |
| How many lights this renderer will draw, or Read this rather than LightCollection.Capacity. That number is a floor — the fewest any backend has ever offered, and the number to write a scene against if it has to look identical everywhere. This is what the renderer that actually turned up will do, and on three of the four it is unlimited. A scene that sorts its lights and throws away the surplus should sort against this. |
| Whether this frame used a linear floating-point scene target and HDR composition. |
| The longest single wait since the control was attached, in milliseconds. A high-water mark rather than an average, because a one-second stall is one sample: averaged at a tenth it becomes a hundred milliseconds on the mean and then decays away, which is indistinguishable from a renderer that gradually got slower — the opposite conclusion from the right one. |
| Actionable scene/material problems found while preparing this snapshot. |
| The frame-rate cap actually being applied. See What is in force rather than what was asked for: zero while the cap is suspended, which it is for the length of a drag, so that orbiting a capped view does not feel broken. |
| Opt-in synchronous CPU plus GPU completion benchmark, including HDR composition. Set AVA3D_COMPLETE_TIMING=1 before startup. Null in ordinary asynchronous rendering and on the software renderer. |
| Median frame interval in the last 240 post-warm-up frames; includes compositor pacing. |
| Median completed GPU duration in a rolling 240-sample window after 30 warm-up samples. Metal only; null elsewhere. |
| How many draw items in the last snapshot named a normal map that could not be applied. A normal map on a mesh with no tangents, under a material using the mesh's own texture coordinates, has no frame to be sampled against — so it is dropped rather than sampled against an arbitrary one. That is the right behaviour and it is invisible: the surface simply comes out flatter than the material says, which gets chased in the light and in the map before anybody suspects the renderer declined it. |
| 95th percentile of the synchronous completion benchmark, or null when disabled. |
| 95th percentile frame interval in the same window as MedianFrameMilliseconds. |
| 95th percentile completed GPU duration in the same window. Metal only; null elsewhere. |
| The pacing in one line, or null when there is nothing to say. Null rather than "0 late frames" so that a control which has never waited reads as a list of what happened rather than a list of what did not — the house style of |
| How many resources the scene being drawn is still waiting for: textures decoding off the render thread, or decoded and waiting for their turn to be copied. Zero is what |
| The unsupported rows of Derived rather than set. It used to be its own string, which meant the panel and the list could disagree about what this renderer does — and a second copy of a fact is how one of them gets fixed and the other does not. |
| What was asked for. Differs from |
| Set when something went wrong badly enough that the picture is not what was asked for. |
| The display's current extended-dynamic-range headroom: how many times brighter than SDR white it will show right now. One on a display with no headroom, or with EDR off; up to sixteen on a reference display. Null where the platform does not say, which today is everywhere but macOS. Reported because it changes without warning — another application's HDR window opening, a display waking — and every SDR pixel on the screen is scaled against it when it does. |
| Time spent inside this control's own render, exponentially smoothed — the other half of Frames arrive on the compositor's clock, which follows the display: a 60 Hz panel gives 16.7 ms between frames and a 120 Hz one gives 8.3, whatever the renderer is doing. So a reading of "60 fps" on its own cannot distinguish a renderer with ten times the headroom it needs from one that is only just keeping up, and the question that always follows — "is something capping this?" — has no answer in the number. This is that answer. Work well under the interval means the wait is for the display; work at or above it means the wait is for us. On the GPU backends it is the cost of building and submitting the frame, not of drawing it — the card is still working when this stops. On the CPU fallback there is nothing else, so it is the whole cost. |
| How many pixels are being rendered per pixel shown, along each axis. See What was actually applied rather than what was asked for: a request past the pixel budget or the device's largest texture comes back smaller, and |
| "OpenGL", "Metal", "Skia", or "not started". |
| How long the compositor took to come back after this control asked for a frame, exponentially smoothed — the third term of the frame, beside Read what this can and cannot see before drawing a conclusion from it. It measures the gap between What it cannot see is a compositor that keeps calling this control's render on time while presenting stale pixels to the screen. That failure exists, and every counter inside the process — this one included — reads healthy right through it, because the work really is being submitted at the rate it says. Nothing a guest renderer can measure distinguishes "submitted" from "presented"; only a camera pointed at the screen does. So a normal reading here is not evidence that the window is being updated. |
| Bytes of textures, meshes and instance buffers resident on this view's graphics device, on every renderer — decoded pixels in main memory, on the software one. The device's figure rather than this view's: every view on a device shares one set of resources, so two views showing one model read the same number and the model is counted once. See |
| The budget in force on the device — the most permissive of its views' |
| How many samples per pixel the rasteriser is taking. One means edges are a hard step and will crawl once anything moves; |
| Everything the scene held this frame, whether or not the camera could see it. |
| Mean CPU time to build a scene snapshot, excluding render-thread submission and GPU work. |
| Managed bytes the last snapshot rebuild allocated. Worth watching in the browser, where the heap is 32-bit and a game-shaped scene rebuilds every frame indefinitely. In practice this is the size of the draw-item array and nothing else: the walk itself runs on reused buffers, and the array is allocated fresh on purpose because that is what makes a snapshot safe to hand to the render thread. |
| The snapshot cost in one line, or null for a scene that is not being rebuilt. |
| How many times a second the scene graph is being flattened into a fresh snapshot. Zero for a static scene, which is the point of the design — the renderer keeps drawing the same snapshot until something changes. A scene that animates by mutating node transforms rebuilds once per UI frame however many nodes it touched, so this tracks the frame rate rather than the number of mutations. |
| A shadow resource or pass failure, or null when no failure was reported. |
| What the shadow map's frustum did this frame, in one line, or "none". Which shape the casting light asked for — a box for a directional, its own cone for a spot, six faces of a cube for a point light — and how large one of its texels is in the world. That last number is the one worth reading: a shadow map spread over more world than it can describe is technically working and practically useless, and it looks exactly like one that is fine, because a bad shadow is still a shadow. |
| Shadow-map refreshes since backend creation. A cached map does not increment this counter. |
| Shadowed lights actually applied by the backend, after capability and resource limits. |
| Size of the render target in real pixels. |
| Draw items — meshes and sprites — the software renderer left out of the last frame entirely because its triangle budget was spent before they were reached, or zero. The first item is never among them: a single mesh over the whole budget is drawn cut short rather than dropped, and its missing triangles count in |
| Triangles the software renderer's budget left undrawn in the last frame, or zero — on every GPU backend, and on the software one whenever the scene fits. The number behind the sentence in |
| Resident material/environment texture allocations including mips and driver alignment. Excludes render targets and shadow maps. Metal only; null elsewhere. |
| What the renderer wants to say about this frame beyond the numbers: texture upload progress and filtering on the GPU backends, and on the CPU one whatever budget it had to apply — triangles it did not draw, or a frame filled smaller than the control and stretched to fit. Null when there is nothing to report, which is the ordinary case. More than one thing can be true at once, so treat it as a sentence to show rather than a code to switch on. |
| Triangles submitted in the last frame. |
| Uploads the device has made since its registry was created — a refresh of a resident texture counts as one more. |