Multithreaded rendering with Scaleform involves at least two threads - the advance thread and the rendering thread.
Advance thread is typically the thread that creates a movie instance, handles its input processing, and calls GFx::Movie::Advance to execute the ActionScript and timeline animation. There can be more than one Advance thread in the application; however, each movie is typically accessed by only one advance thread. In most Scaleform samples, the advance thread is also the main application control thread, issuing commands to the render thread as needed.
The main job of the rendering thread is to render graphics on screen, which it does by outputting commands to the graphics device, typically through an abstraction layer such as Render::HAL. To minimize communication overhead, the Scaleform render thread processes macro commands such as “add movie” and “draw frame” rather than micro-commands such as “draw triangles”. The render thread also manages mesh and glyph caches.
The render thread can either operate in lock-step or independently of the controlling advance thread, as follows: • With Lock-step rendering, the controlling thread issues “draw frame” commands to the render thread, typically between movie advance processing. The two threads work in parallel with the rendering thread drawing the previous frame while the advance thread processes the next frame. On multi-core systems this results in overall improved performance, while the one-to-one relationship between thread frames is maintained. • With independent operation mode, render thread draws frames independently from the advance thread, potentially running at a different frame-rate. Modifications to the movie become displayed shortly after they are complete, which is the next time the render thread gets around to drawing the updated movie snapshot.
The Scaleform Player application and Scaleform samples make use of the lock-step rendering mode both because it is easier to setup and more common in games.
During multi-threaded rendering, the Advance thread can modify internal movie state at the same time as the render thread accesses it for display purposes. Since non-deterministic access of data by two threads would result crashes and/or corrupted frames, the new renderer makes use of snapshots to ensure that the render thread always sees a coherent frame.
In Scaleform, a movie snapshot is a captured state of a movie at a given point in time. By default, a snapshot is captured automatically at the end of the GFx::Movie::Advance call; it can also be captured explicitly by calling GFx::Movie::Capture. Once a snapshot is captured, its frame state becomes available for the render thread, while being stored separately from the dynamic state of the movie. When drawing a frame, the render thread calls NextCapture on a movie handle to grab the most recent snapshot and use it in rendering.
This design has several implications for developers:
The last point exhibits an important behavior difference between Scaleform 4.0 and earlier versions. While in Scaleform 3.3 developer could call Movie::Invoke followed by Display to present changes to screen, they are now required to either call Capture or Advance for the changes to show up. Typically this is not required as input processing and Invoke calls can be grouped together before the call to Advance.
Render::HAL::Display makes device calls to render a frame of the movie on the rendering device. For performance reasons, various device states, such as blending modes and texture storage settings, are not preserved and the state of the device will be different after the call to Render::HAL::Display. Some applications may be adversely affected by this. The most straightforward solution is to save device state before the call to Display and restore it afterwards. Greater performance can be achieved by having the game engine re-initialize its required states after Scaleform rendering.
Scaleform 4.4 introduced the ability to perform a large portion of CPU-side rendering on a thread, other than the main render thread. This is either implemented using platform specific graphics APIs that allow for the recording of command, or by recording the commands in a format outside the rendering API. In some platform specific documentation, this process is referred to as deferred contexts, or as command buffer recording.
Enabling this functionality in Scaleform varies per platform. Refer to the table below for the details.
Platform | Details |
---|---|
D3D9 | Pass HALConfig_SoftwareDeferredContext in the ConfigFlags member of HALInitParams. |
D3D11 | Pass a deferred context created with ID3D11Device::CreateDeferredContext (instead of the immediate context) as the pD3DContext member of D3D1x::HALInitParams. |
XBox One | Pass a deferred context created with ID3D11Device::CreateDeferredContext (instead of the immediate context) as the pD3DContext member of D3D1x::HALInitParams. |
Playstation 4 | No additional requirements. |
WiiU | Available in future version of 4.4 |
OpenGL/GLES | Pass HALConfig_SoftwareDeferredContext in the ConfigFlags member of HALInitParams. |
The execution of the recorded command buffer needs to eventually be executed on the main rendering thread. This is done via the HAL::Submit command, which must be called once per HAL::BeginScene/ EndScene pair. If HAL::EndScene is called on the main rendering thread, HAL::Submit will be called implicitly for this scene. The user must ensure that the command buffer recording is finished on the Scaleform rendering thread, before calling HAL::Submit.
Note: On Playstation 4, the process is slightly different. The HAL::Submit call does not execute the command buffer. Instead, the contents of the command buffers can be accessed with the PS4::HAL::AppendCommandBuffersToList function, and submitted by the user to sce::Gnm::submitCommandBuffers. Alternatively, the context can be allocated by the user, provided to Scaleform via PS4::HAL::SetGfxContext, and submitted by the user.