API Reference · Renderers

RenderInfo

Namespace: Ava3D

public sealed record class RenderInfo

What 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

MemberDescription
RenderBackendKind ActiveBackend { get; set; }

Which renderer drew the last frame.

IReadOnlyList<BackendOption> AvailableBackends { get; set; }

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.

string ColorPipelineStatus { get; set; }

Actual frame colour pipeline and any format fallback.

string ContactOcclusionStatus { get; set; }

Active camera-depth resolution and contact sampling quality, or the fallback reason.

string Context { get; set; }

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 RenderInfo.Renderer cannot carry and the difference between a desktop driver and a phone's.

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.

string CullingSummary { get; }

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.

MaterialDebugView DebugView { get; set; }

Material diagnostic currently selected on the scene.

Nullable<long> DepthMemoryBytes { get; set; }

Resident shadow and contact-depth texture allocations. Metal only; null elsewhere.

string Device { get; set; }

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.

int DrawCalls { get; set; }

Draw calls in the last frame — one per visible mesh node, per lighting pass.

string EnvironmentStatus { get; set; }

Environment atlas size, probe count and most recent preparation latency.

string Error { get; set; }

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.

long Evictions { get; set; }

Resources released from the device since its registry was created, by grace or by budget.

IReadOnlyList<RendererFeature> Features { get; set; }

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.

double FrameMilliseconds { get; set; }

Time between frames, exponentially smoothed. Startup frames are excluded.

double FramesPerSecond { get; set; }

Frames drawn per second, averaged over the last half-second.

long FramesRendered { get; set; }

Total frames rendered since the control was attached — the scene drawn, not shown again.

long FramesReused { get; set; }

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 RenderInfo.MaxFrameRate. Now a repaint that is not due a frame, or that arrives under RenderTrigger.OnDemand with nothing changed, shows the frame already rendered: a blit, counted here. A view sitting under a busy overlay reads a steady RenderInfo.FramesRendered and a climbing number here, which is the saving working. It says nothing about what reached the screen — see RenderInfo.RepaintWaitMilliseconds for why no counter in this process can.

Nullable<double> GpuMilliseconds { get; set; }

Most recently completed GPU frame duration, excluding presentation. Reported by the Metal renderer only; null elsewhere.

int InternalHeight { get; set; }

Actual internal render-target height, after scale and allocation limits.

int InternalWidth { get; set; }

Actual internal render-target width, after scale and allocation limits.

long LateFrames { get; set; }

How many waits have exceeded a tenth of a second. See RenderInfo.RepaintWaitMilliseconds.

int LightCapacity { get; set; }

How many lights this renderer will draw, or Int32.MaxValue when there is no limit.

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.

bool LinearHdr { get; set; }

Whether this frame used a linear floating-point scene target and HDR composition.

double LongestRepaintWaitMilliseconds { get; set; }

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.

IReadOnlyList<string> MaterialDiagnostics { get; set; }

Actionable scene/material problems found while preparing this snapshot.

double MaxFrameRate { get; set; }

The frame-rate cap actually being applied. See Ava3DView.MaxFrameRate.

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.

Nullable<double> MedianCompletedMilliseconds { get; set; }

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.

double MedianFrameMilliseconds { get; set; }

Median frame interval in the last 240 post-warm-up frames; includes compositor pacing.

Nullable<double> MedianGpuMilliseconds { get; set; }

Median completed GPU duration in a rolling 240-sample window after 30 warm-up samples. Metal only; null elsewhere.

int NormalMapsDropped { get; set; }

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. Mesh.WithGeneratedTangents() is the fix, or a projected Material.UvSource, which brings its own frame.

Nullable<double> P95CompletedMilliseconds { get; set; }

95th percentile of the synchronous completion benchmark, or null when disabled.

double P95FrameMilliseconds { get; set; }

95th percentile frame interval in the same window as MedianFrameMilliseconds.

Nullable<double> P95GpuMilliseconds { get; set; }

95th percentile completed GPU duration in the same window. Metal only; null elsewhere.

