RenderSender class
Streams the application's rendered frames to one target peer, displayed in the bound app
volume. Create one per target with RenderSenderControl.Create.
Namespace: Quaternar.Qaros · Assembly: Quaternar.Qaros.dll
public sealed class RenderSender : IDisposable
Implements: IDisposable
Remarks
Frame loop: RenderSender.BeginFrame (or RenderSender.BeginFrameAsync /
RenderSender.TryBeginFrame) hands out the per-view poses and FOVs; render with exactly
those into the frame from RenderSender.GetCpuFrame, RenderSender.GetD3D11Frame,
RenderSender.GetOpenGlFrame or RenderSender.GetVulkanFrame (the one matching the
sender's graphics API); then RenderSender.ShowFrame with the near/far actually used. The
mixer re-projects the frame with that metadata and the depth, so wrong metadata shows as
swimming content, not as an error. Only one frame cycle is open at a time; calling
RenderSender.ShowFrame without a begun frame fails with
StatusCode.LogicError.
Everything the sender hands out (view poses, hands) is in the bound app volume's app
content space, never room space. With auto-reconnects enabled, frame calls fail with
StatusCode.MediaStreamingStreamIsReconnecting while the stream reconnects
and recover on their own; keep the sender.
StatusCode.RenderingProducerStreamIsClosed from begin-frame means the
stream closed for good: dispose the sender and create a new one if the target is still
to be served.
Methods
| Member | Description |
|---|---|
| GetLayout() | The negotiated frame layout. Available as soon as the sender exists, before the first begin-frame and while reconnecting, so size lasting resources from it. |
| GetStreamId() | |
| ChangeLayout(VideoFrameLayout) | Requests a layout for subsequent frames; their views and textures keep the order given in layout. |
| GetCpuFrame() | CPU-backed frame access. The frame exists from creation, so its textures, views and sizes are readable before the first begin-frame; its pixels may be written only inside a frame cycle. |
| GetD3D11Frame() | D3D11-backed frame access: shared textures to render into directly. Readable before the first begin-frame, writable only inside a frame cycle. |
| BeginFrame(CancellationToken) | Blocks until a new frame may be produced and returns its per-view poses and FOVs. The poses are in the bound app volume's app content space. |
| BeginFrameAsync(CancellationToken) | Async RenderSender.BeginFrame; fails the same way, including with StatusCode.RenderingProducerAppVolumeUnresolved. |
| TryBeginFrame() | Non-blocking RenderSender.BeginFrame: starts the begin-frame wait on the first call and polls it on every call. The value is the frame info once the wait completed, or null while it is still pending - poll again later. A failure is a real failure, such as StatusCode.RenderingProducerAppVolumeUnresolved (recoverable). |
| GetOpenGlFrame() | OpenGL-backed frame access; requires a library built with QAR_ENABLE_OPENGL (otherwise EntryPointNotFoundException). Readable before the first begin-frame, writable only inside a frame cycle. |
| GetVulkanFrame() | Sender-owned Vulkan frame and its current image state; requires a library built with QAR_ENABLE_VULKAN (otherwise EntryPointNotFoundException). Valid until the next begin-frame or this sender's disposal. Inside a frame cycle, record final layouts, queue ownership and synchronization on it before RenderSender.ShowFrame. |
| ShowFrame(RenderFrameShow?) | Submits the rendered frame. RenderFrameShow.RenderedNearFar and any view-override pose are in app content space. |
| GetLastHands() | The newest hands sample the target device sent since the previous call; poll once per frame. |
| Dispose() | Ends the stream to the target. Every RenderFrameInfo this sender handed out becomes invalid. |
GetLayout
The negotiated frame layout. Available as soon as the sender exists, before the first begin-frame and while reconnecting, so size lasting resources from it.
public Result<VideoFrameLayout> GetLayout()
Returns: Result<VideoFrameLayout>
Remarks
Its views come in the order declared in RenderSenderOptions.FrameViews,
or after RenderSender.ChangeLayout in the order of that layout, and every frame
matches it.
GetStreamId
public Result<StreamId> GetStreamId()
ChangeLayout
Requests a layout for subsequent frames; their views and textures keep the order given
in layout.
public Result<Unit> ChangeLayout(VideoFrameLayout layout)
Parameters
| Name | Type | Description |
|---|---|---|
layout | VideoFrameLayout |
Exceptions
| Exception | Condition |
|---|---|
| ArgumentNullException | layout is null. |
Remarks
Not callable inside a frame cycle: between a successful begin-frame and its
RenderSender.ShowFrame it fails with StatusCode.LogicError and changes
nothing.
GetCpuFrame
CPU-backed frame access. The frame exists from creation, so its textures, views and sizes are readable before the first begin-frame; its pixels may be written only inside a frame cycle.
public Result<VideoFrameCpu> GetCpuFrame()
Returns: Result<VideoFrameCpu>
GetD3D11Frame
D3D11-backed frame access: shared textures to render into directly. Readable before the first begin-frame, writable only inside a frame cycle.
public Result<VideoFrameD3D11> GetD3D11Frame()
Returns: Result<VideoFrameD3D11>
Remarks
The sender acquires and releases the textures' keyed mutexes itself unless
StreamParamsD3D11.AcquireKeyedMutexSync was false at creation; then the
application drives the keyed-mutex chain with the frame's synchronization data.
BeginFrame
Blocks until a new frame may be produced and returns its per-view poses and FOVs. The poses are in the bound app volume's app content space.
public Result<RenderFrameInfo> BeginFrame(CancellationToken cancellationToken = default)
Parameters
| Name | Type | Description |
|---|---|---|
cancellationToken | CancellationToken |
Returns: Result<RenderFrameInfo>
Remarks
Fails with StatusCode.RenderingProducerAppVolumeUnresolved
while the sender's app volume does not resolve (not known to the session
yet, or removed): app content space is undefined, no frame is begun and
nothing is handed out in room space instead. That is recoverable - keep
calling, it succeeds once the volume resolves; a removed volume never
does, so recreate it. Meanwhile RenderSender.GetLastHands reports both
hands untracked. The call is held about 16 ms before failing, so a retry
loop does not spin.
While the receiver has paused the stream (for example its display is not
visible) the call waits instead of failing, and the frame begins once the
receiver resumes. Cancel cancellationToken to stop
waiting; the call then fails. A frame still open from a previous
begin-frame without RenderSender.ShowFrame is ended first.
Read the returned RenderFrameInfo before RenderSender.ShowFrame
and dispose it afterwards.
BeginFrameAsync
Async RenderSender.BeginFrame; fails the same way, including with StatusCode.RenderingProducerAppVolumeUnresolved.
public Task<Result<RenderFrameInfo>> BeginFrameAsync(CancellationToken cancellationToken = default)
Parameters
| Name | Type | Description |
|---|---|---|
cancellationToken | CancellationToken |
Returns: Task<Result<RenderFrameInfo>>
Remarks
Like RenderSender.BeginFrame it waits through a paused stream; cancel
cancellationToken to stop waiting.
TryBeginFrame
Non-blocking RenderSender.BeginFrame: starts the begin-frame wait on the
first call and polls it on every call. The value is the frame info once
the wait completed, or null while it is still pending - poll again later.
A failure is a real failure, such as
StatusCode.RenderingProducerAppVolumeUnresolved (recoverable).
public Result<RenderFrameInfo?> TryBeginFrame()
Returns: Result<RenderFrameInfo>
Remarks
While the receiver has paused the stream the wait does not complete: the call keeps succeeding with null until the receiver resumes.
GetOpenGlFrame
OpenGL-backed frame access; requires a library built with
QAR_ENABLE_OPENGL (otherwise EntryPointNotFoundException).
Readable before the first begin-frame, writable only inside a frame cycle.
public Result<VideoFrameOpenGl> GetOpenGlFrame()
Returns: Result<VideoFrameOpenGl>
GetVulkanFrame
Sender-owned Vulkan frame and its current image state; requires a library
built with QAR_ENABLE_VULKAN (otherwise EntryPointNotFoundException).
Valid until the next begin-frame or this sender's disposal. Inside a frame cycle,
record final layouts, queue ownership and synchronization on it before
RenderSender.ShowFrame.
public Result<VideoFrameVulkan> GetVulkanFrame()
Returns: Result<VideoFrameVulkan>
Remarks
Readable from creation, so resources can be created against the real images during
initialization. Every begin-frame discards the previously returned frame: fetch it
again in each frame cycle, and do not write through an instance from an earlier
cycle. RenderSender.ShowFrame of a Vulkan sender fails with
StatusCode.LogicError when this was not called in the current cycle.
ShowFrame
Submits the rendered frame. RenderFrameShow.RenderedNearFar and any view-override pose are in app content space.
public Result<Unit> ShowFrame(RenderFrameShow? frameShow = null)
Parameters
| Name | Type | Description |
|---|---|---|
frameShow | RenderFrameShow |
Remarks
Report the near/far actually rendered with; the defaults (0.1 to 100) are used when
frameShow is null. When a view was rendered with a pose or FOV
other than the one RenderFrameInfo handed out, declare it with
RenderFrameShow.WithViewOverrides. Fails with
StatusCode.LogicError when no frame was begun.
GetLastHands
The newest hands sample the target device sent since the previous call; poll once per frame.
public Result<DeviceHandsWithJoints> GetLastHands()
Returns: Result<DeviceHandsWithJoints>
Remarks
Every pose (the aim pose and the joint poses), joint radius and joint linear/angular velocity is in the bound app volume's app content space, like the view poses: right-handed, +Y up, -Z forward, app units. Samples arrive only while the target peer is focused on the volume and not editing it. With nothing new, the last sample is returned again while it is younger than a second; after that, and while the app volume does not resolve, both hands are reported untracked. Fails once the hand stream has closed.
Dispose
Ends the stream to the target. Every RenderFrameInfo this sender handed
out becomes invalid.
public void Dispose()