Backdrop Filter
Overview
With version 1.18 Gameface supports the backdrop-filter
CSS property. This allows certain elements to filter what is behind them and use the result as a part of their background. The feature is used to create the popular frosted glass effect.
This page gives information about details about Gameface’s support of the feature, how this affects the SDK integration, and some general points to keep in mind when using the backdrop-filter
property.
Filtered Background Content
There are certain peculiarities when it comes to what content specifically is filtered by the HTML elements using the backdrop-filter
property. In this section, we’ll explain what exactly is considered “content behind” in the context of this feature and Gameface.
First off, it should be clear that the elements with backdrop-filter
do not filter everything behind them. According to the standard, each backdrop-filter
takes only a certain part of the elements that come before it as input for the filter operations. For this reason, per the standard, we’ll introduce the term “Backdrop Root Image”. The Backdrop Root Image refers to the contents that will actually get filtered in the context of backdrop-filter
. When the DOM is rendered, there are several conditions where a new Backdrop Root Image is created. When a new Backdrop Root Image is introduced, it contains only elements that come after it and no elements that are part of the previous Backdrop Root Image. Each time a backdrop-filter
is used, the current (i.e. the last) Backdrop Root Image will be used as an input to the filtering operations. Thus the background of the element having backdrop-filter
will be formed by filtering only a certain number of the preceding elements and not all of them. This also means that nodes that come after this element in the DOM will not be filtered through the backdrop-filter
.
We’ll now go over the conditions under which a new Backdrop Root Image is introduced. According to the standard, those are:
- The root element of the document (HTML).
- An element with a filter property other than “none”
- An element with an opacity value less than 1.0
- An element with mask, mask-image, mask-border, or clip-path properties with values other than “none”
- An element with a backdrop-filter value other than “none”
- An element with a mix-blend-mode value other than “normal”
We can think of the listed conditions as barriers that “split” the background into sections and the filtered content comes only from the last section.
The following snippet illustrated the said so far:
<style>
#wrapper {
opacity: 0.5;
}
.rectangle {
width: 50px;
height: 150px;
background: red;
border: 2px solid green;
}
.filtered-background {
width: 500px;
height: 500px;
backdrop-filter: blur(7px);
}
</style>
...
<body>
<div class="rectangle">
Outer Div 1
</div>
<!-- this div intoruces new Backdrop Root as it has opacity 0.5 -->
<div id="wrapper">
<!-- this div does not intoruce a new Backdrop Root and it will be a part of the last one-->
<div class="rectangle">
Inner Div 1
</div>
<!-- this div does not intoruce a new Backdrop Root and it will be a part of the last one-->
<div class="rectangle">
Inner Div 2
</div>
<!-- this will filter the previous two divs but not the content outside of the wrapper div -->
<div class="filtered-background">
The content behind this will be filtered
</div>
<!-- this div will not be filtered by the previous element as it comes after it in the DOM-->
<div class="rectangle">
Inner Div 2
</div>
</div>
</body>
Example
To further illustrate the concept of backdrop-filter
we’ll look at two possible results of the following HTML:
<body class="mountain-image">
<div class="wrapper">
Wrapper element with no opacity
<div class="blue-rect">
Child of the wrapper
</div>
<div class="with-backdrop-filter">
The background of this element will be
inverted
<div class="red-rect"></div>
</div>
</div>
</body>
We’ll not go over the used styles as those are not important and can easily be inferred by their names.
In the case that the wrapper element does not have opacity, we get the following result:
The image is part of the root element and as the wrapper does not have opacity, the contents of the wrapper and the body itself are part of the backdrop root image at the point when the backdrop-filter
is used. Under those conditions, everything that is behind the div with the backdrop-filter
is filtered.
This will not be the case when we set some opacity on the wrapper element. Then we can expect the following resulting image:
The wrapper element introduces a new backdrop root image. The body element will not be part of this backdrop root and thus the mountain of the image will not be filtered by the backdrop-filter
. In this case, the backdrop root contains only elements that are part of the wrapper element.
Backdrop Filter in Gameface
For the most part, Gameface follows the standard. The concept of Backdrop Root Image is valid in Gameface and backdrop root images will be created under the same conditions as the standard mandates. However, there are a few details of our implementation that should be addressed.
First off, when using a page rendered by Gameface there is an implicit background that is currently unknown to the SDK. To explain this, it is useful to think of how the UI texture is used. Gameface first renders the contents of some view to a texture and then the texture is composed over the scene in some way. This means that effectively there is “something” (in most cases the 3D scene) behind the UI texture and this “something” is unknown at the time of rendering the DOM. For the backdrop-filter
to work properly and to be able to filter all of the contents behind a given element, Gameface has to have access to the real background that will be behind the UI texture when it is composited.
The following diagram illustrates what is being said:
For this reason, for Cohtml to be able to filter the user content behind the UI texture, the user has to provide what we term a “user-defined background”. This is a texture that contains the content that will end up behind the UI texture in the final compositing. There are a couple of implications stemming from this.
the user-defined background has to be fully rendered before the UI is rendered – Gameface needs to have access to the already scene to be able to filter it properly.
the user-defined background texture has to be in a readable state during the rendering of the UI – when the
backdrop-filter
property is used, the texture can potentially be used as a shader resource and Gameface has to have a guarantee that this will be possible.
It is important to note that the user-defined background will be filtered only when the root element (the body
) is part of the Backdrop Root Image used for a given backdrop-filter
. If the backdrop-filter
will use a backdrop root image created by some other element (say a div
element with an opacity of 0.5), the user-defined background will not be considered as part of the backdrop root image.
In Gameface there is also a Cohtml-specific property that can create a new Backdrop Root Image – coh-composition-id
. Elements with this property are drawn in separate layers which are then provided to the user via callbacks (see more about the Compositor usage). This means that Gameface cannot filter content behind the elements with coh-composition-id
.
A somewhat underspecified case is what happens when an element with a backdrop filter also has the mix-blend-mode
property. On its own, mix-blend-mode
causes the creation of a new Backdrop Root Image but when the same element also uses backdrop-filter
, the filter refers to the current backdrop root. Because of that, what happens in Gameface is the following: the current backdrop root is filtered with the given filters, then then the content of the element (background, border, and children) is blended with the filtered backdrop root based on the value of the mix-blend-mode
.
Integration changes
As explained in the previous section, using the backdrop-filter
feature in Gameface requires some special considerations when integrating the SDK in some game engines. Here we’ll go over these considerations again in more technical detail.
Readable Main Render Target
In a lot of cases when using backdrop-filter
, Cohtml has to filter content that is already in the UI texture. For this reason, the main render target (the UI texture given to Cohtml through the SetRenderTaget
method of the ViewRenderer
object) has to be bindable as a shader resource. This can mean different things for the different Graphics APIs but generally, we can say that the GPU resource representing the UI texture has to be created with the appropriate flags. For example, in DirectX 11 the texture has to be created with the D3D11_BIND_SHADER_RESOURCE
flag and in Vulkan the image has to be created with the VK_IMAGE_USAGE_SAMPLED_BIT
flag.
To be noted is that probably this is already the case and the user code will not have to worry about this point. The API of the SetRenderTaget
method of ViewRenderer
is not changed and setting the render target is not changed in any way.
User-defined background
If you want the content that will end up behind the UI texture to be filtered by the elements using backdrop-filter, the user code has to provide user-defined background.
This is done through the SetUserBackground
method of the View
. The API is similar to the one used for user images).
The method takes a void*
to an opaque user object which will end up in WrapUserTexture
method of the rendering backend. When the standard backends are used, this pointer has to be one of the provided object types for each backend (renoir::OpaqueUserTextureDx11
, renoir::OpaqueUserTextureVulkan
, renoir::Dx12Backend::StateResource
, etc).
The other argument is a description of the provided texture in the form of a renoir::Texture2D
object. The relevant fields are Width
, Height
, Format
, ContentRectX
, ContentRectY
, ContentRectWidth
, and ContentRectHeight
.
The following snippet shows an example usage of the method.
// this assumes that the DirectX 11 backend is in use
OpaqueUserTextureDx11 userObject;
...
userObject.Resource = uiTexturePtr;
userObject.SRV = uiTextureSRVPtr;
userObject.RTV = uiTextureRTVPtr;
renoir::Texture2D desc;
description.Width = width;
description.Height = height;
description.Format = rneoir::PF_R8G8B8A8;
description.ContentRectX = 0;
description.ContentRectY = 0;
description.ContentRectWidth = width;
description.ContentRectHeight = height;
view->SetUserBackground(&userObject, desc);
Paint
call to the ViewRenderer
.backdrop-filter
will be redrawn every frame. By the design of the feature, the filtered content can come from the user background. That is, the 3D scene itself will be filtered and Cohtml assumes that this scene changes every frame. For this reason, Gameface redraws the elements in every frame. Be mindful of this fact and the performance implications when using elements with backdrop-filter
.For a given frame, View::SetUserBackground
has to be called before View::Advance
for the user background to be used this frame. The WrapUserTexture
command will sent to the backend during the corresponding frame’s ViewRenderer::Paint
call. Multiple calls to View::SetUserBackground
can be issued but only the last set user background will be used in the next View::Advance
. If the user background need to change every frame, The proper flow of calling View::SetUserBackground
is as follows :
View::SetUserBackground
with user background for frameN
View::Advance
for frameN
- On the render thread,
ViewRenderer::Paint
for frameN