Shared View Render Targets
Overview
In cases where there are multiple cohtml::Views
, the memory/performance overhead of the UTextureRenderTarget2D
s 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, Gameface 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::OnProvideUITextureForViewWrapper
event, which will be fired when anFCohtmlViewWrapper
is created, just before it has to create itsFCohTexture
, allowing you to instead provide it with aFCohTexture
that you have created on your own. - To bypass the
FCohTextureCollector
lifetime management, we’ve provided aFCohTexture::AcquireManuallyManagedTexture
API, 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
FCohtmlViewWrapper
to the expected size (this may potentially also require a clear of the render target texture). This process is showcased in theShared Render Targets
sample. - 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 Gameface Views work with textures
While Unreal’s render target class is technically UTextureRenderTarget2D
, what Gameface is interested in is the underlying FRHITexture
and ensuring its lifetime. This is why Gameface’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 Gameface 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::View
which will render your HTML page. - To render things, a
cohtml::View
requires a render target texture. Such a texture is automatically created by theFCohtmlViewWrapper
when it is initialized. Internally, this render target texture is wrapped in aFCohTexture
class 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 Gameface 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 theFCohTextureCollector
singleton class.
- This
Limitations
- Displaying UI elements that share the same render target texture and size at the same time is possible, however, if all
Views
draw over the same section of the texture, you will see visual errors due to the overlap.- For such a scenario to work (given two
Views
let’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