Skip to content

WIP: Asynchronous remoting prototype

This merge request is supported by another merge request by the same name on the same branch in VTK.

Changes include disabling certain synchronous data transfers in the main thread; in vtkPVClientServerSynchronizedRenderers Master-/SlaveEndRender, in vtkPVSynchronizedRenderWindows::HandleEndRender, ClientStartRender and RootStartRender, and in the vtkProgressHandler. Basically everywhere in which frames or their parameters are sent and waited upon, or barriers are in place.

Instead, asychronous remoting launches a dedicated thread based on the CommunicateFrame() function, initiated by SetParallelController/SetClientServerController in renderers and windows. One thread is for sending frame parameters in vtkPVSynchronizedRenderWindows, and another for sending the compressed frames back in vtkPVClientServerSynchronizedRenderers. These set up their own communicators, that use two different ports (derived from the main thead's communication port) for asynchronous data transfer. The relevant data is passed from the main thread to the asynchronous threads or back using shared variables. Both the client and the server therefore have two additional asynchronous threads running, a parameter thread and a frame thread.

The execution flow is as follows: the client has a timed trigger generating render calls (not based on interaction, see the VTK commit), which ends up at vtkPVSynchronizedRenderWindows::ClientStartRender(). This function updates the camera parameter shared variables, and once the client-side parameter thread is notified of a change in parameters, it sends those to the server side. The sending/receiving of the render trigger event still happens from/in the main thread, but if the server-side main thread loop is quicker than the timed trigger interval, it will wait for new camera parameters to arrive. Otherwise, camera parameters in the parameter thread on the server side just overwrite older camera parameters, and the main thread just picks up what has last been received by the parameter thread. After rendering has been completed by the main thread, vtkPVClientServerSynchronizedRenderers::SlaveEndRender() enqueues the newly compressed frame data in a shared queue for the frame thread to send back to the client. All rendered frames have to be sent back and decompressed, nothing is skipped, to support temporal compression schemes. The server-side main thread can throttle itself (wait) if the frame thread notices network congestion. This is based upon network stats received at the frame thread on the client side, sent back to the server side. The suggested throttle rate is calculated in vtkPVClientServerSynchronizedRenderers::CalculateThrottleRate(). Lastly, the client-side frame thread receives the compressed frames and again enqueues them in a shared queue for the main thread, in order for frames not to be skipped. If the client main thread is too slow (which is uncommon) or there is lots of stutter in the network causing multiple frames to arrive within a short time interval, the client thread will decompress multiple frames from the queue after one another and display only the last one.

This describes pretty much all of the functionality, except for a change in the vtkPVRenderView class: the server-side can skip rendering for a particular render event, effectively "eating" the render event without doing any work. This is necessary in the common case where rendering on the server happens at a lower frequency than the render triggers are sent via the client: even though render parameters are received asynchronously and overwritten by the server-side parameter thread, the client main thread will still queue up an ever-growing number of render events to the server. As the main thread still has all its communication aside from rendering going on via the same event queue, opening menus with communication to the server while having a huge backlog of render events sitting around will massively impact interactivity. So from time to time, we'd like to just remove render events from that queue and keep it small in size. To this end, vtkPVRenderView::SkipRender() simply compares the last frame id as received from the parameter thread (stored as NumAsyncFrames) to the amount of render calls performed on the server side (SkipRenderId). If that difference is too large, the server will skip as many render calls as possible as long that doesn't cost too much time. Skipping is done in a batched fashion like this, as there is always a discrepancy between the asynchronous and synchronous frame ids in timing; if the render event queue on the main thread is (almost) empty and the next render event happens to be delayed by the network, this introduces unnecessary waiting. It is better to have a handful of "old" render events laying around to be processed in this case. Also, the amortized costs of event processing is reduced in this way.

Merge request reports