Skip to main content
Version: 0.9.0

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 · Source: RenderSender.cs

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​

MemberDescription
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()

Returns: Result<StreamId>

ChangeLayout​

Requests a layout for subsequent frames; their views and textures keep the order given in layout.

public Result<Unit> ChangeLayout(VideoFrameLayout layout)

Parameters

NameTypeDescription
layoutVideoFrameLayout

Returns: Result<Unit>

Exceptions

ExceptionCondition
ArgumentNullExceptionlayout 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

NameTypeDescription
cancellationTokenCancellationToken

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

NameTypeDescription
cancellationTokenCancellationToken

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

NameTypeDescription
frameShowRenderFrameShow

Returns: Result<Unit>

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()