Ava3DView
public sealed class Ava3DView : Controls.ControlA 3D view that works on every platform Avalonia targets, including the browser.
It renders through Avalonia's public SkiaSharp graphics lease rather than through OpenGlControlBase — which does not exist on the web platform — into its own framebuffer, so it composites like a normal control: clipping, transforms, opacity and z-order all behave. Where the platform offers OpenGL it uses it, where it offers Metal it uses that, and where it offers neither it falls back to drawing triangles through Skia rather than showing nothing.
Properties
| Member | Description |
|---|---|
| Frame the scene automatically the first time it has any extent. On by default. |
| The camera. Mutable and owned by the caller — move it, then call |
| How many captures may be in flight on this view at once — queued for a frame, or on their way back from the device. Eight by default. A request past the limit throws |
| What the view is painted in before there is a scene, and for as long as The default is the dark blue-grey the empty view has always had. Set it to the colour of whatever the view sits on, and the moment before the first frame stops being a visible rectangle — and |
| What the renderer is doing. Refreshed a few times a second. |
| Whether the control advances the animators in Turn it off for a caller that drives time itself — a recorder rendering thirty frames a second of a scene the display is showing at 120, a test pinning a pose, a slider being scrubbed. Both clocks running is not a subtle fault: the clips play at whatever the sum of the two rates is. |
| Left-drag orbits the camera. |
| Middle- or right-drag moves what the camera looks at. |
| Whether a click raises |
| The wheel moves the camera closer and further. |
| The most frames a second this control will draw. Zero, the default, is uncapped. A separate lever from , and all four combinations are real. Continuous with a cap is a low-rate heartbeat, for a view that must catch a change nothing announced. On demand with a cap is a game view that goes quiet when parked and holds thirty while it is playing. One enum could not express both without four members and a payload, and two scalars in a style say it in two lines. An absolute rate, not a division of the display's. Unity's Suspended for the length of a drag. A capped view that lags the pointer reads as a broken control rather than as a saving. The cap only ever removes frames. It cannot add one, so a rate above what the display offers does nothing at all, and a view already idle under It caps rendering, not repainting. A compositor also draws a visual when the window around it changes — a panel over the viewport, a host's overlay — and nothing here can decline that. What this control can do is answer such a repaint with the frame it already has: a repaint that arrives before the next frame is due shows the last rendered frame again, and only a due one renders. So a view sharing a window with something that repaints on its own no longer keeps that thing's rate as a floor; it keeps its cap, and |
| Which renderer to use. Switching between the GPU and |
| How many pixels to render for each pixel shown, along each axis. One by default; two renders four times as many and shows the average of them. This is the anti-aliasing control. Every renderer here rasterises one sample per pixel, so a silhouette is a hard step and, once anything moves, that step crawls from one pixel to the next. Nothing about the geometry, the mip chain or the map's resolution touches it — the edge is where two triangles meet and there is one sample to decide it with. Rendering larger and letting the composite average it down is the oldest fix there is and the only one that works identically on all four backends, including the software one, which has no multisampling to offer. What it costs. The square. 1.5 is 2.25× the fill and 2 is 4×, and fill is most of a frame at any real window size, so this is a knob to turn up until the frame time says stop rather than a default to raise. Measured on a consumer's interior scene at 2: pixels changing by more than a fifth between two frames of a slow pan fell from 1.91% to 1.18%, and the difference against a 3× reference fell with it. Where it stops. The target is capped by the same two guards every other size is — a total pixel budget and a maximum side, and then whatever the backend says it can allocate. Asking for more than that is not an error and does not fail: the frame comes out at the largest size that fits, which is what A capture's own scale — see |
| When this control draws: every compositor frame, or only after something changed. Defaults to
|
| How long this view's device keeps textures and meshes nothing is drawing, how much it may keep, and how much of each frame it spends copying new ones up. Null — the default — applies The policy is the device's, not this view's alone: every view on the same graphics device draws from one set of resident resources, and where their policies differ the most permissive value of each field is in force — the largest budget, the longest grace, the most upload time. Read once when assigned; assign again to change it. |
| How many samples per pixel to rasterise with: 1, 2, 4 or 8. One by default. The cheaper half of the same job does. Multisampling shades once per pixel and only tests coverage per sample, so it fixes the silhouette — which is where nearly all of the crawl is — for a fraction of what rendering the whole frame larger costs. What it does not fix is anything inside a triangle: a shimmering texture or a specular sparkle is unaffected, because there is still one shade per pixel. For those, Not every renderer can. Metal takes it. OpenGL takes it on ES 3 and desktop GL 3.0 and above, which is every context but GL ES 2. Vulkan and the software renderer do not, and neither does a device that will not allocate the count asked for. None of that is an error and none of it changes what is drawn — the frame comes back at one sample. |
| The scene to draw. Mutate it freely on the UI thread; the view keeps up. |
| Whether the CPU renderer shares its work across cores. True by default; no effect on the GPU backends, which have nothing to share. Two things share, and the second is the larger of them. Placing and lighting vertices is split by draw item; filling pixels is split into horizontal bands. Between and after them — clipping, emitting into one buffer, sorting the frame's triangles back to front, handing the result to Skia — is one list in one order and stays on one thread. Since the fill is most of a frame at any real window size, most of a frame is shared. The reason to turn it off is to measure what it is worth on your own scene, which is a comparison worth making rather than taking on trust: the answer differs by scene and by platform, and this is a switch rather than a cleverer default because of that. A WebAssembly build compiled for threads also pays a little for shared memory on every access whether a second thread runs or not, so holding one to a single core with this is the worst of both — it keeps the cost and gives up the benefit. |
| What the frame after a scene assignment shows: the new scene at once, with stand-ins for what has not arrived, or the old scene's frame until the new one is resident. |
| Per-frame records, when somebody wants them. Null — the default — costs nothing. Assign a |
Fields
| Member | Description |
|---|---|
| Backing property for |
| Backing property for |
| Backing property for |
| Backing property for |
| Backing property for |
| Backing property for |
| Backing property for |
| Backing property for |
| Backing property for |
| Backing property for |
| Backing property for |
| Backing property for |
| Backing property for |
| Backing property for |
| Backing property for |
| Backing property for |
Events
| Member | Description |
|---|---|
| Raised when anything that moves a projection has moved: the camera, or the control's size. This is the signal for keeping 2D placed against 3D — see
|
| Raised on the UI thread once per renderer, after it has drawn its first frame — whatever that frame was: the empty view in Once per renderer rather than once per view: a view taken out of the tree and put back starts again, with a new renderer and a new first frame, and so does one whose renderer was dropped by |
| Raised when a click hits geometry, or misses it — check |
| Raised on the UI thread once per assignment of Once per assignment, not per change: a scene that moves after it was announced is a ready scene that moves. Assign it again to hear about it again — or put the view back in the tree, which gives it a new renderer that has to draw the scene afresh and announces it once it has. |
Methods
| Member | Description |
|---|---|
| Reads one rendered frame back into memory. The request is queued for the render thread, and the next frame it renders serves it — the frame on screen, when the request is for the view's own size and camera; a frame rendered for the request alone otherwise, so that a capture at another size or from another camera never changes what is shown, or the view's own target. The scene is as it is at the time of the call: a change made just before is in the frame, however the dispatcher schedules its rebuild. Several requests queued before one frame all come out of it. The readback runs without the CPU waiting for the GPU where the graphics API allows it — Metal and OpenGL — and the encoding runs on the thread pool; the task completes on the caller's synchronisation context, never inside a frame. A view that is not in the tree waits for its attachment. Leaving the tree completes every pending capture as cancelled, as does the token. A request past
returns The frame, which the caller disposes. A request for linear pixels from a scene that has none fails with |
| The view and the Avalonia content around it as one picture:
returns The bitmap, which the caller disposes. |
| Frames the whole scene. |
| Call after moving |
| Rebuilds the snapshot and draws, whether or not anything in the scene said it had changed. Not usually needed: an attached |
| Casts a ray from a point on the control into the scene and returns the nearest triangle it hits. Runs on the UI thread against the live scene graph, which is the only place the geometry is authoritative. Bounding boxes reject most nodes before any triangle is touched. |
| Decodes every texture and readies every mesh of The work is done as the frames go by: decoding on the thread pool, copies on the render thread within the policy's milliseconds a frame, exactly as for a scene that is on screen. A view that is not in the tree waits for its attachment first, because there is no device to prepare for until then. What a preparation makes resident stays in use until the scene is assigned to this view, or the view leaves the tree; a scene prepared and never shown holds its resources until then. Cancelling releases what was decoded and nothing else is drawing, and the task ends cancelled.
|
| Where a world position lands on this control. X and Y are in this control's own device-independent coordinates — the same ones Z at or below zero means the position is not in front of the near plane — behind the camera, or nearer than the frustum begins. X and Y are then meaningless and must not be placed: a point behind the camera projects mirrored, so it lands on the opposite side of the picture looking entirely plausible. Test Z before using X and Y. This is the mistake this method is shaped to make visible rather than to prevent, because a caller clamping an off-screen marker to an edge needs the direction and a nullable would take it away. On the control rather than on The inverse is
|
| A transparent rectangle over the control, drawn for one reason: so that a pointer can land on it. This control's picture is not drawn here. It is drawn by a Everything the control offers the pointer is downstream of that — Transparent rather than a The one time it draws a picture is during |
| Drops the renderer and builds it again on the next frame, on whatever graphics context the platform then offers. Every GPU resource — programs, buffers, textures, shadow maps — is made again, and the scene is redrawn. The hook for a loss the control cannot see. It notices its own: a Vulkan device lost, an OpenGL context that Avalonia or the driver reports reset, a Metal command buffer that finished with a device error — each of those rebuilds without being asked. What it cannot notice is a loss only the host is told about, and the one that matters is the browser's: WebGL says |
| Keeps everything |
| The inverse of X and Y are this control's device-independent coordinates and Z is the distance from the near clip plane, exactly as
|