Shared View Render Targets
Overview
In cases where there are multiple cohtml::Views, the memory/performance overhead of the UTextureRenderTarget2Ds that will be needed could potentially become far from negligible, because each View uses its own render target texture. This can impact memory when the textures are very high resolution, in addition to impacting performance because each render target texture will have to be managed individually (for example, during resizes).
In such scenarios where GPU memory needs to be reduced and/or achieve better performance, Prysm allows multiple cohtml::Views to share a single render target texture between them.
How to use
When a FCohtmlViewWrapper is created, normally it would also automatically request the creation of a FCohTexture of type ViewRenderTargetTexture and from then on, this FCohTexture will be tied to the lifetime of the FCohtmlViewWrapper. This is done through the FCohTexture::AcquireAutoManagedTexture API, which makes the internal FCohTextureCollector “collect” the created FCohTexture and provide a raw pointer to it.
In order to make multiple FCohtmlViewWrappers share the same FCohTexture (and thus the used render target textures), two things need to be done:
- You have to subscribe to the
ICohtmlPlugin::OnProvideUITextureForViewWrapperevent, which will be fired when anFCohtmlViewWrapperis created, just before it has to create itsFCohTexture, allowing you to instead provide it with aFCohTexturethat you have created on your own. - To bypass the
FCohTextureCollectorlifetime management, we’ve provided aFCohTexture::AcquireManuallyManagedTextureAPI, which returns a shared pointer to the createdFCohTexture.
Views with different sizes, extra steps may be required to ensure correctness. For more information, check the resizing section.Doing the above steps will enable you to determine if you want to provide a manually-managed FCohTexture or an auto-managed FCohTexture based on some custom conditions. The code would look something like this:
bool bSomeCustomCondition = false;
TSharedPtr<FCohTexture> ManuallyManagedCohTexture = FCohTexture::AcquireManuallyManagedTexture(ECohTextureType::ViewRenderTargetTexture);
ICohtmlPlugin::Get().OnProvideUITextureForViewWrapper.BindLambda([ManuallyManagedCohTexture, bSomeCustomCondition]() {
// Use the FCohTexture that will be manually managed or the FCohTextureCollector-managed one
return bSomeCustomCondition ? ManuallyManagedCohTexture : FCohTexture::AcquireAutoManagedTexture(ECohTextureType::ViewRenderTargetTexture);
});
For a more complete example of how similar code can work, we’ve created a Shared Render Targets map sample that you can check out.
Views should not be drawing to the same render target texture at the same time. Please visit the limitations section for more details.Resizing
There are a few things to note when deciding to share render targets between Views:
- If you have a UI element that will cover the size of your viewport and an in-world UI element which won’t be the same size as the viewport, but they share the same render target texture, you won’t be able to have both of them displayed at the same time without having visual artifacts, due to the size mismatch.
- Instead, it is recommended to either show only one of those components at a time, and when showing the other, ensure that you are also manually resizing the
FCohtmlViewWrapperto the expected size (this may potentially also require a clear of the render target texture). This process is showcased in theShared Render Targetssample. - An even better approach would be to split your shared render targets based on the types of components. For example, share only render targets between UI elements of the same size (e.g., in-world
Views), so that you won’t have to resize each time you switch between UI elements that will draw to a given render target.
- Instead, it is recommended to either show only one of those components at a time, and when showing the other, ensure that you are also manually resizing the
For cases where certain actions need to be performed after a resize has happened, you can subscribe to the following events:
ICohtmlPlugin::Get().OnResizeOrRecreateTexture; // Fired when a FCohtmlViewWrapper render target texture gets created or resized
ICohtmlPlugin::Get().OnResizeView; // Fired when the FCohtmlViewWrapper resizes the cohtml::View
ICohtmlPlugin::Get().OnSetViewResolutionForRendering; // Fired when the FCohtmlViewWrapper invokes the cohtml::View SetViewResolutionForRendering API
UTextureRenderTarget2D generally adds overhead, because its internal FTextureResource (which is what holds a reference to the actual FRHITexture) needs to get recreated as well.How Prysm Views work with textures
While Unreal’s render target class is technically UTextureRenderTarget2D, what Prysm is interested in is the underlying FRHITexture and ensuring its lifetime. This is why Prysm’s systems work with FCohTexture primarily. This doesn’t only go for render target textures that will be used by a cohtml::View, but also extends to every other Unreal texture type that is supported.
When a Prysm UI element is created (be it Component-based or UMG-based, there are two main things that happen:
- Each UI element will create a
FCohtmlViewWrapper, which, as the name implies, is a class wrapper around thecohtml::Viewwhich will render your HTML page. - To render things, a
cohtml::Viewrequires a render target texture. Such a texture is automatically created by theFCohtmlViewWrapperwhen it is initialized. Internally, this render target texture is wrapped in aFCohTextureclass whose main purpose is to work with Unreal APIs to retrieve a reference to theUTextureRenderTarget2D’sFRHITexture, in order to prevent Unreal from deleting it too early, while Prysm is still in the process using it in its rendering pipeline.- This
FCohTexture’s lifetime is tied to aFCohtmlViewWrapper’s lifetime, but it is not managed by it. That is the job of theFCohTextureCollectorsingleton class.
- This
Limitations
- Displaying UI elements that share the same render target texture and size at the same time is possible, however, if all
Viewsdraw over the same section of the texture, you will see visual errors due to the overlap.- For such a scenario to work (given two
Viewslet’s say) you would need to only draw to one section of the texture with oneView, and another section of the texture with the otherView. This behavior would very closely resemble our Surface Partitioning feature, so perhaps that would be a better solution depending on the case.
- For such a scenario to work (given two