Compare commits

..
Author SHA1 Message Date
Powei Feng 5a2b46e134 [WIP] Trying to fix issue 2025-08-14 23:48:03 -07:00
183 changed files with 11780 additions and 4232 deletions
@@ -1,23 +0,0 @@
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
+2 -3
View File
@@ -1,6 +1,5 @@
name: 'Get and cache Mesa'
description: 'Get and cache Mesa'
name: 'Get Mesa'
description: 'Caches and installs Mesa'
runs:
using: "composite"
steps:
+2 -6
View File
@@ -8,8 +8,8 @@ runs:
uses: actions/cache@v3
id: cache-vulkan-sdk
with:
path: ${{ runner.homedir }}/VulkanSDK
key: vulkansdk-${{ env.GITHUB_VULKANSDK_VERSION }}-2-${{ runner.os }}
path: vulkansdk
key: vulkansdk-${{ env.GITHUB_VULKANSDK_VERSION }}-${{ runner.os }}
- name: Download Vulkan SDK
if: steps.cache-vulkan-sdk.outputs.cache-hit != 'true'
run: |
@@ -21,8 +21,4 @@ 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
-2
View File
@@ -19,7 +19,5 @@ runs:
- name: Install Mac Prerequisites
shell: bash
run: |
# Install brew prereqs
brew install coreutils
# Install ninja
source ./build/common/get-ninja.sh
+180 -173
View File
@@ -9,36 +9,6 @@ on:
- main
jobs:
build-desktop-mac:
name: build-mac
runs-on: macos-14-xlarge
steps:
- uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- uses: ./.github/actions/mac-prereq
- name: Run build script
run: |
cd build/mac && printf "y" | ./build.sh presubmit
- name: Test material parser
run: |
out/cmake-release/filament/test/test_material_parser
build-desktop-linux:
name: build-linux
runs-on: ubuntu-22.04-16core
steps:
- uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- uses: ./.github/actions/linux-prereq
- name: Run build script
run: |
cd build/linux && printf "y" | ./build.sh presubmit
- name: Test material parser
run: |
out/cmake-release/filament/test/test_material_parser
build-windows:
name: build-windows
runs-on: windows-2022-32core
@@ -48,157 +18,194 @@ jobs:
fetch-depth: 0
- name: Run build script
run: |
build\windows\build-github.bat presubmit
build\windows\build-github.bat continuous
shell: cmd
build-android:
name: build-android
runs-on: 'ubuntu-24.04-16core'
steps:
- uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- uses: ./.github/actions/linux-prereq
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
- name: Run build script
# Only build 1 64 bit target during presubmit to cut down build times during presubmit
# Continuous builds will build everything
run: |
cd build/android && printf "y" | ./build.sh presubmit arm64-v8a
# build-desktop-mac:
# name: build-mac
# runs-on: macos-14-xlarge
# steps:
# - uses: actions/checkout@v4.1.6
# with:
# fetch-depth: 0
# - uses: ./.github/actions/mac-prereq
# - name: Run build script
# run: |
# cd build/mac && printf "y" | ./build.sh presubmit
# - name: Test material parser
# run: |
# out/cmake-release/filament/test/test_material_parser
build-ios:
name: build-iOS
runs-on: macos-14-xlarge
steps:
- uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- uses: ./.github/actions/mac-prereq
- name: Run build script
run: |
cd build/ios && printf "y" | ./build.sh presubmit
- name: Build iOS samples
run: |
cd build/ios && ./build-samples.sh presubmit
# build-desktop-linux:
# name: build-linux
# runs-on: ubuntu-22.04-16core
# steps:
# - uses: actions/checkout@v4.1.6
# with:
# fetch-depth: 0
# - uses: ./.github/actions/linux-prereq
# - name: Run build script
# run: |
# cd build/linux && printf "y" | ./build.sh presubmit
# - name: Test material parser
# run: |
# out/cmake-release/filament/test/test_material_parser
build-web:
name: build-web
runs-on: 'ubuntu-24.04-16core'
steps:
- uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- uses: ./.github/actions/linux-prereq
- uses: ./.github/actions/web-prereq
- name: Run build script
run: |
cd build/web && printf "y" | ./build.sh presubmit
# build-windows:
# name: build-windows
# runs-on: windows-2022-32core
# steps:
# - uses: actions/checkout@v4.1.6
# with:
# fetch-depth: 0
# - name: Run build script
# run: |
# build\windows\build-github.bat presubmit
# shell: cmd
validate-docs:
name: validate-docs
runs-on: 'ubuntu-24.04-4core'
steps:
- uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- id: get_commit_msg
uses: ./.github/actions/get-commit-msg
- name: Check for manual edits to /docs
run: |
COMMIT_ID=$(echo "${{ steps.get_commit_msg.outputs.msg }}" | head -n 1 | sed "s/commit //g")
bash docs_src/build/presubmit_check.sh ${COMMIT_ID}
# build-android:
# name: build-android
# runs-on: 'ubuntu-24.04-16core'
# steps:
# - uses: actions/checkout@v4.1.6
# with:
# fetch-depth: 0
# - uses: ./.github/actions/linux-prereq
# - uses: actions/setup-java@v3
# with:
# distribution: 'temurin'
# java-version: '17'
# - name: Run build script
# # Only build 1 64 bit target during presubmit to cut down build times during presubmit
# # Continuous builds will build everything
# run: |
# cd build/android && printf "y" | ./build.sh presubmit arm64-v8a
test-renderdiff:
name: test-renderdiff
runs-on: macos-14-xlarge
steps:
- 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
- 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_
GOLDEN_BRANCH=$(echo "${{ steps.get_commit_msg.outputs.msg }}" | python3 ${TEST_DIR}/src/commit_msg.py)
bash ${TEST_DIR}/generate.sh && \
python3 ${TEST_DIR}/src/golden_manager.py \
--branch=${GOLDEN_BRANCH} \
--output=${GOLDEN_OUTPUT_DIR}
# build-ios:
# name: build-iOS
# runs-on: macos-14-xlarge
# steps:
# - uses: actions/checkout@v4.1.6
# with:
# fetch-depth: 0
# - uses: ./.github/actions/mac-prereq
# - name: Run build script
# run: |
# cd build/ios && printf "y" | ./build.sh presubmit
# - name: Build iOS samples
# run: |
# cd build/ios && ./build-samples.sh presubmit
# Note that we need to upload the output even if comparison fails, so we undo `set -ex`
end_
# build-web:
# name: build-web
# runs-on: 'ubuntu-24.04-16core'
# steps:
# - uses: actions/checkout@v4.1.6
# with:
# fetch-depth: 0
# - uses: ./.github/actions/linux-prereq
# - uses: ./.github/actions/web-prereq
# - name: Run build script
# run: |
# cd build/web && printf "y" | ./build.sh presubmit
python3 ${TEST_DIR}/src/compare.py \
--src=${GOLDEN_OUTPUT_DIR} \
--dest=${RENDER_OUTPUT_DIR} \
--out=${DIFF_OUTPUT_DIR} 2>&1 | tee compare_output.txt
# validate-docs:
# name: validate-docs
# runs-on: 'ubuntu-24.04-4core'
# steps:
# - uses: actions/checkout@v4.1.6
# with:
# fetch-depth: 0
# - id: get_commit_msg
# uses: ./.github/actions/get-commit-msg
# - name: Check for manual edits to /docs
# run: |
# COMMIT_ID=$(echo "${{ steps.get_commit_msg.outputs.msg }}" | head -n 1 | sed "s/commit //g")
# bash docs_src/build/presubmit_check.sh ${COMMIT_ID}
if grep "Failed" compare_output.txt > /dev/null; then
DELIMITER="EOF_FILE_CONTENT_$(date +%s)" # Using timestamp to make it more unique
echo "err<<$DELIMITER" >> "$GITHUB_OUTPUT"
cat compare_output.txt >> "$GITHUB_OUTPUT"
echo "$DELIMITER" >> "$GITHUB_OUTPUT"
fi
- uses: actions/upload-artifact@v4
with:
name: presubmit-renderdiff-result
path: ./out/renderdiff
- name: Compare result
run: |
ERROR_STR="${{ steps.render_compare.outputs.err }}"
if [ -n "${ERROR_STR}" ]; then
echo "${ERROR_STR}"
exit 1
fi
# test-renderdiff:
# name: test-renderdiff
# runs-on: macos-14-xlarge
# steps:
# - uses: actions/checkout@v4.1.6
# with:
# fetch-depth: 0
# - 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
# shell: bash
# - name: Render and compare
# id: render_compare
# run: |
# TEST_DIR=test/renderdiff
# source ${TEST_DIR}/src/preamble.sh
# start_
# GOLDEN_BRANCH=$(echo "${{ steps.get_commit_msg.outputs.msg }}" | python3 ${TEST_DIR}/src/commit_msg.py)
# bash ${TEST_DIR}/generate.sh && \
# python3 ${TEST_DIR}/src/golden_manager.py \
# --branch=${GOLDEN_BRANCH} \
# --output=${GOLDEN_OUTPUT_DIR}
validate-wgsl-webgpu:
name: validate-wgsl-webgpu
runs-on: 'ubuntu-24.04-8core'
steps:
- uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- uses: ./.github/actions/linux-prereq
- name: Run build script
run: ./build.sh -W debug test_filamat filament
- name: Run test
run: ./out/cmake-debug/libs/filamat/test_filamat --gtest_filter=MaterialCompiler.Wgsl*
# # Note that we need to upload the output even if comparison fails, so we undo `set -ex`
# end_
test-code-correctness:
name: test-code-correctness
runs-on: 'macos-14-xlarge'
steps:
- uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- uses: ./.github/actions/mac-prereq
- name: Install clang-tidy and deps
run: |
pip install pyyaml
brew install llvm@${GITHUB_LLVM_VERSION}
sudo ln -s "$(brew --prefix llvm)@${GITHUB_LLVM_VERSION}/bin/clang-tidy" "/usr/local/bin/clang-tidy"
- name: Run build script
# We need to build before clang-tidy can run analysis
run: |
# This will build for all three desktop backends on mac
./build.sh -p desktop debug gltf_viewer
- name: Run test
run: bash test/code-correctness/test.sh
# python3 ${TEST_DIR}/src/compare.py \
# --src=${GOLDEN_OUTPUT_DIR} \
# --dest=${RENDER_OUTPUT_DIR} \
# --out=${DIFF_OUTPUT_DIR} 2>&1 | tee compare_output.txt
# if grep "Failed" compare_output.txt > /dev/null; then
# DELIMITER="EOF_FILE_CONTENT_$(date +%s)" # Using timestamp to make it more unique
# echo "err<<$DELIMITER" >> "$GITHUB_OUTPUT"
# cat compare_output.txt >> "$GITHUB_OUTPUT"
# echo "$DELIMITER" >> "$GITHUB_OUTPUT"
# fi
# - uses: actions/upload-artifact@v4
# with:
# name: presubmit-renderdiff-result
# path: ./out/renderdiff
# - name: Compare result
# run: |
# ERROR_STR="${{ steps.render_compare.outputs.err }}"
# if [ -n "${ERROR_STR}" ]; then
# echo "${ERROR_STR}"
# exit 1
# fi
# validate-wgsl-webgpu:
# name: validate-wgsl-webgpu
# runs-on: 'ubuntu-24.04-8core'
# steps:
# - uses: actions/checkout@v4.1.6
# with:
# fetch-depth: 0
# - uses: ./.github/actions/linux-prereq
# - name: Run build script
# run: ./build.sh -W debug test_filamat filament
# - name: Run test
# run: ./out/cmake-debug/libs/filamat/test_filamat --gtest_filter=MaterialCompiler.Wgsl*
# test-code-correctness:
# name: test-code-correctness
# runs-on: 'macos-14-xlarge'
# steps:
# - uses: actions/checkout@v4.1.6
# with:
# fetch-depth: 0
# - uses: ./.github/actions/mac-prereq
# - name: Install clang-tidy and deps
# run: |
# pip install pyyaml
# brew install llvm@${GITHUB_LLVM_VERSION}
# sudo ln -s "$(brew --prefix llvm)@${GITHUB_LLVM_VERSION}/bin/clang-tidy" "/usr/local/bin/clang-tidy"
# - name: Run build script
# # We need to build before clang-tidy can run analysis
# run: |
# # This will build for all three desktop backends on mac
# ./build.sh -p desktop debug gltf_viewer
# - name: Run test
# run: bash test/code-correctness/test.sh
+2 -1
View File
@@ -544,6 +544,7 @@ 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()
@@ -633,7 +634,7 @@ endif()
# With WebGPU, Tint does not support ClipDistance which is used in Stereo. Mentioned in comment
# https://github.com/google/dawn/blob/855d17b08abdf02f9142bf5a8f14d0ea088810a4/src/tint/lang/spirv/reader/ast_parser/function.cc#L4434
if (FILAMENT_SUPPORTS_WEBGPU)
set(MATC_API_FLAGS ${MATC_API_FLAGS} -a webgpu --variant-filter=stereo)
set(MATC_API_FLAGS ${MATC_API_FLAGS} -a webgpu --variant-filter=skinning,stereo)
endif()
# Disable ESSL 1.0 code generation.
+2 -1
View File
@@ -8,4 +8,5 @@ appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
## Release notes for next branch cut
- materials: added `getEyeFromViewMatrix()` for vertex shader [⚠️ **Recompile Materials**]
- Update CMake minimum version to 3.22.1
- material: Add a material parameter to control shadow far attenuation (b/436680157)
+6 -6
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.65.0'
implementation 'com.google.android.filament:filament-android:1.63.1'
}
```
@@ -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.65.0'
pod 'Filament', '~> 1.63.1'
```
## Documentation
@@ -222,7 +222,7 @@ MaterialInstance* materialInstance = material->createInstance();
```
To learn more about materials and `matc`, please refer to the
[materials documentation](https://google.github.io/filament/Materials.html).
[materials documentation](./docs/Materials.md.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](/BUILDING.md#running-the-native-samples).
[BUILDING.md](https://github.com/google/filament/blob/main/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`](/BUILDING.md#running-the-native-samples) or
[`cmgen`](https://github.com/google/filament/blob/main/BUILDING.md#running-the-native-samples) or
using the `libiblprefilter` library.
## How to make contributions
-9
View File
@@ -7,15 +7,6 @@ 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.65.0
lighting: the intermediate froxel record buffer is now dynamically sized [⚠️ **New Material Version**]
## 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**]
@@ -146,7 +146,7 @@ abstract class MaterialCompiler extends TaskWithBinary {
.gradleProperty("com.google.android.filament.include-webgpu")
.forUseAtConfigurationTime().present
if (include_webgpu) {
matcArgs += ['-a', 'webgpu', '--variant-filter=stereo']
matcArgs += ['-a', 'webgpu', '--variant-filter=skinning,stereo']
}
def mat_no_opt = providers
@@ -1949,18 +1949,6 @@ 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;
}
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.65.0
VERSION_NAME=1.63.1
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
-60
View File
@@ -1,60 +0,0 @@
# 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."
-3
View File
@@ -137,9 +137,6 @@ 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
+6 -1
View File
@@ -109,8 +109,13 @@ function install_mac() {
unzip -t vulkan_sdk.zip
fi
echo "recognized zip layout 'vulkan_sdk.zip' ${InstallVulkan}.app/Contents" >&2
${InstallVulkan}.app/Contents/MacOS/${InstallVulkan} --accept-licenses --default-answer --confirm-command install
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/
if [[ -d ${InstallVulkan}.app/Contents ]] ; then
sudo rm -rf "$sdk_temp"
rm -rf ${InstallVulkan}.app
fi
}
+1 -2
View File
@@ -5,5 +5,4 @@ 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_GLTF_SAMPLE_ASSETS_COMMIT=d441dfdb87413ff412c620849a649d61789a470f
GITHUB_VULKANSDK_VERSION=1.4.321.0
+3 -1
View File
@@ -135,7 +135,9 @@ set build_flags=-j %NUMBER_OF_PROCESSORS%
:: cmake --build . --target sample-resources --config %config% %build_flags% || exit /b
:: cmake --build . --target suzanne-resources --config %config% %build_flags% || exit /b
cmake --build . %INSTALL% --config %config% %build_flags% -- /m || exit /b
cmake --build . --target sample-resources --config %config% %build_flags% -- /m || exit /b
:: cmake --build . %INSTALL% --config %config% %build_flags% -- /m || exit /b
@echo off
echo Disk info after building variant: %variant%
+2 -4
View File
@@ -221,8 +221,6 @@ 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
@@ -231,7 +229,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_mdbook/src/notes</code> directory. No additional
Filament source tree. We collect them into the <code>docs_src/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>
@@ -244,7 +242,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_mdbook/src/notes</code></li>
<li>Place the document (file with extension <code>.md</code>) in <code>docs_src/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>
+9 -9
View File
@@ -181,7 +181,7 @@ important for <code>matc</code> (material compiler).</p>
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.64.0'
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>
@@ -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.64.0'
<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="../main/filament.html">Filament</a>, an in-depth explanation of
<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="../main/materials.html">Materials</a>, the full reference
<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="../notes/material_properties.html">Material Properties</a>, a reference
<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>
@@ -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="../main/materials.html">materials documentation</a>.</p>
<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)) {
@@ -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="building.html#running-the-native-samples">BUILDING.md</a>.</p>
<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>
@@ -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="building.html#running-the-native-samples"><code>cmgen</code></a> or
<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
@@ -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="https://github.com/google/filament/blob/main/LICENSE">LICENSE</a>.</p>
<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>
+563 -10
View File
@@ -1,11 +1,564 @@
<!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>
<!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>
</html>
+2 -2
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="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>
<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>
</ul>
</main>
+2 -2
View File
@@ -250,7 +250,7 @@ execution profile data.</p>
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../notes/performance_analysis.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<a rel="next prefetch" href="../notes/framegraph.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
@@ -264,7 +264,7 @@ execution profile data.</p>
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../notes/performance_analysis.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<a rel="next prefetch" href="../notes/framegraph.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
+2 -2
View File
@@ -331,7 +331,7 @@ nodes</li>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../notes/performance_analysis.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<a rel="prev" href="../notes/coverage.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
@@ -345,7 +345,7 @@ nodes</li>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../notes/performance_analysis.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<a rel="prev" href="../notes/coverage.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
-352
View File
@@ -1,352 +0,0 @@
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Performance analysis - 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="performance-analysis"><a class="header" href="#performance-analysis">Performance Analysis</a></h1>
<h2 id="android"><a class="header" href="#android">Android</a></h2>
<h3 id="prerequisites"><a class="header" href="#prerequisites">Prerequisites</a></h3>
<ul>
<li>Download and install Android GPU Inspector (AGI). See https://developer.android.com/agi.</li>
</ul>
<hr />
<h4 id="profiling"><a class="header" href="#profiling">Profiling</a></h4>
<ol>
<li>
<p>Before profiling the application or analyzing the performance in a consistent way, ideally
the <strong>GPU frequency on the target hardware should get locked</strong>. In order to do this, you need to do the following:</p>
<ol>
<li>Ensure your device is OEM unlocked. If your phone is carrier-locked, you may need
to wait a period, such as 60 or 90 days after activation to be eligible for unlocking.
Some phones don't support this at all. You will need to enable developer options
(e.g. Settings &gt; About Phone and tap the Build number 7 times). You need to go to
Settings &gt; System &gt; Developer options and toggle on "OEM unlocking".</li>
<li>Next, you need to unlock the phone. You will need to install Android SDK Platform
Tools on you computer, enable "USB debugging" in your phone's Settings &gt; System &gt; Developer options.</li>
<li>Connect the phone to the computer via a USB cable and run from the command line:
<pre><code class="language-shell">adb reboot bootloader
# once the phone is in bootloader mode, run the following to begin the unlocking
# process:
fastboot flashing unlock
</code></pre>
A warning will appear on your phone's screen. Use the volume buttons to navigate and
the power button to select the "Unlock the bootloader" option. The phone will perform
a factory data reset and reboot with an unlocked bootloader.</li>
<li>You would need to flash an image to the phone with root permissions, such as a *-userdebug or *-eng
build. One way to do this is with the <a href="https://flash.android.com/">Android Flash Tool</a>. Connect the
tool to your device and find a build to flash ending in <code>-userdebug</code> or <code>-eng</code>. Once you have that
selected run <code>Install build</code>.</li>
<li>Shell into your device as root and configure the gpu frequency to be locked, e.g.:
<pre><code class="language-shell">adb shell
su
# navigate to the system GPU directory. this varies on different phones. One phone might have
# it at /sys/class/kgsl/kgsl-3d0 and another might be in something similar, maybe with "mali" instead
# of "kgsl". At the time of writing this for the device at hand it was /sys/devices/platform/1f000000.mali
cd /sys/devices/platform/1f000000.mali
# get the current available GPU governors and frequencies.
# note that some systems may have these at gpu_available_governors and gpu_available_frequencies, but
# the system at the time of writing this had available_governors and available_frequences
cat available_governors
cat available_frequencies
# depending on the governors, you may want to set it prioritize performance over other things like
# battery. Some systems allow you to do this with something like (although, for the device used at the
# time of writing this did not have an equivalent option):
echo performance &gt; gpu_governor
# finally, lock your frequency in, usually to something high like 897 MHz. Some systems may have you
# pipe the value to gpu_min_freq and gpu_max_freq, but the system used at the time of writing this had:
echo 940000 &gt; hint_min_freq
echo 940000 &gt; hint_max_freq
# you can typically verify the GPU is running at that frequency consistently by running something like
# the following a few times over time, which should show the frequency you want to lock the device to:
cat cur_freq
</code></pre>
</li>
<li>You may need to re-apply the <code>hint_min_freq</code> just before starting the profiling trace and check before and after
that the frequency remained at the value expected. Some systems may adjust the frequency on you, but you
may want to ensure the frequency remains the same through the analysis.</li>
<li>The GPU frequency settings should be undone after restarting the device, but after you have done your app profiling,
you can revert the state of the device, such as the OS build image, back to the way you had it
initially, as needed.</li>
</ol>
</li>
<li>
<p>Build a release build of Filament with the applicable backend(s) enabled
<em>(+ any special flags for enabling sys strace. Nothing special is needed for Vulkan or WebGPU aside from
building a release build with no flags)</em> <em>(debug builds for this are useless)</em></p>
<pre><code class="language-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
</code></pre>
</li>
<li>
<p>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.
<em>(to determine how these numbers map to the backends, see the <code>enum class Backend</code>
definition in <a href="../../../../filament/backend/include/backend/DriverEnums.h">filament/filament/backend/include/backend/DriverEnums.h</a>)</em>:</p>
<pre><code class="language-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
</code></pre>
</li>
<li>
<p>Build and run a sample, e.g. sample-gltf-viewer, on your Android device
<em>(<a href="https://developer.android.com/studio">Android Studio</a> recommended)</em>.</p>
</li>
<li>
<p>Run <a href="https://developer.android.com/agi">AGI</a> 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.</p>
<ul>
<li>We typically only run "Capture System Profiler trace" <em>(not necessarily "Capture Frame Profiler trace")</em></li>
<li>When configuring the trace:
<ul>
<li>For both the WebGPU and Vulkan backends configure the profiler for the Vulkan API
<em>(since WebGPU should be using Vulkan under the hood as well)</em></li>
<li>Running for ~1 seconds should suffice</li>
<li>Hit the "Configure" button in Trace objects, select "Switch to advanced mode" and add:
<pre><code>data_sources {
config {
name: "track_event"
track_event_config {
disabled_categories: "*"
enabled_categories: "filament/filament"
enabled_categories: "filament/jobsystem"
enabled_categories: "filament/gltfio"
}
}
}
</code></pre>
</li>
</ul>
</li>
<li>One you open the trace, zoom into a series of frames to get a sense of generally how long they typically take
<em>(use <code>W</code>, <code>S</code>, <code>A</code> and <code>D</code> keys and mouse wheel for navigation)</em> and find a representative one.
We are most interested in the performance of the
<code>FEngine::loop</code> 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.</li>
</ul>
</li>
</ol>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../notes/coverage.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../notes/framegraph.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="prev" href="../notes/coverage.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../notes/framegraph.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>
</html>
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -5
View File
@@ -65,9 +65,6 @@ 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
@@ -78,7 +75,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_mdbook/src/notes` directory. No additional
Filament source tree. We collect them into the `docs_src/src/notes` directory. No additional
processing is needed for these documents.
### Raw source files
@@ -93,7 +90,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_mdbook/src/notes`
- Place the document (file with extension `.md`) in `docs_src/src/notes`
- Add a link in [`docs_src/src_mdbook/src/SUMMARY.md`]
- Run the commands in the [Generate](#how-to-generate) section
+1 -6
View File
@@ -2,14 +2,9 @@
"README.md": {
"dest": "dup/intro.md",
"link_transforms": {
"/BUILDING.md#running-the-native-samples": "building.md#running-the-native-samples",
"/BUILDING.md": "building.md",
"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"
}
},
-1
View File
@@ -24,7 +24,6 @@
- [Running with ASAN and UBSAN](./notes/asan_ubsan.md)
- [Using Instruments on macOS](./notes/instruments.md)
- [Code coverage analysis](./notes/coverage.md)
- [Performance analysis](./notes/performance_analysis.md)
- [Framegraph](./notes/framegraph.md)
- [Libraries](./notes/libs.md)
- [bluegl](./dup/bluegl.md)
+2 -2
View File
@@ -1,5 +1,5 @@
# Core Concepts
- [Filament](filament.md) - High-level designs; Filament's PBR/math assumptions; implementation details.
- [Materials](materials.md) - A guide to Filament's material definition.
- [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.
@@ -1,129 +0,0 @@
# Performance Analysis
## Android
### Prerequisites
- Download and install Android GPU Inspector (AGI). See https://developer.android.com/agi.
---
#### Profiling
1. Before profiling the application or analyzing the performance in a consistent way, ideally
the **GPU frequency on the target hardware should get locked**. In order to do this, you need to do the following:
1. Ensure your device is OEM unlocked. If your phone is carrier-locked, you may need
to wait a period, such as 60 or 90 days after activation to be eligible for unlocking.
Some phones don't support this at all. You will need to enable developer options
(e.g. Settings > About Phone and tap the Build number 7 times). You need to go to
Settings > System > Developer options and toggle on "OEM unlocking".
1. Next, you need to unlock the phone. You will need to install Android SDK Platform
Tools on you computer, enable "USB debugging" in your phone's Settings > System > Developer options.
1. Connect the phone to the computer via a USB cable and run from the command line:
```shell
adb reboot bootloader
# once the phone is in bootloader mode, run the following to begin the unlocking
# process:
fastboot flashing unlock
```
A warning will appear on your phone's screen. Use the volume buttons to navigate and
the power button to select the "Unlock the bootloader" option. The phone will perform
a factory data reset and reboot with an unlocked bootloader.
1. You would need to flash an image to the phone with root permissions, such as a *-userdebug or *-eng
build. One way to do this is with the [Android Flash Tool](https://flash.android.com/). Connect the
tool to your device and find a build to flash ending in `-userdebug` or `-eng`. Once you have that
selected run `Install build`.
1. Shell into your device as root and configure the gpu frequency to be locked, e.g.:
```shell
adb shell
su
# navigate to the system GPU directory. this varies on different phones. One phone might have
# it at /sys/class/kgsl/kgsl-3d0 and another might be in something similar, maybe with "mali" instead
# of "kgsl". At the time of writing this for the device at hand it was /sys/devices/platform/1f000000.mali
cd /sys/devices/platform/1f000000.mali
# get the current available GPU governors and frequencies.
# note that some systems may have these at gpu_available_governors and gpu_available_frequencies, but
# the system at the time of writing this had available_governors and available_frequences
cat available_governors
cat available_frequencies
# depending on the governors, you may want to set it prioritize performance over other things like
# battery. Some systems allow you to do this with something like (although, for the device used at the
# time of writing this did not have an equivalent option):
echo performance > gpu_governor
# finally, lock your frequency in, usually to something high like 897 MHz. Some systems may have you
# pipe the value to gpu_min_freq and gpu_max_freq, but the system used at the time of writing this had:
echo 940000 > hint_min_freq
echo 940000 > hint_max_freq
# you can typically verify the GPU is running at that frequency consistently by running something like
# the following a few times over time, which should show the frequency you want to lock the device to:
cat cur_freq
```
1. You may need to re-apply the `hint_min_freq` just before starting the profiling trace and check before and after
that the frequency remained at the value expected. Some systems may adjust the frequency on you, but you
may want to ensure the frequency remains the same through the analysis.
1. The GPU frequency settings should be undone after restarting the device, but after you have done your app profiling,
you can revert the state of the device, such as the OS build image, back to the way you had it
initially, as needed.
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.
-11
View File
@@ -1,11 +0,0 @@
<!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>
+1 -17
View File
@@ -258,8 +258,6 @@ 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
@@ -414,7 +412,6 @@ 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)
@@ -530,7 +527,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 -lX11)
target_link_libraries(${TARGET} PRIVATE -lxcb)
endif()
# ==================================================================================================
@@ -579,11 +576,6 @@ 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
test/test_WebGPUComposeSwizzle.cpp)
endif()
if (APPLE)
# Metal-specific tests
list(APPEND BACKEND_TEST_SRC
@@ -601,7 +593,6 @@ 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()
@@ -631,10 +622,6 @@ if (APPLE AND NOT IOS)
target_link_libraries(backend_test PRIVATE image imageio)
list(APPEND BACKEND_TEST_DEPS image)
endif()
if (FILAMENT_SUPPORTS_WEBGPU)
target_link_libraries(backend_test PRIVATE webgpu_dawn dawncpp_headers)
list(APPEND BACKEND_TEST_DEPS webgpu_dawn dawncpp_headers)
endif()
set(BACKEND_TEST_COMBINED_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/libbackendtest_combined.a")
combine_static_libs(backend_test "${BACKEND_TEST_COMBINED_OUTPUT}" "${BACKEND_TEST_DEPS}")
@@ -666,9 +653,6 @@ if (LINUX)
add_executable(backend_test_linux test/linux_runner.cpp ${BACKEND_TEST_SRC})
target_link_libraries(backend_test_linux PRIVATE ${BACKEND_TEST_LIBS})
set_target_properties(backend_test_linux PROPERTIES FOLDER Tests)
if (FILAMENT_SUPPORTS_WEBGPU)
target_link_libraries(backend_test_linux PRIVATE webgpu_dawn dawncpp_headers)
endif()
endif()
# ==================================================================================================
@@ -629,18 +629,6 @@ enum class PrimitiveType : uint8_t {
TRIANGLE_STRIP = 5 //!< triangle strip
};
[[nodiscard]] constexpr bool isStripPrimitiveType(const PrimitiveType type) {
switch (type) {
case PrimitiveType::POINTS:
case PrimitiveType::LINES:
case PrimitiveType::TRIANGLES:
return false;
case PrimitiveType::LINE_STRIP:
case PrimitiveType::TRIANGLE_STRIP:
return true;
}
}
/**
* Supported uniform types
*/
+1 -1
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 = true;
bool disableAmortizedShaderCompile = false;
/**
* Disable backend handles use-after-free checks.
@@ -19,16 +19,6 @@
#include <backend/Platform.h>
#if defined(__linux__) && defined(FILAMENT_SUPPORTS_X11)
// Resolve the conflicts between webgpu_cpp.h and X11 defines
#undef Always
#undef Success
#undef None
#undef True
#undef False
#undef Status
#undef Bool
#endif
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
@@ -40,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 : public Platform {
class WebGPUPlatform final : public Platform {
public:
WebGPUPlatform();
~WebGPUPlatform() override = default;
@@ -62,14 +52,6 @@ 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;
+5 -7
View File
@@ -97,13 +97,11 @@ void CommandStream::execute(void* buffer) {
}
}
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);
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);
}
});
@@ -118,10 +118,6 @@ 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("
+1 -1
View File
@@ -1948,7 +1948,7 @@ void OpenGLDriver::destroyTexture(Handle<HwTexture> th) {
if (UTILS_UNLIKELY(t->hwStream)) {
detachStream(t);
}
if (UTILS_UNLIKELY(t->externalTexture)) {
if (UTILS_UNLIKELY(t->target == SamplerType::SAMPLER_EXTERNAL)) {
mPlatform.destroyExternalImageTexture(t->externalTexture);
} else {
glDeleteTextures(1, &t->gl.id);
@@ -211,9 +211,7 @@ VkSemaphore VulkanCommandBuffer::submit() {
FVK_LOGI << "Submitting cmdbuffer=" << mBuffer
<< " wait=(";
for (size_t s = 0, count = mWaitSemaphores.size(); s < count; ++s) {
FVK_LOGI << "\tsemaphore=" << mWaitSemaphores[s] << "|stage="
<< mWaitSemaphoreStages[s]
<< (s < mWaitSemaphores.size() - 1 ? "\n" : "");
FVK_LOGI << mWaitSemaphores[s] << " ";
}
FVK_LOGI << ") "
<< " signal=" << mSubmission
@@ -436,13 +434,10 @@ bool VulkanCommands::flush() {
}
if (lastSubmit != VK_NULL_HANDLE) {
// Note that the stage we're waiting on is the fragment shader stage. This assumes
// that the subsequent command buffer will only depend on
// 1) fragment output of the previous command buffer
// 2) reading/writing of buffers (i.e. UBO) of the previous command buffer
// Restricting the wait stages will allow for vertex work to proceed (more overlapping
// that the subsequent command buffer will only dependent on the fragment output of the
// previous buffer, allowing for vertex work to proceed (more overlapping
// vertex/fragment work).
pool->waitFor(lastSubmit,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT);
pool->waitFor(lastSubmit, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
lastSubmit = VK_NULL_HANDLE;
}
dependency = pool->flush();
+1 -13
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 (UTILS_UNLIKELY(mExternalImageManager.isExternallySampledTexture(texture))) {
if (mExternalImageManager.isExternallySampledTexture(texture)) {
mExternalImageManager.bindExternallySampledTexture(set, binding, texture, params);
mAppState.hasBoundExternalImages = true;
} else {
@@ -1939,18 +1939,6 @@ 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);
}
@@ -121,7 +121,7 @@ fvkutils::DescriptorSetMask VulkanExternalImageManager::prepareBindSets(LayoutAr
}
bool VulkanExternalImageManager::hasExternalSampler(
fvkmemory::resource_ptr<VulkanDescriptorSet> set) const {
fvkmemory::resource_ptr<VulkanDescriptorSet> set) {
auto itr = std::find_if(mSetBindings.begin(), mSetBindings.end(),
[&](SetBindingInfo const& info) { return info.set == set; });
return itr != mSetBindings.end();
@@ -191,11 +191,9 @@ void VulkanExternalImageManager::updateSetAndLayout(
VkDescriptorSet const srcSet = oldSet != VK_NULL_HANDLE ? oldSet : set->getVkSet();
copySet(mPlatform->getDevice(), srcSet, newSet, copyBindings);
set->setExternalSamplerVkSet(newSet,
[&descriptorSetCache = mDescriptorSetCache, layoutCount = layout->count, newLayout,
newSet](VulkanDescriptorSet*) {
descriptorSetCache->manualRecycle(layoutCount, newLayout, newSet);
});
set->setExternalSamplerVkSet(newSet, [&](VulkanDescriptorSet*) {
mDescriptorSetCache->manualRecycle(layout->count, newLayout, newSet);
});
if (oldLayout != newLayout) {
layout->setExternalSamplerVkLayout(newLayout);
}
@@ -86,9 +86,9 @@ public:
VkSamplerYcbcrConversion conversion = VK_NULL_HANDLE;
};
bool hasExternalSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set) const;
private:
bool hasExternalSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set);
void updateSetAndLayout(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
+1 -6
View File
@@ -308,10 +308,7 @@ struct VulkanRenderTarget : private HwRenderTarget, fvkmemory::Resource {
// Creates a special "default" render target (i.e. associated with the swap chain)
explicit VulkanRenderTarget();
VulkanRenderTarget(VulkanRenderTarget&& target)
: HwRenderTarget(0, 0),
mOffscreen(false),
mProtected(false) {
VulkanRenderTarget(VulkanRenderTarget&& target) {
swap(std::move(target));
}
@@ -368,8 +365,6 @@ struct VulkanRenderTarget : private HwRenderTarget, fvkmemory::Resource {
private:
void swap(VulkanRenderTarget&& target) {
std::swap(width, target.width);
std::swap(height, target.height);
std::swap(mOffscreen, target.mOffscreen);
std::swap(mProtected, target.mProtected);
std::swap(mInfo, target.mInfo);
@@ -46,13 +46,17 @@ fvkmemory::resource_ptr<VulkanTimerQuery> VulkanQueryManager::getNextQuery(
return {};
}
bool found = false;
std::pair<uint32_t, uint32_t> queryIndices;
size_t const firstUnused = unused.firstSetBit();
{
unused.forEachSetBit([&](size_t index) {
if (found) {
return;
}
std::unique_lock<utils::Mutex> lock(mMutex);
mUsed.set(firstUnused);
queryIndices = { firstUnused * 2, firstUnused * 2 + 1 };
}
mUsed.set(index);
found = true;
queryIndices = std::make_pair(index * 2, index * 2 + 1);
});
return fvkmemory::resource_ptr<VulkanTimerQuery>::construct(resourceManager, queryIndices.first,
queryIndices.second);
}
@@ -106,7 +106,7 @@ VkComponentMapping composeSwizzle(VkComponentMapping const& prev, VkComponentMap
return { vals[0], vals[1], vals[2], vals[3] };
};
// We make sure all identities are mapped into respective channels so that actual channel
// We make sure all all identities are mapped into respective channels so that actual channel
// mapping will be passed onto the output.
VkComponentMapping const prevExplicit = identityToChannel(prev);
VkComponentMapping const nextExplicit = identityToChannel(next);
File diff suppressed because it is too large Load Diff
+24 -50
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,20 +206,9 @@ 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:
@@ -278,20 +267,22 @@ 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";
copyByteByByte(commandEncoder, args);
copy(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
@@ -336,6 +327,7 @@ 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,
@@ -446,7 +438,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()));
@@ -456,7 +448,7 @@ void WebGPUBlitter::blit(wgpu::Queue const& queue, wgpu::CommandEncoder const& c
renderPassEncoder.End();
}
void WebGPUBlitter::copyByteByByte(wgpu::CommandEncoder const& commandEncoder, BlitArgs const& args) {
void WebGPUBlitter::copy(wgpu::CommandEncoder const& commandEncoder, BlitArgs const& args) {
const wgpu::TexelCopyTextureInfo sourceCopyInfo{
.texture = args.source.texture,
.mipLevel = args.source.mipLevel,
@@ -513,7 +505,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;
@@ -524,8 +516,6 @@ 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,
@@ -539,9 +529,6 @@ 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) {
@@ -593,7 +580,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;
}
@@ -608,8 +595,6 @@ 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) {
@@ -622,8 +607,6 @@ 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) {
@@ -636,7 +619,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;
}
@@ -650,8 +633,6 @@ 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) {
@@ -664,9 +645,6 @@ 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) {
@@ -677,11 +655,11 @@ wgpu::BindGroupLayout WebGPUBlitter::createTextureBindGroupLayout(const SamplerM
.texture = {
.sampleType = depthSource
? wgpu::TextureSampleType::Depth
: (multisampledSource
? wgpu::TextureSampleType::UnfilterableFloat
: wgpu::TextureSampleType::Float), // only F32 scalar sample
// type supported for now
// (aside from depth)
: wgpu::TextureSampleType::Float, // only F32 scalar sample
// type supported for now
// (aside from depth)
// see assumptions listed
// in blit function
.viewDimension = sourceDimension,
.multisampled = multisampledSource,
},
@@ -715,7 +693,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;
}
@@ -729,8 +707,6 @@ 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) {
@@ -743,9 +719,6 @@ 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) {
@@ -757,8 +730,9 @@ wgpu::ShaderModule WebGPUBlitter::createShaderModule(
textureType = "texture_depth_2d";
}
} else {
// non-depth
if (multisampledSource) {
textureType = "texture_multisampled_2d<f32>";
textureType = "texture_multisampled_2d";
} else {
if (sourceDimension == wgpu::TextureViewDimension::e3D) {
textureType = "texture_3d<f32>";
@@ -811,7 +785,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;
}
+1 -17
View File
@@ -26,17 +26,6 @@
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 {
@@ -69,15 +58,10 @@ private:
/**
* ONLY should be called if canDoDirectCopy(...) is true (see it in WebGPUBlitter.cpp)
*/
void copyByteByByte(wgpu::CommandEncoder const&, BlitArgs const&);
void copy(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);
@@ -33,9 +33,6 @@ 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).
@@ -58,11 +55,6 @@ 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)
@@ -28,10 +28,6 @@ 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:
/**
@@ -50,8 +46,7 @@ protected:
private:
const wgpu::Buffer mBuffer;
// 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.
// FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS (e.g. 4) bytes to hold any extra chunk we need.
std::array<uint8_t, FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS> mRemainderChunk{};
};
@@ -40,8 +40,6 @@ 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 },
@@ -31,12 +31,6 @@ 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);
+3 -52
View File
@@ -21,7 +21,6 @@
#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
@@ -49,16 +48,8 @@ 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 & ~FWGPU_DEBUG_PROFILING)
#define FWGPU_DEBUG_PERFORMANCE FWGPU_DEBUG_SYSTRACE
#define FWGPU_DEBUG_EVERYTHING 0xFFFFFFFF
#if defined(FILAMENT_BACKEND_DEBUG_FLAG)
#define FWGPU_DEBUG_FORWARDED_FLAG (FILAMENT_BACKEND_DEBUG_FLAG & FWGPU_DEBUG_EVERYTHING)
@@ -67,53 +58,13 @@ constexpr size_t FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS = 4;
#endif
#ifndef NDEBUG
#define FWGPU_DEBUG_FLAGS (FWGPU_DEBUG_PERFORMANCE | FWGPU_DEBUG_FORWARDED_FLAG)
#define FWGPU_DEBUG_FLAGS FWGPU_DEBUG_FORWARDED_FLAG
#else
#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)
#define FWGPU_DEBUG_FLAGS 0
#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
@@ -42,9 +42,6 @@ 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 },
@@ -55,10 +52,11 @@ WebGPUDescriptorSet::WebGPUDescriptorSet(wgpu::BindGroupLayout const& layout,
for (size_t i = 0; i < bindGroupEntries.size(); ++i) {
mEntries[i].binding = bindGroupEntries[i].binding;
}
// Create a mapping from binding index to entry index for efficient lookup.
// Establish the size of entries based on the layout. This should be reliable and efficient.
assert_invariant(INVALID_INDEX > mEntryIndexByBinding.size());
std::fill(mEntryIndexByBinding.begin(), mEntryIndexByBinding.end(), INVALID_INDEX);
for (size_t i = 0; i < mEntryIndexByBinding.size(); i++) {
mEntryIndexByBinding[i] = INVALID_INDEX;
}
for (size_t index = 0; index < mEntries.size(); index++) {
wgpu::BindGroupEntry const& entry = mEntries[index];
assert_invariant(entry.binding < mEntryIndexByBinding.size());
@@ -68,20 +66,19 @@ WebGPUDescriptorSet::WebGPUDescriptorSet(wgpu::BindGroupLayout const& layout,
void WebGPUDescriptorSet::addEntry(const unsigned int index, wgpu::BindGroupEntry&& entry) {
if (mBindGroup) {
// 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.
// We will keep getting hits from future updates, but shouldn't do anything
// Filament guarantees this won't change after things have 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 (out of range or >= "
<< "impossible/invalid index for a descriptor/binding (our of range or >= "
"MAX_DESCRIPTOR_COUNT) "
<< index;
uint8_t entryIndex = mEntryIndexByBinding[index];
FILAMENT_CHECK_POSTCONDITION(entryIndex != INVALID_INDEX && entryIndex < mEntries.size())
<< "Invalid binding index: " << index;
<< "Invalid binding " << index;
entry.binding = index;
mEntries[entryIndex] = std::move(entry);
}
@@ -97,9 +94,8 @@ 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 the bind group is created, we can release the resources used to create it.
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
#if FWGPU_ENABLED(FWGPU_DEBUG_BIND_GROUPS)
FWGPU_LOGD << "WebGPUDescriptorSet (lockAndReturn):";
FWGPU_LOGD << " wgpu::BindGroupLayout handle: " << mLayout.Get();
@@ -32,46 +32,23 @@
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)
@@ -83,7 +60,6 @@ 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;
};
@@ -40,9 +40,7 @@ namespace filament::backend {
namespace {
/**
* Converts Filament shader stage flags to the corresponding WebGPU shader stage bitmask.
*/
// Convert Filament Shader Stage Flags bitmask to webgpu equivalent
[[nodiscard]] wgpu::ShaderStage filamentStageToWGPUStage(const ShaderStageFlags fFlags) {
wgpu::ShaderStage retStages = wgpu::ShaderStage::None;
if (any(ShaderStageFlags::VERTEX & fFlags)) {
@@ -72,6 +70,10 @@ 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);
@@ -85,11 +87,6 @@ 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;
@@ -181,13 +178,11 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const&
} else if (isCubeArrayTypeDescriptor(fEntry.type)) {
wEntry.texture.viewDimension = wgpu::TextureViewDimension::CubeArray;
}
// fEntry.count is unused currently
}
// 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) ;
std::string label = "layout_" + baseLabel + std::to_string(++layoutNum) ;
const wgpu::BindGroupLayoutDescriptor layoutDescriptor{
.label{label.c_str()},
.label{label.c_str()}, // Use .c_str() if label needs to be const char*
.entryCount = wEntries.size(),
.entries = wEntries.data()
};
@@ -27,11 +27,6 @@
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 {
+133 -370
View File
@@ -32,12 +32,10 @@
#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>
@@ -73,7 +71,7 @@ void setDefaultTargetFlags(WebGPURenderTarget& defaultRenderTarget,
const wgpu::TextureFormat depthFormat{ swapChain.getDepthFormat() };
TargetBufferFlags newTargetFlags{ filament::backend::TargetBufferFlags::NONE };
// Color is always assumed to be present in the default render target.
// Assuming Color always present in default render target.
newTargetFlags |= filament::backend::TargetBufferFlags::COLOR;
if (depthFormat != wgpu::TextureFormat::Undefined) {
if (hasDepth(depthFormat)) {
@@ -88,12 +86,6 @@ 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};
@@ -149,7 +141,6 @@ 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,
@@ -166,11 +157,9 @@ 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] = {};
}
@@ -180,11 +169,10 @@ 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 commands recorded so far.
// submit the command buffer thus far...
assert_invariant(mRenderPassEncoder == nullptr);
wgpu::CommandBufferDescriptor commandBufferDescriptor{
.label = "frame_command_after_flush",
@@ -201,24 +189,12 @@ 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 */) {
FWGPU_SYSTRACE_SCOPE();
mDevice.Tick();
mAdapter.GetInstance().ProcessEvents();
mReadPixelMapsCounter.waitForAllToFinish();
flush();
// Some applications/samples might call finish in the very beginning of an application before
// creating any resources/tasks to flush out the system on any pending work from previous runs.
// Currently, gltf_viewer sample on android exhibit this behavior.
// Bail out early to safeguard against those cases. Otherwise, OnSubmittedWorkDone will
// hang on work that was never submitted.
if (mCommandEncoder == nullptr) {
return;
}
// Wait for all previously submitted work to finish.
flush();
std::mutex syncPoint;
std::condition_variable syncCondition;
bool done = false;
@@ -238,43 +214,33 @@ void WebGPUDriver::finish(int /* dummy */) {
}
void WebGPUDriver::destroyRenderPrimitive(Handle<HwRenderPrimitive> rph) {
if (!rph) {
return;
if (rph) {
destructHandle<WebGPURenderPrimitive>(rph);
}
FWGPU_SYSTRACE_SCOPE();
destructHandle<WebGPURenderPrimitive>(rph);
}
void WebGPUDriver::destroyVertexBufferInfo(Handle<HwVertexBufferInfo> vertexBufferInfoHandle) {
if (!vertexBufferInfoHandle) {
return;
if (vertexBufferInfoHandle) {
destructHandle<WebGPUVertexBufferInfo>(vertexBufferInfoHandle);
}
FWGPU_SYSTRACE_SCOPE();
destructHandle<WebGPUVertexBufferInfo>(vertexBufferInfoHandle);
}
void WebGPUDriver::destroyVertexBuffer(Handle<HwVertexBuffer> vertexBufferHandle) {
if (!vertexBufferHandle) {
return;
if (vertexBufferHandle) {
destructHandle<WebGPUVertexBuffer>(vertexBufferHandle);
}
FWGPU_SYSTRACE_SCOPE();
destructHandle<WebGPUVertexBuffer>(vertexBufferHandle);
}
void WebGPUDriver::destroyIndexBuffer(Handle<HwIndexBuffer> indexBufferHandle) {
if (!indexBufferHandle) {
return;
if (indexBufferHandle) {
destructHandle<WebGPUIndexBuffer>(indexBufferHandle);
}
FWGPU_SYSTRACE_SCOPE();
destructHandle<WebGPUIndexBuffer>(indexBufferHandle);
}
void WebGPUDriver::destroyBufferObject(Handle<HwBufferObject> bufferObjectHandle) {
if (!bufferObjectHandle) {
return;
if (bufferObjectHandle) {
destructHandle<WebGPUBufferObject>(bufferObjectHandle);
}
FWGPU_SYSTRACE_SCOPE();
destructHandle<WebGPUBufferObject>(bufferObjectHandle);
}
void WebGPUDriver::destroyTexture(Handle<HwTexture> textureHandle) {
@@ -298,8 +264,10 @@ void WebGPUDriver::destroyRenderTarget(Handle<HwRenderTarget> renderTargetHandle
if (renderTarget == mCurrentRenderTarget) {
mCurrentRenderTarget = nullptr;
}
// The HwTexture handles stored within WGPURenderTarget are not owned by it,
// so they are not destroyed here. They are destroyed via WebGPUDriver::destroyTexture.
// 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.
destructHandle<WebGPURenderTarget>(renderTargetHandle);
}
}
@@ -344,14 +312,6 @@ 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>();
}
@@ -436,17 +396,9 @@ Handle<HwTexture> WebGPUDriver::createTextureExternalImagePlaneS() noexcept {
return allocHandle<WebGPUTexture>();
}
// ------------------------------------------------------------------------------------------------
// 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();
void WebGPUDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) {
mNativeWindow = nativeWindow;
assert_invariant(!mSwapChain);
wgpu::Surface surface = mPlatform.createSurface(nativeWindow, flags);
wgpu::Extent2D extent = mPlatform.getSurfaceExtent(mNativeWindow);
@@ -481,14 +433,12 @@ 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);
@@ -496,14 +446,12 @@ 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);
}
@@ -511,7 +459,6 @@ 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);
}
@@ -527,32 +474,13 @@ 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) {
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 const nextSwizzle{
.r = toWGPUComponentSwizzle(r),
.g = toWGPUComponentSwizzle(g),
.b = toWGPUComponentSwizzle(b),
.a = toWGPUComponentSwizzle(a),
};
// The WebGPUTexture constructor for swizzled views will handle composing the swizzle and
// creating the new texture view.
constructHandle<WebGPUTexture>(textureHandle, sourceTexture, nextSwizzle);
PANIC_POSTCONDITION("Swizzle WebGPU Texture is not supported");
}
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");
}
@@ -560,7 +488,6 @@ 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");
}
@@ -580,7 +507,6 @@ 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);
@@ -591,7 +517,6 @@ void WebGPUDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> renderPrimit
}
void WebGPUDriver::createProgramR(Handle<HwProgram> programHandle, Program&& program) {
FWGPU_SYSTRACE_SCOPE();
constructHandle<WebGPUProgram>(programHandle, mDevice, program);
}
@@ -609,7 +534,6 @@ 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,
@@ -620,7 +544,7 @@ void WebGPUDriver::createRenderTargetR(Handle<HwRenderTarget> renderTargetHandle
}
void WebGPUDriver::createFenceR(Handle<HwFence> fenceHandle, const int /* dummy */) {
// The handle is constructed synchronously in createFenceS.
// the handle was already constructed in createFenceS
const auto fence = handleCast<WebGPUFence>(fenceHandle);
assert_invariant(mQueue);
fence->addMarkerToQueueState(mQueue);
@@ -631,13 +555,11 @@ 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());
@@ -692,12 +614,14 @@ 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 mDevice.HasFeature(wgpu::FeatureName::TextureComponentSwizzle);
return false;
}
bool WebGPUDriver::isTextureFormatMipmappable(const TextureFormat format) {
@@ -872,10 +796,6 @@ 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,
@@ -892,15 +812,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
@@ -913,8 +833,10 @@ void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t
inputData->type) };
const wgpu::TextureFormat outputLinearFormat{ toLinearFormat(
texture->getTexture().GetFormat()) };
const bool doBlit{ conversionNecessary(inputPixelFormat, outputLinearFormat, inputData->type) };
const bool conversionNecessary{
inputPixelFormat != outputLinearFormat && inputData->type != PixelDataType::COMPRESSED
}; // compressed formats should never need conversion
const bool doBlit{ conversionNecessary };
#if FWGPU_ENABLED(FWGPU_DEBUG_UPDATE_IMAGE)
if (texture->width > 1000 && texture->height > 500) {
FWGPU_LOGD << "Update3DImage(..., level=" << level << ", xoffset=" << xoffset
@@ -979,7 +901,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 = 0,
.mipLevel = level,
.origin = { .x = 0, .y = 0, .z = 0 },
.aspect = texture->getAspect(),
} };
@@ -1034,8 +956,9 @@ void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t
mQueue.Submit(1, &blitCommand);
mCommandEncoder = nullptr;
}
stagingTexture.Destroy();
} else {
// Direct copy without a blit.
// not doing blit (copy byte-by-byte)...
const auto copyInfo { wgpu::TexelCopyTextureInfo{
.texture = texture->getTexture(),
.mipLevel = level,
@@ -1147,15 +1070,10 @@ 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);
@@ -1307,7 +1225,6 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
}
void WebGPUDriver::endRenderPass(int /* dummy */) {
FWGPU_SYSTRACE_SCOPE();
mRenderPassEncoder.End();
mRenderPassEncoder = nullptr;
}
@@ -1316,12 +1233,10 @@ void WebGPUDriver::nextSubpass(int) {
//todo
}
void WebGPUDriver::makeCurrent(Handle<HwSwapChain> drawSwapChain,
Handle<HwSwapChain> readSwapChain) {
FWGPU_SYSTRACE_SCOPE();
ASSERT_PRECONDITION_NON_FATAL(drawSwapChain == readSwapChain,
void WebGPUDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain> readSch) {
ASSERT_PRECONDITION_NON_FATAL(drawSch == readSch,
"WebGPU driver does not support distinct draw/read swap chains.");
auto swapChain = handleCast<WebGPUSwapChain>(drawSwapChain);
auto swapChain = handleCast<WebGPUSwapChain>(drawSch);
mSwapChain = swapChain;
assert_invariant(mSwapChain);
@@ -1334,16 +1249,13 @@ void WebGPUDriver::makeCurrent(Handle<HwSwapChain> drawSwapChain,
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> swapChainHandle) {
FWGPU_SYSTRACE_SCOPE();
void WebGPUDriver::commit(Handle<HwSwapChain> sch) {
if (UTILS_VERY_UNLIKELY(!mCommandEncoder)) {
// nothing to submit
if (UTILS_LIKELY(mSwapChain)) {
@@ -1394,41 +1306,34 @@ 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::stopCapture(int /* dummy */) {
void WebGPUDriver::popGroupMarker(int) {
//todo
}
void WebGPUDriver::startCapture(int) {
//todo
}
void WebGPUDriver::stopCapture(int) {
//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) {
const auto srcTarget{ handleCast<WebGPURenderTarget>(sourceRenderTargetHandle) };
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);
@@ -1443,172 +1348,116 @@ 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 (UTILS_UNLIKELY(x >= srcWidth || y >= srcHeight)) {
if (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;
}
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?";
}
// 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();
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};
const size_t dstBytesPerPixel = PixelBufferDescriptor::computePixelSize(
pixelBufferDescriptor.format, pixelBufferDescriptor.type);
const size_t srcBytesPerPixel = getWGPUTextureFormatPixelSize(srcTexture.GetFormat());
// 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);
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.";
// The subsequent read will be from the top-left of the new intermediate texture.
readX = 0;
readY = 0;
}
// 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);
// 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) };
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);
assert_invariant(stagingBuffer);
// 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 };
wgpu::CommandEncoder encoder = mDevice.CreateCommandEncoder();
assert_invariant(encoder);
const wgpu::TexelCopyTextureInfo source{
.texture = textureToReadFrom, // Read from the original or intermediate texture
.mipLevel = 0,
.origin = {.x = readX, .y = flippedY, .z = 0,},
// 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::TexelCopyBufferInfo destination{
.layout = {
.offset = 0,
.bytesPerRow = static_cast<uint32_t>(paddedBytesPerRow),
.rowsPerImage = actualHeight,
},
.buffer = stagingBuffer
wgpu::TexelCopyBufferInfo destination {
.layout = {
.offset = 0,
.bytesPerRow = static_cast<uint32_t>(paddedBytesPerRow),
.rowsPerImage = actualHeight,
},
.buffer = stagingBuffer
};
const wgpu::Extent3D copySize{
.width = actualWidth,
wgpu::Extent3D copySize{ .width = actualWidth,
.height = actualHeight,
.depthOrArrayLayers = 1,
};
mCommandEncoder.CopyTextureToBuffer(&source, &destination, &copySize);
mCommandBuffer = mCommandEncoder.Finish();
assert_invariant(mCommandBuffer);
mCommandEncoder = nullptr;
mQueue.Submit(1, &mCommandBuffer);
mCommandBuffer = nullptr;
.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);
// Map the buffer to read the data
struct UserData final {
struct UserData {
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 = actualHeight,
.driver = this,
.pixelBufferDescriptor = std::move(pixelBufferDescriptor),
.buffer = std::move(stagingBuffer),
.unpaddedBytesPerRow = unpaddedBytesPerRow,
.paddedBytesPerRow = paddedBytesPerRow,
.height = height,
.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) };
// 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);
}
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);
}
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());
}
@@ -1623,99 +1472,14 @@ void WebGPUDriver::blitDEPRECATED(TargetBufferFlags buffers,
Handle<HwRenderTarget> destinationRenderTargetHandle, const Viewport destinationViewport,
Handle<HwRenderTarget> sourceRenderTargetHandle, const Viewport sourceViewport,
const SamplerMagFilter filter) {
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;
}
PANIC_PRECONDITION("WebGPUDriver::blitDEPRECATED not supported");
}
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) {
FWGPU_SYSTRACE_SCOPE();
if (!mCommandEncoder) {
const wgpu::CommandEncoderDescriptor encoderDescriptor{
.label = "command_created_with_resolve",
};
mCommandEncoder = mDevice.CreateCommandEncoder(&encoderDescriptor);
}
FILAMENT_CHECK_PRECONDITION(mCommandEncoder)
<< "Resolve assumes there is a valid command encoder to piggyback on.";
FILAMENT_CHECK_PRECONDITION(mRenderPassEncoder == nullptr)
<< "Resolve cannot be called during an existing render pass";
const auto sourceTexture{ handleCast<WebGPUTexture>(sourceTextureHandle) };
@@ -1747,7 +1511,6 @@ 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
@@ -1806,7 +1569,6 @@ 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);
@@ -1843,10 +1605,13 @@ 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) {
@@ -1895,7 +1660,6 @@ 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());
@@ -1913,8 +1677,7 @@ void WebGPUDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> renderPrimitive
void WebGPUDriver::draw2(const uint32_t indexOffset, const uint32_t indexCount,
const uint32_t instanceCount) {
FWGPU_SYSTRACE_SCOPE();
// Bind groups are deferred until the actual draw call.
// We defer actually binding until we actually draw
for (size_t i = 0; i < MAX_DESCRIPTOR_SET_COUNT; i++) {
auto& binding = mCurrentDescriptorSets[i];
if (binding.bindGroup) {
@@ -1960,7 +1723,6 @@ 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()) {
@@ -1981,13 +1743,14 @@ 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 here.
// 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
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() };
@@ -2021,7 +1784,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 unbinds the descriptor set.
// An empty handle signifies we need to release this bind point
if (!descriptorSetHandle) {
mCurrentDescriptorSets[setIndex] = {};
return;
@@ -2151,4 +1914,4 @@ wgpu::AddressMode WebGPUDriver::fWrapModeToWAddressMode(const SamplerWrapMode& f
return wgpu::AddressMode::Undefined;
}
} // namespace filament::backend
} // namespace filament
+3 -9
View File
@@ -24,7 +24,6 @@
#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"
@@ -48,12 +47,8 @@
namespace filament::backend {
class WebGPUSwapChain;
/**
* 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.
* WebGPU backend (driver) implementation
*/
class WebGPUDriver final : public DriverBase {
public:
@@ -69,8 +64,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;
@@ -91,7 +86,6 @@ private:
spd::MipmapGenerator mSpdComputePassMipmapGenerator;
WebGPUMsaaTextureResolver mMsaaTextureResolver{};
WebGPUBlitter mBlitter;
webgpuutils::AsyncTaskCounter mReadPixelMapsCounter{};
struct DescriptorSetBindingInfo{
wgpu::BindGroup bindGroup;
+3 -2
View File
@@ -28,11 +28,12 @@ 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);
@@ -28,11 +28,6 @@ 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();
@@ -40,9 +35,6 @@ 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 };
};
@@ -30,10 +30,6 @@ 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);
@@ -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 MSAA resolve.";
renderPassEncoder.End(); // The resolve is implicit in the pass.
<< "Failed to create wgpu::RenderPassEncoder for WebGPUDriver::resolve";
renderPassEncoder.End(); // only the implicit resolve is happening in the pass
}
} // namespace
@@ -59,29 +59,30 @@ 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",
@@ -115,7 +116,7 @@ void WebGPUMsaaTextureResolver::resolve(ResolveRequest const& request) {
<< "Failed to create wgpu::TextureView destinationTextureView.";
if (hasDepth(format)) {
PANIC_PRECONDITION("Depth resolve not yet implemented.");
PANIC_PRECONDITION("DEPTH RESOLVE NOT IMPLEMENTED YET");
} else {
resolveColorTextures(request.commandEncoder, sourceTextureView, destinationTextureView);
}
@@ -31,10 +31,6 @@ 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 {
@@ -51,9 +47,6 @@ public:
TextureInfo destination;
};
/**
* Resolves a multisampled texture into a single-sampled texture.
*/
void resolve(ResolveRequest const&);
};
@@ -54,7 +54,7 @@ namespace {
case CullingMode::FRONT: return wgpu::CullMode::Front;
case CullingMode::BACK: return wgpu::CullMode::Back;
case CullingMode::FRONT_AND_BACK:
// WebGPU does not support culling both front and back faces simultaneously.
// no WegGPU equivalent of front and back
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 "
@@ -293,11 +293,10 @@ wgpu::RenderPipeline WebGPUPipelineCache::createRenderPipeline(
},
.primitive = {
.topology = toWebGPU(request.primitiveType),
// TODO This won't always be the case for the primitives bound. But this particular field
// in the pipeline struct is only meant for restarting the strip, which we don't need so far.
.stripIndexFormat =
isStripPrimitiveType(request.primitiveType)? wgpu::IndexFormat::Uint16 :
wgpu::IndexFormat::Undefined,
// TODO should we assume some constant format here or is there a way to get
// this from PipelineState somehow or elsewhere?
// Perhaps, cache/assert format from index buffers as they are requested?
.stripIndexFormat = wgpu::IndexFormat::Undefined,
.frontFace = request.rasterState.inverseFrontFaces ? wgpu::FrontFace::CW : wgpu::FrontFace::CCW,
.cullMode = toWebGPU(request.rasterState.culling),
// TODO no depth clamp in WebGPU supported directly. unclippedDepth is close, so we are
@@ -313,11 +312,6 @@ wgpu::RenderPipeline WebGPUPipelineCache::createRenderPipeline(
},
.fragment = nullptr // will add below if fragment module is included
};
FILAMENT_CHECK_POSTCONDITION(!isStripPrimitiveType(request.primitiveType) ||
pipelineDescriptor.primitive.stripIndexFormat != wgpu::IndexFormat::Undefined)
<< "If the topology is a strip format, e.g. TriangleStrip, the stripIndexFormat cannot "
"be Undefined.";
wgpu::FragmentState fragmentState = {};
const wgpu::BlendState blendState {
.color = {
@@ -371,7 +365,6 @@ 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() };
@@ -34,11 +34,6 @@
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 {
@@ -68,9 +63,6 @@ public:
[[nodiscard]] wgpu::RenderPipeline const& getOrCreateRenderPipeline(
RenderPipelineRequest const&);
/**
* Should be called at the end of each frame to perform cache maintenance.
*/
void onFrameEnd();
private:
@@ -161,7 +153,7 @@ private:
// render targets... //
uint8_t multisampleCount{ 0 }; // 1 : 313
uint8_t colorFormatCount{ 0 }; // 1 : 314
uint8_t padding[5]{ 0 }; // 5 : 315
uint8_t padding[5]{ 0 }; // 5 : 319
TargetBufferFlags targetRenderFlags{ TargetBufferFlags::NONE }; // 4 : 320
wgpu::TextureFormat depthStencilFormat { wgpu::TextureFormat::Undefined }; // 4 : 324
wgpu::TextureFormat colorFormats[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT]{ //
@@ -77,27 +77,26 @@ 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)) {
// The pipeline layout has not been used recently, so we can remove it from the cache.
// pipeline layout expired...
iterator = mPipelineLayouts.erase(iterator);
} else {
// pipeline layout not yet expired...
++iterator;
}
}
@@ -31,11 +31,6 @@
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 {
@@ -50,16 +45,9 @@ 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:
@@ -71,10 +59,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 {
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 { // 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
};
static_assert(sizeof(PipelineLayoutKey) == 40,
"PipelineLayoutKey must not have implicit padding.");
@@ -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, so we don't need to replace it.
// not an assignment statement, so stream to the end of the statement and continue...
processedShaderSource << std::string_view(sourceData + pos,
posEndOfStatement + 1 - pos);
pos = posEndOfStatement + 1;
@@ -122,7 +122,10 @@ namespace {
}
const auto newValueItr = specConstants.find(static_cast<uint32_t>(constantId));
if (newValueItr == specConstants.end()) {
// The constant is not being overridden, so keep the default value.
// 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)...
processedShaderSource << std::string_view(sourceData + pos,
posEndOfStatement + 1 - pos);
pos = posEndOfStatement + 1;
@@ -171,7 +174,7 @@ namespace {
utils::FixedCapacityVector<uint8_t> const& sourceBytes =
shaderSource[static_cast<size_t>(stage)];
if (sourceBytes.empty()) {
return nullptr; // No shader source to compile.
return nullptr;// nothing to compile/create, the shader was not provided
}
std::stringstream labelStream;
labelStream << programName << " " << filamentShaderStageToString(stage) << " shader";
@@ -189,8 +192,7 @@ namespace {
};
const wgpu::ShaderModule shaderModule = device.CreateShaderModule(&descriptor);
const wgpu::Instance instance = device.GetAdapter().GetInstance();
// Synchronously compile the shader module.
// synchronously creates the shader module...
const wgpu::WaitStatus waitResult = instance.WaitAny(
shaderModule.GetCompilationInfo(wgpu::CallbackMode::WaitAnyOnly,
[&descriptor](auto const& status,
@@ -280,7 +282,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);
@@ -25,10 +25,6 @@ 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&);
@@ -27,8 +27,9 @@ namespace filament::backend {
namespace {
// 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.
// The shaders expect a single-sampled 2D texture
// with a format compatible with scalar sample format 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 };
@@ -84,7 +85,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;
}
@@ -97,7 +98,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;
}
@@ -130,7 +131,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;
}
@@ -144,7 +145,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;
}
@@ -193,7 +194,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;
}
@@ -209,7 +210,8 @@ 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:
@@ -218,16 +220,20 @@ 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.",
.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.",
};
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) };
@@ -236,19 +242,21 @@ 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.",
"sampler, which does not natively support linear filtering, "
"which is needed for render pass based mipmap generation.",
};
case ScalarSampleType::I32:
return {
.compatible = false,
.reason = "The provided texture format requires a signed integer "
"sampler, which does not natively support linear filtering.",
"sampler, which does not natively support linear filtering, "
"which is needed for render pass based mipmap generation.",
};
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 {
@@ -274,7 +282,7 @@ WebGPURenderPassMipmapGenerator::getCompatibilityFor(const wgpu::TextureFormat f
case wgpu::TextureDimension::e2D:
break;
}
// Check that the texture is single-sampled.
// check that the texture is single-sampled (for now)...
if (sampleCount > 1) {
return {
.compatible = false,
@@ -419,7 +427,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{
@@ -428,19 +436,20 @@ void WebGPURenderPassMipmapGenerator::generateMipmaps(wgpu::Queue const& queue,
const wgpu::CommandEncoder commandEncoder{ mDevice.CreateCommandEncoder(
&commandEncoderDescriptor) };
FILAMENT_CHECK_POSTCONDITION(commandEncoder)
<< "Failed to create command encoder for render pass mipmap generation.";
<< "Failed to create command encoder for layer 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 render pass mipmap generation.";
<< "Failed to create command buffer for layer for render pass mipmap generation?";
queue.Submit(1, &commandBuffer);
}
@@ -463,7 +472,7 @@ void WebGPURenderPassMipmapGenerator::generateMipmap(wgpu::CommandEncoder const&
.usage = wgpu::TextureUsage::TextureBinding,
};
const wgpu::TextureView sourceView{ texture.CreateView(&sourceViewDescriptor) };
FILAMENT_CHECK_POSTCONDITION(sourceView)
FILAMENT_CHECK_POSTCONDITION(pipeline)
<< "Failed to create source texture view for layer " << layer << " and mip level "
<< mipLevel << " for render pass mipmap generation?";
const wgpu::TextureViewDescriptor destinationViewDescriptor{
@@ -478,7 +487,7 @@ void WebGPURenderPassMipmapGenerator::generateMipmap(wgpu::CommandEncoder const&
.usage = wgpu::TextureUsage::RenderAttachment,
};
const wgpu::TextureView destinationView{ texture.CreateView(&destinationViewDescriptor) };
FILAMENT_CHECK_POSTCONDITION(destinationView)
FILAMENT_CHECK_POSTCONDITION(pipeline)
<< "Failed to create destination texture view for layer " << layer << " and mip level "
<< mipLevel << " for render pass mipmap generation?";
// create the render pass...
@@ -499,7 +508,7 @@ void WebGPURenderPassMipmapGenerator::generateMipmap(wgpu::CommandEncoder const&
.entries = textureBindGroupEntries,
};
const wgpu::BindGroup textureBindGroup{ mDevice.CreateBindGroup(&textureBindGroupDescriptor) };
FILAMENT_CHECK_POSTCONDITION(textureBindGroup)
FILAMENT_CHECK_POSTCONDITION(pipeline)
<< "Failed to create texture bind group for layer " << layer << " and mip level "
<< mipLevel << " for render pass mipmap generation?";
const wgpu::RenderPassColorAttachment colorAttachment{
@@ -525,7 +534,8 @@ 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 a full-screen triangle.
renderPassEncoder.Draw(3); // draw the full-screen triangle
// with hard-coded vertices in the shader
renderPassEncoder.End();
}
@@ -27,9 +27,6 @@
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 {
@@ -37,11 +34,6 @@ 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);
@@ -24,10 +24,6 @@ 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;
@@ -41,9 +41,9 @@ namespace filament::backend {
namespace {
/**
* 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.
* 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.
*/
[[nodiscard]] uint8_t createMsaaSidecarTextures(const uint8_t renderTargetSampleCount,
const TargetBufferFlags targetFlags, MRT const& colorAttachments,
@@ -100,6 +100,9 @@ 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);
@@ -133,7 +136,7 @@ WebGPURenderTarget::WebGPURenderTarget(const uint32_t width, const uint32_t heig
mColorAttachmentDesc.reserve(MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
}
// Constructor for the default render target
// Default constructor for the default render target
WebGPURenderTarget::WebGPURenderTarget()
: HwRenderTarget{ 0, 0 },
mDefaultRenderTarget{ true },
@@ -179,19 +182,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 an MSAA sidecar but at least "
<< "A color or depth/stencil attachment texture has a MSAA sidecar but at least "
"one other color attachment texture does not.";
FILAMENT_CHECK_PRECONDITION(customDepthStencilMsaaSidecarTextureView != nullptr)
<< "The color attachment texture(s) have MSAA sidecars but the depth/stencil "
<< "The color attachment texture(s) have MSAA sidecar(s) 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 an MSAA sidecar but "
<< "A color or depth/stencil attachment texture does not have a 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 sidecars but "
<< "Custom color textures for the render target do not have MSAA sidecar(s) but "
"the depth/stencil texture does.";
}
@@ -31,10 +31,6 @@
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
@@ -42,8 +38,7 @@ 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();
@@ -85,7 +80,7 @@ private:
MRT mColorAttachments{};
// TODO WebGPU only supports a DepthStencil attachment, should this be just
// mDepthStencilAttachment?
// mDepthStencilAttachment?
Attachment mDepthAttachment{};
Attachment mStencilAttachment{};
@@ -187,8 +187,7 @@ 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 |
wgpu::TextureUsage::TextureBinding;
config.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
config.width = extent.width;
config.height = extent.height;
config.format = selectColorFormat(capabilities.formatCount, capabilities.formats, useSRGBColorSpace);
@@ -270,6 +269,8 @@ 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
@@ -296,6 +297,8 @@ 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,
+96 -64
View File
@@ -107,6 +107,84 @@ 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) {
@@ -208,17 +286,13 @@ 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,
device.HasFeature(wgpu::FeatureName::TransientAttachments)) },
mViewUsage{ fToWGPUTextureUsage(usage, samples, false, false,
device.HasFeature(wgpu::FeatureName::TransientAttachments)) },
mDimension{ toWebGPUTextureViewDimension(samplerType) },
mMipmapGenerationStrategy == MipmapGenerationStrategy::RENDER_PASS) },
mViewUsage{ fToWGPUTextureUsage(usage, samples, false, false) },
mDimension{toWebGPUTextureViewDimension(samplerType)},
mBlockWidth{ filament::backend::getBlockWidth(format) },
mBlockHeight{ filament::backend::getBlockHeight(format) },
mDefaultMipLevel{ 0 },
mDefaultBaseArrayLayer{ 0 },
// an undefined mSwizzle means that is not a swizzle view
mSwizzle{} {
mDefaultBaseArrayLayer{ 0 } {
assert_invariant(
samples == 1 ||
samples == 4 &&
@@ -281,63 +355,21 @@ WebGPUTexture::WebGPUTexture(WebGPUTexture const* src, const uint8_t baseLevel,
mTexture{ src->mTexture },
mDefaultMipLevel{ baseLevel },
mDefaultBaseArrayLayer{ src->mArrayLayerCount },
mMsaaSidecarTexture{ src->mMsaaSidecarTexture },
mSwizzle{ src->mSwizzle } {
const bool swizzleUndefined{ (src->mSwizzle.r == wgpu::ComponentSwizzle::Undefined) &&
(src->mSwizzle.g == wgpu::ComponentSwizzle::Undefined) &&
(src->mSwizzle.b == wgpu::ComponentSwizzle::Undefined) &&
(src->mSwizzle.a == wgpu::ComponentSwizzle::Undefined) };
if (swizzleUndefined) {
mDefaultTextureView = makeTextureView(mDefaultMipLevel, levelCount, 0,
mDefaultBaseArrayLayer, src->getViewDimension());
} else {
wgpu::TextureComponentSwizzleDescriptor swizzleDesc{};
swizzleDesc.swizzle = mSwizzle;
const wgpu::TextureViewDescriptor viewDesc{
.nextInChain = &swizzleDesc,
.label = "swizzled_texture_view",
.format = mTexture.GetFormat(),
.dimension = src->getViewDimension(),
.baseMipLevel = mDefaultMipLevel,
.mipLevelCount = levelCount,
.baseArrayLayer = 0,
.arrayLayerCount = mDefaultBaseArrayLayer,
};
mDefaultTextureView = mTexture.CreateView(&viewDesc);
FILAMENT_CHECK_POSTCONDITION(mDefaultTextureView)
<< "Failed to create swizzled Texture view";
mDefaultTextureView{ makeTextureView(mDefaultMipLevel, levelCount, 0, mDefaultBaseArrayLayer,
toWebGPUTextureViewDimension(src->target)) },
mMsaaSidecarTexture{ src->mMsaaSidecarTexture } {}
wgpu::Texture const& WebGPUTexture::getMsaaSidecarTexture(const uint8_t sampleCount) const {
if (mMsaaSidecarTexture == nullptr) {
return mMsaaSidecarTexture; // nullptr (no such sidecar)
}
}
WebGPUTexture::WebGPUTexture(const WebGPUTexture* src,
const wgpu::TextureComponentSwizzle nextSwizzle) 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 },
mMsaaSidecarTexture{src->mMsaaSidecarTexture},
mSwizzle{ composeSwizzle(src->getSwizzle(), nextSwizzle) } {
wgpu::TextureComponentSwizzleDescriptor swizzleDesc{};
swizzleDesc.swizzle = mSwizzle;
const wgpu::TextureViewDescriptor viewDesc{
.nextInChain = &swizzleDesc,
.label = "swizzled_texture_view",
.format = mTexture.GetFormat(),
.dimension = src->getViewDimension(),
};
mDefaultTextureView = mTexture.CreateView(&viewDesc);
FILAMENT_CHECK_POSTCONDITION(mDefaultTextureView) << "Failed to create swizzled Texture view";
FILAMENT_CHECK_PRECONDITION(sampleCount == mMsaaSidecarTexture.GetSampleCount())
<< "The MSAA sidecar texture has a different sample count ("
<< mMsaaSidecarTexture.GetSampleCount() << ") than requested (" << +sampleCount
<< "). Note that this restriction was written when WebGPU only supported msaa "
"textures with 4 samples. If that has changed, this implementation should be "
"updated (e.g. map of sidecar textures by sampleCount or something).";
return mMsaaSidecarTexture;
}
bool WebGPUTexture::supportsMultipleMipLevelsViaStorageBinding(const wgpu::TextureFormat format) {
+10 -20
View File
@@ -34,25 +34,11 @@ 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 nextSwizzle the swizzle to apply to the source texture.
* Creates a new texture view by composing the nextSwizzle with the source which itself it
* could be a swizzled view
*/
WebGPUTexture(WebGPUTexture const* source, wgpu::TextureComponentSwizzle nextSwizzle) noexcept;
[[nodiscard]] wgpu::TextureAspect getAspect() const { return mAspect; }
[[nodiscard]] size_t getBlockWidth() const { return mBlockWidth; }
@@ -61,6 +47,8 @@ public:
[[nodiscard]] wgpu::Texture const& getTexture() const { return mTexture; }
[[nodiscard]] wgpu::Texture const& getMsaaSidecarTexture(uint8_t sampleCount) const;
[[nodiscard]] wgpu::TextureView const& getDefaultTextureView() const {
return mDefaultTextureView;
}
@@ -71,8 +59,6 @@ public:
[[nodiscard]] wgpu::TextureViewDimension getViewDimension() const { return mDimension; }
[[nodiscard]] wgpu::TextureFormat getViewFormat() const { return mViewFormat; }
[[nodiscard]] wgpu::TextureComponentSwizzle getSwizzle() const { return mSwizzle; }
[[nodiscard]] uint32_t getArrayLayerCount() const { return mArrayLayerCount; }
[[nodiscard]] MipmapGenerationStrategy getMipmapGenerationStrategy() const {
@@ -92,15 +78,21 @@ public:
/**
* @param samples The number of samples the underlying texture supports
* @param mipLevel The mip level into the underlying texture for which this view will reference
* @param mipLevel The mip level into the underyling texture for which this view will reference
* (this view will only have one mip level)
* @param arrayLayer The layer into the underlying texture for which this view will reference
* @param arrayLayer The layer into the underyling texture for which this view will reference
* (this view will only have one layer)
* @return A texture view for the MSAA sidecar texture
*/
wgpu::TextureView makeMsaaSidecarTextureViewIfTextureSidecarExists(uint8_t samples,
uint8_t mipLevel, uint32_t arrayLayer) const;
/**
* @return nullptr if a MSAA sidecar texture is not appliable, otherwise a view to one
*/
[[nodiscard]] wgpu::TextureView makeMsaaSidecarTextureView(wgpu::Texture const&,
uint8_t mipLevel, uint32_t arrayLayer) const;
/**
* @param format a required texture format (can be a view to a different underlying texture
* format)
@@ -137,8 +129,6 @@ private:
// by sampleCount or something like that.
// For now that complexity and cost is not warranted due to WebGPU's restrictions.
wgpu::Texture mMsaaSidecarTexture = nullptr;
// an undefined mSwizzle means that is not a swizzle view
wgpu::TextureComponentSwizzle mSwizzle{};
[[nodiscard]] wgpu::TextureView makeTextureView(const uint8_t& baseLevel,
const uint8_t& levelCount, const uint32_t& baseArrayLayer,
@@ -198,21 +198,81 @@ 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::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::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::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: 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::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::DXT1_RGB: return wgpu::TextureFormat::BC1RGBAUnorm;
case TextureFormat::DXT1_RGBA: return wgpu::TextureFormat::BC1RGBAUnorm;
case TextureFormat::DXT3_RGBA: return wgpu::TextureFormat::BC2RGBAUnorm;
@@ -221,20 +281,6 @@ 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;
}
}
@@ -295,20 +341,6 @@ 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:
@@ -502,188 +534,6 @@ 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;
}
/**
* "Updates" the previous swizzle parameters with the next/new parameters, essentially stacking
* swizzle parameters. Essentially this combines the parameters such that the result is
* the same mapping of channels you would get if you first applied the "previous" mapping
* followed by the "next" mapping.
*/
[[nodiscard]] constexpr wgpu::TextureComponentSwizzle composeSwizzle(
wgpu::TextureComponentSwizzle const& previous, wgpu::TextureComponentSwizzle const& next) {
// start with the "next" values...
wgpu::ComponentSwizzle result[] { next.r, next.g, next.b, next.a };
// track the current channel as we go through values to know what to assign in the undefined
// case...
wgpu::ComponentSwizzle channel{wgpu::ComponentSwizzle::R};
for (auto& out: result) {
switch (out) {
case wgpu::ComponentSwizzle::R:
if (previous.r != wgpu::ComponentSwizzle::Undefined) {
out = previous.r;
}
break;
case wgpu::ComponentSwizzle::G:
if (previous.g != wgpu::ComponentSwizzle::Undefined) {
out = previous.g;
}
break;
case wgpu::ComponentSwizzle::B:
if (previous.b != wgpu::ComponentSwizzle::Undefined) {
out = previous.b;
}
break;
case wgpu::ComponentSwizzle::A:
if (previous.a != wgpu::ComponentSwizzle::Undefined) {
out = previous.a;
}
break;
// Do not modify the (next) value for explicit One or Zero...
case wgpu::ComponentSwizzle::Zero:
case wgpu::ComponentSwizzle::One:
break;
case wgpu::ComponentSwizzle::Undefined:
// fallback to the previous value if the next value is undefined...
switch (channel) {
case wgpu::ComponentSwizzle::R:
out = previous.r;
break;
case wgpu::ComponentSwizzle::G:
out = previous.g;
break;
case wgpu::ComponentSwizzle::B:
out = previous.b;
break;
case wgpu::ComponentSwizzle::A:
out = previous.a;
break;
default:
// should not be possible...
out = wgpu::ComponentSwizzle::Undefined;
break;
}
break;
}
switch (channel) {
case wgpu::ComponentSwizzle::R:
channel = wgpu::ComponentSwizzle::G;
break;
case wgpu::ComponentSwizzle::G:
channel = wgpu::ComponentSwizzle::B;
break;
case wgpu::ComponentSwizzle::B:
channel = wgpu::ComponentSwizzle::A;
break;
default:
channel = wgpu::ComponentSwizzle::Undefined;
break;
}
}
return { .r = result[0], .g = result[1], .b = result[2], .a = result[3] };
}
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_WEBGPUTEXTUREHELPERS_H
@@ -67,11 +67,9 @@ constexpr std::array REQUIRED_FEATURES = {
};
constexpr std::array OPTIONAL_FEATURES = {
wgpu::FeatureName::CoreFeaturesAndLimits,
wgpu::FeatureName::DepthClipControl,
wgpu::FeatureName::Depth32FloatStencil8,
wgpu::FeatureName::TextureComponentSwizzle,
};
wgpu::FeatureName::CoreFeaturesAndLimits };
enum class LimitToValidate : uint8_t {
begin = 0,// needs to be first for iterating through all possible values in the enum
@@ -507,7 +505,6 @@ 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;
@@ -515,10 +512,6 @@ 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;
}
@@ -594,7 +587,7 @@ wgpu::Adapter WebGPUPlatform::requestAdapter(wgpu::Surface const& surface) {
}
const std::unordered_set<AdapterDetails, AdapterDetailsHash> compatibleAdapters =
requestCompatibleAdapters(mInstance, requests);
return selectPreferredAdapter(getConfiguration(), compatibleAdapters);
return selectPreferredAdapter(compatibleAdapters);
}
wgpu::Device WebGPUPlatform::requestDevice(wgpu::Adapter const& adapter) {
@@ -38,12 +38,7 @@ std::vector<wgpu::RequestAdapterOptions> WebGPUPlatform::getAdapterOptions() {
constexpr std::array powerPreferences = {
wgpu::PowerPreference::HighPerformance,
wgpu::PowerPreference::LowPower };
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 backendTypes = { wgpu::BackendType::Metal };
constexpr std::array forceFallbackAdapters = { false, true };
constexpr size_t totalCombinations =
powerPreferences.size() * backendTypes.size() * forceFallbackAdapters.size();
@@ -41,21 +41,36 @@
uint32_t height;
} wl;
}// namespace
#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
#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).
#include <dlfcn.h>
#include <X11/Xlib.h>
static constexpr const char* LIBRARY_X11 { "libX11.so.6" };
namespace {
#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*);
struct XEnv final {
X11_OPEN_DISPLAY openDisplay;
Display* display { nullptr };
void* library { nullptr };
} g_x11;
} // namespace
}// namespace
#endif
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
#else
#error Not a supported Linux or FeeBSD + WebGPU platform
#endif
@@ -101,26 +116,59 @@ 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) && defined (FILAMENT_SUPPORTS_XLIB)
#elif defined(LINUX_OR_FREEBSD) && defined(FILAMENT_SUPPORTS_X11)
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 (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);
#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
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 XLIB?)";
"(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?)";
#else
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
<< "Not a supported (Linux) OS + WebGPU platform";
@@ -128,40 +176,80 @@ 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 const surfaceDescriptor {
wgpu::SurfaceDescriptor 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) && defined(FILAMENT_SUPPORTS_XLIB)
#elif defined(LINUX_OR_FREEBSD) && defined(FILAMENT_SUPPORTS_X11)
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 (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);
#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
FILAMENT_CHECK_POSTCONDITION(surface != nullptr)
<< "Unable to create Linux (or FreeBSD) XLib-backed surface.";
<< "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?)";
#else
FILAMENT_CHECK_POSTCONDITION(surface != nullptr)
<< "Not a supported (Linux) OS + WebGPU platform";
@@ -1,45 +0,0 @@
/*
* 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
@@ -1,62 +0,0 @@
/*
* 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
+1 -1
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'|'webgpu'."
std::cerr << "Unrecognized target API. Must be 'opengl'|'vulkan'|'metal'."
<< std::endl
<< "Defaulting to OpenGL."
<< std::endl;
+7 -40
View File
@@ -85,18 +85,6 @@ 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.
@@ -140,6 +128,8 @@ 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
@@ -147,41 +137,18 @@ 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 += 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;
}
for (int i = 0; i < mResult.bytes().size(); ++i) {
if (std::abs( mResult.bytes()[i] - loadedImage.bytes()[i] ) >
mParams.pixelMatchThreshold()) {
pixelDeviations++;
}
}
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);
}
EXPECT_LE(pixelDeviations, mParams.allowedPixelDeviations());
// TODO: Add better debug output, such as generating a diff image.
#else
// For builds that can't load PNGs (currently iOS only) use the expected hash.
uint32_t actualHash = mResult.hash();
@@ -58,9 +58,6 @@ 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;
-2
View File
@@ -19,8 +19,6 @@ 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
-44
View File
@@ -1,44 +0,0 @@
/*
* 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
+28 -34
View File
@@ -144,7 +144,6 @@ 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(); });
@@ -161,18 +160,17 @@ 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 |
TextureUsage::BLIT_SRC));
Handle<HwTexture> const srcTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
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 | TextureUsage::BLIT_DST));
Handle<HwTexture> const dstTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
// Create a RenderTarget for each texture's miplevel.
Handle<HwRenderTarget> srcRenderTargets[kNumLevels];
@@ -208,7 +206,6 @@ 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(); });
@@ -225,18 +222,17 @@ 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 |
TextureUsage::BLIT_SRC));
Handle<HwTexture> const srcTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
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 | TextureUsage::BLIT_DST));
Handle<HwTexture> const dstTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
// Create a RenderTarget for each texture's miplevel.
Handle<HwRenderTarget> srcRenderTargets[kNumLevels];
@@ -264,7 +260,6 @@ 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();
@@ -288,12 +283,13 @@ 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 | TextureUsage::BLIT_SRC | TextureUsage::UPLOADABLE));
1,
TextureUsage::COLOR_ATTACHMENT));
// 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::BLIT_DST));
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
// Create a 4-sample render target with the 4-sample texture.
Handle<HwRenderTarget> const srcRenderTarget = mCleanup.add(api.createRenderTarget(
@@ -368,18 +364,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 |
TextureUsage::BLIT_SRC));
Handle<HwTexture> srcTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D_ARRAY, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth,
kSrcTexHeight, kSrcTexDepth,
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
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 | TextureUsage::BLIT_DST));
Handle<HwTexture> dstTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
// Create two RenderTargets.
const int level = 0;
@@ -433,18 +429,17 @@ 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 |
TextureUsage::BLIT_SRC));
Handle<HwTexture> srcTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
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 | TextureUsage::BLIT_DST));
Handle<HwTexture> dstTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
// Blit one-quarter of src level 1 to dst level 0.
Viewport srcRect = {
@@ -492,7 +487,6 @@ 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(); });
+5 -4
View File
@@ -22,7 +22,6 @@
#include "SharedShaders.h"
#include "Skip.h"
#include "TrianglePrimitive.h"
#include "Workarounds.h"
namespace test {
@@ -156,6 +155,8 @@ 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);
@@ -178,9 +179,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 TEXTURE_USAGE_READ_PIXELS));
auto colorTexture =
cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1,
screenWidth(), screenHeight(), 1, TextureUsage::COLOR_ATTACHMENT));
auto renderTarget = cleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR0, screenWidth(),
screenHeight(), 1, 0, { { colorTexture } }, {}, {}));
+2 -1
View File
@@ -104,6 +104,7 @@ 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);
@@ -126,7 +127,7 @@ TEST_F(BackendTest, FeedbackLoops) {
TrianglePrimitive const triangle(getDriverApi());
// Create a texture.
auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE;
auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE;
Handle<HwTexture> const texture = cleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kTexFormat, 1, kTexWidth, kTexHeight, 1, usage));
+7 -14
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(binding = 0, set = 0) uniform {samplerType} test_tex;
layout(location = 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(binding = 0, set = 0) uniform {samplerType} test_tex;
layout(location = 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(binding = 0, set = 0) uniform sampler2D test_tex;
layout(location = 0, set = 0) uniform sampler2D test_tex;
void main() {
vec2 fbsize = vec2({texSize});
@@ -231,7 +231,6 @@ 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.
@@ -272,18 +271,15 @@ 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);
@@ -291,12 +287,9 @@ 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.
// 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);
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)"
@@ -570,7 +563,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::COLOR_ATTACHMENT;
TextureUsage usage = TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE;
// Create a platform-specific SwapChain and make it current.
auto swapChain = cleanup.add(createSwapChain());
@@ -80,7 +80,6 @@ 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,
@@ -46,9 +46,8 @@ 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::WEBGPU, "External images aren't supported in WebGPU");
SKIP_IF(Backend::METAL, "External images aren't supported in metal");
SKIP_IF(Backend::VULKAN, "External images aren't supported in vulkan");
auto& api = getDriverApi();
Cleanup cleanup(api);
@@ -110,9 +109,8 @@ 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::WEBGPU, "External images aren't supported in WebGPU");
SKIP_IF(Backend::METAL, "External images aren't supported in metal");
SKIP_IF(Backend::VULKAN, "External images aren't supported in vulkan");
auto& api = getDriverApi();
Cleanup cleanup(api);
-1
View File
@@ -31,7 +31,6 @@ 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();
@@ -126,7 +126,6 @@ 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();
@@ -153,7 +152,6 @@ 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();
@@ -180,7 +178,6 @@ 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");

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