Compare commits

...

51 Commits

Author SHA1 Message Date
Eliza Velasquez
84cecd334b materials: fix mutable spec constants nvidia bug
It turns out that I didn't understand how `FixedCapacityVector` works.
2025-09-03 18:13:09 -07:00
Eliza Velasquez
bb6ebd3aea materials: reintroduce mutable spec constants
This change apparently breaks nvidia, still working on it.
2025-08-27 13:27:39 -07:00
Sungun Park
1aab0585da Disable amortized shader compile by default (#9151)
This is a temporary resolution until we fix an underlying issue.
2025-08-27 12:46:58 -07:00
Andy Hovingh
1d1e15d915 webgpu: profiling and system tracing support 2025-08-27 10:41:14 -05:00
Doris Wu
ff394f7c1b feat: Add Visibility Bitmask method for GTAO (#9101)
* Quick test

* Fix incorrect logic and add some comments

* Set thickness parameter

* refactoring

* Update

* Generated files

* Some comments

* Update

* Update generated files

* Update

* Update

* Add comments
2025-08-26 23:27:02 +00:00
Syed Idris Shah
161b75b9b7 wgpu: Fix samples on linux
Window size should be calculated from windows not screen on XLIB.
XCB window is not implemented in Dawn. Remove the stale/wrong implementation in filament
2025-08-25 16:51:04 -04:00
Powei Feng
bc12a56920 vk: check rebind flow if bindDescriptorSet called between draw2 (#9139)
In the case with external samplers, if another descriptor set gets
bound between draw2(), then we need to check to see if a rebinding
of the pipeline is necessary.

Co-authored-by: Serge Metral <sergemetral@google.com>
2025-08-25 09:04:11 -07:00
Mathias Agopian
15bb295ec6 A new froxel grid vizualization for FilamentApp
FilamentApp now has debugging options to enable or disable the
camera and directional shadow frustums, as well as the new
"froxel grid" visualization.

"froxel grid" is automatically enabled when "froxel debugging" is
enabled in the debug gui in gltf_viewer.


New corresponding debugging APIs were added to View.
2025-08-22 15:55:46 -07:00
Mathias Agopian
cea178a40c froxel vizualization can now be turned on/off per view
Before this change once the froxel vizualizaition was enabled via
the Engine debug framework, it applied to all views. This adds a
debug API on View to control whether the view will display this
vizualization.
2025-08-22 15:55:46 -07:00
Mathias Agopian
f34bb3d775 ViewerGui: hide stereo options when stereo is not enabled 2025-08-22 15:37:06 -07:00
Mathias Agopian
30112b5b5b gltf_Viewer: the -y (--eyes) options wasn't working 2025-08-22 15:37:06 -07:00
Mathias Agopian
9aaa1bf413 FilamentApp: fix projection matrix
For some reason we were not setting the aspect-ratio to 1 and scaling 
the clip-space instead. This could lead to lighting and clipping
issues.
2025-08-22 15:37:06 -07:00
Powei Feng
0471c5de2f vk: use firstBitSet API (#9128)
This is more efficient than the prevoius way
2025-08-22 22:31:48 +00:00
Juan Caldas
65cca71abf webGPU Run Load Image Tests (#9140) 2025-08-22 18:11:16 -04:00
Doris Wu
e3384feee0 Clamp before assign to uint (#9132) (b/440220363) 2025-08-22 03:51:11 +00:00
Mathias Agopian
3650556de4 make sure PixelParams::thickness is initialized
If screen-space refraction was used enabled but the thickness
parameter wasn't explicitly set in the material, it would end-up
being uninitialized. Now it's initialized to 0.5 by default,
which is the value used for subsurface.

Also removed some calculations dependent on thickness being set, as
they assumed a thickness of 0.
2025-08-21 20:25:01 -07:00
Powei Feng
679b08b5db github: add script and action for getting gltf assets (#9085)
- Script for getting models from glTF-Sample-Assets
- Add support for reading gltf models from a file.
2025-08-21 22:16:12 +00:00
Juan Caldas
5fd5cd270c webgpu: Skip failing tests for WebGPU (#9133)
BUGS = [424157731]
2025-08-21 20:37:32 +00:00
Andy Hovingh
275ffb409f wgpu: add texture swizzle support (#9129)
BUGS = [433944896]
2025-08-21 20:03:37 +00:00
Powei Feng
0261af22b0 Feature flag guard GEN_MIPMAPPABLE precondition (#9134) 2025-08-21 18:50:06 +00:00
Powei Feng
60bdbb3d1f matdbg: fix matinfo no variants bug (#9127)
matinfo -w doesn't show the variants for any backend other than
metal.  The reason is that the code for updating the variant
list wasn't being triggered on currentBackend changes.
2025-08-21 11:04:59 -07:00
Juan Caldas
84c68f7080 webgpu: Remove texture logs (#9126) 2025-08-20 19:02:43 +00:00
Juan Caldas
d9b3535be9 webgpu: Implement blitDEPRECATED (#9121)
BUGS = [436887647]
2025-08-20 18:20:34 +00:00
Juan Caldas
fe3c804c73 webgpu: Add missing constexpr (#9125) 2025-08-20 15:56:08 +00:00
Juan Caldas
d76ba8fb19 webgpu: refactor texture usage (#9044) 2025-08-20 10:49:25 -04:00
Filament Bot
5daa0cfe4b [automated] Updating /docs due to commit a7653cf
Full commit hash is a7653cf773

DOCS_ALLOW_DIRECT_EDITS
2025-08-20 06:27:19 +00:00
Powei Feng
a7653cf773 docs: override index.html to redirect to intro.md 2025-08-19 23:24:38 -07:00
Filament Bot
1b3a22876c [automated] Updating /docs due to commit 663a451
Full commit hash is 663a451031

DOCS_ALLOW_DIRECT_EDITS
2025-08-20 06:13:36 +00:00
Powei Feng
663a451031 renderdiff: enable vulkan (#9108)
RDIFF_BRANCH=pf/renderdiff-enable-vulkan
2025-08-20 06:10:01 +00:00
Mathias Agopian
96b26b85cc add a --workarounds option to matc
Currently the only values possible are 'none' and 'all'. 'all' is the
default. This option will be used to control code generation
workarounds individually. Currently 'all' disable the
MergeReturn and Simplification passes, which have causes issues in
the past on some older Android devices.
2025-08-19 22:10:46 -07:00
Mathias Agopian
69fe317052 matc: add -O0 and -Os aliases for -g and -S 2025-08-19 22:10:46 -07:00
Mathias Agopian
9e16263876 matc: source cleanup 2025-08-19 22:10:46 -07:00
Mathias Agopian
cb43e53b71 a small micro optimization when executing commands 2025-08-19 22:10:24 -07:00
Powei Feng
45fcea101f webgpu: enable choosing vulkan backend on MacOS (#9106)
- Add a Configuration struct to the WebGPUPlatform class.  This
   allows for client-side setting of WebGPU backend configurations.
 - Add a configuration for forcing the wgpu backend to pick a
   certain backend.
 - Add Vulkan as a potential backend for WebGPU + MacOS.
 - Locally modify Dawn so that it does not assume only switfshader
   is available for Vulkan on MacOS.
 - Enable vulkan support for Dawn (third_party/dawn/tnt/CMakeLists.txt)
 - Plumb option to pick different WebGPU backend through
   FilamentApp and gltf_viewer.
2025-08-20 04:50:17 +00:00
Powei Feng
a150fabace docs: fix broken links (#9118)
- Fix broken links in https://google.github.io/filament/main/
 - Make /README.md links consistent
 - Add additional replacement recipes for /README.md
 - Make /index.html redirect to /dup/intro.html because
   /index.html has the wrong relative links. This is essentially
   adding a redirect-only index.html to src_raw.
2025-08-19 22:37:07 +00:00
Sungun Park
95db13a544 Release Filament 1.64.0 2025-08-19 21:50:50 +00:00
Andy Hovingh
a8732caa1f temporary test workaround for missing read_pixels texture usage 2025-08-19 16:42:39 -05:00
Andy Hovingh
0d6995babc wgpu: fix finish(), where it was erroneously returning if a command encoder was not in flight. Also, it was not waiting for async buffer maps to finish 2025-08-19 09:00:08 -05:00
granade-work
c9a1e446c8 Add feature to emit image diff highlighting differing pixels when image comparison tests fail. (#9097) 2025-08-18 23:48:28 +00:00
Juan Caldas
39aef4b430 Remove assert and precondition (#9117)
BUGS = [422704995]
2025-08-18 19:18:39 -04:00
Ben Doherty
cc382fd571 Metal: verify that the native window is a CAMetalLayer (#9104) 2025-08-18 15:18:53 -04:00
rafadevai
55c65fb8a2 VK: Fix memory corruption in VulkanExternalImageManager (#9116)
When calling setExternalSamplerVkSet the recycle
function lambda captured some variables allocated
on the stack, which are out of scope by the time
the function is called.

Change the capture to be done by value instead of
by ref to avoid this problem.
2025-08-18 10:57:46 -07:00
Sungun Park
bcea4ef75d Update MATERIAL_VERSION to 64 2025-08-18 09:45:08 -07:00
Sungun Park
0ef7507464 mat: Store matc parameters in material packages (#9070)
This commit enhances the material compilation process by embedding the
`matc` command-line parameters directly into the compiled material file.
This feature is valuable for debugging, as it allows developers to
inspect the exact compilation settings used for a given material.

A key consideration is the potential for personally identifiable
information (PII) in the command-line arguments (e.g., file paths). To
address this, a `toPIISafeString` method has been implemented to filter
out PII-sensitive options before they are stored in the material.

With this change, the matc command below
    /path/to/matc -a opengl --api vulkan -p desktop -g -o /path/to/my.filamat /path/to/my.mat

is stored to the package as below. (veryfied by running `matinfo my.filamat`)
    Compilation Parameters:         -a opengl --api vulkan -p desktop -g
2025-08-17 19:25:37 +00:00
Mathias Agopian
39268a6ad0 correctly handle UTF8 strings in materials 2025-08-16 22:50:57 -07:00
Konrad Piascik
01f7744025 Remove WebGPU warning since most features now work 2025-08-15 22:15:47 -04:00
Juan Caldas
6b2804985b Modify Load Image (#9068) 2025-08-15 15:29:21 +00:00
Juan Caldas
320ee183c6 webgpu: Gemini provided/inspired documentation
# Conflicts:
#	filament/backend/src/webgpu/WebGPUDriver.cpp
2025-08-15 10:10:04 -05:00
Juan Caldas
8145a5dd25 webgpu: Perform Blit when textures dont match (#9105)
BUGS = [435200998]
2025-08-15 14:41:13 +00:00
Powei Feng
ba0793ac18 Revert "[WIP] renderdiff: enable vulkan"
This reverts commit 2c154be0b3.
2025-08-14 16:19:46 -07:00
Powei Feng
2c154be0b3 [WIP] renderdiff: enable vulkan 2025-08-14 16:19:17 -07:00
174 changed files with 3688 additions and 1609 deletions

View File

@@ -0,0 +1,23 @@
name: 'Get and cache glTF Assets'
description: 'Downloads and caches glTF assets by calling the get-gltf-sample-assets.sh script.'
runs:
using: "composite"
steps:
- uses: ./.github/actions/dep-versions
- name: Hash models file
id: hash-models
shell: bash
run: echo "hash=$(cat test/renderdiff/tests/gltf_models.txt | md5sum | sed 's/ -//g')" >> $GITHUB_OUTPUT
- name: Cache glTF assets
id: cache-gltf
uses: actions/cache@v4
with:
path: gltf
key: gltf-assets-${{ env.GITHUB_GLTF_SAMPLE_ASSETS_COMMIT }}-${{ steps.hash-models.outputs.hash }}
- name: Download assets via script if cache not found
if: steps.cache-gltf.outputs.cache-hit != 'true'
shell: bash
run: |
echo "Cache miss for commit ${{ env.GITHUB_GLTF_SAMPLE_ASSETS_COMMIT }}. Running download script..."
xargs bash build/common/get-gltf-sample-assets.sh < test/renderdiff/tests/gltf_models.txt

View File

@@ -1,5 +1,6 @@
name: 'Get Mesa'
description: 'Caches and installs Mesa'
name: 'Get and cache Mesa'
description: 'Get and cache Mesa'
runs:
using: "composite"
steps:

View File

@@ -8,8 +8,8 @@ runs:
uses: actions/cache@v3
id: cache-vulkan-sdk
with:
path: vulkansdk
key: vulkansdk-${{ env.GITHUB_VULKANSDK_VERSION }}-${{ runner.os }}
path: ${{ runner.homedir }}/VulkanSDK
key: vulkansdk-${{ env.GITHUB_VULKANSDK_VERSION }}-2-${{ runner.os }}
- name: Download Vulkan SDK
if: steps.cache-vulkan-sdk.outputs.cache-hit != 'true'
run: |
@@ -21,4 +21,8 @@ runs:
run: |
source ${{ github.workspace }}/build/common/get-vulkan-sdk.sh
unpack_vulkan_installer
pushd .
cd ~/VulkanSDK/${GITHUB_VULKANSDK_VERSION}
sudo ./install_vulkan.py
popd
shell: bash

View File

@@ -19,5 +19,7 @@ runs:
- name: Install Mac Prerequisites
shell: bash
run: |
# Install brew prereqs
brew install coreutils
# Install ninja
source ./build/common/get-ninja.sh

View File

@@ -118,17 +118,22 @@ jobs:
- uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- uses: ./.github/actions/mac-prereq
- uses: ./.github/actions/get-gltf-assets
- uses: ./.github/actions/get-mesa
- uses: ./.github/actions/get-vulkan-sdk
- id: get_commit_msg
uses: ./.github/actions/get-commit-msg
- uses: ./.github/actions/mac-prereq
- uses: ./.github/actions/get-mesa
- name: Prerequisites
run: |
pip install tifffile numpy
# Must have at least clang-16 for a webgpu/dawn build.
sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer
shell: bash
- name: Render and compare
id: render_compare
run: |
ls ./gltf/Models
TEST_DIR=test/renderdiff
source ${TEST_DIR}/src/preamble.sh
start_

View File

@@ -544,7 +544,6 @@ if (FILAMENT_SUPPORTS_VULKAN)
endif()
if (FILAMENT_SUPPORTS_WEBGPU)
message(AUTHOR_WARNING "WebGPU is in development stage and broken at this stage. Its support is very limited.")
add_definitions(-DFILAMENT_SUPPORTS_WEBGPU)
endif()

View File

@@ -8,5 +8,5 @@ appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
## Release notes for next branch cut
- Update CMake minimum version to 3.22.1
- material: Add a material parameter to control shadow far attenuation (b/436680157)
- Rename `sampler` parameter `unfilterable` to `filterable` [⚠️ **New Material Version**]
- materials: new mutable specialization constants feature. See the [materials documentation](https://google.github.io/filament/Materials.html) for details. [⚠️ **New Material Version**]

View File

@@ -18,7 +18,7 @@ Filament release archives contains host-side tools that are required to generate
Make sure you always use tools from the same release as the runtime library. This is particularly
important for `matc` (material compiler).
If you'd rather build Filament yourself, please refer to our [build manual](BUILDING.md).
If you'd rather build Filament yourself, please refer to our [build manual](/BUILDING.md).
### Android
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.63.1'
implementation 'com.google.android.filament:filament-android:1.64.0'
}
```
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
iOS projects can use CocoaPods to install the latest release:
```shell
pod 'Filament', '~> 1.63.1'
pod 'Filament', '~> 1.64.0'
```
## Documentation
@@ -222,7 +222,7 @@ MaterialInstance* materialInstance = material->createInstance();
```
To learn more about materials and `matc`, please refer to the
[materials documentation](./docs/Materials.md.html).
[materials documentation](https://google.github.io/filament/Materials.html).
To render, simply pass the `View` to the `Renderer`:
@@ -240,7 +240,7 @@ in the `samples/` directory. These samples are all based on `libs/filamentapp/`
code that creates a native window with SDL2 and initializes the Filament engine, renderer and views.
For more information on how to prepare environment maps for image-based lighting please refer to
[BUILDING.md](https://github.com/google/filament/blob/main/BUILDING.md#running-the-native-samples).
[BUILDING.md](/BUILDING.md#running-the-native-samples).
### Android
@@ -272,7 +272,7 @@ To get started you can use the textures and environment maps found respectively
refer to their respective `URL.txt` files to know more about the original authors.
Environments must be pre-processed using
[`cmgen`](https://github.com/google/filament/blob/main/BUILDING.md#running-the-native-samples) or
[`cmgen`](/BUILDING.md#running-the-native-samples) or
using the `libiblprefilter` library.
## How to make contributions

View File

@@ -7,6 +7,11 @@ A new header is inserted each time a *tag* is created.
Instead, if you are authoring a PR for the main branch, add your release note to
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
## v1.64.1
- Update CMake minimum version to 3.22.1
- material: Add a material parameter to control shadow far attenuation (b/436680157)
## v1.64.0
- engine: add a `linearFog` material parameter. [⚠️ **New Material Version**]

View File

@@ -60,6 +60,14 @@ static void setParameter(JNIEnv* env, jlong nativeMaterialInstance, jstring name
env->ReleaseStringUTFChars(name_, name);
}
template<typename T>
static void setConstant(JNIEnv* env, jlong nativeMaterialInstance, jstring name_, T v) {
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
const char *name = env->GetStringUTFChars(name_, 0);
instance->setConstant(name, v);
env->ReleaseStringUTFChars(name_, name);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_MaterialInstance_nSetParameterBool(JNIEnv *env, jclass,
@@ -264,6 +272,13 @@ Java_com_google_android_filament_MaterialInstance_nSetParameterTexture(
env->ReleaseStringUTFChars(name_, name);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_MaterialInstance_nSetConstantBool(JNIEnv *env, jclass,
jlong nativeMaterialInstance, jstring name_, jboolean x) {
setConstant(env, nativeMaterialInstance, name_, bool(x));
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_MaterialInstance_nSetScissor(

View File

@@ -402,6 +402,16 @@ public class MaterialInstance {
nSetParameterFloat4(getNativeObject(), name, color[0], color[1], color[2], color[3]);
}
/**
* Sets the value of a bool constant.
*
* @param name the name of the material constant
* @param x the value of the material constant
*/
public void setConstant(@NonNull String name, boolean x) {
nSetConstantBool(getNativeObject(), name, x);
}
/**
* Set-up a custom scissor rectangle; by default it is disabled.
*
@@ -921,6 +931,9 @@ public class MaterialInstance {
@NonNull String name, int element, @NonNull @Size(min = 1) float[] v,
@IntRange(from = 0) int offset, @IntRange(from = 1) int count);
private static native void nSetConstantBool(long nativeMaterialInstance,
@NonNull String name, boolean x);
private static native void nSetParameterTexture(long nativeMaterialInstance,
@NonNull String name, long nativeTexture, long sampler);

View File

@@ -1949,6 +1949,18 @@ public class View {
* Ground Truth-base Ambient Occlusion (GTAO) options
*/
public float gtaoThicknessHeuristic = 0.004f;
/**
* Ground Truth-base Ambient Occlusion (GTAO) options
*/
public boolean gtaoUseVisibilityBitmasks = false;
/**
* Ground Truth-base Ambient Occlusion (GTAO) options
*/
public float gtaoConstThickness = 0.5f;
/**
* Ground Truth-base Ambient Occlusion (GTAO) options
*/
public boolean gtaoLinearThickness = false;
}

View File

@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.63.1
VERSION_NAME=1.64.0
POM_DESCRIPTION=Real-time physically based rendering engine for Android.

View File

@@ -0,0 +1,60 @@
# Copyright (C) 2025 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#!/usr/bin/bash
set -e
GLTF_SAMPLE_ASSETS_COMMIT=${GITHUB_GLTF_SAMPLE_ASSETS_COMMIT:-d441dfdb87413ff412c620849a649d61789a470f}
COMMIT_HASH="${GLTF_SAMPLE_ASSETS_COMMIT}"
REPO_URL="https://github.com/KhronosGroup/glTF-Sample-Assets.git"
TARGET_DIR="gltf"
# The default directories to check out if none are specified
DEFAULT_SPARSE_PATHS=(
"Models/Box/"
"Models/Triangle/"
"Models/AnimatedCube/"
)
# Check if command-line arguments are provided
if [ "$#" -gt 0 ]; then
# If arguments are provided, use them as the paths
SPARSE_PATHS=()
for model_name in "$@"; do
SPARSE_PATHS+=("Models/${model_name}/")
done
echo "Downloading specified models: $@"
else
# Otherwise, use the default list
SPARSE_PATHS=("${DEFAULT_SPARSE_PATHS[@]}")
echo "No models specified, downloading default set."
fi
echo "Removing old directory..."
rm -rf "${TARGET_DIR}"
# Clone the repository using a "treeless" clone, which is highly efficient.
# --filter=tree:0: Clones only the repository structure without file content (no historical directory listings), making the initial clone very small.
# --no-checkout: Prevents automatically checking out the main branch. We will check out a specific commit later.
# --sparse: Initializes the repository for sparse checkout, allowing us to fetch only specific directories.
git clone --filter=tree:0 --no-checkout --sparse "${REPO_URL}" "${TARGET_DIR}"
cd "${TARGET_DIR}"
git sparse-checkout set "${SPARSE_PATHS[@]}"
echo "Checking out commit ${COMMIT_HASH}..."
git checkout "${COMMIT_HASH}"
echo "Successfully checked out the specified models into the '${TARGET_DIR}' directory."

View File

@@ -137,6 +137,9 @@ if [[ "$OS_NAME" == "Darwin" ]]; then
# This is necessary to be able to build vk (lavapipe) on macOS. Doesn't seem like a real dependency.
sed -I '' "s/error('Vulkan drivers require dri3 for X11 support')//g" meson.build
# This is to properly link lib-xcb-present on the mac build (though we won't be drawing to any
# real hardware surface).
sed -I '' "s/dep_xcb_present = null_dep/dep_xcb_present = dependency('xcb-present')/g" meson.build
fi
# -Dosmesa=true => builds OSMesa, which is an offscreen GL context

View File

@@ -109,13 +109,8 @@ function install_mac() {
unzip -t vulkan_sdk.zip
fi
echo "recognized zip layout 'vulkan_sdk.zip' ${InstallVulkan}.app/Contents" >&2
local sdk_temp=${VULKAN_SDK_DIR}.tmp
sudo ${InstallVulkan}.app/Contents/MacOS/${InstallVulkan} --root "$sdk_temp" --accept-licenses --default-answer --confirm-command install
du -hs $sdk_temp
test -d $sdk_temp/macOS || { echo "unrecognized dmg folder layout: $sdk_temp" ; ls -l $sdk_temp ; }
cp -r $sdk_temp/macOS/* $VULKAN_SDK_DIR/
${InstallVulkan}.app/Contents/MacOS/${InstallVulkan} --accept-licenses --default-answer --confirm-command install
if [[ -d ${InstallVulkan}.app/Contents ]] ; then
sudo rm -rf "$sdk_temp"
rm -rf ${InstallVulkan}.app
fi
}

View File

@@ -5,4 +5,5 @@ GITHUB_MESA_VERSION=24.2.1
GITHUB_LLVM_VERSION=16
GITHUB_NDK_VERSION=27.0.11718014
GITHUB_EMSDK_VERSION=3.1.60
GITHUB_VULKANSDK_VERSION=1.4.321.0
GITHUB_VULKANSDK_VERSION=1.4.321.0
GITHUB_GLTF_SAMPLE_ASSETS_COMMIT=d441dfdb87413ff412c620849a649d61789a470f

View File

@@ -221,6 +221,8 @@ export is an html with the output captured in a <code>&lt;pre&gt;</code> tag).</
<li>Replace css styling in the exported output as needed (so they don't interfere with the book's css.</li>
<li>Replace resource urls to refer to locations relative to the mdbook structure.</li>
</ul>
<p>Any <code>markdeep</code> doc can be placed in <code>docs_src/src_markdeep/</code> and they will be parsed to html and included
in the book as above.</p>
<h3 id="readmes"><a class="header" href="#readmes">READMEs</a></h3>
<p>Filament depends on a number of libraries, which reside in the directory <code>libs</code>. These individual
libaries often have README.md in their root to describe itself. We collect these descriptions into our
@@ -229,7 +231,7 @@ located in <code>tools</code>. Some of tools also have README.md as description.
<p>The process for copying and processing these READMEs is outlined in <a href="#introductory-doc">Introductory docs</a>.</p>
<h3 id="other-technical-notes"><a class="header" href="#other-technical-notes">Other technical notes</a></h3>
<p>These are technical documents that do not fit into a library, tool, or directory of the
Filament source tree. We collect them into the <code>docs_src/src/notes</code> directory. No additional
Filament source tree. We collect them into the <code>docs_src/src_mdbook/src/notes</code> directory. No additional
processing is needed for these documents.</p>
<h3 id="raw-source-files"><a class="header" href="#raw-source-files">Raw source files</a></h3>
<p>These are files that are not part of the <code>mdbook</code> generation, but should be included output in <code>/docs</code>
@@ -242,7 +244,7 @@ add a link in <code>SUMMARY.md</code>, and perform the steps outlined in
<a href="#how-to-create">how-to create section</a>.</p>
<p>For example, if you are adding a general technical note, then you would</p>
<ul>
<li>Place the document (file with extension <code>.md</code>) in <code>docs_src/src/notes</code></li>
<li>Place the document (file with extension <code>.md</code>) in <code>docs_src/src_mdbook/src/notes</code></li>
<li>Add a link in <a href="https://github.com/google/filament/blob/main/docs_src/src_mdbook/src/SUMMARY.md"><code>docs_src/src_mdbook/src/SUMMARY.md</code></a></li>
<li>Run the commands in the <a href="#how-to-generate">Generate</a> section</li>
</ul>

View File

@@ -181,7 +181,7 @@ important for <code>matc</code> (material compiler).</p>
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.63.1'
implementation 'com.google.android.filament:filament-android:1.64.0'
}
</code></pre>
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
@@ -196,18 +196,18 @@ dependencies {
</div>
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
<p>iOS projects can use CocoaPods to install the latest release:</p>
<pre><code class="language-shell">pod 'Filament', '~&gt; 1.63.1'
<pre><code class="language-shell">pod 'Filament', '~&gt; 1.64.0'
</code></pre>
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
<ul>
<li><a href="https://google.github.io/filament/Filament.html">Filament</a>, an in-depth explanation of
<li><a href="../main/filament.html">Filament</a>, an in-depth explanation of
real-time physically based rendering, the graphics capabilities and implementation of Filament.
This document explains the math and reasoning behind most of our decisions. This document is a
good introduction to PBR for graphics programmers.</li>
<li><a href="https://google.github.io/filament/Materials.html">Materials</a>, the full reference
<li><a href="../main/materials.html">Materials</a>, the full reference
documentation for our material system. This document explains our different material models, how
to use the material compiler <code>matc</code> and how to write custom materials.</li>
<li><a href="https://google.github.io/filament/notes/material_properties.html">Material Properties</a>, a reference
<li><a href="../notes/material_properties.html">Material Properties</a>, a reference
sheet for the standard material model.</li>
</ul>
<h2 id="examples"><a class="header" href="#examples">Examples</a></h2>
@@ -395,7 +395,7 @@ by <code>matc</code>:</p>
MaterialInstance* materialInstance = material-&gt;createInstance();
</code></pre>
<p>To learn more about materials and <code>matc</code>, please refer to the
<a href="./docs/Materials.html">materials documentation</a>.</p>
<a href="../main/materials.html">materials documentation</a>.</p>
<p>To render, simply pass the <code>View</code> to the <code>Renderer</code>:</p>
<pre><code class="language-c++">// beginFrame() returns false if we need to skip a frame
if (renderer-&gt;beginFrame(swapChain)) {
@@ -408,7 +408,7 @@ if (renderer-&gt;beginFrame(swapChain)) {
in the <code>samples/</code> directory. These samples are all based on <code>libs/filamentapp/</code> which contains the
code that creates a native window with SDL2 and initializes the Filament engine, renderer and views.</p>
<p>For more information on how to prepare environment maps for image-based lighting please refer to
<a href="https://github.com/google/filament/blob/main/BUILDING.md#running-the-native-samples">BUILDING.md</a>.</p>
<a href="building.html#running-the-native-samples">BUILDING.md</a>.</p>
<h3 id="android-1"><a class="header" href="#android-1">Android</a></h3>
<p>See <code>android/samples</code> for examples of how to use Filament on Android.</p>
<p>You must always first initialize Filament by calling <code>Filament.init()</code>.</p>
@@ -430,7 +430,7 @@ OpenGL ES.</p>
<code>third_party/textures</code> and <code>third_party/environments</code>. These assets are under CC0 license. Please
refer to their respective <code>URL.txt</code> files to know more about the original authors.</p>
<p>Environments must be pre-processed using
<a href="https://github.com/google/filament/blob/main/BUILDING.md#running-the-native-samples"><code>cmgen</code></a> or
<a href="building.html#running-the-native-samples"><code>cmgen</code></a> or
using the <code>libiblprefilter</code> library.</p>
<h2 id="how-to-make-contributions"><a class="header" href="#how-to-make-contributions">How to make contributions</a></h2>
<p>Please read and follow the steps in <a href="contributing.html">CONTRIBUTING.md</a>. Make sure you are
@@ -513,7 +513,7 @@ and tools.</p>
<li><code>web</code>: JavaScript bindings, documentation, and samples</li>
</ul>
<h2 id="license"><a class="header" href="#license">License</a></h2>
<p>Please see <a href="/LICENSE">LICENSE</a>.</p>
<p>Please see <a href="https://github.com/google/filament/blob/main/LICENSE">LICENSE</a>.</p>
<h2 id="disclaimer"><a class="header" href="#disclaimer">Disclaimer</a></h2>
<p>This is not an officially supported Google product.</p>

View File

@@ -1,564 +1,11 @@
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Introduction - Filament</title>
<!-- Custom HTML head -->
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="shortcut icon" href="favicon.png">
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/general.css">
<link rel="stylesheet" href="css/chrome.css">
<!-- Fonts -->
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="highlight.css">
<link rel="stylesheet" href="tomorrow-night.css">
<link rel="stylesheet" href="ayu-highlight.css">
<!-- Custom theme stylesheets -->
<!-- MathJax -->
<script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<!-- Provide site root to javascript -->
<script>
var path_to_root = "";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
</script>
<!-- Start loading toc.js asap -->
<script src="toc.js"></script>
</head>
<body>
<div id="body-container">
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
const html = document.documentElement;
html.classList.remove('light')
html.classList.add(theme);
html.classList.add("js");
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
var sidebar = null;
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div style="display:flex;align-items:center;justify-content:center">
<img class="flogo" src="images/filament_logo_small.png"></img>
</div>
<!-- populated by js -->
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
<noscript>
<iframe class="sidebar-iframe-outer" src="toc.html"></iframe>
</noscript>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<!-- Filament: disable themes because the markdeep part does not look good for dark themes -->
<!--
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
-->
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Filament</h1>
<div class="right-buttons">
<a href="https://github.com/google/filament" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="filament"><a class="header" href="#filament">Filament</a></h1>
<p><a href="https://github.com/google/filament/actions?query=workflow%3AAndroid"><img src="https://github.com/google/filament/workflows/Android/badge.svg" alt="Android Build Status" /></a>
<a href="https://github.com/google/filament/actions?query=workflow%3AiOS"><img src="https://github.com/google/filament/workflows/iOS/badge.svg" alt="iOS Build Status" /></a>
<a href="https://github.com/google/filament/actions?query=workflow%3ALinux"><img src="https://github.com/google/filament/workflows/Linux/badge.svg" alt="Linux Build Status" /></a>
<a href="https://github.com/google/filament/actions?query=workflow%3AmacOS"><img src="https://github.com/google/filament/workflows/macOS/badge.svg" alt="macOS Build Status" /></a>
<a href="https://github.com/google/filament/actions?query=workflow%3AWindows"><img src="https://github.com/google/filament/workflows/Windows/badge.svg" alt="Windows Build Status" /></a>
<a href="https://github.com/google/filament/actions?query=workflow%3AWeb"><img src="https://github.com/google/filament/workflows/Web/badge.svg" alt="Web Build Status" /></a></p>
<p>Filament is a real-time physically based rendering engine for Android, iOS, Linux, macOS, Windows,
and WebGL. It is designed to be as small as possible and as efficient as possible on Android.</p>
<h2 id="download"><a class="header" href="#download">Download</a></h2>
<p><a href="https://github.com/google/filament/releases">Download Filament releases</a> to access stable builds.
Filament release archives contains host-side tools that are required to generate assets.</p>
<p>Make sure you always use tools from the same release as the runtime library. This is particularly
important for <code>matc</code> (material compiler).</p>
<p>If you'd rather build Filament yourself, please refer to our <a href="building.html">build manual</a>.</p>
<h3 id="android"><a class="header" href="#android">Android</a></h3>
<p>Android projects can simply declare Filament libraries as Maven dependencies:</p>
<pre><code class="language-gradle">repositories {
// ...
mavenCentral()
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.63.1'
}
</code></pre>
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
<div class="table-wrapper"><table><thead><tr><th>Artifact</th><th>Description</th></tr></thead><tbody>
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-android"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-android/badge.svg?subject=filament-android" alt="filament-android" /></a></td><td>The Filament rendering engine itself.</td></tr>
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-android-debug"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-android-debug/badge.svg?subject=filament-android-debug" alt="filament-android-debug" /></a></td><td>Debug version of <code>filament-android</code>.</td></tr>
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/gltfio-android"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/gltfio-android/badge.svg?subject=gltfio-android" alt="gltfio-android" /></a></td><td>A glTF 2.0 loader for Filament, depends on <code>filament-android</code>.</td></tr>
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-utils-android"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-utils-android/badge.svg?subject=filament-utils-android" alt="filament-utils-android" /></a></td><td>KTX loading, Kotlin math, and camera utilities, depends on <code>gltfio-android</code>.</td></tr>
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filamat-android"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filamat-android/badge.svg?subject=filamat-android" alt="filamat-android" /></a></td><td>A runtime material builder/compiler. This library is large but contains a full shader compiler/validator/optimizer and supports both OpenGL and Vulkan.</td></tr>
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filamat-android-lite"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filamat-android-lite/badge.svg?subject=filamat-android-lite" alt="filamat-android-lite" /></a></td><td>A much smaller alternative to <code>filamat-android</code> that can only generate OpenGL shaders. It does not provide validation or optimizations.</td></tr>
</tbody></table>
</div>
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
<p>iOS projects can use CocoaPods to install the latest release:</p>
<pre><code class="language-shell">pod 'Filament', '~&gt; 1.63.1'
</code></pre>
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
<ul>
<li><a href="https://google.github.io/filament/Filament.html">Filament</a>, an in-depth explanation of
real-time physically based rendering, the graphics capabilities and implementation of Filament.
This document explains the math and reasoning behind most of our decisions. This document is a
good introduction to PBR for graphics programmers.</li>
<li><a href="https://google.github.io/filament/Materials.html">Materials</a>, the full reference
documentation for our material system. This document explains our different material models, how
to use the material compiler <code>matc</code> and how to write custom materials.</li>
<li><a href="https://google.github.io/filament/notes/material_properties.html">Material Properties</a>, a reference
sheet for the standard material model.</li>
</ul>
<h2 id="examples"><a class="header" href="#examples">Examples</a></h2>
<p><img src="../images/samples/example_bistro1.jpg" alt="Night scene" />
<img src="../images/samples/example_bistro2.jpg" alt="Night scene" />
<img src="../images/samples/example_materials1.jpg" alt="Materials" />
<img src="../images/samples/example_materials2.jpg" alt="Materials" />
<img src="../images/samples/example_helmet.jpg" alt="Helmet" />
<img src="../images/samples/example_ssr.jpg" alt="Screen-space refraction" /></p>
<h2 id="features"><a class="header" href="#features">Features</a></h2>
<h3 id="apis"><a class="header" href="#apis">APIs</a></h3>
<ul>
<li>Native C++ API for Android, iOS, Linux, macOS and Windows</li>
<li>Java/JNI API for Android</li>
<li>JavaScript API</li>
</ul>
<h3 id="backends"><a class="header" href="#backends">Backends</a></h3>
<ul>
<li>OpenGL 4.1+ for Linux, macOS and Windows</li>
<li>OpenGL ES 3.0+ for Android and iOS</li>
<li>Metal for macOS and iOS</li>
<li>Vulkan 1.0 for Android, Linux, macOS, and Windows</li>
<li>WebGL 2.0 for all platforms</li>
</ul>
<h3 id="rendering"><a class="header" href="#rendering">Rendering</a></h3>
<ul>
<li>Clustered forward renderer</li>
<li>Cook-Torrance microfacet specular BRDF</li>
<li>Lambertian diffuse BRDF</li>
<li>Custom lighting/surface shading</li>
<li>HDR/linear lighting</li>
<li>Metallic workflow</li>
<li>Clear coat</li>
<li>Anisotropic lighting</li>
<li>Approximated translucent (subsurface) materials</li>
<li>Cloth/fabric/sheen shading</li>
<li>Normal mapping &amp; ambient occlusion mapping</li>
<li>Image-based lighting</li>
<li>Physically-based camera (shutter speed, sensitivity and aperture)</li>
<li>Physical light units</li>
<li>Point lights, spot lights, and directional light</li>
<li>Specular anti-aliasing</li>
<li>Point, spot, and directional light shadows</li>
<li>Cascaded shadows</li>
<li>EVSM, PCSS, DPCF, or PCF shadows</li>
<li>Transparent shadows</li>
<li>Contact shadows</li>
<li>Screen-space ambient occlusion</li>
<li>Screen-space reflections</li>
<li>Screen-space refraction</li>
<li>Global fog</li>
<li>Dynamic resolution (with support for AMD FidelityFX FSR)</li>
</ul>
<h3 id="post-processing"><a class="header" href="#post-processing">Post processing</a></h3>
<ul>
<li>HDR bloom</li>
<li>Depth of field bokeh</li>
<li>Multiple tone mappers: generic (customizable), ACES, filmic, etc.</li>
<li>Color and tone management: luminance scaling, gamut mapping</li>
<li>Color grading: exposure, night adaptation, white balance, channel mixer,
shadows/mid-tones/highlights, ASC CDL, contrast, saturation, etc.</li>
<li>TAA, FXAA, MSAA</li>
<li>Screen-space lens flares</li>
</ul>
<h3 id="gltf-20"><a class="header" href="#gltf-20">glTF 2.0</a></h3>
<ul>
<li>
<p>Encodings</p>
<ul>
<li><input disabled="" type="checkbox" checked=""/>
Embeded</li>
<li><input disabled="" type="checkbox" checked=""/>
Binary</li>
</ul>
</li>
<li>
<p>Primitive Types</p>
<ul>
<li><input disabled="" type="checkbox" checked=""/>
Points</li>
<li><input disabled="" type="checkbox" checked=""/>
Lines</li>
<li><input disabled="" type="checkbox"/>
Line Loop</li>
<li><input disabled="" type="checkbox" checked=""/>
Line Strip</li>
<li><input disabled="" type="checkbox" checked=""/>
Triangles</li>
<li><input disabled="" type="checkbox" checked=""/>
Triangle Strip</li>
<li><input disabled="" type="checkbox"/>
Triangle Fan</li>
</ul>
</li>
<li>
<p>Animation</p>
<ul>
<li><input disabled="" type="checkbox" checked=""/>
Transform animation</li>
<li><input disabled="" type="checkbox" checked=""/>
Linear interpolation</li>
<li><input disabled="" type="checkbox" checked=""/>
Morph animation
<ul>
<li><input disabled="" type="checkbox" checked=""/>
Sparse accessor</li>
</ul>
</li>
<li><input disabled="" type="checkbox" checked=""/>
Skin animation</li>
<li><input disabled="" type="checkbox" checked=""/>
Joint animation</li>
</ul>
</li>
<li>
<p>Extensions</p>
<ul>
<li><input disabled="" type="checkbox" checked=""/>
KHR_draco_mesh_compression</li>
<li><input disabled="" type="checkbox" checked=""/>
KHR_lights_punctual</li>
<li><input disabled="" type="checkbox" checked=""/>
KHR_materials_clearcoat</li>
<li><input disabled="" type="checkbox" checked=""/>
KHR_materials_emissive_strength</li>
<li><input disabled="" type="checkbox" checked=""/>
KHR_materials_ior</li>
<li><input disabled="" type="checkbox" checked=""/>
KHR_materials_pbrSpecularGlossiness</li>
<li><input disabled="" type="checkbox" checked=""/>
KHR_materials_sheen</li>
<li><input disabled="" type="checkbox" checked=""/>
KHR_materials_transmission</li>
<li><input disabled="" type="checkbox" checked=""/>
KHR_materials_unlit</li>
<li><input disabled="" type="checkbox" checked=""/>
KHR_materials_variants</li>
<li><input disabled="" type="checkbox" checked=""/>
KHR_materials_volume</li>
<li><input disabled="" type="checkbox" checked=""/>
KHR_materials_specular</li>
<li><input disabled="" type="checkbox" checked=""/>
KHR_mesh_quantization</li>
<li><input disabled="" type="checkbox" checked=""/>
KHR_texture_basisu</li>
<li><input disabled="" type="checkbox" checked=""/>
KHR_texture_transform</li>
<li><input disabled="" type="checkbox" checked=""/>
EXT_meshopt_compression</li>
</ul>
</li>
</ul>
<h2 id="rendering-with-filament"><a class="header" href="#rendering-with-filament">Rendering with Filament</a></h2>
<h3 id="native-linux-macos-and-windows"><a class="header" href="#native-linux-macos-and-windows">Native Linux, macOS and Windows</a></h3>
<p>You must create an <code>Engine</code>, a <code>Renderer</code> and a <code>SwapChain</code>. The <code>SwapChain</code> is created from a
native window pointer (an <code>NSView</code> on macOS or a <code>HWND</code> on Windows for instance):</p>
<pre><code class="language-c++">Engine* engine = Engine::create();
SwapChain* swapChain = engine-&gt;createSwapChain(nativeWindow);
Renderer* renderer = engine-&gt;createRenderer();
</code></pre>
<p>To render a frame you must then create a <code>View</code>, a <code>Scene</code> and a <code>Camera</code>:</p>
<pre><code class="language-c++">Camera* camera = engine-&gt;createCamera(EntityManager::get().create());
View* view = engine-&gt;createView();
Scene* scene = engine-&gt;createScene();
view-&gt;setCamera(camera);
view-&gt;setScene(scene);
</code></pre>
<p>Renderables are added to the scene:</p>
<pre><code class="language-c++">Entity renderable = EntityManager::get().create();
// build a quad
RenderableManager::Builder(1)
.boundingBox({{ -1, -1, -1 }, { 1, 1, 1 }})
.material(0, materialInstance)
.geometry(0, RenderableManager::PrimitiveType::TRIANGLES, vertexBuffer, indexBuffer, 0, 6)
.culling(false)
.build(*engine, renderable);
scene-&gt;addEntity(renderable);
</code></pre>
<p>The material instance is obtained from a material, itself loaded from a binary blob generated
by <code>matc</code>:</p>
<pre><code class="language-c++">Material* material = Material::Builder()
.package((void*) BAKED_MATERIAL_PACKAGE, sizeof(BAKED_MATERIAL_PACKAGE))
.build(*engine);
MaterialInstance* materialInstance = material-&gt;createInstance();
</code></pre>
<p>To learn more about materials and <code>matc</code>, please refer to the
<a href="./docs/Materials.html">materials documentation</a>.</p>
<p>To render, simply pass the <code>View</code> to the <code>Renderer</code>:</p>
<pre><code class="language-c++">// beginFrame() returns false if we need to skip a frame
if (renderer-&gt;beginFrame(swapChain)) {
// for each View
renderer-&gt;render(view);
renderer-&gt;endFrame();
}
</code></pre>
<p>For complete examples of Linux, macOS and Windows Filament applications, look at the source files
in the <code>samples/</code> directory. These samples are all based on <code>libs/filamentapp/</code> which contains the
code that creates a native window with SDL2 and initializes the Filament engine, renderer and views.</p>
<p>For more information on how to prepare environment maps for image-based lighting please refer to
<a href="https://github.com/google/filament/blob/main/BUILDING.md#running-the-native-samples">BUILDING.md</a>.</p>
<h3 id="android-1"><a class="header" href="#android-1">Android</a></h3>
<p>See <code>android/samples</code> for examples of how to use Filament on Android.</p>
<p>You must always first initialize Filament by calling <code>Filament.init()</code>.</p>
<p>Rendering with Filament on Android is similar to rendering from native code (the APIs are largely
the same across languages). You can render into a <code>Surface</code> by passing a <code>Surface</code> to the
<code>createSwapChain</code> method. This allows you to render to a <code>SurfaceTexture</code>, a <code>TextureView</code> or
a <code>SurfaceView</code>. To make things easier we provide an Android specific API called <code>UiHelper</code> in the
package <code>com.google.android.filament.android</code>. All you need to do is set a render callback on the
helper and attach your <code>SurfaceView</code> or <code>TextureView</code> to it. You are still responsible for
creating the swap chain in the <code>onNativeWindowChanged()</code> callback.</p>
<h3 id="ios-1"><a class="header" href="#ios-1">iOS</a></h3>
<p>Filament is supported on iOS 11.0 and above. See <code>ios/samples</code> for examples of using Filament on
iOS.</p>
<p>Filament on iOS is largely the same as native rendering with C++. A <code>CAEAGLLayer</code> or <code>CAMetalLayer</code>
is passed to the <code>createSwapChain</code> method. Filament for iOS supports both Metal (preferred) and
OpenGL ES.</p>
<h2 id="assets"><a class="header" href="#assets">Assets</a></h2>
<p>To get started you can use the textures and environment maps found respectively in
<code>third_party/textures</code> and <code>third_party/environments</code>. These assets are under CC0 license. Please
refer to their respective <code>URL.txt</code> files to know more about the original authors.</p>
<p>Environments must be pre-processed using
<a href="https://github.com/google/filament/blob/main/BUILDING.md#running-the-native-samples"><code>cmgen</code></a> or
using the <code>libiblprefilter</code> library.</p>
<h2 id="how-to-make-contributions"><a class="header" href="#how-to-make-contributions">How to make contributions</a></h2>
<p>Please read and follow the steps in <a href="contributing.html">CONTRIBUTING.md</a>. Make sure you are
familiar with the <a href="code_style.html">code style</a>.</p>
<h2 id="directory-structure"><a class="header" href="#directory-structure">Directory structure</a></h2>
<p>This repository not only contains the core Filament engine, but also its supporting libraries
and tools.</p>
<ul>
<li><code>android</code>: Android libraries and projects
<ul>
<li><code>filamat-android</code>: Filament material generation library (AAR) for Android</li>
<li><code>filament-android</code>: Filament library (AAR) for Android</li>
<li><code>filament-utils-android</code>: Extra utilities (KTX loader, math types, etc.)</li>
<li><code>gltfio-android</code>: Filament glTF loading library (AAR) for Android</li>
<li><code>samples</code>: Android-specific Filament samples</li>
</ul>
</li>
<li><code>art</code>: Source for various artworks (logos, PDF manuals, etc.)</li>
<li><code>assets</code>: 3D assets to use with sample applications</li>
<li><code>build</code>: CMake build scripts</li>
<li><code>docs</code>: Documentation
<ul>
<li><code>math</code>: Mathematica notebooks used to explore BRDFs, equations, etc.</li>
</ul>
</li>
<li><code>filament</code>: Filament rendering engine (minimal dependencies)
<ul>
<li><code>backend</code>: Rendering backends/drivers (Vulkan, Metal, OpenGL/ES)</li>
</ul>
</li>
<li><code>ide</code>: Configuration files for IDEs (CLion, etc.)</li>
<li><code>ios</code>: Sample projects for iOS</li>
<li><code>libs</code>: Libraries
<ul>
<li><code>bluegl</code>: OpenGL bindings for macOS, Linux and Windows</li>
<li><code>bluevk</code>: Vulkan bindings for macOS, Linux, Windows and Android</li>
<li><code>camutils</code>: Camera manipulation utilities</li>
<li><code>filabridge</code>: Library shared by the Filament engine and host tools</li>
<li><code>filaflat</code>: Serialization/deserialization library used for materials</li>
<li><code>filagui</code>: Helper library for <a href="https://github.com/ocornut/imgui">Dear ImGui</a></li>
<li><code>filamat</code>: Material generation library</li>
<li><code>filamentapp</code>: SDL2 skeleton to build sample apps</li>
<li><code>filameshio</code>: Tiny filamesh parsing library (see also <code>tools/filamesh</code>)</li>
<li><code>geometry</code>: Mesh-related utilities</li>
<li><code>gltfio</code>: Loader for glTF 2.0</li>
<li><code>ibl</code>: IBL generation tools</li>
<li><code>image</code>: Image filtering and simple transforms</li>
<li><code>imageio</code>: Image file reading / writing, only intended for internal use</li>
<li><code>matdbg</code>: DebugServer for inspecting shaders at run-time (debug builds only)</li>
<li><code>math</code>: Math library</li>
<li><code>mathio</code>: Math types support for output streams</li>
<li><code>utils</code>: Utility library (threads, memory, data structures, etc.)</li>
<li><code>viewer</code>: glTF viewer library (requires gltfio)</li>
</ul>
</li>
<li><code>samples</code>: Sample desktop applications</li>
<li><code>shaders</code>: Shaders used by <code>filamat</code> and <code>matc</code></li>
<li><code>third_party</code>: External libraries and assets
<ul>
<li><code>environments</code>: Environment maps under CC0 license that can be used with <code>cmgen</code></li>
<li><code>models</code>: Models under permissive licenses</li>
<li><code>textures</code>: Textures under CC0 license</li>
</ul>
</li>
<li><code>tools</code>: Host tools
<ul>
<li><code>cmgen</code>: Image-based lighting asset generator</li>
<li><code>filamesh</code>: Mesh converter</li>
<li><code>glslminifier</code>: Minifies GLSL source code</li>
<li><code>matc</code>: Material compiler</li>
<li><code>filament-matp</code>: Material parser</li>
<li><code>matinfo</code> Displays information about materials compiled with <code>matc</code></li>
<li><code>mipgen</code> Generates a series of miplevels from a source image</li>
<li><code>normal-blending</code>: Tool to blend normal maps</li>
<li><code>resgen</code> Aggregates binary blobs into embeddable resources</li>
<li><code>roughness-prefilter</code>: Pre-filters a roughness map from a normal map to reduce aliasing</li>
<li><code>specular-color</code>: Computes the specular color of conductors based on spectral data</li>
</ul>
</li>
<li><code>web</code>: JavaScript bindings, documentation, and samples</li>
</ul>
<h2 id="license"><a class="header" href="#license">License</a></h2>
<p>Please see <a href="/LICENSE">LICENSE</a>.</p>
<h2 id="disclaimer"><a class="header" href="#disclaimer">Disclaimer</a></h2>
<p>This is not an officially supported Google product.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="next prefetch" href="dup/building.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="next prefetch" href="dup/building.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script>
window.playground_copyable = true;
</script>
<script src="elasticlunr.min.js"></script>
<script src="mark.min.js"></script>
<script src="searcher.js"></script>
<script src="clipboard.min.js"></script>
<script src="highlight.js"></script>
<script src="book.js"></script>
<!-- Custom JS scripts -->
</div>
</body>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Redirecting...</title>
<meta http-equiv="refresh" content="0; url=./dup/intro.html">
</head>
<body>
</body>
</html>

View File

@@ -160,8 +160,8 @@
<main>
<h1 id="core-concepts"><a class="header" href="#core-concepts">Core Concepts</a></h1>
<ul>
<li><a href="main/filament.html">Filament</a> - High-level designs; Filament's PBR/math assumptions; implementation details.</li>
<li><a href="main/materials.html">Materials</a> - A guide to Filament's material definition.</li>
<li><a href="filament.html">Filament</a> - High-level designs; Filament's PBR/math assumptions; implementation details.</li>
<li><a href="materials.html">Materials</a> - A guide to Filament's material definition.</li>
</ul>
</main>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -65,6 +65,9 @@ We describe step 1 in detail for the sake of record:
- Replace css styling in the exported output as needed (so they don't interfere with the book's css.
- Replace resource urls to refer to locations relative to the mdbook structure.
Any `markdeep` doc can be placed in `docs_src/src_markdeep/` and they will be parsed to html and included
in the book as above.
### READMEs
Filament depends on a number of libraries, which reside in the directory `libs`. These individual
libaries often have README.md in their root to describe itself. We collect these descriptions into our
@@ -75,7 +78,7 @@ The process for copying and processing these READMEs is outlined in [Introductor
### Other technical notes
These are technical documents that do not fit into a library, tool, or directory of the
Filament source tree. We collect them into the `docs_src/src/notes` directory. No additional
Filament source tree. We collect them into the `docs_src/src_mdbook/src/notes` directory. No additional
processing is needed for these documents.
### Raw source files
@@ -90,7 +93,7 @@ add a link in `SUMMARY.md`, and perform the steps outlined in
[how-to create section](#how-to-create).
For example, if you are adding a general technical note, then you would
- Place the document (file with extension `.md`) in `docs_src/src/notes`
- Place the document (file with extension `.md`) in `docs_src/src_mdbook/src/notes`
- Add a link in [`docs_src/src_mdbook/src/SUMMARY.md`]
- Run the commands in the [Generate](#how-to-generate) section

View File

@@ -2,9 +2,14 @@
"README.md": {
"dest": "dup/intro.md",
"link_transforms": {
"BUILDING.md": "building.md",
"/BUILDING.md#running-the-native-samples": "building.md#running-the-native-samples",
"/BUILDING.md": "building.md",
"/CONTRIBUTING.md": "contributing.md",
"/CODE_STYLE.md": "code_style.md",
"/LICENSE": "https://github.com/google/filament/blob/main/LICENSE",
"https://google.github.io/filament/Filament.html": "../main/filament.md",
"https://google.github.io/filament/Materials.html": "../main/materials.md",
"https://google.github.io/filament/notes/material_properties.html": "../notes/material_properties.md",
"docs/images/samples": "../images/samples"
}
},

View File

@@ -212,7 +212,7 @@ when using textures.
This property can dramatically change the appearance of a surface. Non-metallic surfaces have
chromatic diffuse reflection and achromatic specular reflection (reflected light does not change
color). Metallic surfaces do not have any diffuse reflection and chromatic specular reflection
(reflected light takes on the color of the surfaced as defined by `baseColor`).
(reflected light takes on the color of the surfaced as defined by `baseColor`).
The effect of `metallic` is shown in figure [metallicProperty] (click on the image to see a
larger version).
@@ -248,7 +248,7 @@ The effect of `roughness` on metallic surfaces is shown in figure [roughnessCond
When refraction through an object is enabled (using a `refractonType` of `thin` or `solid`), the
`roughness` property will also affect the refractions, as shown in figure
[roughnessRefractionProperty] (click on the image to see a larger version).
![Figure [roughnessRefractionProperty]: Refractive sphere with `roughness` varying from 0.0
(left) to 1.0 (right)](images/materials/refraction_roughness.png)
@@ -307,7 +307,7 @@ The sheen color controls the color appearance and strength of an optional sheen
base layer described by the properties above. The sheen layer always sits below the clear coat layer
if such a layer is present.
The sheen layer can be used to represent cloth and fabric materials. Please refer to
The sheen layer can be used to represent cloth and fabric materials. Please refer to
section [Cloth model] for more information about cloth and fabric materials.
The effect of `sheenColor` is shown in figure [materialSheenColor]
@@ -520,14 +520,14 @@ light to bend further away from the initial path.
Table [commonMatIOR] describes acceptable refractive indices for various types of materials.
Material | IOR
Material | IOR
--------------------------:|:-----------------
Air | 1.0
Water | 1.33
Common liquids | 1.33 to 1.5
Common gemstones | 1.58 to 2.33
Plastics, glass | 1.5 to 1.58
Other dielectric materials | 1.33 to 1.58
Water | 1.33
Common liquids | 1.33 to 1.5
Common gemstones | 1.58 to 2.33
Plastics, glass | 1.5 to 1.58
Other dielectric materials | 1.33 to 1.58
[Table [commonMatIOR]: Index of refraction of common materials]
The appearance of a refractive material will greatly depend on the `refractionType` and
@@ -1096,30 +1096,37 @@ Value
`bool` or `number`, depending on the `type` of the constant. The type must be one of the types
described in table [materialConstantsTypes].
Type | Description | Default
:----------------------|:-----------------------------------------|:------------------
int | A signed, 32 bit GLSL int | 0
float | A single-precision GLSL float | 0.0
bool | A GLSL bool | false
Constants may also be specified as mutable by setting the `mutable` property to `true`. Only
`bool` spec constants may be specified as mutable.
| Type | Description | Default | May be mutable? |
|:------|:------------------------------|:--------|-----------------|
| int | A signed, 32 bit GLSL int | | no |
| float | A single-precision GLSL float | 0.0 | no |
| bool | A GLSL bool | false | yes |
[Table [materialConstantsTypes]: Material constants types]
Description
: Lists the constant parameters accepted by your material. These constants can be set, or
"specialized", at runtime when loading a material package. Multiple materials can be loaded from
the same material package with differing constant parameter specializations. Once a material is
loaded from a material package, its constant parameters cannot be changed. Compared to regular
parameters, constant parameters allow the compiler to generate more efficient code. Access
constant parameters from the shader by prefixing the name with `materialConstant_`. For example,
a constant parameter named `myConstant` is accessed in the shader as
`materialConstant_myConstant`. If a constant parameter is not set at runtime, the default is
used.
the same material package with differing constant parameter specializations. If a constant
parameter is specialized as mutable, it may be changed at any time on a per-instance basis via
`MaterialInstance::setConstant(name, value)`. Otherwise, once a material is loaded from a
material package, its constant parameters cannot be changed.
Compared to regular parameters, constant parameters allow the compiler to generate more
efficient code. Access constant parameters from the shader by prefixing the name with
`materialConstant_`. For example, a constant parameter named `myConstant` is accessed in the
shader as `materialConstant_myConstant`. If a constant parameter is not set at runtime, the
default is used.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
material {
constants : [
{
name : overrideAlpha,
type : bool
type : bool,
mutable : true,
},
{
name : customAlpha,
@@ -2639,7 +2646,7 @@ standard skybox material. It produces a list of 2 parameters, named `showSun` an
respectively a boolean and a cubemap texture.
```text
$ matc --reflect parameters filament/src/materials/skybox.mat
$ matc --reflect parameters filament/src/materials/skybox.mat
{
"parameters": [
{
@@ -2655,7 +2662,7 @@ $ matc --reflect parameters filament/src/materials/skybox.mat
}
]
}
```
```
### --variant-filter

View File

@@ -1,5 +1,5 @@
# Core Concepts
- [Filament](main/filament.md) - High-level designs; Filament's PBR/math assumptions; implementation details.
- [Materials](main/materials.md) - A guide to Filament's material definition.
- [Filament](filament.md) - High-level designs; Filament's PBR/math assumptions; implementation details.
- [Materials](materials.md) - A guide to Filament's material definition.

View File

@@ -0,0 +1,77 @@
# Performance Analysis
## Android
### Prerequisites
- Download and install Android GPU Inspector (AGI). See https://developer.android.com/agi.
### Android with the Vulkan Backend
---
#### Profiling
1. Build a release build of Filament with the applicable backend(s) enabled
_(+ any special flags for enabling sys strace. Nothing special is needed for Vulkan or WebGPU aside from
building a release build with no flags)_ _(debug builds for this are useless)_
```shell
# the following command assumes you are in the root filament directory
# and ANDROID_HOME is exported (and possibly also CC and CXX on linux as needed)
#
# NOTE: to build with WebGPU support you need to explicitly include the -W flag
# (it doesn't get compiled in by default), e.g.:
# ./build.sh -W -p android,desktop -i release
#
# Note that you can speed this up a bit (and reduce disk space usage) by limiting the target to just
# the ABI you plan on testing with the -q flag, e.g. -q arm64-v8a. If you do this, you
# will need to update the android/gradle.properties file to specify the ABI(s) you are targeting
# with the com.google.android.filament.abis property.
# Thus, a build command that would target BOTH Vulkan AND WebGPU AND only target the ARM64 ABI would look something
# like:
# ./build.sh -W -q arm64-v8a -p android,desktop -i release
./build.sh -p android,desktop -i release
```
1. Connect your Android device to your computer via a USB cord with USB debugging enabled and configure
the system property to default to the desired backend, e.g.
_(to determine how these numbers map to the backends, see the `enum class Backend`
definition in [filament/filament/backend/include/backend/DriverEnums.h](../../../../filament/backend/include/backend/DriverEnums.h))_:
```shell
# to set the backend to Vulkan:
adb shell setprop debug.filament.backend 2
# to set the backend to WebGPU:
adb shell setprop debug.filament.backend 4
# to view the current property:
adb shell getprop debug.filament.backend
```
1. Build and run a sample, e.g. sample-gltf-viewer, on your Android device
_([Android Studio](https://developer.android.com/studio) recommended)_.
1. Run [AGI](https://developer.android.com/agi) and follow instructions to profile the app/system
with trace capture(s). See https://developer.android.com/agi/start for more details to
get started.
- We typically only run "Capture System Profiler trace" _(not necessarily "Capture Frame Profiler trace")_
- When configuring the trace:
- For both the WebGPU and Vulkan backends configure the profiler for the Vulkan API
_(since WebGPU should be using Vulkan under the hood as well)_
- Running for ~1 seconds should suffice
- Hit the "Configure" button in Trace objects, select "Switch to advanced mode" and add:
```
data_sources {
config {
name: "track_event"
track_event_config {
disabled_categories: "*"
enabled_categories: "filament/filament"
enabled_categories: "filament/jobsystem"
enabled_categories: "filament/gltfio"
}
}
}
```
- One you open the trace, zoom into a series of frames to get a sense of generally how long they typically take
_(use `W`, `S`, `A` and `D` keys and mouse wheel for navigation)_ and find a representative one.
We are most interested in the performance of the
`FEngine::loop` thread, how long it takes, overlap in activities/processes/commands, reduction in queue submissions,
etc. Similarly, we can view GPU timeline as it relates to that. We want to see overlapping
shader invocations and non-interrupted fragment shader runs.

View File

@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Redirecting...</title>
<meta http-equiv="refresh" content="0; url=./dup/intro.html">
</head>
<body>
</body>
</html>

View File

@@ -258,6 +258,8 @@ if (FILAMENT_SUPPORTS_WEBGPU)
include/backend/platforms/WebGPUPlatform.h
src/webgpu/platform/WebGPUPlatform.cpp
src/webgpu/SpdMipmapGenerator/SpdMipmapGenerator.cpp
src/webgpu/utils/AsyncTaskCounter.cpp
src/webgpu/utils/AsyncTaskCounter.h
src/webgpu/utils/StringPlaceholderTemplateProcessor.cpp
src/webgpu/utils/StringPlaceholderTemplateProcessor.h
src/webgpu/WebGPUBlitter.cpp
@@ -412,6 +414,7 @@ endif()
if (APPLE AND NOT IOS)
target_link_libraries(${TARGET} PRIVATE "-framework Cocoa")
target_link_libraries(${TARGET} PRIVATE "-framework QuartzCore")
endif()
target_link_libraries(${TARGET} PUBLIC math)
@@ -527,7 +530,7 @@ target_link_libraries(${TARGET} PRIVATE
if (LINUX AND FILAMENT_SUPPORTS_WEBGPU)
# Unclear why this needs to be explicit while other linux libs do not, but it is necessary for
# WebGPU to build on linux.
target_link_libraries(${TARGET} PRIVATE -lxcb)
target_link_libraries(${TARGET} PRIVATE -lxcb -lX11)
endif()
# ==================================================================================================
@@ -576,6 +579,9 @@ if (APPLE OR LINUX)
test/test_CommandBufferQueue.cpp
test/test_Template.cpp
)
if (FILAMENT_SUPPORTS_WEBGPU)
list(APPEND BACKEND_TEST_SRC test/test_WebGPUAsyncTaskCounter.cpp)
endif()
if (APPLE)
# Metal-specific tests
list(APPEND BACKEND_TEST_SRC
@@ -593,6 +599,7 @@ if (APPLE OR LINUX)
spirv-cross-glsl)
# Create input/output directories for test result images.
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/images/actual_images)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/images/diff_images)
file(COPY test/expected_images DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/images)
endif()

View File

@@ -156,7 +156,7 @@ public:
* Set to `true` to forcibly disable amortized shader compilation in the backend.
* Currently only honored by the GL backend.
*/
bool disableAmortizedShaderCompile = false;
bool disableAmortizedShaderCompile = true;
/**
* Disable backend handles use-after-free checks.

View File

@@ -20,6 +20,7 @@
#include <utils/CString.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Invocable.h>
#include <utils/bitset.h>
#include <backend/DriverEnums.h>
@@ -66,6 +67,7 @@ public:
using DescriptorBindingsInfo = utils::FixedCapacityVector<Descriptor>;
using DescriptorSetInfo = std::array<DescriptorBindingsInfo, MAX_DESCRIPTOR_SET_COUNT>;
using SpecializationConstantsInfo = utils::FixedCapacityVector<SpecializationConstant>;
using MutableSpecConstantsInfo = utils::bitset8;
using ShaderBlob = utils::FixedCapacityVector<uint8_t>;
using ShaderSource = std::array<ShaderBlob, SHADER_TYPE_COUNT>;
@@ -103,7 +105,8 @@ public:
Program& descriptorBindings(backend::descriptor_set_t set,
DescriptorBindingsInfo descriptorBindings) noexcept;
Program& specializationConstants(SpecializationConstantsInfo specConstants) noexcept;
Program& specializationConstants(SpecializationConstantsInfo specConstants,
uint32_t firstMutableId, MutableSpecConstantsInfo mutableSpecConstants) noexcept;
struct PushConstant {
utils::CString name;

View File

@@ -30,7 +30,7 @@ namespace filament::backend {
* A Platform interface, handling the environment-specific concerns, e.g. OS, for creating a WebGPU
* driver (backend).
*/
class WebGPUPlatform final : public Platform {
class WebGPUPlatform : public Platform {
public:
WebGPUPlatform();
~WebGPUPlatform() override = default;
@@ -52,6 +52,14 @@ public:
// either returns a valid device or panics
[[nodiscard]] wgpu::Device requestDevice(wgpu::Adapter const& adapter);
struct Configuration {
wgpu::BackendType forceBackendType = wgpu::BackendType::Undefined;
};
[[nodiscard]] virtual Configuration getConfiguration() const noexcept {
return {};
}
protected:
[[nodiscard]] Driver* createDriver(void* sharedContext,
const Platform::DriverConfig& driverConfig) noexcept override;

View File

@@ -97,11 +97,13 @@ void CommandStream::execute(void* buffer) {
}
}
mDriver.execute([this, buffer]() {
Driver& UTILS_RESTRICT driver = mDriver;
CommandBase* UTILS_RESTRICT base = static_cast<CommandBase*>(buffer);
while (UTILS_LIKELY(base)) {
base = base->execute(driver);
Driver& UTILS_RESTRICT driver = mDriver;
CommandBase* UTILS_RESTRICT base = static_cast<CommandBase*>(buffer);
mDriver.execute([&driver, base] {
auto& d = driver;
auto p = base;
while (UTILS_LIKELY(p)) {
p = p->execute(d);
}
});

View File

@@ -82,8 +82,19 @@ Program& Program::attributes(AttributesInfo attributes) noexcept {
return *this;
}
Program& Program::specializationConstants(SpecializationConstantsInfo specConstants) noexcept {
mSpecializationConstants = std::move(specConstants);
Program& Program::specializationConstants(SpecializationConstantsInfo specConstants,
uint32_t firstMutableId, MutableSpecConstantsInfo mutableSpecConstants) noexcept {
// String the two lists together.
mSpecializationConstants = SpecializationConstantsInfo(
specConstants.size() + mutableSpecConstants.size());
std::uninitialized_move(specConstants.begin(), specConstants.end(),
mSpecializationConstants.begin());
for (uint32_t i = 0; i < mutableSpecConstants.size(); i++) {
mSpecializationConstants[i + specConstants.size()] = SpecializationConstant {
.id = i + firstMutableId,
.value = mutableSpecConstants[i],
};
}
return *this;
}

View File

@@ -118,6 +118,10 @@ MetalSwapChain::MetalSwapChain(
layerDrawableMutex(std::make_shared<std::mutex>()),
type(SwapChainType::CAMETALLAYER) {
FILAMENT_CHECK_PRECONDITION([nativeWindow isKindOfClass:[CAMetalLayer class]])
<< "nativeWindow pointer of class "
<< [NSStringFromClass([nativeWindow class]) UTF8String] << " is not a CAMetalLayer";
if (!(flags & SwapChain::CONFIG_TRANSPARENT) && !nativeWindow.opaque) {
LOG(WARNING) << "Warning: Filament SwapChain has no CONFIG_TRANSPARENT flag, but the "
"CAMetaLayer("

View File

@@ -452,7 +452,7 @@ void VulkanDriver::updateDescriptorSetTexture(
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
auto texture = resource_ptr<VulkanTexture>::cast(&mResourceManager, th);
if (mExternalImageManager.isExternallySampledTexture(texture)) {
if (UTILS_UNLIKELY(mExternalImageManager.isExternallySampledTexture(texture))) {
mExternalImageManager.bindExternallySampledTexture(set, binding, texture, params);
mAppState.hasBoundExternalImages = true;
} else {
@@ -1939,6 +1939,18 @@ void VulkanDriver::bindDescriptorSet(
if (dsh) {
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
mDescriptorSetCache.bind(setIndex, set, std::move(offsets));
if (mAppState.hasExternalSamplers()) {
auto const& bindInDrawBundle = mPipelineState.bindInDraw.second;
// The set index being bound has already been bound or will be bound. If it's already
// been bound and this set has external samplers, we do the doBindindraw block in
// draw2() again. Because this set might potentially cause a new pipelineLayout
// (therefore pipeline) to be bound.
if (bindInDrawBundle.descriptorSetMask[setIndex] &&
mExternalImageManager.hasExternalSampler(set)) {
mPipelineState.bindInDraw.first = true;
}
}
} else {
mDescriptorSetCache.unbind(setIndex);
}

View File

@@ -121,7 +121,7 @@ fvkutils::DescriptorSetMask VulkanExternalImageManager::prepareBindSets(LayoutAr
}
bool VulkanExternalImageManager::hasExternalSampler(
fvkmemory::resource_ptr<VulkanDescriptorSet> set) {
fvkmemory::resource_ptr<VulkanDescriptorSet> set) const {
auto itr = std::find_if(mSetBindings.begin(), mSetBindings.end(),
[&](SetBindingInfo const& info) { return info.set == set; });
return itr != mSetBindings.end();
@@ -191,9 +191,11 @@ void VulkanExternalImageManager::updateSetAndLayout(
VkDescriptorSet const srcSet = oldSet != VK_NULL_HANDLE ? oldSet : set->getVkSet();
copySet(mPlatform->getDevice(), srcSet, newSet, copyBindings);
set->setExternalSamplerVkSet(newSet, [&](VulkanDescriptorSet*) {
mDescriptorSetCache->manualRecycle(layout->count, newLayout, newSet);
});
set->setExternalSamplerVkSet(newSet,
[&descriptorSetCache = mDescriptorSetCache, layoutCount = layout->count, newLayout,
newSet](VulkanDescriptorSet*) {
descriptorSetCache->manualRecycle(layoutCount, newLayout, newSet);
});
if (oldLayout != newLayout) {
layout->setExternalSamplerVkLayout(newLayout);
}

View File

@@ -86,9 +86,9 @@ public:
VkSamplerYcbcrConversion conversion = VK_NULL_HANDLE;
};
private:
bool hasExternalSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set);
bool hasExternalSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set) const;
private:
void updateSetAndLayout(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);

View File

@@ -46,17 +46,13 @@ fvkmemory::resource_ptr<VulkanTimerQuery> VulkanQueryManager::getNextQuery(
return {};
}
bool found = false;
std::pair<uint32_t, uint32_t> queryIndices;
unused.forEachSetBit([&](size_t index) {
if (found) {
return;
}
size_t const firstUnused = unused.firstSetBit();
{
std::unique_lock<utils::Mutex> lock(mMutex);
mUsed.set(index);
found = true;
queryIndices = std::make_pair(index * 2, index * 2 + 1);
});
mUsed.set(firstUnused);
queryIndices = { firstUnused * 2, firstUnused * 2 + 1 };
}
return fvkmemory::resource_ptr<VulkanTimerQuery>::construct(resourceManager, queryIndices.first,
queryIndices.second);
}

View File

@@ -58,10 +58,10 @@ namespace {
areCopyCompatible(sourceFormat, destinationFormat) &&
(sourceExtent.width == destinationExtent.width) &&
(sourceExtent.height == destinationExtent.height) &&
((sourceOrigin.x & sourceBlockSize.width) == 0) &&
((sourceOrigin.y & sourceBlockSize.height) == 0) &&
((destinationOrigin.x & destinationBlockSize.width) == 0) &&
((destinationOrigin.y & destinationBlockSize.height) == 0);
((sourceOrigin.x % sourceBlockSize.width) == 0) &&
((sourceOrigin.y % sourceBlockSize.height) == 0) &&
((destinationOrigin.x % destinationBlockSize.width) == 0) &&
((destinationOrigin.y % destinationBlockSize.height) == 0);
}
struct BlitFragmentShaderArgs final {
@@ -206,9 +206,20 @@ constexpr std::string_view FRAGMENT_SHADER_SNIPPET_2D_INPUT_TEMPLATE{ R"(
} // namespace
// It can perform a direct memory copy if the formats are compatible and no scaling or format
// conversion is needed. Otherwise, it uses a render pass with a custom shader to perform
// the blit. This allows for scaling, format conversion, and resolving multisampled textures.
// The blitter caches pipelines, layouts, and shaders to avoid recompilation.
WebGPUBlitter::WebGPUBlitter(wgpu::Device const& device)
: mDevice{ device } {}
// Performs a blit operation from a source to a destination texture.
// This function first checks if a direct texture-to-texture copy can be performed.
// A direct copy is possible if the source and destination textures have the same sample count,
// compatible formats, and the copy extents match. If a direct copy is not possible, this
// function falls back to a render pass-based blit. The render pass uses a shader to read from
// the source texture and write to the destination texture, allowing for format conversion,
// scaling, and MSAA resolves.
void WebGPUBlitter::blit(wgpu::Queue const& queue, wgpu::CommandEncoder const& commandEncoder,
BlitArgs const& args) {
// current assumptions/simplifications made in this implementation:
@@ -267,22 +278,20 @@ void WebGPUBlitter::blit(wgpu::Queue const& queue, wgpu::CommandEncoder const& c
args.destination.texture.GetUsage() & wgpu::TextureUsage::CopyDst)
<< "destination texture usage doesn't have wgpu::TextureUsage::CopyDst";
copy(commandEncoder, args);
copyByteByByte(commandEncoder, args);
return;
}
// need to do a render pass blit...
FILAMENT_CHECK_PRECONDITION(args.destination.texture.GetSampleCount() == 1)
<< "Blitting does not currently support writing to multisampled output textures.";
FILAMENT_CHECK_PRECONDITION(args.source.texture.GetUsage() & wgpu::TextureUsage::TextureBinding)
<< "source texture usage doesn't have wgpu::TextureUsage::TextureBinding";
<< "Source texture usage doesn't have wgpu::TextureUsage::TextureBinding";
FILAMENT_CHECK_PRECONDITION(
args.destination.texture.GetUsage() & wgpu::TextureUsage::RenderAttachment)
<< "destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment";
<< "Destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment";
// create input/output texture views...
const wgpu::TextureViewDimension sourceDimension{ args.source.texture.GetDimension() ==
wgpu::TextureDimension::e3D
? wgpu::TextureViewDimension::e3D
@@ -327,7 +336,6 @@ void WebGPUBlitter::blit(wgpu::Queue const& queue, wgpu::CommandEncoder const& c
<< destinationViewDescriptor.baseArrayLayer << " and mip level "
<< destinationViewDescriptor.baseMipLevel << " for render pass blit?";
// create uniform buffer (for shader args)...
const BlitFragmentShaderArgs blitFragmentShaderArgs{
.depthPlane = args.source.layerOrDepth,
.scale = { static_cast<float>(args.source.extent.width) / args.destination.extent.width,
@@ -438,7 +446,7 @@ void WebGPUBlitter::blit(wgpu::Queue const& queue, wgpu::CommandEncoder const& c
const wgpu::RenderPassEncoder renderPassEncoder{ commandEncoder.BeginRenderPass(
&renderPassDescriptor) };
FILAMENT_CHECK_POSTCONDITION(renderPassEncoder)
<< "Failed to create render pass encoder for blit?";
<< "Failed to create render pass encoder for blit.";
renderPassEncoder.SetPipeline(getOrCreateRenderPipeline(args.filter, sourceDimension,
args.source.texture.GetSampleCount(), depthSource,
args.destination.texture.GetFormat()));
@@ -448,7 +456,7 @@ void WebGPUBlitter::blit(wgpu::Queue const& queue, wgpu::CommandEncoder const& c
renderPassEncoder.End();
}
void WebGPUBlitter::copy(wgpu::CommandEncoder const& commandEncoder, BlitArgs const& args) {
void WebGPUBlitter::copyByteByByte(wgpu::CommandEncoder const& commandEncoder, BlitArgs const& args) {
const wgpu::TexelCopyTextureInfo sourceCopyInfo{
.texture = args.source.texture,
.mipLevel = args.source.mipLevel,
@@ -505,7 +513,7 @@ void WebGPUBlitter::createSampler(const SamplerMagFilter filter) {
.maxAnisotropy = 1, // should not matter, just being consistently defined
};
const wgpu::Sampler sampler{ mDevice.CreateSampler(&descriptor) };
FILAMENT_CHECK_POSTCONDITION(sampler) << "Failed to create blit sampler?";
FILAMENT_CHECK_POSTCONDITION(sampler) << "Failed to create blit sampler.";
switch (filter) {
case SamplerMagFilter::NEAREST:
mNearestSampler = sampler;
@@ -516,6 +524,8 @@ void WebGPUBlitter::createSampler(const SamplerMagFilter filter) {
}
}
// Caches and returns a render pipeline for a given blit configuration.
// If a pipeline for the given configuration does not exist, it creates and caches one.
wgpu::RenderPipeline const& WebGPUBlitter::getOrCreateRenderPipeline(
const SamplerMagFilter filterType, const wgpu::TextureViewDimension sourceDimension,
const uint32_t sourceSampleCount, const bool depthSource,
@@ -529,6 +539,9 @@ wgpu::RenderPipeline const& WebGPUBlitter::getOrCreateRenderPipeline(
return mRenderPipelines[key];
}
// Creates a render pipeline for a blit operation.
// The pipeline is configured with the appropriate vertex and fragment shaders,
// pipeline layout, and render target state based on the blit parameters.
wgpu::RenderPipeline WebGPUBlitter::createRenderPipeline(const SamplerMagFilter filterType,
const wgpu::TextureViewDimension sourceDimension, const uint32_t sourceSampleCount,
const bool depthSource, const wgpu::TextureFormat destinationTextureFormat) {
@@ -580,7 +593,7 @@ wgpu::RenderPipeline WebGPUBlitter::createRenderPipeline(const SamplerMagFilter
.fragment = &fragmentState,
};
const wgpu::RenderPipeline pipeline{ mDevice.CreateRenderPipeline(&pipelineDescriptor) };
FILAMENT_CHECK_POSTCONDITION(pipeline) << "Failed to create pipeline for render pass blit?";
FILAMENT_CHECK_POSTCONDITION(pipeline) << "Failed to create pipeline for render pass blit.";
return pipeline;
}
@@ -595,6 +608,8 @@ size_t WebGPUBlitter::hashRenderPipelineKey(const SamplerMagFilter filterType,
return seed;
}
// Caches and returns a pipeline layout for a given blit configuration.
// If a layout for the given configuration does not exist, it creates and caches one.
wgpu::PipelineLayout const& WebGPUBlitter::getOrCreatePipelineLayout(
const SamplerMagFilter filterType, const wgpu::TextureViewDimension sourceDimension,
const bool multisampledSource, const bool depthSource) {
@@ -607,6 +622,8 @@ wgpu::PipelineLayout const& WebGPUBlitter::getOrCreatePipelineLayout(
return mPipelineLayouts[key];
}
// Creates a pipeline layout for a blit operation.
// The layout defines the bind group layouts used by the blit pipeline.
wgpu::PipelineLayout WebGPUBlitter::createPipelineLayout(const SamplerMagFilter filterType,
const wgpu::TextureViewDimension sourceDimension, const bool multisampledSource,
const bool depthSource) {
@@ -619,7 +636,7 @@ wgpu::PipelineLayout WebGPUBlitter::createPipelineLayout(const SamplerMagFilter
const wgpu::PipelineLayout pipelineLayout{ mDevice.CreatePipelineLayout(
&pipelineLayoutDescriptor) };
FILAMENT_CHECK_POSTCONDITION(pipelineLayout)
<< "Failed to create pipeline layout for render pass blit?";
<< "Failed to create pipeline layout for render pass blit.";
return pipelineLayout;
}
@@ -633,6 +650,8 @@ size_t WebGPUBlitter::hashPipelineLayoutKey(const SamplerMagFilter filterType,
return seed;
}
// Caches and returns a bind group layout for a given blit configuration.
// If a layout for the given configuration does not exist, it creates and caches one.
wgpu::BindGroupLayout const& WebGPUBlitter::getOrCreateTextureBindGroupLayout(
const SamplerMagFilter filterType, const wgpu::TextureViewDimension sourceDimension,
const bool multisampledSource, const bool depthSource) {
@@ -645,6 +664,9 @@ wgpu::BindGroupLayout const& WebGPUBlitter::getOrCreateTextureBindGroupLayout(
return mTextureBindGroupLayouts[key];
}
// Creates a bind group layout for the blit operation's texture resources.
// This layout specifies the bindings for the source texture, a uniform buffer with blit parameters,
// and optionally a sampler.
wgpu::BindGroupLayout WebGPUBlitter::createTextureBindGroupLayout(const SamplerMagFilter filterType,
const wgpu::TextureViewDimension sourceDimension, const bool multisampledSource,
const bool depthSource) {
@@ -655,11 +677,11 @@ wgpu::BindGroupLayout WebGPUBlitter::createTextureBindGroupLayout(const SamplerM
.texture = {
.sampleType = depthSource
? wgpu::TextureSampleType::Depth
: wgpu::TextureSampleType::Float, // only F32 scalar sample
// type supported for now
// (aside from depth)
// see assumptions listed
// in blit function
: (multisampledSource
? wgpu::TextureSampleType::UnfilterableFloat
: wgpu::TextureSampleType::Float), // only F32 scalar sample
// type supported for now
// (aside from depth)
.viewDimension = sourceDimension,
.multisampled = multisampledSource,
},
@@ -693,7 +715,7 @@ wgpu::BindGroupLayout WebGPUBlitter::createTextureBindGroupLayout(const SamplerM
const wgpu::BindGroupLayout textureBindGroupLayout{ mDevice.CreateBindGroupLayout(
&textureBindGroupLayoutDescriptor) };
FILAMENT_CHECK_POSTCONDITION(textureBindGroupLayout)
<< "Failed to create texture bind group layout for render pass blit?";
<< "Failed to create texture bind group layout for render pass blit.";
return textureBindGroupLayout;
}
@@ -707,6 +729,8 @@ size_t WebGPUBlitter::hashTextureBindGroupLayoutKey(const SamplerMagFilter filte
return seed;
}
// Caches and returns a shader module for a given blit configuration.
// If a shader module for the given configuration does not exist, it creates and caches one.
wgpu::ShaderModule const& WebGPUBlitter::getOrCreateShaderModule(
const wgpu::TextureViewDimension sourceDimension, const bool multisampledSource,
const bool depthSource, const bool depthDestination) {
@@ -719,6 +743,9 @@ wgpu::ShaderModule const& WebGPUBlitter::getOrCreateShaderModule(
return mShaderModules[key];
}
// Creates a shader module containing the vertex and fragment shaders for the blit operation.
// The shader source is generated from a template, with placeholders filled in based on the
// blit configuration (e.g., texture type, sample count).
wgpu::ShaderModule WebGPUBlitter::createShaderModule(
const wgpu::TextureViewDimension sourceDimension, const bool multisampledSource,
const bool depthSource, const bool depthDestination) {
@@ -730,9 +757,8 @@ wgpu::ShaderModule WebGPUBlitter::createShaderModule(
textureType = "texture_depth_2d";
}
} else {
// non-depth
if (multisampledSource) {
textureType = "texture_multisampled_2d";
textureType = "texture_multisampled_2d<f32>";
} else {
if (sourceDimension == wgpu::TextureViewDimension::e3D) {
textureType = "texture_3d<f32>";
@@ -785,7 +811,7 @@ wgpu::ShaderModule WebGPUBlitter::createShaderModule(
};
const wgpu::ShaderModule shaderModule{ mDevice.CreateShaderModule(&shaderModuleDescriptor) };
FILAMENT_CHECK_POSTCONDITION(shaderModule)
<< "Failed to create shader module for render pass blit?";
<< "Failed to create shader module for render pass blit.";
return shaderModule;
}

View File

@@ -26,6 +26,17 @@
namespace filament::backend {
/**
* A blit essentially writes pixels to a region of one texture given a region of another texture.
* Such a process can be a byte-by-byte copy when possible, while other scenarios involve various
* transformations, such as pixel format conversion, image scaling, filtering/sampling
* (such as down sampling), or even multi-sample resolves. Some blits can be faster than others,
* depending on what is needed; for example, a byte-by-byte copy is generally faster than other
* operations.
*
* This class is responsible for performing blits throughout the lifecycle of its owner/caller,
* e.g. the WebGPU backend/driver, where it can leverage state to optimize such blit calls over time.
*/
class WebGPUBlitter final {
public:
struct BlitArgs final {
@@ -58,10 +69,15 @@ private:
/**
* ONLY should be called if canDoDirectCopy(...) is true (see it in WebGPUBlitter.cpp)
*/
void copy(wgpu::CommandEncoder const&, BlitArgs const&);
void copyByteByByte(wgpu::CommandEncoder const&, BlitArgs const&);
void createSampler(SamplerMagFilter);
// The following methods are used to create and cache WebGPU objects.
// The pattern is to have a `getOrCreate...` method that looks up the object in a cache,
// and if it's not found, it calls a `create...` method to create it and then stores it in the
// cache. A `hash...` method is used to generate a key for the cache.
[[nodiscard]] wgpu::RenderPipeline const& getOrCreateRenderPipeline(SamplerMagFilter,
wgpu::TextureViewDimension sourceDimension, uint32_t sourceSampleCount,
bool depthSource, wgpu::TextureFormat destinationTextureFormat);

View File

@@ -33,6 +33,9 @@ namespace filament::backend {
namespace {
// Creates a wgpu::Buffer, ensuring its size is a multiple of FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS.
// WebGPU's WriteBuffer requires the write size to be a multiple of 4. By ensuring the buffer
// size is also a multiple of 4, we simplify the update logic.
[[nodiscard]] wgpu::Buffer createBuffer(wgpu::Device const& device, const wgpu::BufferUsage usage,
uint32_t size, const char* const label) {
// Write size must be divisible by WEBGPU_BUFFER_SIZE_MODULUS (e.g. 4).
@@ -55,6 +58,11 @@ WebGPUBufferBase::WebGPUBufferBase(wgpu::Device const& device, const wgpu::Buffe
const uint32_t size, char const* const label)
: mBuffer{ createBuffer(device, usage, size, label) } {}
// Updates the GPU buffer with data from a BufferDescriptor.
// WebGPU requires that the size of the data written to a buffer is a multiple of 4.
// This function handles cases where the buffer descriptor's size is not a multiple of 4
// by writing the bulk of the data first, and then copying the remaining bytes into a
// padded temporary chunk which is then written to the buffer.
void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
const uint32_t byteOffset, wgpu::Queue const& queue) {
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.buffer)

View File

@@ -28,6 +28,10 @@ namespace filament::backend {
class BufferDescriptor;
/**
* A base class for WebGPU buffer objects, providing common functionality for creating and
* updating GPU buffers.
*/
class WebGPUBufferBase /* intended to be extended */ {
public:
/**
@@ -46,7 +50,8 @@ protected:
private:
const wgpu::Buffer mBuffer;
// FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS (e.g. 4) bytes to hold any extra chunk we need.
// WebGPU requires that the source buffer of a writeBuffer call has a size that is a multiple
// of 4. This member is used to pad the data if the source size is not a multiple of 4.
std::array<uint8_t, FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS> mRemainderChunk{};
};

View File

@@ -40,6 +40,8 @@ namespace {
} // namespace
// The usage flags are determined by the binding type, and always include CopyDst to allow for
// updating the buffer.
WebGPUBufferObject::WebGPUBufferObject(wgpu::Device const& device,
const BufferObjectBinding bindingType, const uint32_t byteCount)
: HwBufferObject{ byteCount },

View File

@@ -31,6 +31,12 @@ namespace filament::backend {
enum class BufferObjectBinding : uint8_t;
/**
* A WebGPU-specific implementation of BufferObject.
* A BufferObject is a GPU buffer that can be used for a variety of purposes, such as storing
* uniform data, vertex attributes, or pixel data. The specific usage is determined by the
* BufferObjectBinding.
*/
class WebGPUBufferObject final : public HwBufferObject, public WebGPUBufferBase {
public:
WebGPUBufferObject(wgpu::Device const&, BufferObjectBinding, uint32_t byteCount);

View File

@@ -21,6 +21,7 @@
#include <cstdint>
// WebGPU requires that the source buffer of a writeBuffer call has a size that is a multiple of 4.
constexpr size_t FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS = 4;
// FWGPU is short for Filament WebGPU
@@ -48,8 +49,16 @@ constexpr size_t FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS = 4;
// textures, samplers, and buffers,...), which would otherwise spam the logs.
#define FWGPU_DEBUG_BIND_GROUPS 0x00000020
// Enables Android systrace
#define FWGPU_DEBUG_SYSTRACE 0x00000040
// Enable a minimal set of traces to assess the performance of the backend.
// All other debug features must be disabled.
#define FWGPU_DEBUG_PROFILING 0x00000080
// Useful default combinations
#define FWGPU_DEBUG_EVERYTHING 0xFFFFFFFF
#define FWGPU_DEBUG_EVERYTHING (0xFFFFFFFF & ~FWGPU_DEBUG_PROFILING)
#define FWGPU_DEBUG_PERFORMANCE FWGPU_DEBUG_SYSTRACE
#if defined(FILAMENT_BACKEND_DEBUG_FLAG)
#define FWGPU_DEBUG_FORWARDED_FLAG (FILAMENT_BACKEND_DEBUG_FLAG & FWGPU_DEBUG_EVERYTHING)
@@ -58,13 +67,53 @@ constexpr size_t FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS = 4;
#endif
#ifndef NDEBUG
#define FWGPU_DEBUG_FLAGS FWGPU_DEBUG_FORWARDED_FLAG
#define FWGPU_DEBUG_FLAGS (FWGPU_DEBUG_PERFORMANCE | FWGPU_DEBUG_FORWARDED_FLAG)
#else
#define FWGPU_DEBUG_FLAGS 0
#define FWGPU_DEBUG_FLAGS FWGPU_DEBUG_SYSTRACE
#endif
// Override the debug flags if we are forcing profiling mode
#if defined(FILAMENT_FORCE_PROFILING_MODE)
#undef FWGPU_DEBUG_FLAGS
#define FWGPU_DEBUG_FLAGS (FWGPU_DEBUG_PROFILING)
#endif
#define FWGPU_ENABLED(flags) (((FWGPU_DEBUG_FLAGS) & (flags)) == (flags))
#if FWGPU_ENABLED(FWGPU_DEBUG_PROFILING) && FWGPU_DEBUG_FLAGS != FWGPU_DEBUG_PROFILING
#error PROFILING is exclusive; all other debug features must be disabled.
#endif
#if FWGPU_DEBUG_FLAGS == FWGPU_DEBUG_PROFILING
#ifndef NDEBUG
#error PROFILING is meaningless in DEBUG mode.
#endif
#define FWGPU_SYSTRACE_CONTEXT()
#define FWGPU_SYSTRACE_START(marker)
#define FWGPU_SYSTRACE_END()
#define FWGPU_SYSTRACE_SCOPE()
#define FWGPU_PROFILE_MARKER(marker) PROFILE_SCOPE(marker)
#elif FWGPU_ENABLED(FWGPU_DEBUG_SYSTRACE)
#include <private/utils/Tracing.h>
#define FWGPU_SYSTRACE_CONTEXT() FILAMENT_TRACING_CONTEXT(FILAMENT_TRACING_CATEGORY_FILAMENT)
#define FWGPU_SYSTRACE_START(marker) FILAMENT_TRACING_NAME_BEGIN(FILAMENT_TRACING_CATEGORY_FILAMENT, marker)
#define FWGPU_SYSTRACE_END() FILAMENT_TRACING_NAME_END(FILAMENT_TRACING_CATEGORY_FILAMENT)
#define FWGPU_SYSTRACE_SCOPE() FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT)
#define FWGPU_PROFILE_MARKER(marker) FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT)
#else
#define FWGPU_SYSTRACE_CONTEXT()
#define FWGPU_SYSTRACE_START(marker)
#define FWGPU_SYSTRACE_END()
#define FWGPU_SYSTRACE_SCOPE()
#define FWGPU_PROFILE_MARKER(marker)
#endif
#if FWGPU_ENABLED(FWGPU_DEBUG_FORCE_LOG_TO_I)
#define FWGPU_LOGI LOG(INFO)
#define FWGPU_LOGD FWGPU_LOGI

View File

@@ -42,6 +42,9 @@ constexpr uint8_t INVALID_INDEX = MAX_DESCRIPTOR_COUNT + 1;
} // namespace
// The constructor initializes the descriptor set based on a given layout.
// It pre-allocates space for the bind group entries and sets up a mapping from binding index
// to the internal entry array index. This prepares the object to be populated with resources.
WebGPUDescriptorSet::WebGPUDescriptorSet(wgpu::BindGroupLayout const& layout,
std::vector<WebGPUDescriptorSetLayout::BindGroupEntryInfo> const& bindGroupEntries)
: mLayout{ layout },
@@ -52,11 +55,10 @@ WebGPUDescriptorSet::WebGPUDescriptorSet(wgpu::BindGroupLayout const& layout,
for (size_t i = 0; i < bindGroupEntries.size(); ++i) {
mEntries[i].binding = bindGroupEntries[i].binding;
}
// Establish the size of entries based on the layout. This should be reliable and efficient.
// Create a mapping from binding index to entry index for efficient lookup.
assert_invariant(INVALID_INDEX > mEntryIndexByBinding.size());
for (size_t i = 0; i < mEntryIndexByBinding.size(); i++) {
mEntryIndexByBinding[i] = INVALID_INDEX;
}
std::fill(mEntryIndexByBinding.begin(), mEntryIndexByBinding.end(), INVALID_INDEX);
for (size_t index = 0; index < mEntries.size(); index++) {
wgpu::BindGroupEntry const& entry = mEntries[index];
assert_invariant(entry.binding < mEntryIndexByBinding.size());
@@ -66,19 +68,20 @@ WebGPUDescriptorSet::WebGPUDescriptorSet(wgpu::BindGroupLayout const& layout,
void WebGPUDescriptorSet::addEntry(const unsigned int index, wgpu::BindGroupEntry&& entry) {
if (mBindGroup) {
// We will keep getting hits from future updates, but shouldn't do anything
// Filament guarantees this won't change after things have locked.
// The bind group has already been created, so we can't add new entries.
// Filament guarantees that the descriptor set will not be modified after it has been locked.
return;
}
// TODO: Putting some level of trust that Filament is not going to reuse indexes or go past the
// layout index for efficiency. Add guards if wrong.
FILAMENT_CHECK_POSTCONDITION(index < mEntryIndexByBinding.size())
<< "impossible/invalid index for a descriptor/binding (our of range or >= "
<< "impossible/invalid index for a descriptor/binding (out of range or >= "
"MAX_DESCRIPTOR_COUNT) "
<< index;
uint8_t entryIndex = mEntryIndexByBinding[index];
FILAMENT_CHECK_POSTCONDITION(entryIndex != INVALID_INDEX && entryIndex < mEntries.size())
<< "Invalid binding " << index;
<< "Invalid binding index: " << index;
entry.binding = index;
mEntries[entryIndex] = std::move(entry);
}
@@ -94,8 +97,9 @@ wgpu::BindGroup WebGPUDescriptorSet::lockAndReturn(wgpu::Device const& device) {
.entries = mEntries.data()
};
mBindGroup = device.CreateBindGroup(&descriptor);
FILAMENT_CHECK_POSTCONDITION(mBindGroup) << "Failed to create bind group?";
// once we have created the bind group itself we should no longer need any other state
FILAMENT_CHECK_POSTCONDITION(mBindGroup) << "Failed to create bind group.";
// Once the bind group is created, we can release the resources used to create it.
#if FWGPU_ENABLED(FWGPU_DEBUG_BIND_GROUPS)
FWGPU_LOGD << "WebGPUDescriptorSet (lockAndReturn):";
FWGPU_LOGD << " wgpu::BindGroupLayout handle: " << mLayout.Get();

View File

@@ -32,23 +32,46 @@
namespace filament::backend {
/**
* A WebGPU-specific implementation of DescriptorSet.
* This class manages a collection of resource bindings (e.g., textures, buffers) that are used by
* a shader. It corresponds to a `wgpu::BindGroup` in WebGPU. The actual `wgpu::BindGroup` is
* created lazily when `lockAndReturn` is called, at which point the descriptor set becomes
* immutable.
*/
class WebGPUDescriptorSet final : public HwDescriptorSet {
public:
WebGPUDescriptorSet(wgpu::BindGroupLayout const&,
std::vector<WebGPUDescriptorSetLayout::BindGroupEntryInfo> const&);
~WebGPUDescriptorSet();
/**
* Adds or updates a resource binding in the descriptor set.
* This method is called to populate the descriptor set with buffers, textures, and samplers.
* It is only valid to call this before the descriptor set is locked (i.e., before
* `lockAndReturn` is called).
*/
void addEntry(unsigned int index, wgpu::BindGroupEntry&& entry);
/**
* Finalizes the descriptor set by creating the wgpu::BindGroup. After this call, the
* descriptor set is considered "locked" and cannot be modified further.
*/
[[nodiscard]] wgpu::BindGroup lockAndReturn(wgpu::Device const&);
/**
* @return true if the descriptor set has been locked (i.e., the wgpu::BindGroup has been
* created).
*/
[[nodiscard]] bool getIsLocked() const { return mBindGroup != nullptr; }
[[nodiscard]] size_t getEntitiesWithDynamicOffsetsCount() const {
return mEntriesWithDynamicOffsetsCount;
}
// May be nullptr. Use lockAndReturn to create the bind group when appropriate
/**
* May be nullptr. Use lockAndReturn to create the bind group when appropriate
*/
[[nodiscard]] wgpu::BindGroup const& getBindGroup() const { return mBindGroup; }
#if FWGPU_ENABLED(FWGPU_DEBUG_BIND_GROUPS)
@@ -60,6 +83,7 @@ private:
std::array<uint8_t, MAX_DESCRIPTOR_COUNT> mEntryIndexByBinding{};
std::vector<wgpu::BindGroupEntry> mEntries;
const size_t mEntriesWithDynamicOffsetsCount;
// This is created lazily when lockAndReturn is called.
wgpu::BindGroup mBindGroup = nullptr;
};

View File

@@ -40,7 +40,9 @@ namespace filament::backend {
namespace {
// Convert Filament Shader Stage Flags bitmask to webgpu equivalent
/**
* Converts Filament shader stage flags to the corresponding WebGPU shader stage bitmask.
*/
[[nodiscard]] wgpu::ShaderStage filamentStageToWGPUStage(const ShaderStageFlags fFlags) {
wgpu::ShaderStage retStages = wgpu::ShaderStage::None;
if (any(ShaderStageFlags::VERTEX & fFlags)) {
@@ -70,10 +72,6 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const&
baseLabel = temp->c_str();
}
// TODO: layoutDescriptor has a "Label". Ideally we can get info on what this layout is for
// debugging. For now, hack an incrementing value.
static int layoutNum = 0;
const unsigned int samplerCount =
std::count_if(layout.bindings.begin(), layout.bindings.end(), [](auto& fEntry) {
return DescriptorSetLayoutBinding::isSampler(fEntry.type);
@@ -87,6 +85,11 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const&
auto& wEntry = wEntries.emplace_back();
auto& entryInfo = mBindGroupEntries.emplace_back();
wEntry.visibility = filamentStageToWGPUStage(fEntry.stageFlags);
// In WebGPU, textures and samplers are separate bindings.
// We map the Filament binding index to two WebGPU binding indices:
// - texture: binding * 2
// - sampler: binding * 2 + 1
wEntry.binding = fEntry.binding * 2;
entryInfo.binding = wEntry.binding;
@@ -178,11 +181,13 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const&
} else if (isCubeArrayTypeDescriptor(fEntry.type)) {
wEntry.texture.viewDimension = wgpu::TextureViewDimension::CubeArray;
}
// fEntry.count is unused currently
}
std::string label = "layout_" + baseLabel + std::to_string(++layoutNum) ;
// TODO: layoutDescriptor has a "Label". Ideally we can get info on what this layout is for
// debugging. For now, hack an incrementing value.
static int layoutNum = 0;
std::string label = "layout_" + baseLabel + "_" + std::to_string(++layoutNum) ;
const wgpu::BindGroupLayoutDescriptor layoutDescriptor{
.label{label.c_str()}, // Use .c_str() if label needs to be const char*
.label{label.c_str()},
.entryCount = wEntries.size(),
.entries = wEntries.data()
};

View File

@@ -27,6 +27,11 @@
namespace filament::backend {
/**
* A WebGPU implementation of the HwDescriptorSetLayout.
* This class defines the layout of a descriptor set, which specifies the types and arrangement
* of resources that can be bound to a pipeline.
*/
class WebGPUDescriptorSetLayout final : public HwDescriptorSetLayout {
public:
struct BindGroupEntryInfo final {

View File

@@ -32,10 +32,12 @@
#include "WebGPUTextureHelpers.h"
#include "WebGPUVertexBuffer.h"
#include "WebGPUVertexBufferInfo.h"
#include "webgpu/utils/AsyncTaskCounter.h"
#include <backend/platforms/WebGPUPlatform.h>
#include "CommandStreamDispatcher.h"
#include "DriverBase.h"
#include "SystraceProfile.h"
#include "private/backend/Dispatcher.h"
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
@@ -71,7 +73,7 @@ void setDefaultTargetFlags(WebGPURenderTarget& defaultRenderTarget,
const wgpu::TextureFormat depthFormat{ swapChain.getDepthFormat() };
TargetBufferFlags newTargetFlags{ filament::backend::TargetBufferFlags::NONE };
// Assuming Color always present in default render target.
// Color is always assumed to be present in the default render target.
newTargetFlags |= filament::backend::TargetBufferFlags::COLOR;
if (depthFormat != wgpu::TextureFormat::Undefined) {
if (hasDepth(depthFormat)) {
@@ -86,6 +88,12 @@ void setDefaultTargetFlags(WebGPURenderTarget& defaultRenderTarget,
} // namespace
// The WebGPUDriver is the main entry point for the WebGPU backend. It is responsible for
// creating and managing all WebGPU resources, and for submitting commands to the GPU.
// It implements the Driver interface, which is the abstraction layer used by Filament's
// renderer. The driver uses a handle-based system to manage resources, and it operates
// asynchronously, with `create...S` methods for synchronous handle allocation and
// `create...R` methods for resource creation on the backend thread.
Driver* WebGPUDriver::create(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept {
constexpr size_t defaultSize = FILAMENT_WEBGPU_HANDLE_ARENA_SIZE_IN_MB * 1024U * 1024U;
Platform::DriverConfig validConfig {driverConfig};
@@ -141,6 +149,7 @@ void WebGPUDriver::tick(int) {
void WebGPUDriver::beginFrame(int64_t monotonic_clock_ns,
int64_t refreshIntervalNs, uint32_t frameId) {
FWGPU_PROFILE_MARKER(PROFILE_NAME_BEGINFRAME);
}
void WebGPUDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
@@ -157,9 +166,11 @@ void WebGPUDriver::setPresentationTime(int64_t monotonic_clock_ns) {
}
void WebGPUDriver::endFrame(const uint32_t /* frameId */) {
FWGPU_PROFILE_MARKER(PROFILE_NAME_ENDFRAME);
mPipelineLayoutCache.onFrameEnd();
mPipelineCache.onFrameEnd();
// Clear the currently bound descriptor sets at the end of the frame.
for (size_t i = 0; i < MAX_DESCRIPTOR_SET_COUNT; i++) {
mCurrentDescriptorSets[i] = {};
}
@@ -169,10 +180,11 @@ void WebGPUDriver::endFrame(const uint32_t /* frameId */) {
// A new command encoder is created fresh after it.
// If a command encoder is not in flight (nullptr), then this is a noop.
void WebGPUDriver::flush(int /* dummy */) {
FWGPU_SYSTRACE_SCOPE();
if (mCommandEncoder == nullptr) {
return;
}
// submit the command buffer thus far...
// Submit the commands recorded so far.
assert_invariant(mRenderPassEncoder == nullptr);
wgpu::CommandBufferDescriptor commandBufferDescriptor{
.label = "frame_command_after_flush",
@@ -189,12 +201,14 @@ void WebGPUDriver::flush(int /* dummy */) {
assert_invariant(mCommandEncoder);
}
// Submits the currently recorded commands and waits for them to complete on the GPU.
// This is a synchronous operation and should be used sparingly.
void WebGPUDriver::finish(int /* dummy */) {
if (mCommandEncoder == nullptr) {
return;
}
FWGPU_SYSTRACE_SCOPE();
mDevice.Tick();
mAdapter.GetInstance().ProcessEvents();
flush();
// Wait for all previously submitted work to finish.
std::mutex syncPoint;
std::condition_variable syncCondition;
bool done = false;
@@ -211,36 +225,47 @@ void WebGPUDriver::finish(int /* dummy */) {
});
std::unique_lock<std::mutex> lock(syncPoint);
syncCondition.wait(lock, [&done] { return done; });
mReadPixelMapsCounter.waitForAllToFinish();
}
void WebGPUDriver::destroyRenderPrimitive(Handle<HwRenderPrimitive> rph) {
if (rph) {
destructHandle<WebGPURenderPrimitive>(rph);
if (!rph) {
return;
}
FWGPU_SYSTRACE_SCOPE();
destructHandle<WebGPURenderPrimitive>(rph);
}
void WebGPUDriver::destroyVertexBufferInfo(Handle<HwVertexBufferInfo> vertexBufferInfoHandle) {
if (vertexBufferInfoHandle) {
destructHandle<WebGPUVertexBufferInfo>(vertexBufferInfoHandle);
if (!vertexBufferInfoHandle) {
return;
}
FWGPU_SYSTRACE_SCOPE();
destructHandle<WebGPUVertexBufferInfo>(vertexBufferInfoHandle);
}
void WebGPUDriver::destroyVertexBuffer(Handle<HwVertexBuffer> vertexBufferHandle) {
if (vertexBufferHandle) {
destructHandle<WebGPUVertexBuffer>(vertexBufferHandle);
if (!vertexBufferHandle) {
return;
}
FWGPU_SYSTRACE_SCOPE();
destructHandle<WebGPUVertexBuffer>(vertexBufferHandle);
}
void WebGPUDriver::destroyIndexBuffer(Handle<HwIndexBuffer> indexBufferHandle) {
if (indexBufferHandle) {
destructHandle<WebGPUIndexBuffer>(indexBufferHandle);
if (!indexBufferHandle) {
return;
}
FWGPU_SYSTRACE_SCOPE();
destructHandle<WebGPUIndexBuffer>(indexBufferHandle);
}
void WebGPUDriver::destroyBufferObject(Handle<HwBufferObject> bufferObjectHandle) {
if (bufferObjectHandle) {
destructHandle<WebGPUBufferObject>(bufferObjectHandle);
if (!bufferObjectHandle) {
return;
}
FWGPU_SYSTRACE_SCOPE();
destructHandle<WebGPUBufferObject>(bufferObjectHandle);
}
void WebGPUDriver::destroyTexture(Handle<HwTexture> textureHandle) {
@@ -264,10 +289,8 @@ void WebGPUDriver::destroyRenderTarget(Handle<HwRenderTarget> renderTargetHandle
if (renderTarget == mCurrentRenderTarget) {
mCurrentRenderTarget = nullptr;
}
// WGPURenderTarget destructor is trivial.
// The HwTexture handles stored within WGPURenderTarget (via MRT, TargetBufferInfo)
// are not owned by WGPURenderTarget, so they are not destroyed here.
// They are destroyed via WebGPUDriver::destroyTexture.
// The HwTexture handles stored within WGPURenderTarget are not owned by it,
// so they are not destroyed here. They are destroyed via WebGPUDriver::destroyTexture.
destructHandle<WebGPURenderTarget>(renderTargetHandle);
}
}
@@ -312,6 +335,14 @@ void WebGPUDriver::destroyDescriptorSet(Handle<HwDescriptorSet> descriptorSetHan
}
}
// ------------------------------------------------------------------------------------------------
// Synchronous handle creation methods (S-methods)
//
// These methods are called from the frontend thread to allocate a handle for a backend resource.
// They are lightweight and do not perform any GPU operations. The actual resource creation
// happens in the corresponding R-method on the backend thread.
// ------------------------------------------------------------------------------------------------
Handle<HwSwapChain> WebGPUDriver::createSwapChainS() noexcept {
return allocHandle<WebGPUSwapChain>();
}
@@ -396,9 +427,17 @@ Handle<HwTexture> WebGPUDriver::createTextureExternalImagePlaneS() noexcept {
return allocHandle<WebGPUTexture>();
}
void WebGPUDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) {
// ------------------------------------------------------------------------------------------------
// Asynchronous resource creation methods (R-methods)
//
// These methods are called on the backend thread to create the actual GPU resources
// associated with a handle that was previously allocated by an S-method.
// ------------------------------------------------------------------------------------------------
void WebGPUDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
const uint64_t flags) {
FWGPU_SYSTRACE_SCOPE();
mNativeWindow = nativeWindow;
assert_invariant(!mSwapChain);
wgpu::Surface surface = mPlatform.createSurface(nativeWindow, flags);
wgpu::Extent2D extent = mPlatform.getSurfaceExtent(mNativeWindow);
@@ -433,12 +472,14 @@ void WebGPUDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch, uint32_t wi
void WebGPUDriver::createVertexBufferInfoR(Handle<HwVertexBufferInfo> vertexBufferInfoHandle,
const uint8_t bufferCount, const uint8_t attributeCount, const AttributeArray attributes) {
FWGPU_SYSTRACE_SCOPE();
constructHandle<WebGPUVertexBufferInfo>(vertexBufferInfoHandle, bufferCount, attributeCount,
attributes, mDeviceLimits);
}
void WebGPUDriver::createVertexBufferR(Handle<HwVertexBuffer> vertexBufferHandle,
const uint32_t vertexCount, Handle<HwVertexBufferInfo> vertexBufferInfoHandle) {
FWGPU_SYSTRACE_SCOPE();
const auto vertexBufferInfo = handleCast<WebGPUVertexBufferInfo>(vertexBufferInfoHandle);
constructHandle<WebGPUVertexBuffer>(vertexBufferHandle, vertexCount,
vertexBufferInfo->bufferCount, vertexBufferInfoHandle);
@@ -446,12 +487,14 @@ void WebGPUDriver::createVertexBufferR(Handle<HwVertexBuffer> vertexBufferHandle
void WebGPUDriver::createIndexBufferR(Handle<HwIndexBuffer> indexBufferHandle,
const ElementType elementType, const uint32_t indexCount, const BufferUsage usage) {
FWGPU_SYSTRACE_SCOPE();
const auto elementSize = static_cast<uint8_t>(getElementTypeSize(elementType));
constructHandle<WebGPUIndexBuffer>(indexBufferHandle, mDevice, elementSize, indexCount);
}
void WebGPUDriver::createBufferObjectR(Handle<HwBufferObject> bufferObjectHandle,
const uint32_t byteCount, const BufferObjectBinding bindingType, const BufferUsage usage) {
FWGPU_SYSTRACE_SCOPE();
constructHandle<WebGPUBufferObject>(bufferObjectHandle, mDevice, bindingType, byteCount);
}
@@ -459,6 +502,7 @@ void WebGPUDriver::createTextureR(Handle<HwTexture> textureHandle, const Sampler
const uint8_t levels, const TextureFormat format, const uint8_t samples,
const uint32_t width, const uint32_t height, const uint32_t depth,
const TextureUsage usage) {
FWGPU_SYSTRACE_SCOPE();
constructHandle<WebGPUTexture>(textureHandle, target, levels, format, samples, width, height,
depth, usage, mDevice);
}
@@ -474,13 +518,47 @@ void WebGPUDriver::createTextureViewSwizzleR(Handle<HwTexture> textureHandle,
Handle<HwTexture> sourceTextureHandle, const backend::TextureSwizzle r,
const backend::TextureSwizzle g, const backend::TextureSwizzle b,
const backend::TextureSwizzle a) {
PANIC_POSTCONDITION("Swizzle WebGPU Texture is not supported");
if (!isTextureSwizzleSupported()) {
FWGPU_LOGW << "WebGPUDriver::createTextureViewSwizzleR called while texture swizzling is "
"not supported by the device/driver";
}
auto sourceTexture{ handleCast<WebGPUTexture>(sourceTextureHandle) };
assert_invariant(sourceTexture);
wgpu::TextureComponentSwizzle swizzle
{
.r = toWGPUComponentSwizzle(r),
.g = toWGPUComponentSwizzle(g),
.b = toWGPUComponentSwizzle(b),
.a = toWGPUComponentSwizzle(a),
};
wgpu::TextureComponentSwizzleDescriptor swizzleDesc {};
swizzleDesc.swizzle = swizzle;
const wgpu::TextureViewDescriptor viewDesc {
.nextInChain = &swizzleDesc,
.label = "swizzled_texture_view",
.format = sourceTexture->getTexture().GetFormat(),
.dimension = sourceTexture->getViewDimension(),
.baseMipLevel = 0,
.mipLevelCount = sourceTexture->getTexture().GetMipLevelCount(),
.baseArrayLayer = 0,
.arrayLayerCount = sourceTexture->getTexture().GetDepthOrArrayLayers(),
};
wgpu::TextureView swizzledView{ sourceTexture->getTexture().CreateView(&viewDesc) };
FILAMENT_CHECK_POSTCONDITION(swizzledView) << "Failed to create swizzled Texture view";
constructHandle<WebGPUTexture>(textureHandle, sourceTexture, swizzledView);
}
void WebGPUDriver::createTextureExternalImage2R(Handle<HwTexture> textureHandle,
const backend::SamplerType target, const backend::TextureFormat format,
const uint32_t width, const uint32_t height, const backend::TextureUsage usage,
Platform::ExternalImageHandleRef externalImage) {
FWGPU_SYSTRACE_SCOPE();
PANIC_POSTCONDITION("External WebGPU Texture is not supported");
}
@@ -488,6 +566,7 @@ void WebGPUDriver::createTextureExternalImageR(Handle<HwTexture> textureHandle,
const backend::SamplerType target, const backend::TextureFormat format,
const uint32_t width, const uint32_t height, const backend::TextureUsage usage,
void* externalImage) {
FWGPU_SYSTRACE_SCOPE();
PANIC_POSTCONDITION("External WebGPU Texture is not supported");
}
@@ -507,6 +586,7 @@ void WebGPUDriver::importTextureR(Handle<HwTexture> textureHandle, const intptr_
void WebGPUDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> renderPrimitiveHandle,
Handle<HwVertexBuffer> vertexBufferHandle, Handle<HwIndexBuffer> indexBufferHandle,
const PrimitiveType primitiveType) {
FWGPU_SYSTRACE_SCOPE();
assert_invariant(mDevice);
const auto renderPrimitive = constructHandle<WebGPURenderPrimitive>(renderPrimitiveHandle);
const auto vertexBuffer = handleCast<WebGPUVertexBuffer>(vertexBufferHandle);
@@ -517,6 +597,7 @@ void WebGPUDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> renderPrimit
}
void WebGPUDriver::createProgramR(Handle<HwProgram> programHandle, Program&& program) {
FWGPU_SYSTRACE_SCOPE();
constructHandle<WebGPUProgram>(programHandle, mDevice, program);
}
@@ -534,6 +615,7 @@ void WebGPUDriver::createRenderTargetR(Handle<HwRenderTarget> renderTargetHandle
const TargetBufferFlags targetFlags, const uint32_t width, const uint32_t height,
const uint8_t samples, const uint8_t layerCount, const MRT color,
const TargetBufferInfo depth, const TargetBufferInfo stencil) {
FWGPU_SYSTRACE_SCOPE();
constructHandle<WebGPURenderTarget>(
renderTargetHandle, width, height, samples, layerCount, color, depth, stencil,
targetFlags,
@@ -544,7 +626,7 @@ void WebGPUDriver::createRenderTargetR(Handle<HwRenderTarget> renderTargetHandle
}
void WebGPUDriver::createFenceR(Handle<HwFence> fenceHandle, const int /* dummy */) {
// the handle was already constructed in createFenceS
// The handle is constructed synchronously in createFenceS.
const auto fence = handleCast<WebGPUFence>(fenceHandle);
assert_invariant(mQueue);
fence->addMarkerToQueueState(mQueue);
@@ -555,11 +637,13 @@ void WebGPUDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {}
void WebGPUDriver::createDescriptorSetLayoutR(
Handle<HwDescriptorSetLayout> descriptorSetLayoutHandle,
backend::DescriptorSetLayout&& info) {
FWGPU_SYSTRACE_SCOPE();
constructHandle<WebGPUDescriptorSetLayout>(descriptorSetLayoutHandle, std::move(info), mDevice);
}
void WebGPUDriver::createDescriptorSetR(Handle<HwDescriptorSet> descriptorSetHandle,
Handle<HwDescriptorSetLayout> descriptorSetLayoutHandle) {
FWGPU_SYSTRACE_SCOPE();
auto layout = handleCast<WebGPUDescriptorSetLayout>(descriptorSetLayoutHandle);
constructHandle<WebGPUDescriptorSet>(descriptorSetHandle, layout->getLayout(),
layout->getBindGroupEntries());
@@ -614,14 +698,12 @@ FenceStatus WebGPUDriver::getFenceStatus(Handle<HwFence> fenceHandle) {
return fence->getStatus();
}
// We create all textures using VK_IMAGE_TILING_OPTIMAL, so our definition of "supported" is that
// the GPU supports the given texture format with non-zero optimal tiling features.
bool WebGPUDriver::isTextureFormatSupported(const TextureFormat format) {
return toWGPUTextureFormat(format) != wgpu::TextureFormat::Undefined;
}
bool WebGPUDriver::isTextureSwizzleSupported() {
return false;
return mDevice.HasFeature(wgpu::FeatureName::TextureComponentSwizzle);
}
bool WebGPUDriver::isTextureFormatMipmappable(const TextureFormat format) {
@@ -796,6 +878,10 @@ void WebGPUDriver::setVertexBufferObject(Handle<HwVertexBuffer> vertexBufferHand
vertexBuffer->getBuffers()[index] = bufferObject->getBuffer();
}
// Updates a 3D texture region with pixel data from a buffer.
// This function handles various pixel formats and performs a blit operation if the source
// format needs to be converted to the destination format. For compressed textures, it ensures
// that the offsets are aligned to the block dimensions.
void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t level,
const uint32_t xoffset, const uint32_t yoffset, const uint32_t zoffset,
const uint32_t width, const uint32_t height, const uint32_t depth,
@@ -812,15 +898,15 @@ void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t
const auto texture{ handleCast<WebGPUTexture>(textureHandle) };
FILAMENT_CHECK_PRECONDITION((texture->getTexture().GetWidth() + xoffset) >= width)
<< "Blitting requires the destination region to have enough width ("
<< texture->getTexture().GetWidth() << " to accommodate the requested " << width
<< texture->getTexture().GetWidth() << ") to accommodate the requested " << width
<< " width to write/blit (accounting for xoffset, which is " << xoffset << ").";
FILAMENT_CHECK_PRECONDITION((texture->getTexture().GetHeight() + yoffset) >= height)
<< "Blitting requires the destination region to have enough height ("
<< texture->getTexture().GetHeight() << " to accommodate the requested " << height
<< texture->getTexture().GetHeight() << ") to accommodate the requested " << height
<< " height to write/blit (accounting for yoffset, which is " << yoffset << ").";
FILAMENT_CHECK_PRECONDITION((texture->getTexture().GetDepthOrArrayLayers() + zoffset) >= depth)
<< "Blitting requires the destination region to have enough depth/arrayLayers ("
<< texture->getTexture().GetDepthOrArrayLayers() << " to accommodate the requested "
<< texture->getTexture().GetDepthOrArrayLayers() << ") to accommodate the requested "
<< depth << " depth to write/blit (accounting for zoffset, which is " << zoffset << ").";
// TODO: Writing to a depth texture is illegal and errors. I'm not sure why Filament is trying
@@ -833,10 +919,8 @@ void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t
inputData->type) };
const wgpu::TextureFormat outputLinearFormat{ toLinearFormat(
texture->getTexture().GetFormat()) };
const bool conversionNecessary{
inputPixelFormat != outputLinearFormat && inputData->type != PixelDataType::COMPRESSED
}; // compressed formats should never need conversion
const bool doBlit{ conversionNecessary };
const bool doBlit{ conversionNecessary(inputPixelFormat, outputLinearFormat, inputData->type) };
#if FWGPU_ENABLED(FWGPU_DEBUG_UPDATE_IMAGE)
if (texture->width > 1000 && texture->height > 500) {
FWGPU_LOGD << "Update3DImage(..., level=" << level << ", xoffset=" << xoffset
@@ -901,7 +985,7 @@ void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t
<< "Failed to create staging input texture for blit?";
const auto copyInfo{ wgpu::TexelCopyTextureInfo{
.texture = stagingTexture,
.mipLevel = level,
.mipLevel = 0,
.origin = { .x = 0, .y = 0, .z = 0 },
.aspect = texture->getAspect(),
} };
@@ -956,9 +1040,8 @@ void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t
mQueue.Submit(1, &blitCommand);
mCommandEncoder = nullptr;
}
stagingTexture.Destroy();
} else {
// not doing blit (copy byte-by-byte)...
// Direct copy without a blit.
const auto copyInfo { wgpu::TexelCopyTextureInfo{
.texture = texture->getTexture(),
.mipLevel = level,
@@ -1070,10 +1153,15 @@ void WebGPUDriver::compilePrograms(CompilerPriorityQueue priority,
}
}
// Begins a render pass. This sets up the render pass descriptor with the appropriate
// color and depth/stencil attachments, based on whether the target is the default
// swap chain render target or a custom one. It also handles MSAA by setting up
// resolve targets if necessary.
void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
RenderPassParams const& params) {
FWGPU_SYSTRACE_SCOPE();
if (!mCommandEncoder) {
wgpu::CommandEncoderDescriptor commandEncoderDescriptor = { .label = "frame_command_encoder" };
wgpu::CommandEncoderDescriptor commandEncoderDescriptor{ .label = "frame_command_encoder" };
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
}
assert_invariant(mCommandEncoder);
@@ -1225,6 +1313,7 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
}
void WebGPUDriver::endRenderPass(int /* dummy */) {
FWGPU_SYSTRACE_SCOPE();
mRenderPassEncoder.End();
mRenderPassEncoder = nullptr;
}
@@ -1233,10 +1322,12 @@ void WebGPUDriver::nextSubpass(int) {
//todo
}
void WebGPUDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain> readSch) {
ASSERT_PRECONDITION_NON_FATAL(drawSch == readSch,
void WebGPUDriver::makeCurrent(Handle<HwSwapChain> drawSwapChain,
Handle<HwSwapChain> readSwapChain) {
FWGPU_SYSTRACE_SCOPE();
ASSERT_PRECONDITION_NON_FATAL(drawSwapChain == readSwapChain,
"WebGPU driver does not support distinct draw/read swap chains.");
auto swapChain = handleCast<WebGPUSwapChain>(drawSch);
auto swapChain = handleCast<WebGPUSwapChain>(drawSwapChain);
mSwapChain = swapChain;
assert_invariant(mSwapChain);
@@ -1249,13 +1340,16 @@ void WebGPUDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain>
assert_invariant(mTextureView);
if (!mCommandEncoder) {
wgpu::CommandEncoderDescriptor commandEncoderDescriptor = { .label = "frame_command_encoder" };
wgpu::CommandEncoderDescriptor commandEncoderDescriptor = {
.label = "frame_command_encoder"
};
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
}
assert_invariant(mCommandEncoder);
}
void WebGPUDriver::commit(Handle<HwSwapChain> sch) {
void WebGPUDriver::commit(Handle<HwSwapChain> swapChainHandle) {
FWGPU_SYSTRACE_SCOPE();
if (UTILS_VERY_UNLIKELY(!mCommandEncoder)) {
// nothing to submit
if (UTILS_LIKELY(mSwapChain)) {
@@ -1306,34 +1400,41 @@ void WebGPUDriver::insertEventMarker(char const* string) {
}
void WebGPUDriver::pushGroupMarker(char const* string) {
FWGPU_SYSTRACE_CONTEXT();
FWGPU_SYSTRACE_START(string);
}
void WebGPUDriver::popGroupMarker(int /* dummy */) {
FWGPU_SYSTRACE_CONTEXT();
FWGPU_SYSTRACE_END();
}
void WebGPUDriver::startCapture(int /* dummy */) {
//todo
}
void WebGPUDriver::popGroupMarker(int) {
//todo
}
void WebGPUDriver::startCapture(int) {
//todo
}
void WebGPUDriver::stopCapture(int) {
void WebGPUDriver::stopCapture(int /* dummy */) {
//todo
}
// Reads a block of pixels from a render target into a buffer.
// This function is asynchronous. It copies the pixel data to a staging buffer on the GPU,
// and then maps the buffer for reading on the CPU. The provided callback is invoked when the
// data is ready. This function also handles the 256-byte row alignment requirement for
// buffer-to-texture copies in WebGPU.
void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, const uint32_t x,
const uint32_t y, const uint32_t width, const uint32_t height,
PixelBufferDescriptor&& pixelBufferDescriptor) {
auto srcTarget = handleCast<WebGPURenderTarget>(sourceRenderTargetHandle);
const auto srcTarget{ handleCast<WebGPURenderTarget>(sourceRenderTargetHandle) };
assert_invariant(srcTarget);
wgpu::Texture srcTexture = nullptr;
wgpu::Texture srcTexture{ nullptr };
if (srcTarget->isDefaultRenderTarget()) {
assert_invariant(mSwapChain);
srcTexture = mSwapChain->getCurrentTexture();
} else {
// Handle custom render targets. Read from the first color attachment.
const auto& colorAttachmentInfos = srcTarget->getColorAttachmentInfos();
const auto& colorAttachmentInfos{ srcTarget->getColorAttachmentInfos() };
// TODO we are currently assuming the first attachment is the desired texture.
if (colorAttachmentInfos[0].handle) {
auto texture = handleCast<WebGPUTexture>(colorAttachmentInfos[0].handle);
@@ -1348,116 +1449,172 @@ void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, c
scheduleDestroy(std::move(pixelBufferDescriptor));
return;
}
const uint32_t srcWidth = srcTexture.GetWidth();
const uint32_t srcHeight = srcTexture.GetHeight();
const uint32_t srcWidth {srcTexture.GetWidth()};
const uint32_t srcHeight{srcTexture.GetHeight()};
// Clamp read region to texture bounds
if (x >= srcWidth || y >= srcHeight) {
if (UTILS_UNLIKELY(x >= srcWidth || y >= srcHeight)) {
scheduleDestroy(std::move(pixelBufferDescriptor));
return;
}
auto actualWidth = std::min(width, srcWidth - x);
auto actualHeight = std::min(height, srcHeight - y);
auto actualWidth{ std::min(width, srcWidth - x) };
auto actualHeight{ std::min(height, srcHeight - y)};
if (UTILS_UNLIKELY(actualWidth == 0 || actualHeight == 0)) {
scheduleDestroy(std::move(pixelBufferDescriptor));
return;
}
// Once we're ready to read the pixels, we need to flush all the previous work of this frame.
// This ensures that the readPixels will be ordered after all the draws.
flush();
if (!mCommandEncoder) {
const wgpu::CommandEncoderDescriptor commandEncoderDescriptor{
.label = "read_pixels_command",
};
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
FILAMENT_CHECK_POSTCONDITION(mCommandEncoder)
<< "Failed to create command encoder for readPixels?";
}
const size_t dstBytesPerPixel = PixelBufferDescriptor::computePixelSize(
pixelBufferDescriptor.format, pixelBufferDescriptor.type);
const size_t srcBytesPerPixel = getWGPUTextureFormatPixelSize(srcTexture.GetFormat());
const wgpu::TextureFormat srcFormat{srcTexture.GetFormat()};
const wgpu::TextureFormat dstFormat{toWebGPUFormat(pixelBufferDescriptor.format,
pixelBufferDescriptor.type)};
wgpu::Texture textureToReadFrom{srcTexture};
wgpu::Texture stagingTexture{ nullptr };
uint32_t readX{x};
uint32_t readY{y};
FILAMENT_CHECK_PRECONDITION(dstBytesPerPixel == srcBytesPerPixel && dstBytesPerPixel > 0)
<< "Source texture pixel size (" << srcBytesPerPixel
<< ") does not match destination pixel buffer pixel size (" << dstBytesPerPixel
<< "), or the format is not supported for readPixels.";
// If the source format is different from the destination (e.g. BGRA vs RGBA),
// we need to perform a conversion using an intermediate blit.
if (conversionNecessary(srcFormat, dstFormat, pixelBufferDescriptor.type)) {
const wgpu::TextureDescriptor stagingDescriptor{
.label = "readpixels_staging_texture",
.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment,
.dimension = wgpu::TextureDimension::e2D, // are we sure we can assume this? thus far we have
.size = {
.width = actualWidth,
.height = actualHeight,
.depthOrArrayLayers = 1,
},
.format = dstFormat,
.mipLevelCount = 1,
.sampleCount = srcTexture.GetSampleCount(),
};
stagingTexture = mDevice.CreateTexture(&stagingDescriptor);
assert_invariant(stagingTexture);
textureToReadFrom = stagingTexture;
const WebGPUBlitter::BlitArgs blitArgs{
.source = {
.texture = srcTexture,
.origin = {.x = x, .y = y,},
.extent = {
.width = actualWidth,
.height = actualHeight,
},
},
.destination = {
.texture = stagingTexture,
.origin = {.x = 0, .y = 0},
.extent = {
.width = actualWidth,
.height = actualHeight,
},
},
.filter = SamplerMagFilter::NEAREST, // Use NEAREST for a 1:1 copy
};
mBlitter.blit(mQueue, mCommandEncoder, blitArgs);
// Create a staging buffer to copy the texture to. WebGPU requires 256 byte alignment
const size_t bytesPerPixel = dstBytesPerPixel;
const size_t unpaddedBytesPerRow = actualWidth * bytesPerPixel;
const size_t alignment = 256;
const size_t paddedBytesPerRow = (unpaddedBytesPerRow + alignment - 1) & ~(alignment - 1);
// The subsequent read will be from the top-left of the new intermediate texture.
readX = 0;
readY = 0;
}
size_t bufferSize = paddedBytesPerRow * height;
wgpu::BufferDescriptor bufferDesc;
bufferDesc.size = bufferSize;
bufferDesc.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead;
wgpu::Buffer stagingBuffer = mDevice.CreateBuffer(&bufferDesc);
// Create a staging buffer to copy the texture to. WebGPU requires 256 byte alignment for buffer-to-texture copies.
const size_t bytesPerPixel{ PixelBufferDescriptor::computePixelSize(
pixelBufferDescriptor.format, pixelBufferDescriptor.type) };
const size_t unpaddedBytesPerRow{ actualWidth * bytesPerPixel };
const size_t alignment{ 256 };
const size_t paddedBytesPerRow = { (unpaddedBytesPerRow + alignment - 1) & ~(alignment - 1) };
const size_t bufferSize{ paddedBytesPerRow * actualHeight };
const wgpu::BufferDescriptor bufferDesc{
.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead,
.size = bufferSize,
};
wgpu::Buffer stagingBuffer{ mDevice.CreateBuffer(&bufferDesc) };
assert_invariant(stagingBuffer);
wgpu::CommandEncoder encoder = mDevice.CreateCommandEncoder();
assert_invariant(encoder);
// WebGPU's texture coordinates for copies are top-left, but Filament's y-coordinate is
// bottom-left. We must flip the y-coordinate relative to the texture we are reading from.
const uint32_t textureHeight{ textureToReadFrom.GetHeight() };
const uint32_t flippedY{ textureHeight - readY - actualHeight };
// WebGPU flips the y axis like Metal does
const uint32_t flippedY = srcHeight - y - height;
wgpu::TexelCopyTextureInfo source{
.texture = srcTexture,
.mipLevel = 0,
.origin = { .x = x, .y = flippedY, .z = 0 },
const wgpu::TexelCopyTextureInfo source{
.texture = textureToReadFrom, // Read from the original or intermediate texture
.mipLevel = 0,
.origin = {.x = readX, .y = flippedY, .z = 0,},
};
wgpu::TexelCopyBufferInfo destination {
.layout = {
.offset = 0,
.bytesPerRow = static_cast<uint32_t>(paddedBytesPerRow),
.rowsPerImage = actualHeight,
},
.buffer = stagingBuffer
const wgpu::TexelCopyBufferInfo destination{
.layout = {
.offset = 0,
.bytesPerRow = static_cast<uint32_t>(paddedBytesPerRow),
.rowsPerImage = actualHeight,
},
.buffer = stagingBuffer
};
wgpu::Extent3D copySize{ .width = actualWidth,
const wgpu::Extent3D copySize{
.width = actualWidth,
.height = actualHeight,
.depthOrArrayLayers = 1 };
// TODO consider removing encoder finish + submit once we have test coverage and can
// safely verify doing so will not result in strange errors. We don't think this
// submit is necessary, as the encoder should get finished and the command
// submitted in commit(), finish(), and/or flush() in this frame
// or the next anyway. And, the read/copy bits will be encoded before other things.
// Thus, not submitting here _seems_ ok. Nonetheless, doing the submit here likely
// represents unnecessary inefficiency and higher bandwidth
// between the CPU and GPU, but we are worried about functional correctness at this
// stage.
encoder.CopyTextureToBuffer(&source, &destination, &copySize);
wgpu::CommandBuffer commandBuffer = encoder.Finish();
mQueue.Submit(1, &commandBuffer);
.depthOrArrayLayers = 1,
};
mCommandEncoder.CopyTextureToBuffer(&source, &destination, &copySize);
mCommandBuffer = mCommandEncoder.Finish();
assert_invariant(mCommandBuffer);
mCommandEncoder = nullptr;
mQueue.Submit(1, &mCommandBuffer);
mCommandBuffer = nullptr;
// Map the buffer to read the data
struct UserData {
struct UserData final {
PixelBufferDescriptor pixelBufferDescriptor;
wgpu::Buffer buffer;
size_t unpaddedBytesPerRow;
size_t paddedBytesPerRow;
uint32_t height;
WebGPUDriver* driver;
WebGPUDriver *driver;
};
auto userData = std::make_unique<UserData>(UserData{
.pixelBufferDescriptor = std::move(pixelBufferDescriptor),
.buffer = std::move(stagingBuffer),
.unpaddedBytesPerRow = unpaddedBytesPerRow,
.paddedBytesPerRow = paddedBytesPerRow,
.height = height,
.driver = this,
.pixelBufferDescriptor = std::move(pixelBufferDescriptor),
.buffer = std::move(stagingBuffer),
.unpaddedBytesPerRow = unpaddedBytesPerRow,
.paddedBytesPerRow = paddedBytesPerRow,
.height = actualHeight,
.driver = this,
});
mReadPixelMapsCounter.startTask();
userData->buffer.MapAsync(
wgpu::MapMode::Read, 0, bufferSize, wgpu::CallbackMode::AllowSpontaneous,
[](wgpu::MapAsyncStatus status, const char* message, UserData* userdata) {
std::unique_ptr<UserData> data(static_cast<UserData*>(userdata));
if (UTILS_LIKELY(status == wgpu::MapAsyncStatus::Success)) {
const char* src = static_cast<const char*>(
data->buffer.GetConstMappedRange(0, data->buffer.GetSize()));
char* dst = static_cast<char*>(data->pixelBufferDescriptor.buffer);
for (uint32_t i = 0; i < data->height; ++i) {
memcpy(dst + i * data->unpaddedBytesPerRow,
src + i * data->paddedBytesPerRow, data->unpaddedBytesPerRow);
const char *src {static_cast<const char *>(
data->buffer.GetConstMappedRange(0, data->buffer.GetSize()))};
char* dst{ static_cast<char*>(data->pixelBufferDescriptor.buffer) };
// If padding was added for alignment, we need to copy row by row.
if (data->paddedBytesPerRow == data->unpaddedBytesPerRow) {
memcpy(dst, src, data->unpaddedBytesPerRow * data->height);
} else {
for (uint32_t i{ 0 }; i < data->height; ++i) {
memcpy(dst + i * data->unpaddedBytesPerRow,
src + i * data->paddedBytesPerRow, data->unpaddedBytesPerRow);
}
}
data->buffer.Unmap();
} else {
FWGPU_LOGE << "Failed to map staging buffer for readPixels: " << message;
}
data->driver->scheduleDestroy(std::move(data->pixelBufferDescriptor));
data->driver->mReadPixelMapsCounter.finishTask();
},
userData.release());
}
@@ -1472,14 +1629,99 @@ void WebGPUDriver::blitDEPRECATED(TargetBufferFlags buffers,
Handle<HwRenderTarget> destinationRenderTargetHandle, const Viewport destinationViewport,
Handle<HwRenderTarget> sourceRenderTargetHandle, const Viewport sourceViewport,
const SamplerMagFilter filter) {
PANIC_PRECONDITION("WebGPUDriver::blitDEPRECATED not supported");
FWGPU_SYSTRACE_SCOPE();
auto const sourceTarget{ handleCast<WebGPURenderTarget>(sourceRenderTargetHandle) };
auto const destinationTarget{ handleCast<WebGPURenderTarget>(destinationRenderTargetHandle) };
assert_invariant(sourceTarget && destinationTarget);
FILAMENT_CHECK_PRECONDITION(buffers == TargetBufferFlags::COLOR0)
<< "blitDEPRECATED only supports COLOR0";
FILAMENT_CHECK_PRECONDITION(sourceViewport.left >= 0 && sourceViewport.bottom >= 0 &&
destinationViewport.left >= 0 && destinationViewport.bottom >= 0)
<< "Source and destination viewports must be positive.";
// We always blit from/to the COLOR0 attachment.
auto sourceAttachment {sourceTarget->getColorAttachmentInfos()[0]};
Handle<HwTexture> sourceTextureHandle{ sourceAttachment.handle };
auto destinationAttachment {destinationTarget->getColorAttachmentInfos()[0]};
Handle<HwTexture> destinationTextureHandle{
destinationAttachment.handle
};
if (UTILS_UNLIKELY(!sourceTextureHandle || !destinationTextureHandle)) {
FWGPU_LOGE << "blitDEPRECATED could not find a valid color attachment to blit.";
return;
}
// WebGPU's texture coordinates are top-left, while Filament's are bottom-left.
// We need to flip the y-coordinate for both source and destination.
auto const sourceTexture{ handleCast<WebGPUTexture>(sourceTextureHandle) };
auto const destinationTexture{ handleCast<WebGPUTexture>(destinationTextureHandle) };
const uint32_t sourceTextureHeight{ sourceTexture->height };
const uint32_t flippedSourceY{ sourceTextureHeight - sourceViewport.bottom -
sourceViewport.height };
const uint32_t destinationTextureHeight{ destinationTexture->height };
const uint32_t flippedDestinationY{ destinationTextureHeight - destinationViewport.bottom -
destinationViewport.height };
const wgpu::Origin2D sourceOrigin{ static_cast<uint32_t>(sourceViewport.left), flippedSourceY };
const wgpu::Origin2D destinationOrigin{ static_cast<uint32_t>(destinationViewport.left),
flippedDestinationY };
const wgpu::Extent2D sourceSize{ static_cast<uint32_t>(sourceViewport.width),
static_cast<uint32_t>(sourceViewport.height) };
const wgpu::Extent2D destinationSize{ static_cast<uint32_t>(destinationViewport.width),
static_cast<uint32_t>(destinationViewport.height) };
bool reusedCommandEncoder{ true };
if (mCommandEncoder) {
flush();
} else {
reusedCommandEncoder = false;
const wgpu::CommandEncoderDescriptor desc{ .label = "blit_deprecated_command" };
mCommandEncoder = mDevice.CreateCommandEncoder(&desc);
}
const WebGPUBlitter::BlitArgs blitArgs{
.source = { .texture = sourceTexture->getTexture(),
.aspect = sourceTexture->getAspect(),
.origin = sourceOrigin,
.extent = sourceSize,
.mipLevel = sourceAttachment.level,
.layerOrDepth = sourceAttachment.layer,
},
.destination = { .texture = destinationTexture->getTexture(),
.aspect = destinationTexture->getAspect(),
.origin = destinationOrigin,
.extent = destinationSize,
.mipLevel = destinationAttachment.level,
.layerOrDepth = destinationAttachment.layer,
},
.filter = filter,
};
mBlitter.blit(mQueue, mCommandEncoder, blitArgs);
if (!reusedCommandEncoder) {
const wgpu::CommandBufferDescriptor desc{ .label = "blit_deprecated_command_buffer" };
const wgpu::CommandBuffer blitCommand{ mCommandEncoder.Finish(&desc) };
mQueue.Submit(1, &blitCommand);
mCommandEncoder = nullptr;
}
}
void WebGPUDriver::resolve(Handle<HwTexture> destinationTextureHandle, const uint8_t sourceLevel,
const uint8_t sourceLayer, Handle<HwTexture> sourceTextureHandle,
const uint8_t destinationLevel, const uint8_t destinationLayer) {
FILAMENT_CHECK_PRECONDITION(mCommandEncoder)
<< "Resolve assumes there is a valid command encoder to piggyback on.";
FWGPU_SYSTRACE_SCOPE();
if (!mCommandEncoder) {
const wgpu::CommandEncoderDescriptor encoderDescriptor{
.label = "command_created_with_resolve",
};
mCommandEncoder = mDevice.CreateCommandEncoder(&encoderDescriptor);
}
FILAMENT_CHECK_PRECONDITION(mRenderPassEncoder == nullptr)
<< "Resolve cannot be called during an existing render pass";
const auto sourceTexture{ handleCast<WebGPUTexture>(sourceTextureHandle) };
@@ -1511,6 +1753,7 @@ void WebGPUDriver::blit(Handle<HwTexture> destinationTextureHandle, const uint8_
const uint8_t sourceLayer, const math::uint2 destinationOrigin,
Handle<HwTexture> sourceTextureHandle, const uint8_t destinationLevel,
const uint8_t destinationLayer, const math::uint2 sourceOrigin, const math::uint2 size) {
FWGPU_SYSTRACE_SCOPE();
bool reusedCommandEncoder{ true };
if (mCommandEncoder) {
// make sure command elements (draws, etc.) prior to this blit are processed before the blit
@@ -1569,6 +1812,7 @@ void WebGPUDriver::blit(Handle<HwTexture> destinationTextureHandle, const uint8_
}
void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
FWGPU_SYSTRACE_SCOPE();
assert_invariant(mRenderPassEncoder);
const auto program{ handleCast<WebGPUProgram>(pipelineState.program) };
assert_invariant(program);
@@ -1605,13 +1849,10 @@ void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
};
wgpu::TextureFormat depthStencilFormat{ wgpu::TextureFormat::Undefined };
if (renderTarget->isDefaultRenderTarget()) {
// default render target color(s) (one)...
colorFormatCount = 1;
colorFormats[0] = mSwapChain->getColorFormat();
// default render target depth/stencil...
depthStencilFormat = mSwapChain->getDepthFormat();
} else {
// custom render target color(s)...
MRT const& mrtColorAttachments{ mCurrentRenderTarget->getColorAttachmentInfos() };
for (size_t i{ 0 }; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; ++i) {
if (mrtColorAttachments[i].handle) {
@@ -1660,6 +1901,7 @@ void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
}
void WebGPUDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> renderPrimitiveHandle) {
FWGPU_SYSTRACE_SCOPE();
const auto renderPrimitive = handleCast<WebGPURenderPrimitive>(renderPrimitiveHandle);
const auto vertexBufferInfo = handleCast<WebGPUVertexBufferInfo>(
renderPrimitive->vertexBuffer->getVertexBufferInfoHandle());
@@ -1677,7 +1919,8 @@ void WebGPUDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> renderPrimitive
void WebGPUDriver::draw2(const uint32_t indexOffset, const uint32_t indexCount,
const uint32_t instanceCount) {
// We defer actually binding until we actually draw
FWGPU_SYSTRACE_SCOPE();
// Bind groups are deferred until the actual draw call.
for (size_t i = 0; i < MAX_DESCRIPTOR_SET_COUNT; i++) {
auto& binding = mCurrentDescriptorSets[i];
if (binding.bindGroup) {
@@ -1723,6 +1966,7 @@ void WebGPUDriver::resetState(int) {
void WebGPUDriver::updateDescriptorSetBuffer(Handle<HwDescriptorSet> descriptorSetHandle,
const backend::descriptor_binding_t binding, Handle<HwBufferObject> bufferObjectHandle,
const uint32_t offset, const uint32_t size) {
FWGPU_SYSTRACE_SCOPE();
const auto bindGroup = handleCast<WebGPUDescriptorSet>(descriptorSetHandle);
const auto buffer = handleCast<WebGPUBufferObject>(bufferObjectHandle);
if (!bindGroup->getIsLocked()) {
@@ -1743,14 +1987,13 @@ void WebGPUDriver::updateDescriptorSetBuffer(Handle<HwDescriptorSet> descriptorS
void WebGPUDriver::updateDescriptorSetTexture(Handle<HwDescriptorSet> descriptorSetHandle,
const backend::descriptor_binding_t binding, Handle<HwTexture> textureHandle,
const SamplerParams params) {
FWGPU_SYSTRACE_SCOPE();
auto bindGroup = handleCast<WebGPUDescriptorSet>(descriptorSetHandle);
auto texture = handleCast<WebGPUTexture>(textureHandle);
if (!bindGroup->getIsLocked()) {
// Dawn will cache duplicate samplers, so we don't strictly need to maintain a cache.
// Making a cache might save us minor perf by reducing param translation
// Dawn will cache duplicate samplers, so we don't strictly need to maintain a cache here.
const auto sampler = makeSampler(params);
// TODO making assumptions that size and offset mean the same thing here.
wgpu::BindGroupEntry tEntry{
.binding = static_cast<uint32_t>(binding * 2),
.textureView = texture->getDefaultTextureView() };
@@ -1784,7 +2027,7 @@ void WebGPUDriver::bindDescriptorSet(Handle<HwDescriptorSet> descriptorSetHandle
const backend::descriptor_set_t setIndex, backend::DescriptorSetOffsetArray&& offsets) {
assert_invariant(setIndex < MAX_DESCRIPTOR_SET_COUNT);
// An empty handle signifies we need to release this bind point
// An empty handle unbinds the descriptor set.
if (!descriptorSetHandle) {
mCurrentDescriptorSets[setIndex] = {};
return;
@@ -1914,4 +2157,4 @@ wgpu::AddressMode WebGPUDriver::fWrapModeToWAddressMode(const SamplerWrapMode& f
return wgpu::AddressMode::Undefined;
}
} // namespace filament
} // namespace filament::backend

View File

@@ -24,6 +24,7 @@
#include "webgpu/WebGPUPipelineCache.h"
#include "webgpu/WebGPUPipelineLayoutCache.h"
#include "webgpu/WebGPURenderPassMipmapGenerator.h"
#include "webgpu/utils/AsyncTaskCounter.h"
#include <backend/platforms/WebGPUPlatform.h>
#include "DriverBase.h"
@@ -47,8 +48,12 @@
namespace filament::backend {
class WebGPUSwapChain;
/**
* WebGPU backend (driver) implementation
* Implements the private backend driver API for WebGPU specifically (that API is essentially
* expressed in private/backend/DriverAPI.inc)
*
* It manages all the WebGPU resources necessary to accomplish this.
*/
class WebGPUDriver final : public DriverBase {
public:
@@ -64,8 +69,8 @@ private:
[[nodiscard]] wgpu::Sampler makeSampler(SamplerParams const& params);
[[nodiscard]] static wgpu::AddressMode fWrapModeToWAddressMode(const filament::backend::SamplerWrapMode& fUsage);
// the platform (e.g. OS) specific aspects of the WebGPU backend are strictly only
// handled in the WebGPUPlatform
// The platform (e.g. OS) specific aspects of the WebGPU backend are strictly only
// handled in the WebGPUPlatform.
WebGPUPlatform& mPlatform;
wgpu::Adapter mAdapter = nullptr;
wgpu::Device mDevice = nullptr;
@@ -86,6 +91,7 @@ private:
spd::MipmapGenerator mSpdComputePassMipmapGenerator;
WebGPUMsaaTextureResolver mMsaaTextureResolver{};
WebGPUBlitter mBlitter;
webgpuutils::AsyncTaskCounter mReadPixelMapsCounter{};
struct DescriptorSetBindingInfo{
wgpu::BindGroup bindGroup;

View File

@@ -28,12 +28,11 @@ namespace filament::backend {
FenceStatus WebGPUFence::getStatus() { return mStatus.load(); }
void WebGPUFence::addMarkerToQueueState(wgpu::Queue const& queue) {
// The lambda function is called when the work is done. It updates the fence status based on the
// result of the work.
queue.OnSubmittedWorkDone(
wgpu::CallbackMode::AllowSpontaneous,
[this](const wgpu::QueueWorkDoneStatus status, wgpu::StringView message) {
// Note: The 'message' parameter is required by the function signature
// but can be ignored if not needed.
switch (status) {
case wgpu::QueueWorkDoneStatus::Success:
mStatus.store(FenceStatus::CONDITION_SATISFIED);

View File

@@ -28,6 +28,11 @@ class Queue;
namespace filament::backend {
/**
* A WebGPU-specific implementation of the HwFence.
* This class is used to synchronize the CPU and GPU. It allows the CPU to know when the GPU has
* finished a set of commands.
*/
class WebGPUFence final : public HwFence {
public:
[[nodiscard]] FenceStatus getStatus();
@@ -35,6 +40,9 @@ public:
void addMarkerToQueueState(wgpu::Queue const&);
private:
// The current status of the fence.
// This is atomic because it can be updated by a WebGPU callback thread and read from the main
// thread.
std::atomic<FenceStatus> mStatus{ FenceStatus::TIMEOUT_EXPIRED };
};

View File

@@ -30,6 +30,10 @@ enum class IndexFormat : uint32_t;
namespace filament::backend {
/**
* A WebGPU implementation of the HwIndexBuffer.
* This class represents a GPU buffer that stores indices for indexed drawing.
*/
class WebGPUIndexBuffer final : public HwIndexBuffer, public WebGPUBufferBase {
public:
WebGPUIndexBuffer(wgpu::Device const&, uint8_t elementSize, uint32_t indexCount);

View File

@@ -48,8 +48,8 @@ void resolveColorTextures(wgpu::CommandEncoder const& commandEncoder,
const wgpu::RenderPassEncoder renderPassEncoder{ commandEncoder.BeginRenderPass(
&renderPassDescriptor) };
FILAMENT_CHECK_POSTCONDITION(renderPassEncoder)
<< "Failed to create wgpu::RenderPassEncoder for WebGPUDriver::resolve";
renderPassEncoder.End(); // only the implicit resolve is happening in the pass
<< "Failed to create wgpu::RenderPassEncoder for MSAA resolve.";
renderPassEncoder.End(); // The resolve is implicit in the pass.
}
} // namespace
@@ -59,30 +59,29 @@ void WebGPUMsaaTextureResolver::resolve(ResolveRequest const& request) {
ResolveRequest::TextureInfo const& destination{ request.destination };
FILAMENT_CHECK_PRECONDITION(destination.texture.GetWidth() == source.texture.GetWidth() &&
destination.texture.GetHeight() == source.texture.GetHeight())
<< "invalid resolve: source and destination sizes don't match";
<< "Invalid resolve: source and destination sizes don't match.";
FILAMENT_CHECK_PRECONDITION(
source.texture.GetSampleCount() > 1 && destination.texture.GetSampleCount() == 1)
<< "invalid resolve: source.samples=" << source.texture.GetSampleCount()
<< "Invalid resolve: source.samples=" << source.texture.GetSampleCount()
<< ", destination.samples=" << destination.texture.GetSampleCount();
FILAMENT_CHECK_PRECONDITION(source.texture.GetFormat() == destination.texture.GetFormat())
<< "source and destination texture format don't match";
<< "Source and destination texture format don't match.";
const wgpu::TextureFormat format{ source.texture.GetFormat() };
FILAMENT_CHECK_PRECONDITION(!hasDepth(format)) << "can't resolve depth formats";
FILAMENT_CHECK_PRECONDITION(!hasStencil(format)) << "can't resolve stencil formats";
FILAMENT_CHECK_PRECONDITION(!hasDepth(format)) << "Can't resolve depth formats.";
FILAMENT_CHECK_PRECONDITION(!hasStencil(format)) << "Can't resolve stencil formats.";
FILAMENT_CHECK_PRECONDITION(source.texture.GetUsage() & wgpu::TextureUsage::RenderAttachment)
<< "source texture usage doesn't have wgpu::TextureUsage::RenderAttachment";
<< "Source texture usage doesn't have wgpu::TextureUsage::RenderAttachment.";
FILAMENT_CHECK_PRECONDITION(
destination.texture.GetUsage() & wgpu::TextureUsage::RenderAttachment)
<< "destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment";
<< "Destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment.";
FILAMENT_CHECK_PRECONDITION(destination.texture.GetUsage() & wgpu::TextureUsage::TextureBinding)
<< "destination texture usage doesn't have wgpu::TextureUsage::TextureBinding";
<< "Destination texture usage doesn't have wgpu::TextureUsage::TextureBinding.";
const wgpu::TextureViewDescriptor sourceTextureViewDescriptor{
.label = "resolve_source_texture_view",
@@ -116,7 +115,7 @@ void WebGPUMsaaTextureResolver::resolve(ResolveRequest const& request) {
<< "Failed to create wgpu::TextureView destinationTextureView.";
if (hasDepth(format)) {
PANIC_PRECONDITION("DEPTH RESOLVE NOT IMPLEMENTED YET");
PANIC_PRECONDITION("Depth resolve not yet implemented.");
} else {
resolveColorTextures(request.commandEncoder, sourceTextureView, destinationTextureView);
}

View File

@@ -31,6 +31,10 @@ namespace filament::backend {
class WebGPUTexture;
/**
* A utility class for resolving multisampled textures in WebGPU.
* This is necessary because WebGPU does not have an implicit resolve step like other APIs.
*/
class WebGPUMsaaTextureResolver final {
public:
struct ResolveRequest final {
@@ -47,6 +51,9 @@ public:
TextureInfo destination;
};
/**
* Resolves a multisampled texture into a single-sampled texture.
*/
void resolve(ResolveRequest const&);
};

View File

@@ -54,7 +54,7 @@ namespace {
case CullingMode::FRONT: return wgpu::CullMode::Front;
case CullingMode::BACK: return wgpu::CullMode::Back;
case CullingMode::FRONT_AND_BACK:
// no WegGPU equivalent of front and back
// WebGPU does not support culling both front and back faces simultaneously.
FILAMENT_CHECK_POSTCONDITION(false)
<< "WebGPU does not support CullingMode::FRONT_AND_BACK";
return wgpu::CullMode::Undefined;
@@ -220,7 +220,7 @@ wgpu::RenderPipeline WebGPUPipelineCache::createRenderPipeline(
const bool requestedDepth{ any(request.targetRenderFlags & TargetBufferFlags::DEPTH) };
const bool requestedStencil{ any(request.targetRenderFlags & TargetBufferFlags::STENCIL) };
const bool depthOrStencilRequested{ requestedDepth || requestedStencil };
// depth/stencil...
if (depthOrStencilRequested) {
FILAMENT_CHECK_PRECONDITION(request.depthStencilFormat != wgpu::TextureFormat::Undefined)
<< "Depth or Stencil requested for pipeline, but depthStencilFormat is "
@@ -312,6 +312,13 @@ wgpu::RenderPipeline WebGPUPipelineCache::createRenderPipeline(
},
.fragment = nullptr // will add below if fragment module is included
};
// TODO:
if (pipelineDescriptor.primitive.topology == wgpu::PrimitiveTopology::LineStrip ||
pipelineDescriptor.primitive.topology == wgpu::PrimitiveTopology::TriangleStrip) {
PANIC_POSTCONDITION("stripIndexFormat must be set for strip topologies. "
"This needs to be plumbed through from the RenderPrimitive.");
}
wgpu::FragmentState fragmentState = {};
const wgpu::BlendState blendState {
.color = {
@@ -365,6 +372,7 @@ bool WebGPUPipelineCache::RenderPipelineKeyEqual::operator()(RenderPipelineKey c
}
void WebGPUPipelineCache::removeExpiredPipelines() {
FWGPU_SYSTRACE_SCOPE();
using Iterator = decltype(mRenderPipelines)::const_iterator;
for (Iterator iterator{ mRenderPipelines.begin() }; iterator != mRenderPipelines.end();) {
RenderPipelineCacheEntry const& entry{ iterator.value() };

View File

@@ -34,6 +34,11 @@
namespace filament::backend {
/**
* A cache for WebGPU render pipelines.
* This class is responsible for creating and caching wgpu::RenderPipeline objects to avoid
* expensive pipeline creation at runtime.
*/
class WebGPUPipelineCache final {
public:
struct RenderPipelineRequest final {
@@ -63,6 +68,9 @@ public:
[[nodiscard]] wgpu::RenderPipeline const& getOrCreateRenderPipeline(
RenderPipelineRequest const&);
/**
* Should be called at the end of each frame to perform cache maintenance.
*/
void onFrameEnd();
private:

View File

@@ -77,26 +77,27 @@ wgpu::PipelineLayout WebGPUPipelineLayoutCache::createPipelineLayout(
};
const wgpu::PipelineLayout layout{ mDevice.CreatePipelineLayout(&descriptor) };
FILAMENT_CHECK_POSTCONDITION(layout)
<< "Failed to create pipeline layout " << descriptor.label << "?";
<< "Failed to create pipeline layout " << descriptor.label << ".";
return layout;
}
bool WebGPUPipelineLayoutCache::PipelineLayoutKeyEqual::operator()(PipelineLayoutKey const& key1,
PipelineLayoutKey const& key2) const {
// Compare the raw bytes of the keys for equality.
return 0 == memcmp(reinterpret_cast<void const*>(&key1), reinterpret_cast<void const*>(&key2),
sizeof(key1));
}
void WebGPUPipelineLayoutCache::removeExpiredPipelineLayouts() {
FWGPU_SYSTRACE_SCOPE();
using Iterator = decltype(mPipelineLayouts)::const_iterator;
for (Iterator iterator{ mPipelineLayouts.begin() }; iterator != mPipelineLayouts.end();) {
PipelineLayoutCacheEntry const& entry{ iterator.value() };
if (mFrameCount > (entry.lastUsedFrameCount +
FILAMENT_WEBGPU_PIPELINE_LAYOUT_EXPIRATION_IN_FRAME_COUNT)) {
// pipeline layout expired...
// The pipeline layout has not been used recently, so we can remove it from the cache.
iterator = mPipelineLayouts.erase(iterator);
} else {
// pipeline layout not yet expired...
++iterator;
}
}

View File

@@ -31,6 +31,11 @@
namespace filament::backend {
/**
* A cache for WebGPU pipeline layouts.
* This class is responsible for creating and caching wgpu::PipelineLayout objects to avoid
* expensive pipeline layout creation at runtime.
*/
class WebGPUPipelineLayoutCache final {
public:
struct PipelineLayoutRequest final {
@@ -45,9 +50,16 @@ public:
WebGPUPipelineLayoutCache& operator=(WebGPUPipelineLayoutCache const&) = delete;
WebGPUPipelineLayoutCache& operator=(WebGPUPipelineLayoutCache const&&) = delete;
/**
* Retrieves a pipeline layout from the cache or creates a new one if it doesn't exist.
* @return A constant reference to the cached or newly created pipeline layout.
*/
[[nodiscard]] wgpu::PipelineLayout const& getOrCreatePipelineLayout(
PipelineLayoutRequest const&);
/**
* Should be called at the end of each frame to perform cache maintenance.
*/
void onFrameEnd();
private:
@@ -59,10 +71,10 @@ private:
* instances, single bytes for booleans etc.), trivial copying and comparison (byte by byte),
* and a word-aligned structure with a size in bytes as a multiple of 4 (for murmer hash).
*/
struct PipelineLayoutKey final { // size : offset (need multiples of 4 bytes for hashing)
WGPUBindGroupLayout bindGroupLayoutHandles[MAX_DESCRIPTOR_SET_COUNT]{ nullptr }; // 32 : 0
uint8_t bindGroupLayoutCount{ 0 }; // 1 : 32
uint8_t padding[7]{ 0 }; // 7 : 33
struct PipelineLayoutKey final {
WGPUBindGroupLayout bindGroupLayoutHandles[MAX_DESCRIPTOR_SET_COUNT]{ nullptr }; // 32 :0
uint8_t bindGroupLayoutCount{ 0 }; // 1 :32
uint8_t padding[7]{ 0 }; // 7 :33
};
static_assert(sizeof(PipelineLayoutKey) == 40,
"PipelineLayoutKey must not have implicit padding.");

View File

@@ -89,7 +89,7 @@ namespace {
std::string_view(sourceData + posAfterId, posEndOfStatement - posAfterId);
size_t posOfEqual = statementSegment.find('=');
if (posOfEqual == std::string::npos) {
// not an assignment statement, so stream to the end of the statement and continue...
// Not an assignment, so we don't need to replace it.
processedShaderSource << std::string_view(sourceData + pos,
posEndOfStatement + 1 - pos);
pos = posEndOfStatement + 1;
@@ -122,10 +122,7 @@ namespace {
}
const auto newValueItr = specConstants.find(static_cast<uint32_t>(constantId));
if (newValueItr == specConstants.end()) {
// not going to override the constant,
// as the specConstants parameter doesn't specify it. So, we will keep the default
// already in the source text
// (stream to the end of the statement)...
// The constant is not being overridden, so keep the default value.
processedShaderSource << std::string_view(sourceData + pos,
posEndOfStatement + 1 - pos);
pos = posEndOfStatement + 1;
@@ -174,7 +171,7 @@ namespace {
utils::FixedCapacityVector<uint8_t> const& sourceBytes =
shaderSource[static_cast<size_t>(stage)];
if (sourceBytes.empty()) {
return nullptr;// nothing to compile/create, the shader was not provided
return nullptr; // No shader source to compile.
}
std::stringstream labelStream;
labelStream << programName << " " << filamentShaderStageToString(stage) << " shader";
@@ -192,7 +189,8 @@ namespace {
};
const wgpu::ShaderModule shaderModule = device.CreateShaderModule(&descriptor);
const wgpu::Instance instance = device.GetAdapter().GetInstance();
// synchronously creates the shader module...
// Synchronously compile the shader module.
const wgpu::WaitStatus waitResult = instance.WaitAny(
shaderModule.GetCompilationInfo(wgpu::CallbackMode::WaitAnyOnly,
[&descriptor](auto const& status,
@@ -282,7 +280,7 @@ WebGPUProgram::WebGPUProgram(wgpu::Device const& device, Program const& program)
: HwProgram{ program.getName() } {
std::unordered_map<uint32_t, std::variant<int32_t, float, bool>> specConstants;
toMap(program.getSpecializationConstants(), specConstants);
// TODO consider creating/compiling these shaders in parallel
// TODO: Consider creating/compiling these shaders in parallel.
vertexShaderModule = createShaderModule(device, program, ShaderStage::VERTEX, specConstants);
fragmentShaderModule =
createShaderModule(device, program, ShaderStage::FRAGMENT, specConstants);

View File

@@ -25,6 +25,10 @@ namespace filament::backend {
class Program;
/**
* A WebGPU implementation of the HwProgram.
* This class holds the compiled shader modules for a given program.
*/
class WebGPUProgram final : public HwProgram {
public:
WebGPUProgram(wgpu::Device const&, Program const&);

View File

@@ -27,9 +27,8 @@ namespace filament::backend {
namespace {
// The shaders expect a single-sampled 2D texture
// with a format compatible with scalar sample format f32
// (this is checked with the getCompatibilityFor function)
// The shaders expect a single-sampled 2D texture with a format compatible with a scalar sample format of f32.
// This is checked with the getCompatibilityFor function.
constexpr uint32_t TEXTURE_BIND_GROUP_INDEX{ 0 };
constexpr size_t TEXTURE_BIND_GROUP_ENTRY_SIZE{ 2 }; // sampler and texture
constexpr uint32_t SAMPLER_BINDING_INDEX{ 0 };
@@ -85,7 +84,7 @@ constexpr std::string_view SHADER_SOURCE{ R"(
.maxAnisotropy = 1, // should not matter, just being consistently defined
};
const wgpu::Sampler sampler{ device.CreateSampler(&descriptor) };
FILAMENT_CHECK_POSTCONDITION(sampler) << "Failed to create previous mip level sampler?";
FILAMENT_CHECK_POSTCONDITION(sampler) << "Failed to create previous mip level sampler.";
return sampler;
}
@@ -98,7 +97,7 @@ constexpr std::string_view SHADER_SOURCE{ R"(
};
const wgpu::ShaderModule shaderModule{ device.CreateShaderModule(&shaderModuleDescriptor) };
FILAMENT_CHECK_POSTCONDITION(shaderModule)
<< "Failed to create shader module for render pass mipmap generation?";
<< "Failed to create shader module for render pass mipmap generation.";
return shaderModule;
}
@@ -131,7 +130,7 @@ constexpr std::string_view SHADER_SOURCE{ R"(
const wgpu::BindGroupLayout textureBindGroupLayout{ device.CreateBindGroupLayout(
&textureBindGroupLayoutDescriptor) };
FILAMENT_CHECK_POSTCONDITION(textureBindGroupLayout)
<< "Failed to create texture bind group layout for render pass mipmap generation?";
<< "Failed to create texture bind group layout for render pass mipmap generation.";
return textureBindGroupLayout;
}
@@ -145,7 +144,7 @@ constexpr std::string_view SHADER_SOURCE{ R"(
const wgpu::PipelineLayout pipelineLayout{ device.CreatePipelineLayout(
&pipelineLayoutDescriptor) };
FILAMENT_CHECK_POSTCONDITION(pipelineLayout)
<< "Failed to create pipeline layout for render pass mipmap generation?";
<< "Failed to create pipeline layout for render pass mipmap generation.";
return pipelineLayout;
}
@@ -194,7 +193,7 @@ constexpr std::string_view SHADER_SOURCE{ R"(
};
const wgpu::RenderPipeline pipeline{ device.CreateRenderPipeline(&pipelineDescriptor) };
FILAMENT_CHECK_POSTCONDITION(pipeline)
<< "Failed to create pipeline for render pass mipmap generation?";
<< "Failed to create pipeline for render pass mipmap generation.";
return pipeline;
}
@@ -210,8 +209,7 @@ WebGPURenderPassMipmapGenerator::WebGPURenderPassMipmapGenerator(wgpu::Device co
WebGPURenderPassMipmapGenerator::FormatCompatibility
WebGPURenderPassMipmapGenerator::getCompatibilityFor(const wgpu::TextureFormat format,
const wgpu::TextureDimension dimension, const uint32_t sampleCount) {
// check that the format is compatible (currently expects that the scalar sample type is
// f32)...
// Check that the format is compatible (currently expects that the scalar sample type is f32).
switch (format) {
case wgpu::TextureFormat::Depth16Unorm:
case wgpu::TextureFormat::Depth24Plus:
@@ -220,20 +218,16 @@ WebGPURenderPassMipmapGenerator::getCompatibilityFor(const wgpu::TextureFormat f
case wgpu::TextureFormat::Depth32FloatStencil8:
return {
.compatible = false,
.reason = "A depth texture format requires special sampler treatment and does "
"not "
"generally support linear filtering needed for render pass based "
"mipmap "
"generation, thus this texture is not supported, as it is a kind of "
"depth texture.",
.reason = "A depth texture format requires special sampler treatment and does not "
"generally support linear filtering needed for render pass based mipmap "
"generation.",
};
case wgpu::TextureFormat::Undefined:
case wgpu::TextureFormat::External:
return {
.compatible = false,
.reason = "Undefined or External textures are not supported for render pass "
"based "
"mipmap generation",
.reason = "Undefined or External textures are not supported for render pass based "
"mipmap generation.",
};
default:
const ScalarSampleType scalarSampleType{ getScalarSampleTypeFrom(format) };
@@ -242,21 +236,19 @@ WebGPURenderPassMipmapGenerator::getCompatibilityFor(const wgpu::TextureFormat f
return {
.compatible = false,
.reason = "The provided texture format requires an unsigned integer "
"sampler, which does not natively support linear filtering, "
"which is needed for render pass based mipmap generation.",
"sampler, which does not natively support linear filtering.",
};
case ScalarSampleType::I32:
return {
.compatible = false,
.reason = "The provided texture format requires a signed integer "
"sampler, which does not natively support linear filtering, "
"which is needed for render pass based mipmap generation.",
"sampler, which does not natively support linear filtering.",
};
case ScalarSampleType::F32:
break;
}
}
// check that the dimensionality is compatible (currently expects 2D textures)...
// Check that the dimensionality is compatible (currently expects 2D textures).
switch (dimension) {
case wgpu::TextureDimension::Undefined:
return {
@@ -282,7 +274,7 @@ WebGPURenderPassMipmapGenerator::getCompatibilityFor(const wgpu::TextureFormat f
case wgpu::TextureDimension::e2D:
break;
}
// check that the texture is single-sampled (for now)...
// Check that the texture is single-sampled.
if (sampleCount > 1) {
return {
.compatible = false,
@@ -427,7 +419,7 @@ void WebGPURenderPassMipmapGenerator::generateMipmaps(wgpu::Queue const& queue,
wgpu::Texture const& texture) {
const uint32_t mipLevelCount{ texture.GetMipLevelCount() };
if (mipLevelCount < 2) {
return; // nothing to do
return; // Nothing to do.
}
wgpu::RenderPipeline const& pipeline{ getOrCreatePipelineFor(texture.GetFormat()) };
const wgpu::CommandEncoderDescriptor commandEncoderDescriptor{
@@ -436,20 +428,19 @@ void WebGPURenderPassMipmapGenerator::generateMipmaps(wgpu::Queue const& queue,
const wgpu::CommandEncoder commandEncoder{ mDevice.CreateCommandEncoder(
&commandEncoderDescriptor) };
FILAMENT_CHECK_POSTCONDITION(commandEncoder)
<< "Failed to create command encoder for layer for render pass mipmap generation?";
<< "Failed to create command encoder for render pass mipmap generation.";
const uint32_t layerCount{ texture.GetDepthOrArrayLayers() };
for (uint32_t layer = 0; layer < layerCount; layer++) {
for (uint32_t mipLevel = 1; mipLevel < mipLevelCount; mipLevel++) {
generateMipmap(commandEncoder, texture, pipeline, layer, mipLevel);
}
}
// submit the command buffer...
const wgpu::CommandBufferDescriptor commandBufferDescriptor{
.label = "mipmap_generation_render_pass_cmd_buffer",
};
const wgpu::CommandBuffer commandBuffer{ commandEncoder.Finish(&commandBufferDescriptor) };
FILAMENT_CHECK_POSTCONDITION(commandBuffer)
<< "Failed to create command buffer for layer for render pass mipmap generation?";
<< "Failed to create command buffer for render pass mipmap generation.";
queue.Submit(1, &commandBuffer);
}
@@ -472,7 +463,7 @@ void WebGPURenderPassMipmapGenerator::generateMipmap(wgpu::CommandEncoder const&
.usage = wgpu::TextureUsage::TextureBinding,
};
const wgpu::TextureView sourceView{ texture.CreateView(&sourceViewDescriptor) };
FILAMENT_CHECK_POSTCONDITION(pipeline)
FILAMENT_CHECK_POSTCONDITION(sourceView)
<< "Failed to create source texture view for layer " << layer << " and mip level "
<< mipLevel << " for render pass mipmap generation?";
const wgpu::TextureViewDescriptor destinationViewDescriptor{
@@ -487,7 +478,7 @@ void WebGPURenderPassMipmapGenerator::generateMipmap(wgpu::CommandEncoder const&
.usage = wgpu::TextureUsage::RenderAttachment,
};
const wgpu::TextureView destinationView{ texture.CreateView(&destinationViewDescriptor) };
FILAMENT_CHECK_POSTCONDITION(pipeline)
FILAMENT_CHECK_POSTCONDITION(destinationView)
<< "Failed to create destination texture view for layer " << layer << " and mip level "
<< mipLevel << " for render pass mipmap generation?";
// create the render pass...
@@ -508,7 +499,7 @@ void WebGPURenderPassMipmapGenerator::generateMipmap(wgpu::CommandEncoder const&
.entries = textureBindGroupEntries,
};
const wgpu::BindGroup textureBindGroup{ mDevice.CreateBindGroup(&textureBindGroupDescriptor) };
FILAMENT_CHECK_POSTCONDITION(pipeline)
FILAMENT_CHECK_POSTCONDITION(textureBindGroup)
<< "Failed to create texture bind group for layer " << layer << " and mip level "
<< mipLevel << " for render pass mipmap generation?";
const wgpu::RenderPassColorAttachment colorAttachment{
@@ -534,8 +525,7 @@ void WebGPURenderPassMipmapGenerator::generateMipmap(wgpu::CommandEncoder const&
<< mipLevel << " for render pass mipmap generation?";
renderPassEncoder.SetPipeline(pipeline);
renderPassEncoder.SetBindGroup(TEXTURE_BIND_GROUP_INDEX, textureBindGroup);
renderPassEncoder.Draw(3); // draw the full-screen triangle
// with hard-coded vertices in the shader
renderPassEncoder.Draw(3); // Draw a full-screen triangle.
renderPassEncoder.End();
}

View File

@@ -27,6 +27,9 @@
namespace filament::backend {
/**
* A utility class for generating mipmaps for a texture using a series of render passes.
*/
class WebGPURenderPassMipmapGenerator final {
public:
struct FormatCompatibility final {
@@ -34,6 +37,11 @@ public:
std::string_view reason;
};
/**
* Checks if a given texture format is compatible with render pass-based mipmap generation.
* @return A FormatCompatibility struct indicating whether the format is compatible
* and a reason if not.
*/
[[nodiscard]] static FormatCompatibility getCompatibilityFor(wgpu::TextureFormat,
wgpu::TextureDimension, uint32_t sampleCount);

View File

@@ -24,6 +24,10 @@ namespace filament::backend {
class WebGPUVertexBuffer;
class WebGPUIndexBuffer;
/**
* A WebGPU implementation of the HwRenderPrimitive.
* This struct holds the vertex and index buffers that define a renderable primitive.
*/
struct WebGPURenderPrimitive : public HwRenderPrimitive {
WebGPUVertexBuffer* vertexBuffer = nullptr;
WebGPUIndexBuffer* indexBuffer = nullptr;

View File

@@ -41,9 +41,9 @@ namespace filament::backend {
namespace {
/**
* Panics if the number of samples in the original attachment textures do not match.
* @return The number of samples in the original attachment textures (the number/count in each one).
* Returns 0 if there are no attachments.
* Creates MSAA sidecar textures for attachments if necessary.
* This function also verifies that all attachments have the same sample count.
* @return The number of samples per attachment.
*/
[[nodiscard]] uint8_t createMsaaSidecarTextures(const uint8_t renderTargetSampleCount,
const TargetBufferFlags targetFlags, MRT const& colorAttachments,
@@ -100,9 +100,6 @@ namespace {
sampleCountPerAttachment = texture->samples;
firstAttachment = false;
}
FILAMENT_CHECK_PRECONDITION(texture->samples == sampleCountPerAttachment)
<< target.name << " attachment texture has " << +texture->samples
<< " but the other attachment(s) have " << +sampleCountPerAttachment;
if (renderTargetSampleCount > 1 && sampleCountPerAttachment == 1) {
texture->createMsaaSidecarTextureIfNotAlreadyCreated(renderTargetSampleCount,
device);
@@ -136,7 +133,7 @@ WebGPURenderTarget::WebGPURenderTarget(const uint32_t width, const uint32_t heig
mColorAttachmentDesc.reserve(MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
}
// Default constructor for the default render target
// Constructor for the default render target
WebGPURenderTarget::WebGPURenderTarget()
: HwRenderTarget{ 0, 0 },
mDefaultRenderTarget{ true },
@@ -182,19 +179,19 @@ void WebGPURenderTarget::setUpRenderPassAttachments(wgpu::RenderPassDescriptor&
FILAMENT_CHECK_PRECONDITION(std::all_of(customColorMsaaSidecarTextureViews,
customColorMsaaSidecarTextureViews + customColorTextureViewCount,
[](wgpu::TextureView const& msaaView) { return msaaView != nullptr; }))
<< "A color or depth/stencil attachment texture has a MSAA sidecar but at least "
<< "A color or depth/stencil attachment texture has an MSAA sidecar but at least "
"one other color attachment texture does not.";
FILAMENT_CHECK_PRECONDITION(customDepthStencilMsaaSidecarTextureView != nullptr)
<< "The color attachment texture(s) have MSAA sidecar(s) but the depth/stencil "
<< "The color attachment texture(s) have MSAA sidecars but the depth/stencil "
"texture does not.";
} else {
FILAMENT_CHECK_PRECONDITION(std::all_of(customColorMsaaSidecarTextureViews,
customColorMsaaSidecarTextureViews + customColorTextureViewCount,
[](wgpu::TextureView const& msaaView) { return msaaView == nullptr; }))
<< "A color or depth/stencil attachment texture does not have a MSAA sidecar but "
<< "A color or depth/stencil attachment texture does not have an MSAA sidecar but "
"at least one color attachment texture does.";
FILAMENT_CHECK_PRECONDITION(customDepthStencilMsaaSidecarTextureView == nullptr)
<< "Custom color textures for the render target do not have MSAA sidecar(s) but "
<< "Custom color textures for the render target do not have MSAA sidecars but "
"the depth/stencil texture does.";
}

View File

@@ -31,6 +31,10 @@
namespace filament::backend {
/**
* A WebGPU implementation of the HwRenderTarget.
* This class represents a collection of attachments (textures) that can be rendered to.
*/
class WebGPURenderTarget : public HwRenderTarget {
public:
using Attachment = TargetBufferInfo; // Using TargetBufferInfo directly for attachments
@@ -38,7 +42,8 @@ public:
WebGPURenderTarget(uint32_t width, uint32_t height, uint8_t samples, uint8_t layerCount,
MRT const& colorAttachments, Attachment const& depthAttachment,
Attachment const& stencilAttachment, TargetBufferFlags const& targetFlags,
std::function<WebGPUTexture*(const Handle<HwTexture>)> const&, wgpu::Device const&);
std::function<WebGPUTexture*(const Handle<HwTexture>)> const&,
wgpu::Device const&);
// Default constructor for the default render target
WebGPURenderTarget();
@@ -80,7 +85,7 @@ private:
MRT mColorAttachments{};
// TODO WebGPU only supports a DepthStencil attachment, should this be just
// mDepthStencilAttachment?
// mDepthStencilAttachment?
Attachment mDepthAttachment{};
Attachment mStencilAttachment{};

View File

@@ -187,7 +187,8 @@ void initConfig(wgpu::SurfaceConfiguration& config, wgpu::Device const& device,
wgpu::SurfaceCapabilities const& capabilities, wgpu::Extent2D const& extent,
bool useSRGBColorSpace) {
config.device = device;
config.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
config.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc |
wgpu::TextureUsage::TextureBinding;
config.width = extent.width;
config.height = extent.height;
config.format = selectColorFormat(capabilities.formatCount, capabilities.formats, useSRGBColorSpace);
@@ -269,8 +270,6 @@ WebGPUSwapChain::WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Extent2D const&
const bool useSRGBColorSpace = (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0;
initConfig(mConfig, device, capabilities, extent, useSRGBColorSpace);
mDepthFormat = selectDepthFormat(device.HasFeature(wgpu::FeatureName::Depth32FloatStencil8),
mNeedStencil);
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printSurfaceConfiguration(mConfig, mDepthFormat);
#endif
@@ -297,8 +296,6 @@ WebGPUSwapChain::WebGPUSwapChain(wgpu::Extent2D const& extent,
mConfig.presentMode = wgpu::PresentMode::Fifo;
mConfig.alphaMode = wgpu::CompositeAlphaMode::Auto;
mDepthFormat = selectDepthFormat(device.HasFeature(wgpu::FeatureName::Depth32FloatStencil8),
mNeedStencil);
const wgpu::TextureDescriptor textureDescriptor = {
.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc,
.dimension = wgpu::TextureDimension::e2D,

View File

@@ -107,84 +107,6 @@ namespace {
}
}
/**
* @param fUsage Filament's requested texture usage
* @param samples How many samples to use for MSAA
* @param needsComputeStorageSupport if we need to use this texture as storage binding in something
* like a compute shader
* @param needsRenderAttachmentSupport if we need to use this texture as a render pass attachment
* in something like a render pass blit (e.g. mipmap generation)
* @return The appropriate texture usage flags for the underlying texture
*/
[[nodiscard]] wgpu::TextureUsage fToWGPUTextureUsage(TextureUsage const& fUsage,
const uint8_t samples, const bool needsComputeStorageSupport,
const bool needsRenderAttachmentSupport) {
wgpu::TextureUsage retUsage = wgpu::TextureUsage::None;
// if needsComputeStorageSupport we need to read and write to the texture in a shader and thus
// require CopySrc, CopyDst, TextureBinding, & StorageBinding
// Basing this mapping off of VulkanTexture.cpp's getUsage func and suggestions from Gemini
// TODO Validate assumptions, revisit if issues.
if (needsComputeStorageSupport || any(TextureUsage::BLIT_SRC & fUsage)) {
retUsage |= wgpu::TextureUsage::CopySrc;
}
if (needsComputeStorageSupport ||
any((TextureUsage::BLIT_DST | TextureUsage::UPLOADABLE) & fUsage)) {
retUsage |= wgpu::TextureUsage::CopyDst;
}
if (needsComputeStorageSupport || any(TextureUsage::SAMPLEABLE & fUsage)) {
retUsage |= wgpu::TextureUsage::TextureBinding;
}
if (needsComputeStorageSupport) {
retUsage |= wgpu::TextureUsage::StorageBinding;
}
if (needsRenderAttachmentSupport) {
retUsage |= wgpu::TextureUsage::RenderAttachment;
}
if (any(TextureUsage::BLIT_SRC & fUsage)) {
retUsage |= wgpu::TextureUsage::TextureBinding;
}
if (any(TextureUsage::BLIT_DST & fUsage)) {
retUsage |= wgpu::TextureUsage::RenderAttachment;
}
if (any(TextureUsage::GEN_MIPMAPPABLE & fUsage)) {
retUsage |= (wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding);
}
// WGPU Render attachment covers either color or stencil situation dependant
// NOTE: Depth attachment isn't used this way in Vulkan but logically maps to WGPU docs. If
// issues, investigate here.
if (any((TextureUsage::COLOR_ATTACHMENT | TextureUsage::STENCIL_ATTACHMENT |
TextureUsage::DEPTH_ATTACHMENT) &
fUsage)) {
retUsage |= wgpu::TextureUsage::RenderAttachment;
}
// This is from Vulkan logic- if there are any issues try disabling this first, allows perf
// benefit though
const bool useTransientAttachment =
// Usage consists of attachment flags only.
none(fUsage & ~TextureUsage::ALL_ATTACHMENTS) &&
// Usage contains at least one attachment flag.
any(fUsage & TextureUsage::ALL_ATTACHMENTS) &&
// Depth resolve cannot use transient attachment because it uses a custom shader.
// TODO: see VulkanDriver::isDepthStencilResolveSupported() to know when to remove this
// restriction.
// Note that the custom shader does not resolve stencil. We do need to move to vk 1.2
// and above to be able to support stencil resolve (along with depth).
!(any(fUsage & TextureUsage::DEPTH_ATTACHMENT) && samples > 1);
if (useTransientAttachment) {
retUsage |= wgpu::TextureUsage::TransientAttachment;
}
// NOTE: Unused wgpu flags:
// StorageAttachment
// NOTE: Unused Filament flags:
// SUBPASS_INPUT VK goes to input attachment which we don't support right now
// PROTECTED
return retUsage;
}
[[nodiscard]] wgpu::TextureAspect fToWGPUTextureViewAspect(TextureUsage const& fUsage,
TextureFormat const& fFormat) {
@@ -286,9 +208,11 @@ WebGPUTexture::WebGPUTexture(const SamplerType samplerType, const uint8_t levels
mAspect{ fToWGPUTextureViewAspect(usage, format) },
mWebGPUUsage{ fToWGPUTextureUsage(usage, samples,
mMipmapGenerationStrategy == MipmapGenerationStrategy::SPD_COMPUTE_PASS,
mMipmapGenerationStrategy == MipmapGenerationStrategy::RENDER_PASS) },
mViewUsage{ fToWGPUTextureUsage(usage, samples, false, false) },
mDimension{toWebGPUTextureViewDimension(samplerType)},
mMipmapGenerationStrategy == MipmapGenerationStrategy::RENDER_PASS,
device.HasFeature(wgpu::FeatureName::TransientAttachments)) },
mViewUsage{ fToWGPUTextureUsage(usage, samples, false, false,
device.HasFeature(wgpu::FeatureName::TransientAttachments)) },
mDimension{ toWebGPUTextureViewDimension(samplerType) },
mBlockWidth{ filament::backend::getBlockWidth(format) },
mBlockHeight{ filament::backend::getBlockHeight(format) },
mDefaultMipLevel{ 0 },
@@ -359,6 +283,24 @@ WebGPUTexture::WebGPUTexture(WebGPUTexture const* src, const uint8_t baseLevel,
toWebGPUTextureViewDimension(src->target)) },
mMsaaSidecarTexture{ src->mMsaaSidecarTexture } {}
WebGPUTexture::WebGPUTexture(const WebGPUTexture* src, const wgpu::TextureView view) noexcept
: HwTexture{ src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage},
mViewFormat{ src->mViewFormat },
mMipmapGenerationStrategy{ src->mMipmapGenerationStrategy },
mWebGPUFormat{ src->mWebGPUFormat },
mAspect{ src->mAspect },
mWebGPUUsage{ src->mWebGPUUsage },
mViewUsage{ src->mViewUsage },
mBlockWidth{ src->mBlockWidth },
mBlockHeight{ src->mBlockHeight },
mArrayLayerCount{ src->mArrayLayerCount },
mTexture{ src->mTexture },
mDefaultMipLevel{ src->mDefaultMipLevel },
mDefaultBaseArrayLayer{ 0 },
mDefaultTextureView{ view },
mMsaaSidecarTexture{src->mMsaaSidecarTexture}{}
wgpu::Texture const& WebGPUTexture::getMsaaSidecarTexture(const uint8_t sampleCount) const {
if (mMsaaSidecarTexture == nullptr) {
return mMsaaSidecarTexture; // nullptr (no such sidecar)

View File

@@ -34,11 +34,26 @@ public:
NONE,
};
/**
* Creates a Filament texture and a texture view
*/
WebGPUTexture(SamplerType, uint8_t levels, TextureFormat, uint8_t samples, uint32_t width,
uint32_t height, uint32_t depth, TextureUsage, wgpu::Device const&) noexcept;
/**
* Creates a "Filament Texture View", where the underlying texture is the same as the source,
* but the view elements differ
*/
WebGPUTexture(WebGPUTexture const* src, uint8_t baseLevel, uint8_t levelCount) noexcept;
/**
* @param textureView texture view to use for the respective texture. e.g. it can be a
* swizzled Texture view for that filament texture
*
* Associates the underlying texture with the given texture view
*/
WebGPUTexture(WebGPUTexture const* source, wgpu::TextureView textureView) noexcept;
[[nodiscard]] wgpu::TextureAspect getAspect() const { return mAspect; }
[[nodiscard]] size_t getBlockWidth() const { return mBlockWidth; }
@@ -78,9 +93,9 @@ public:
/**
* @param samples The number of samples the underlying texture supports
* @param mipLevel The mip level into the underyling texture for which this view will reference
* @param mipLevel The mip level into the underlying texture for which this view will reference
* (this view will only have one mip level)
* @param arrayLayer The layer into the underyling texture for which this view will reference
* @param arrayLayer The layer into the underlying texture for which this view will reference
* (this view will only have one layer)
* @return A texture view for the MSAA sidecar texture
*/

View File

@@ -198,81 +198,21 @@ namespace filament::backend {
case TextureFormat::RGB_BPTC_SIGNED_FLOAT: return wgpu::TextureFormat::BC6HRGBFloat;
case TextureFormat::RGBA_BPTC_UNORM: return wgpu::TextureFormat::BC7RGBAUnorm;
case TextureFormat::SRGB_ALPHA_BPTC_UNORM: return wgpu::TextureFormat::BC7RGBAUnormSrgb;
case TextureFormat::RGB565:
// No direct mapping in wgpu. Could potentially map to RGBA8Unorm
// and discard the alpha and lower precision.
FWGPU_LOGW << "Requested Filament texture format RGB565 but getting "
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)";
return wgpu::TextureFormat::Undefined;
case TextureFormat::RGB9_E5: return wgpu::TextureFormat::RGB9E5Ufloat;
case TextureFormat::RGB5_A1:
// No direct mapping in wgpu. Could potentially map to RGBA8Unorm
// and handle the packing/unpacking in shaders.
FWGPU_LOGW << "Requested Filament texture format RGB5_A1 but getting "
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)";
return wgpu::TextureFormat::Undefined;
case TextureFormat::RGBA4:
// No direct mapping in wgpu. Could potentially map to RGBA8Unorm
// and handle the packing/unpacking in shaders.
FWGPU_LOGW << "Requested Filament texture format RGBA4 but getting "
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)";
return wgpu::TextureFormat::Undefined;
case TextureFormat::RGB8:
FWGPU_LOGW << "Requested Filament texture format RGB8 but getting "
"wgpu::TextureFormat::RGBA8Unorm (no direct sRGB equivalent in wgpu "
"without alpha)";
return wgpu::TextureFormat::RGBA8Unorm;
case TextureFormat::SRGB8:
FWGPU_LOGW << "Requested Filament texture format SRGB8 but getting "
"wgpu::TextureFormat::RGBA8UnormSrgb (no direct sRGB equivalent in wgpu "
"without alpha)";
return wgpu::TextureFormat::RGBA8UnormSrgb;
case TextureFormat::RGB8_SNORM:
FWGPU_LOGW
<< "Requested Filament texture format RGB8_SNORM but getting "
"wgpu::TextureFormat::RGBA8Snorm (no direct mapping in wgpu without alpha)";
return wgpu::TextureFormat::RGBA8Snorm;
case TextureFormat::RGB8UI:
FWGPU_LOGW << "Requested Filament texture format RGB8UI but getting "
"wgpu::TextureFormat::RGBA8Uint (no direct mapping in wgpu without alpha)";
return wgpu::TextureFormat::RGBA8Uint;
case TextureFormat::RGB8I:
FWGPU_LOGW << "Requested Filament texture format RGB8I but getting "
"wgpu::TextureFormat::RGBA8Sint (no direct mapping in wgpu without alpha)";
return wgpu::TextureFormat::RGBA8Sint;
case TextureFormat::RGB9_E5: return wgpu::TextureFormat::RGB9E5Ufloat;
case TextureFormat::RGB8: return wgpu::TextureFormat::RGBA8Unorm;
case TextureFormat::SRGB8: return wgpu::TextureFormat::RGBA8UnormSrgb;
case TextureFormat::RGB8_SNORM: return wgpu::TextureFormat::RGBA8Snorm;
case TextureFormat::RGB8UI: return wgpu::TextureFormat::RGBA8Uint;
case TextureFormat::RGB8I: return wgpu::TextureFormat::RGBA8Sint;
case TextureFormat::R11F_G11F_B10F: return wgpu::TextureFormat::RG11B10Ufloat;
case TextureFormat::UNUSED: return wgpu::TextureFormat::Undefined;
case TextureFormat::RGB10_A2: return wgpu::TextureFormat::RGB10A2Unorm;
case TextureFormat::RGB16F:
FWGPU_LOGW
<< "Requested Filament texture format RGB16F but getting "
"wgpu::TextureFormat::RGBA16Float (no direct mapping in wgpu without alpha)";
return wgpu::TextureFormat::RGBA16Float;
case TextureFormat::RGB16UI:
FWGPU_LOGW
<< "Requested Filament texture format RGB16UI but getting "
"wgpu::TextureFormat::RGBA16Uint (no direct mapping in wgpu without alpha)";
return wgpu::TextureFormat::RGBA16Uint;
case TextureFormat::RGB16I:
FWGPU_LOGW
<< "Requested Filament texture format RGB16I but getting "
"wgpu::TextureFormat::RGBA16Sint (no direct mapping in wgpu without alpha)";
return wgpu::TextureFormat::RGBA16Sint;
case TextureFormat::RGB32F:
FWGPU_LOGW
<< "Requested Filament texture format RGB32F but getting "
"wgpu::TextureFormat::RGBA32Float (no direct mapping in wgpu without alpha)";
return wgpu::TextureFormat::RGBA32Float;
case TextureFormat::RGB32UI:
FWGPU_LOGW
<< "Requested Filament texture format RGB32UI but getting "
"wgpu::TextureFormat::RGBA32Uint (no direct mapping in wgpu without alpha)";
return wgpu::TextureFormat::RGBA32Uint;
case TextureFormat::RGB32I:
FWGPU_LOGW
<< "Requested Filament texture format RGB32I but getting "
"wgpu::TextureFormat::RGBA32Sint (no direct mapping in wgpu without alpha)";
return wgpu::TextureFormat::RGBA32Sint;
case TextureFormat::RGB16F: return wgpu::TextureFormat::RGBA16Float;
case TextureFormat::RGB16UI: return wgpu::TextureFormat::RGBA16Uint;
case TextureFormat::RGB16I: return wgpu::TextureFormat::RGBA16Sint;
case TextureFormat::RGB32F: return wgpu::TextureFormat::RGBA32Float;
case TextureFormat::RGB32UI: return wgpu::TextureFormat::RGBA32Uint;
case TextureFormat::RGB32I: return wgpu::TextureFormat::RGBA32Sint;
case TextureFormat::DXT1_RGB: return wgpu::TextureFormat::BC1RGBAUnorm;
case TextureFormat::DXT1_RGBA: return wgpu::TextureFormat::BC1RGBAUnorm;
case TextureFormat::DXT3_RGBA: return wgpu::TextureFormat::BC2RGBAUnorm;
@@ -281,6 +221,20 @@ namespace filament::backend {
case TextureFormat::DXT1_SRGBA: return wgpu::TextureFormat::BC1RGBAUnormSrgb;
case TextureFormat::DXT3_SRGBA: return wgpu::TextureFormat::BC2RGBAUnormSrgb;
case TextureFormat::DXT5_SRGBA: return wgpu::TextureFormat::BC3RGBAUnormSrgb;
// No direct mapping in wgpu. Could potentially map to RGBA8Unorm and handle the
// packing/unpacking in shaders.
case TextureFormat::RGB565:
FWGPU_LOGW << "Requested Filament texture format RGB565 but getting "
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)";
return wgpu::TextureFormat::Undefined;
case TextureFormat::RGB5_A1:
FWGPU_LOGW << "Requested Filament texture format RGB5_A1 but getting "
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)";
return wgpu::TextureFormat::Undefined;
case TextureFormat::RGBA4:
FWGPU_LOGW << "Requested Filament texture format RGBA4 but getting "
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)";
return wgpu::TextureFormat::Undefined;
}
}
@@ -341,6 +295,20 @@ namespace filament::backend {
}
}
[[nodiscard]] constexpr wgpu::ComponentSwizzle toWGPUComponentSwizzle(const backend::TextureSwizzle swizzle) {
switch (swizzle) {
case backend::TextureSwizzle::CHANNEL_0: return wgpu::ComponentSwizzle::R;
case backend::TextureSwizzle::CHANNEL_1: return wgpu::ComponentSwizzle::G;
case backend::TextureSwizzle::CHANNEL_2: return wgpu::ComponentSwizzle::B;
case backend::TextureSwizzle::CHANNEL_3: return wgpu::ComponentSwizzle::A;
case backend::TextureSwizzle::SUBSTITUTE_ZERO: return wgpu::ComponentSwizzle::Zero;
case backend::TextureSwizzle::SUBSTITUTE_ONE: return wgpu::ComponentSwizzle::One;
default:
FWGPU_LOGW << "invalid swizzle component returning wgpu::ComponentSwizzle::Undefined";
return wgpu::ComponentSwizzle::Undefined;
}
}
/**
* @return true if https://www.w3.org/TR/webgpu/#copy-compatible which states:
* Two GPUTextureFormats format1 and format2 are copy-compatible if:
@@ -534,6 +502,109 @@ namespace filament::backend {
}
}
/**
* Todo: should this take into account sRGB/linear when determining if conversion is necessary?
* For instance, if the output format is the same as the input, except the output is sRGB
* do we really need to do a conversion? If the answer is no, we should do that check here
* and return conversionNecessary false in that case.
* However, doing a straight-forward comparison is the safest most conservative thing to do
* for functional correctness and NOT doing a conversion in such cases could be considered
* an optimization. Thus, consider the optimization when we have better test coverage to
* experiment with such a refactor.
* @return True if theres a format mismatch
*/
[[nodiscard]] constexpr bool conversionNecessary(const wgpu::TextureFormat source,
const wgpu::TextureFormat destination, const PixelDataType pixelDataType) {
return source != destination && pixelDataType != PixelDataType::COMPRESSED;
}
/**
* @param fUsage Filament's requested texture usage
* @param samples How many samples to use for MSAA
* @param needsComputeStorageSupport if we need to use this texture as storage binding in something
* like a compute shader
* @param needsRenderAttachmentSupport if we need to use this texture as a render pass attachment
* in something like a render pass blit (e.g. mipmap generation)
* @param deviceSupportsTransientAttachments if the device itself supports Render Attachments
* @return The appropriate texture usage flags for the underlying texture
*/
[[nodiscard]] constexpr wgpu::TextureUsage fToWGPUTextureUsage(TextureUsage const& fUsage,
const uint8_t samples, const bool needsComputeStorageSupport,
const bool needsRenderAttachmentSupport, const bool deviceSupportsTransientAttachments) {
wgpu::TextureUsage retUsage{ wgpu::TextureUsage::None };
if (any(TextureUsage::BLIT_SRC & fUsage)) {
retUsage |= (wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::TextureBinding);
}
if (any(TextureUsage::BLIT_DST & fUsage)) {
retUsage |= (wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
}
if (any(TextureUsage::UPLOADABLE & fUsage)) {
retUsage |= wgpu::TextureUsage::CopyDst;
}
if (any(TextureUsage::GEN_MIPMAPPABLE & fUsage)) {
retUsage |= (wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding);
}
if (any(TextureUsage::SAMPLEABLE & fUsage)) {
retUsage |= wgpu::TextureUsage::TextureBinding;
}
// if needsComputeStorageSupport we need to read and write to the texture in a shader and thus
// require CopySrc, CopyDst, TextureBinding, & StorageBinding
if (needsComputeStorageSupport) {
retUsage |= (wgpu::TextureUsage::StorageBinding |
wgpu::TextureUsage::CopySrc |
wgpu::TextureUsage::CopyDst |
wgpu::TextureUsage::TextureBinding);
}
wgpu::TextureUsage transientAttachmentNeeded{ wgpu::TextureUsage::None };
const bool useTransientAttachment {
deviceSupportsTransientAttachments &&
// Usage consists of attachment flags only.
none(fUsage & ~TextureUsage::ALL_ATTACHMENTS) &&
// Usage contains at least one attachment flag.
any(fUsage & TextureUsage::ALL_ATTACHMENTS) &&
// Depth resolve cannot use transient attachment because it uses a custom shader.
// TODO: see VulkanDriver::isDepthStencilResolveSupported() to know when to remove this
// restriction.
// Note that the custom shader does not resolve stencil. We do need to move to vk 1.2
// and above to be able to support stencil resolve (along with depth).
!(any(fUsage & TextureUsage::DEPTH_ATTACHMENT) && samples > 1)};
if (useTransientAttachment) {
transientAttachmentNeeded |= wgpu::TextureUsage::TransientAttachment;
}
// A texture that is a blit destination or render attachment will often need to be
// a copy source for subsequent operations (e.g., mipmap generation, readbacks).
// However, we dont need to add the CopySrc IF its a transientAttachment
if (any((TextureUsage::BLIT_DST | TextureUsage::COLOR_ATTACHMENT |
TextureUsage::DEPTH_ATTACHMENT) &
fUsage)) {
if (!useTransientAttachment) {
retUsage |= wgpu::TextureUsage::CopySrc;
}
}
if (needsRenderAttachmentSupport) {
retUsage |= wgpu::TextureUsage::RenderAttachment;
}
// WGPU Render attachment covers either color or stencil situation dependant
if (any((TextureUsage::COLOR_ATTACHMENT | TextureUsage::STENCIL_ATTACHMENT |
TextureUsage::DEPTH_ATTACHMENT) &
fUsage)) {
retUsage |= wgpu::TextureUsage::RenderAttachment;
retUsage |= transientAttachmentNeeded;
}
// NOTE: Unused wgpu flags:
// StorageAttachment
// NOTE: Unused Filament flags:
// SUBPASS_INPUT: VK goes to input attachment which we don't support right now
// PROTECTED
return retUsage;
}
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_WEBGPUTEXTUREHELPERS_H

View File

@@ -67,9 +67,11 @@ constexpr std::array REQUIRED_FEATURES = {
};
constexpr std::array OPTIONAL_FEATURES = {
wgpu::FeatureName::CoreFeaturesAndLimits,
wgpu::FeatureName::DepthClipControl,
wgpu::FeatureName::Depth32FloatStencil8,
wgpu::FeatureName::CoreFeaturesAndLimits };
wgpu::FeatureName::TextureComponentSwizzle,
};
enum class LimitToValidate : uint8_t {
begin = 0,// needs to be first for iterating through all possible values in the enum
@@ -505,6 +507,7 @@ struct AdapterDetailsHash final {
// selects one preferred adapter or panics if none can be found
wgpu::Adapter selectPreferredAdapter(
WebGPUPlatform::Configuration const& configuration,
std::unordered_set<AdapterDetails, AdapterDetailsHash> const& compatibleAdapters) {
// for each unique adapter...
AdapterDetails const* selectedAdapter = nullptr;
@@ -512,6 +515,10 @@ wgpu::Adapter selectPreferredAdapter(
// choose the most desirable adapter that meets the minimum requirements...
for (AdapterDetails const& details: compatibleAdapters) {
if (configuration.forceBackendType != wgpu::BackendType::Undefined &&
configuration.forceBackendType != details.info.backendType) {
continue;
}
if (!adapterMeetsMinimumRequirements(details)) {
continue;
}
@@ -587,7 +594,7 @@ wgpu::Adapter WebGPUPlatform::requestAdapter(wgpu::Surface const& surface) {
}
const std::unordered_set<AdapterDetails, AdapterDetailsHash> compatibleAdapters =
requestCompatibleAdapters(mInstance, requests);
return selectPreferredAdapter(compatibleAdapters);
return selectPreferredAdapter(getConfiguration(), compatibleAdapters);
}
wgpu::Device WebGPUPlatform::requestDevice(wgpu::Adapter const& adapter) {

View File

@@ -38,7 +38,12 @@ std::vector<wgpu::RequestAdapterOptions> WebGPUPlatform::getAdapterOptions() {
constexpr std::array powerPreferences = {
wgpu::PowerPreference::HighPerformance,
wgpu::PowerPreference::LowPower };
constexpr std::array backendTypes = { wgpu::BackendType::Metal };
constexpr std::array backendTypes = {
wgpu::BackendType::Metal,
// To enable software rasterization on MacOS, we need to ensure Vulkan adapters are
// available.
wgpu::BackendType::Vulkan,
};
constexpr std::array forceFallbackAdapters = { false, true };
constexpr size_t totalCombinations =
powerPreferences.size() * backendTypes.size() * forceFallbackAdapters.size();

View File

@@ -41,36 +41,21 @@
uint32_t height;
} wl;
}// namespace
#elif defined(LINUX_OR_FREEBSD) && defined(FILAMENT_SUPPORTS_X11)
// TODO: we should allow for headless on Linux explicitly. Right now this is the headless path
// (with no FILAMENT_SUPPORTS_XCB or FILAMENT_SUPPORTS_XLIB).
#elif defined(LINUX_OR_FREEBSD) && defined(FILAMENT_SUPPORTS_X11) && defined(FILAMENT_SUPPORTS_XLIB)
// TODO: we should allow for headless on Linux explicitly. Right now headless path doesn't work
#include <dlfcn.h>
#if defined(FILAMENT_SUPPORTS_XCB)
#include <xcb/xcb.h>
namespace {
typedef xcb_connection_t* (*XCB_CONNECT)(const char* displayname, int* screenp);
}// namespace
#endif
#if defined(FILAMENT_SUPPORTS_XLIB)
#include <X11/Xlib.h>
namespace {
typedef Display* (*X11_OPEN_DISPLAY)(const char*);
}// namespace
#endif
static constexpr const char* LIBRARY_X11 = "libX11.so.6";
#include <X11/Xlib.h>
static constexpr const char* LIBRARY_X11 { "libX11.so.6" };
namespace {
struct XEnv {
#if defined(FILAMENT_SUPPORTS_XCB)
XCB_CONNECT xcbConnect;
xcb_connection_t* connection;
#endif
#if defined(FILAMENT_SUPPORTS_XLIB)
X11_OPEN_DISPLAY openDisplay;
Display* display;
#endif
void* library = nullptr;
} g_x11;
}// namespace
typedef Display* (*X11_OPEN_DISPLAY)(const char*);
struct XEnv final {
X11_OPEN_DISPLAY openDisplay;
Display* display { nullptr };
void* library { nullptr };
} g_x11;
} // namespace
#else
#error Not a supported Linux or FeeBSD + WebGPU platform
#endif
@@ -116,59 +101,26 @@ wgpu::Extent2D WebGPUPlatform::getSurfaceExtent(void* nativeWindow) const {
surfaceExtent.height = ptrval->height;
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
<< "Unable to get window size for Linux Wayland-backed surface.";
#elif defined(LINUX_OR_FREEBSD) && defined(FILAMENT_SUPPORTS_X11)
#elif defined(LINUX_OR_FREEBSD) && defined(FILAMENT_SUPPORTS_X11) && defined (FILAMENT_SUPPORTS_XLIB)
if (g_x11.library == nullptr) {
g_x11.library = dlopen(LIBRARY_X11, RTLD_LOCAL | RTLD_NOW);
FILAMENT_CHECK_PRECONDITION(g_x11.library) << "Unable to open X11 library.";
#if defined(FILAMENT_SUPPORTS_XCB)
g_x11.xcbConnect = (XCB_CONNECT) dlsym(g_x11.library, "xcb_connect");
int screen = 0;
g_x11.connection = g_x11.xcbConnect(nullptr, &screen);
#endif
#if defined(FILAMENT_SUPPORTS_XLIB)
g_x11.openDisplay = (X11_OPEN_DISPLAY) dlsym(g_x11.library, "XOpenDisplay");
g_x11.display = g_x11.openDisplay(NULL);
FILAMENT_CHECK_PRECONDITION(g_x11.display) << "Unable to open X11 display.";
#endif
}
#if defined(FILAMENT_SUPPORTS_XCB) || defined(FILAMENT_SUPPORTS_XLIB)
bool useXcb = false;
#endif
#if defined(FILAMENT_SUPPORTS_XCB)
#if defined(FILAMENT_SUPPORTS_XLIB)
useXcb = (SWAP_CHAIN_CONFIG_ENABLE_XCB) != 0;
#else
useXcb = true;
#endif
if (useXcb) {
const xcb_setup_t* setup = xcb_get_setup(g_x11.connection);
xcb_screen_iterator_t screen_iter = xcb_setup_roots_iterator(setup);
xcb_screen_t* screen = screen_iter.data;
surfaceExtent.width = static_cast<uint32_t>(screen->width_in_pixels);
surfaceExtent.height = static_cast<uint32_t>(screen->height_in_pixels);
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
<< "Unable to get window surface size for Linux (or FreeBSD) "
"XCB-backed surface.";
}
#endif
#if defined(FILAMENT_SUPPORTS_XLIB)
if (!useXcb) {
int screenNumber = DefaultScreen(g_x11.display);
Screen* screen = ScreenOfDisplay(g_x11.display, screenNumber);
surfaceExtent.width = static_cast<uint32_t>(WidthOfScreen(screen));
surfaceExtent.height = static_cast<uint32_t>(HeightOfScreen(screen));
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
<< "Unable to get window surface size for Linux (or FreeBSD) "
"XLib-backed surface.";
}
#endif
if (g_x11.openDisplay == nullptr) {
g_x11.openDisplay = reinterpret_cast<X11_OPEN_DISPLAY>(dlsym(g_x11.library, "XOpenDisplay"));
g_x11.display = g_x11.openDisplay(NULL);
FILAMENT_CHECK_PRECONDITION(g_x11.display) << "Unable to open X11 display.";
}
Window window { reinterpret_cast<Window const>(nativeWindow) };
XWindowAttributes windowAttributes;
Status ok { XGetWindowAttributes(g_x11.display, window, &windowAttributes) };
FILAMENT_CHECK_PRECONDITION(ok != 0) << " XGetWindowAttributes failed";
surfaceExtent.width = static_cast<uint32_t>(windowAttributes.width);
surfaceExtent.height = static_cast<uint32_t>(windowAttributes.height);
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
<< "Cannot get window surface size for X11 surface for Linux (or FreeBSD) OS "
"(not built with support for XCB or XLIB?)";
#elif defined(__linux__)
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
<< "Cannot get window surface size for Linux (or FreeBSD) OS "
"(not built with support for Wayland or X11?)";
"(not built with support for XLIB?)";
#else
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
<< "Not a supported (Linux) OS + WebGPU platform";
@@ -176,80 +128,40 @@ wgpu::Extent2D WebGPUPlatform::getSurfaceExtent(void* nativeWindow) const {
return surfaceExtent;
}
wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t flags) {
wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t /*flags*/) {
wgpu::Surface surface = nullptr;
#if defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND)
wl* ptrval = reinterpret_cast<wl*>(nativeWindow);
wgpu::SurfaceSourceWaylandSurface surfaceSourceWayland{};
surfaceSourceWayland.display = ptrval->display;
surfaceSourceWayland.surface = ptrval->surface;
wgpu::SurfaceDescriptor surfaceDescriptor{
wgpu::SurfaceDescriptor const surfaceDescriptor {
.nextInChain = &surfaceSourceWayland,
.label = "linux_wayland_surface"
};
surface = mInstance.CreateSurface(&surfaceDescriptor);
FILAMENT_CHECK_POSTCONDITION(surface != nullptr)
<< "Unable to create Linux Wayland-backed surface.";
#elif defined(LINUX_OR_FREEBSD) && defined(FILAMENT_SUPPORTS_X11)
#elif defined(LINUX_OR_FREEBSD) && defined(FILAMENT_SUPPORTS_X11) && defined(FILAMENT_SUPPORTS_XLIB)
if (g_x11.library == nullptr) {
g_x11.library = dlopen(LIBRARY_X11, RTLD_LOCAL | RTLD_NOW);
FILAMENT_CHECK_PRECONDITION(g_x11.library) << "Unable to open X11 library.";
#if defined(FILAMENT_SUPPORTS_XCB)
g_x11.xcbConnect = (XCB_CONNECT) dlsym(g_x11.library, "xcb_connect");
int screen = 0;
g_x11.connection = g_x11.xcbConnect(nullptr, &screen);
#endif
#if defined(FILAMENT_SUPPORTS_XLIB)
g_x11.openDisplay = (X11_OPEN_DISPLAY) dlsym(g_x11.library, "XOpenDisplay");
g_x11.display = g_x11.openDisplay(NULL);
FILAMENT_CHECK_PRECONDITION(g_x11.display) << "Unable to open X11 display.";
#endif
}
#if defined(FILAMENT_SUPPORTS_XCB) || defined(FILAMENT_SUPPORTS_XLIB)
bool useXcb = false;
#endif
#if defined(FILAMENT_SUPPORTS_XCB)
#if defined(FILAMENT_SUPPORTS_XLIB)
useXcb = (flags & SWAP_CHAIN_CONFIG_ENABLE_XCB) != 0;
#else
useXcb = true;
#endif
if (useXcb) {
wgpu::SurfaceSourceXCBWindow surfaceSourceXcb{};
surfaceSourceXcb.connection = g_x11.connection;
// TODO: this looks really wrong, please fix!!
surfaceSourceXcb.window = *((uint32_t*) nativeWindow);
wgpu::SurfaceDescriptor surfaceDescriptor{
.nextInChain = &surfaceSourceXcb,
.label = "linux_xcb_surface"
};
surface = mInstance.CreateSurface(&surfaceDescriptor);
FILAMENT_CHECK_POSTCONDITION(surface != nullptr)
<< "Unable to create Linux (or FreeBSD) XCB-backed surface.";
}
#endif
#if defined(FILAMENT_SUPPORTS_XLIB)
if (!useXcb) {
wgpu::SurfaceSourceXlibWindow surfaceSourceXlib{};
surfaceSourceXlib.display = g_x11.display;
surfaceSourceXlib.window = reinterpret_cast<uint64_t>(nativeWindow);
wgpu::SurfaceDescriptor surfaceDescriptor{
.nextInChain = &surfaceSourceXlib,
.label = "linux_xlib_surface"
};
surface = mInstance.CreateSurface(&surfaceDescriptor);
FILAMENT_CHECK_POSTCONDITION(surface != nullptr)
<< "Unable to create Linux (or FreeBSD) XLib-backed surface.";
}
#endif
if (g_x11.openDisplay == nullptr) {
g_x11.openDisplay = (X11_OPEN_DISPLAY) dlsym(g_x11.library, "XOpenDisplay");
g_x11.display = g_x11.openDisplay(NULL);
FILAMENT_CHECK_PRECONDITION(g_x11.display) << "Unable to open X11 display.";
}
wgpu::SurfaceSourceXlibWindow surfaceSourceXlib{};
surfaceSourceXlib.display = g_x11.display;
surfaceSourceXlib.window = reinterpret_cast<Window const>(nativeWindow);
wgpu::SurfaceDescriptor const surfaceDescriptor {
.nextInChain = &surfaceSourceXlib,
.label = "linux_xlib_surface"
};
surface = mInstance.CreateSurface(&surfaceDescriptor);
FILAMENT_CHECK_POSTCONDITION(surface != nullptr)
<< "Cannot create WebGPU X11 surface for Linux (or FreeBSD) OS "
"(not built with support for XCB or XLIB?)";
#elif defined(__linux__)
FILAMENT_CHECK_POSTCONDITION(surface != nullptr)
<< "Cannot create WebGPU surface for Linux (or FreeBSD) OS "
"(not built with support for Wayland or X11?)";
<< "Unable to create Linux (or FreeBSD) XLib-backed surface.";
#else
FILAMENT_CHECK_POSTCONDITION(surface != nullptr)
<< "Not a supported (Linux) OS + WebGPU platform";

View File

@@ -0,0 +1,45 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "AsyncTaskCounter.h"
#include "utils/debug.h"
#include <mutex>
namespace filament::backend::webgpuutils {
void AsyncTaskCounter::startTask() {
std::lock_guard<std::mutex> lock{ mMutex };
++mTasksInProgress;
}
void AsyncTaskCounter::finishTask() {
std::lock_guard<std::mutex> lock{ mMutex };
--mTasksInProgress;
assert_invariant(mTasksInProgress >= 0);
if (mTasksInProgress == 0) {
mFinishedCondition.notify_all();
}
}
void AsyncTaskCounter::waitForAllToFinish() {
std::unique_lock<std::mutex> lock{ mMutex };
mFinishedCondition.wait(lock, [this] { return mTasksInProgress == 0; });
}
} // namespace filament::backend::webgpuutils

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_UTIL_ASYNCWORKCOUNTER_H
#define TNT_FILAMENT_BACKEND_UTIL_ASYNCWORKCOUNTER_H
#include <condition_variable>
#include <mutex>
namespace filament::backend::webgpuutils {
/**
* Counts the number of asynchronous tasks of a given type.
* This can be used to wait for such a count to decrement to 0 (wait for work to complete) in a
* thead safe manner.
*/
class AsyncTaskCounter final {
public:
AsyncTaskCounter() = default;
AsyncTaskCounter(AsyncTaskCounter const&) = delete;
AsyncTaskCounter(AsyncTaskCounter const&&) = delete;
AsyncTaskCounter& operator=(AsyncTaskCounter const&) = delete;
AsyncTaskCounter& operator=(AsyncTaskCounter const&&) = delete;
~AsyncTaskCounter() = default;
/**
* Asynchronously (thread-safe) increase the work counter by one
*/
void startTask();
/**
* Asynchronously (thread-safe) decrease the work counter by one
*/
void finishTask();
/**
* Wait for all tasks to finish (for the work counter to reach 0) (thread-safe)
*/
void waitForAllToFinish();
private:
std::mutex mMutex;
std::condition_variable mFinishedCondition;
int mTasksInProgress{ 0 };
};
} // namespace filament::backend::webgpuutils
#endif // TNT_FILAMENT_BACKEND_UTIL_ASYNCWORKCOUNTER_H

View File

@@ -49,7 +49,7 @@ Backend parseArgumentsForBackend(int argc, char* argv[]) {
} else if (arg == "webgpu") {
backend = Backend::WEBGPU;
} else {
std::cerr << "Unrecognized target API. Must be 'opengl'|'vulkan'|'metal'."
std::cerr << "Unrecognized target API. Must be 'opengl'|'vulkan'|'metal'|'webgpu'."
<< std::endl
<< "Defaulting to OpenGL."
<< std::endl;

View File

@@ -85,6 +85,18 @@ std::filesystem::path ScreenshotParams::expectedFilePath() const {
return expectedDirectoryPath().append(expectedFileName());
}
std::filesystem::path ScreenshotParams::diffFilePath() const {
return diffDirectoryPath().append(diffFileName());
}
std::filesystem::path ScreenshotParams::diffDirectoryPath() {
return BackendTest::binaryDirectory().append("images/diff_images");
}
std::string ScreenshotParams::diffFileName() const {
return absl::StrFormat("%s_diff.png", mFileName);
}
const std::string ScreenshotParams::filePrefix() const {
// TODO(b/422804941): If there are platform specific goldens, when on those platforms append a
// unique platform identifying string to this.
@@ -128,8 +140,6 @@ void ImageExpectation::compareImage() const {
if (bytesFilled) {
#ifndef FILAMENT_IOS
LoadedPng loadedImage(mParams.expectedFilePath());
// Bytewise compare.
EXPECT_EQ(loadedImage.bytes().size(), mResult.bytes().size());
if (loadedImage.bytes().size() != mResult.bytes().size()) {
// Something is wrong with the size of the expected result, which usually means the file
// is missing. Fail the test and early return so later steps can assume the image is
@@ -137,18 +147,41 @@ void ImageExpectation::compareImage() const {
BackendTest::markImageAsFailure(mParams.filePrefix());
return;
}
// Initialize the vector to full saturation on all channels, bad pixels will have
// color channels but not alpha set to 0 to produce highly contrasting black pixels.
std::vector<unsigned char> imageDiff( loadedImage.bytes().size(), 255 );
int pixelDeviations = 0;
for (int i = 0; i < mResult.bytes().size(); ++i) {
if (std::abs( mResult.bytes()[i] - loadedImage.bytes()[i] ) >
mParams.pixelMatchThreshold()) {
pixelDeviations++;
for (int i = 0; i < mResult.bytes().size(); i += 4) {
// In order to handle color channels propoerly for the output, stride 4 bytes at a time.
// A failure of any byte in a pixel counts as a failure of the whole pixel.
for (int j = 0; j < 4; ++j) {
if( std::abs( mResult.bytes()[i+j] - loadedImage.bytes()[i+j] ) >
mParams.pixelMatchThreshold() ) {
pixelDeviations++;
imageDiff[i] = 0;
imageDiff[i+1] = 0;
imageDiff[i+2] = 0;
break;
}
}
}
EXPECT_LE(pixelDeviations, mParams.allowedPixelDeviations());
if (pixelDeviations > mParams.allowedPixelDeviations()) {
BackendTest::markImageAsFailure(mParams.filePrefix());
image::LinearImage image;
image = image::toLinearWithAlpha<uint8_t>(
mParams.width(), mParams.height(),
mParams.width() * 4, (uint8_t *)imageDiff.data(),
[](uint8_t value) -> float { return value; },
[](filament::math::float4 rgba) -> filament::math::float4 { return rgba; });
std::string filePath = mParams.diffFilePath();
std::ofstream pngStream(filePath, std::ios::binary | std::ios::trunc);
// To avoid going from linear -> sRGB -> linear save the PNG as linear.
image::ImageEncoder::encode(pngStream, image::ImageEncoder::Format::PNG_LINEAR, image, "",
filePath);
}
// TODO: Add better debug output, such as generating a diff image.
EXPECT_LE(pixelDeviations, mParams.allowedPixelDeviations());
#else
// For builds that can't load PNGs (currently iOS only) use the expected hash.
uint32_t actualHash = mResult.hash();

View File

@@ -58,6 +58,9 @@ public:
static std::filesystem::path expectedDirectoryPath();
std::string expectedFileName() const;
std::filesystem::path expectedFilePath() const;
static std::filesystem::path diffDirectoryPath();
std::string diffFileName() const;
std::filesystem::path diffFilePath() const;
const std::string filePrefix() const;
int allowedPixelDeviations() const;
int pixelMatchThreshold() const;

View File

@@ -19,6 +19,8 @@ code. When cmake is run it will copy this directory to the build output creating
The unit tests will then write their resulting images into `images/actual_images` in order to make
the tests easier to debug.
If an image comparison test fails, it writes a diff image to `images/diff_images` where each pixel of the diff is white (255,255,255) if the comparison for the corresponding pixel succeeds and black (0,0,0) of the comparison for the corresponding pixel fails.
### Python utility for updating golden images and comparing results
Inside the unit test source code directory there is a python script called

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_BACKEND_TEST_WORKAROUNDS_H
#define TNT_BACKEND_TEST_WORKAROUNDS_H
#if defined(FILAMENT_SUPPORTS_WEBGPU)
// consider, if needed, adding (as Vulkan also has transient attachments): || defined(FILAMENT_SUPPORTS_VULKAN)
// TODO: support TextureUsage::READ_PIXELS (or something like that)
// and search-and-replace TEXTURE_USAGE_READ_PIXELS with
// "| TextureUsage::READ_PIXELS" when it is available.
// Until then, this serves as a workaround for WebGPU testing, such that
// it can easily be swapped out for the "right" thing when that is available.
// The problem this is trying to address is that the WebGPU backend will
// assign a texture with wgpu::TextureUsage::TransientAttachment if the Filament
// usage flags don't indicate the pixels are needed CPU-side. If transient attachment is
// assigned to the texture, the texture will not be available later for readPixels, which
// is often needed for the unit tests. If we suggest that
// the texture needs to support being a blit source, then TransientAttachment will
// not get assigned. However, this is not quite right in the test, because a test
// may not really want to do a blit with the texture, just read the pixels from it later for
// image comparison (renderdiff).
// Ideally, the test could express this with a TextureUsage::READ_PIXELS flag, but that
// does not yet exist. So, a hack would be to explicitly set the BLIT_SRC flag instead in
// its place.
#define TEXTURE_USAGE_READ_PIXELS | TextureUsage::BLIT_SRC
#else
#define TEXTURE_USAGE_READ_PIXELS
#endif
#endif // TNT_BACKEND_TEST_WORKAROUNDS_H

View File

@@ -144,6 +144,7 @@ static void createFaces(DriverApi& dapi, Handle<HwTexture> texture, int baseWidt
}
TEST_F(BlitTest, ColorMagnify) {
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
auto& api = getDriverApi();
mCleanup.addPostCall([&]() { executeCommands(); });
@@ -160,17 +161,18 @@ TEST_F(BlitTest, ColorMagnify) {
api.makeCurrent(swapChain, swapChain);
// Create a source texture.
Handle<HwTexture> const srcTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
Handle<HwTexture> const srcTexture = mCleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT |
TextureUsage::BLIT_SRC));
const bool flipY = sBackend == Backend::OPENGL;
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 0, float3(0.5, 0, 0), flipY);
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 1, float3(0, 0, 0.5), flipY);
// Create a destination texture.
Handle<HwTexture> const dstTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
Handle<HwTexture> const dstTexture = mCleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT | TextureUsage::BLIT_DST));
// Create a RenderTarget for each texture's miplevel.
Handle<HwRenderTarget> srcRenderTargets[kNumLevels];
@@ -206,6 +208,7 @@ TEST_F(BlitTest, ColorMagnify) {
}
TEST_F(BlitTest, ColorMinify) {
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
auto& api = getDriverApi();
mCleanup.addPostCall([&]() { executeCommands(); });
@@ -222,17 +225,18 @@ TEST_F(BlitTest, ColorMinify) {
api.makeCurrent(swapChain, swapChain);
// Create a source texture.
Handle<HwTexture> const srcTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
Handle<HwTexture> const srcTexture = mCleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT |
TextureUsage::BLIT_SRC));
const bool flipY = sBackend == Backend::OPENGL;
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 0, float3(0.5, 0, 0), flipY);
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 1, float3(0, 0, 0.5), flipY);
// Create a destination texture.
Handle<HwTexture> const dstTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
Handle<HwTexture> const dstTexture = mCleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT | TextureUsage::BLIT_DST));
// Create a RenderTarget for each texture's miplevel.
Handle<HwRenderTarget> srcRenderTargets[kNumLevels];
@@ -260,6 +264,7 @@ TEST_F(BlitTest, ColorMinify) {
}
TEST_F(BlitTest, ColorResolve) {
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
NONFATAL_FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
"Nothing is drawn, see b/417229577");
auto& api = getDriverApi();
@@ -283,13 +288,12 @@ TEST_F(BlitTest, ColorResolve) {
// Create 4-sample texture.
Handle<HwTexture> const srcColorTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, 1, kColorTexFormat, kSampleCount, kSrcTexWidth, kSrcTexHeight,
1,
TextureUsage::COLOR_ATTACHMENT));
1, TextureUsage::COLOR_ATTACHMENT | TextureUsage::BLIT_SRC | TextureUsage::UPLOADABLE));
// Create 1-sample texture.
Handle<HwTexture> const dstColorTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, 1, kColorTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT | TextureUsage::BLIT_DST));
// Create a 4-sample render target with the 4-sample texture.
Handle<HwRenderTarget> const srcRenderTarget = mCleanup.add(api.createRenderTarget(
@@ -364,18 +368,18 @@ TEST_F(BlitTest, Blit2DTextureArray) {
api.makeCurrent(swapChain, swapChain);
// Create a source texture.
Handle<HwTexture> srcTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D_ARRAY, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth,
kSrcTexHeight, kSrcTexDepth,
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
Handle<HwTexture> srcTexture = mCleanup.add(api.createTexture(SamplerType::SAMPLER_2D_ARRAY,
kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, kSrcTexDepth,
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT |
TextureUsage::BLIT_SRC));
const bool flipY = sBackend == Backend::OPENGL;
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 0, kSrcTexLayer, float3(0.5, 0, 0),
flipY);
// Create a destination texture.
Handle<HwTexture> dstTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
Handle<HwTexture> dstTexture = mCleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT | TextureUsage::BLIT_DST));
// Create two RenderTargets.
const int level = 0;
@@ -429,17 +433,18 @@ TEST_F(BlitTest, BlitRegion) {
api.makeCurrent(swapChain, swapChain);
// Create a source texture.
Handle<HwTexture> srcTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
Handle<HwTexture> srcTexture = mCleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT |
TextureUsage::BLIT_SRC));
const bool flipY = sBackend == Backend::OPENGL;
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 0, float3(0.5, 0, 0), flipY);
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 1, float3(0, 0, 0.5), flipY);
// Create a destination texture.
Handle<HwTexture> dstTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
Handle<HwTexture> dstTexture = mCleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT | TextureUsage::BLIT_DST));
// Blit one-quarter of src level 1 to dst level 0.
Viewport srcRect = {
@@ -487,6 +492,7 @@ TEST_F(BlitTest, BlitRegion) {
TEST_F(BlitTest, BlitRegionToSwapChain) {
FAIL_IF(Backend::VULKAN, "Crashes due to not finding color attachment, see b/417481493");
SKIP_IF(Backend::WEBGPU, "WebGPU Crashes due to not finding color attachment");
auto& api = getDriverApi();
mCleanup.addPostCall([&]() { executeCommands(); });

View File

@@ -22,6 +22,7 @@
#include "SharedShaders.h"
#include "Skip.h"
#include "TrianglePrimitive.h"
#include "Workarounds.h"
namespace test {
@@ -155,8 +156,6 @@ TEST_F(BufferUpdatesTest, VertexBufferUpdate) {
// This test renders two triangles in two separate draw calls. Between the draw calls, a uniform
// buffer object is partially updated.
TEST_F(BufferUpdatesTest, BufferObjectUpdateWithOffset) {
NONFATAL_FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
"All values including alpha are written as 0, see b/417254943");
auto& api = getDriverApi();
Cleanup cleanup(api);
@@ -179,9 +178,9 @@ TEST_F(BufferUpdatesTest, BufferObjectUpdateWithOffset) {
shader.bindUniform<SimpleMaterialParams>(api, ubuffer, kBindingConfig);
// Create a render target.
auto colorTexture =
cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1,
screenWidth(), screenHeight(), 1, TextureUsage::COLOR_ATTACHMENT));
auto colorTexture = cleanup.add(
api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1, screenWidth(),
screenHeight(), 1, TextureUsage::COLOR_ATTACHMENT TEXTURE_USAGE_READ_PIXELS));
auto renderTarget = cleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR0, screenWidth(),
screenHeight(), 1, 0, { { colorTexture } }, {}, {}));

View File

@@ -104,7 +104,6 @@ TEST_F(BackendTest, FeedbackLoops) {
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::OPENGL),
"OpenGL image is upside down due to readPixels failing for texture with uploaded image "
"data");
FAIL_IF(Backend::WEBGPU, "BUG");
auto& api = getDriverApi();
Cleanup cleanup(api);
@@ -127,7 +126,7 @@ TEST_F(BackendTest, FeedbackLoops) {
TrianglePrimitive const triangle(getDriverApi());
// Create a texture.
auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE;
auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE;
Handle<HwTexture> const texture = cleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kTexFormat, 1, kTexWidth, kTexHeight, 1, usage));

View File

@@ -48,7 +48,7 @@ layout(location = 0) out vec4 fragColor;
// Filament's Vulkan backend requires a descriptor set index of 1 for all samplers.
// This parameter is ignored for other backends.
layout(location = 0, set = 0) uniform {samplerType} test_tex;
layout(binding = 0, set = 0) uniform {samplerType} test_tex;
void main() {
vec2 fbsize = vec2({texSize});
@@ -66,7 +66,7 @@ std::string fragmentUpdateImage3DTemplate (R"(#version 450 core
layout(location = 0) out vec4 fragColor;
// Filament's Vulkan backend requires a descriptor set index of 1 for all samplers.
layout(location = 0, set = 0) uniform {samplerType} test_tex;
layout(binding = 0, set = 0) uniform {samplerType} test_tex;
float getLayer(in sampler3D s) { return 2.5f / 4.0f; }
float getLayer(in sampler2DArray s) { return 2.0f; }
@@ -87,7 +87,7 @@ std::string fragmentUpdateImageMip (R"(#version 450 core
layout(location = 0) out vec4 fragColor;
// Filament's Vulkan backend requires a descriptor set index of 1 for all samplers.
layout(location = 0, set = 0) uniform sampler2D test_tex;
layout(binding = 0, set = 0) uniform sampler2D test_tex;
void main() {
vec2 fbsize = vec2({texSize});
@@ -231,6 +231,7 @@ public:
};
TEST_F(LoadImageTest, UpdateImage2D) {
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
FAIL_IF(Backend::VULKAN, "Multiple test cases crash, see b/417481434");
// All of these test cases should result in the same rendered image, and thus the same hash.
@@ -271,15 +272,18 @@ TEST_F(LoadImageTest, UpdateImage2D) {
// Test format conversion.
// TODO: Vulkan crashes with `Texture at colorAttachment[0] has usage (0x01) which doesn't specify MTLTextureUsageRenderTarget (0x04)'
// TODO: WebGPU Crashes with "destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment"
testCases.emplace_back("RGBA FLOAT to RGBA16F", PixelDataFormat::RGBA, PixelDataType::FLOAT, TextureFormat::RGBA16F);
// Test texture formats not all backends support natively.
// TODO: Vulkan crashes with "VK_FORMAT_R32G32B32_SFLOAT is not supported"
// TODO: WebGPU Crashes with "destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment"
testCases.emplace_back("RGB FLOAT to RGB32F", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB32F);
testCases.emplace_back("RGB FLOAT to RGB16F", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB16F);
// Test packed format uploads.
// TODO: Vulkan crashes with "Texture at colorAttachment[0] has usage (0x01) which doesn't specify MTLTextureUsageRenderTarget (0x04)"
// TODO: WebGPU Crashes with "destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment"
testCases.emplace_back("RGBA UINT_2_10_10_10_REV to RGB10_A2", PixelDataFormat::RGBA, PixelDataType::UINT_2_10_10_10_REV, TextureFormat::RGB10_A2);
testCases.emplace_back("RGB UINT_10F_11F_11F_REV to R11F_G11F_B10F", PixelDataFormat::RGB, PixelDataType::UINT_10F_11F_11F_REV, TextureFormat::R11F_G11F_B10F);
testCases.emplace_back("RGB HALF to R11F_G11F_B10F", PixelDataFormat::RGB, PixelDataType::HALF, TextureFormat::R11F_G11F_B10F);
@@ -287,9 +291,12 @@ TEST_F(LoadImageTest, UpdateImage2D) {
// Test integer format uploads.
// TODO: These cases fail on OpenGL and Vulkan.
// TODO: These cases now also fail on Metal, but at some point previously worked.
testCases.emplace_back("RGB_INTEGER UBYTE to RGB8UI", PixelDataFormat::RGB_INTEGER, PixelDataType::UBYTE, TextureFormat::RGB8UI);
testCases.emplace_back("RGB_INTEGER USHORT to RGB16UI", PixelDataFormat::RGB_INTEGER, PixelDataType::USHORT, TextureFormat::RGB16UI);
testCases.emplace_back("RGB_INTEGER INT to RGB32I", PixelDataFormat::RGB_INTEGER, PixelDataType::INT, TextureFormat::RGB32I);
// TODO: These cases fail for WebGPU. However, leaving them causes
// Tint Reader Error: error: sampled image must have float component type
// Beginning SpirV-output dump with ret 0
// testCases.emplace_back("RGB_INTEGER UBYTE to RGB8UI", PixelDataFormat::RGB_INTEGER, PixelDataType::UBYTE, TextureFormat::RGB8UI);
// testCases.emplace_back("RGB_INTEGER USHORT to RGB16UI", PixelDataFormat::RGB_INTEGER, PixelDataType::USHORT, TextureFormat::RGB16UI);
// testCases.emplace_back("RGB_INTEGER INT to RGB32I", PixelDataFormat::RGB_INTEGER, PixelDataType::INT, TextureFormat::RGB32I);
// Test uploads with buffer padding.
// TODO: Vulkan crashes with "Assertion failed: (offset + size <= allocationSize)"
@@ -563,7 +570,7 @@ TEST_F(LoadImageTest, UpdateImage3D) {
PixelDataType pixelType = PixelDataType::FLOAT;
TextureFormat textureFormat = TextureFormat::RGBA16F;
SamplerType samplerType = SamplerType::SAMPLER_2D_ARRAY;
TextureUsage usage = TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE;
TextureUsage usage = TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT;
// Create a platform-specific SwapChain and make it current.
auto swapChain = cleanup.add(createSwapChain());

View File

@@ -80,6 +80,7 @@ public:
};
TEST_F(ReadPixelsTest, ReadPixels) {
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
NONFATAL_FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
"Two cases fail, see b/417255941 and b/417255943");
// These test scenarios use a known hash of the result pixel buffer to decide pass / fail,

View File

@@ -46,8 +46,9 @@ Shader createShader(DriverApi& api, Cleanup& cleanup, Backend backend) {
// Rendering an external image without setting any data should not crash.
TEST_F(BackendTest, RenderExternalImageWithoutSet) {
SKIP_IF(Backend::METAL, "External images aren't supported in metal");
SKIP_IF(Backend::VULKAN, "External images aren't supported in vulkan");
SKIP_IF(Backend::METAL, "External images aren't supported in Metal");
SKIP_IF(Backend::VULKAN, "External images aren't supported in Vulkan");
SKIP_IF(Backend::WEBGPU, "External images aren't supported in WebGPU");
auto& api = getDriverApi();
Cleanup cleanup(api);
@@ -109,8 +110,9 @@ TEST_F(BackendTest, RenderExternalImageWithoutSet) {
}
TEST_F(BackendTest, RenderExternalImage) {
SKIP_IF(Backend::METAL, "External images aren't supported in metal");
SKIP_IF(Backend::VULKAN, "External images aren't supported in vulkan");
SKIP_IF(Backend::METAL, "External images aren't supported in Metal");
SKIP_IF(Backend::VULKAN, "External images aren't supported in Vulkan");
SKIP_IF(Backend::WEBGPU, "External images aren't supported in WebGPU");
auto& api = getDriverApi();
Cleanup cleanup(api);

View File

@@ -31,6 +31,7 @@ using namespace filament;
using namespace filament::backend;
TEST_F(BackendTest, ScissorViewportRegion) {
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
NONFATAL_FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
"Affected area in wrong corner, see b/417229118");
auto& api = getDriverApi();

View File

@@ -126,6 +126,7 @@ public:
};
TEST_F(BasicStencilBufferTest, StencilBuffer) {
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
"Stencil not supported, see b/417230776");
auto& api = getDriverApi();
@@ -152,6 +153,7 @@ TEST_F(BasicStencilBufferTest, StencilBuffer) {
}
TEST_F(BasicStencilBufferTest, DepthAndStencilBuffer) {
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
"Stencil not supported, see b/417230776");
auto& api = getDriverApi();
@@ -178,6 +180,7 @@ TEST_F(BasicStencilBufferTest, DepthAndStencilBuffer) {
}
TEST_F(BasicStencilBufferTest, StencilBufferMSAA) {
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
"Stencil not supported, see b/417230776");
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::OPENGL), "Stencil isn't applied");

View File

@@ -0,0 +1,171 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <gtest/gtest.h>
#include "webgpu/utils/AsyncTaskCounter.h"
#include <chrono>
#include <future>
#include <thread>
namespace test {
namespace {
constexpr int TOLERANCE_MILLISECONDS{ 5 };
constexpr int SMALLER_JOB_MILLISECONDS{ 10 };
static_assert(SMALLER_JOB_MILLISECONDS > TOLERANCE_MILLISECONDS);
constexpr int LARGER_JOB_MILLISECONDS{ 20 };
static_assert(LARGER_JOB_MILLISECONDS > (SMALLER_JOB_MILLISECONDS + TOLERANCE_MILLISECONDS));
} // namespace
TEST(AsyncTaskCounter, noWorkJobs) {
filament::backend::webgpuutils::AsyncTaskCounter counter{};
const auto waitFuture{ std::async(std::launch::async,
[&counter] { counter.waitForAllToFinish(); }) };
const auto status{ waitFuture.wait_for(std::chrono::milliseconds(TOLERANCE_MILLISECONDS)) };
ASSERT_NE(status, std::future_status::timeout)
<< "Timed out waiting for no work to finish. This should have returned immediately. "
"Tolerance/timeout: "
<< TOLERANCE_MILLISECONDS << " milliseconds.";
}
TEST(AsyncTaskCounter, oneWorkJob) {
filament::backend::webgpuutils::AsyncTaskCounter counter{};
const auto task{ [&counter]() {
std::this_thread::sleep_for(std::chrono::milliseconds(SMALLER_JOB_MILLISECONDS));
counter.finishTask();
} };
const auto startTime{ std::chrono::steady_clock::now() };
counter.startTask();
std::thread t{ task };
const auto waitFuture{ std::async(std::launch::async,
[&counter] { counter.waitForAllToFinish(); }) };
const auto expectedDuration{ SMALLER_JOB_MILLISECONDS };
const auto status{ waitFuture.wait_for(
std::chrono::milliseconds(expectedDuration + TOLERANCE_MILLISECONDS)) };
const auto endTime{ std::chrono::steady_clock::now() };
const auto duration{ std::chrono::duration_cast<std::chrono::milliseconds>(
endTime - startTime) };
ASSERT_NE(status, std::future_status::timeout)
<< "Timed out waiting for one work item to finish. Expected job duration: "
<< expectedDuration
<< " milliseconds. Timeout: " << (expectedDuration + TOLERANCE_MILLISECONDS)
<< " milliseconds.";
ASSERT_GT(duration, std::chrono::milliseconds(expectedDuration - TOLERANCE_MILLISECONDS))
<< "The one job should have taken at least its expected duration of "
<< expectedDuration << " milliseconds (" << TOLERANCE_MILLISECONDS
<< " milliseconds tolerance), but took " << duration << " milliseconds";
t.join();
}
TEST(AsyncTaskCounter, multipleWorkJobs) {
filament::backend::webgpuutils::AsyncTaskCounter counter{};
const auto smallerTasks{ [&counter]() {
std::this_thread::sleep_for(std::chrono::milliseconds(SMALLER_JOB_MILLISECONDS));
// validates the counter incrementing and decrementing before the work is done.
counter.startTask();
counter.finishTask();
std::this_thread::sleep_for(std::chrono::milliseconds(SMALLER_JOB_MILLISECONDS));
counter.finishTask();
} };
const auto largerTask{ [&counter]() {
std::this_thread::sleep_for(std::chrono::milliseconds(LARGER_JOB_MILLISECONDS));
counter.finishTask();
} };
const auto startTime{ std::chrono::steady_clock::now() };
counter.startTask();
std::thread t1{ smallerTasks };
counter.startTask();
std::thread t2{ largerTask };
const auto expectedDuration{ std::max(SMALLER_JOB_MILLISECONDS * 2, LARGER_JOB_MILLISECONDS) };
const auto waitFuture{ std::async(std::launch::async,
[&counter] { counter.waitForAllToFinish(); }) };
const auto status{ waitFuture.wait_for(
std::chrono::milliseconds(expectedDuration + TOLERANCE_MILLISECONDS)) };
const auto endTime{ std::chrono::steady_clock::now() };
const auto duration{ std::chrono::duration_cast<std::chrono::milliseconds>(
endTime - startTime) };
ASSERT_NE(status, std::future_status::timeout)
<< "Timed out waiting for 3 work items to finish. Expected job duration: "
<< expectedDuration
<< " milliseconds. Timeout: " << (expectedDuration + TOLERANCE_MILLISECONDS)
<< " milliseconds.";
ASSERT_GT(duration, std::chrono::milliseconds(expectedDuration - TOLERANCE_MILLISECONDS))
<< "The jobs should have taken at least their expected duration of " << expectedDuration
<< " milliseconds (" << TOLERANCE_MILLISECONDS << " milliseconds tolerance), but took "
<< duration << " milliseconds";
t1.join();
t2.join();
}
TEST(AsyncTaskCounter, counterReuse) {
filament::backend::webgpuutils::AsyncTaskCounter counter{};
const auto batchOfTasks{ [&counter]() {
std::this_thread::sleep_for(std::chrono::milliseconds(SMALLER_JOB_MILLISECONDS));
counter.startTask();
counter.finishTask();
std::this_thread::sleep_for(std::chrono::milliseconds(SMALLER_JOB_MILLISECONDS));
counter.finishTask();
} };
// first batch of tasks...
const auto firstStartTime{ std::chrono::steady_clock::now() };
counter.startTask();
std::thread firstBatch{ batchOfTasks };
const auto firstWaitFuture{ std::async(std::launch::async,
[&counter] { counter.waitForAllToFinish(); }) };
const auto expectedDuration{ SMALLER_JOB_MILLISECONDS * 2 };
const auto firstStatus{ firstWaitFuture.wait_for(
std::chrono::milliseconds(expectedDuration + TOLERANCE_MILLISECONDS)) };
const auto firstEndTime{ std::chrono::steady_clock::now() };
const auto firstDuration{ std::chrono::duration_cast<std::chrono::milliseconds>(
firstEndTime - firstStartTime) };
ASSERT_NE(firstStatus, std::future_status::timeout)
<< "Timed out waiting for first batch to finish. Expected job duration: "
<< expectedDuration
<< " milliseconds. Timeout: " << (expectedDuration + TOLERANCE_MILLISECONDS)
<< " milliseconds.";
ASSERT_GT(firstDuration, std::chrono::milliseconds(expectedDuration - TOLERANCE_MILLISECONDS))
<< "The first batch of tasks should have taken at least its expected duration of "
<< expectedDuration << " milliseconds (" << TOLERANCE_MILLISECONDS
<< " milliseconds tolerance), but took " << firstDuration << " milliseconds";
firstBatch.join();
// second batch of tasks...
const auto secondStartTime{ std::chrono::steady_clock::now() };
counter.startTask();
std::thread secondBatch{ batchOfTasks };
const auto secondWaitFuture{ std::async(std::launch::async,
[&counter] { counter.waitForAllToFinish(); }) };
const auto secondStatus{ secondWaitFuture.wait_for(
std::chrono::milliseconds(expectedDuration + TOLERANCE_MILLISECONDS)) };
const auto secondEndTime{ std::chrono::steady_clock::now() };
const auto secondDuration{ std::chrono::duration_cast<std::chrono::milliseconds>(
secondEndTime - secondStartTime) };
ASSERT_NE(secondStatus, std::future_status::timeout)
<< "Timed out waiting for second batch to finish. Expected job duration: "
<< expectedDuration
<< " milliseconds. Timeout: " << (expectedDuration + TOLERANCE_MILLISECONDS)
<< " milliseconds.";
ASSERT_GT(secondDuration, std::chrono::milliseconds(expectedDuration - TOLERANCE_MILLISECONDS))
<< "The second batch of tasks should have taken at least its expected duration of "
<< expectedDuration << " milliseconds (" << TOLERANCE_MILLISECONDS
<< " milliseconds tolerance), but took " << secondDuration << " milliseconds";
secondBatch.join();
}
} // namespace test

View File

@@ -87,6 +87,9 @@ public:
std::is_same_v<math::mat3f, T>
>;
template<typename T>
using is_supported_constant_parameter_t = std::enable_if_t<std::is_same_v<bool, T>>;
/**
* Creates a new MaterialInstance using another MaterialInstance as a template for initialization.
* The new MaterialInstance is an instance of the same Material of the template instance and
@@ -238,13 +241,13 @@ public:
/**
* Gets the value of a parameter by name.
*
*
* Note: Only supports non-texture parameters such as numeric and math types.
*
*
* @param name Name of the parameter as defined by Material. Cannot be nullptr.
* @param nameLength Length in `char` of the name parameter.
* @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled.
*
*
* @see Material::hasParameter
*/
template<typename T>
@@ -262,6 +265,53 @@ public:
return getParameter<T>(name, strlen(name));
}
/**
* Set a mutable constant parameter by name.
*
* @param name Name of the parameter as defined by Material. Cannot be nullptr.
* @param nameLength Length in `char` of the name parameter.
* @param value Value of the parameter to set.
* @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled.
*/
template<typename T, typename = is_supported_constant_parameter_t<T>>
void setConstant(const char* UTILS_NONNULL name, size_t nameLength, T const& value);
/** inline helper to provide the name as a null-terminated string literal */
template<typename T, typename = is_supported_constant_parameter_t<T>>
void setConstant(StringLiteral const name, T const& value) {
setConstant<T>(name.data, name.size, value);
}
/** inline helper to provide the name as a null-terminated C string */
template<typename T, typename = is_supported_constant_parameter_t<T>>
void setConstant(const char* UTILS_NONNULL name, T const& value) {
setConstant<T>(name, strlen(name), value);
}
/**
* Gets the value of a mutable constant parameter by name.
*
* @param name Name of the parameter as defined by Material. Cannot be nullptr.
* @param nameLength Length in `char` of the name parameter.
* @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled.
*
* @see Material::hasConstant
*/
template<typename T, typename = is_supported_constant_parameter_t<T>>
T getConstant(const char* UTILS_NONNULL name, size_t nameLength) const;
/** inline helper to provide the name as a null-terminated C string */
template<typename T, typename = is_supported_constant_parameter_t<T>>
T getConstant(StringLiteral const name) const {
return getConstant<T>(name.data, name.size);
}
/** inline helper to provide the name as a null-terminated C string */
template<typename T, typename = is_supported_constant_parameter_t<T>>
T getConstant(const char* UTILS_NONNULL name) const {
return getConstant<T>(name, strlen(name));
}
/**
* Set-up a custom scissor rectangle; by default it is disabled.
*

View File

@@ -437,7 +437,20 @@ struct AmbientOcclusionOptions {
struct Gtao {
uint8_t sampleSliceCount = 4; //!< # of slices. Higher value makes less noise.
uint8_t sampleStepsPerSlice = 3; //!< # of steps the radius is divided into for integration. Higher value makes less bias.
float thicknessHeuristic = 0.004f; //!< thickness heuristic, should be closed to 0
float thicknessHeuristic = 0.004f; //!< thickness heuristic, should be closed to 0. No effect when useVisibilityBitmasks sets to true.
/**
* Enables or disables visibility bitmasks mode. Notes that bent normal doesn't work under this mode.
* Caution: Changing this option at runtime is very expensive as it may trigger a shader re-compilation.
*/
bool useVisibilityBitmasks = false;
float constThickness = 0.5f; //!< constant thickness value of objects on the screen in world space. Only take effect when useVisibilityBitmasks is set to true.
/**
* Increase thickness with distance to maintain detail on distant surfaces.
* Caution: Changing this option at runtime is very expensive as it may trigger a shader re-compilation.
*/
bool linearThickness = false;
};
Gtao gtao; // %codegen_skip_javascript% %codegen_java_flatten%
};

View File

@@ -27,6 +27,7 @@
#include <utils/FixedCapacityVector.h>
#include <math/mathfwd.h>
#include <math/mat4.h>
#include <utility>
@@ -757,6 +758,29 @@ public:
//! debugging: returns a Camera from the point of view of *the* dominant directional light used for shadowing.
utils::FixedCapacityVector<Camera const*> getDirectionalShadowCameras() const noexcept;
//! debugging: enable or disable froxel visualisation for this view.
void setFroxelVizEnabled(bool enabled) noexcept;
//! debugging: returns information about the froxel configuration
struct FroxelConfigurationInfo {
uint8_t width;
uint8_t height;
uint8_t depth;
uint32_t viewportWidth;
uint32_t viewportHeight;
math::uint2 froxelDimension;
float zLightFar;
float linearizer;
math::mat4f p;
math::float4 clipTransform;
};
struct FroxelConfigurationInfoWithAge {
FroxelConfigurationInfo info;
uint32_t age;
};
FroxelConfigurationInfoWithAge getFroxelConfigurationInfo() const noexcept;
/** Result of a picking query */
struct PickingQueryResult {

View File

@@ -124,6 +124,20 @@ size_t Froxelizer::getFroxelBufferByteCount(FEngine::DriverApi& driverApi) noexc
return std::min(FROXEL_BUFFER_MAX_ENTRY_COUNT * sizeof(FroxelEntry), targetSize);
}
View::FroxelConfigurationInfo Froxelizer::getFroxelConfigurationInfo() const noexcept {
return { uint8_t(getFroxelCountX()),
uint8_t(getFroxelCountY()),
uint8_t(getFroxelCountZ()),
mViewport.width,
mViewport.height,
mFroxelDimension,
mZLightFar,
mLinearizer[0],
mProjection,
mClipTransform
};
}
Froxelizer::Froxelizer(FEngine& engine)
: mArena("froxel", PER_FROXELDATA_ARENA_SIZE),
mZLightNear(FROXEL_FIRST_SLICE_DEPTH),
@@ -198,10 +212,14 @@ void Froxelizer::setProjection(const mat4f& projection,
bool Froxelizer::prepare(
FEngine::DriverApi& driverApi, RootArenaScope& rootArenaScope,
filament::Viewport const& viewport,
const mat4f& projection, float const projectionNear, float const projectionFar) noexcept {
const mat4f& projection, float const projectionNear, float const projectionFar,
float4 const& clipTransform) noexcept {
setViewport(viewport);
setProjection(projection, projectionNear, projectionFar);
// Only for debugging
mClipTransform = clipTransform;
bool uniformsNeedUpdating = false;
if (UTILS_UNLIKELY(mDirtyFlags)) {
uniformsNeedUpdating = update();
@@ -361,8 +379,10 @@ bool Froxelizer::update() noexcept {
getFroxelBufferEntryCount(), viewport);
mFroxelDimension = froxelDimension;
mClipToFroxelX = (0.5f * float(viewport.width)) / float(froxelDimension.x);
mClipToFroxelY = (0.5f * float(viewport.height)) / float(froxelDimension.y);
// note: because froxelDimension is a power-of-two and viewport is an integer, mClipFroxel
// is an exact value (which is not true for 1/mClipToFroxelX, btw)
mClipToFroxelX = float(viewport.width) / float(2 * froxelDimension.x);
mClipToFroxelY = float(viewport.height) / float(2 * froxelDimension.y);
uniformsNeedUpdating = true;
@@ -408,8 +428,7 @@ bool Froxelizer::update() noexcept {
}
// for the inverse-transformation (view-space z to z-slice)
mLinearizer = 1.0f / linearizer;
mZLightFar = zLightFar;
mLinearizer = { linearizer, 1.0f / linearizer };
mParamsZ[0] = 0; // updated when camera changes
mParamsZ[1] = 0; // updated when camera changes
@@ -466,7 +485,7 @@ bool Froxelizer::update() noexcept {
// ==> i = log2(z_screen * (far/near)) * (-1/linearizer) + zcount
mParamsZ[0] = mZLightFar / Pw;
mParamsZ[1] = 0.0f;
mParamsZ[2] = -mLinearizer;
mParamsZ[2] = -mLinearizer[1];
} else {
// orthographic projection
// z_view = (1 - z_screen) * (near - far) - near
@@ -476,7 +495,7 @@ bool Froxelizer::update() noexcept {
// Pw = far / (far - near)
mParamsZ[0] = -1.0f / (Pz * mZLightFar); // -(far-near) / mZLightFar
mParamsZ[1] = Pw / (Pz * mZLightFar); // far / mZLightFar
mParamsZ[2] = mLinearizer;
mParamsZ[2] = mLinearizer[1];
}
uniformsNeedUpdating = true;
}
@@ -507,7 +526,7 @@ size_t Froxelizer::findSliceZ(float const z) const noexcept {
// This whole function is now branch-less.
int s = int( fast::log2(-z / mZLightFar) * mLinearizer + float(mFroxelCountZ) );
int s = int( fast::log2(-z / mZLightFar) * mLinearizer[1] + float(mFroxelCountZ) );
// there are cases where z can be negative here, e.g.:
// - the light is visible, but its center is behind the camera

View File

@@ -22,6 +22,7 @@
#include "details/Scene.h"
#include "details/Engine.h"
#include <filament/View.h>
#include <filament/Viewport.h>
#include <backend/Handle.h>
@@ -31,6 +32,7 @@
#include <utils/Slice.h>
#include <math/mat4.h>
#include <math/vec2.h>
#include <math/vec4.h>
namespace filament {
@@ -112,7 +114,8 @@ public:
* return true if updateUniforms() needs to be called
*/
bool prepare(backend::DriverApi& driverApi, RootArenaScope& rootArenaScope, Viewport const& viewport,
const math::mat4f& projection, float projectionNear, float projectionFar) noexcept;
const math::mat4f& projection, float projectionNear, float projectionFar,
math::float4 const& clipTransform) noexcept;
Froxel getFroxelAt(size_t x, size_t y, size_t z) const noexcept;
size_t getFroxelCountX() const noexcept { return mFroxelCountX; }
@@ -161,6 +164,8 @@ public:
static size_t getFroxelBufferByteCount(FEngine::DriverApi& driverApi) noexcept;
View::FroxelConfigurationInfo getFroxelConfigurationInfo() const noexcept;
private:
size_t getFroxelBufferEntryCount() const noexcept {
return mFroxelBufferEntryCount;
@@ -260,9 +265,10 @@ private:
uint16_t mFroxelCountZ = 0;
uint32_t mFroxelCount = 0;
math::uint2 mFroxelDimension = {};
math::float4 mClipTransform = { 1, 1, 0, 0 };
math::mat4f mProjection;
float mLinearizer = 0.0f;
math::float2 mLinearizer{};
float mClipToFroxelX = 0.0f;
float mClipToFroxelY = 0.0f;
backend::BufferObjectHandle mRecordsBuffer;

View File

@@ -233,6 +233,46 @@ template UTILS_PUBLIC mat3f MaterialInstance::getParameter<mat3f> (const ch
// ------------------------------------------------------------------------------------------------
template<>
inline void FMaterialInstance::setConstantImpl<bool>(std::string_view const name, bool const& value) {
std::optional<uint32_t> id = mMaterial->getMutableConstantId(name);
FILAMENT_CHECK_PRECONDITION(id.has_value()) << "No mutable constant with name " << name;
mConstants.set(*id, value);
}
template<typename T, typename>
void MaterialInstance::setConstant(const char* name, size_t nameLength, T const& value) {
downcast(this)->setConstantImpl({ name, nameLength }, value);
}
// Explicit template instantiation of our supported types.
//
// Mutable spec constants will probably only ever allow bools, but it's nice to keep the API
// forwards-compatible.
template UTILS_PUBLIC void MaterialInstance::setConstant<bool>(const char* name, size_t nameLength, bool const& v);
// ------------------------------------------------------------------------------------------------
template<>
inline bool FMaterialInstance::getConstantImpl<bool>(std::string_view const name) const {
std::optional<uint32_t> id = mMaterial->getMutableConstantId(name);
FILAMENT_CHECK_PRECONDITION(id.has_value()) << "No mutable constant with name " << name;
return mConstants[*id];
}
template<typename T, typename>
T MaterialInstance::getConstant(const char* name, size_t nameLength) const {
return downcast(this)->getConstantImpl<T>({ name, nameLength });
}
// Explicit template instantiation of our supported types.
//
// Mutable spec constants will probably only ever allow bools, but it's nice to keep the API
// forwards-compatible.
template UTILS_PUBLIC bool MaterialInstance::getConstant<bool>(const char* name, size_t nameLength) const;
// ------------------------------------------------------------------------------------------------
Material const* MaterialInstance::getMaterial() const noexcept {
return downcast(this)->getMaterial();
}

View File

@@ -229,6 +229,11 @@ bool MaterialParser::getConstants(FixedCapacityVector<MaterialConstant>* contain
return get<ChunkMaterialConstants>(container);
}
bool MaterialParser::getMutableConstants(
FixedCapacityVector<MaterialMutableConstant>* container) const noexcept {
return get<ChunkMaterialMutableConstants>(container);
}
bool MaterialParser::getPushConstants(CString* structVarName,
FixedCapacityVector<MaterialPushConstant>* value) const noexcept {
auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialPushConstants);
@@ -761,6 +766,38 @@ bool ChunkMaterialConstants::unflatten(Unflattener& unflattener,
return true;
}
bool ChunkMaterialMutableConstants::unflatten(Unflattener& unflattener,
FixedCapacityVector<MaterialMutableConstant>* materialConstants) {
assert_invariant(materialConstants);
// Read number of constants.
uint64_t numConstants = 0;
if (!unflattener.read(&numConstants)) {
return false;
}
materialConstants->reserve(numConstants);
materialConstants->resize(numConstants);
for (uint64_t i = 0; i < numConstants; i++) {
CString constantName;
bool defaultValue;
if (!unflattener.read(&constantName)) {
return false;
}
if (!unflattener.read(&defaultValue)) {
return false;
}
(*materialConstants)[i].name = std::move(constantName);
(*materialConstants)[i].defaultValue = defaultValue;
}
return true;
}
bool ChunkMaterialPushConstants::unflatten(Unflattener& unflattener,
CString* structVarName,
FixedCapacityVector<MaterialPushConstant>* materialPushConstants) {

View File

@@ -49,6 +49,7 @@ class BufferInterfaceBlock;
class SamplerInterfaceBlock;
struct SubpassInfo;
struct MaterialConstant;
struct MaterialMutableConstant;
struct MaterialPushConstant;
class MaterialParser {
@@ -79,6 +80,8 @@ public:
bool getShaderModels(uint32_t* value) const noexcept;
bool getMaterialProperties(uint64_t* value) const noexcept;
bool getConstants(utils::FixedCapacityVector<MaterialConstant>* value) const noexcept;
bool getMutableConstants(utils::FixedCapacityVector<MaterialMutableConstant>* value)
const noexcept;
bool getPushConstants(utils::CString* structVarName,
utils::FixedCapacityVector<MaterialPushConstant>* value) const noexcept;
@@ -238,6 +241,13 @@ struct ChunkMaterialConstants {
static filamat::ChunkType const tag = filamat::MaterialConstants;
};
struct ChunkMaterialMutableConstants {
static bool unflatten(filaflat::Unflattener& unflattener,
utils::FixedCapacityVector<MaterialMutableConstant>* materialConstants);
using Container = utils::FixedCapacityVector<MaterialMutableConstant>;
static filamat::ChunkType const tag = filamat::MaterialMutableConstants;
};
struct ChunkMaterialPushConstants {
static bool unflatten(filaflat::Unflattener& unflattener, utils::CString* structVarName,
utils::FixedCapacityVector<MaterialPushConstant>* materialPushConstants);

Some files were not shown because too many files have changed in this diff Show More