string PacingSummary { get; }

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 RenderInfo.CullingSummary, RenderInfo.ShadowSummary and RenderInfo.SceneRebuildSummary.

int PendingUploads { get; set; }

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 Ava3DView.SceneReady means, and the frames until then draw a stand-in where each is missing.

string PlatformLimitations { get; }

The unsupported rows of RenderInfo.Features in one line, for a log or a status bar.

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.

RenderBackendKind PreferredBackend { get; set; }

What was asked for. Differs from RenderInfo.ActiveBackend when the request needs a restart — see BackendAvailability.RequiresRestart.

string PresentationColorSpace { get; set; }

Set when something went wrong badly enough that the picture is not what was asked for.

Nullable<double> PresentationHeadroom { get; set; }

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.

double RenderMilliseconds { get; set; }

Time spent inside this control's own render, exponentially smoothed — the other half of RenderInfo.FrameMilliseconds, and the only one of the two that says anything about headroom.

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.

double RenderScale { get; set; }

How many pixels are being rendered per pixel shown, along each axis. See Ava3DView.RenderScale.

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 RenderInfo.Size is the number of pixels that resulted.

string Renderer { get; set; }

"OpenGL", "Metal", "Skia", or "not started".

double RepaintWaitMilliseconds { get; set; }

How long the compositor took to come back after this control asked for a frame, exponentially smoothed — the third term of the frame, beside RenderInfo.FrameMilliseconds and RenderInfo.RenderMilliseconds.

Read what this can and cannot see before drawing a conclusion from it. It measures the gap between Invalidate and the render that answers it, which is where a render thread parked in [CAMetalLayer nextDrawable] shows up: three drawables, a one-second timeout, and the whole frame stops between the ask and the draw. A stall of that kind lands just over one second, and the shape is as diagnostic as the size — a contended lock has no reason to stop anywhere in particular, and a timeout does, so a number clustering at a constant across runs whose scene and draw counts are identical is a timeout upstream and not a load.

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.

long ResidentBytes { get; set; }

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 ResourcePolicy.

long ResourceBudgetBytes { get; set; }

The budget in force on the device — the most permissive of its views' ResourcePolicy.BudgetBytes, or the device's own recommendation where none named one. Past it, the least recently drawn resource nothing is showing is released first.

int SampleCount { get; set; }

How many samples per pixel the rasteriser is taking. One means edges are a hard step and will crawl once anything moves; RenderInfo.RenderScale is the lever that works on every backend.

int SceneItems { get; set; }

Everything the scene held this frame, whether or not the camera could see it.

double ScenePreparationMilliseconds { get; set; }

Mean CPU time to build a scene snapshot, excluding render-thread submission and GPU work.

long SceneRebuildBytes { get; set; }

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.

string SceneRebuildSummary { get; }

The snapshot cost in one line, or null for a scene that is not being rebuilt.

double SceneRebuildsPerSecond { get; set; }

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.

string ShadowError { get; set; }

A shadow resource or pass failure, or null when no failure was reported.

string ShadowSummary { get; set; }

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. Scene.ShadowCasters is what narrows it.

long ShadowUpdates { get; set; }

Shadow-map refreshes since backend creation. A cached map does not increment this counter.

int ShadowedLights { get; set; }

Shadowed lights actually applied by the backend, after capability and resource limits.

string Size { get; set; }

Size of the render target in real pixels.

int SkippedItems { get; set; }

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 RenderInfo.SkippedTriangles alone.

int SkippedTriangles { get; set; }

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 RenderInfo.Textures, for a caller that wants to compare rather than display: a test asserting a frame was whole, a panel colouring a figure. SoftwareSettings.TriangleBudget is the lever, and RenderInfo.SkippedItems says how many draw items were not reached at all.

Nullable<long> TextureMemoryBytes { get; set; }

Resident material/environment texture allocations including mips and driver alignment. Excludes render targets and shadow maps. Metal only; null elsewhere.

string Textures { get; set; }

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.

long Triangles { get; set; }

Triangles submitted in the last frame.

long Uploads { get; set; }

Uploads the device has made since its registry was created — a refresh of a resident texture counts as one more.

See also